From 956dc577635f5a1f13c0ef7be8992d305b022141 Mon Sep 17 00:00:00 2001 From: ncode Date: Thu, 17 Sep 2026 01:05:09 +0300 Subject: [PATCH] Phase 2: React scaffolding, self-hosted fonts, design tokens Vite builds to dist/, deliberately not www/. www/ is still produced by sync-www.sh for the Phase 1 APK, which stays installable throughout the port; cutover is a one-line webDir change in capacitor.config.json. base is relative because Capacitor serves the bundle from the APK, where absolute asset paths would 404. index.html omits viewport-fit=cover on purpose. That string is what makes Capacitor's SystemBars plugin pass insets through to the page; without it the plugin pads the decor view and the layout is always between the status and gesture bars. Phase 1 learned this the hard way. Fonts are self-hosted for offline use, latin subsets only: three files at 68K, against the 133K Barlow costs today. Space Grotesk is variable, so one file covers 400-700. Dropped latin-ext and vietnamese. Carets are drawn as SVG, not typed. U+25BE and U+23EE sit outside every Latin subset and would fall back to a system font mid-label. base.css carries the settled palette and the measured vertical budget as tokens, so a component cannot quietly exceed it: 8 + 44 + 8 + 525 + 8 + 84 + 8 + 50 = 735 Verified: typecheck clean, 103 tests, vite build emits with the fonts copied, and www/ is untouched. Co-Authored-By: Claude Opus 5 --- .gitignore | 4 + CLAUDE.md | 13 ++- index.html | 23 +++++ package.json | 5 +- public/fonts/space-grotesk-var-latin.woff2 | Bin 0 -> 22288 bytes public/fonts/space-mono-400-latin.woff2 | Bin 0 -> 16520 bytes public/fonts/space-mono-700-latin.woff2 | Bin 0 -> 16724 bytes src/App.module.css | 101 ++++++++++++++++++++ src/App.tsx | 88 ++++++++++++++++++ src/main.tsx | 13 +++ src/styles/base.css | 102 +++++++++++++++++++++ src/vite-env.d.ts | 1 + vite.config.ts | 24 +++++ 13 files changed, 369 insertions(+), 5 deletions(-) create mode 100644 index.html create mode 100644 public/fonts/space-grotesk-var-latin.woff2 create mode 100644 public/fonts/space-mono-400-latin.woff2 create mode 100644 public/fonts/space-mono-700-latin.woff2 create mode 100644 src/App.module.css create mode 100644 src/App.tsx create mode 100644 src/main.tsx create mode 100644 src/styles/base.css create mode 100644 src/vite-env.d.ts create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index 4a795d9..22efc23 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ node_modules/ # Generated by scripts/sync-www.sh from reference/ - never edit by hand. www/ +# Vite build output for the Phase 2 React app. Kept separate from www/ so the +# Phase 1 APK stays installable until cutover. +dist/ + # Capacitor's generated Android project. Tracked selectively: the few files we # hand-edit (MainActivity, manifest, styles, variables.gradle) are force-added. android/.gradle/ diff --git a/CLAUDE.md b/CLAUDE.md index 3bea162..01e0c3b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -254,10 +254,15 @@ panel 8 + 36 + 8 + 124 + 10 + 134 + 10 + 187 + 8 = 525 pads (187 - 15) / 4 = 43px rows ``` -**Still open:** the typeface (mockups use Space Grotesk + Space Mono; Martian -Mono and Archivo are the alternates on the specimen board) and whether the accent -is `#FF4A00` or the current, softer `#ff7a1a`. Neither blocks the data and logic -work, only the UI. +**Typeface: Space Grotesk + Space Mono.** Space Grotesk for labels and headings, +Space Mono for numerals and technical annotations. Self-hosted as woff2 under +`public/fonts/` — the app must work with no network, as Barlow did. Martian Mono +was the alternate and was rejected on measurement: it is a wide mono, and key +labels already clear their buttons by only 2.3px at 7px. + +**Accent: `#FF4A00`.** TE's orange, not the softer `#ff7a1a` Phase 1 used. It +holds against both the light body and the dark mode's near-black, which matters +because the accent has exactly one job — marking a control as live. ## Phase 2 must carry these UI changes diff --git a/index.html b/index.html new file mode 100644 index 0000000..cbac5ee --- /dev/null +++ b/index.html @@ -0,0 +1,23 @@ + + + + + + + + SP-404MK2 layers + + +
+ + + diff --git a/package.json b/package.json index 26e2547..26b652a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,10 @@ "test": "vitest run", "test:watch": "vitest", "typecheck": "tsc --noEmit", - "extract:data": "node scripts/extract-reference-data.mjs" + "extract:data": "node scripts/extract-reference-data.mjs", + "dev": "vite", + "build": "vite build", + "preview": "vite preview" }, "dependencies": { "@capacitor/android": "^8.5.2", diff --git a/public/fonts/space-grotesk-var-latin.woff2 b/public/fonts/space-grotesk-var-latin.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..0f3474ee8af2f8253225f3b41bf79be986a1ae13 GIT binary patch literal 22288 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 S8#S~?xaXqu;tAo2S~9su2vSq{fi^hG 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 literal 0 HcmV?d00001 diff --git a/public/fonts/space-mono-400-latin.woff2 b/public/fonts/space-mono-400-latin.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2752f605c1ece3797d3285191f5ffebe3c2873e1 GIT binary patch literal 16520 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 literal 0 HcmV?d00001 diff --git a/public/fonts/space-mono-700-latin.woff2 b/public/fonts/space-mono-700-latin.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..6563bad69c80c737252e98378763276116c17a3a GIT binary patch literal 16724 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 literal 0 HcmV?d00001 diff --git a/src/App.module.css b/src/App.module.css new file mode 100644 index 0000000..38a9dcf --- /dev/null +++ b/src/App.module.css @@ -0,0 +1,101 @@ +/* Heights come from the tokens, which carry the measured 360x735 budget: + 8 + 44 + 8 + 525 + 8 + 84 + 8 + 50 = 735, with no bottom padding. */ + +.app { + height: 100dvh; + padding: var(--gap) var(--gap) 0; + display: flex; + flex-direction: column; + gap: var(--gap); +} + +.strip { + height: var(--strip-h); + flex: 0 0 auto; + display: flex; + gap: var(--gap); +} + +.picker { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + background: var(--card); + border: var(--outline); + box-shadow: var(--shadow-sm); + padding: 0 10px; + font-size: 12px; + letter-spacing: 0.06em; + text-align: left; +} + +.picker b { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.count { + flex: 0 0 auto; + font-family: var(--mono); + font-size: 10px; + color: var(--accent); +} + +.panel { + height: var(--panel-h); + flex: 0 0 auto; + background: var(--body); + border: var(--outline); + box-shadow: var(--shadow); + display: grid; + place-items: center; +} + +.placeholder { + margin: 0; + font-family: var(--mono); + font-size: 11px; + letter-spacing: 0.08em; + color: var(--muted); +} + +.info { + height: var(--info-h); + flex: 0 0 auto; + background: var(--card); + border: var(--outline); + box-shadow: var(--shadow-sm); +} + +.nav { + height: var(--nav-h); + flex: 0 0 auto; + display: flex; + background: var(--card); + border: var(--outline); +} + +.tab, +.tabOn { + flex: 1; + background: none; + border: 0; + font-size: 11px; + letter-spacing: 0.08em; + color: var(--muted); +} + +.nav button + button { + border-left: var(--outline); +} + +.tabOn { + background: var(--accent); + color: var(--on-accent); + font-weight: 700; +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..abe98b4 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,88 @@ +/** + * App shell: the two pickers, a placeholder panel, and the tab bar. + * + * Angular: useState is roughly a component field plus change detection. Setting + * it re-runs this function, so `entries` and `counts` below are recomputed each + * render — cheap here, and `useMemo` is the explicit opt-out if it stops being. + */ +import { useMemo, useState } from "react"; +import { ENTRIES } from "./domain/entries"; +import { CTX } from "./domain/labels"; +import { controlName } from "./domain/format"; +import { layerCounts, orderLayerKeys } from "./domain/layers"; +import type { ContextCode } from "./domain/types"; +import styles from "./App.module.css"; + +const TABS = ["Explore", "Drill", "Find", "Rules"] as const; + +/** Drawn, not typed: the caret glyphs sit outside every Latin font subset. */ +function Caret() { + return ( + + ); +} + +const layerLabel = (key: string): string => + key === "" ? "NO MODIFIER" : key.split("+").map(controlName).join(" + "); + +export function App() { + const [ctx, setCtx] = useState("S"); + const [layer, setLayer] = useState(""); + const [tab, setTab] = useState<(typeof TABS)[number]>("Explore"); + + const counts = useMemo(() => layerCounts(ENTRIES, ctx), [ctx]); + const layers = useMemo(() => orderLayerKeys(counts.keys()), [counts]); + + // Changing mode can strip the held layer out from under us. + const activeLayer = counts.has(layer) ? layer : ""; + + return ( +

+
+ + + +
+ +
+

+ {counts.get(activeLayer) ?? 0} functions on this layer +

+
+ +
+ + +
+ ); +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..0d5ff81 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App"; +import "./styles/base.css"; + +const root = document.getElementById("root"); +if (!root) throw new Error("index.html is missing #root"); + +createRoot(root).render( + + + , +); diff --git a/src/styles/base.css b/src/styles/base.css new file mode 100644 index 0000000..fea38a4 --- /dev/null +++ b/src/styles/base.css @@ -0,0 +1,102 @@ +/* Design tokens and reset. The visual spec lives in CLAUDE.md. */ + +/* Latin subsets only. Space Grotesk is variable, so one file covers 400-700. */ +@font-face { + font-family: "Space Grotesk"; + font-weight: 400 700; + font-style: normal; + font-display: swap; + src: url("/fonts/space-grotesk-var-latin.woff2") format("woff2"); +} +@font-face { + font-family: "Space Mono"; + font-weight: 400; + font-style: normal; + font-display: swap; + src: url("/fonts/space-mono-400-latin.woff2") format("woff2"); +} +@font-face { + font-family: "Space Mono"; + font-weight: 700; + font-style: normal; + font-display: swap; + src: url("/fonts/space-mono-700-latin.woff2") format("woff2"); +} + +:root { + --font: "Space Grotesk", system-ui, sans-serif; + --mono: "Space Mono", ui-monospace, monospace; + + /* Direction A, manual. */ + --page: #e9e9e6; + --body: #d2d2ce; + --card: #f5f5f2; + --ink: #111111; + --muted: #6e6e69; + --rule: #b5b5b0; + --key: #f5f5f2; + --pad: #1b1b1b; + --pad-ink: #ededeb; + --lcd: #c9d2c4; + + /* One accent, one meaning: this control is live. */ + --accent: #ff4a00; + --on-accent: #111111; + + --outline: 2px solid var(--ink); + --shadow: 4px 4px 0 var(--ink); + --shadow-sm: 3px 3px 0 var(--ink); + + /* The measured vertical budget at 360x735. Nothing may exceed it. */ + --gap: 8px; + --strip-h: 44px; + --panel-h: 525px; + --info-h: 84px; + --nav-h: 50px; +} + +@media (prefers-color-scheme: dark) { + :root { + --page: #1a1a17; + --body: #2e2e2a; + --card: #2e2e2a; + --ink: #ededeb; + --muted: #8a8a85; + --rule: #4a4a46; + --key: #3e3e3a; + --pad: #0e0e0c; + --outline: 2px solid #000000; + --shadow: 5px 5px 0 #000000; + --shadow-sm: 3px 3px 0 #000000; + } +} + +* { + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; +} + +html, +body { + margin: 0; + overscroll-behavior: none; +} + +body { + background: var(--page); + color: var(--ink); + font-family: var(--font); + font-size: 16px; + line-height: 1.35; +} + +button { + font: inherit; + color: inherit; + cursor: pointer; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ea98ddd --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,24 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +/** + * Builds to dist/, deliberately not www/. + * + * www/ is still produced by scripts/sync-www.sh for the Phase 1 APK, which stays + * installable while the port is in progress. Cutover is a one-line change to + * webDir in capacitor.config.json once the React app replaces it. + * + * base is relative because Capacitor serves the bundle from the APK, not a + * domain root; absolute asset paths would 404 there. + */ +export default defineConfig({ + plugins: [react()], + base: "./", + build: { + outDir: "dist", + // The panel is one dense screen; a single bundle beats chunk round-trips + // from a file:// style origin. + assetsInlineLimit: 0, + target: "es2022", + }, +});