# 光の中の埃 — Style Reference
> 暗い店内に斜めの朝日が1本入り、その中だけ埃が舞っている

**Theme:** dark  **Series:** PARTS 039  **World:** 架空のコーヒースタンド「Kiri」の開店前。窓から入る光の帯と、その中に見える埃だけで、朝の時間を伝える。

## Concept
画面はほぼ黒。右上から左下へ、暖かいクリーム色の光の帯が1本通る。埃の粒子は部屋全体に 16,384 個あるが、帯の中に入った粒子だけが見える。手前の粒子は大きくぼけ、奥の粒子は小さく鋭い。粒子は GPU 上のカールノイズでゆっくり漂い、少しずつ沈む。カーソルを動かすと、その周りの空気が動いて埃が流れる。焦点は光の帯1本。

## Signature Technique
GPGPU 粒子 + 画面空間の帯マスク。

1. **位置テクスチャ**: 一辺 `side`(128、モバイル 96)の RGBA float。`xy`=位置(画面高さ=1、幅=aspect)、`z`=奥行き(0 手前〜1 奥)、`w`=seed。WebGL2 は `RGBA32F`(不可なら `RGBA16F`)、WebGL1 は `OES_texture_float`。
2. **移動**(`SIM_FS`):
   ```glsl
   vec3 v = curl(p*scale + vec3(0,0,t*evo)) * drift;   // 3D カールノイズ、drift 0.018(画面高さ/秒)
   v.y += settle;                                       // 沈降 0.004
   v.xy += cursorVel * smoothstep(R, 0, |p.xy - cursor|) * stir * (1 - 0.6 z);   // 手前ほど強く動く
   p += v*dt;  下端を抜けたら上端の乱数位置へ、左右は折り返し
   ```
3. **帯マスク**(`beam()`、頂点シェーダーと背景シェーダーで共用): 原点 `o`(上辺の上)から方向 `d=(sin θ, cos θ)` に伸びる帯。中心線からの距離 `n` と、原点からの距離 `t` で
   `side = 1 − smoothstep(0.55w, w, n)`(w は t とともに 35% 広がる)、`along = smoothstep(−0.1, 0.25, t)·(1 − smoothstep(0.55L, L, t))`。
4. **粒子**: `alpha = (beam·0.7 + 0.015) · twinkle · mix(1, 0.45, near)`。`twinkle = 0.75 + 0.25 sin(t·(1.5+2·fract(seed)) + 40·seed)`(seed は 0〜100 の乱数。速さには小数部だけを使い、明滅の周期は粒ごとに約 1.8〜4.2 秒に収まる)。`gl_PointSize = size·(0.6 + 1.6·near)`、フラグメントの縁は手前ほど柔らかい(`edge = mix(0.5, 0.25, near)`)。`ONE / ONE` の加算。
5. **背景**: `bg·vignette + light·beam·(0.13 + 0.09·(1 − smoothstep(0, 0.9, t)))`。窓に近いほど明るい。
6. IntersectionObserver と visibilitychange で画面外停止。リサイズは canvas のみ。

## Color Palette
| Token | HEX | Role |
|---|---|---|
| --room | #141210 | 地。背景シェーダーの基調色も同じ(ビネットで周辺を 35% 落とす) |
| --cream | #fff4d6 | 光の帯、埃、見出し、ブランド名、クレジット |
| --mute | #b9b0a1 | 本文 |
| --ash | #6e685f | ヘッダーのラベル、操作説明 |
| --brick | #e82c2a | ヘッダーの区切り点のみ。面や粒子には使わない |

## Typography
- 日本語見出し: Noto Sans JP 400、`clamp(20px, 1.8vw, 26px)`、letter-spacing −0.01em、line-height 1.45。
- 本文: Noto Sans JP 300、`clamp(13px, 1.05vw, 15px)`、最大幅 30em。粒子数は JS で実測。
- ラベル: JetBrains Mono 400、11–12px、letter-spacing 0.06–0.08em。
- WebGL 不可時は `.fallback` にブランド名を Noto Sans JP 300 の大きな字で出す。

## Spacing & Layout
- hero `min-height: 100svh`、3行グリッド。canvas は背面全面。
- 余白: 横 `clamp(20px, 5vw, 72px)`、上 `clamp(20px, 3vw, 40px)`、下 `clamp(18px, 2.6vw, 32px)`。モバイル左右 18px。
- 帯は原点 `x = 0.74·aspect`、`y = −0.3`、θ = −0.5 rad(左下へ)、半幅 0.2。縦長画面では原点 `1.25·aspect`、θ = −0.2、半幅 0.1 にして、左下のテキストに掛からないようにする。
- breakpoint 760px: ブランド横の説明を消し、フッターを縦積み。

## Motion
- 漂い 0.018(画面高さ/秒)、ノイズの時間変化 0.05、沈降 0.004。1粒が画面を横切るのに 1 分前後。
- 明滅は粒子ごとに角速度 `1.5 + 2·seed`(seed 0〜100)。遅い粒は周期 4 秒前後、速い粒は細かくちらつく。振幅は 0.75〜1.0 なので消えはしない。
- カーソル: 速度に比例して半径 0.16 の空気を動かす(0.35)。離すと 0.85/フレームで減衰。
- `prefers-reduced-motion: reduce`: rAF を回さず、60 ステップ進めて1枚描く。帯と埃の静止画になる。

## Components
- hero: 背面 canvas(`aria-hidden`)+前面テキスト。テキスト層は `pointer-events: none`。
- header: 左にブランド名、右に `open 7:00 · 16,384 particles`。区切り点だけ赤。
- copy: 見出し1行+本文2文。
- footer tag: `VALE / PARTS 039 / 光の中の埃` と操作説明1行(タッチ端末では文言差し替え)。

## Do / Don't
- Do: 帯の外にも粒子を置き、alpha 0.015 でほぼ見えない状態にする。帯の縁で粒子が「現れる」ように見える。
- Do: 奥行きで粒子径と縁の柔らかさを変える。これが無いと紙吹雪に見える。
- Do: 帯は1本。2本以上あると舞台照明になる。
- Don't: 粒子を速くしない(drift 0.05 以上)。雪になる。
- Don't: 帯を白にしない。クリーム(#fff4d6)で朝日になる。
- Don't: 帯の中にテキストを置かない。読めなくなる。

## References
- Redbrick Coffee: 白と黒だけの紙面に赤を1点だけ置く構造を借りた。紙面を暗い店内に反転し、赤はヘッダーの区切り点に残した。
- Zora: 暗いガラスの面に光る要素を1つだけ置く考え方を借りた。光る要素を光の帯と埃に置き換えた。

## Usage
1. `<main id="hero">` の中に `<canvas id="gl">` を置く(canvas は `position:absolute; inset:0`)。スクリプトは `#hero` の大きさで解像度を決め、`#hero` で pointerleave とタッチを受ける。フォントは任意。
2. `<script>` ブロック全体を移植する。`boot()` が粒子テクスチャを作って描き始める。
3. 帯の位置は `P.beam` と `beamUniform()` で決める。テキストの位置に合わせて原点と角度を変える。
4. 色は `ink`(帯と粒子)/ `bg` を CSS 変数と揃える。
5. 粒子数の読み出しが不要なら `#n` の行を消す。

| パラメータ | 既定 | 意味 |
|---|---|---|
| side | 128 / 96(モバイル) | 粒子テクスチャの一辺。粒子数は side² |
| scale / evo / drift | 2.6 / 0.05 / 0.018 | ノイズの空間周波数と時間変化と流速 |
| settle | 0.004 | 沈降速度 |
| stir / stirR | 0.35 / 0.16 | カーソルで空気を動かす強さと半径 |
| beam.x, y | 0 / −0.3 | 帯の原点(`beamUniform()` で aspect 比に補正) |
| beam.ang / w / len | −0.5 / 0.2 / 1.7 | 傾き(rad)と半幅と長さ |
| size | 2.8 / 2.4(モバイル) | 粒子径(px、dpr 倍)。手前は 2.2 倍まで |
| dprMax | 2 | 表示解像度の上限 |

帯を動かしたい場合は `beamUniform()` の θ を時間でゆっくり変える(1 時間で 0.1 rad 程度)。
