diff --git a/apps/website/src/app/pilot-to-prod/page.spec.tsx b/apps/website/src/app/pilot-to-prod/page.spec.tsx new file mode 100644 index 000000000..4bcb284a7 --- /dev/null +++ b/apps/website/src/app/pilot-to-prod/page.spec.tsx @@ -0,0 +1,20 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import PilotToProdPage from './page'; + +vi.mock('../../lib/analytics/client', () => ({ + track: vi.fn(), + trackCtaClick: vi.fn(), + trackExternalLinkClick: vi.fn(), + trackWhitepaperDownloadClick: vi.fn(), +})); + +describe('PilotToProdPage', () => { + it('renders rail-kicked section headers and the outcomes ledger', () => { + const { container } = render(); + expect(container.querySelectorAll('.pilot-rail2')).toHaveLength(1); + expect(container.querySelectorAll('.pilot-outcome-row')).toHaveLength(4); + expect(container.querySelectorAll('.pilot-outcomes-grid')).toHaveLength(0); + expect(screen.getByRole('heading', { level: 1 })).toBeTruthy(); + }); +}); diff --git a/apps/website/src/app/pilot-to-prod/page.tsx b/apps/website/src/app/pilot-to-prod/page.tsx index 2e315d51b..3972089ab 100644 --- a/apps/website/src/app/pilot-to-prod/page.tsx +++ b/apps/website/src/app/pilot-to-prod/page.tsx @@ -3,7 +3,6 @@ import { Section } from '../../components/ui/Section'; import { Eyebrow } from '../../components/ui/Eyebrow'; import { Button } from '../../components/ui/Button'; import { Pill } from '../../components/ui/Pill'; -import { Card } from '../../components/ui/Card'; import { FeatureBlock } from '../../components/landing/FeatureBlock'; import { BrowserFrame } from '../../components/ui/BrowserFrame'; import { WhitePaperBlock } from '../../components/landing/WhitePaperBlock'; @@ -147,26 +146,25 @@ export default function PilotToProdPage() {
- What you walk away with +
+ What you walk away with +

A working agent. A trained team. A runbook.

-
+
{[ - { t: 'Working demo', d: 'Live on your data, in your app — not in a sandbox.' }, - { t: 'Hardened patterns', d: 'Error/fallback/observability built in from the start.' }, - { t: 'Deploy-ready', d: 'Integrated with your CI/CD, your auth, your data.' }, - { t: 'Trained team', d: 'Your engineers own the framework and the runbook.' }, + { claim: 'Live on your data, in your app — not in a sandbox.', tail: 'working demo' }, + { claim: 'Error, fallback, and observability built in from the start.', tail: 'hardened patterns' }, + { claim: 'Integrated with your CI/CD, your auth, your data.', tail: 'deploy-ready' }, + { claim: 'Your engineers own the framework and the runbook.', tail: 'trained team' }, ].map((o) => ( - -

- {o.t} -

-

- {o.d} -

-
+
+

{o.claim}

+

{o.tail}

+
))}
diff --git a/apps/website/src/app/solutions/[slug]/page.spec.tsx b/apps/website/src/app/solutions/[slug]/page.spec.tsx new file mode 100644 index 000000000..ba9114766 --- /dev/null +++ b/apps/website/src/app/solutions/[slug]/page.spec.tsx @@ -0,0 +1,31 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import SolutionPage from './page'; +import { getAllSolutionSlugs } from '../../../lib/solutions-data'; + +vi.mock('../../../lib/analytics/client', () => ({ + track: vi.fn(), + trackCtaClick: vi.fn(), + trackExternalLinkClick: vi.fn(), +})); + +vi.mock('../../../components/solutions/SolutionCodeBlock', () => ({ + SolutionCodeBlock: () => null, +})); + +vi.mock('../../../components/solutions/SolutionDemoBlock', () => ({ + SolutionDemoBlock: () => null, +})); + +describe('SolutionPage', () => { + it('renders navy rails with no per-solution accent theming', async () => { + const slug = getAllSolutionSlugs()[0]; + const ui = await SolutionPage({ params: Promise.resolve({ slug }) }); + const { container } = render(ui); + expect(container.querySelectorAll('[data-accent-text]')).toHaveLength(0); + expect(container.querySelectorAll('[style*="--accent"]')).toHaveLength(0); + expect(container.querySelectorAll('.sol-page-rail').length).toBeGreaterThanOrEqual(3); + expect(container.querySelector('.sol-page-metric')).toBeTruthy(); + expect(screen.getByRole('heading', { level: 1 })).toBeTruthy(); + }); +}); diff --git a/apps/website/src/app/solutions/[slug]/page.tsx b/apps/website/src/app/solutions/[slug]/page.tsx index 26daddf9f..508f45e00 100644 --- a/apps/website/src/app/solutions/[slug]/page.tsx +++ b/apps/website/src/app/solutions/[slug]/page.tsx @@ -48,12 +48,15 @@ export async function generateMetadata({ params }: PageProps) { }; } -function PainPoints({ items, accent }: { items: SolutionPainPoint[]; accent: string }) { +function PainPoints({ items }: { items: SolutionPainPoint[] }) { return (
- The problem +
+ The problem +

Why this is hard today.

@@ -78,17 +81,18 @@ function PainPoints({ items, accent }: { items: SolutionPainPoint[]; accent: str function Architecture({ intro, layers, - accent, }: { intro: string; layers: ArchitectureLayer[]; - accent: string; }) { return (
- Architecture +
+ Architecture +

How the three libraries compose.

@@ -131,12 +135,15 @@ function Architecture({ ); } -function Capabilities({ items, accent }: { items: ProofPoint[]; accent: string }) { +function Capabilities({ items }: { items: ProofPoint[] }) { return (
- What you ship +
+ What you ship +

Capabilities the framework delivers.

@@ -144,7 +151,7 @@ function Capabilities({ items, accent }: { items: ProofPoint[]; accent: string }
{items.map((p) => ( -
+
{p.metric}

@@ -169,7 +176,7 @@ export default async function SolutionPage({ params }: PageProps) {

- {solution.eyebrow} + {solution.eyebrow}

{solution.title}

@@ -188,11 +195,11 @@ export default async function SolutionPage({ params }: PageProps) {
- - - - - {solution.demo && } + + + + + {solution.demo && }
- By use case +
+ By use case +

Where agents earn their keep.

diff --git a/apps/website/src/components/solutions/SolutionCodeBlock.tsx b/apps/website/src/components/solutions/SolutionCodeBlock.tsx index 9ab58ae04..8c53c0ee3 100644 --- a/apps/website/src/components/solutions/SolutionCodeBlock.tsx +++ b/apps/website/src/components/solutions/SolutionCodeBlock.tsx @@ -20,7 +20,7 @@ async function highlight(block: SolutionCode) { return codeToHtml(block.source, { lang: block.language, theme: 'tokyo-night' }); } -export async function SolutionCodeBlock({ code, accent }: { code: SolutionCodeBlocks; accent: string }) { +export async function SolutionCodeBlock({ code }: { code: SolutionCodeBlocks }) { // Highlight every block up front: an async map inside JSX would give React // promises to render rather than markup. const rendered = await Promise.all( @@ -31,7 +31,7 @@ export async function SolutionCodeBlock({ code, accent }: { code: SolutionCodeBl
- In practice + In practice

What it looks like in your codebase

diff --git a/apps/website/src/components/solutions/SolutionDemoBlock.tsx b/apps/website/src/components/solutions/SolutionDemoBlock.tsx index 1714ca224..f1b0f2a73 100644 --- a/apps/website/src/components/solutions/SolutionDemoBlock.tsx +++ b/apps/website/src/components/solutions/SolutionDemoBlock.tsx @@ -18,12 +18,12 @@ import type { DemoClip } from '../../lib/demo-media'; * No client JS: `autoPlay muted loop playsInline` is the whole behaviour, so * this stays a Server Component. */ -export function SolutionDemoBlock({ clip, accent }: { clip: DemoClip; accent: string }) { +export function SolutionDemoBlock({ clip }: { clip: DemoClip }) { return (
- See it running + See it running

The approval gate, in the product

diff --git a/apps/website/src/lib/solutions-data.ts b/apps/website/src/lib/solutions-data.ts index 3041dbd28..899a2e3fd 100644 --- a/apps/website/src/lib/solutions-data.ts +++ b/apps/website/src/lib/solutions-data.ts @@ -71,8 +71,6 @@ export type SolutionCodeBlocks = readonly SolutionCode[]; export interface SolutionConfig { slug: string; - color: string; - rgb: string; eyebrow: string; title: string; subtitle: string; @@ -97,8 +95,6 @@ export interface SolutionConfig { export const SOLUTIONS: SolutionConfig[] = [ { slug: 'compliance', - color: '#D4850F', - rgb: '212,133,15', eyebrow: 'Compliance & Audit', title: 'AI agents your compliance\nteam will actually approve', subtitle: 'Human-in-the-loop approvals, auditable thread history, and deterministic testing — built into the framework, not bolted on.', @@ -190,8 +186,6 @@ export const SOLUTIONS: SolutionConfig[] = [ }, { slug: 'analytics', - color: '#0F7B8D', - rgb: '15,123,141', eyebrow: 'Analytics & BI', title: 'Natural language queries.\nReal-time dashboards.', subtitle: 'Your users ask questions in plain English. The agent queries, visualizes, and streams results — all inside your Angular app.', @@ -268,8 +262,6 @@ export class DashboardComponent { }, { slug: 'customer-support', - color: '#5B4FCF', - rgb: '91,79,207', eyebrow: 'Customer Support', title: 'AI agents that know when\nto escalate to a human', subtitle: 'Resolve routine tickets autonomously, surface context instantly, and hand off to humans seamlessly — with full conversation history.', diff --git a/apps/website/src/styles/pages.css b/apps/website/src/styles/pages.css index a0a4af39c..ed455a865 100644 --- a/apps/website/src/styles/pages.css +++ b/apps/website/src/styles/pages.css @@ -335,9 +335,18 @@ max-width: 640px; } .sol-index-grid-header { - text-align: center; margin-bottom: 48px; } +.sol-index-rail { + display: flex; + align-items: baseline; + gap: 14px; +} +.sol-index-rail-line { + flex: 1; + height: 1px; + background: var(--color-border); +} .sol-index-eyebrow-tight { margin-bottom: 12px; } @@ -399,13 +408,22 @@ /* Solution detail page — app/solutions/[slug]/page.tsx */ .sol-page-section-header { - text-align: center; max-width: 720px; - margin: 0 auto 48px; + margin-bottom: 48px; } .sol-page-eyebrow-tight { margin-bottom: 12px; } +.sol-page-rail { + display: flex; + align-items: baseline; + gap: 14px; +} +.sol-page-rail-line { + flex: 1; + height: 1px; + background: var(--color-border); +} .sol-page-h2 { font-family: var(--font-garamond); font-size: var(--text-h2); @@ -492,10 +510,11 @@ } .sol-page-metric { font-family: var(--font-garamond); - font-style: italic; - font-size: 32px; + font-size: 40px; font-weight: 700; - line-height: 1.1; + line-height: 0.9; + letter-spacing: -0.02em; + color: var(--color-text-primary); margin: 0; margin-bottom: 12px; } @@ -639,10 +658,18 @@ } .pilot-section-header { max-width: 720px; - margin: 0 auto; - text-align: center; margin-bottom: 48px; } +.pilot-rail2 { + display: flex; + align-items: baseline; + gap: 14px; +} +.pilot-rail2-line { + flex: 1; + height: 1px; + background: var(--color-border); +} .pilot-h2 { font-family: var(--font-garamond); font-size: var(--text-h2); @@ -655,29 +682,39 @@ .pilot-h2-spaced { margin-bottom: 16px; } -.pilot-outcomes-grid { +.pilot-outcome-rows { + margin-top: 22px; + border-top: 2px solid var(--color-text-primary); + max-width: 56ch; +} +.pilot-outcome-row { display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + grid-template-columns: 1fr auto; gap: 16px; - max-width: 1000px; - margin: 0 auto; + align-items: baseline; + border-bottom: 1px solid var(--color-border); + padding: 10px 0; } -.pilot-outcome-h3 { +.pilot-outcome-claim { font-family: var(--font-inter); - font-size: 17px; - line-height: 1.3; - font-weight: 600; + font-size: 15px; + line-height: 1.5; color: var(--color-text-primary); margin: 0; - margin-bottom: 8px; } -.pilot-outcome-body { - font-family: var(--font-inter); - font-size: 14px; - line-height: var(--text-body--line-height); - color: var(--color-text-secondary); +.pilot-outcome-tail { + font-family: var(--font-mono); + font-size: 11.5px; + color: var(--color-text-muted); + white-space: nowrap; margin: 0; } +@media (max-width: 640px) { + .pilot-outcome-row { + grid-template-columns: 1fr; + gap: 2px; + } +} .pilot-contact-inner { max-width: 720px; margin: 0 auto; @@ -1168,13 +1205,3 @@ gap: 12px; flex-wrap: wrap; } - -/* Per-page accent color, set inline as --accent (unbounded prop — the - * lint-guard escape hatch). The compound selector matches the Eyebrow tone - * rules' 0-2-0 specificity ([data-ui="eyebrow"][data-tone=…] in ui.css) and - * wins the tie because pages.css imports after ui.css — the inline color it - * replaces used to outrank everything. */ -[data-accent-text], -[data-ui='eyebrow'][data-accent-text] { - color: var(--accent); -} diff --git a/docs/superpowers/plans/2026-09-01-solutions-pilot.md b/docs/superpowers/plans/2026-09-01-solutions-pilot.md new file mode 100644 index 000000000..608d0a984 --- /dev/null +++ b/docs/superpowers/plans/2026-09-01-solutions-pilot.md @@ -0,0 +1,196 @@ +# Solutions + Pilot Back Half Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** The ninth arc per `docs/superpowers/specs/2026-09-01-solutions-pilot-arc-design.md`: kill per-solution accent theming, rail the remaining centered headers, proof-cell the capability metrics, and ledger the pilot outcomes. + +**Architecture:** Next.js 16 / React 19; UNLAYERED CSS (`pages.css` holds sol-/pilot- rules). `solution.color` has exactly two consumers: `lib/solutions-data.ts` (definition, 5 values) and `app/solutions/[slug]/page.tsx` (threading) — the index page does not use it. No page specs exist for these routes yet. + +**Test command:** `cd apps/website && npx vitest run --config vite.config.mts` — baseline all green (53 files; count from a fresh run before starting). +**Branch:** `blove/solutions-pilot` — create from `blove/final-review-fixes` (the fix batch, PR #922 pending): `git checkout -b blove/solutions-pilot blove/final-review-fixes`. If #922 has merged by start time, branch from origin/main instead and note it. + +--- + +### Task 1: /solutions/[slug] — accent removal + rails + proof metrics + +**Files:** +- Modify: `apps/website/src/app/solutions/[slug]/page.tsx` +- Modify: `apps/website/src/lib/solutions-data.ts` +- Create: `apps/website/src/app/solutions/[slug]/page.spec.tsx` +- Modify: `apps/website/src/styles/pages.css` + +- [ ] **Step 1: Spec FIRST (failing).** New spec rendering ONE solution page (pick the first slug from `getAllSolutionSlugs()`): + +```tsx +// apps/website/src/app/solutions/[slug]/page.spec.tsx +import { render, screen } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import SolutionPage from './page'; +import { getAllSolutionSlugs } from '../../../lib/solutions-data'; + +vi.mock('../../../lib/analytics/client', () => ({ + track: vi.fn(), + trackCtaClick: vi.fn(), + trackExternalLinkClick: vi.fn(), +})); + +describe('SolutionPage', () => { + it('renders navy rails with no per-solution accent theming', async () => { + const slug = getAllSolutionSlugs()[0]; + const ui = await SolutionPage({ params: Promise.resolve({ slug }) }); + const { container } = render(ui); + expect(container.querySelectorAll('[data-accent-text]')).toHaveLength(0); + expect(container.querySelectorAll('.sol-page-rail').length).toBeGreaterThanOrEqual(3); + expect(container.querySelector('.sol-page-metric')).toBeTruthy(); + expect(screen.getByRole('heading', { level: 1 })).toBeTruthy(); + }); +}); +``` + +Adapt mocks if children demand more (async CodeShowcase-style components: mock to `() => null` if present — check imports). Run → FAIL (no `.sol-page-rail` exists). Paste it. + +- [ ] **Step 2: Page changes.** + - Delete the `accent` prop from `PainPoints`, `Architecture`, `Capabilities` (signatures + call sites) and every `style={{ '--accent': … }}` + `data-accent-text` attribute in the file (hero Eyebrow included). All Eyebrows: `tone="accent"`, keep classNames. + - Each `sol-page-section-header` gains the railkick as its first child: +```tsx +
+ +
+``` + (the Eyebrow MOVES into the rail; the h2/intro stay below). + - Hero eyebrow: plain `tone="accent"` — hero layout otherwise untouched (heroes stay centered sitewide). +- [ ] **Step 3: Data.** In `solutions-data.ts`: remove the `color` field from the interface and all 5 entries (verify zero remaining consumers first: `grep -rn 'solution.color\|\.color' apps/website/src/app/solutions apps/website/src/components` — the only hits must be the ones you're deleting). +- [ ] **Step 4: CSS (pages.css).** + - Delete the `[data-accent-text]` rule block (~line 1172) IF grep shows zero remaining `data-accent-text` in tsx (index page has none — verify). + - `sol-page-section-header`: remove `text-align: center` (and auto-margin centering; keep max-widths). + - Add: +```css +.sol-page-rail { + display: flex; + align-items: baseline; + gap: 14px; +} +.sol-page-rail-line { + flex: 1; + height: 1px; + background: var(--color-border); +} +``` + - `sol-page-metric`: restyle as a proof numeral — replace its current color/size with: +```css +.sol-page-metric { + font-family: var(--font-garamond); + font-size: 40px; + font-weight: 700; + line-height: 0.9; + letter-spacing: -0.02em; + color: var(--color-text-primary); +} +``` + (replace the existing rule's typography wholesale; keep any margin it had). +- [ ] **Step 5:** Spec PASS; full suite green; commit: `feat(website): solution pages join the design language — navy, rails, proof metrics` + +--- + +### Task 2: /solutions index rail + +**Files:** +- Modify: `apps/website/src/app/solutions/page.tsx` +- Modify: `apps/website/src/styles/pages.css` + +- [ ] **Step 1:** The "By use case" header (`sol-index-eyebrow-tight` + `sol-index-h2`) gains the railkick (same pattern; classes `sol-index-rail`/`sol-index-rail-line`, CSS values identical). Remove that header's centering in pages.css (grep `sol-index-` rules; hero stays centered). Cards untouched. +- [ ] **Step 2:** Full suite; commit: `refactor(website): rail the solutions index header` + +--- + +### Task 3: /pilot-to-prod back half + +**Files:** +- Modify: `apps/website/src/app/pilot-to-prod/page.tsx` +- Modify: `apps/website/src/styles/pages.css` +- Create: `apps/website/src/app/pilot-to-prod/page.spec.tsx` + +- [ ] **Step 1: Spec FIRST (failing):** minimal spec asserting: ≥2 `.pilot-rail2` rails, 4 `.pilot-outcome-row`s, zero `.pilot-outcomes-grid`, h1 present. (Async page? check — if the page is sync, no await needed. Mock analytics + any async child as in Task 1.) Run → FAIL; paste. +- [ ] **Step 2:** Both `pilot-section-header` blocks gain the railkick (`pilot-rail2`/`pilot-rail2-line` — the homepage PilotBlock already owns `pilot-rail`; do NOT collide). Outcomes grid becomes the ledger: + +```tsx +
+ {[ + { claim: 'Live on your data, in your app — not in a sandbox.', tail: 'working demo' }, + { claim: 'Error, fallback, and observability built in from the start.', tail: 'hardened patterns' }, + { claim: 'Integrated with your CI/CD, your auth, your data.', tail: 'deploy-ready' }, + { claim: 'Your engineers own the framework and the runbook.', tail: 'trained team' }, + ].map((o) => ( +
+

{o.claim}

+

{o.tail}

+
+ ))} +
+``` + + Remove the `Card` import if now unused in the file (grep within file first). +- [ ] **Step 3: CSS.** Delete `pilot-outcomes-grid`/`pilot-outcome-h3`/`pilot-outcome-body` rules (zero-consumer verify). Remove `pilot-section-header`'s centering. Add `pilot-rail2`(+line) with the standard values and the rows ledger (copy `.pilot-rows`-family values from landing.css — 2px cap `--color-text-primary`, 1fr/auto baseline grid, 16px gap, inter 15px claim, mono 11.5px muted nowrap tail, 640px stack): + +```css +.pilot-rail2 { + display: flex; + align-items: baseline; + gap: 14px; +} +.pilot-rail2-line { + flex: 1; + height: 1px; + background: var(--color-border); +} +.pilot-outcome-rows { + margin-top: 22px; + border-top: 2px solid var(--color-text-primary); + max-width: 56ch; +} +.pilot-outcome-row { + display: grid; + grid-template-columns: 1fr auto; + gap: 16px; + align-items: baseline; + border-bottom: 1px solid var(--color-border); + padding: 10px 0; +} +.pilot-outcome-claim { + font-family: var(--font-inter); + font-size: 15px; + line-height: 1.5; + color: var(--color-text-primary); + margin: 0; +} +.pilot-outcome-tail { + font-family: var(--font-mono); + font-size: 11.5px; + color: var(--color-text-muted); + white-space: nowrap; + margin: 0; +} +@media (max-width: 640px) { + .pilot-outcome-row { + grid-template-columns: 1fr; + gap: 2px; + } +} +``` + +- [ ] **Step 4:** Spec PASS; full suite; prod build (`npx nx build website --configuration=production`); commit: `feat(website): pilot back half joins the language — rails and outcome ledger` + +--- + +### Task 4: Verification gate (controller-run) + +- [ ] Full suite + prod build green. +- [ ] Chrome at 1440px: one slug page (navy everywhere, 3+ rails, serif metrics), the index rail, pilot rails + ledger; 390px: no h-scroll on all three routes. +- [ ] Confirm zero `data-accent-text` and zero `color:` in solutions-data remain. +- [ ] Commit fixes; stop. No push/PR — separate decision. + +## Deviations that require stopping + +- Any third consumer of `solution.color` surfacing in step 3's grep. +- Slug-page children that can't render in jsdom without component changes. diff --git a/docs/superpowers/specs/2026-09-01-solutions-pilot-arc-design.md b/docs/superpowers/specs/2026-09-01-solutions-pilot-arc-design.md new file mode 100644 index 000000000..e79fb175c --- /dev/null +++ b/docs/superpowers/specs/2026-09-01-solutions-pilot-arc-design.md @@ -0,0 +1,42 @@ +# Ninth arc — /solutions + /pilot-to-prod back half + +**Date:** 2026-09-01 · **Status:** Approved (accent theming: KILL, navy everywhere). · **Branch:** `blove/solutions-pilot` + +## 1 · /solutions/[slug] (the orphan) + +- **Accent theming dies.** `solution.color` stops flowing into the UI: the + `accent` props on PainPoints/Architecture/Capabilities, every inline + `--accent` style, and `data-accent-text` usage on these pages go; Eyebrows + become plain `tone="accent"` (navy). The `[data-accent-text]` CSS rule in + pages.css is deleted IF these pages were its only consumers (grep). + `solution.color` stays in the data if other consumers exist (index cards? + grep) — otherwise remove the field and its type. +- **Section headers → rail** (`sol-page-section-header` gets the railkick; + centered rules removed). +- **Capabilities metrics become proof-cell numerals**: `sol-page-metric` + restyled serif Garamond (proof-strip-value scale ~40px here), navy → + `--color-text-primary`; cards keep paper chrome. +- PainPoints/Architecture cards keep their card form (content-bearing); + headers/rails only. +- Hero: eyebrow plain accent; structure unchanged; FinalCTA stays tinted + (commerce rule). + +## 2 · /solutions index + +- "By use case" header → rail. Cards stay (nav device, like PostCards); + their per-card Eyebrows plain accent. + +## 3 · /pilot-to-prod back half + +- The two `pilot-section-header` blocks ("What you walk away with", + contact/"Tell us about your stack") → railkick, left-aligned (centered + rule removed). +- The outcomes `Card` grid → the rows ledger (claim → mono tail), tails + drawn from each card's substance; BrowserFrame visuals stay. +- FinalCTA stays tinted. + +## Testing + +Page specs: extend/create minimal specs asserting rail presence + zero +`data-accent-text` on slug pages + a metric renders. Full suite + prod +build + Chrome pass (desktop/390).