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 <noreply@anthropic.com>
This commit is contained in:
101
src/App.module.css
Normal file
101
src/App.module.css
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user