# 跳ねる文字 — Style Reference
> 白いカタログの紙。見出しの文字が上から落ちてきて、黒い床の線で跳ね、傾いて着地してから起き上がる

**Theme:** light  **Series:** PARTS 050  **World:** 架空のスニーカーブランド「Hop」のトップ。軽いから何度でも跳ねる、という商品の性格を見出しの物理で示す。

## Concept
見出し「Hop in.」を1文字ずつ剛体にする。各文字は少しずつ遅れて上から落ち、床の線で反発係数 0.58 で跳ね、着地のたびに傾き、床に触れているあいだに起き上がる。すべて静止すると数秒後にまた落ちる。クリックでいつでも落とし直せる。色は白と黒と、ピリオドとボタンのコバルトだけ。

## Signature Technique
DOM の `<span>` を1文字ごとに `translate3d + rotate` で動かす簡易剛体。ライブラリなし。

- **状態**: 文字ごとに `y`(床からの高さ、下が正)、`vy`、`a`(角度)、`va`(角速度)、`delay`(落下開始 = i × 0.14 秒)、`rest`。
- **落下**: `vy += 1500·dt`、`y += vy·dt`、`a += va·dt`。空中では `va` を毎秒 40% 減衰。初期高さは見出しの 1.9〜2.5 倍上。
- **床**: `y ≥ 0` で `y = 0`。`vy > 60` なら `vy = −vy·0.58`、`va = 0.5·va + 乱数 − 3·a`(傾いて落ちた側へ回る)、跳ね回数を加算。遅ければ `vy = 0`。
- **起き上がり**: 床に触れているあいだ `va += (−a·28 − va·6)·dt`。角度と角速度が小さくなったら `rest`。
- **傾きの上限**: `|a| > maxTilt(0.32 rad)` なら `a` を上限に止め、`va *= −0.3`。大きな文字で隣に深く食い込まない。
- **字形の足元**: 読み込み時と落とし直しのたびに、canvas の `measureText` で各文字の `fontBoundingBoxAscent / Descent` と `actualBoundingBoxDescent` を取り、行ボックスの下端から字形の一番下までの差 `base = (fs − A + D)/2 − desc` を出す。描画で `base` だけ下げ、`transform-origin` を字形の一番下に置く。H も o も p の棒も床の線にぴたりと着き、p の丸は浮く(剛体の文字としての置き方)。
- **文字同士の衝突**: 各文字の字形の箱(`measureText` の `actualBoundingBoxLeft/Right/Ascent/Descent`、支点=字形の底の中央からの相対)を角度 `a` で回し、4隅から画面上の AABB を出す。隣り合う字(空白は除く)の AABB が横に `gap(4px)` 未満まで近づき、縦の範囲も重なっていたら、重なり量の半分ずつ左右へ `x` をずらす(1フレーム3回反復)。`x` は毎フレーム `1 − exp(−5·dt)` で 0 へ戻るので、離れれば元の字間に戻る。
- **描画**: `transform: translate3d(x, y − lift + base, 0) rotate(a)`。`lift = 幅/2 · |sin a|` で、傾いた文字は底の角で床に立つ(床にめり込まない)。順番待ちの文字にも毎フレーム同じ式で上空の位置を当てる(床に残って見えないように)。
- **周期**: 全文字が静止してから 4.5 秒で再落下(7 秒周期)。読み込み時は 0.35 秒ぶん進めて空中の状態から見せる。
- 床は `.floor` の 2px の黒い線。見出しの行ボックスの下端に重ねる。

```js
l.vy += g*dt; l.y += l.vy*dt; l.a += l.va*dt;
if (l.y >= 0) { l.y = 0;
  if (l.vy > 60) { l.vy = -l.vy*e; l.va = l.va*.5 + (Math.random()-.5)*spin*.8 - l.a*3; }
  else { l.vy = 0; l.rest = Math.abs(l.a) < .02 && Math.abs(l.va) < .1; }
  l.va += (-l.a*upright - l.va*6)*dt; l.a += l.va*dt; }
```

## Color Palette
| Token | HEX | Role |
|---|---|---|
| --paper | #ffffff | 紙 |
| --ink | #000000 | 文字と床の線と見出し |
| --cobalt | #1a5fff | ピリオドとボタン。画面で唯一の色 |
| --stone | #6c6c6c | 本文 |
| --silver | #c8c7cd | ヘッダー補助とメタ情報と操作説明 |
| --mist | #ecf2ff | 予備の淡い面 |

## Typography
- 見出し: Outfit 700、`clamp(64px, 20vw, 300px)`(画面幅の約 2/3 を占める。モバイルは別指定)、letter-spacing −0.005em、1文字ずつ `<span>`。
- 日本語見出し: Noto Sans JP 500、`clamp(17px, 1.5vw, 21px)`。本文 400、`clamp(13px, 1.05vw, 15px)`。本文の2文節目を `inline-block` の span で包み、「起/き上がる」のように語の途中で折り返さない。
- ボタン: Noto Sans JP 500 14px、コバルトの面、角丸 999px。
- ラベル: JetBrains Mono 400、11–12px。

## Spacing & Layout
- hero `min-height: 100svh`、3行グリッド。見出しは中央行の下端に置き、その直下に床の線、下にコピーとボタン。
- 見出しの行ボックス高さ 1.1em、`align-items: flex-end`。
- 余白: 横 `clamp(20px, 5vw, 72px)`、縦 `clamp(20px, 3vw, 40px)`。モバイル左右 18px。
- breakpoint 760px: ヘッダー右とメタ情報を消し、コピーを縦積み。

## Motion
- 落下 0.7 秒、跳ね返り 3〜4 回、全体で約 3 秒で静止。文字ごとに 0.14 秒ずつ遅れる。
- 静止後 4.5 秒で再落下。クリック/タップ(ボタン、または hero のどこでも)で即落下。
- rAF 1本。IntersectionObserver と visibilitychange で画面外停止。静止したら止まる。
- `prefers-reduced-motion: reduce`: 落下を計算し切った静止状態で表示し、自動の再落下はしない。

## Components
- hero: 見出し+床の線+コピー+ボタン。
- header: 左にブランド名、右に技法名。
- button: 「もう一度落とす」。`focus-visible` に黒い 2px アウトライン。
- meta: 左下に重力と反発係数(どちらも実装の P の値)、跳ねた回数。実装で使っていない値は表示しない。
- footer tag: `VALE / PARTS 050 / 跳ねる文字` と操作説明1行(タッチ端末では差し替え)。

## Do / Don't
- Do: 床は線1本で示す。面を塗ると帯に見える。
- Do: 着地後は必ず起き上がらせる。傾いたままだと読めない。
- Do: 文字数は 10 文字以下。多いと落下が終わるまで長い。
- Don't: 反発係数を 0.7 以上にしない。いつまでも止まらない。
- Don't: 文字に影を付けない。
- Don't: 文字同士の衝突を厳密な剛体衝突にしない。AABB の押し戻しと元の位置へのばねだけにする(回転の受け渡しまで入れると語が崩れる)。

## References
- Slingshot: 白いカタログにコバルトを1点だけ置く配色と、幾何学的なサンセリフの見出しを借りた。コバルトはピリオドとボタンに絞った。
- Ecosia: 白い画面に太い見出しと明るい差し色という構成を借りた。緑は使わず、差し色はコバルト1色にした。

## Usage
1. `.word` の見出しと `.floor` の線、`<script>` ブロック全体を移植する。
2. 文字列は `.word` のテキストを変える(空白は `&nbsp;` に置換される)。色を変えたい文字には JS 内で `className` を付ける。
3. 衝突は `box()` と `collide()` の2関数。文字列を変えても `fit()` が字形の箱を測り直すので、そのまま動く。
4. `P.g / e / stagger / dropH` で落ち方、`P.loop` で周期、`P.start` で初期表示の進み具合を決める。
5. 自動の再落下が不要なら `P.loop` を大きくする。

| パラメータ | 既定 | 意味 |
|---|---|---|
| g | 1500 | 重力(px/s²) |
| e | 0.58 | 反発係数 |
| dropH | 1.9 | 落下開始の高さ(見出し高さの倍数) |
| stagger | 0.14 | 文字ごとの遅れ(秒) |
| spin | 2.2 | 着地時の回転のばらつき(rad/s) |
| upright | 28 | 起き上がりのばね |
| loop | 7 | 再落下の周期(秒) |
| start | 0.35 | 読み込み時に進めておく秒数 |
| maxTilt | 0.32 | 傾きの上限(rad) |
| gap | 4 | 隣の文字との最小のすき間(px)。衝突の押し戻しに使う |
