Production-Grade UI Refactor — Design Spec
Date: 2026-04-14 Status: Approved for implementation planning Target: system-design-ultimatum static docs site generated by build-site.js Visual reference: Vercel / Nextra documentation sites — clean, airy, minimal color, strong hierarchy, great code blocks.
Goal
Refactor the site's UI to a production-grade standard: extract CSS/JS from the monolithic build-site.js into real source files, refine the visual language, rework layout and navigation, add standard modern-docs interactions, and meet accessibility/performance expectations — all while preserving every existing feature.
Non-Goals
- No framework migration. The site remains vanilla HTML generated by Node.
- No bundler, PostCSS, Tailwind, or new runtime dependencies.
- No content changes.
- No rewrite of the Excalidraw diagram viewers.
- No change to the search backend (still client-side over
search-index.json).
Features Preserved
Every current feature must continue to work after the refactor:
- Sidebar navigation with collapsible folders
- Client-side search over
search-index.json - Right-rail table of contents
- Persisted dark-mode toggle
- Prev/next page navigation
- Excalidraw diagram viewers
- Mobile drawer navigation
- Dashboard/homepage with category grid
- Breadcrumbs, back-to-top button, reading progress bar
Section 1 — Architecture & File Layout
Extract all styles and client-side JavaScript from the globalStyles template literal and inline <script> blocks in build-site.js into dedicated source files.
assets/
site.css ← all styles (new)
site.js ← all client-side JS (new)
fonts/
Geist-*.woff2 ← self-hosted
GeistMono-*.woff2
build-site.js ← pure HTML generator
docs/assets/ ← generated output (copied at build)
site.<hash>.css
site.<hash>.js
fonts/…
Build flow:
build-site.jsreadsassets/site.cssandassets/site.jsfrom disk.- Computes a short sha1 content hash per file.
- Writes to
docs/assets/site.<hash>.css/.js. - Copies fonts to
docs/assets/fonts/. - Emits
<link rel="stylesheet" href="assets/site.<hash>.css">and<script defer src="assets/site.<hash>.js"></script>in the page template. - The existing
globalStylesconstant and inline scripts are deleted frombuild-site.js.
Why hashed filenames: long-term caching without manual cache-busting, no runtime cost, no tooling required.
Inline exception: a tiny (~10 line) theme-init script stays inline in <head> to prevent FOUC — it reads localStorage.theme and sets data-theme on <html> before first paint.
Section 2 — Visual Language
Typography
- Sans: Geist (self-hosted woff2). Fallback chain:
Geist, Inter, -apple-system, BlinkMacSystemFont, sans-serif. - Mono: Geist Mono. Fallback:
'Geist Mono', 'JetBrains Mono', ui-monospace, monospace. - Body: 15px / 1.7.
- Scale: h1 2rem, h2 1.5rem, h3 1.2rem, h4 1rem.
- Negative letter-spacing on headings (-0.02em).
- Generous top margins on h2/h3 for section separation.
- Max content width: 720px (down from 860).
Color tokens
Light theme:
--bg: #ffffff--bg-subtle: #fafafa--bg-muted: #f4f4f5--text: #0a0a0a--text-secondary: #52525b--text-muted: #a1a1aa--border: #e4e4e7--border-strong: #d4d4d8--accent: #0070f3--accent-bg: rgba(0, 112, 243, 0.08)
Dark theme:
--bg: #0a0a0a--bg-subtle: #111111--bg-muted: #18181b--text: #fafafa--text-secondary: #a1a1aa--text-muted: #71717a--border: #27272a--border-strong: #3f3f46--accent: #3b82f6--accent-bg: rgba(59, 130, 246, 0.12)
One accent color only. No purple/teal/amber.
Surfaces & depth
- Flat by default. Borders over shadows.
- One shadow token
--shadow-lgfor elevated surfaces (Cmd-K modal, search dropdown). - Radii: 6px default, 10px cards, 4px inline code.
Motion
- 150ms ease for hover/focus.
- 200ms for theme attribute transition.
- No
transform: translateYon hover (drop card-lift). - Focus ring: 2px accent, 2px offset, visible in both themes.
@media (prefers-reduced-motion: reduce): disable all transitions and smooth scroll.
Code blocks
- Dark background in both themes (
#0a0a0a). - Subtle 1px border.
- Top-right copy button, top-left language label (from first line or lang class).
- Line-height 1.65, 14px.
- Inline code:
--bg-mutedbackground,--textcolor (not accent), 4px radius.
Callouts (info / warning / success)
- Left border 3px in the tone color.
- Tinted background derived from that tone at ~8% alpha.
- Same typography as body.
Section 3 — Layout & Navigation
Top bar (new)
- Sticky, 56px tall, full-width, z-index above content.
backdrop-filter: blur(8px)with translucent bg so content shows through.- Border-bottom
--border. - Contents: brand link (left) · Cmd-K search trigger (center-left) · spacer · theme toggle · GitHub link (right).
- The search trigger is a button that shows "Search…" + ⌘K kbd hint and opens the palette on click.
Left sidebar
- 280px wide, starts flush under the top bar, independent scroll.
- No header inside — just nav content.
- Folders rendered as
<details>with persisted open/closed state inlocalStorageunder keynav:<folder-slug>. - The folder containing the current page always force-opens on load.
- Active page link: 2px accent left-border +
--accent-bg, not a pill. - Section labels: uppercase 11px,
letter-spacing: 0.08em,--text-muted.
Right TOC
- 240px wide, sticky below top bar.
- Shown at viewport ≥ 1280px. Hidden below.
- Same visual treatment as the sidebar (1px left-border on the nav, active item gets accent color + medium weight).
- Scroll-spy: IntersectionObserver on
h2/h3inside.content-body,rootMargin: "-80px 0px -70% 0px". Active heading's TOC link gets.is-active. - Click smooth-scrolls to target (unless
prefers-reduced-motion).
Content column
- Centered,
max-width: 720px. - Breadcrumb row above h1 (smaller,
--text-muted). - Headings h2/h3/h4 with an
idget a hover-visible<a class="heading-anchor" href="#id">#</a>appended. Clicking copies the full URL and updateslocation.hash. - Prev/next at bottom inside a bordered two-card row.
Mobile (< 768px)
- Top bar stays visible; left hamburger replaces brand area actions.
- Sidebar becomes a drawer that slides in from the left, with a backdrop overlay.
- TOC hidden.
- Content padding reduces to 1rem.
- Cmd-K palette still works; the top-bar search trigger opens it full-screen.
Removed / replaced
- Search input inside sidebar → replaced by top-bar trigger + Cmd-K modal.
- Theme toggle inside sidebar → moved to top bar.
- Floating
.kbd-hintpill bottom-left → removed; shortcuts documented in Cmd-K footer. - Reading progress bar kept, but thinner (2px) and solid accent.
Section 4 — Interactions & Client JS
All vanilla JS in assets/site.js. No dependencies. Modules below; each is a small IIFE or function called from a single init() on DOMContentLoaded.
1. Theme
- Inline
<head>script readslocalStorage.theme(ormatchMedia('(prefers-color-scheme: dark)')) and setsdata-themeon<html>before paint. - Top-bar toggle button updates storage and the attribute. Dispatches a
themechangecustom event for any listeners.
2. Command palette
- Overlay modal, 640px wide, centered, fades in 150ms.
- Triggers:
⌘K,Ctrl+K,/(when not focused in an input), click on top-bar search trigger. - Dismiss:
Esc, backdrop click. - Input fetches
search-index.json(cached after first load). Fuzzy-match over title + path + snippet fields. Highlights matched substring with<mark>. - Result rows: title, folder breadcrumb, snippet.
- Keyboard:
↑/↓navigate,↵open,⌘↵/Ctrl+↵open in new tab. - Empty state: recent pages (last 5 from
localStorage.recent). - Footer row shows keybinds.
role="dialog",aria-modal="true", focus trap, restores focus on close.
3. TOC scroll-spy
- IntersectionObserver over all
h2,h3inside.content-bodythat haveids. rootMargin: "-80px 0px -70% 0px".- Add
.is-activeto the matching TOC link; remove from others.
4. Sidebar folder persistence
- On
<details>toggleevent, writelocalStorage["nav:<slug>"] = "open" | "closed". - On load, restore each folder's state; always force-open the folder containing the current page.
5. Copy-code buttons
- For each
<pre>in.content-body, wrap in a relative container and inject aCopybutton top-right. - Click copies
pre.textContentvianavigator.clipboard.writeText, swaps label toCopiedfor 1.5s.
6. Heading anchor links
- For each
h2/h3/h4with anid, append a#link. - CSS shows it on hover of the heading.
- Click copies full URL to clipboard and updates
history.replaceStatehash.
7. Reading progress bar
- Scroll listener (rAF-throttled). Computes
window.scrollY / (document.documentElement.scrollHeight - window.innerHeight). Sets--progressCSS variable on the bar element.
8. Back-to-top
- Shows after 400px scroll via class toggle.
- Click smooth-scrolls to top (respects reduced-motion).
9. Mobile drawer
- Hamburger button toggles
nav-openclass on<html>. - Backdrop click /
Esccloses. - Focus trap within the drawer while open.
10. Keyboard shortcuts
⌘K/Ctrl+K//→ open palettet→ toggle theme (only when no input focused)g h→ navigate to homeEsc→ close palette or drawer
Section 5 — Accessibility, Performance, Build
Accessibility
- Semantic landmarks:
<header>,<nav aria-label="Primary">,<main>,<aside aria-label="On this page">,<footer>. - All interactive controls are real
<button>or<a>; focus ring always visible. - Skip-to-content link, visible on focus.
- Cmd-K modal:
role="dialog"+aria-modal="true"+ focus trap + focus restore on close. - WCAG AA color contrast in both themes — token values above are chosen to meet this for body and secondary text.
@media (prefers-reduced-motion: reduce): disable transitions and smooth scroll.
Performance
- Self-hosted Geist + Geist Mono woff2 with
font-display: swap, preloaded via<link rel="preload" as="font" crossorigin>. Drops the Google Fonts CDN request. - CSS and JS shipped as content-hashed static files for long-term caching.
site.jsloaded withdefer.- Inline
<head>theme-init script is tiny (~10 lines) to prevent FOUC. - Images keep
loading="lazy".
Build changes to build-site.js
- New
loadAssets()helper: readsassets/site.cssandassets/site.js, computescrypto.createHash('sha1').update(contents).digest('hex').slice(0, 8)per file, writes todocs/assets/site.<hash>.cssand.js, returns{ cssHref, jsHref }. - New
copyFonts()helper: copiesassets/fonts/*todocs/assets/fonts/. - The
globalStylestemplate literal is deleted. - Inline
<script>blocks embedded in page templates are deleted (moved intosite.js). - Layout helpers take
cssHref/jsHrefand emit<link>and<script defer>tags. - No new npm dependencies.
Out of scope (explicitly not changed)
- Markdown → HTML conversion logic
- Excalidraw diagram rendering
search-index.jsongeneration- Folder tree / navigation generation
- Dashboard / category card data
Acceptance Criteria
- Every feature listed in "Features Preserved" continues to work.
build-site.jsno longer contains theglobalStylestemplate literal or any inline client-side<script>blocks (except the small theme-init FOUC-prevention script).assets/site.cssandassets/site.jsexist as editable source files.- Generated pages link to
docs/assets/site.<hash>.cssand.jswith a content-hashed filename. - Cmd-K / Ctrl-K /
/opens a command palette that searchessearch-index.json. - TOC right-rail highlights the currently visible section as the user scrolls.
- Sidebar folder open/closed state persists across page loads.
- Each
<pre>has a working copy button. - Each
h2/h3/h4with anidhas a hover-visible anchor link. - Theme toggle lives in the top bar, persists, and does not FOUC on reload.
- No regression in mobile layout; sidebar drawer works.
- WCAG AA contrast met in both themes for primary and secondary text.
prefers-reduced-motiondisables transitions and smooth scroll.- No new runtime dependencies introduced.