assessment-model/DESIGN.md
Jun-te Kim b69ef47f22 feat(impeccable): vendor the skill and wire up live mode
Live mode (impeccable.style/live-mode) never worked here because the skill was
never actually installed: postCreate ran `skills add pbakaus/impeccable
--global`, which silently no-ops — it's absent from ~/.claude/skills. Vendor it
into .claude/skills/impeccable instead, so live mode's scripts/ sit on a stable
path and everyone gets the same version (`npx impeccable update` to refresh).
Drop the dead postCreate step.

Live-mode wiring:

- .impeccable/live/config.json for Next.js App Router (src/app/layout.tsx,
  </body>, jsx). detect-csp.mjs reports no CSP in this project, so no config
  patch is needed and cspChecked is honestly true.
- Forward port 8400 with requireLocalPort. The script injected into the page
  hard-codes http://localhost:8400, so if VS Code remaps it the element picker
  silently never loads — the one devcontainer-specific trap here.
- Ignore the per-session journal/screenshots; keep the configs tracked.

DESIGN.md is required — live.mjs refuses to boot without it (context_missing).
Generated via the `document` skill from the actual codebase, so it records what
the code really does, including the gaps (dark mode defined but dead, --font-sans
referenced but never defined, --radius unused, the broken Tremor colour block).
Worth a read rather than a rubber stamp: it's the context live mode generates
variants against.

Verified: live.mjs boots ok with the helper on 8400 and injects into layout.tsx;
live-server.mjs stop strips the tag back out cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:32:06 +00:00

29 KiB
Raw Permalink Blame History

name description colors typography rounded spacing components
Ara Domna's portfolio intelligence platform — housing stock data turned into clear, costed retrofit and investment plans.
brand-navy hover-navy mid-navy bright-blue pale-blue brand-tan hover-tan brand-brown brand-gold hover-gold surface ink muted-surface muted-ink hairline focus-ring destructive epc-a epc-b epc-c epc-d epc-e epc-f epc-g chart-blue-4 chart-blue-5 chart-blue-6
#14163d #3e4073 #2d348f #3943b7 #eff6fc #d3b488 #947750 #c4a47c #f1bb06 #c79d12 #ffffff #0f172a #f1f5f9 #64748b #e2e8f0 #94a3b8 #ff0000 #117d58 #2da55c #8dbd40 #f7cd14 #f3a96a #ef8026 #e41e3b #5d6be0 #8b96e9 #b8bef4
display headline title body label
fontFamily fontSize fontWeight lineHeight letterSpacing
Manrope, ui-sans-serif, system-ui, sans-serif 110px 800 1 -0.05em
fontFamily fontSize fontWeight lineHeight letterSpacing
Manrope, ui-sans-serif, system-ui, sans-serif 1.875rem 800 2.25rem -0.025em
fontFamily fontSize fontWeight lineHeight letterSpacing
Inter, ui-sans-serif, system-ui, sans-serif 1.125rem 600 1 -0.025em
fontFamily fontSize fontWeight lineHeight letterSpacing
Inter, ui-sans-serif, system-ui, sans-serif 0.875rem 400 1.25rem normal
fontFamily fontSize fontWeight lineHeight letterSpacing
Manrope, ui-sans-serif, system-ui, sans-serif 0.75rem 700 1rem 0.1em
sm md lg xl 2xl full tremor-small tremor-default tremor-full
0.125rem 0.375rem 0.5rem 0.75rem 1rem 9999px 0.375rem 0.5rem 9999px
1 2 3 4 6 8
0.25rem 0.5rem 0.75rem 1rem 1.5rem 2rem
button-primary button-primary-hover button-brand button-brand-hover button-outline button-ghost card-shadcn card-portfolio input-default badge-default status-pill epc-bubble navbar navbar-link-hover
backgroundColor textColor rounded padding height typography
{colors.ink} #f8fafc {rounded.md} 0.5rem 1rem 2.5rem {typography.body}
backgroundColor
{colors.ink}
backgroundColor textColor rounded padding typography
{colors.brand-navy} #ffffff {rounded.xl} 0.625rem 1.5rem {typography.body}
backgroundColor
{colors.hover-navy}
backgroundColor textColor rounded padding height
{colors.surface} {colors.ink} {rounded.md} 0.5rem 1rem 2.5rem
backgroundColor textColor rounded padding height
transparent {colors.ink} {rounded.md} 0.5rem 1rem 2.5rem
backgroundColor textColor rounded padding
{colors.surface} {colors.ink} {rounded.lg} 1.5rem
backgroundColor textColor rounded padding
{colors.surface} {colors.ink} {rounded.xl} 1rem
backgroundColor textColor rounded padding height typography
{colors.surface} {colors.ink} {rounded.md} 0.5rem 0.75rem 2.5rem {typography.body}
backgroundColor textColor rounded padding
{colors.ink} #f8fafc {rounded.full} 0.125rem 0.625rem
rounded padding typography
{rounded.full} 0.125rem 0.625rem {typography.label}
textColor rounded height width
#ffffff {rounded.full} 1.25rem 1.25rem
backgroundColor textColor height padding
{colors.brand-navy} #d1d5db 4rem 0 1rem
backgroundColor textColor
{colors.hover-navy} #ffffff

Design System: Ara

1. Overview

Creative North Star: "The Chartered Surveyor's Desk"

Ara is a working instrument, not a showroom. Its users are analysts, asset managers and approvers with a spreadsheet open on the next monitor; the interface's job is to hand them a number they will stake a retrofit budget on. So the system reads like an institutional document rather than a product tour: white paper, hairline rules, one deep navy that signs the page, and figures set with more care than any decoration on the screen. PRODUCT.md names the register precisely — "calm, expert, institutional — quiet confidence appropriate to housing-sector procurement, with measured warmth from the brand tan/brown accents."

The surface is neutral by construction. Every semantic token in src/app/globals.css is a slate-based shadcn default: pure white background (--background: 0 0% 100%), near-black slate ink (--foreground: 222.2 47.4% 11.2%), a slate hairline (--border: 214.3 31.8% 91.4%). Colour is reserved for meaning: brand navy (#14163d) for identity and the single dark CTA; the seven-step EPC band scale (#117d58#e41e3b) for the one dimension the whole product is about; amber/red only for risk. Density is high and deliberate — home-page cards sit at 16px padding (p-4, PortfolioCard.tsx), table cells at 12px/8px, because these screens carry hundreds of properties on large desk displays.

What the system rejects, in PRODUCT.md's own words: generic SaaS admin templates (Bootstrap/AdminLTE dashboards, hero-metric cards, widget walls), the bare centred icon-card grid of the old home page, and over-designed AI mockup output — 2rem-rounded cards, giant soft shadows, decorative gradients, invented data the schema cannot supply. If a surface could be dropped into any other B2B dashboard without changing a word, it is wrong.

Key Characteristics:

  • White surface, slate ink, hairline borders — depth comes from rules and tone, not shadow.
  • Navy (#14163d) as signature, not wallpaper: nav bar, one primary CTA, chart series anchor.
  • Two typefaces, one job each: Inter reads, Manrope counts.
  • tabular-nums on every figure that a user might compare down a column (39 usages across src/).
  • Seven-step EPC colour scale is the only decorative-looking colour in the product, and it is load-bearing data.
  • Light-mode only in practice — the dark palette exists but is dead (see the honest gaps below).

2. Colors

A near-monochrome institutional surface, signed in navy, warmed sparingly with tan/brown, and cut by a seven-step EPC scale that is the product's only saturated vocabulary.

Primary

  • Brand Navy (#14163d, brandblue in tailwind.config.js): the identity colour. It fills the nav bar (src/app/components/Navbar.tsx, bg-brandblue), the footer (src/app/layout.tsx), and the single dominant CTA on a page. 535 usages across src/ — by far the most-used brand token, and the one most at risk of overuse.
  • Hover Navy (#3e4073, hoverblue): the only hover partner for brand navy. Nav links and brand buttons lift to it (hover:bg-hoverblue).
  • Slate Ink (#0f172a, --primary / --foreground in src/app/globals.css, expressed there as 222.2 47.4% 11.2%): the shadcn primary. Every default <Button> and <Badge> is this near-black, not brand navy. Two dark colours therefore coexist in the product — see The One Dark Rule.

Secondary

  • Mid Navy (#2d348f, midblue) and Bright Blue (#3943b7, brandmidblue): the interactive blues. brandmidblue carries link hover, checkmarks and progress fills (PortfolioCard.tsx: hover:text-brandmidblue, budget bar bg-brandmidblue); midblue is the middle stop of the chart ramp.
  • Pale Blue (#eff6fc, brandlightblue): the lightest tinted surface, used as a chart wash and card tint. Note the collision documented under gaps: theme.extend.textColor.brandlightblue is #00a9f4, a completely different colour from the background token of the same name.

Tertiary

  • Brand Tan (#d3b488) / Hover Tan (#947750) / Brand Brown (#c4a47c): the "measured warmth" PRODUCT.md asks for. In practice they are almost entirely a chart palette — brandbrown appears 94 times, mostly as a Tremor/Recharts series colour (src/lib/chartUtils.ts), while brandtan appears 7 times. The warmth is real but currently lives in the charts, not the chrome.
  • Brand Gold (#f1bb06) / Hover Gold (#c79d12): reserved for the starred/pinned state (PortfolioCard.tsx, fill={starred ? "#f1bb06" : "none"}). At 1.77:1 against white it is decorative only, and the critiques flag it: state must never be carried by the gold fill alone.

Neutral

  • Surface (#ffffff, --background): the page. Always.
  • Muted Surface (#f1f5f9, --muted / --secondary / --accent, all 210 40% 96.1%): row hover, tab track, skeleton fill, secondary button.
  • Muted Ink (#64748b, --muted-foreground): table headers, card meta, timestamps.
  • Hairline (#e2e8f0, --border and --input): every divider and every input stroke, at exactly 1px.
  • Focus Ring (#94a3b8, --ring): the 2px offset ring on every focusable primitive.
  • Destructive (#ff0000, --destructive: 0 100% 50%): pure sRGB red. Honest note: this is a raw, unmodulated red and the only token in the system that is not tuned — it sits oddly against an otherwise slate-derived palette.

The EPC Scale

Seven fixed hues, epc_a through epc_g (tailwind.config.js), rendered as 20px letter bubbles (EpcLetterBubble, propertyTableColumns.tsx) and as chart fills (src/lib/chartUtils.ts). This scale is data, not decoration: A #117d58, B #2da55c, C #8dbd40, D #f7cd14, E #f3a96a, F #ef8026, G #e41e3b.

Named Rules

The One Dark Rule. A screen gets one dark button. PRODUCT.md puts navy "in small, deliberate doses", and the July critique of the property table found two competing navy CTAs side by side ("Add properties" and "Run modelling") and scored it down. Pick the one action the user should take; everything else is outline or ghost.

The Band-Colour Rule. epc_aepc_g are reserved for EPC bands. They may never be borrowed for status, emphasis, or chart series that are not EPC bands. When a chart needs series colours, it takes them from the navy ramp (#14163d#2d348f#3943b7#5d6be0#8b96e9#b8bef4) or brandbrown.

The Tokenised-Hex Rule. Raw hex in a component is prohibited. The critique of the home page caught exactly this drift (#c3ccd8, #e2e7ee, #e4e9f1, #e9edf3, #f8fafc, #eceefb inline in PortfolioCard.tsx and PortfolioHome.tsx) while a full brand palette sat unused in tailwind.config.js. Add the token; don't paste the hex.

3. Typography

Display Font: Manrope (next/font/google, weights 400/500/700/800, exposed as --font-manrope; src/app/layout.tsx) Body Font: Inter (next/font/google, variable, applied via inter.className on <html>; src/app/layout.tsx) Label/Mono Font: none. There is no monospace face in the system. Numeric alignment is achieved with Inter/Manrope's tabular-nums feature, not with a mono font.

Character: Inter is the clerk — invisible, legible at 1314px, carrying every table cell and every paragraph. Manrope is the accountant — it appears only when a number or a label needs to assert itself: the 110px EPC score in CurrentEfficiencyCard.tsx, the plan headline in PlanHeroCard.tsx, the tiny uppercase section labels. 114 font-manrope usages, concentrated almost entirely in the building-passport surfaces.

Hierarchy

  • Display (Manrope, 800, up to text-[110px], leading-none, tracking-tighter): the single hero figure on a building passport — the EPC score. One per screen, never more (CurrentEfficiencyCard.tsx:124).
  • Headline (Manrope, 800, text-3xl = 1.875rem, tracking-tight): plan and passport page titles (PlanHeroCard.tsx:173).
  • Title (Inter, 600, text-lg = 1.125rem / leading-none / tracking-tight): shadcn CardTitle and DialogTitle. Portfolio names on the home grid sit slightly below at text-[17px], 600, leading-snug (PortfolioCard.tsx:200).
  • Body (Inter, 400, text-sm = 0.875rem / 1.25rem line-height): the default everywhere — buttons, inputs, table cells, dialog copy. Card metadata drops to text-[13px].
  • Label (Manrope, 700, text-xs = 0.75rem / 1rem, uppercase, tracking-widest): micro-labels above figures (CurrentEfficiencyCard.tsx:117). Also used unstyled by Inter at text-[11px], 600 for table headers (PortfolioCard.tsx:245).
  • Numeric (any face, tabular-nums): mandatory on money, counts, percentages, EPC scores.

Named Rules

The Trustworthy Numbers Rule. Every figure a user could compare down a column carries tabular-nums. PRODUCT.md: "Figures are the product; typography and alignment treat numeric data with more care than any decorative element." Money right-aligns. Counts right-align. A ragged numeric column is a defect, not a style choice.

The Quiet Label Rule. A micro-label must never outweigh the number it introduces. The property-table critique caught RESULTS: set in font-black uppercase — louder than the count beside it. Labels are text-xs/text-[11px], uppercase, tracked, and lighter in visual weight than their value.

The Two Voices Rule. Inter reads, Manrope counts. Manrope on a paragraph of body copy is prohibited; Inter on the 110px hero score is prohibited.

4. Elevation

Ara is a flat, hairline system. Depth is carried by 1px #e2e8f0 borders and by tonal shifts against white — bg-muted/50 on a hovered table row, bg-[#f8fafc] on a hovered list row — not by shadow. The shadow vocabulary that exists is Tailwind's own and is used sparingly: shadow-sm dominates (98 usages) and is reserved for the resting card; shadow-md (34) and shadow-lg (25) appear almost exclusively on floating layers — dropdowns, popovers, dialogs, tooltips. That distribution is the system: shadow means "this element is in front of the page", not "this element is important".

Shadow Vocabulary

  • Resting card (box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05), Tailwind shadow-sm; also tremor-input in tailwind.config.js): the shadcn Card default (card.tsx: rounded-lg border bg-card shadow-sm). Barely there — it exists to lift the card off white by a hair, and the 1px border does most of the work.
  • Overlay (box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1), Tailwind shadow-md; tremor-dropdown in tailwind.config.js): select menus, popovers, tooltips, the portalled card menu (PortfolioCard.tsx:96).
  • Modal (Tailwind shadow-lg): DialogContent only (dialog.tsx:57), paired with a bg-background/80 backdrop-blur-sm scrim.
  • Tremor card (0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1), tremor-card): defined in tailwind.config.js for Tremor chart cards.

Named Rules

The Hairline-First Rule. Reach for a 1px #e2e8f0 border before you reach for a shadow. If both a border and a shadow are present on a resting surface, the shadow is probably wrong.

The Floating-Only Rule. shadow-md and above are reserved for elements that are literally floating above the page — menus, popovers, dialogs, toasts. A card, a table, a panel, a section: never. shadow-xl and shadow-2xl are drift; the codebase has 19 and 11 usages respectively and every one of them should be justified or removed.

The 2014 Test. If a surface looks like a 2014 admin panel, the shadow is too dark and the radius is too big. PRODUCT.md names the failure mode directly: "2rem-rounded cards, giant soft shadows, decorative gradients."

5. Components

The library is shadcn/ui (components.json, style: default, baseColor: slate, cssVariables: true) generated into src/app/shadcn_components/ui/, sitting on Radix primitives, with Tremor (@tremor/react v3) for charts and Headless UI for a handful of menus. Variants are declared with class-variance-authority.

Buttons

src/app/shadcn_components/ui/button.tsx

  • Shape: gently rounded (rounded-md, 0.375rem) at every size. Brand-styled buttons written by hand outside the primitive use rounded-xl (0.75rem) — a real inconsistency, not a documented variant.
  • Sizes: default 40px tall (h-10 py-2 px-4), sm 36px (h-9 px-3), lg 44px (h-11 px-8).
  • Primary (default): slate ink #0f172a on #f8fafc text, hover to 90% opacity (bg-primary text-primary-foreground hover:bg-primary/90). Note: 104 components bypass this and use bg-brandblue (#14163d) directly against 15 uses of bg-primary — brand navy is the de facto primary button in feature code even though the primitive says otherwise.
  • Focus: 2px ring in #94a3b8, offset 2px, on :focus-visible only. Outline is removed and replaced, never removed and left.
  • Secondary / Outline / Ghost / Link / Destructive: the five other cva variants. outline = 1px #e2e8f0 stroke, transparent fill, hovers to muted surface; ghost = no stroke, hovers to muted surface; link = navy text with a 4px-offset underline on hover.

Chips / Pills

Two distinct pill families exist and they are not interchangeable.

  • Badge (badge.tsx): fully round (rounded-full), px-2.5 py-0.5, text-xs font-semibold, 1px border, four cva variants (default / secondary / destructive / outline).
  • Status pill (PortfolioCard.tsx:34-44): same geometry, but an eight-tone map keyed to portfolio status — early slate, tender indigo, underway blue, ontrack green, delayed amber, atrisk red, done emerald, review yellow, each as a -100 background with a -800/-900 foreground. Every pill carries a 1.5px dot and a text label — colour is never the sole carrier of state.

Cards / Containers

  • shadcn Card (card.tsx): rounded-lg (0.5rem), white --card fill, 1px hairline border, shadow-sm. Internal padding p-6 (24px) on header/content/footer; header stacks with space-y-1.5.
  • Portfolio card (PortfolioCard.tsx:195): the denser home-page variant — rounded-xl (0.75rem), 1px border, no shadow at all, p-4 (16px), gap-2.5. Hover changes only the border colour. This is the more honest expression of the system than the shadcn default.
  • Radius drift is real: rounded-lg (249), rounded-full (232), rounded-xl (131), rounded-md (125), rounded-2xl (82) across src/. rounded-2xl (1rem) is the outer edge of acceptable and trends toward the "over-rounded AI mockup" anti-reference.

Inputs / Fields

input.tsx, select.tsx

  • Style: 40px tall (h-10), rounded-md, 1px --input (#e2e8f0) stroke on --background white, px-3 py-2, text-sm. Placeholder in muted ink.
  • Focus: 2px #94a3b8 ring, 2px offset — identical to buttons, so the focus vocabulary is one thing across the app.
  • Disabled: cursor-not-allowed + 50% opacity.
  • Error: not established in the primitives. There is no error variant on Input; forms use react-hook-form + zod (form.tsx) and render messages, but the field has no visual error state token. Flagged as a gap.
  • Select: Radix SelectTrigger matches Input exactly (h-10 rounded-md border border-input px-3 py-2); content panel is rounded-md + shadow-md with a chevron and a check-mark indicator.

Navigation

src/app/components/Navbar.tsx

  • Style: a solid bg-brandblue (#14163d) bar, 64px tall (h-16), constrained to max-w-7xl with responsive gutters (px-4 sm:px-6 lg:px-8). White Domna wordmark on the left.
  • Links: text-gray-300, text-sm font-medium, px-3 py-2, rounded-md. Hover fills to hoverblue (#3e4073) with white text. There is no active/current-page state — a genuine gap.
  • Mobile: below md, links collapse into a hamburger panel with a Headless UI Transition (100ms ease-out in, 75ms ease-in out, opacity + 95% scale).
  • Hidden on /: the nav returns null on the root path.

EPC Letter Bubble (signature component)

propertyTableColumns.tsx:68. A 20px (h-5 w-5) circle, rounded-full, text-xs font-bold text-white, filled from the seven-step band scale via getEpcColorClass (src/app/utils.ts:49). Its empty state is deliberate and hard-won: a hollow circle with a 1px slate-200 border, a slate-400 en-dash and an aria-label="No rating yet" — because the critique found that blank bubbles read as breakage. There is a companion EmptyCell (, text-slate-400, aria-label="No data") so that one absence vocabulary covers the whole table.

Named Rules

The One Overlay Rule. Menus, dialogs and popovers come from one primitive stack (Radix via shadcn, or Headless UI Menu where portalling is needed). window.confirm is banned — the home-page critique found it live in PortfolioHome.tsx and scored Consistency 2/10 for having three overlay vocabularies on one page.

The Portalled-Menu Rule. Any menu inside a scrolling or overflow-x-auto container must portal to the top layer (MenuButton + MenuItems anchor= in PortfolioCard.tsx). A menu clipped by its container can hold focus while invisible — a hard accessibility failure the critiques caught in the list view.

The Visible-Failure Rule. A mutation that fails must be visible to sighted users, not only announced into an sr-only live region. Toasts exist (toaster.tsx, Radix Toast, mounted in layout.tsx); use them.

6. Do's and Don'ts

Do:

  • Do put brand navy #14163d on exactly one CTA per screen, and reach for outline/ghost for everything else. PRODUCT.md: navy carries identity "in small, deliberate doses."
  • Do set every comparable figure in tabular-nums and right-align money and counts. Trustworthy numbers is the product's whole claim.
  • Do separate surfaces with a 1px #e2e8f0 hairline before considering a shadow.
  • Do reserve shadow-md/shadow-lg for things that literally float: menus, popovers, dialogs, toasts.
  • Do pair every colour-coded state with a text label — the status pill's dot-plus-label pattern (PortfolioCard.tsx) is the reference implementation.
  • Do design the empty state as a first-class surface. The July critique's P0 was that a new portfolio — properties loaded, nothing modelled — falls through to a table of em-dashes and reads as "the import broke."
  • Do use one absence vocabulary: the EmptyCell em-dash at text-slate-400 with an aria-label. Not four.
  • Do add a Tailwind token when you need a colour. The palette is in tailwind.config.js for a reason.
  • Do keep focus rings visible: 2px #94a3b8, 2px offset, on :focus-visible. Never focus:outline-none without a replacement.
  • Do meet WCAG 2.1 AA — ≥4.5:1 body text, ≥3:1 large text and non-text state indicators. PRODUCT.md warns that procurement may demand conformance evidence.

Don't:

  • Don't build generic SaaS admin templates — no Bootstrap/AdminLTE dashboards, no hero-metric cards, no widget walls (PRODUCT.md anti-reference #1).
  • Don't rebuild the current home page — a bare centred grid of identical icon cards with no data, hierarchy, or organisation (PRODUCT.md anti-reference #2).
  • Don't ship over-designed AI mockup output: "2rem-rounded cards, giant soft shadows, decorative gradients, invented data the schema can't supply" (PRODUCT.md anti-reference #3). Concretely: no rounded-2xl on a data card, no shadow-xl/shadow-2xl, no bg-gradient-to-br on a surface, no bg-clip-text gradient headings — all four exist in the codebase today and all four are drift.
  • Don't put two dark navy CTAs on one screen. Two dark buttons say "both of these are the most important thing."
  • Don't paste raw hex into a component. #c3ccd8, #e2e7ee, #e9edf3, #eceefb are all live in src/ and all should be tokens.
  • Don't borrow the EPC band colours (#117d58#e41e3b) for anything that is not an EPC band.
  • Don't carry state in the gold star fill alone (#f1bb06 is 1.77:1 on white). Colour plus label, always.
  • Don't set body copy in Manrope or hero figures in Inter.
  • Don't use window.confirm, alert, or a bare <details> popover. One overlay stack.
  • Don't render text below AA on white — the property-table critique found text-slate-300 "No cost"/"Unknown" cells failing outright.
  • Don't let a micro-label outshout its number (RESULTS: in font-black uppercase was the offender).
  • Don't write a dark: variant in a component. Dark mode is defined but dead (see below); the only dark: classes in src/ are inherited Tremor helper strings in src/lib/utils.ts, and adding one variant to one component makes the app half-dark, which is worse than light-only.

Honest gaps: what this codebase does not establish

Every value in this document is copied from source. These are the things the codebase genuinely does not settle, recorded rather than invented — do not treat any of them as a licence to invent a value, and do not "fix" them silently in a feature PR:

  1. Dark mode is defined but dead. tailwind.config.js sets darkMode: ["class"], globals.css carries a full .dark token block, next-themes is a dependency, and a dark-tremor colour scale exists. The only dark: utilities in src/ are the Tremor-derived focus/error class strings in src/lib/utils.ts (focusInput, focusRing, hasErrorInput); no component authored for this product writes one, and nothing ever applies the dark class, so those variants never activate. The dark palette is aspirational; treat the product as light-only until that changes.
  2. --font-sans is referenced but never defined. tailwind.config.js:157 maps fontFamily.sans to var(--font-sans), but no file defines --font-sans (layout.tsx defines --font-inter and --font-manrope). font-sans therefore silently falls back to Tailwind's default stack. Inter is applied only because inter.className is on <html> — the token wiring is broken, the outcome happens to be right.
  3. Manrope weight 900 is used but not loaded. layout.tsx requests weights 400/500/700/800; font-black (900) appears in CurrentEfficiencyCard.tsx and PlanHeroCard.tsx. The browser synthesises or falls back. The frontmatter records 800, the weight that actually exists.
  4. --radius: 0.5rem is defined and unused. globals.css declares it in both themes, but tailwind.config.js has no borderRadius mapping to it (its borderRadius.extend contains only tremor-* keys). All rounded-* classes resolve to Tailwind defaults. There is no project-defined radius scale — the values in the frontmatter's rounded block are Tailwind's, recorded because they are what the code actually renders.
  5. No project spacing scale. tailwind.config.js extends no spacing. The spacing frontmatter block lists the Tailwind default steps the codebase actually leans on (p-4 on dense cards, p-6 on shadcn cards, px-3 py-2 on dense table cells), not an invented scale.
  6. brandlightblue is two different colours. theme.extend.colors.brandlightblue is #eff6fc (a near-white wash); theme.extend.textColor.brandlightblue is #00a9f4 (a saturated cyan). bg-brandlightblue and text-brandlightblue are unrelated colours. This is a bug, not a system.
  7. The Tremor colour block is broken. theme.extend.colors.tremor.* and dark-tremor.* are set to the string literals "colors.blue[50]", "colors.gray[200]", etc. — the colors import at the top of tailwind.config.js is never interpolated. Those tokens do not resolve to colours. Tremor charts are actually coloured through the safelisted arbitrary-value classes (fill-[#14163d] …) mapped in src/lib/chartUtils.ts.
  8. domna-gradient is defined in tailwind.config.js (linear-gradient(135deg, #14163d 0%, #2d348f 45%, #3943b7 70%, #eff6fc 100%)) and used nowhere in src/. It is not part of the live system.
  9. No motion tokens. No --ease-*, no --duration-*. What exists: accordion-down/accordion-up at 0.2s ease-out (tailwind.config.js), Tailwind's default transition-colors, tailwindcss-animate, and 15 files using framer-motion ad hoc. There is no documented motion vocabulary and no prefers-reduced-motion handling in globals.css, despite PRODUCT.md requiring it.
  10. No error state on form fields, no active/current state on nav links, and no defined breakpoint scale beyond Tailwind's defaults plus container.screens.2xl: 1400px and maxWidth.8xl: 90rem.
  11. The North Star is proposed, not confirmed. PRODUCT.md marks the brand personality as a "Hypothesis (user undecided — validate against mockup variants)". "The Chartered Surveyor's Desk" is derived from that hypothesis and from what the code actually does; it has not been ratified by the team.