Skip to content
AskFlorence
Main Navigation ArchitectureFlorence AIAgentsMembersAgent PlatformValidationInfrastructure

Appearance

Sidebar Navigation

Overview

Home

Glossary

System Architecture

Consumer & Agent Flow

Florence AI

Overview

Principles

Runtime

Tool surface

Adding a tool

Tool registry

Knowledge: SBC scenarios & CSR

Voice

Evals & observability

Provider risk & portability

Outage playbook

Roadmap

Build plan

Agents

Overview

Workflows & pain points

Members

Overview

Medicaid coverage gap

Carriers

Overview

Marketplaces

Overview

Agency

Overview

Regulations

Overview

Agent Platform

Overview

Auth Architecture

MongoDB Permissioning

Compliance Model

Data Models

Data Sources

Overview

CMS Marketplace API

CMS dependency map

PUF Data

State Subsidies

SBE Ingestion Playbook

SBE State Watchouts + Decisions

CA Phase C/D Playbook

NY Phase C/D Playbook

Validation

Overview

Methodology

APTC Formula

California 2026

New York 2026

CAPS Formula

Scenario Results

Infrastructure

Account Inventory

AWS Setup Runbook

AWS Organizations

CloudTrail

GuardDuty

Security Hub

Config

CloudFront + WAFv2

Data sources & ingest

Phase 4 DNS

Change Log

Vulnerability Management

MongoDB Setup

Access Control

Data Classification

Documentation Hosting

Post-deploy Smoke

Development

Preflight (local CI mirror)

Testing strategy

Compliance

Overview (auditor entry point)

SOC 2 Control Mapping

HIPAA Control Mapping

CMS EDE Appendix A Mapping

Risk Assessment

Encryption Policy

Data Retention Policy

Privacy Impact Assessment

Consent Capture & Versioning

Incident Response Plan

Access Control Policy

Marketing vs. Portal Analytics

Vendor / Subprocessor Register

Dependency Vulnerability Policy

BAA / Compliance Evidence

Compliance-Automation Integration

Compliance-Automation Vendor Evaluation

Penetration Test Reports

Architecture

Portal entry handoff

Mobile app strategy

Deferred architecture decisions

Session cookie architecture

Share flows

Decisions (ADRs)

Index

0001 — Atlas project isolation

0002 — Append-only audit log

0003 — Narrow-scoped Mongo users

0004 — Cross-cluster Atlas PrivateLink

0005 — Delayed-job architecture

0006 — Mongo user simplification

0007 — Terraform owns ECS task def

0008 — E2E testing strategy

0009 — Self-hosted analytics + observability (superseded)

0010 — PostHog HIPAA Cloud (supersedes 0009)

Runbooks

Security Incident Response

Break-Glass Root Login

Onboard Team Member

Offboard Team Member

Atlas user provisioning

Deploy via Terraform (ENG-277)

Rollback via Terraform (ENG-277)

S3 data bucket migration (planned Phase 11)

Access Reviews

2026-Q2 Review

Session log

Index

2026-04-23 — Phase 10 DNS cutover

2026-04-22 — Phase 8 prod AWS mirror

2026-04-22 — Phase 7 Atlas VPC peering

2026-04-22 — Phase 6 CloudFront + WAF

2026-04-21 — Phase 5 staging go-live

2026-04-17 — Atlas staging

Briefs

Index

Member portal plan (ENG-187)

2026-04-16/17 handoff

2026-04-17 Atlas handoff

System briefing (2026-04-17)

Creative AdBundance proposal brief

Creative AdBundance analytics brief

ElevenLabs RN integration research

Policies

Overview

On this page

AskFlorence design system ​

The canonical reference for building screens that match the editorial home register. Read this BEFORE writing any new page or component. The home page (/, source at src/app/_home/) is the reference implementation — when this doc and the home disagree, the home wins and this doc gets updated.

This document describes the L2 editorial register — the typography, color, layout, motion, and copy patterns that shipped with the v0.29.0 home swap. All new pages on askflorence.health should adopt this register unless explicitly noted otherwise. The legacy /landing-1 archive is a SaaS-style register and is NOT a design reference; it exists only for rollback.


1. Register and philosophy ​

The site reads like an editorial weekend supplement, not a SaaS marketing page. The audience is Americans being lied to by Healthcare.gov about their insurance options — they don't need another sales pitch, they need clarity. So:

  • Paper, not screens. Backgrounds are warm cream and paper tones. Pure white and pure black are off-limits except where calls demand them (primary CTAs, ink text on cream, etc.).
  • Editorial, not flashy. Headlines lean on Playfair Display in regular weight with deliberate italic-gold accents on the emotional second clause. Body type is unobtrusive Inter. Prices use Outfit at display sizes — the only place the type rhythm visibly shifts.
  • Restraint over motion. Animations are minimal and bulletproof. Scroll-driven reveals were tried and removed (see CLAUDE.md v0.34.0) because consistency matters more than flourish. Default to static; add motion only when it carries meaning.
  • Trust over persuasion. Every claim is backed by real plan data. No fake urgency, no countdown timers, no "limited time" copy. The hero proof block compares actual carrier plans byte-for-byte audited against CMS.
  • Hyphens, not em dashes or en dashes. Anywhere user-facing. This is a hard rule.

2. Design tokens ​

Tokens live in src/app/_home/tokens.css scoped to .af-root. Every page that uses this register must wrap its content in <div className="af-root"> and import the tokens.

2.1 Color tokens ​

TokenHexIntended use
--af-cream#F6F1E8Page background (the "paper" base)
--af-paper#FBF6EBCard backgrounds within sections; lighter than cream so cards float
--af-sand#EFE3C6Image placeholder backgrounds; subtle lift behind portraits
--af-line#E5DCCBHairline borders, dividers
--af-glow#F5E8C4Gold-tinted glow / soft highlights
--af-ink#141C2EPrimary text, primary CTA backgrounds
--af-ink2#0F1222Slightly deeper ink for hover states on --af-ink
--af-body#3A404FBody copy when ink is too heavy
--af-stone#6A6F7CStone-grey for secondary copy, captions, helper text
--af-gold#C9A967Brand gold; subtle accent — labels, gold dots, soft eyebrows
--af-gold-2#B8903FDeeper gold; the editorial italic accent — never on bodies of text, always on emotional words
--af-ember#EFE3C6Synonym for sand at the moment; reserve for warm tonal shifts
--af-green#1F6B49Saved-money green; success values
--af-green-d#184F36Hover state for green
--af-red#A85C3FWarm-red for "rejected" / sticker-price strikes; never harsh
--af-red-d#8B4730Hover state for red
--af-white#FFFFFFUse sparingly — deck cards, focused inputs

Color usage rules:

  • Primary CTA = --af-ink background + white text, 4px radius, navy. Always.
  • Tertiary/text-link CTA = --af-gold-2 text with dashed underline. Always italic Playfair when in editorial sections, Inter when in instructional UI.
  • Eyebrow labels = --af-gold-2 Inter 11px 600 0.22em uppercase. Optionally preceded by a 32x1px gold rule.
  • Editorial italic accent = always --af-gold-2, never --af-gold. Use on the emotional second clause: "Healthcare.gov showed me $960/mo. I'm paying $7." The italic gold is the brand voice in typography.
  • Helper / muted text = --af-stone Inter 11.5-13px.
  • Strike-through prices (fictional / sticker) = --af-red 0.55 opacity with a 3px line-through.
  • Save / subsidized prices = --af-green Outfit at display sizes.
  • Strikes through Healthcare.gov sticker prices use --af-red not --af-red-d. The deeper red is for hover only.

2.2 Typography ​

Three font families are loaded via next/font in the root layout. Use the variables, not bare names:

VariableFamilyRoles
--af-font-serifPlayfair Display, Fraunces, Times New RomanDisplay headings, editorial emphasis, italic accents
--af-font-sansInter, system-uiUI, body copy, labels, eyebrows
--af-font-labelOutfit, InterPrices and numerics at display sizes

Declarative type scale (used everywhere in _home/v4.css):

ElementMobileDesktopLine-heightTrackingNotes
H1 heroclamp(40px, 7.5vw, 96px)96px0.98-0.035emPlayfair, weight 400. Italic-gold accent on second clause.
H2 section opener30px50-72px1.0-1.06-0.03emPlayfair, weight 400. Use :where(.af-h2) for fluid clamp(30px, 7vw, 72px).
H2 conversion moment40px60px1.04-0.025emPlayfair, weight 400. Larger than openers — Proof, How It Works, Audience all use this size.
H3 spread title30-36px56px1.03-0.025emPlayfair, weight 400. Editorial pull-quote feel.
H4 / step head18-22px24-28px1.15-1.2-0.012emPlayfair or Inter; use Playfair when within an editorial spread, Inter for UI.
Body large16-17px17-18px1.55-1.75normalInter. Use 17px for editorial spreads, 16px for general body.
Body14-15px14-16px1.5-1.60.01emInter. UI body, helper paragraphs.
Helper / caption11-13.5px12-14px1.35-1.50.01-0.02emInter, often --af-stone.
Eyebrow10.5-11px11px10.18-0.22em UPPERInter, weight 600, --af-gold-2. Always preceded by a 32x1px gold rule unless space-constrained.
Display price big26-32px32-44px1-0.02 to -0.03emOutfit, weight 500.
Display price huge34-56px56-84px1-0.02 to -0.035emOutfit, weight 500. The conversion moment ("$7").

Editorial italic accent recipe:

html
<h1>Healthcare.gov showed me $960/mo. <em style="color: var(--af-gold-2); font-style: italic;">I'm paying $7.</em></h1>

Inside <em>, the color shifts to --af-gold-2 and the style stays italic. This is the single most-recognizable typographic move in the brand. Use it sparingly — once per major heading. Never on pure-information lines.

2.3 Spacing & layout ​

Layout tokens:

TokenValueUse
--af-gutter-d48pxDesktop section horizontal padding
--af-gutter-m20pxMobile section horizontal padding
--af-max-hero1240pxInner container max-width (canonical)
--af-max-content1200pxOlder sections still use this; standard moving forward is 1240
--af-nav-h80pxUsed by hero min-height: calc(100svh - var(--af-nav-h))

Section structure (canonical):

html
<section class="af-section af-l2-{name}" style="padding: 0px 48px 120px;">
  <div style="max-width: 1240px; margin: 0px auto;">
    <!-- content -->
  </div>
</section>
  • Desktop horizontal padding: 48px
  • Desktop bottom padding: 120px
  • Inner container: max-width 1240px, margin auto
  • Top padding: 0px (relies on previous section's 120px bottom)
  • Exception: a section that follows the hero (which has no bottom padding) gets 80px top.

Mobile section pattern (CSS at _home/v4.css:151-163):

css
@media (max-width: 759px) {
  .af-section, .af-proof, .af-feature-strip, .af-audience,
  .af-stories, .af-namesake, .af-how, .af-calculator {
    padding-left: 20px !important;
    padding-right: 20px !important;
    padding-bottom: 64px !important;
  }
}

Critical: every new section MUST have class="af-section ..." so this mobile gutter override catches it. The lifestyle wrapper bug (fixed in v0.34.0) was a section without a class — its desktop 48px padding leaked to mobile and made content 16% narrower than the rest of the page.

Vertical rhythm:

  • Section-to-section gap = 0 (sections butt cleanly; the previous's 120/64 bottom is the gap)
  • Inside sections, consistent margin-bottom on hero blocks: clamp(28px, 4.5vh, 52px)
  • Stories spreads: 140px margin-bottom with :last-of-type override to prevent double-stacking
  • H2 to first content: 48-72px gap (smaller for tight sections, larger for hero-style sections)
  • H3 to body: 24-32px
  • Body to dividers: 36-40px

Container width hierarchy:

ContextMax-width
Hero inner, calc container, howw container, aud container, proof card1240px (canonical)
Feature row, lifestyle spread (legacy)1200px (will migrate to 1240px in next pass)
H1 / H2 line-length cap1100px (so they wrap before the container does)
Body paragraphs in editorial spreads500-540px (50-60ch optimal)
Body in pitch sidebars720px
Form max-width480px (calc form column)

2.4 Borders, radii, shadows ​

TokenValue
--af-border-hairline1px solid var(--af-line)
Card corner radius4px (use --radius-lg from globals or hardcode)
Pill / badge radius2-4px (sharp; never round)
Image card radius0px (sharp aspects, editorial)
--af-shadow-card0 18px 40px -30px rgba(20, 16, 8, 0.35)
--af-shadow-portrait0 30px 80px -40px rgba(20, 16, 8, 0.45)

Radii are flat-and-low. The brand reads as architectural, not playful — no big-radius pill chrome. The deepest shadow on the site is on lifestyle portraits.

2.5 Motion tokens ​

TokenValue
--af-motion-fast120ms cubic-bezier(0.4, 0, 0.2, 1)
--af-motion-base200ms cubic-bezier(0.4, 0, 0.2, 1)
--af-motion-slow320ms cubic-bezier(0.4, 0, 0.2, 1)

Use rules:

  • Hover transitions: --af-motion-base
  • Focus rings: --af-motion-fast
  • Card lift / settle: --af-motion-slow
  • Avoid scroll-driven reveals. They've burned us 3 times. If you must reveal on scroll, default to visible and treat the animation as enhancement, never required.

3. Component patterns ​

3.1 Eyebrows ​

The most-repeated UI element. Always Inter 11px / 600 / --af-gold-2 / 0.22em uppercase. Optionally preceded by a 32x1px gold rule.

html
<div style="display: flex; align-items: baseline; gap: 14px; color: var(--af-gold-2); font-family: Inter; font-size: 11px; font-weight: 600; letter-spacing: 0.22em; text-transform: uppercase;">
  <span style="width: 32px; height: 1px; background: var(--af-gold-2); display: inline-block;"></span>
  The subsidy Healthcare.gov didn't show you
</div>

Variants:

  • Section eyebrow — full pattern above
  • Card eyebrow — same color/size, no leading rule, often paired with a heavier card-internal label below
  • Persona eyebrow ("FOR FAMILIES", "FOR THE SELF-EMPLOYED") — same recipe
  • Dark-on-light eyebrow — when used on a navy panel, swap color to var(--af-glow) or var(--af-cream)

3.2 Buttons & CTAs ​

Three roles:

Primary CTA (one per section, max):

html
<a class="af-l2-cta-primary" href="..." style="
  display: inline-flex; align-items: center; gap: 10px;
  background: var(--af-ink); color: var(--af-white);
  text-decoration: none;
  padding: 16px 28px; border-radius: 4px;
  font-family: Inter; font-size: 15px; font-weight: 600; letter-spacing: 0.04em;
">See your real price <span style="font-size: 18px;">→</span></a>

Hover: bg shifts to --af-ink2. Mobile: padding 18px 24px, full-width if it's the only CTA.

Secondary CTA (rarely used; pairs with a primary):

html
<a class="af-l2-cta-secondary" href="..." style="
  background: transparent; color: var(--af-ink); border: 1px solid var(--af-ink);
  padding: 14px 24px; border-radius: 4px;
  font-family: Inter; font-size: 14px; font-weight: 500; letter-spacing: 0.02em;
">View all plans</a>

Tertiary / text-link CTA (most common; the "next step" affordance in editorial spreads):

html
<a style="
  font-family: Inter; font-size: 14px; font-weight: 600; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--af-gold-2); text-decoration: none;
  display: inline-flex; align-items: center; gap: 10px;
">Check your eligibility <span style="font-size: 16px;">→</span></a>

Hover: dashed underline appears. The arrow is a literal →, not an SVG icon.

3.3 Form inputs ​

Two flavors:

White-on-paper input (canonical — calculator form, /plans coverage panel, any future field on the editorial register):

css
background: var(--af-white);
border: 1px solid var(--af-line);          /* hairline shape boundary */
padding: 12px 14px;
font-family: Inter;
font-size: 16px;
border-radius: 4px;
transition: background 200ms, border 200ms, box-shadow 200ms;
/* Hover */
border-color: rgba(184, 144, 63, 0.45);    /* gold-2 at 45% — warming hint */
/* Focus */
background: var(--af-white);
border-color: var(--af-gold-2);
box-shadow: 0 0 0 3px rgba(184, 144, 63, 0.16);

Why white bg + hairline border: earlier versions of this recipe used cream bg with a transparent border, relying on cream-on-paper background differentiation to define the field. That bg-only approach was 1.04:1 contrast — decorative, failed WCAG 1.4.11 (3:1 for non-text UI components), and was genuinely hard on the eyes when fields sat in default state for any length of time.

The current recipe uses white bg + visible hairline border at var(--af-line) (#E5DCCB). White-on-paper reads as a clearly different surface (the universal "ready to type" signal) even though the strict bg-vs-bg ratio is small — perceptual differentiation between pure white and warm paper is unambiguous. The line border carries the WCAG shape boundary at ~1.4:1.

Editorial register holds because the surrounding chrome (Playfair headings, gold accents, paper sections, italic-gold copy emphasis) carries the brand voice. Inputs are a utility surface and should be unambiguous, not decorative — white inputs are universally readable and ship in every editorial-meets-utility context (Apple Card, Wealthfront, Stripe forms inside otherwise editorial pages).

Hover signal: since default and focus both have white bg, hover can't darken the bg without going off-brand. Instead, the border color shifts to gold-2 at 45% alpha (rgba(184, 144, 63, 0.45)) — a warming hint that signals interactivity without competing with the focus ring. Focus then steps the border to full gold-2 + adds the 3px ring.

This recipe was upgraded from the earlier "transparent border + cream bg" pattern in two stages: first added the hairline border (after the /plans coverage inputs sitting in disabled-ready state for minutes made the contrast issue obvious), then flipped the bg to white (after even the line-on-cream version still read as too quiet). Every input on the editorial register uses this recipe now.

Min height 48px on mobile (WCAG 2.5.5). The current 45px ZIP field is just-barely-compliant; new inputs should default to 48px.

Money input with $ prefix:

html
<div class="af-l2-calc__input-money" style="position: relative;">
  <span style="position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--af-stone);">$</span>
  <input type="text" inputmode="numeric" style="padding-left: 28px; ..." />
</div>

Income field gotcha: if the value can legitimately be 0, do NOT render value={n ? n.toString() : ""} — the truthy check eats user-typed zeros. Use a local string state mirrored to the numeric form state. See LandingCalculator.tsx lines ~104-117 for the canonical pattern.

3.4 Cards ​

Three card families:

Editorial card (paper-on-cream, used for content cards in any section):

css
background: var(--af-paper);
border: 1px solid var(--af-line);
padding: 28px;
border-radius: 4px;
box-shadow: var(--af-shadow-card);

Feature card (the home's "Free to use" + "Proof, not promise" tiles): solid color blocks. The olive variant is #3F4A2F with cream text. Deliberate departure from paper-on-cream for accent.

Comparison card (proof block with two columns): outer .af-l2-proof-card is paper, inner two .af-l2-proof-col divs are bordered cells. The "without subsidy" cell uses warm-red tones, the "with subsidies" cell uses gold + green.

3.5 Badges & pills ​

Sharp corners, small. Never rounded.

Metal-tier pill:

css
font-family: Inter; font-size: 9.5px; font-weight: 600; letter-spacing: 0.14em;
text-transform: uppercase; color: var(--af-gold-2);
background: rgba(201, 169, 103, 0.12);
padding: 3px 7px;
border-radius: 2px;

Persona pill (the "Built for: Self-employed | Freelancers | ..." row):

css
font-family: Inter; font-size: 13px; font-weight: 500;
color: var(--af-ink);
border: 1px solid var(--af-line);
padding: 8px 14px;
border-radius: 4px;
text-decoration: none;
transition: ...;
/* Hover */
background: var(--af-ink); color: var(--af-white); border-color: var(--af-ink);

3.6 Lifestyle spreads ​

The 2-col editorial pattern that appears in lifestyle sections + stories.

html
<div class="af-lifestyle-spread" style="
  display: grid;
  grid-template-columns: 1fr 1.1fr;
  gap: 72px;
  align-items: center;
  margin-bottom: 140px;
">
  <!-- Image column (4:5 aspect, sand background, gold inset shadow) -->
  <div style="position: relative; aspect-ratio: 4 / 5; background: var(--af-sand); overflow: hidden;">
    <img src="..." style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: saturate(0.92) contrast(1.02);" />
    <div style="position: absolute; inset: 0; box-shadow: rgba(184, 144, 63, 0.22) 0px 0px 0px 1px inset;"></div>
  </div>
  <!-- Copy column -->
  <div>
    <div class="af-eyebrow"><span class="af-eyebrow-rule"></span>For the self-employed</div>
    <h3 style="font-family: Playfair; font-size: 56px; line-height: 1.03; letter-spacing: -0.025em; max-width: 16ch;">
      Without an employer plan, <em style="color: var(--af-gold-2);">the real price is usually different.</em>
    </h3>
    <p style="font-family: Inter; font-size: 17px; line-height: 1.75; color: var(--af-body); max-width: 500px;">...</p>
    <!-- 2-col stat grid -->
    <!-- Tertiary CTA -->
  </div>
</div>

Mobile: collapses to single column with display: contents flattening intermediate wrappers (see _home/v4.css:3399-...). Order: eyebrow → image → name/role → facts → quote → stats → callout → CTA.

3.7 Form sidebar (the "what happens next" pitch column) ​

Used on the calculator pre-submit. 2-col grid, form on right, support copy on left. See v0.32.0 entry in CLAUDE.md.

3.8 Proof block (Healthcare.gov vs AskFlorence) ​

The conversion-critical comparison. Two columns inside a paper card:

  • Warning column (left): --af-red-tinted callout, sticker price in red
  • Result column (right): gold-tinted callout, subsidized price in green, struck-through original

Both columns share padding, eyebrow, issuer, plan name, premium row, stats grid, callout. The callout text in the result column always frames savings as "Save about $X/yr."

3.9 Navigation ​

html
<nav class="af-nav" style="
  position: sticky; top: 0; z-index: 30;
  background: var(--af-cream);
  border-bottom: 1px solid var(--af-line);
  padding: 18px clamp(16px, 3vw, 48px);
  display: flex; align-items: center; justify-content: space-between;
">
  <!-- AskFlorence wordmark with gold dot -->
  <!-- Inline links: Price calculator, How it works, Stories, Namesake, See your price button -->
  <!-- Hamburger, hidden desktop -->
</nav>

Mobile: links hide, hamburger shows. The hamburger drawer is rendered imperatively in TargetPage.tsx (makeDrawer).

3.10 Sticky mobile CTA ​

When the user scrolls past the hero CTA but isn't yet at the calculator, a fixed bottom-right pill appears. State managed in TargetPage.tsx:186-203 — show when heroPastTop && !calcInView && !footerInView.


4. Page composition (the home as a template) ​

The home flows top-to-bottom in this order. New pages should pick from this menu:

  1. Hero — H1 with italic-gold accent, mobile photo card, primary CTA, trust strip below.
  2. Feature strip — 2-card row + persona pill row. Sets up the "what + who".
  3. Proof block — the conversion-critical comparison.
  4. Lifestyle spread(s) — editorial 2-col cases (For the Self-Employed, For Families).
  5. Calculator — pre-submit form with takeover-on-submit. The conversion gate.
  6. Trust headline — "We handle your data like your doctor."
  7. How It Works — 5-step zig-zag journey. (Currently static; animations deprioritized.)
  8. Audience — 3x2 grid of persona tiles. (Currently static.)
  9. Stories — 5 editorial spreads, named cases.
  10. Namesake — Florence Nightingale brand origin.
  11. Footer / Email capture — final CTA + newsletter sign-up.

Plain-text rules for ALL pages:

  • One primary CTA per fold; multiple is allowed only when separated by clear vertical breaks.
  • Italic-gold accent: max once per major heading. Don't dilute it.
  • Eyebrows on every section unless the section is a single block.

5. Mobile-specific patterns ​

The breakpoint is (max-width: 759px). At that breakpoint:

  • Section padX collapses: 48px → 20px
  • Section padBottom collapses: 120px → 64px
  • All grids collapse to single column unless they were already vertical
  • H1 hits its clamp floor (40px)
  • H2 fluid clamp lands at 30-40px depending on the H2 class
  • Mobile photo card (in hero) is rendered SSR (NOT injected client-side — see v0.29.7 fix)
  • Mobile sticky CTA appears (see 3.10)
  • Lifestyle spread reading order is rebuilt with display: contents + per-item order:
  • prefers-reduced-motion: reduce zeros all transitions and forces transform/opacity to end-state

Mobile gotchas:

  • Always include class="af-section" on new sections so the gutter override catches them.
  • Touch targets must be ≥ 44px (WCAG 2.5.5). 48px preferred (HIG).
  • Body inputs must use inputmode for the right keyboard (numeric for ZIP/age/income, email for email, etc.)
  • iOS Safari URL bar collapses; use 100svh (small viewport height) for hero sizing, NOT 100vh.

6. Naming conventions ​

CSS class prefix: af-l2-{section}__{element}--{modifier}.

  • af- = AskFlorence project namespace
  • l2- = "L2 register" — the editorial home register (vs L1 legacy, no longer maintained on apex)
  • Section name: howw, aud, stories, proof, feature, calc, namesake, etc.
  • Element name: __head, __card, __cta, __pill, etc.
  • Modifier: --olive, --proof, --strike, --green, --mobile

When adding a new section, pick a short prefix and use it consistently across CSS + markup.

Inline styles are OK for one-off positioning but token usage should still prefer CSS variables.


7. Voice & copy ​

Voice is editorial-direct. Short sentences, no jargon, no marketing fluff.

Hard rules:

  • No em dashes (—) or en dashes (–) anywhere. Use hyphens. This rule is enforced even in lib/updates/index.ts. Search for — before committing.
  • No exclamation points outside conversational copy.
  • No "we" / "our" in editorial body copy. Use "you" / "your".
  • No "limited time", "today only", "act now". The platform is free to use forever; urgency cheapens the trust.
  • No fake testimonials. The "stories" section uses illustrative cases ("Common cases, hidden coverage") not pretend customer quotes.
  • "Agent" not "broker". This is a regulatory + relationship distinction.

Headline structure:

  • Lead with the surprise. ("Healthcare.gov showed me $960/mo.")
  • Resolve with the alternative in italic-gold. ("I'm paying $7.")
  • Optional clarifier in body: "Real ACA plans, audited byte-for-byte against CMS."

Helper copy:

  • 11-13.5px Inter --af-stone.
  • One sentence max in helper roles.
  • "30 seconds. No spam calls." is the canonical reassurance line in calculator contexts.

8. Don'ts (the negative space) ​

Don'tWhy
Em dashes anywhereHard rule.
Pure white backgroundsReads as SaaS / generic. Use --af-cream or --af-paper.
Pure black textUse --af-ink (#141C2E).
Round (>8px) cornersBrand is architectural; high radii read as bouncy/chat-app.
Rotating logos / badgesThe lantern is fixed.
Bouncing or elastic motionEasing is cubic-bezier(0.16, 1, 0.3, 1) or (0.4, 0, 0.2, 1). Never back.out.
Glow halos, particles, parallaxDistracting.
Stock-photo crowds, generic shutterstockUse the existing customer-* portrait library or hire a real shoot.
"Powered by AI" badgesWe don't lead with the tech.
Multi-color gradientsSingle-tone gold rules + dot accents are the entire decorative vocabulary.
Below-44px touch targetsWCAG 2.5.5 minimum.
100vh on mobile heroesUse 100svh.
Loading spinners that spin foreverUse the calculator's "Finding your plan…" pattern with a quiet pulse instead.
Modals that auto-show without dismissal persistenceThe /plans WIP modal always shows because it's investor-facing notice; everywhere else, persist dismissal in sessionStorage.
Animation-driven UX states (where missing the animation breaks the flow)Animations are enhancement only. The page must work statically.

9. Accessibility ​

The brand uses warm, low-contrast tones by design. That means accessibility is not optional — it requires deliberate care because the editorial palette flirts with the WCAG line.

9.1 Color contrast audit ​

Computed against WCAG 2.1 AA / AAA thresholds. AA = 4.5:1 for normal text, 3:1 for large text (≥18px regular OR ≥14px bold). AAA = 7:1.

ForegroundOn creamOn paperOn whiteOn ink
--af-ink (#141C2E)15.11 AAA15.77 AAA17.00 AAA—
--af-body (#3A404F)9.21 AAA9.62 AAA10.36 AAA—
--af-stone (#6A6F7C)4.47 ✗ AA4.66 AA5.03 AA—
--af-gold (#C9A967)2.00 ✗2.08 ✗2.24 ✗7.57 AAA
--af-gold-2 (#B8903F)2.63 ✗2.74 ✗2.96 ✗5.75 AA
--af-green (#1F6B49)5.73 AA5.98 AA6.45 AA—
--af-green-d (#184F36)8.44 AAA8.81 AAA9.50 AAA—
--af-red (#A85C3F)4.37 ✗ AA4.57 AA4.92 AA—
--af-red-d (#8B4730)6.14 AA6.41 AA6.90 AA—
--af-cream / --af-white on --af-ink15.11 AAA / 17.00 AAA———

Critical findings:

  • --af-stone on --af-cream is 4.47 — fails AA at small sizes by 0.03. In practice that means: helper text in stone on the cream page background must be ≥18px regular or ≥14px bold (AA-large) OR must move to --af-body (which passes 9.21).
  • --af-gold and --af-gold-2 only meet AA on --af-ink — never use either as body-text color on light backgrounds. Reserve them for headings (≥18px), eyebrows on ink panels, and the italic-gold accent inside H2/H3 (where the font size always exceeds 18px).
  • --af-red on --af-cream is 4.37 — fails AA. Use --af-red-d for any red text on cream/paper that needs to read at body size. --af-red is fine for the strike-through 18px+ price treatment because the visible character is large enough for AA-large.
  • The italic-gold accent in H2/H3 is always safe because H-text is 30px+; AA-large 3:1 is met (gold-2 on cream is 2.63 — wait, that still fails 3:1). Implication: gold-2 italic accents should be on backgrounds where the contrast clears 3:1. On cream/paper (where it's 2.63-2.74), 3:1 isn't met. Rule: the italic-gold accent is decorative, and all critical meaning in a heading must be readable from the ink portion alone. The accent enriches but doesn't carry the message. This is OK because in every home use ("Healthcare.gov showed me $960/mo. I'm paying $7.") the surrounding ink text carries the message; the italic-gold is the emotional flavoring.

Concrete rules:

  • Helper text → --af-body (#3A404F), not --af-stone, when the font size is < 18px and the body bg is cream.
  • --af-stone is fine for ≥18px or for paper/white backgrounds.
  • For data values in plan cards (premium, deductible), the numerals are large enough (≥17px Outfit 600) to pass AA-large in both green and red. Don't shrink them below 17px without retesting.
  • The italic-gold accent never carries critical meaning alone.
  • Strike-through prices on the home use red (4.37 ✗) at large font size (40-56px) — AA-large passes. If you ever ship a small red strike (e.g. inline at 13-14px), use --af-red-d not --af-red.

9.2 Focus states ​

Every interactive element must have a visible focus ring. The canonical focus treatment, used on the calculator inputs (v4.css: 1712-1717):

css
outline: none;
border-color: var(--af-gold-2);
box-shadow: 0 0 0 3px rgba(184, 144, 63, 0.16);

Apply the same recipe to:

  • Buttons: outline: 2px solid var(--af-gold-2); outline-offset: 2px;
  • Links inside body copy: native browser focus is fine (system underline + outline) since most are inside cards with bg-tinting.
  • Cards (when they're the entire tappable area, like aud-tile): use the input recipe — gold-2 ring at 3px alpha.

Do NOT remove focus styles globally. If a treatment looks too heavy visually, fix it with the gold-2 ring at lower alpha — never outline: none without a replacement.

9.3 Keyboard navigation ​

  • All interactive controls are reachable via Tab in document order.
  • Skip-link to main content (NOT yet shipped — flag for the next accessibility pass).
  • Modal dialogs trap focus (the takeover lifecycle does this; see LandingCalculator.tsx).
  • Escape closes modals, drawers, and the takeover.
  • Arrow keys are NOT used for navigation in any pattern (no custom carousels, sliders, tab-ARIA roles).

9.4 ARIA and semantics ​

  • Use semantic HTML first. Native <button>, <a href>, <form>, <input> — these carry the right semantics out of the box.
  • The takeover wrapper has role="dialog" + aria-modal="true" and focus moves to the back button on entry.
  • Hamburger menu has aria-label="Menu" and aria-expanded toggles.
  • Eyebrows are decorative — they don't need ARIA roles.
  • Plan cards in lists should have <article> root with the plan name as the accessible heading.
  • Stat-strike (the through-line on $11,800) doesn't carry meaning on its own; the visible "$0" carries it. Don't add <del> if the strike is decorative — it'll be read aloud as struck-through which is correct here.

9.5 Reduced motion ​

The system honors prefers-reduced-motion: reduce everywhere:

css
@media (prefers-reduced-motion: reduce) {
  /* zero out all transitions + animations */
  /* force end-states for any transform/opacity */
}

Every animation in _home/v4.css includes a reduced-motion override. The takeover cinematic, the calc "searching" pulse, the polaroid hover lift — all collapse to instant transitions when reduced motion is requested. Maintain this discipline for every new pattern.

9.6 Color-blindness considerations ​

The home's conversion moment is built on red (sticker) → green (subsidized). This is the most-common form of color blindness (red-green deuteranopia / protanopia, ~5-8% of men). Mitigations already in place:

  • The strike-through line is a graphic on top of the red text, carrying meaning by shape (line through), not by color alone.
  • The save-pill says "Save 99%" in text, not just green.
  • Premium labels ("Premium" / "Deductible" / "Max OOP") are always attached to numerals, never standalone color swatches.

For new patterns: never use color alone to convey state. Pair color with iconography, label text, or shape (strike-through, badge, border-style).

9.7 Touch targets ​

Minimum 44×44 px (WCAG 2.5.5). 48×48 preferred (HIG primary).

  • ZIP input on mobile: 45 px (just barely AA; bump to 48 in next iteration).
  • Hero CTA: 59 px on mobile ✓.
  • Sticky mobile CTA: 59 px ✓.
  • Audience tile (whole tile is link): 160 px tall ✓.
  • Hamburger button: 24 px icon in 40+ px hit area ✓.

10. Spacing scale ​

The home uses values that align to a 4px base scale with these canonical multiples:

SteppxCommon use
00flush
14hairline gaps, inline icon spacing
28tight vertical rhythm (helper to input, label gap)
312default form-field gaps, card internal spacing
416small section padding, default card gaps
520mobile section gutter (--af-gutter-m)
624inter-element padding, eyebrow → H2
728card padding (proof col), form internals
832H2 → first content, lifestyle copy → stat grid
936lifestyle copy max margin
1040tablet section transition
1248desktop section gutter (--af-gutter-d), H2 → content
1456hero internal vertical
1664mobile section bottom padding
1872lifestyle spread internal gap
2080feature-strip top padding
2496ample vertical breathing
30120desktop section bottom padding
35140lifestyle spread margin-bottom

Rules:

  • Stay on the 4px scale. If you need a value not on this list, question whether you really need it.
  • Don't introduce 22, 26, 30 (between 20/24 and 24/28/32). The current home has a few exceptions (28/22 in calc card padding) but these are tiny and the system won't grow more.
  • Shrink at mobile by jumping down two steps: 120 → 64 (16→8 ratio doesn't hold — but the 64 mobile bottom matches all other sections).

11. State matrices ​

Every interactive element must define its full state set. Most components in the home today only document default + hover. The canonical specs:

11.1 Buttons ​

StateTreatment
Defaultbg --af-ink, color white, padding 16/28, radius 4, font Inter 15/600, ls 0.04em
Hoverbg --af-ink2 (+ same chevron arrow translates 2 px right via CSS)
Focus-visible+ outline: 2px solid var(--af-gold-2); outline-offset: 2px;
Active (pressed)bg --af-ink2, transform translateY(1px)
Disabledbg var(--af-stone), color var(--af-cream), cursor not-allowed, opacity 0.6
Loadingtext replaced with "Submitting…", disabled+pulse on a tiny gold dot to the left of label, button width reserved (no shift)

11.2 Inputs (calculator pattern) ​

StateTreatment
Defaultbg --af-white, border 1px solid var(--af-line), Outfit 17/500 ink
Hoverbg stays --af-white, border rgba(184, 144, 63, 0.45) (gold-2 at 45%)
Focus-visiblebg --af-white, border 1px solid var(--af-gold-2), ring 0 0 0 3px rgba(184,144,63,0.16)
Disabled (broken / locked / unavailable)bg --af-mist (#E8E5DF), color var(--af-stone), cursor not-allowed, opacity 0.6. Use when the field is genuinely off-limits (form-level lock, permission gate, error state).
Disabled-ready (placeholder for upcoming feature)bg --af-white, border var(--af-line) (no opacity dim), cursor: not-allowed, aria-disabled="true", data-tooltip="Coming soon" (or similar). Use when the field is shape-correct and visually confident but not yet wired (e.g., /plans coverage inputs awaiting Phase C/D). The tooltip + cursor signal state without making the field look broken.
Errorbg --af-white, border 1px solid var(--af-red-d), ring 0 0 0 3px rgba(168,92,63,0.18) + helper text below in --af-red-d italic Playfair
Successbg --af-white, border 1px solid var(--af-green-d), ring 0 0 0 3px rgba(31,107,73,0.16) (use sparingly; mostly for live-validation like ZIP code recognized)

11.3 Cards ​

StateTreatment
Defaultbg --af-paper, border 1px solid var(--af-line), radius 4, shadow var(--af-shadow-card)
Hover (clickable cards only)translateY(-1px), border-color var(--af-gold-2), shadow deepens
Focus-visible (clickable cards)+ outline: 2px solid var(--af-gold-2); outline-offset: 2px;
Active (pressed)translateY(0), shadow returns to default
Loading (skeleton)bg --af-cream, no border, shimmer pulse from left to right at 12% alpha gold
Emptydashed border 1px dashed var(--af-line), bg transparent, italic Playfair empty-state copy at --af-stone

11.4 Pills ​

StateTreatment
Defaultper the pill type (metal silver #DCD7C8/#5A5444, etc.) — see §3.5
Hover (interactive only)persona pills: bg #EAE5DB. Static pills (metal/HSA/save): no hover.
Focus-visible (interactive only)outline: 2px solid var(--af-gold-2); outline-offset: 1px;
Active (pressed)persona pills: opacity 0.85
Selected (filter context)persona-pill recipe + border: 1px solid var(--af-ink) + background: var(--af-ink) + color: var(--af-cream). To be used in the /plans filter chip pattern.

12. Logo & asset library ​

The web-rendering subset is mirrored under public/brand/ and rendered on /design-system for the internal browseable reference (and on /brand-guide for the public-facing partner-and-press version). The full canonical asset library (with print PDFs, business cards, email signatures, social templates, web fonts in all subsets, vector QR codes) lives at:

~/Documents/Documents - Taha's MacBook Pro/image-assets/AskFlorence Assets/AskFlorence-Brand-Assets/

Web-mirrored assets (in public/brand/):

Folder / fileUse
logo-primary-{cream,paper,ink,white}.pngPrimary lockup (lantern + wordmark) for landing pages, decks, social covers
logo-stacked-{cream,ink,tagline}.pngVertical lockup for tight horizontals + when tagline is needed
logo-icon-{cream,ink,sand}.pngLantern only (no wordmark) for headers, app icons, watermarks
logo-mono-{dark,light,pure-black,pure-white}.pngSingle-color variants for print, mono partner co-branding, emergencies
favicons/favicon-{16,32,64}-WEB.pngBrowser tab icons
favicons/apple-touch-icon-180-WEB.pngiOS home-screen pin
favicons/app-icon-512-{cream,ink}-WEB.pngPWA + iOS/Android app icon
social/linkedin-company-cover-1584x396-WEB.pngLinkedIn company banner
social/linkedin-personal-cover-1584x396-WEB.pngLinkedIn personal banner (founders)
social/twitter-header-1500x500-WEB.pngTwitter/X header
social/instagram-story-1080x1920-WEB.pngInstagram story backdrop
social/instagram-avatar-{cream,ink}-flame-WEB.pngIG / generic 400x400 avatar
logo-full.{svg,png} + logo-mark.{svg,png}Legacy aliases (kept for back-compat with existing references)

Print / offline assets (canonical library only — do NOT mirror to public/):

WhatPath
Business cards (CMYK PRINT)business-cards/*-PRINT.pdf
Business card previews (web)business-cards/*-WEB.png
Email signature PNGsemail-signatures/email-signature-*-WEB.png
Vector QR codes (per-founder)logos/qr_*.svg
Print logo masters (CMYK)logos/*-PRINT.pdf
Brand kit print-shop guideBRAND-KIT-GUIDE.md

Logo usage rules:

  • Maintain clear space equal to the core node diameter (≈ height of the "A" in the wordmark).
  • Don't add nodes to the circuit, change the flame shape, or rotate the lantern.
  • Don't apply gradients to the logo itself. The brand-asset library's rendered files are the only versions in circulation.
  • The gold dot to the right of the wordmark is part of the lockup. Don't suppress it.
  • For headers and small contexts, use logo-icon-* or the wordmark-only treatment from the home nav. Don't shrink the full lockup below 120px wide.

Web fonts:

The home loads three families via next/font in src/app/layout.tsx — Playfair Display, Inter, Outfit. They're available everywhere via --af-font-serif, --af-font-sans, --af-font-label. Don't import fonts manually in component CSS; use the variables.

A fourth family (Fraunces) ships in the brand-asset library but is used only for brand-asset rendering (the wordmark and certain print materials). Don't add it to the web bundle.


13. Where the canonical reference lives in code ​

WhatPath
Tokenssrc/app/_home/tokens.css
Full register CSSsrc/app/_home/v4.css
Hero + section markup (SSR)src/app/_home/target-body.ts
Calculator (the takeover + form)src/app/_home/components/LandingCalculator.tsx
Page wiring + mobile injects + sticky CTAsrc/app/_home/components/TargetPage.tsx
Plan card primitivesrc/components/PlanCard.tsx (variants: default, compact, micro)
Price reveal primitivesrc/components/PriceReveal.tsx (compact variant)
Hook for calculator pipelinesrc/lib/hooks/use-calculator.ts
Internal design system (visual)src/app/design-system/page.tsx (this doc rendered as a browseable page; noindex)
Public brand guidesrc/app/brand-guide/page.tsx (partner / press / agent-facing identity guide; indexable)

When patterns drift, the home is the authoritative source. Update this doc to match the home, not the other way around.


14. When you're building something new ​

  1. Wrap your page in <div className="af-root">.
  2. Import tokens.css and v4.css from _home/ if you need the full register, or pull just the tokens if you're building a one-off lightweight page.
  3. Pick a class prefix (af-l2-{name}__{element}).
  4. Build sections with the canonical structure (see 2.3).
  5. Make sure every section has class="af-section ..." so the mobile gutter override catches it.
  6. Match the type scale from section 2.2.
  7. Use the editorial italic-gold accent ONCE per major heading.
  8. Default to static; add motion only where it has meaning.
  9. Verify on 360 / 393 / 768 / 1366 / 1440 / 1920 before considering it done.
  10. Run the calculator audit (scripts/audit/calculator-baseline-diff.ts) if you touched any pricing math (you shouldn't be).

When in doubt, look at how the home does it. The home IS the design system.


15. /plans patterns (Wave 1, v0.36.0) ​

The marketplace browse page applies the editorial register to a list-view interaction. It introduces a focused set of /plans-only components on top of the home tokens. Every component uses the af-l2-plans__* class prefix and consumes the shared --af-* tokens. CSS lives at src/app/plans/plans.css.

15.1 Component spec ​

ComponentPathRole
PlansNavsrc/components/plans/PlansNav.tsxEditorial nav for the /plans route. Direct visual port of the home navbar (Fraunces wordmark, gold dot, cream bg, hairline). Inline links: Calculator / How it works / Stories. Right-side primary ink CTA "Edit my info ↗" → /#calculator. Hamburger drawer on mobile.
MarketplaceHerosrc/components/plans/MarketplaceHero.tsxSavings-anchor hero. Eyebrow + H2 with italic-gold accent on second clause + green-chip annual savings line + one primary ink CTA "See best plan". Returns null when totalCount === 0.
CoveragePanelsrc/components/plans/CoveragePanel.tsxThe prominent doctor + Rx affordance below the hero. Two large disabled inputs side-by-side (⚕ doctor / ℞ prescription) + helper note. The user's primary intent on /plans (they came from the calculator's "Check doctor & drug coverage" CTA), so it sits in pride of place. Phase C/D will flip these inputs from disabled to live without restructuring the layout.
MarketplacePlanCardsrc/components/plans/MarketplacePlanCard.tsxEditorial plan card. Carrier + plan name + plan ID + metal/type/star pills + big premium row with strike + ded/MOOP grid (per-person + family when household ≥ 2) + dedicated copays section (5 standard copays) + disabled coverage pills (⚕ / ℞) + disabled save heart (top-right) + footer plan-document links. 4px metal-tier left strip.
FilterSidebarsrc/components/plans/FilterSidebar.tsxDesktop sticky left column. Metal pills, carrier checkboxes, plan-type pills, HSA toggle, premium / deductible range sliders. Bottom carries an italic-Playfair note signposting where Phase C/D coverage filters will surface (in CoveragePanel above, not here).
FilterBottomSheetsrc/components/plans/FilterBottomSheet.tsxMobile slide-up sheet using the same FilterSidebar body in variant="sheet" mode. Drag-handle, "Clear all" + "Apply (N)" footer with safe-area-inset-bottom, body scroll-lock, focus trap, Escape to close, backdrop click to close.
SortPillssrc/components/plans/SortPills.tsx4 sort options as a pill row (desktop) / native select with custom chrome (mobile). Active state = ink bg + cream text per §11.4 selected pill recipe. Designed to grow to 6 when Phase C/D adds rx-coverage and doctor-coverage sort options.
RangeSlidersrc/components/plans/RangeSlider.tsxDual-handle range with header showing current min / max formatted. Two stacked range inputs + track-fill overlay. Gold-2 thumb, accessible via Tab.
EmptyStatesrc/components/plans/EmptyState.tsxItalic Playfair "No plans match your filters." + helper + ink primary "Reset filters" CTA + tertiary "Edit my info" link.
PlanLoadingSkeletonsrc/components/plans/PlanLoadingSkeleton.tsx5 paper cards with shimmer keyframes. Same height + structure as MarketplacePlanCard so layout doesn't reflow on data load.
PlansWipModal (restyled)src/components/PlansWipModal.tsxEditorial paper card, italic-gold accent on "still being painted." headline, ink primary CTA. Always-mount preserved by design — investor visits should see this every time until founder explicitly says to remove it.

15.2 Disabled-affordance pattern ​

The save heart on each card and the two coverage pills (⚕ Check doctors / ℞ Check Rx) are visible-but-disabled in Wave 1. They carry:

  • aria-disabled="true" plus the native disabled attribute
  • Lower opacity (0.5 for save heart, 0.85 for coverage pills)
  • Dashed border (coverage pills only) to signal "input field, not yet wired"
  • A data-tooltip attribute that surfaces via CSS ::after on hover/focus
  • cursor: not-allowed

Screen readers announce "Save plan coming soon, button, dimmed". When Phase C/D ships, removing the disabled attr + wiring the onClick is the only change required — layout doesn't shift because the affordance is already in place.

15.3 Forward-compat hooks for Phase C/D ​

The /plans Wave 1 ships with explicit hook-points for the parallel doctor + Rx worktree (~/Developer/ask-florence-doctor-rx/):

  1. CoveragePanel inputs flip from disabled to live; chips appear above the plan list when filled.
  2. Card coverage pills flip from disabled to live, drop their data-tooltip, and gain a state machine (default / loading / "3 of 3 covered").
  3. SortOption type grows by two values ("rx_coverage_desc", "doctor_coverage_desc") and SortPills renders 6 pills.
  4. FilterSidebar grows by two filter sections; the existing italic note signposts where they'll appear.

None of these hooks involve new MongoDB fields or API changes — all the data is already on each PlanDisplay (puf.formularyId, puf.networkId).

15.4 Visual reference ​

The /design-system page renders all /plans components in their default states under section 16. The home page itself (/) is still the authoritative implementation of the L2 register; the /plans surface is the register applied to a marketplace interaction.


16. Mermaid diagrams in docs ​

Mermaid is the canonical diagram tool for docs/ (VitePress + vitepress-plugin-mermaid). The architecture page and the agents workflows page are the reference implementations — both use a "small focused unit" pattern: short intro paragraph → one tight diagram → table → cross-reference.

16.1 Pick the right diagram type for the job ​

Use thisWhenExample in repo
sequenceDiagram"Who does what when" — the actor for each step mattersarchitecture.md state routing, workflows-and-pain-points today vs AskFlorence
flowchart / graph TB, LRBranching logic, decision trees, or a topology shown spatiallyarchitecture.md AWS Org topology, workflows visual summary
erDiagramShowing relationships between data modelsarchitecture.md MongoDB collections
stateDiagram-v2Lifecycle of a single entity moving between statesconsumer-agent-flow agent queue

Sequence diagrams beat flowcharts when actor responsibility is the load-bearing question. The agents workflows page works because each row of the sequence shows who's doing the work. A flowchart hides that — actors are implicit. If the diagram is making an "X is overloaded" or "X is automated away" point, sequence diagram first.

16.2 Gotchas (we've hit these — don't repeat them) ​

GotchaSymptomFix
Semicolons in message textA->>B: foo; bar parses as two statements (A->>B: foo + bogus bar). The diagram silently fails to render with no error visible in the dev server output (Mermaid errors are client-side).Use commas, hyphens, or break into two messages. Never put ; in sequenceDiagram message text.
Em dashesDon't cause parse errors but violate the project style rule (no em dashes anywhere new).Use hyphens (-) or commas.
Activation operators + / - immediately after arrowA->>+ B: msg activates B; A->>- B: msg deactivates B. Fine if intentional, confusing if accidental.Inside message text + / - are fine. At the start of the participant ID (right after the arrow) they're activation operators.
Parens in participant alias with HTML breakparticipant X as Name<br/>(detail) works in modern Mermaid but some older versions choke on the parens.If a Mermaid build pinned to an older version fails, wrap the display name in quotes: participant X as "Name<br/>(detail)".
Trailing colon on NoteNote over A,B: During the year: — fine, but the parser sometimes shows weird spacing.Acceptable; just be aware.
Mermaid errors are client-sideThe page returns HTTP 200 even when a diagram fails. The dev server log shows nothing. Errors only appear when you open the page in a browser (sometimes only as a missing diagram block, sometimes as a rendered error message).When a diagram doesn't appear, open the page in a browser and check the DevTools console for the Mermaid parse error. Or bisect: comment out half the lines, see if the rest renders.

16.3 Style conventions ​

  • Diagrams are authoritative. When a diagram disagrees with code, the code is wrong (architecture.md TL;DR establishes this rule for the whole docs/ tree).
  • Mermaid colors should align with the project palette (--florence-cloud, --florence-cream, --florence-gold, etc.) where styling matters. The workflows visual-summary diagram uses sand/blue/green/red/dark-green to encode "agent / member / AI / waiting / earns passively" — that color encoding stays consistent across the doc.
  • Don't hack invisible edges to control layout. The ~~~ cross-subgraph anchor trick + transparent "legend" subgraphs (used in v1 of workflows-and-pain-points) is brittle. Prefer multiple smaller diagrams with proper headings + a separate legend table.
  • Caption each diagram. Short paragraph above explaining what to look for; short paragraph or table below tying the takeaway back to the larger narrative. The diagram alone shouldn't have to do the storytelling.
  • Cross-link to ADRs / session logs in the prose around the diagram when the architecture has a decision-record.

16.4 Local preview ​

VitePress dev server (cd docs && npm run dev → localhost:5173) hot-reloads Mermaid diagrams on save. If your local dev fails with a Cannot find module '@tailwindcss/postcss' error, the docs subproject needs a docs/postcss.config.cjs stub with module.exports = { plugins: [] } — this shields Vite from walking up and picking up the parent Next.js app's Tailwind config. (Already in place since 2026-05-12.)

Pager
Next pageHome

AskFlorence Internal Documentation. Not for public distribution.

AskFlorence

Internal Documentation

Access restricted. Not for public distribution.