RELEASE
planning/2.8.0/mockups/optics-picker-notes.md
sha256 2752fb50fabde520 · 7668 bytes ·
original held in the private archive
# SLOT 01 — VDU Optics Driver · Phosphor Tube Picker Redesign (Mockup Notes)
Mockup: `planning/mockups/optics-picker.html` (self-contained — inline CSS + a little JS so the
expand/collapse is demonstrable in any browser; open it directly, no server needed).
## What it is
The flat rectangular color swatches are replaced with the Module Bay's established
**phosphor tube** graphic — a **byte-identical copy** of `module-bay-mockup.html`'s
PHOSPHOR TUBE RACK tube CSS (62px socket, 22×44 rounded-top glass envelope with the
soft bottom-weighted colored gradient fill, anode nib on top, 8.5px label under each;
seated = lit glass at full opacity + colored glow). One owner-requested consistency
change over the module-bay original: the seated `●` is no longer an inline suffix
appended beside the seated tube's label — **every** tube (standalone AND the greens
inside the expanded family) carries a small **seat lamp** (`.t-led`) centered directly
below its name: a faint hollow ring when idle, filled + glowing in the tube's own color
when seated. Uniform "name, then dot below" placement across the whole rack. The rack
has **5 sockets instead of 7**:
| Socket | Contents | Behavior |
| --- | --- | --- |
| GREEN FAMILY cartridge | ROBCO GREEN · PIP-BOY GREEN · GHOUL GREEN | tap → fans out into 3 tubes |
| NEW VEGAS AMBER | single tube | tap → seats directly |
| VAULT-TEC BLUE | single tube | tap → seats directly |
| LEGION RED | single tube | tap → seats directly |
| NEON VIOLET | single tube | tap → seats directly |
The family cartridge is visually distinguished from single tubes by two faint **ghost tube
outlines** peeking out behind its glass (colored as the two greens NOT currently chosen) and
an amber `▤ 3 TYPES` tag — it reads as a cartridge holding multiple tubes, not just one more tube.
## The expand animation ("cartridge ejects its tubes")
- Tapping the green cartridge swaps it for an **opened shell** (dashed empty socket labeled
`CARTRIDGE OPEN ◂`) in the same rack cell, and the 3 green variant tubes **slide out of the
shell's position** into the next rack cells.
- Mechanics: each variant carries `--i` (its distance in cells from the shell) and a stagger
delay `--d` (0/70/140 ms). One `@keyframes tubeEject` animates from
`translateX(calc(-1 * var(--cell) * var(--i))) translateY(4px) scale(0.6)` + opacity 0 to
identity — so all three visually originate stacked inside the shell and fan out left-to-right
with a spring-ish cubic-bezier. The shell itself flashes (`shellOpen`) as it unlatches.
- The variant matching the currently seated green is lit (`.seated`) inside the open tray, so
the current choice reads at a glance.
## Selection / collapse
- **Tap a green variant** → it becomes the seated optic (real build:
`changeOpticsColor(key)`), the tray collapses, and the family cartridge re-appears showing the
**chosen** green as its representative (glass color, label, `●`, ghost outlines recolored to
the other two greens) with a short `tubeSeat` drop-in flourish. Status line updates.
- **Tap the open shell** → collapses with **no change** (escape hatch, no accidental re-seat).
- **Single-hue tubes** never expand — tap seats immediately. Seating a non-green unlights the
cartridge but it keeps displaying the last-chosen green as its representative.
## Motion / accessibility notes (Protocol UI-9 / 17 compliance)
- Every verb (`tubeEject`, `shellOpen`, `tubeSeat`) is a **plain `animation:` declaration**, so
the app's existing global `prefers-reduced-motion` block (zeroing `animation-duration` on `*`)
degrades each to its instant final frame — expanded tray / reseated cartridge still appear,
just without motion. The mockup replicates that global block so this is demonstrable.
- Hover glow is gated behind `@media (hover:hover) and (pointer:fine)` — during mockup
verification the eject animation left a **stuck `:hover` glow on a touch-style tap** (variants
animate out from under the pointer), the exact sticky-hover class the mode pill fix already
covers; the real build should keep this gate.
- Tap targets: every tube/shell is a real `<button>`, 62×96 px (≥28px). The family cartridge
carries `aria-expanded` + a descriptive `aria-label` naming the current green; rack is a
`radiogroup`.
- Verified via Playwright at 412px and 1280px: `scrollWidth === innerWidth` (no horizontal
overflow) in collapsed, expanded, and post-pick states.
## Build contract (reskin only — behavior unchanged)
- Every tube routes through the **unchanged** `changeOpticsColor(<themeKey>)`; colors/labels
come from `window.THEMES` (keys `green`/`green3`/`ghoul`/`amber`/`blue`/`legion`/`neon` —
never re-declared); per-game persistence (`robco_optic_<ctx>` via MetaStore) untouched.
- The family grouping is **pure presentation**: no new state field, no new device pref. The
cartridge's representative green is *derived* from the seated optic (if seated ∈ family →
that green; else last family member seated, defaulting to the game's default green). The
expanded/collapsed state is transient (collapsed on every render), so nothing to persist
under Protocol UI-6 — it's a momentary picker gesture, not a view mode.
- Grouping should be **data-driven** for Protocol 38 cleanliness — e.g. a `family: 'green'`
tag on the THEMES rows (or a small `OPTIC_FAMILIES` map next to THEMES), not a hardcoded
key list inside the render function, so a future added hue family works without touching
the picker code.
## ⚠ Latent bug found in module-bay-mockup.html (owner revision round)
While matching the tube icons exactly, measurement showed `module-bay-mockup.html`'s own
tube glass **never renders at its designed 22×44 size**: `.glass` is an inline `<span>`,
and inline elements ignore `width`/`height`, so the glass silently collapses to ~2×16 —
what its screenshots show is the collapsed sliver + nib, not the designed vacuum tube.
The optics picker fixes this with one `display: block` on `.tube .glass` (all other rules
byte-identical), which is what makes the designed rounded-top glowing tube actually paint.
**If the Module Bay's tube rack ever ships from that mockup, carry the same one-line fix
over** (or use a `<div>`); the reference mockup file was deliberately left untouched.
## Screenshots
- `planning/mockups/optics-picker-412-collapsed.png` — resting rack at phone width (green as ONE cartridge + 4 singles)
- `planning/mockups/optics-picker-412-expanded.png` — green family fanned out (shell open, current green lit)
- `planning/mockups/optics-picker-412-picked.png` — after choosing PIP-BOY GREEN (cartridge re-latched showing the pick)
- `planning/mockups/optics-picker-desktop.png` — desktop resting rack
- `planning/mockups/optics-picker-desktop-expanded.png` — desktop expanded
## Open questions for the owner
1. **Where should the expanded variants seat-preview?** Currently picking a green applies it
immediately (matches today's swatch behavior). Alternative: hovering/tapping once previews,
tapping again seats — more "trying a tube," but adds a tap (Protocol 25 guardrail says no
added tap-count, so immediate-apply is what's mocked).
2. **Representative when a non-green is seated:** the cartridge currently keeps showing the
last-chosen green (dim, unlit). OK, or should it always revert to the game's default green
when unlit?
3. **Should AMBER stay standalone?** It's single-hue today, but if a second amber ever lands
(e.g. a dimmer "old amber"), the same cartridge pattern generalizes — hence the data-driven
family map suggestion above.
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).