Files
proof-of-work/design/DESIGN.md
T
Keysat 7fda9ceb7e
CI / proof-of-work (Next.js app) (push) Waiting to run
CI / start9/0.4 (StartOS package code) (push) Waiting to run
design: extract document-as-is UI contract (DESIGN.md + DTCG tokens)
Case-B extract of the as-built dark UI into design/: 9-section DESIGN.md,
W3C DTCG tokens.tokens.json, brand/palette.css + assets, and inspiration/
provenance (incl. the rendered red-shade decision). Two owner calls layered
in: red elevated to the single brand accent (#DC2626), two-tier radius
(4px controls / 8px containers). AGENTS.md gains the read-before-UI Design
line + design/ in the layout tree; ROADMAP gains the design-checker cleanup
backlog. No UI code changed.
2026-06-19 12:14:51 -05:00

208 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Proof of Work — Design Brief
The durable brand brief for Proof of Work's user-facing UI. Read this and
[`tokens.tokens.json`](./tokens.tokens.json) before building or changing any UI, and conform
to them. Machine-readable values live in the tokens file and
[`brand/palette.css`](./brand/palette.css); this file is the *why* and the rules.
> **Provenance.** Established 2026-06-19 by a **document-as-is extract** (Case B) of the
> as-built UI — there were no prior brand guidelines; the look grew in the code. Values were
> harvested by frequency census of the Tailwind classes in `proof-of-work/app` +
> `proof-of-work/components` and reconciled with the owner. The only owner-driven *elevations*
> over the literal as-built state: red promoted from error-only to a **brand accent**
> (canonical `#DC2626`), and a documented two-tier radius rule. See
> [`inspiration/README.md`](./inspiration/README.md) for what served as the reference.
---
## 1. Visual theme
**Monochrome gym-brutalist.** A near-black canvas, a single cool-gray (zinc) neutral ramp,
white as the primary voice, and **red as the one accent** — the heat in an otherwise
black-and-white system. The feeling is a piece of gym equipment: heavy, blunt, high-contrast,
no ornament. The product's voice ("Track. Lift. Dominate.") shows up visually as
**UPPERCASE, wide-tracked, condensed display type** and flat, edge-defined surfaces.
It is **not** soft, pastel, glassy, or playful. No gradients-as-decoration, no drop-shadow
depth, no rounded-pill friendliness, no multi-hue palette. Restraint is the brand: color is
rare and earns its place.
## 2. Color palette
The system is **one neutral ramp (zinc) + white/black + four semantic hues**, on a near-black
canvas. Red is both the **brand accent** and the **error/destructive** hue, told apart by
*treatment*, never by a second red (see §7).
**Canvas & surfaces** (depth is built by stepping *up* in lightness, not by shadow):
| Role | Token | Hex | Notes |
|------|-------|-----|-------|
| Canvas (app background) | `color.bg.canvas` | `#0A0A0A` | The PWA `theme_color`; `<body>` bg. The anchor. |
| Surface (cards, panels) | `color.bg.surface` | `#18181B` | zinc-900 — the default raised surface. |
| Surface raised (inputs, chips, hover) | `color.bg.raised` | `#27272A` | zinc-800 — controls and the next step up. |
| Surface inset (rare deep wells) | `color.bg.inset` | `#09090B` | zinc-950 — slightly below canvas. |
**Borders** (the primary depth cue alongside bg layering):
| Role | Token | Hex |
|------|-------|-----|
| Subtle (default hairline) | `color.border.subtle` | `#27272A` (zinc-800) |
| Default | `color.border.default` | `#3F3F46` (zinc-700) |
| Strong (emphasis/hover) | `color.border.strong` | `#52525B` (zinc-600) |
**Text** (on the dark canvas):
| Role | Token | Hex |
|------|-------|-----|
| Primary | `color.text.primary` | `#FFFFFF` |
| Secondary | `color.text.secondary` | `#A1A1AA` (zinc-400) |
| Muted | `color.text.muted` | `#71717A` (zinc-500) |
| Subtle / disabled | `color.text.subtle` | `#52525B` (zinc-600) |
| Inverted (on white surfaces) | `color.text.inverted` | `#000000` |
**Accent / semantic.** The accent and error share `#DC2626`; success/warning/info round out
the state palette. On the dark canvas the *text/icon* tint is the lighter -400 step; fills and
edges use the -600 step; washes use a translucent dark step.
| Role | Token | Hex | Use |
|------|-------|-----|-----|
| **Accent / error (canonical red)** | `color.accent.red` | `#DC2626` (red-600) | Brand emphasis fills/edges **and** destructive intent. |
| Accent hover/pressed | `color.accent.red-strong` | `#B91C1C` (red-700) | |
| Red text/icon on dark | `color.accent.red-text` | `#F87171` (red-400) | Error text, destructive links, accent labels. |
| Red border | `color.accent.red-border` | `#991B1B` (red-800) | Error/destructive outlines. |
| Red wash (bg) | `color.accent.red-wash` | `rgba(127,29,29,.30)` (red-900/30) | Error banners, destructive hover. |
| Success | `color.state.success` | `#34D399` (emerald-400) text / `#059669` (emerald-600) fill | PRs, saved, positive deltas. |
| Warning | `color.state.warning` | `#FBBF24` (amber-400) text / `#78350F` (amber-900) edge | Cautions, cost/limit notices. |
| Info | `color.state.info` | `#60A5FA` (blue-400) text / `#172554` (blue-950) wash | Neutral notices (used sparingly). |
**Primary action color is not a hue — it's white.** The primary button is `#FFFFFF` bg /
`#000000` text (see §4). White, not red, is the loudest thing on screen.
## 3. Typography
Two families, both already wired as CSS variables in `app/layout.tsx`:
- **Display — Bebas Neue** (`var(--font-display)`): condensed, all-caps by nature. Used for
**all headings (h1h3), buttons, and labels**, always `text-transform: uppercase` with
`letter-spacing: 0.05em` (Tailwind `tracking-wider`). This UPPERCASE + tracking pairing is
the single strongest brand signal — ~115 uses in the as-built UI. Don't set body copy in it.
- **Body — Space Grotesk** (`var(--font-sans)`): all running text, form values, data, numbers.
Use `tabular-nums` for stat/metric columns.
**Type scale** (Tailwind rem, the app is data-dense so the small end dominates):
| Token | Size | Typical use |
|-------|------|-------------|
| `font.size.xs` | 12px | Labels, meta, table cells (the workhorse — ~240 uses) |
| `font.size.sm` | 14px | Default body / form text (~175 uses) |
| `font.size.base` | 16px | Comfortable body |
| `font.size.lg` | 18px | Sub-headings |
| `font.size.xl` | 20px | |
| `font.size.2xl` | 24px | Section headings (Bebas) |
| `font.size.3xl` | 30px | Page headings (Bebas) |
| `font.size.4xl` | 36px | Hero / display (Bebas) |
**Weights:** 400 normal, 500 medium (default for emphasis), 600 semibold, 700 bold. Bebas
ships a single weight; weight contrast lives in the body font.
## 4. Component styling
- **Primary button** — `bg-white text-black`, `font-bold uppercase tracking-wider`,
`hover:bg-zinc-200`, `disabled:bg-zinc-700 disabled:text-zinc-500`. Radius 4px (`rounded`),
padding ≈ `px-5 py-2` (inline) / `py-3` (full-width). **This is the signature; keep it white.**
- **Secondary / accent (ghost) button** — transparent bg, `border` + text in the accent red
(`#DC2626` border, `text-red-400`), uppercase tracking. For secondary emphasis (e.g.
"Refine"). Never a *solid* red fill (see §7).
- **Destructive button** — same ghost treatment in red (`text-red-400`/`text-red-500`,
`hover:bg-red-950/30`) or a red-wash block. Destructive is red-as-*outline/wash*, so it never
competes with the white primary.
- **Cards / panels** — `bg-zinc-900`, `border border-zinc-800`, radius 8px (`rounded-lg`),
padding `p-4`. Accent a card by adding a **left edge** `border-l-4` in `#DC2626`.
- **Inputs / selects** — `bg-zinc-800` (or `bg-zinc-900`), `border border-zinc-700`, radius 4px,
white text, `placeholder` in zinc-500, focus ring `ring-white/20``ring-white/30`.
- **Badges / chips** — uppercase, `text-xs`, tracked; semantic ones use the wash pattern
(tinted text + matching translucent bg + matching border at ~45% — e.g. a red "PR" badge:
`text-red-400 bg-red-900/30 border border-red-800`).
- **Active nav / selected state** — accent red: `text-red-400` + a `border-b-2` underline or a
left indicator in `#DC2626`; inactive items in `text-zinc-500`.
- **Error / alert banners** — `bg-red-900/30 border border-red-800 text-red-400`, radius 8px.
## 5. Layout
- **Mobile-first PWA**, portrait-primary, installable. Most styling is unprefixed (mobile);
`sm:` is the dominant breakpoint, `md:` introduces the desktop sidebar.
- **App shell:** a fixed **240px sidebar** on `md+` (`md:pl-[var(--sidebar-width)]` via the
`.app-content` utility) and a **64px bottom nav** on mobile. Top nav height 64px. These three
dimensions are CSS vars in `globals.css` (`--sidebar-width`, `--nav-height`,
`--bottom-nav-height`) — reuse them, don't hardcode.
- **Spacing** follows Tailwind's 4px scale. Dense by intent: `p-4` cards, `px-4 py-3` controls,
`gap-2`/`gap-3` between elements.
- **Content** is the focus; chrome is minimal. Single-column on mobile; the sidebar is the only
persistent chrome on desktop.
## 6. Depth & elevation
**Flat by design — depth comes from background layering + 1px borders, not shadows.** The
surface ladder (`#0A0A0A` canvas → `zinc-900``zinc-800`) plus zinc-700/800 hairlines *is*
the elevation system. Shadows are reserved for **truly floating overlays only** (modals,
popovers, dropdowns) — `shadow-lg`/`shadow-xl`/`shadow-2xl`. Never put a shadow on a static
card; raise it with bg + border instead.
## 7. Do's and don'ts
**Do**
- Keep the **primary button white** (`bg-white text-black`). It's the brand's loudest element.
- Use **UPPERCASE + `tracking-wider` Bebas** for headings, buttons, and labels.
- Reach for **zinc** for every neutral — backgrounds, borders, secondary text.
- Use **`#DC2626` red as the single accent**: active states, emphasis edges, key deltas, links,
the destructive intent. Make it rare and deliberate.
- Build depth with **bg steps + borders**; keep surfaces flat.
- Use the **wash pattern** for semantic blocks (tinted text + translucent bg + matching border).
**Don't**
- ❌ Don't make a **solid red button** — it collides with the white primary *and* with the red
destructive meaning. Red buttons are ghost/outline/wash only. (Red as a solid *fill* is for
small accents — nav indicators, edges, badges — not full buttons.)
- ❌ Don't introduce a **second neutral** (gray/slate/stone) — zinc only. (`gray-100` strays
exist in the code; they're cleanup, not precedent.)
- ❌ Don't introduce a **second red, second green, or second yellow**`#DC2626` red, `emerald`
success, `amber` warning. (`green-*`/`yellow-*` strays are cleanup.)
- ❌ Don't add **decorative shadows or gradients**. Flat only.
- ❌ Don't set **body text in Bebas Neue**, or leave headings/labels lowercase.
- ❌ Don't add a **new arbitrary radius** — controls are 4px, containers 8px (see §8 below).
## 8. Responsive behavior
- **Mobile-first**: author the mobile layout unprefixed, layer desktop with `sm:`/`md:`.
- The **bottom nav** is the mobile primary navigation; the **240px sidebar** replaces it at
`md+`. The main content reserves the sidebar via `md:pl-[var(--sidebar-width)]`.
- Touch targets stay ≥ 44px tall on mobile; the dense `text-xs`/`text-sm` scale is for
*information density*, not for shrinking tap targets.
- Viewport is locked (`maximum-scale=1, user-scalable=no`) — this is an app, not a document.
**Radius scale (canonical):**
| Token | Value | Use |
|-------|-------|-----|
| `radius.control` | 4px (`rounded`) | Buttons, inputs, chips, small elements |
| `radius.container` | 8px (`rounded-lg`) | Cards, panels, modals, banners |
| `radius.full` | 9999px (`rounded-full`) | Pills, avatars, dots |
(`rounded-md`/6px exists in the code as a third value; treat it as drift toward one of the two.)
## 9. Agent prompt guide
When building or editing UI in `proof-of-work/`:
> Build it **monochrome-first**: near-black canvas (`#0A0A0A`), **zinc** for every neutral,
> **white** for primary text and the primary button (`bg-white text-black font-bold uppercase
> tracking-wider`). Headings/labels/buttons are **Bebas Neue, UPPERCASE, `tracking-wider`**;
> body is **Space Grotesk**, dense (`text-xs`/`text-sm`). The **only accent is red `#DC2626`** —
> use it sparingly for active states, emphasis edges (`border-l-4`), key deltas, links, and
> destructive intent; **never as a solid button fill**. Build depth with **background layering +
> 1px zinc borders**, not shadows (shadows are for floating overlays only). Radius: **4px**
> controls, **8px** containers. Pull exact values from `design/tokens.tokens.json` /
> `design/brand/palette.css` rather than re-deriving hexes; reuse the layout CSS vars
> (`--sidebar-width`, `--nav-height`, `--bottom-nav-height`). Stay in the system — no second
> neutral, no second red/green/yellow, no decorative gradients or shadows.