# 群れ — Style Reference
> 陽の当たるノートの紙の上を、二千羽の黒い鳥が流れる。天敵は橙の点ひとつ

**Theme:** light  **Series:** PARTS 036  **World:** 架空の野鳥観察ノート「Mure」のトップ。群れに先頭はいない、という事実を本物の群れの計算で見せる。

## Concept
クリーム色の紙一面に 2,560 羽(モバイル 1,024 羽)の鳥を飛ばす。1羽ごとに近くの数羽だけを見て、分離・整列・結合の3つの規則で速度を変える。計算は GPGPU で、全羽の位置と速度をテクスチャに持ち、毎フレーム総当たりで近傍を読む。カーソルは天敵で、近づくと群れが割れて避ける。見出しの周りには弱い反発場を置き、文字の上を飛びにくくする。色は黒い鳥と橙の点だけ。

## Signature Technique
WebGL2 + `EXT_color_buffer_float`。状態 `(x, y, vx, vy)` を RGBA32F の 64×40 テクスチャに持ち、ping-pong で更新、`gl_VertexID` で読んで POINTS で描く。

- **近傍探索**: 更新シェーダーで全テクセルを二重ループで読む(2,560² ≒ 655 万回/フレーム。GPU なら軽い)。半径 60px 内を近傍、その 38% 内を分離対象にする。
- **3規則**: 整列 `a += (normalize(Σv/n)·maxV − v)·0.9`、結合 `a += (Σp/n − p)·0.5·2`、分離 `a += Σ(−d/|d|·(1 − |d|/sr))·1.6·60`(合計。大きさ 1400 で頭打ち)。密集するほど強く効く。中心へ戻る弱い力 `(center − p)·0.12` も常に足す。
- **天敵**: カーソルから半径 170px(モバイル 120px)内で `a += d̂·(1 − l/R)·900`。
- **見出しの反発場**: 中心 (20% W, 50% H)、半径 min(30% W, 400px) で同じ形の力 320。モバイルは文字を下に置くので、円の代わりに画面下 30% の帯を使い、帯に入った鳥を `a.y += (1 − y/0.3H)·320·1.3` で上へ押し返す(`uA.x < 0` で切り替え)。モバイルはほかに分離 2.0、結合 0.35、壁 5。
- **画面端**: 余白 110px(モバイル 50px)に入ったら `a += (margin − p)·9` で内側へ。位置は −20〜W+20 に clamp。
- **気まぐれ**: 鳥の番号と時刻の hash で毎フレーム向きを決めた 18px/s² を足し、整列しすぎて一塊にならないようにする。
- **速度**: 70〜150px/s に制限。`p += v·dt`、dt は 0〜33ms に丸める(rAF の時刻は start() 直前の now より前になることがあり、負の dt を防ぐ)。開始前に 120 ステップ回して群れができた状態から見せる。
- **描画**: 点サイズ 9px×2.2×dpr×z。z は個体番号のハッシュから作る 0.55〜1.0 の疑似奥行きで、大きさと不透明度 `0.35 + 0.6·z²` に掛ける。全羽が同じ大きさだと分離の釣り合いで格子状に並んで見えるため、奥行きの差で崩す。フラグメントで速度方向に回転した座標 r を作り、後ろに開いた V 字(外側の三角から内側の三角を抜く)を描く。色 (0.02, 0.02, 0.06)。天敵は `uMode = 1` の1点で、14px×dpr の橙の丸(ポインタがあるときだけ描く)。
- CSS の `.hero::before` に見出しの周りだけクリームの楕円グラデーションを重ねる(`ellipse 34% 40% at 18% 50%`、不透明度 0.85→0。モバイルは `ellipse 80% 36% at 50% 86%`、0.92)。

```glsl
for(int y=0;y<uN.y;y++)for(int x=0;x<uN.x;x++){ vec4 q=texelFetch(uS,ivec2(x,y),0); vec2 d=q.xy-p; float d2=dot(d,d);
  if(d2<r2){ n+=1.; ali+=q.zw; coh+=q.xy; if(d2<sr*sr){ float dl=sqrt(d2); sep-=d/dl*(1.-dl/sr); ns+=1.; } } }
a+=(normalize(ali/n)*uMaxV-v)*uAli + (coh/n-p)*uCoh*2. + min(sep*uSep*60., 1400.) + (uRes*.5-p)*.12;
```

## Color Palette
| Token | HEX | Role |
|---|---|---|
| --cream | #fffefb | 紙。canvas のクリア色も同じ |
| --ink | #010110 | 見出しとノート名とクレジット。鳥は (0.02, 0.02, 0.06) |
| --fog | #73737c | 本文 |
| --drift | #b7b6b3 | ヘッダー補助と規則の数値と操作説明 |
| --ember | #ff4f00 | 天敵の点と操作説明の点。画面で唯一の色 |

## Typography
- 日本語見出し: Noto Serif JP 600、`clamp(30px, 3.8vw, 56px)`、line-height 1.3、letter-spacing −0.01em、`word-break: keep-all`、`<br>` で2行(モバイルは normal、26px から)。
- 本文: Noto Sans JP 300、`clamp(13px, 1.05vw, 15px)`、最大幅 26em。761px以上では1文目の後で改行する `br.d` を表示し、語の途中で行を割らない。
- ラベル: JetBrains Mono 400、11–12px、letter-spacing 0.06em。

## Spacing & Layout
- hero `min-height: 100svh`、3行グリッド。canvas は背面全面、その上に `.hero::before` の楕円グラデーション。
- 見出しは中央行の左、垂直中央。規則の数値は左下、クレジットは右下。
- 余白: 横 `clamp(20px, 5vw, 72px)`、縦 `clamp(20px, 3vw, 40px)`。モバイル左右 18px。
- breakpoint 760px: ヘッダー右と規則の数値を消し、フッターを縦積み。

## Motion
- 鳥は 70〜150px/s。群れは画面端で折り返し、数十秒周期で形を変え続ける。
- カーソル(天敵)に 170px(モバイル 120px)まで近づくと群れが割れる。離れると数秒で戻る。
- rAF 1本。IntersectionObserver と visibilitychange で画面外停止。dt の上限 33ms で、タブ復帰時に飛び散らない。
- `prefers-reduced-motion: reduce`: 開始前の 120 ステップだけ計算して1フレームを描き、以後は更新しない。カーソル移動で天敵の点だけ描き直す。

## Components
- hero: 背面 canvas(`aria-hidden`)+クリームの楕円+前面テキスト。WebGL2 か float FBO が無いときは `.fallback` に「群れ」を表示。
- header: 左にノート名、右に技法名。
- copy: 見出し2行+本文2文。
- rules: 左下に羽数と近傍半径と3規則の重み。
- footer tag: `VALE / PARTS 036 / 群れ` と橙の点+操作説明1行(タッチ端末では差し替え)。

## Do / Don't
- Do: 鳥は小さく(10px 以下)、数で群れを見せる。
- Do: 文字の周りには反発場を置く。鳥が文字に重なると読めない。
- Do: 気まぐれの力を少し残す。0 にすると全羽が1本の線になる。
- Don't: 鳥に色を付けない。黒1色と天敵の橙だけ。
- Don't: 画面端でワープさせない。折り返す方が群れに見える。
- Don't: 羽数を 1 万以上にしない。総当たりが二乗で重くなる(必要なら空間ハッシュに変える)。

## References
- Zapier: 陽の当たるクリーム色の紙と、橙を1点だけ使う構成を借りた。イラストの代わりに計算した群れを置いた。
- Privy: 白い紙に黒のセリフを詰めて組む見出しの型を借りた。紫のアクセントは使わず、色は橙の点に絞った。

## Usage
1. `<canvas id="gl">` を hero の背面に置き、`<script>` ブロック全体を移植する。WebGL2 と `EXT_color_buffer_float` が必要。
2. 羽数は `P.nx × P.ny`。群れの密度は `P.radius` と `P.coh`、割れやすさは `P.sep`。
3. 文字の位置に合わせて `step()` 内の `av`(`[中心x, 中心y, 半径]`。y は下端原点。x を負にすると下端の帯モード)と `.hero::before` の楕円を動かす。
4. 色は `FS_PT` の `o = vec4(.02,.02,.06, …)`(鳥)と `vec4(1.,.31,0., …)`(天敵)、`clearColor`(紙)。
5. 天敵が不要なら `pointermove` のリスナーを消す(`pred` は画面外のまま)。

| パラメータ | 既定 | 意味 |
|---|---|---|
| nx × ny | 64×40 / 32×32(モバイル) | 羽数 |
| radius | 60 / 44(モバイル) | 近傍半径(px) |
| sepFrac | 0.38 | 分離が効く距離(radius 比) |
| sep / ali / coh | 1.6 / 0.9 / 0.5(モバイル 2.0 / 0.9 / 0.35) | 3規則の重み |
| minV / maxV | 70 / 150 | 速度の範囲(px/s) |
| predR / pred | 170(モバイル 120) / 900 | 天敵の半径(px)と力 |
| avoid | 320 | 見出し周りの反発力(PCは見出し中心の円、スマホは画面下30%の帯から上へ押し返す) |
| margin / wall | 110 / 9(モバイル 50 / 5) | 画面端の余白(px)と戻す強さ |
| size | 9 / 7(モバイル) | 鳥の大きさ(px) |
| dprMax | 2 | 表示解像度の上限 |
