헤더 배경 수정 + three.js 를 두 곳으로

헤더에 배경이 왜 있냐는 지적에서 시작했다.
site-header 자체는 배경이 없었고, 안쪽 유리가 불투명 판으로
폴백된 것이었다. prefers-reduced-transparency 가 참이면
(윈도우 투명 효과 끄기 등) --surface-raised 단색이 깔린다.

투명도를 줄여달라는 요청은 "불투명한 판을 깔아라"가 아니다.
배경에 가까운 톤으로 낮추고 형태는 테두리와 그림자가 유지한다.
떠 있는 헤더는 더 가깝게 맞췄다.

three.js
- 공용 부트스트랩을 src/lib/shader-plane.ts 로 추출했다.
  references/three.md 가 권하는 구조를 그대로 구현한 것이다.
  지연 로드 · DPR 클램프 1.5 · 화면 밖이면 정지 · 실패 시 CSS 폴백
- 히어로: 광원 두 개가 흐르고 하나가 포인터를 느리게 따라온다
- 재료: 좌표를 노이즈로 밀어 유리 너머의 결을 만든다
- 두 셰이더가 같은 three 청크를 공유한다. 네트워크 요청 1건

--glow-2 를 토큰으로 승격해 셰이더의 하드코딩 색을 없앴다.
This commit is contained in:
Yun Chan 2026-08-20 11:52:45 +09:00
parent be343cdab5
commit ed9463c3c2
5 changed files with 247 additions and 114 deletions

View file

@ -16,6 +16,8 @@ const { c } = Astro.props;
<div class="full stage glass">
<div class="stage-fallback" aria-hidden="true"></div>
{/* GPU 가 만드는 재질. WebGL 이 없으면 위 그라디언트가 그대로 결과물이다 */}
<canvas id="mat-canvas" class="stage-canvas" aria-hidden="true"></canvas>
<ul class="tiles">
{
@ -87,11 +89,12 @@ const { c } = Astro.props;
max-width: 1180px;
width: 100%;
margin-inline: auto;
padding: var(--space-6) var(--space-4);
padding: var(--space-5) var(--space-4);
border-radius: var(--radius-shell);
overflow: hidden;
}
.stage-fallback {
.stage-fallback,
.stage-canvas {
position: absolute;
inset: 0;
z-index: -1;
@ -106,6 +109,9 @@ const { c } = Astro.props;
var(--surface-raised);
}
.stage-canvas { opacity: 0; transition: opacity var(--dur-slow) var(--ease-soft); }
.stage-canvas.is-live { opacity: 1; }
/* 3개 항목에 3개 셀. 빈 칸이 생기지 않고 균등 3열도 아니다 */
.tiles {
list-style: none;
@ -122,8 +128,8 @@ const { c } = Astro.props;
.tile:nth-child(2) { grid-area: top; }
.tile:nth-child(3) { grid-area: bottom; }
/* 큰 셀은 더 넓은 화면을 갖는다 */
.tile:nth-child(1) .swatch { aspect-ratio: 5 / 4; }
.tile:nth-child(n + 2) .swatch { aspect-ratio: 16 / 7; }
.tile:nth-child(1) .swatch { aspect-ratio: 16 / 11; }
.tile:nth-child(n + 2) .swatch { aspect-ratio: 16 / 6; }
.tile { display: grid; gap: var(--space-2); align-content: start; }
.tile .shell { box-shadow: var(--glass-hi), var(--shadow-2); }
.tile .core { overflow: hidden; }
@ -156,3 +162,47 @@ const { c } = Astro.props;
.tile:nth-child(n + 2) .swatch { aspect-ratio: 5 / 4; }
}
</style>
<script>
// 무대 배경. 유리 표면을 지나는 빛의 굴절을 흉내낸다.
// 히어로와 같은 모듈, 같은 three 청크를 쓴다. 새로 받는 것은 없다.
import { mountShaderPlane, GLSL_NOISE } from "../lib/shader-plane";
const canvas = document.getElementById("mat-canvas") as HTMLCanvasElement | null;
if (canvas) {
mountShaderPlane({
canvas,
speed: 0.7,
colors: { uA: "--accent", uB: "--glow-2" },
fragment: `
precision mediump float;
varying vec2 vUv;
uniform float uTime;
uniform float uAspect;
uniform vec3 uA;
uniform vec3 uB;
${GLSL_NOISE}
void main() {
vec2 p = vUv;
p.x *= uAspect;
float t = uTime * 0.06;
// 좌표 자체를 노이즈로 밀어 굴절처럼 보이게 한다
vec2 warp = vec2(fbm(p * 2.2 + t), fbm(p * 2.2 - t + 4.7));
vec2 q = p + (warp - 0.5) * 0.42;
// 밀린 좌표 위에 얇은 띠를 얹는다. 유리 너머의 결이다
float band = sin((q.x + q.y) * 7.0 + uTime * 0.35) * 0.5 + 0.5;
band = smoothstep(0.35, 0.95, band);
float depth = smoothstep(0.1, 1.0, fbm(q * 1.6 - t * 0.5));
vec3 col = mix(uB, uA, depth);
float alpha = (0.16 + band * 0.22) * (0.55 + depth * 0.45);
gl_FragColor = vec4(col, alpha);
}
`,
});
}
</script>