# 色ずれ — Style Reference
> a black-and-white dancer in a hairline frame on pure black; move the cursor and her outline splits into red, green and blue ghosts trailing in three directions, stop and the three fall back into one photograph.

**Theme:** dark  **Series:** PARTS 096  **World:** ダンス公演の案内。モノクロの舞台写真が、カーソルの速さに応じて三色に割れる。

## Concept
RGB shift as a response to motion. The photo is monochrome on purpose: when the channels are aligned there is no colour at all, so every bit of colour on screen is evidence of movement. Each channel is displaced along its own direction with a short streak so the ghosts look like motion blur. The shader samples at `uv + d`, so on screen each ghost moves along `-d` (uv y points down): red to the upper left, green to the lower right, blue to the upper right. A small automatic sweep ping-pongs between 0 and 60 percent of full strength, starting at 30 percent so a still frame shows it; pointer speed adds on top and decays in about a third of a second once the cursor stops. The frame's caption prints each channel's horizontal offset in pixels, signed by its direction.

## Signature Technique
Raw WebGL1, one fullscreen triangle in a 3:4 card.

- **Per-channel UV offset.** `uS` is the current shift as a fraction of width. For channel c with unit direction `d_c`, the shader averages `uN` samples along `uv + d_c * uS * f`, f in (0, 1), and takes only that channel. 10 samples on desktop, 6 on mobile. This turns a hard triple copy into a smeared one, which reads as speed.
- **Directions.** R (1, 0.35), G (-0.6, -0.8), B (-0.4, 0.9), normalised. The three directions sit 95 to 145 degrees apart, so the fringe spreads around the subject in a star.
- **Row tearing.** 7 percent of 180 rows get a horizontal offset of up to 35 percent of `uS`, re-rolled 12 times a second, so the strongest moments have a few sliced lines.
- **Scanlines and grain.** A sine at 900 cycles per height, strength `0.08 * clamp(uS * 12)`, so the lines fade in only while shifting; constant 3 percent grain to hide banding on the black.
- **Strength drive.** `auto = 0.5 + 0.5 sin(2 pi t / 6)` scaled by 0.6, plus a pointer term: pointer speed in px/ms times 0.0045 times 60 accumulates into `pStr` (clamped 1) and relaxes with `exp(-3 dt)`. `S = min(1, auto + pStr) * 0.09`.
- **Texture.** The photo is a base64 WebP (640 px, about 9 KB because it is monochrome) so WebGL can upload it from `file://`. Clamp-to-edge, linear, no mipmaps. Sampling is clamped to (0.001, 0.999) so no edge smear.
- **Loop control.** `IntersectionObserver` and `visibilitychange` stop the loop when the card is off screen or the tab hidden. Render size is the card's CSS size times min(dpr, 2), or min(dpr, 1.5) on mobile (mobile user agent or a viewport whose short side is under 640 px). `dt` is clamped to 0.1 s.
- **Reduced motion.** Auto sweep fixed at 50 percent, time frozen (no tearing re-roll); pointer still works.

## Color Palette
| Token | HEX | Role |
|---|---|---|
| --void | #050505 | page |
| --ink | #f4f4f2 | heading, pill, caption values |
| --mute | #8a8a8a | eyebrow, body, caption labels |
| --hair | rgba(244,244,242,.16) | card border |
| R / G / B | pure channels of the photo | the only colour, produced by displacement |

## Typography
- **Noto Sans JP** 300 / 400 / 500. h1 clamp(34px, 3.8vw, 56px) weight 400, -0.02em. Body clamp(15px, 1.05vw, 17px) weight 300, width set by the 440px copy column; each clause is an inline-block span so lines break only after a comma.
- **IBM Plex Mono** 400 for the eyebrow, the in-card caption (10px, 0.16em, uppercase) and the corner tag.

## Spacing & Layout
- Grid 6 / 5, image left, copy right, gap `clamp(24px, 5vw, 80px)`, centred in `100svh`.
- Card `aspect-ratio: 3 / 4`, max 520px, hairline border, caption 16px from the left and 14px from the bottom.
- Under 760px: stacked, card max 300px centred so the button stays in the first screen, copy below, tag at the end of the page bottom left (`position: absolute`).

## Motion
- Auto ping-pong 6 s at amplitude 0.6 (starts at the midpoint, 0.3 of full strength); pointer adds up to full strength; decay 3 per second.
- rAF loop with `dt` clamped to 0.1 s; time-based, so frame rate does not change the speed.
- Nothing else animates; the pill has no hover transition so the photo owns all motion.

## Components
- **card**: canvas, hairline border, mono caption with live R / G / B offsets in px.
- **copy**: eyebrow, h1, one sentence, outlined pill link.
- **tag**: `VALE / PARTS 096 / 色ずれ` plus the one-line hint.
- **fallback**: without WebGL a note appears; the card stays black.

## Do / Don't
Do
- Use a monochrome or near-monochrome photo so colour only appears with motion.
- Give each channel its own direction; a mirrored left-right pair reads as an anaglyph 3D image.
- Smear each channel along its shift; hard copies look like a broken display.

Don't
- No permanent shift; the resting state must be a clean photo.
- No shift larger than about 9 percent of width; beyond that the subject is unreadable.
- No colour in the UI around the card.

## Usage
1. Put `<div class="card"><canvas id="gl"></canvas></div>` where the photo goes; set its aspect ratio to the photo's.
2. Copy the `<script>` block. Replace the base64 in `#img` (or set `img.src` to a same-origin URL when served over http).
3. If you want the effect on hover only, set `autoAmp` to 0; if you want it as a page-load flourish, set `autoAmp` to 1 and `period` to a long value.
4. Tune `P`.

| パラメータ | 既定 | 意味 |
|---|---|---|
| maxShift | 0.09 | 強さ 1.0 のときのUVずれ(幅に対する割合) |
| autoAmp | 0.6 | 自動往復の振幅(0 で自動なし) |
| period | 6 | 往復1回の秒数 |
| velGain | 0.0045 | カーソル速度(px/ms)→強さ |
| decay | 3.0 | 止めたあと戻る速さ(1/s) |
| dirs | R(1,.35) G(-.6,-.8) B(-.4,.9) | チャンネルごとのずれ方向 |
| streak | 10 / 6(モバイル) | ずれ方向に沿ったサンプル数(流れ感) |
| scan | 0.08 | 走査線の濃さ |

The caption readout (`#rv #gv #bv`) is optional; delete the three lines in `loop()` to drop it.

## References
- **Graphite** (stock/refero/md/graphite.md): dark bordered product surfaces around precise white type with sparse bursts of spectral light. Abstracted: hairline-bordered card on black, light as the only colour. Changed: the spectral light is the photo's own channels coming apart, no gradients.
- **Factory** (stock/refero/md/factory.md): one white card landing on a black control surface; the only object in the room is the work. Abstracted: single object, black room, mono captions. Changed: the card holds a live shader, and the caption is a readout of the effect.
