# 振り子 — Style Reference
> 紙色の机の上のニュートンのゆりかご。端の球だけが芥子色

**Theme:** light  **Series:** PARTS 047  **World:** 架空の机上小物ブランド「Furiko」のトップ。製品のゆりかごを1つ置き、衝突で運動量が端から端へ渡る様子をそのまま見せる。

## Concept
画面中央に梁から吊られた 5 つの球。起動時に左端の球が持ち上がった状態から落ち、中央の 3 球は動かずに右端の球だけが跳ね上がる。以後は往復しながら空気抵抗で止まる。球をつかんで引き、離すと同じことが起きる。両端の球だけ芥子色、中央は黒。右上に反発係数 e と周期の読み出し。文字は左下に見出し 1 本(2 行)と本文 2 文。

## Signature Technique
剛体振り子の運動方程式 + 等質量 1 次元衝突。2D canvas で描く。

1. **状態**: 各球の角度 θ と角速度 ω。糸の長さ `L = min(使える高さ − 1.3R, 11R, (x0 − R − 12)/sin(startAngle))` px(x0 は左端の支点)、`lengthM = 0.65 m` として `pxPerM = L / 0.65`。重力 9.81 m/s² を px に換算。
2. **積分**(1 フレームに 8 回、半陰的 Euler):
   ```js
   ω += (−(g/L)·sin θ − airDamp·ω)·dt;  θ += ω·dt;        // airDamp 0.06 /s
   ```
3. **衝突**: 隣同士の球の距離 `d < 2R` で、`ω_a − ω_b > 0`(近づいている)なら、糸の長さと支点の高さが同じことを使って角速度を 1 次元の等質量衝突として交換する。
   ```js
   ωa' = (ωa+ωb)/2 − e·(ωa−ωb)/2;  ωb' = (ωa+ωb)/2 + e·(ωa−ωb)/2;   // e = 0.98
   ```
   つかんでいる球は質量無限大として相手だけ反射。重なりは角度 `(2R − d)/L` で両側に半分ずつ解消。1 サブステップで球数ぶん繰り返し、運動量が鎖を一気に通る。
4. **静止時の隙間**: 球の間隔 `2R·(1 + 0.02)`。静止中に触れ合わず、浮動小数の誤差で衝突が起きない。
5. **描画**: 梁(黒 3px)、両端の柱(灰 2px)、糸(灰 1px)、支点の点、球は放射状グラデーション(端: #f3cf6a → #d9a62e → #8c6612、中央: #6a6a6a → #2e2e2e → #0d0d0d)に白いハイライトの楕円。床には高さに応じて薄くなる楕円の影(`blur(8px)`)。
6. **停止**: 全球の `|ω| + |θ|` の和が 0.004 未満の状態が 60 フレーム続いたら rAF を止める。つかむと再開。IntersectionObserver と visibilitychange で画面外停止。

## Color Palette
| Token | HEX | Role |
|---|---|---|
| --paper | #faf9f7 | 地 |
| --linen | #edecea | 予備の面(未使用) |
| --felt | #d3d3d3 | 両端の柱 |
| --slate | #727272 | 糸と本文とラベルと操作説明 |
| --ink | #242424 | 梁、支点、中央の球、見出し、クレジット |
| --mustard | #d9a62e | 両端の球、読み出しの e の値 |

## Typography
- ブランド名: Inter 600、16px、letter-spacing −0.02em。
- 見出し: Noto Sans JP 500、`clamp(26px, 2.6vw, 40px)`、letter-spacing −0.01em、line-height 1.2。
- 本文: Noto Sans JP 300、`clamp(13px, 1.05vw, 15px)`、最大幅 30em。
- ラベル: JetBrains Mono 400、11–12px、letter-spacing 0.06–0.08em。e と周期は P から計算して書き込む。
- canvas 不可時は `.fallback` の黒い円 5 つを CSS で表示。

## Spacing & Layout
- hero `min-height: 100svh`、3行グリッド。canvas は背面全面(`touch-action: none`、`cursor: grab`)。
- 余白: 横 `clamp(20px, 5vw, 72px)`、上 `clamp(20px, 3vw, 40px)`、下 `clamp(18px, 2.6vw, 32px)`。モバイル左右 18px。
- 梁は `y = 96px`(モバイル 90px)。球の半径は `min(使える幅 / (2n·1.02 + 2), 42px)`(モバイル上限 24px)。使える幅は `W − 160px`(モバイル `W − 36px`)。中心は `x = 0.56W`(モバイル 0.5W)。糸の長さは、端の球を引いたときに画面の左端から 12px 内側に収まる長さを上限にする(モバイルではこの上限が効き、余った高さの半分だけ梁を下げる)。
- 文字はゆりかごの下。使える高さは `H − 260px`(モバイル `H − 300px`)。
- breakpoint 760px: ブランド横の説明を消し、フッターを縦積み。

## Motion
- 起動: 左端の球を −0.55 rad(モバイル −0.28)に置いて落とす。周期 1.6 秒。約 20 秒で止まる。
- つかむ: 球の中心から 1.8R 以内で。角度は ±1.4 rad、隣の球を越えない範囲。離すときの角速度は直前の動きから推定(±6 rad/s に制限)。
- `prefers-reduced-motion: reduce`: 左端の球を持ち上げた状態で 1 枚だけ描き、自分からは動かない。球をつかむと通常どおり動き、止まれば rAF も止まる。

## Components
- hero: 背面 canvas + 前面テキスト。テキスト層は `pointer-events: none`。
- header: 左にブランド名、右に `e 0.98 · period 1.6 s`。
- copy: 見出し(2 つの句をそれぞれ `inline-block` で包み、句の切れ目で 2 行に折る)+本文 2 文。最大幅 30em。
- footer tag: `VALE / PARTS 047 / 振り子` と操作説明 1 行(タッチ端末では文言差し替え)。

## Do / Don't
- Do: 静止時に球を触れ合わせない。わずかな隙間が無いと誤差で勝手に動き出す。
- Do: 衝突は 1 サブステップ内で球数ぶん繰り返す。1 回だと運動量が 1 球ずつ遅れて伝わり、全体が波打つ。
- Do: 色は両端だけ。5 球とも同色だと「端だけ跳ねる」ことが伝わらない。
- Don't: 反発係数を 1 にしない。永久に止まらず、机上の物に見えない。
- Don't: 糸を太くしない(1px)。球の重さが消える。
- Don't: 球に影を落とさない(球自体の影)。床の影だけで十分。

## References
- Kalstore: 暖かい紙色の地に黒い文字、芥子色を 1 点だけという配色を借りた。芥子色を両端の球に置いた。
- Attio: 白い面に太い Inter の見出しと細い本文、建築的な間隔の取り方を借りた。写真の代わりに物理の小物を置いた。

## Usage
1. `<canvas id="c">` を hero の背面に置く。フォントは任意。
2. `<script>` ブロック全体を移植する。`document.fonts.ready` 後に `boot()` が走る。
3. 球数は `P.n`。奇数でも偶数でもよい。色は `draw()` 内のグラデーションを差し替える。
4. 梁の高さは `resize()` の `barY`。文字の位置に合わせて使える高さ(`H − 260`)を変える。
5. 1 球だけの振り子にする場合は `P.n = 1` にし、衝突のループはそのまま残してよい(何も起きない)。

| パラメータ | 既定 | 意味 |
|---|---|---|
| n | 5 | 球の数 |
| gravity | 9.81 | 重力(m/s²) |
| lengthM | 0.65 | 振り子の長さ(m)。周期 2π√(L/g) |
| e | 0.98 | 反発係数 |
| airDamp | 0.06 | 空気抵抗(1/s) |
| startAngle | 0.55 / 0.28(モバイル) | 起動時に端の球を引く角度(rad) |
| gap | 0.02 | 静止時の隙間(半径比) |
| sub | 8 | 1 フレームの積分回数 |

2 球同時に引きたい場合は `startAngle` を `th[0]` と `th[1]` の両方に入れる。反対側も 2 球跳ねる。
