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.
This commit is contained in:
@@ -5,6 +5,9 @@ Self-hosted multi-user workout logger (Next.js app) packaged as a StartOS 0.4 `s
|
|||||||
> **Inbox check:** At session start, if `~/Projects/standards/INBOX.md` exists, scan it for
|
> **Inbox check:** At session start, if `~/Projects/standards/INBOX.md` exists, scan it for
|
||||||
> items tagged `(proof-of-work)` and surface them before proposing next steps; triage with `/triage`.
|
> items tagged `(proof-of-work)` and surface them before proposing next steps; triage with `/triage`.
|
||||||
|
|
||||||
|
> **Design:** before building or changing any user-facing UI, read `design/DESIGN.md` and
|
||||||
|
> `design/tokens.tokens.json` and conform to them.
|
||||||
|
|
||||||
## Stack (versions that matter)
|
## Stack (versions that matter)
|
||||||
|
|
||||||
- **Next.js 15** (App Router, server components + server actions, SSE streaming) — dynamic request APIs are async (see Conventions)
|
- **Next.js 15** (App Router, server components + server actions, SSE streaming) — dynamic request APIs are async (see Conventions)
|
||||||
@@ -31,6 +34,7 @@ start9/0.4/ ← StartOS packaging wrapper
|
|||||||
docker_entrypoint.sh ← boot: first-boot seed, additive ALTERs, library reconcile
|
docker_entrypoint.sh ← boot: first-boot seed, additive ALTERs, library reconcile
|
||||||
Makefile / s9pk.mk ← s9pk build (ARCHES := x86)
|
Makefile / s9pk.mk ← s9pk build (ARCHES := x86)
|
||||||
startos/versions/ ← one file per ExVer version + index.ts (the version graph)
|
startos/versions/ ← one file per ExVer version + index.ts (the version graph)
|
||||||
|
design/ ← UI design contract: DESIGN.md + tokens.tokens.json (read before UI work); brand/ inspiration/
|
||||||
~/.proof-of-work/ ← publish.sh + unpublish.sh (NOT in repo; self-hosted registry)
|
~/.proof-of-work/ ← publish.sh + unpublish.sh (NOT in repo; self-hosted registry)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+14
@@ -36,6 +36,20 @@ Done in 1.1.0:9 (P2 batch): input-validation 500s → 400 (`lib/http.ts readJson
|
|||||||
- CSV export↔import round-trip: export writes `setX`-prefixed headers (`setCalories`/`setWatts`/`setNotes`) the importer doesn't read (it expects `calories`/`watts`/`notes`), so the app's own CSV export silently drops those on re-import (calories long-standing; watts since 1.2.0:4). Fix by aligning export header names with the parser, or adding the prefixed names as `knownColumns` aliases. (JSON account export/import round-trips fine.)
|
- CSV export↔import round-trip: export writes `setX`-prefixed headers (`setCalories`/`setWatts`/`setNotes`) the importer doesn't read (it expects `calories`/`watts`/`notes`), so the app's own CSV export silently drops those on re-import (calories long-standing; watts since 1.2.0:4). Fix by aligning export header names with the parser, or adding the prefixed names as `knownColumns` aliases. (JSON account export/import round-trips fine.)
|
||||||
- Charts/progress views over history (the data and 1RM estimates already exist).
|
- Charts/progress views over history (the data and 1RM estimates already exist).
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
Cleanup backlog from the 2026-06-19 design-contract extract (`design/DESIGN.md` + `tokens.tokens.json`). The contract documents the *intended* system; these are the spots where the code diverges. All cosmetic/mechanical — none ship-blocking. Found by the `design-checker` agent (re-run it for fresh file:line lists).
|
||||||
|
|
||||||
|
- **Unify off-palette neutrals → zinc** (21 hits, 12 files): every `hover:bg-gray-100`/`active:bg-gray-200`/`hover:text-gray-200` on the white primary button → `zinc-200`/`zinc-300`. Canonical hover is `zinc-200` (`color.action.primary-hover-bg`). Worst: `app/main/workouts/page.tsx` (4), auth `LoginForm`/`SignupForm`, `dashboard/page.tsx`, `programs/page.tsx`.
|
||||||
|
- **Unify success hue → emerald** (10 hits): `green-*` → `emerald-*`. Worst: `components/settings/SettingsForm.tsx` (6), `WorkoutForm.tsx:696`, `SetRow.tsx:477`, `ExerciseCard.tsx:12`.
|
||||||
|
- **Unify warning hue → amber** (13 hits): `yellow-*` → `amber-*`. Worst: `app/main/import/page-csv.tsx` (6), `SettingsForm.tsx` (6), `ExerciseCard.tsx:14`.
|
||||||
|
- **No solid red button** (1): `components/settings/DangerZone.tsx:101` uses `bg-red-700` — convert to the ghost/wash destructive treatment (`text-red-400 border-red-800 hover:bg-red-950/30`). (Red elsewhere — 65 wash/outline/text uses — already conforms.)
|
||||||
|
- **Radius two-tier (4px control / 8px container)**: `rounded-md` (6px, 27 hits, mostly `SetRow.tsx` ×16 + `WorkoutForm.tsx` ×9) → `rounded`; `rounded-lg` on primary *buttons* (5 hits in `workouts/page.tsx`, `dashboard/page.tsx`) → `rounded`. (`rounded-full` on the FAB is fine.)
|
||||||
|
- **Shadows are overlay-only**: drop `shadow-2xl` on the static login/signup cards (`app/auth/{login,signup}/page.tsx:23`) and `shadow-lg` on the FAB + desktop CTA (`workouts/page.tsx:163,174`) — depth = bg layering + border.
|
||||||
|
- **Sub-scale font sizes** (10 hits): `text-[10px]`/`text-[11px]` (below the 12px `text-xs` floor) → `text-xs`.
|
||||||
|
- **Extract a shared `<Button>`** (`components/common/` is empty): the white primary pattern is inlined 44× across 21 files — the structural reason the `gray-100` drift spread everywhere. A single component is the durable fix and the single point to enforce the contract. (Biggest item; do after the mechanical sweeps.)
|
||||||
|
- **Token wiring decision**: `design/brand/palette.css` (`--pow-*` vars) isn't imported anywhere. For a Tailwind app the class names already *are* the tokens (hexes match the zinc/emerald/amber ramp), so the pragmatic path is to keep using Tailwind names and treat `palette.css`/`tokens.tokens.json` as the canonical cross-reference (and for any raw-CSS context). Alternatively import `palette.css` at the root and migrate the ~30 inlined `bg-[#0A0A0A]` canvas hexes to `var(--pow-bg-canvas)`. Decide before doing a hex→var sweep.
|
||||||
|
|
||||||
## Hygiene
|
## Hygiene
|
||||||
|
|
||||||
- Delete the legacy `start9/0.4/workout-log_x86_64.s9pk` build artifact; drop unused `bcryptjs` from `start9/0.4/package.json`.
|
- Delete the legacy `start9/0.4/workout-log_x86_64.s9pk` build artifact; drop unused `bcryptjs` from `start9/0.4/package.json`.
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
# 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 (h1–h3), 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.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
|
<image href="/icons/gemini-kettlebell.png" width="1024" height="1024"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 146 B |
Binary file not shown.
|
After Width: | Height: | Size: 593 KiB |
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Proof of Work — canonical design tokens as CSS custom properties.
|
||||||
|
* Generated-by-hand mirror of design/tokens.tokens.json (the source of truth).
|
||||||
|
* Import once at the app root so surfaces reference var(--pow-*) instead of inlining hexes.
|
||||||
|
* Hexes are the literal Tailwind values the as-built UI already uses (neutral ramp = zinc).
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Backgrounds / surfaces (depth = stepping up in lightness, not shadow) */
|
||||||
|
--pow-bg-canvas: #0A0A0A; /* app background; PWA theme_color */
|
||||||
|
--pow-bg-surface: #18181B; /* zinc-900 — cards, panels */
|
||||||
|
--pow-bg-raised: #27272A; /* zinc-800 — controls, chips, hover */
|
||||||
|
--pow-bg-inset: #09090B; /* zinc-950 — rare deep wells */
|
||||||
|
|
||||||
|
/* Borders */
|
||||||
|
--pow-border-subtle: #27272A; /* zinc-800 — default hairline */
|
||||||
|
--pow-border-default: #3F3F46; /* zinc-700 */
|
||||||
|
--pow-border-strong: #52525B; /* zinc-600 — emphasis */
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--pow-text-primary: #FFFFFF;
|
||||||
|
--pow-text-secondary: #A1A1AA; /* zinc-400 */
|
||||||
|
--pow-text-muted: #71717A; /* zinc-500 */
|
||||||
|
--pow-text-subtle: #52525B; /* zinc-600 — disabled */
|
||||||
|
--pow-text-inverted: #000000; /* on white surfaces */
|
||||||
|
|
||||||
|
/* Accent / error (one canonical red, told apart by treatment) */
|
||||||
|
--pow-accent-red: #DC2626; /* red-600 — accent fills/edges + destructive */
|
||||||
|
--pow-accent-red-strong: #B91C1C; /* red-700 — hover/pressed */
|
||||||
|
--pow-accent-red-text: #F87171; /* red-400 — red text on dark */
|
||||||
|
--pow-accent-red-border: #991B1B; /* red-800 — outlines */
|
||||||
|
--pow-accent-red-wash: rgba(127, 29, 29, 0.30); /* red-900/30 — banners/hover */
|
||||||
|
|
||||||
|
/* Other semantic state */
|
||||||
|
--pow-success: #34D399; /* emerald-400 */
|
||||||
|
--pow-success-fill: #059669; /* emerald-600 */
|
||||||
|
--pow-warning: #FBBF24; /* amber-400 */
|
||||||
|
--pow-warning-edge: #78350F; /* amber-900 */
|
||||||
|
--pow-info: #60A5FA; /* blue-400 */
|
||||||
|
--pow-info-wash: #172554; /* blue-950 */
|
||||||
|
|
||||||
|
/* Primary action = white, not a hue */
|
||||||
|
--pow-action-primary-bg: #FFFFFF;
|
||||||
|
--pow-action-primary-text: #000000;
|
||||||
|
--pow-action-primary-hover-bg: #E4E4E7; /* zinc-200 */
|
||||||
|
--pow-action-primary-disabled-bg: #3F3F46; /* zinc-700 */
|
||||||
|
--pow-action-primary-disabled-text: #71717A;/* zinc-500 */
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--pow-font-display: var(--font-display), 'Bebas Neue', sans-serif;
|
||||||
|
--pow-font-body: var(--font-sans), 'Space Grotesk', system-ui, sans-serif;
|
||||||
|
--pow-tracking-wider: 0.05em;
|
||||||
|
|
||||||
|
/* Radius (two-tier) */
|
||||||
|
--pow-radius-control: 4px; /* buttons, inputs, chips */
|
||||||
|
--pow-radius-container: 8px; /* cards, panels, modals */
|
||||||
|
--pow-radius-full: 9999px; /* pills, avatars */
|
||||||
|
|
||||||
|
/* Layout (mirrors globals.css; the app already defines these) */
|
||||||
|
--pow-sidebar-width: 240px;
|
||||||
|
--pow-nav-height: 64px;
|
||||||
|
--pow-bottom-nav-height: 64px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Inspiration & provenance
|
||||||
|
|
||||||
|
Proof of Work's design was **extracted document-as-is** (Case B) from the existing code on
|
||||||
|
2026-06-19 — there were no prior brand guidelines and no external reference set. So for this
|
||||||
|
repo the "inspiration" is the **as-built UI itself**, plus the brand mark. This folder records
|
||||||
|
the *why/where* behind the contract.
|
||||||
|
|
||||||
|
## De-facto references (the source the look was harvested from)
|
||||||
|
|
||||||
|
- **Brand mark** — [`../brand/logo-kettlebell.png`](../brand/logo-kettlebell.png): a pure-white
|
||||||
|
kettlebell silhouette on near-black. It establishes the core identity directly: **monochrome,
|
||||||
|
white-on-black, no color in the mark.** Everything else follows from this.
|
||||||
|
- **As-built styling surfaces** (where every value was censused by Tailwind-class frequency):
|
||||||
|
- `proof-of-work/app/globals.css` — base bg, heading treatment, scrollbar, layout vars.
|
||||||
|
- `proof-of-work/app/layout.tsx` — fonts (Bebas Neue display, Space Grotesk body), theme color.
|
||||||
|
- `proof-of-work/tailwind.config.ts` — font-family wiring, spacing/radius extensions.
|
||||||
|
- `proof-of-work/components/**` + `proof-of-work/app/main/**` — the inline utility classes that
|
||||||
|
were frequency-ranked to find the canonical neutral (zinc), surface ladder, type scale,
|
||||||
|
radii, and the white-primary / red-error patterns.
|
||||||
|
- `proof-of-work/public/manifest.json` — `theme_color`/`background_color` `#0A0A0A` (the
|
||||||
|
external anchor that confirmed the canonical canvas color).
|
||||||
|
|
||||||
|
## Owner decisions captured in the reconcile (2026-06-19)
|
||||||
|
|
||||||
|
The extract was literal except for two owner-driven calls:
|
||||||
|
|
||||||
|
1. **Red promoted to a brand accent.** As-built, red was error/destructive only. The owner chose
|
||||||
|
to elevate it to *the* accent (still keeping white as the primary button). Canonical red =
|
||||||
|
**`#DC2626`** (Tailwind red-600, "Blood Red"), which also re-tints the error states so the UI
|
||||||
|
carries a single coherent red.
|
||||||
|
- The candidates that were compared on the real `#0A0A0A` background:
|
||||||
|
[`red-accent-candidates.png`](./red-accent-candidates.png) (rendered from
|
||||||
|
[`red-accent-candidates.html`](./red-accent-candidates.html)). Options were Signal `#EF4444`,
|
||||||
|
Blood `#DC2626` *(chosen)*, Vermilion `#FF3B30`, Crimson `#E11D48`.
|
||||||
|
2. **Two-tier radius rule.** The code mixed 4px (`rounded`) and 8px (`rounded-lg`) with no rule;
|
||||||
|
the owner adopted **4px for controls, 8px for containers**.
|
||||||
|
|
||||||
|
Everything else (zinc as the one neutral, white primary button, Bebas-uppercase-tracked
|
||||||
|
headings, flat/border-based depth, dense small type scale) is the as-built look, documented
|
||||||
|
faithfully in [`../DESIGN.md`](../DESIGN.md).
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
* { margin:0; padding:0; box-sizing:border-box; }
|
||||||
|
body {
|
||||||
|
background:#0A0A0A; color:#fff;
|
||||||
|
font-family:'Space Grotesk', system-ui, sans-serif;
|
||||||
|
padding:36px 40px 44px;
|
||||||
|
-webkit-font-smoothing:antialiased;
|
||||||
|
}
|
||||||
|
.display { font-family:'Bebas Neue', sans-serif; letter-spacing:.05em; text-transform:uppercase; }
|
||||||
|
h1 { font-family:'Bebas Neue',sans-serif; letter-spacing:.06em; font-size:34px; }
|
||||||
|
.sub { color:#a1a1aa; font-size:13px; margin-top:4px; margin-bottom:26px; }
|
||||||
|
.row {
|
||||||
|
display:grid; grid-template-columns:200px 1fr; gap:28px;
|
||||||
|
align-items:center;
|
||||||
|
padding:22px 0; border-top:1px solid #27272a;
|
||||||
|
}
|
||||||
|
/* left: raw swatch + name */
|
||||||
|
.swatch { height:120px; border-radius:8px; }
|
||||||
|
.name { font-family:'Bebas Neue',sans-serif; letter-spacing:.06em; font-size:26px; margin-top:12px; }
|
||||||
|
.hex { color:#71717a; font-size:13px; font-variant-numeric:tabular-nums; }
|
||||||
|
|
||||||
|
/* right: vignette strip */
|
||||||
|
.vignette { display:flex; gap:20px; align-items:stretch; flex-wrap:nowrap; }
|
||||||
|
.card {
|
||||||
|
background:#18181b; border:1px solid #27272a; border-left:4px solid var(--a);
|
||||||
|
border-radius:8px; padding:14px 16px; width:230px;
|
||||||
|
}
|
||||||
|
.card .label { font-family:'Bebas Neue',sans-serif; letter-spacing:.12em; font-size:13px; color:var(--a); }
|
||||||
|
.card .h { font-family:'Bebas Neue',sans-serif; letter-spacing:.05em; font-size:22px; margin-top:6px; }
|
||||||
|
.card .stat { display:flex; align-items:baseline; gap:8px; margin-top:8px; }
|
||||||
|
.card .stat .n { font-size:30px; font-weight:700; font-variant-numeric:tabular-nums; }
|
||||||
|
.card .stat .d { color:var(--a); font-size:14px; font-weight:600; }
|
||||||
|
|
||||||
|
.col { display:flex; flex-direction:column; gap:12px; justify-content:center; }
|
||||||
|
|
||||||
|
/* nav active state */
|
||||||
|
.nav { display:flex; gap:18px; align-items:center; }
|
||||||
|
.nav a { font-family:'Bebas Neue',sans-serif; letter-spacing:.1em; font-size:14px; color:#71717a; padding-bottom:6px; }
|
||||||
|
.nav a.active { color:var(--a); border-bottom:2px solid var(--a); }
|
||||||
|
|
||||||
|
/* badge (accent wash) */
|
||||||
|
.badge {
|
||||||
|
display:inline-block; font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:.08em;
|
||||||
|
color:var(--a); background:color-mix(in srgb, var(--a) 15%, transparent);
|
||||||
|
border:1px solid color-mix(in srgb, var(--a) 45%, transparent);
|
||||||
|
padding:4px 10px; border-radius:4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buttons */
|
||||||
|
.btns { display:flex; gap:10px; }
|
||||||
|
.btn-primary {
|
||||||
|
background:#fff; color:#000; font-weight:700; text-transform:uppercase; letter-spacing:.08em;
|
||||||
|
font-size:12px; padding:9px 16px; border-radius:4px; border:none;
|
||||||
|
}
|
||||||
|
.btn-accent {
|
||||||
|
background:transparent; color:var(--a); border:1px solid var(--a);
|
||||||
|
font-weight:700; text-transform:uppercase; letter-spacing:.08em;
|
||||||
|
font-size:12px; padding:9px 16px; border-radius:4px;
|
||||||
|
}
|
||||||
|
.reflabel { font-size:10px; color:#52525b; text-transform:uppercase; letter-spacing:.1em; margin-top:6px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Proof of Work — red accent candidates</h1>
|
||||||
|
<div class="sub">All on the app background #0A0A0A. White stays the primary button (reference, same in every row); red is used only as an accent: labels, active nav, card edge, stat delta, ghost button, badge.</div>
|
||||||
|
|
||||||
|
<!-- A -->
|
||||||
|
<div class="row" style="--a:#EF4444">
|
||||||
|
<div>
|
||||||
|
<div class="swatch" style="background:#EF4444"></div>
|
||||||
|
<div class="name">A · Signal Red</div>
|
||||||
|
<div class="hex">#EF4444 · Tailwind red-500 (in-family)</div>
|
||||||
|
</div>
|
||||||
|
<div class="vignette">
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">Personal Record</div>
|
||||||
|
<div class="h">Back Squat</div>
|
||||||
|
<div class="stat"><span class="n">142.5</span><span class="d">▲ +5kg</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="nav"><a class="active">Today</a><a>History</a><a>Programs</a></div>
|
||||||
|
<span class="badge">PR · Week 6</span>
|
||||||
|
<div class="btns"><button class="btn-primary">Save workout</button><button class="btn-accent">Refine</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- B -->
|
||||||
|
<div class="row" style="--a:#DC2626">
|
||||||
|
<div>
|
||||||
|
<div class="swatch" style="background:#DC2626"></div>
|
||||||
|
<div class="name">B · Blood Red</div>
|
||||||
|
<div class="hex">#DC2626 · Tailwind red-600 (deeper)</div>
|
||||||
|
</div>
|
||||||
|
<div class="vignette">
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">Personal Record</div>
|
||||||
|
<div class="h">Back Squat</div>
|
||||||
|
<div class="stat"><span class="n">142.5</span><span class="d">▲ +5kg</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="nav"><a class="active">Today</a><a>History</a><a>Programs</a></div>
|
||||||
|
<span class="badge">PR · Week 6</span>
|
||||||
|
<div class="btns"><button class="btn-primary">Save workout</button><button class="btn-accent">Refine</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- C -->
|
||||||
|
<div class="row" style="--a:#FF3B30">
|
||||||
|
<div>
|
||||||
|
<div class="swatch" style="background:#FF3B30"></div>
|
||||||
|
<div class="name">C · Vermilion</div>
|
||||||
|
<div class="hex">#FF3B30 · hot orange-red</div>
|
||||||
|
</div>
|
||||||
|
<div class="vignette">
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">Personal Record</div>
|
||||||
|
<div class="h">Back Squat</div>
|
||||||
|
<div class="stat"><span class="n">142.5</span><span class="d">▲ +5kg</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="nav"><a class="active">Today</a><a>History</a><a>Programs</a></div>
|
||||||
|
<span class="badge">PR · Week 6</span>
|
||||||
|
<div class="btns"><button class="btn-primary">Save workout</button><button class="btn-accent">Refine</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- D -->
|
||||||
|
<div class="row" style="--a:#E11D48">
|
||||||
|
<div>
|
||||||
|
<div class="swatch" style="background:#E11D48"></div>
|
||||||
|
<div class="name">D · Crimson</div>
|
||||||
|
<div class="hex">#E11D48 · cooler, pink-edge (rose-600)</div>
|
||||||
|
</div>
|
||||||
|
<div class="vignette">
|
||||||
|
<div class="card">
|
||||||
|
<div class="label">Personal Record</div>
|
||||||
|
<div class="h">Back Squat</div>
|
||||||
|
<div class="stat"><span class="n">142.5</span><span class="d">▲ +5kg</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="nav"><a class="active">Today</a><a>History</a><a>Programs</a></div>
|
||||||
|
<span class="badge">PR · Week 6</span>
|
||||||
|
<div class="btns"><button class="btn-primary">Save workout</button><button class="btn-accent">Refine</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 205 KiB |
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"$description": "Proof of Work design tokens (W3C DTCG). Extracted document-as-is from the as-built Tailwind UI on 2026-06-19; red accent (#DC2626) and the two-tier radius rule are owner-driven. Neutral ramp is Tailwind 'zinc'. Hexes are the literal Tailwind values the code already uses.",
|
||||||
|
"color": {
|
||||||
|
"bg": {
|
||||||
|
"canvas": { "$type": "color", "$value": "#0A0A0A", "$description": "App background; PWA theme_color and <body> bg. The anchor value." },
|
||||||
|
"surface": { "$type": "color", "$value": "#18181B", "$description": "zinc-900 — default raised surface (cards, panels)." },
|
||||||
|
"raised": { "$type": "color", "$value": "#27272A", "$description": "zinc-800 — controls, chips, next step up / hover." },
|
||||||
|
"inset": { "$type": "color", "$value": "#09090B", "$description": "zinc-950 — rare deep wells, slightly below canvas." }
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"subtle": { "$type": "color", "$value": "#27272A", "$description": "zinc-800 — default hairline." },
|
||||||
|
"default": { "$type": "color", "$value": "#3F3F46", "$description": "zinc-700." },
|
||||||
|
"strong": { "$type": "color", "$value": "#52525B", "$description": "zinc-600 — emphasis/hover." }
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"primary": { "$type": "color", "$value": "#FFFFFF" },
|
||||||
|
"secondary": { "$type": "color", "$value": "#A1A1AA", "$description": "zinc-400." },
|
||||||
|
"muted": { "$type": "color", "$value": "#71717A", "$description": "zinc-500." },
|
||||||
|
"subtle": { "$type": "color", "$value": "#52525B", "$description": "zinc-600 — disabled/least emphasis." },
|
||||||
|
"inverted": { "$type": "color", "$value": "#000000", "$description": "Text on white/light surfaces (primary button)." }
|
||||||
|
},
|
||||||
|
"accent": {
|
||||||
|
"red": { "$type": "color", "$value": "#DC2626", "$description": "Canonical red — brand accent AND error/destructive (red-600). Distinguished by treatment, not by a second red." },
|
||||||
|
"red-strong": { "$type": "color", "$value": "#B91C1C", "$description": "red-700 — hover/pressed for the accent." },
|
||||||
|
"red-text": { "$type": "color", "$value": "#F87171", "$description": "red-400 — red text/icon on the dark canvas." },
|
||||||
|
"red-border": { "$type": "color", "$value": "#991B1B", "$description": "red-800 — error/destructive outlines." },
|
||||||
|
"red-wash": { "$type": "color", "$value": "rgba(127, 29, 29, 0.30)", "$description": "red-900/30 — error banner / destructive-hover background wash." }
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"success": { "$type": "color", "$value": "#34D399", "$description": "emerald-400 — success text/icon on dark." },
|
||||||
|
"success-fill": { "$type": "color", "$value": "#059669", "$description": "emerald-600 — success fill." },
|
||||||
|
"warning": { "$type": "color", "$value": "#FBBF24", "$description": "amber-400 — warning text/icon." },
|
||||||
|
"warning-edge": { "$type": "color", "$value": "#78350F", "$description": "amber-900 — warning border/wash edge." },
|
||||||
|
"info": { "$type": "color", "$value": "#60A5FA", "$description": "blue-400 — info text (used sparingly)." },
|
||||||
|
"info-wash": { "$type": "color", "$value": "#172554", "$description": "blue-950 — info wash background." }
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"primary-bg": { "$type": "color", "$value": "{color.text.primary}", "$description": "Primary button background is WHITE — not a hue." },
|
||||||
|
"primary-text": { "$type": "color", "$value": "{color.text.inverted}" },
|
||||||
|
"primary-hover-bg": { "$type": "color", "$value": "#E4E4E7", "$description": "zinc-200." },
|
||||||
|
"primary-disabled-bg": { "$type": "color", "$value": "#3F3F46", "$description": "zinc-700." },
|
||||||
|
"primary-disabled-text": { "$type": "color", "$value": "#71717A", "$description": "zinc-500." }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"font": {
|
||||||
|
"family": {
|
||||||
|
"display": { "$type": "fontFamily", "$value": "Bebas Neue", "$description": "var(--font-display). Headings, buttons, labels — always UPPERCASE + tracked." },
|
||||||
|
"body": { "$type": "fontFamily", "$value": "Space Grotesk", "$description": "var(--font-sans). All running text, data, numbers." }
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"xs": { "$type": "dimension", "$value": "12px" },
|
||||||
|
"sm": { "$type": "dimension", "$value": "14px" },
|
||||||
|
"base": { "$type": "dimension", "$value": "16px" },
|
||||||
|
"lg": { "$type": "dimension", "$value": "18px" },
|
||||||
|
"xl": { "$type": "dimension", "$value": "20px" },
|
||||||
|
"2xl": { "$type": "dimension", "$value": "24px" },
|
||||||
|
"3xl": { "$type": "dimension", "$value": "30px" },
|
||||||
|
"4xl": { "$type": "dimension", "$value": "36px" }
|
||||||
|
},
|
||||||
|
"weight": {
|
||||||
|
"normal": { "$type": "fontWeight", "$value": 400 },
|
||||||
|
"medium": { "$type": "fontWeight", "$value": 500 },
|
||||||
|
"semibold": { "$type": "fontWeight", "$value": 600 },
|
||||||
|
"bold": { "$type": "fontWeight", "$value": 700 }
|
||||||
|
},
|
||||||
|
"letterSpacing": {
|
||||||
|
"wider": { "$type": "dimension", "$value": "0.05em", "$description": "Tailwind tracking-wider — the signature pairing with UPPERCASE Bebas." }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"space": {
|
||||||
|
"$description": "Tailwind 4px scale; the values the as-built UI leans on.",
|
||||||
|
"1": { "$type": "dimension", "$value": "4px" },
|
||||||
|
"2": { "$type": "dimension", "$value": "8px" },
|
||||||
|
"3": { "$type": "dimension", "$value": "12px" },
|
||||||
|
"4": { "$type": "dimension", "$value": "16px" },
|
||||||
|
"5": { "$type": "dimension", "$value": "20px" },
|
||||||
|
"6": { "$type": "dimension", "$value": "24px" },
|
||||||
|
"8": { "$type": "dimension", "$value": "32px" }
|
||||||
|
},
|
||||||
|
"radius": {
|
||||||
|
"control": { "$type": "dimension", "$value": "4px", "$description": "Tailwind `rounded` — buttons, inputs, chips." },
|
||||||
|
"container": { "$type": "dimension", "$value": "8px", "$description": "Tailwind `rounded-lg` — cards, panels, modals, banners." },
|
||||||
|
"full": { "$type": "dimension", "$value": "9999px", "$description": "Pills, avatars, dots." }
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"sidebar-width": { "$type": "dimension", "$value": "240px", "$description": "CSS var --sidebar-width; desktop sidebar (md+)." },
|
||||||
|
"nav-height": { "$type": "dimension", "$value": "64px", "$description": "CSS var --nav-height." },
|
||||||
|
"bottom-nav-height": { "$type": "dimension", "$value": "64px", "$description": "CSS var --bottom-nav-height; mobile bottom nav." }
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"overlay": { "$type": "shadow", "$value": "0 10px 15px -3px rgba(0,0,0,0.5)", "$description": "Reserved for floating overlays ONLY (modals, popovers). Static cards use bg layering + borders, never a shadow. Value approximates Tailwind shadow-lg; the codebase also uses shadow-xl/2xl for overlays." }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user