FastAPI backend + vanilla JS frontend for LiDAR/camera startup, camera settings, and rosbag recording, unifying the previous scan_gui/scan_gui_dual/ scan_gui_triple desktop tools into one web app.
4.8 KiB
Design — scan_web
Locked design system for the unified scan/record control panel. Every future view (the M3 Scanning/LIO view, any later addition) reads this file before shipping new UI — extend it, don't invent a parallel system.
Genre
modern-minimal (Stripe / Linear / instrument-panel school — SaaS/dashboard trigger)
Macrostructure family
This is an app, not a marketing site — no macrostructure/nav-archetype/ footer-archetype apparatus applies (those are page-shape concepts for landing pages). The whole app is one page-type:
- App pages (Setup/Control view, Scanning/Live view, any future view): hairline-bordered panel grid, left-biased control column + right-biased primary content, sticky toolbar with a segmented view switch. No enrichment — function carries the page (never add hero imagery/CSS art).
Theme
Custom — cool-cobalt, referenced against the "Tally" modern-minimal SaaS example the user pointed at, adapted toward Cobalt's instrument-panel discipline (hairlines over shadow, mono status readouts) since the app is an operational dashboard, not a marketing hero.
--color-paperoklch(97.8% 0.005 255)--color-paper-1oklch(99.3% 0.003 255)--color-inkoklch(22.0% 0.020 258)--color-ink-2oklch(38.0% 0.016 257)--color-ruleoklch(85.0% 0.010 255)--color-accentoklch(52.0% 0.185 256)--color-focusoklch(48.0% 0.200 256)- Functional state colours (not brand accent — status semantics):
--color-success(149°),--color-warning(68°),--color-danger(25°)
Full token set: frontend/css/tokens.css.
Typography
- Display: Geist, weight 650, normal style
- Body: Geist, weight 400 (single-family discipline — the modern-minimal signature)
- Mono/outlier: Geist Mono, weight 500 — carries exactly one role: status badges, tab labels, meta/hint text, and numeric-readout fields (camera params). Do not reach for it a third role.
- Korean UI copy falls back per-glyph to "Noto Sans CJK KR" / "Noto Sans KR" automatically — Geist covers Latin only.
- Fonts are self-hosted at
frontend/vendor/fonts/(woff2, variable) — never a Google Fonts CDN link. The field PC/phone may have no internet.
Spacing
4-point named scale in tokens.css (--space-3xs … --space-2xl). Always
reference by name.
Motion
- Easings:
--ease-out/--ease-in/--ease-in-out, no bounce/overshoot. - Modern-minimal default: reveals are OFF — this is a repeatedly-used tool, not a first-impression marketing page. The only motion is functional: button press, badge colour transition, tab crossfade, REC pulse dot.
prefers-reduced-motion: reducecollapses everything to ≤150ms opacity (seetokens.css).
Microinteractions stance
- Silent success (no toast for visible state changes).
- Focus rings appear instantly, never animated in.
- No
transition: allanywhere — properties are named explicitly.
Status/health vocabulary — must stay legible in direct sunlight
Every status signal is icon + text + colour, never colour alone (outdoor/colour-blind requirement, not just a Hallmark default):
badge-ok— ✓, success greenbadge-stale— !, warning amberbadge-down— ✕, danger redbadge-rec— pulsing dot, danger red- The ● / state-line idiom (
● 실행 중/● 정지) is carried over from the original PyQt5 tools on purpose — operators already know it.
CTA voice
- Primary actions (시동/녹화 시작): pill radius, solid semantic fill
(
--color-success/--color-danger), 44px min-height (touch target floor). - Secondary/utility actions:
.btn-gray,.btn-blue— same pill shape, neutral or accent fill. - E-STOP: outlined danger pill that fills solid on hover/press — visually distinct from the recording Stop button, since it's a different severity of action (kills every subsystem at once).
What pages MUST share
- The token set in
tokens.css— never an inline OKLCH/hex value. - The badge/status vocabulary above.
- 44px minimum touch target on every interactive control (phone access is a hard requirement, not a nice-to-have).
- Hairline-bordered panels, whisper shadow at most — no drop-shadow cards.
What pages MAY differ on
- Panel layout within a view (the Scanning/LIO view is full-viewport canvas
- status strip, not the Setup view's panel grid — that's fine, it's a different content need, not a different system).
Related tool: the calibration GUI (~/dvlc_gui/dvlc_calib_gui.py)
Native PyQt5, out of scope for a web port (Phase 2, if ever). Its Qt
stylesheet was updated to reuse this same palette (sRGB conversions of the
tokens above) purely for visual consistency across the two tools the
operator switches between — no functional change. See the STYLESHEET
constant and C_* colour constants near the top of that file.