diff --git a/CLAUDE.md b/CLAUDE.md index e3b6dd2..591b87d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -218,6 +218,15 @@ missing from the panel. The bank row is pinned at six keys ending in SHIFT because `Panel.module.css` widens it with `:last-child:nth-child(6)`, which stops matching silently if that row ever changes length. +**Lit is an outline; the fill means selected.** This deviates from the mockups, +which only ever showed three lit keys at once. The SHIFT layer lights 35 of 54 +controls, and solid orange across all of them is a wall rather than a map. Orange +outline says "something is here"; solid orange says "this is the one you tapped". + +**Round keys clip and shrink when lit.** `SHIFT + VALUE` is `MARK FN` inside a +30px circle. `border-radius: 50%` alone does not clip, so the label escaped the +circle entirely; the knobs need `overflow: hidden` and a smaller size when lit. + **Two strings are load-bearing for migration** and must not be changed casually: `Entry.id` (below) and `STORAGE_KEY`, still `sp404mk2-layers-v1`. Together they mean the boxes already on the phone are picked up by the React app rather than diff --git a/src/App.tsx b/src/App.tsx index aed39ff..fe2e634 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,22 @@ /** - * App shell: pickers, the panel, and the tab bar. + * Explore: pick a mode and a layer, then read what the panel lights up. * * Angular: useState is roughly a component field plus change detection. Setting - * it re-runs this function, so derived values below are recomputed each render; + * it re-runs this function, so derived values are recomputed each render; * useMemo is the explicit opt-out when that stops being cheap. */ import { useMemo, useState } from "react"; import { ENTRIES } from "./domain/entries"; import { controlName } from "./domain/format"; -import { CTX } from "./domain/labels"; -import { entriesByTarget, entriesForLayer, layerCounts, orderLayerKeys } from "./domain/layers"; -import type { ContextCode } from "./domain/types"; +import { CTX, CTXHINT } from "./domain/labels"; +import { + entriesByTarget, + entriesForLayer, + layerCounts, + orderLayerKeys, +} from "./domain/layers"; +import type { ContextCode, ControlId, Entry } from "./domain/types"; +import { InfoCard } from "./ui/InfoCard"; import { Panel } from "./ui/Panel"; import styles from "./App.module.css"; @@ -31,6 +37,7 @@ const layerLabel = (key: string): string => export function App() { const [ctx, setCtx] = useState("S"); const [layer, setLayer] = useState(""); + const [selected, setSelected] = useState(null); const [tab, setTab] = useState<(typeof TABS)[number]>("Explore"); const counts = useMemo(() => layerCounts(ENTRIES, ctx), [ctx]); @@ -47,27 +54,38 @@ export function App() { const held = activeLayer === "" ? [] : activeLayer.split("+"); + /** A control can carry several functions; pressing it again cycles them. */ + function press(id: ControlId) { + const here = lit.get(id); + if (!here || here.length === 0) { + setSelected(null); + return; + } + const at = selected ? here.indexOf(selected) : -1; + setSelected(here[(at + 1) % here.length]!); + } + + function chooseCtx() { + const order = Object.keys(CTX) as ContextCode[]; + setCtx(order[(order.indexOf(ctx) + 1) % order.length]!); + setSelected(null); + } + + function chooseLayer() { + const at = layers.indexOf(activeLayer); + setLayer(layers[(at + 1) % layers.length]!); + setSelected(null); + } + return (
- - @@ -76,15 +94,22 @@ export function App() { e.short), + right: layerLabel(activeLayer), + main: selected ? selected.short : `${onLayer.length} functions`, + chips: selected ? [] : onLayer.slice(0, 3).map((e) => e.short), }} /> -
+