# ホバーの波 — Style Reference
> チョーク色の壁に鞍革色の文字。ボタンに触れると、その中心から細いリングが何本も広がる

**Theme:** light  **Series:** PARTS 059  **World:** 架空の語学アプリ「Kotoba」のトップ。一日ひとつ、という小さな行為が周りへ広がることをボタンの波紋で示す。

## Concept
左にセリフの見出し、右に3つのボタン。ボタンに触れる(またはクリックする)と、その中心から 4 本のリングが 110ms ずつ遅れて広がり、2.2 秒かけて消える。波が届いた他のボタンは小さく揺れる。無操作時は 2 秒おきに順番に波を出すので、静止画にも途中のリングが写る。色は壁と文字の2色だけ。

## Signature Technique
Canvas 2D のリング + DOM のボタン。ライブラリなし。

- **発生**: `emit(b)` で `{x, y, t: −i·0.11, src, r0: ボタン半径·0.9}` を 4 つ積む。`t < 0` のあいだは待機。
- **描画**: 半径 `R = r0 + t·240`、線幅 `2.2·(1 − 0.6u)`、不透明度 `0.85·(1 − u)^1.6`(u = t / 2.2)。色は鞍革 rgba(93,58,25,·)。dpr 2 まで。
- **届いた揺れ**: 各波について、`R ≥ 距離 − ボタン半径·0.3` になった最初のフレームで、そのボタンの `vs += 0.05·(1 − u)·6`。ボタンは `s' = (1 − s)·160 − 10·vs` のばねで 1 に戻る(`transform: scale`)。
- **無操作時**: ホバー中でなく、最後の発生から 2 秒経ったら次のボタンから発生。読み込み時は主ボタンから発生させ、0.7 秒ぶん進めておく。
- `measure()` でボタン中心を取り直す(リサイズ時)。
- 起動順: フォント読み込み後の `boot()` で `ready = true` にしてから rAF を回す。IntersectionObserver が先に発火しても、ボタン中心が未計測(0, 0)のまま波を出さない。

```js
const R = w.r0 + w.t * 240, u = w.t / 2.2;
ctx.arc(w.x, w.y, R, 0, 6.2832);
ctx.lineWidth = 2.2 * (1 - u * .6); ctx.strokeStyle = `rgba(93,58,25,${.85 * (1 - u) ** 1.6})`; ctx.stroke();
if (R >= dist - b.r * .3 && !b.hit.has(w.id)) { b.hit.add(w.id); b.vs += .05 * (1 - u) * 6; }
```

## Color Palette
| Token | HEX | Role |
|---|---|---|
| --chalk | #e6e0d9 | 壁。主ボタンの文字色でもある |
| --saddle | #5d3a19 | 文字、ボタンの枠と主ボタンの面、リング |
| --saddle-soft | rgba(93,58,25,.55) | 本文、ヘッダー補助、ボタン内の小さなラベル |
| --lavender | #b7b3be | 予備(第二の面) |
| --paper | #f4f0ea | 予備 |

## Typography
- 見出し: Instrument Serif 400、`clamp(40px, 5.4vw, 80px)`、line-height 1.1、字間 −.01em。
- 日本語見出し: Noto Sans JP 500、`clamp(17px, 1.5vw, 21px)`。本文 400、`clamp(13px, 1.05vw, 15px)`。
- ボタン: Noto Sans JP 500 15px(モバイル 14px)、padding 16px 28px(モバイル 14px 22px)、1.5px 枠、角丸 999px、中の補助は JetBrains Mono 11px / 字間 .08em。
- ラベル: JetBrains Mono 400、11–12px。

## Spacing & Layout
- hero `min-height: 100svh`、3行グリッド。中央行は 2 列(モバイル 1 列)。canvas は背面全面で `pointer-events: none`。
- ボタンは縦に 18px 間隔(モバイル 14px)、中央寄せ。
- 余白: 横 `clamp(20px, 5vw, 72px)`、上 `clamp(20px, 3vw, 40px)`、下 `clamp(18px, 2.6vw, 32px)`。2 列の間隔 `clamp(24px, 5vw, 80px)`(モバイル 28px)。モバイル左右 18px。
- breakpoint 760px: ヘッダー右とメタ情報を消し、フッターを縦積み。

## Motion
- リングは毎秒 240px で広がり、2.2 秒で消える。4 本が 110ms ずつ遅れる。
- 届いたボタンは約 5% 拡大して 0.5 秒で戻る。
- 無操作時 2 秒ごとに次のボタンから発生。hover 中は自動発生を止める。
- rAF 1本。IntersectionObserver と visibilitychange で画面外停止。リングが無くなれば止まる。
- `prefers-reduced-motion: reduce`: 自動発生なし。hover / click の波だけ描く。

## Components
- hero: 背面 canvas+見出し+ボタン3つ。
- header: 左にアプリ名、右に技法名。
- button(.rb): 枠線のピル。`.primary` は塗り。hover で塗りに反転。`focus-visible` に 2px のアウトライン。
- meta: 左下にリング数と間隔と速度と寿命。
- footer tag: `VALE / PARTS 059 / ホバーの波` と操作説明1行。

## Do / Don't
- Do: リングは線だけ。塗りや影を付けると的に見える。
- Do: 波が届いた要素に小さな反応を付ける。波に意味が出る。
- Do: 色は文字と同じ1色。
- Don't: リングを 6 本より増やさない。
- Don't: 速度を 400px/s 以上にしない。追えない。
- Don't: 自動発生を 1.5 秒より短くしない。

## References
- OhDada: チョーク色の壁に鞍革色の文字という2色の構成と、大きなセリフの見出しを借りた。ラベンダーの面は使わず、2色に絞った。
- Duolingo: 白い紙の上で要素が小さく弾む遊び心を借りた。緑やマスコットは使わず、弾むのはリングが届いたボタンだけにした。

## Usage
1. `<canvas id="cv">` を hero の背面に置き、波を出したい要素に `data-ripple` を付ける。
2. `<script>` ブロック全体を移植する。
3. `P.rings / stagger / speed / life / width / alpha` でリング、`P.nudge` で届いたボタンの揺れ、`P.idleEvery` で自動発生の間隔を決める。
4. リングの色は `draw()` の `rgba(93,58,25,…)` を変える。
5. リンクの `click` を `preventDefault` している行は、実サイトでは消す。

| パラメータ | 既定 | 意味 |
|---|---|---|
| rings | 4 | 1 回に出るリングの本数 |
| stagger | 0.11 | リングの間隔(秒) |
| speed | 240 | 広がる速さ(px/s) |
| life | 2.2 | 寿命(秒) |
| width / alpha | 2.2 / 0.85 | 線幅(px)と最初の不透明度 |
| idleEvery | 2.0 | 無操作時の発生間隔(秒) |
| nudge | 0.05 | 届いたボタンの揺れ |
| start | 0.7 | 読み込み時に進めておく秒数 |
