The design system, locked.
Reading this document
This page is the single source of truth for the design and brand system of citelight.ai as shipped in this version. It is written for whoever touches the site next: designer, engineer, or Andy in six months. Everything here was extracted from the live build, not reconstructed from memory. The :root token block in this page’s own stylesheet is copied verbatim from index.html at build time, and every swatch and specimen below renders from those variables. If this page looks wrong, the system has drifted; fix the drift, then rebuild this page.
“Locked” means: these decisions were made deliberately, tested at both breakpoints, and survived review. Changing a locked value is allowed, but it is a design decision with site-wide consequences, not a tweak. Section 13 defines what is open.
Brand core
Premise. Citelight measures whether AI platforms (ChatGPT, Perplexity, Gemini, Google Search: always these four, always this order) cite a real estate agent when buyers and sellers ask who to work with. The product’s emotional engine is exclusion and its resolution: the agent discovers they are absent from answers their competitors live in, then watches the light move to them. AI Overview is captured and reported when it appears, and not scored.
The metaphor is citation-light. Gold is not decoration; it is the light of being cited. This single rule governs every use of aurum on the site: a gold element must represent citation, presence, or the action that earns them. A gold divider for taste is a violation.
Two registers, one world. The paper world (bone grounds, ink text) is where the agent reads, decides, buys. The vessel (the dark gradient) is inside the machine: streamed answers, audits, the takeover. Paper is calm and editorial; the vessel is where things happen to you. Crossing between them is an event, never a casual background change.
Lexicon. Speak the system’s language in code and conversation: bone (paper grounds), ink (text on paper), aurum (citation gold), void (vessel palette), vessel (the dark surface itself), pmark (platform mark), absence salmon (the not-cited tone), the ledger and the search (the mobile audit mechanics).
Language laws
Copy is part of the design system and carries hard rules. These apply to UI text, marketing copy, study pages, emails, and code comments alike.
Casing. Eyebrows and the marquee keep spaced caps in Söhne. Mono labels and table headers are sentence case with no tracking since 5.87: tracked all-caps mono reads as machine chrome, and the mono face already says data.
Typography
One family speaks for the brand: Söhne (Klim Type Foundry), with Söhne Mono as the machine’s voice. Sans carries reading and display; Mono is reserved for genuine data registers: labels, timestamps, counts, queries, machine output. Mono used decoratively is a violation.
assets/fonts/soehne-*.woff2 behind assets/fonts.css.Faces in the build
Söhne Buch 400 · body, ledes, long reading
Söhne Kräftig 500 · every heading and button on the site
Söhne Halbfett 600 · rare emphasis inside data surfaces
Söhne Mono Buch 400 · labels · queries · counts
Söhne Mono Halbfett 600 · emphasized data
Scale as shipped
| Role | Size | Tracking | Notes |
|---|---|---|---|
| h1 (hero) | clamp(2.65rem,6.6vw,5.5rem) | -.03em class | line-height 1.02, text-wrap balance |
| .h-section | clamp(1.9rem,3.8vw,3.1rem) | -.028em | weight 500, ink-0 |
| .lede | clamp(1rem,1.35vw,1.16rem) | default | max-width 52ch, ink-2, text-wrap pretty |
| .eyebrow | .72rem mono | .16em caps | ink-2, inline-flex with .6em gap |
| body | 1rem/1.6+ | default | ink-1 on paper, void-fg-1 in vessel |
| credits caps (marquee) | clamp(.86rem,1.5vw,1.02rem) | .24em (.18em ≤640) | weight 500, ink-2 |
font-variant-numeric:tabular-nums on every count, percentage, timer, and score. A rolling count in proportional figures is a defect.Color
Four families and one rule of scarcity: the page is bone and ink; aurum appears only as citation-light; the vessel is its own world with its own foreground ramp. Platform tints exist solely to identify the four platforms in data marks.
Bone · paper grounds
--bone-0#F1EEE8Page ground. The default paper.--bone-1#EAE7E0Raised paper: cards on the ground, one step lighter-warm.--bone-2#E3DFD5Recessed paper: wells, input grounds.--bone-3#D9D4C8Deep recess. Rare; use before reaching for a border.Ink · text on paper
--ink-0#1A1613Display and headline ink. Never for long body.--ink-1#37322BBody ink on paper.--ink-2#6E685EMuted ink: ledes, descriptions, marquee names.--ink-3#9C9589Whisper ink: leads, ornament labels. Below AA alone; only for non-essential text.Hairlines on paper
--linergba(26,22,19,.10)Hairline on paper.--line-2rgba(26,22,19,.17)Hairline, emphasized (hover, active edges).Aurum · citation-light
--aurum-300#F6D378Light aurum: chips, CTA fills on the vessel, marks on dark.--aurum-400#F4C95DMid aurum: gradients, glows.--aurum-500#E5B53EBrand aurum: bars, lit dots, citation-light on paper.--aurum-600#B8913ADeep aurum: gradient anchors.--aurum-ink#8A6B22Aurum as TEXT on paper (contrast-corrected). Never use 300-500 for text on bone.--aurum-glowrgba(229,181,62,.30)Glow at strength (lit dots, focus moments).--aurum-glow-softrgba(229,181,62,.09)Ambient glow. Backgrounds only.Void · the vessel
--void-0#141009Vessel floor (gradient end).--void-1#1C1710Vessel body (gradient start rgb(28,23,16) pairs with this family).--void-2#262016Raised element inside the vessel.--void-fg-0#EDE7DCPrimary text in the vessel.--void-fg-1#B7AF9FBody text in the vessel.--void-fg-2#928A7CMuted text in the vessel.--void-linergba(246,211,120,.13)Hairline inside the vessel: aurum at 13%, never grey.Platform tints
--t-gpt150,195,165ChatGPT tint (r,g,b triplet).--t-plx60,165,195Perplexity tint.--t-gem130,145,235Gemini tint.--t-aio235,155,85AI Overview tint.Absence has a color: salmon, used only for the not-cited state. Text rgba(222,138,107,.95), borders rgba(222,138,107,.3). It exists in the vessel only.
color:inherit with opacity, never a named grey. Where text sits on imagery, its color derives from the measured luminance of what is actually behind it, re-derived when the ground can change; if the composite cannot be sampled honestly, the element gets its own opaque scrim. Naming a color here has broken this site before.Surfaces & depth
Bands. Sections are .band blocks with vertical rhythm --band-y: clamp(88px,12vh,164px). Grounds change by band class; content is centered by .band__in (max-width --max, inline padding --gut).
Paper surfaces. Cards use the fill-delta recipe: interior panels are one bone step away from their parent, hairline --line, and at most two shadow layers (a 1px contact shadow plus one long soft drop). No third material inside a card.
The vessel. linear-gradient(rgb(28,23,16), rgb(20,16,9)). Inside it, hairlines are --void-line (aurum at 13%), never grey; text uses the void-fg ramp; raised elements use --void-2.
Radii
--r-lg22pxLarge surfaces: cards, exhibits.--r-md14pxInterior elements.--r-sm9pxSmall interior chips, wells.--r-pill999pxButtons, chips, stamps, bars.0.5px renders nothing on 1x screens. Thinner lines are made with 1px at lower alpha.Layout & breakpoints
| Token | Value | Meaning |
|---|---|---|
--max | 1200px | content column ceiling |
--gut | clamp(20px,5vw,48px) | inline gutter, fluid |
--band-y | clamp(88px,12vh,164px) | section vertical padding |
Breakpoints and what actually changes
| Width | Change |
|---|---|
| ≤860px | Exhibits stack single-column (copy above surface; flipped exhibits un-flip). Share rows compress their grid. |
| ≤720px | The device split. Audit submit opens the mobile takeover instead of the inline theater. This is the boundary between the two exclusive audit experiences. |
| ≤640px | Marquee tracking eases to .18em; share names wrap to 2 lines; after-tag reflows to its two-row grid; type floors enforced. |
Motion
Two curves, one duration scale, and a doctrine: motion is diegetic. Every animation depicts something the interface is actually doing (extracting a name, searching text, filling a segment of elapsed time). Motion that only decorates is removed.
cubic-bezier(0.16,1,0.3,1)
cubic-bezier(0.4,0,0.2,1)
Duration scale
--d-micro.16sPress feedback, transforms under the finger.--d-state.24sHover, color, state changes.--d-move.32sElements changing place.--d-long.5sSurfaces arriving or leaving.--d-epic1.1sReserved: one-time theatrical moments only.--beat.09sStagger unit between siblings.Arrival grammar. Entrances use .rv: opacity 0 and 12px rise, .6s on the house curve, staggered by --beat via .rv-2 etc. Arrival is a one-time event with memory; scroll does not scrub it backwards. High-frequency interactions animate at --d-micro/--d-state or not at all.
Counters roll only when something is decided. A count changes at the moment a conclusion lands (a platform finishes, a chip arrives), rendered as a masked digit swap. Ambient counting is banned.
Reduced-motion contract, per component
| Component | Prefers-reduced-motion behavior |
|---|---|
| .rv reveals | Elements render settled; no rise, no fade choreography. |
| Brokerage marquee | Static centered wall: item-level wrap, mask off, duplicate set hidden. |
| Audit (mobile + desktop) | Theater and takeover never mount; submit goes straight to the done state. |
| qfield ambient prompts | Drift suppressed; field rests composed. |
| Hero / footer video | Poster frame; video does not autoplay. |
Marks & walls
Platform marks. Official monochrome marks (OpenAI, Perplexity, Gemini, Google) inlined as .pmark SVGs on currentColor. On paper they run ink-toned and muted; in data contexts they take their platform tint via color:rgb(var(--t-*)). Full-color logo walls are banned: muted marks read as confidence, color reads as a sponsor wall and implies endorsement.
The brokerage wall. Nine official monochrome marks, one ink. The standing ruling holds and is unchanged: official assets or deliberate typographic voice, never in-between, and never a redrawn wordmark. The ruling never banned logos - it gated them on completeness, and the gate is now met, so the wall converted. Every mark is knocked out of its official file and tinted to a single baked ink (#57514A, measured 6.04:1 in situ against the band). Ink is baked rather than applied as CSS opacity, because opacity over the mark lightens the effective colour and invalidates the measurement. Corcoran is the one name held back: the supplied file is a crop with the outer letterforms amputated at the canvas edge, and a shaved wordmark is not an official asset. It returns to the wall when a clean file exists. wall-scan in the decay gate now enforces the ruling: a wall that mixes marks with typographic brand names closes the gate, as does a mark whose file is missing from the deploy. The italic closer is exempt - it is a phrase, not a brand.
Component registry
Every named component, its purpose, and its laws. File references are the component’s home; shared pieces live in index.html unless noted.
Navigation
- purpose
- Persistent brand + single CTA; recedes behind content, never competes with it.
- laws
- 44px hit areas throughout. CTA is the standard pill button. No hover motion beyond
--d-statecolor.
Hero
- anatomy
- Display h1 on bone, ambient loop video (
hero-loop.mp4, mobile variant) behind measured space, palms as depth props. - laws
- Video rests at opacity 0 until
.playing; poster covers reduced-motion and failed autoplay. Type sits in composed empty space, not over busy imagery.
qfield · the ambient query field
- purpose
- Shows the questions buyers ask, drifting as ambient whispers that converge when the field is engaged.
- laws
- Whisper type (~9px) is a documented ornament exemption from the type floor: the whispers are texture, not reading text, and their convergence state is fully legible. Do not “fix” them.
Monitor · platform rows
- anatomy
- Four rows, official pmarks, cited/not states, position chips.
- laws
- Order is always ChatGPT, Perplexity, Gemini, Google Search. AI Overview is captured and reported when it appears, and not scored. Data must reconcile with every other surface (see Data canon).
Exhibits · the product surfaces
- anatomy
- Three copy+surface pairs: the Monday brief (mail), competitor citation share (ranked bars), coverage grid (prompt dots). Alternating flip layout above 860px.
- laws
- Gold appears only as citation-light (fix labels, your bar, lit dots). Share percentages sum to 100 and use tabular figures. The flipped exhibit uses a mirrored grid template:
orderalone squeezes the surface.
The vessel · inside the answer
- purpose
- First crossing into the machine: a streamed answer with role-anonymized names, then the turn (“yours can be too”).
- laws
- Vessel material rules from section 05. Prose is readable here; ILLUSTRATION marking applies until real data.
Audit dock · form and states (audit/index.html)
- anatomy
- Name + market form → experience → done (email capture) → sent. States swap;
[hidden]{display:none!important}guards every state against display-rule leaks. - laws
- The submit button is the styled 52px pill: a bare UA button shipped once because the page never defined
.btn. Any new page using shared class names must import or redefine them.
Desktop audit theater · psurf
- anatomy
- Four sequential platform surfaces (~12s): query, redacted answer scan, citation chips, conclusion stamps.
- laws
- Desktop-exclusive (>720px). Slated for replacement by the switchboard (four concurrent panes); until then it is the shipped desktop experience.
Mobile audit takeover · the ledger and the search
- anatomy
- Full-screen vessel overlay, story segments, per-platform scenes; persistent bottom ledger (three competitor rows + YOU row pinned at salmon 0); name extraction as chip flight; per-answer search beam (“searching for {name}… no mention found”); finale knife and CTA into the done state.
- laws
- ≤720px only. Data-driven renderer over an embedded SPEC (swap for API mode; ILLUSTRATION marks drop). Tap advances via the scene skip with the applied-guard so counts never double. Counts land exactly 4/3/2/0. Scroll locks while open; X and Escape close; 44px close hit; safe-area padded.
Brokerage marquee
- anatomy
- Duplicated identical sets in a
translateX(-50%)track, 42s linear, hover-pause, 12%/88% mask fades, item-level RM wrap. - laws
- Seam integrity requires the two sets pixel-identical and the track exactly twice one set. eXp keeps its casing. Roster is curated copy, not a dumping ground.
Report template · /report/
- purpose
- Mobile-first rendering of a real audit from the
#auditDataJSON contract; specimen mode shows the fictional market. - laws
- The contract is canonical: engineering fills the same shape the takeover SPEC mirrors. Template renders whatever the data says; no hand-edited numbers.
Footer
- laws
- Dusk imagery register; link hit areas 44px; the footer CTA inherits text color (a named color broke it once).
Floors & exemptions
- Touch floor: every interactive element ≥44px hit area, achieved with padding or negative-margin pads, never by inflating glyphs.
- Type floors: reading text ≥.875rem; data microcopy ≥10px; superscripts
max(.62em, 9.5px). - Documented exemptions (do not fix): qfield ambient whispers (~9px, ornament); illustration watermarks (10px); rcard index numerals (.625rem, non-reading).
- [hidden] guard:
[hidden]{display:none!important}on every page; display utilities silently defeat the attribute otherwise. - Hover gating: hover-only affordances live behind
@media (hover:hover); touch gets resting-state parity. - Focus: visible focus via box-shadow rings on the house curve; never
outline:nonewithout replacement.
Data canon · one coherent world
Until the real audit engine ships, every surface draws from one fictional market so a visitor who reads everything finds a single consistent world. All of it is marked ILLUSTRATION.
| Entity | Canonical value |
|---|---|
| Competitor 1 | Las Olas Isles specialist · cited on 4 of 4 platforms |
| Competitor 2 | Rio Vista listing agent · 3 of 4 (ChatGPT, Perplexity, Google Search) |
| Competitor 3 | Harbor Beach broker · 2 of 4 (GPT, GEM) |
| Share table | 34 / 22 / 17 / You 9 / five others 18 (sums to 100) |
| Queries | Canon held internally; the scan set is B2, S1, B1, S2 |
| Contracts | /report/ #auditData JSON and the takeover SPEC share one shape family; API integration swaps data blobs and drops ILLUSTRATION marks everywhere at once |
Verification doctrine
- Look after green. When mechanical checks pass, the next step is viewing rendered output at shipping size, not adding another check. Measurement finds what it was told to find.
- Resting states are states. Capture every state a flow passes through, at rest; phantom visible states ship when only the happy path is screenshotted.
- Walkers pin
scrollBehavior='auto'before tile capture; smooth scroll races the shutter and manufactures false empties. - Two-probe rule. Expose a suspect element’s own state within two probes; a third failing probe means suspect the probe.
- Assert after every replace. String patches assert their count before and verify after; fixes have “landed” without changing anything.
- Consolidate sediment. When a pass touches a component more than once, end with one authoritative definition, delete the rest at source, then verify the behavior the deleted lines produced.
- Harness: server + Playwright share one bash invocation; mobile runs use
device_scale_factor:2, is_mobile, has_touch; self-verify with saved stills before delivery, never by asking Andy to look.
Governance
Locked: tokens, type system and license scope, the two registers, motion doctrine and RM contracts, language laws, the data canon, component laws above.
Open: new sections and surfaces, the desktop switchboard build, copy refinement inside the laws, roster curation, real-API data replacing the canon.
The extension bar. A new surface earns its place with one structural idea that only it uses. If a proposed section could be described as two-column copy/image, centered hero, or a rounded rectangle grid sharing one rhythm, it does not ship; it gets a concept. “Feels generic” from Andy triggers a full concept rebuild, never a patch.
Ship gates: the study gate exits 0; em-dash grep is clean; both breakpoints captured and reviewed; overflow 0; zero console errors; processor-scan passes, meaning every external host in shipped code is named in the privacy policy; deploy via vercel --prod from ~/citelight-deploy/ with hash verification.