# 磁石のボタン — Style Reference
> 暖かい黒の地下室。蛍光イエローのボタンが1つ、白い枠のボタンが4つ。近づくと寄ってくる

**Theme:** dark  **Series:** PARTS 053  **World:** 架空の地下ライブ会場「Yoru」のトップ。今夜の案内のボタンが、指の側へ寄ってくる。

## Concept
左にイタリックのセリフで時刻、右にボタンの群れ。カーソルが近づくと、一番近いボタンだけが距離に応じて寄ってきて、ラベルはさらに少し先に動く。面の中の淡い光もカーソルを追う。離れるとばねで揺れて戻る。色は骨色と黒と、主ボタンの蛍光イエローだけ。無操作時は仮想のカーソルがボタンの間を巡るので、静止画でもどれかが寄っている。

## Signature Technique
DOM + JS のばね。ライブラリなし。

- **距離**: ボタンの角丸矩形の縁からの距離 `edge = hypot(max(|dx| − w/2, 0), max(|dy| − h/2, 0))`。
- **引き寄せ**: カーソルに一番近いボタンだけが対象。`edge < radius(120px)` のとき `pull = 1 − edge/radius`、目標 `t = d·0.30·pull`(d はカーソルまでのベクトル)。
- **ばね**: `v += ((t − x)·170 − v·14)·dt`、`x += v·dt`。離れると t = 0 に向かって少し行き過ぎてから戻る。
- **ラベルの視差**: `.l` を `x·0.45` だけ余分に動かす。
- **面の光**: CSS 変数 `--mx / --my` にボタン内でのカーソル位置(%)を入れ、`::before` の `radial-gradient` で淡い光を置く。`edge < radius·0.6` で `.near` を付けて表示。
- **無操作時**: 仮想カーソルがボタンの中心を順に巡る(0.28 個/秒、ease-in-out)。初期位置も最初のボタンの近く。
- 位置は `measure()` でボタンの中心を取り、現在の変位を差し引いて保持する。リサイズで再計測。

```js
const edge = Math.hypot(Math.max(Math.abs(dx) - w/2, 0), Math.max(Math.abs(dy) - h/2, 0));
if (it === nearest && edge < R) { const pull = 1 - edge / R; tx = dx * 0.30 * pull; ty = dy * 0.30 * pull; }
it.vx += ((tx - it.x) * 170 - it.vx * 14) * dt; it.x += it.vx * dt;
it.el.style.transform = `translate(${it.x}px, ${it.y}px)`;
it.label.style.transform = `translate(${it.x * 0.45}px, ${it.y * 0.45}px)`;
```

## Color Palette
| Token | HEX | Role |
|---|---|---|
| --black | #19181a | 地。暖かい近黒 |
| --pure | #000000 | 黄色いボタンの文字 |
| --bone | #f5f4f0 | 見出し、ボタンの文字 |
| --sand | #d9d7ce | ボタンの枠 |
| --ash | #8f8d86 | 本文、ヘッダー補助、ボタン内の小さなラベル |
| --yellow | #f0e226 | 主ボタンの面、クレジット、focus ring。画面で唯一の色 |
| --charcoal | #2b2a2c | 予備の罫線色 |

## Typography
- 見出し: Instrument Serif italic 400、`clamp(44px, 5.6vw, 84px)`、line-height 1.05、letter-spacing −0.01em。
- 日本語見出し: Noto Sans JP 500、`clamp(17px, 1.5vw, 21px)`、letter-spacing 0.02em、line-height 1.6、見出しの下 18px。本文 400、`clamp(13px, 1.05vw, 15px)`、上 12px、最大幅 26em。
- ボタン: Noto Sans JP 500 15px、letter-spacing 0.02em、最小高さ 56px、左右 28px、角丸 999px(モバイルは 14px、最小高さ 50px、左右 20px)。中の補助ラベルは JetBrains Mono 400 11px、letter-spacing 0.08em。
- 丸ボタン: 112px、JetBrains Mono 12px、letter-spacing 0.1em(モバイルは 92px、10px、letter-spacing 0.06em、折り返しなし)。
- ラベル: JetBrains Mono 400、11–12px、letter-spacing 0.06em。

## Spacing & Layout
- hero `min-height: 100svh`、3行グリッド。中央行は `1fr 1.1fr` の2列、間隔 `clamp(24px, 5vw, 80px)`(モバイルは1列、間隔 28px)。
- ボタン群は `flex-wrap`、間隔 26px × 22px、上下 6vh、最大幅 34em、中央寄せ、`align-items: center`(丸ボタンに高さを合わせない)。
- 余白: 横 `clamp(20px, 5vw, 72px)`、上 `clamp(20px, 3vw, 40px)`、下 `clamp(18px, 2.6vw, 32px)`。モバイル左右 18px。
- breakpoint 760px: ヘッダー右とメタ情報を消し、ボタン群の間隔を 14px × 12px・上下 2vh / 4vh に詰め、フッターを縦積み。

## Motion
- 引き寄せ半径 120px(モバイル 90px)、最大変位は半径の 30%(約 36px)。
- ばね k 170 / c 14: 離れてから約 0.6 秒で静止、1 回だけ行き過ぎる。
- 面の光は 0.25 秒で現れる。直径 120px の bone 18%(主ボタンは 140px の白 45%)。
- rAF 1本。IntersectionObserver と visibilitychange で画面外停止。
- `prefers-reduced-motion: reduce`: 引き寄せを 0 にし、初期配置のまま。hover の光だけ残る。

## Components
- hero: 左に見出し+日本語+本文、右にボタン群。
- button(.mb): 角丸ピル、1.5px 枠。`.primary` は黄色の面、`.round` は円。`focus-visible` に黄色の 2px アウトライン。
- meta: 左下に半径と強さとばねの定数。
- footer tag: `VALE / PARTS 053 / 磁石のボタン` と操作説明1行(タッチ端末では差し替え)。

## Do / Don't
- Do: 寄るのは一番近い1つだけ。全部が寄ると群れが崩れる。
- Do: 変位は半径の 30% 以下。それ以上は押しているのに逃げる感じになる。
- Do: ラベルは面より少し多く動かす。
- Don't: 色付きのボタンを2つ以上にしない。
- Don't: hover で面を大きくしない。位置の変化だけで十分。
- Don't: 引き寄せ半径をボタンの間隔より大きくしない。

## References
- Hardworkclub: 真っ黒な劇場に丸い光るボタンが1つ、という構成を借りた。青い光は使わず、丸ボタンは枠だけにして、主ボタンの黄色に色を集めた。
- Cassette: 暖かい黒に蛍光イエローを1色、モノスペースの小さなラベルという配色と文字の型を借りた。黄色は主ボタンとクレジットだけに絞った。

## Usage
1. `.mb` 系の CSS と `<script>` ブロック全体を移植する。
2. 寄せたい要素に `data-mag` を付け、中のラベルを `<span class="l">` で囲む(視差用)。
3. `P.radius / P.strength / P.k / P.c / P.labelExtra` で挙動を調整する。
4. 無操作時の巡回が不要なら `idle()` の呼び出しを消す。
5. リンクの `click` を `preventDefault` している行は、実サイトでは消す。

| パラメータ | 既定 | 意味 |
|---|---|---|
| radius | 120 / 90(モバイル) | 引き寄せが始まる距離(縁から、px) |
| strength | 0.30 | 変位 / カーソルまでの距離 |
| k / c | 170 / 14 | ばねの硬さと減衰 |
| labelExtra | 0.45 | ラベルが余分に動く比 |
| idleSpeed | 0.28 | 無操作時に巡る速さ(ボタン/秒) |
