# ピクセルソート — Style Reference
> a night street where only the light trails run vertically, sorted dark to bright, while the buildings stay put; a violet meter at the foot of the frame shows how much of the photo is moving.

**Theme:** dark  **Series:** PARTS 095  **World:** 写真展「Night Transit」の告知。雨上がりの交差点の写真が、明るい区間だけ列ごとに並び替わる。

## Concept
Pixel sorting as a controlled editorial effect. The photo sits in a bordered 4:3 frame to the right of a short copy block. Inside the frame, every column is scanned for runs of pixels whose luminance is above a threshold, and only those runs are re-ordered dark to bright. Low threshold means most of the image streams; high threshold means only the headlights and reflections move. The threshold ping-pongs automatically over 9 seconds, counted from the window load event and starting at 60 percent on the way up, so a still taken about 3 seconds after load lands on a strong sort, and the pointer's height inside the frame pulls it up or down. A 2 px violet meter under the frame reports the current amount.

## Signature Technique
Canvas2D only, no WebGL.

- **Source.** The photo (560 x 419 px; drawn at 560 px wide, 360 on mobile, where mobile means a mobile user agent or a viewport whose short side is under 640 px) is embedded as a base64 WebP so `getImageData` is allowed from `file://`. It is drawn once; its RGBA bytes and a `Float32Array` of Rec.601 luminance are cached.
- **Span detection.** For each column x, walk down y; a span is a maximal run with `lo <= lum <= hi`. Spans shorter than 3 px are left alone so noise does not shimmer.
- **Sort.** Indices of the span go into a reusable `Uint32Array` sub-view and are sorted by cached luminance (`subarray().sort` with a key lookup, no allocation per span). Pixels are copied from the source buffer into the output buffer in sorted order, dark at the top, bright at the bottom, which makes the light trails fall downward like rain on the wet street.
- **Threshold drive.** `u = 0.5 + 0.5 sin(2 pi t / period + 0.2)`, t measured from the load event (u = .6 at 0 s, 1 at 2 s, about .74 at 3.5 s), blended 60 / 40 with pointer height when the pointer is over the frame. `lo = loMax - (loMax - loMin) u`. `lo` is quantised to steps of 1/400 and the sort re-runs only when that step changes, so a frozen threshold (reduced motion, pointer held still) costs nothing.
- **Cost.** 560 x 420 = 235k pixels; a full re-sort takes 6 to 12 ms on a laptop, under 25 ms on a mid phone at 360 px. The loop pauses when the frame leaves the viewport or the tab is hidden.
- **Reduced motion.** The sweep freezes at u = 0.5; the pointer still works.

## Color Palette
| Token | HEX | Role |
|---|---|---|
| --void | #0c0d12 | page |
| --ink | #f2f1ee | heading, tag name |
| --mute | #8b8d96 | eyebrow, body, tag |
| --hair | rgba(242,241,238,.14) | frame border, fallback note border (the meter track is a fainter rgba(242,241,238,.1)) |
| --violet | #8d7bff | meter fill, the only chromatic UI note (the photo carries the rest) |

## Typography
- **Noto Sans JP** 300 / 400 / 500. h1 clamp(30px, 3.2vw, 46px) weight 400, -0.02em, line-height 1.2, broken after the comma with inline-block spans so a phrase never splits. Body clamp(15px, 1.05vw, 17px) weight 300, copy column max 500px.
- **IBM Plex Mono** 400 for the eyebrow (11px, 0.18em, uppercase) and the corner tag (10px, 0.14em).

## Spacing & Layout
- Two-column grid 5 / 7 with `clamp(24px, 4vw, 64px)` gap, vertically centred in `100svh`, side padding `clamp(20px, 5vw, 72px)`.
- Frame: `aspect-ratio: 4 / 3`, 1px hairline border, square corners, meter flush to the bottom edge.
- Under 760px the grid stacks, frame first, copy second; the tag moves to the bottom left.

## Motion
- Threshold ping-pong period 9 s, sine eased, phase +0.2 rad counted from window load, so the first 4 seconds stay between 40 and 100 percent.
- Pointer override weight 0.6, vertical only (`cursor: ns-resize`).
- No transitions on the DOM; the only motion is the re-sorted pixels.

## Components
- **hero**: eyebrow, h1, one sentence, framed canvas with meter.
- **tag**: `VALE / PARTS 095 / ピクセルソート` and the one-line hint, fixed bottom right.
- **fallback**: without a 2D context a bordered note appears under the copy.

## Do / Don't
Do
- Sort along the axis that matches the photo's motion (vertical here, with the falling rain and light trails).
- Keep the threshold window above mid grey so the architecture holds still.
- Show the current amount somewhere small; it tells the reader the effect is controlled.

Don't
- No random per-frame jitter; the sort must be deterministic for a given threshold.
- No sorting of the whole frame; the effect reads only against untouched pixels.
- No hue or channel tricks on top; the effect is the pixel order alone.

## Usage
1. Put `<div class="frame"><canvas id="c"></canvas><div class="meter"><i id="meter"></i></div></div>` where the image goes and give `.frame` the photo's aspect ratio.
2. Copy the whole `<script>` block. Replace the base64 in `#img` with your photo (keep it under about 60 KB, or load from a same-origin URL when the page is served over http, where `getImageData` is allowed).
3. Choose the sort direction: this script sorts columns (vertical). For horizontal sorting swap the roles of x and y in `sortColumns`.
4. Tune `P`.

| パラメータ | 既定 | 意味 |
|---|---|---|
| width | 560 / 360(モバイル) | 作業解像度(px)。CPU コストはこれに比例 |
| lo, hi | 0.46, 1.0 | 並び替える輝度の範囲。lo はスイープで動く |
| loMin, loMax | 0.18, 0.86 | スイープが動く範囲。loMin を下げるほど派手 |
| period | 9 | 往復1回の秒数 |
| pointerWeight | 0.6 | カーソル高さが自動スイープを上書きする割合 |
| minSpan(コード内の `n < 3`) | 3 | これより短い区間は並び替えない |

Remove the pointer listeners for a purely automatic version; for a static poster frame, replace the `u = ...` line in `loop()` with a constant (for example `let u = 0.5`). Do not set `period` to 0; it divides by zero.

## References
- **Tapbots** (stock/refero/md/tapbots.md): a midnight canvas where a single violet frequency does the accent work. Abstracted: near-black page, one violet hairline. Changed: the violet is a 2 px meter, nothing else is tinted, and the photo supplies all other colour.
- **Tana** (stock/refero/md/tana.md): dark editorial spread with one pale accent as the only colour. Abstracted: the discipline of one accent and quiet mono labels. Changed: the accent is violet, and the hero is an image experiment.
