/* Mount the document sections into their roots. */ const { createRoot } = ReactDOM; /* ───────── Critique findings ───────── */ function CritiqueSection() { const findings = [ { sev: 'hi', tag: 'Color', h: 'Rainbow KPIs carry no hierarchy', p: 'Six dashboard tiles each use a different hot color (#eb3349, #11998e, #f7971e, #4facfe, #a855f7, #667eea). Everything shouts equally, so nothing reads as urgent. The color palette is a legend, not a signal.' }, { sev: 'hi', tag: 'Hierarchy', h: 'Decorative icon bubbles everywhere', p: '48px pastel circles with duplicated iconography sit next to every metric on Customers, Jobs, Invoices, Equipment. They double the card height without carrying new information — the label already says what it is.' }, { sev: 'hi', tag: 'Layout', h: 'Welcome gradient steals attention', p: 'The "Welcome Back!" banner uses the full sidebar gradient as background — it dominates the page with a decorative greeting when this is a daily operations tool. People already know their name.' }, { sev: 'md', tag: 'Type', h: 'No numeric typography', p: 'All tabular numbers — job counts, A/R amounts, due dates — render in the same proportional Inter as body text. Columns don\'t line up, and currency scanning is slow. A tabular/mono face for numerals would fix this cheaply.' }, { sev: 'md', tag: 'Density', h: 'Two stat strips on every list page', p: 'Jobs, Customers, Invoices each render a desktop "stats cards" row AND a mobile "stats compact" row. That\'s 120px of chrome before the actual table. The same counts already belong on the dashboard.' }, { sev: 'md', tag: 'Consistency', h: 'Twelve sidebar palettes, one default', p: 'The sidebar ships with 12 preset gradients (ocean, forest, slate, crimson, midnight, purple…) and a light/dark toggle. That\'s surface personalisation where you need identity. Pick one sidebar treatment and invest there.' }, { sev: 'md', tag: 'Motion', h: 'Glowing critical badges', p: 'badge-glow-critical pulses a red shadow forever. Motion should bring attention once, not loop. It\'s visual noise that trains users to tune out the red.' }, { sev: 'lo', tag: 'Forms', h: 'Inputs render with #9ca3af borders always', p: 'Form controls use a uniform 1.5px gray border regardless of state. There\'s no way for a required-but-empty field to distinguish itself from a focused one until you tab in. Low-cost lift with big payoff.' }, { sev: 'lo', tag: 'Brand', h: 'No visual ownership of the domain', p: 'Nothing on screen says "this is for a powder coating shop." Powder color chips, cure temperatures, oven state, batch numbers — the core vocabulary — doesn\'t surface in the UI. The product feels like generic small-business SaaS.' }, ]; return (
01 · Critique

Where the current design leaks value

Nothing below is a bug — the app works. But each of these is a small everyday tax: on legibility, on trust, on the sense that the tool belongs to a shop that takes craft seriously.

{findings.map((f, i) => (
{f.tag}

{f.h}

{f.p}

))}
); } /* ───────── Design system strip ───────── */ function SystemSection() { return (
02 · Direction

A calmer, more industrial voice

Warm neutrals, a single ember accent, Inter + IBM Plex Mono for numerics, Fraunces for occasional display type. The goal is "shop tool, not admin template."

Foundations
Ink#0F0F10
Slate#3A3A3E
Steel#6B6B70
Rule#E4E2DC
Paper 2#F3F1EB
Paper#FAFAF7
Signal
Ember · accentoklch 0.68 / 0.17 / 50
OK · on track0.62 / 0.11 / 155
Warn · tight0.76 / 0.13 / 80
Bad · overdue0.60 / 0.19 / 25
Cool · queued0.58 / 0.09 / 240

Shared chroma & lightness across hues. Status chips use the tinted variant + dot, never a full saturated fill.

Type
Display · Fraunces 500
12 jobs on the floor
UI · Inter 500 / 400
Rodriguez Metal
Fence gate — anodized black
Numerics · IBM Plex Mono
$12,450.00 · J-2041
Principles
  1. One accent, one loud. Orange earns attention. Status colors whisper.
  2. Numerics are monospace. Columns align; currency scans.
  3. Borders, not shadows. 1px rules on paper — reads like a spec sheet.
  4. Domain on screen. Powder swatches, cure temps, batch IDs.
  5. No decorative motion. Animation marks change, not state.
); } /* ───────── Screen chapters ───────── */ function ScreenChapter({ num, title, lede, before, after, delta }) { return (
{num}

{title}

{lede}

{before}} after={{after}} delta={delta} />
); } /* ───────── Wrap-up ───────── */ function WrapSection() { return (
06 · Next

If you want to ship this, start here

Ordered by impact per hour of work. The first three are the ones that carry 80% of the visual-quality lift — the rest compound over time.

{[ ['01', 'Replace the KPI palette and icon bubbles', 'Kill the six hot colors and the pastel circles. One monospace number, one label. Dashboard + every list page inherits the fix.', '½ day'], ['02', 'Pick one typeface for numerics', 'Drop IBM Plex Mono (or similar) into site.css and apply to .mono, tables, KPIs. Alignment fixes itself.', '2 h'], ['03', 'Demote the welcome banner', 'Turn the gradient block into a single line of kerned text + date. Reclaim 140px of dashboard real estate.', '2 h'], ['04', 'Introduce status chips as a component', 'One ts/cs partial: dot + tinted pill + Plex label. Reuse on Jobs, Invoices, Maintenance, Equipment.', '1 day'], ['05', 'Retire 11 of the 12 sidebar gradients', 'Ship Ink + Paper (dark/light) only. Personalization is a feature for consumer apps, not shop ops.', '1 h'], ['06', 'Add powder swatches to any powder reference', 'Tiny 10px swatch next to every powder name on Jobs, Inventory, Board, Oven. Free branding.', '½ day'], ['07', 'Command bar (⌘K)', 'Sidebar has 40+ items. A fuzzy jump-to turns 2 clicks into 0. Biggest productivity lift on the list.', '2 days'], ].map((r, i) => (
{r[0]}
{r[1]}
{r[2]}
{r[3]}
))}
); } /* ───────── Mount ───────── */ createRoot(document.getElementById('critique-root')).render(); createRoot(document.getElementById('system-root')).render(); createRoot(document.getElementById('screen1-root')).render( } after={} delta={{ wrong: [ 'Gradient "Welcome Back" banner dominates the fold', 'Six KPI tiles in six different accent colors, no ranking', 'Icon bubbles repeat the label', 'A/R aging uses four more unrelated colors', ], change: [ 'Opens with a single prose summary of the day', 'Core finance row sits beside it, monospace numerals', '"Needs attention" surfaces only items < 48 h or blocked', 'Floor activity feed grounds the dashboard in real events', ], }} /> ); createRoot(document.getElementById('screen2-root')).render( } after={} delta={{ wrong: [ '4× stat cards with icon bubbles eat 130px', 'Status and priority pills are hard to tell apart', 'Job IDs styled as purple links, no hierarchy with description', 'No saved/quick views, filters hidden behind a button', ], change: [ 'Single 5-metric strip, monospace numerals, sparkline-less', 'Tabular status chips: dot + tint + Plex label', 'Job ID in mono, description bold, customer secondary, hot jobs carry a left bar', 'Quick views inline (All / Due ≤48h / On floor / Ready / Mine) + ⌘K shortcut hint', ], }} /> ); createRoot(document.getElementById('screen3-root')).render( } after={} delta={{ wrong: [ 'Every column uses a different pill color (red for Coating, purple for Curing…)', 'Priority encoded as a 4px colored left border — same visual weight as column tint', 'No domain cues: "powder" is just text, due dates aren\'t scannable', 'Kanban chrome (gray column tint) takes space without adding info', ], change: [ 'Columns carry a Plex Mono label and a count badge — no chromatic encoding', 'Hot jobs get a single red edge; everything else is mono', 'Every card shows a powder swatch + parts count + priority dot in a tight footer', 'Curing column carries a live oven meter (180 °C · 14 min)', ], }} /> ); createRoot(document.getElementById('wrap-root')).render();