Design foundations — single reference for the new system. Locked decisions only; open explorations live at /brand-sandbox.
last updated 2026-06-10
Visual
Wordmark
Fraunces. Locked variable axes: opsz 144, wght 550, SOFT 50, WONK 1 (the "wonky" axis — the cocked serifs + flared terminals that give the mark its character), roman. Tracking -0.02em (em-relative, scales with size). opsz is pinned at 144 (optical sizing off) so every render is the same display cut, at any size — and matches the outlined favicon/SVG exports.
One source of truth: the font-wordmark token (composes over font-display). Every logotype render uses it; retune the axes once in shared-styles.css.
Always lowercase. "noticed", never "Noticed".
Logo and display moments only — never in product UI.
Brand mark
Lowercase "n" in Fraunces inside a square. Rounded uses the iOS-app-icon corner ratio (22.5% of side); the square (non-rounded) version is the one used for the favicon + browser/app icons.
Used as favicon, app icon, and avatar. Light + dark variants, rounded + square. Download is a single 1024×1024 asset — scale down as needed. The favicon ships the dark square mark (regenerate via scripts/generate-brand-icons.mjs).
Re-tune at /brand/tune.
Typography — product UI
Switzer for every product surface. Hierarchy comes from weight (400 / 500 / 600), not from changing typefaces.
These specimens render the live type tokens — size and line-height come from the token itself, so this page can never drift from the system.
Row text, nav items, captions.
Row names, buttons, sub-bar.
Headings, section titles.
Typography — display
Fraunces, regular, tracking -0.04em at large sizes.
Always lowercase. Sentence case for longer marketing copy; never title case or all caps.
Roman only; never italic. Emphasis comes from cadence and line breaks, not from leaning the letterforms.
Used only for the wordmark and marketing display moments. Never appears inside the product UI.
Color — surfaces
Warm monochrome on cream (light) and white-on-near-black (dark).
Muted variants use alpha — oklch(0 0 0 / N) in light, oklch(1 0 0 / N) in dark. Each row shows both modes so the symmetric pattern is visible at a glance.
Two surface levels: the background canvas, and the raised surface shared by card, popover, and sidebar — borders and shadows define hierarchy, bg contrast stays minimal.
Brand accent — spring
spring. A warm yellow-green that sits between the canvas and the deep ink — the moment of new growth, of being noticed.
The hue (125°) is honest about what it is: not fern, not lime, not money green. It pairs with the stone canvas without competing, and stays distinctive against the warm grays that do most of the work.
5-stop ramp · semantic aliases swap by theme.
Brand-colored text is always text-brand-text — never text-brand. The chip fill (brand-200) is ~1.2:1 on light surfaces as text. Lint-enforced.
| token | light | dark | use |
|---|---|---|---|
| --brand | brand-200 | brand-200 | the chip — marketing primary CTA |
| --brand-foreground | brand-700 | brand-900 | text on chip |
| --brand-text | brand-700 | brand-200 | brand-colored text + icons |
| --brand-wash | 22% alpha | 22% alpha | selection wash · brand callout bg |
| --button-app (light) | white card | — | app primary chip · sublime CRM |
| --button-app (dark) | — | 10% white | app primary chip · sublime CRM |
Six muted hues for tags, industry labels, persona categories, project types — anywhere categorization is required. Spring (our brand) is not in this set: categorical color shouldn't compete with brand identity. Pattern: base color for icons + text, 10% alpha of the same hue for the bg.
--tag-blue · --tag-amber · --tag-purple · --tag-pink · --tag-coral · --tag-teal
Surface rules
Marketing and app share the same discipline for everything except three things.
Two elements differ between marketing and app surfaces — primary CTA and secondary CTA. Everything else is identical. Brand appears as interactive state: selection wash, text-selection highlight, focus rings, checkbox/switch ON, tiny unread dots, chat send button, links to data, display accent.
| element | marketing | app |
|---|---|---|
| Primary CTA | Loud green chip — the brand event | White card chip + border — neutral |
| Secondary CTA | Recessed gray chip — no border, matches primary | Active = bordered elevation chip (matches primary). Inactive = ghost (no bg, no border, muted text). Sampled from a segmented control on Lightfield CRM. |
Borders, radius, surfaces
0.5px subpixel borders. 6px radius. One elevation level.
Shadows reserved for popovers and dialogs only.
Tokens: border-hairline all-sides, border-{t|r|b|l|x|y}-hairline directional. Never an arbitrary border-[…].
divider · 0.5px · border-border
Shadows
Two elevation tiers + one menu treatment. Neutral, no glow.
Tokens: shadow-raised / shadow-floating / shadow-popover, defined in packages/tailwind-config/shared-styles.css. The drops are identical in both modes; the popover hairline ring is themed per mode. The demos below render the live utilities.
raised · for buttons + small lifted controls
shadow-raised · 0 1px 3px / 0.04
The minimum "lift" — used on primary CTAs, the + create chip, any control that should feel raised by ~1px.
floating · for panels off the page
shadow-floating · 0 8px 24px / 0.06
Bigger lift for surfaces that overlap the canvas — chat side panel, peek panes, floating cards. Reads as "detached."
popover · for menus + dialogs
shadow-popover · 0.5px themed ring + 0 6px 18px / 0.06
Hairline ring + soft drop. Menus + dropdowns + dialogs need the ring because they portal over arbitrary content; a drop shadow alone can't separate them on busy backgrounds.
Badge patterns
Three variants plus a special count chip. Hierarchy goes outlined → subtle → categorical.
Categorical lives in its own palette (see Brand accent · categorical palette). Outlined + subtle share the same chip shape; only fill + text-weight differ. State-change moments that want emphasis (new, urgent, unread) use the count chip's recipe — brand-wash bg + brand-text — rather than a fourth neutral variant.
| variant | example | recipe + use |
|---|---|---|
| outlined | Beta | Transparent bg · 0.5px standard border · full ink text · 4px radius. Used for status labels that should sit quietly next to a title (Beta, New, v0.1.0). |
| subtle | intro ready | --button-app bg · 0.5px border · muted text. Used for soft status (intro ready, warm lead) — visible but doesn't shout. |
| categorical | SoftwareFinance | Hue at 10% alpha bg · same hue at full saturation text. Six hues available; see Brand accent · categorical palette. Used for tags, industries, persona categories. |
| count | 1 | --brand-wash bg + --brand-text. Used for nav unread counts, notification numbers, any small numeric "new state" indicator. Brand-tinted so the number reads as a state signal, not a neutral counter. |
Buttons on surfaces
One convention, so a button's weight always means the same thing. Weight is fixed by role; width adapts to the label (with a shared min so a column of actions still aligns).
Filled default = the one action that moves you forward (Connect, Import, Request). surface = the grey secondary on any white card (Manage, Disconnect, Share, a done state). ghost = icon + row controls. Never put outline on a card — it's for non-card chrome. Full reference: docs/brand/buttons.md.
| weight | example | recipe + use |
|---|---|---|
| default · primary | Filled brand. The single "do this" action on a card — at most one per card. Also the connected-state signal: filled while there's an action left to take. | |
| surface · secondary | Raised white card + hairline + shadow-raised. The grey secondary on any white surface — Manage, Disconnect, Share, and done/disabled states. Goes grey once the action is complete. | |
| ghost · minimal | Transparent until hover. Icon-only buttons, pagination, table row actions — low-affordance controls that shouldn't compete with the card's content. | |
| outline · not on cards | Recessed off-white. Reserved for non-card chrome (toolbars, consent forms). On a card it reads as a third elevation and competes with surface — use surface instead. |
Icons
Phosphor, Regular weight only.
All icons render through <Icon>, which defaults to weight="regular".
Motion
150–200ms, ease-out.
No bounce, no spring.
Baked into the transition defaults (--default-transition-duration 200ms + ease-out) — a bare transition-* utility is already brand-correct. Declare a duration/easing only to intentionally diverge.
transition-colors · 200ms ease-out from the defaults · color transition only, no transform.
Imagery / art direction
Photo treatment, illustration policy, and where each applies.
Marketing scope. Product UI uses no imagery beyond avatars and icons.
Voice & copy
Positioning
Who we are, who for, what we replace. One paragraph.
Anchors every copy decision downstream.
Personas — user
Who we're designing for.
Distinct from the landing page's investor / client / hire rows — those are marketing slots, not foundational personas.
Personas — agent
Three voices the agent can take. Default donna.
Tone only — capabilities are identical. Voice rules below always win.
Source: packages/agent-core/src/persona-catalog.ts.
Voice
How the brand speaks. Applies to every agent reply, marketing line, and UI label.
Canonical source: docs/brand/voice.mdx. The principles below are a synced snapshot of its core rules.
Hover any rule for approve · edit · dismiss. Picks live in localStorage.
care first. the reader is busy, distracted, maybe a little lost. the job of the copy is to make their day a fraction lighter. warmth is the brand. everything else is technique.
say less, but stay human. one sentence often beats two. white space gives the eye somewhere to land. but short isn't the goal; care is. a sentence that breathes beats one that snaps.
write how people talk. read it out loud. if it sounds like a press release, rewrite it. if it sounds like a text to a smart friend, ship it.
casing follows the typeface. fraunces display headlines + the wordmark are lowercase; everything in switzer (product UI, headings, body, copy) is normal sentence case, with title case for proper nouns. two exceptions stay all-lowercase: the agent's chat replies and @noticedso social posts. the brand name 'noticed' is always lowercase, never a capital N.
don't explain AI. never say "powered by AI" or "leveraging machine learning." the product just works. the agent goes unnoticed. the result is what matters.
use "you," not "users" or "customers." noticed is a conversation between the product and one person.
no emojis in copy, marketing, pitch decks, or product UI. only exception: chat reactions (a single emoji, the way a friend would on Slack).
no em dashes (—). default to a period, a colon, or a new sentence. hyphens are a different mark, not a substitute.
lead with the person, not the product. the user is the founder, the builder, the operator who already has the connections. say what they become, not what we do.
break for breath, not for layout. in display copy, line breaks set rhythm. one thought per line. let the period do the work the comma can't.
earn the irreverent line. once per page, allow one line that breaks formality ("wtf is networking, anyway?"). never twice. never as a default — always as a hook.
Lexicon
What to call things. What to avoid.
Lexicon = copy / voice level — the words the brand says (you not users, agent not assistant). Drives consistency across copy, product UI, and agent replies.
Glossary = technical / internal — definitions of company-specific concepts (signals, opportunities, relationships). Lives elsewhere; don't conflate.
| Use | Rule |
|---|---|
| noticed | always lowercase. never "Noticed" or "NOTICED". |
| agent | not "assistant", not "bot". |
| you | not "users", not "customers". one person, in conversation. |
System
Token system
How tokens are organized and enforced. Three tiers: primitives feed role tokens (content-*, fill-*, border-*, interactive-*) which feed shadcn compat.
Always use a token, never an arbitrary value. The rules + WCAG exemption that used to live in design-system-rules.md.
| Rule | Rationale |
|---|---|
| Never use arbitrary values | `text-[13px]`, `border-[0.5px]`, `text-foreground/60` all forbidden. Use the token (`text-row`, `border-hairline`, `text-content-tertiary`). |
| WCAG AA floor | Normal text ≥ `content-tertiary` (.60). Muted (.50) and faint (.44) reserved for ≥18px text, icons, and disabled states. |
| `border-field` is an intentional exemption | At .16 it sits below the 3:1 threshold by design (WCAG 1.4.11): a form field is identified by its fill + label, not its border alone. |
| Compose off primitives | Use shadcn before custom. Three similar lines beat a premature abstraction. |
| Phosphor via `<Icon>` wrapper | No inline SVG. Defaults to `weight="regular"`. Lucide as fallback. |
| React 19 patterns | No `forwardRef`. Use the `use()` / Context patterns. |
| Enforced | ESLint errors on arbitrary type/border. `npm run audit:tokens` is a CI ratchet that counts 7 bypass axes. Brand, auth, data-viz allowlisted. |
Layout architecture
The three-area shell + single-page detail view + persistent agent rail. Locked structural decisions for the noticed app.
The anatomy that used to live in design-layout.md. URL patterns + keyboard nav + terminology lock.
| Decision | Detail |
|---|---|
| Three-area shell | Sidebar (250px, toggleable, ⌘\) left, canvas middle, agent rail (~30vw, toggleable, ⌘J) right. |
| Single-page detail view | No tabs. Header → brief → timeline (gbrain-style, append-only). |
| Brief is agent synthesis | 4–6 lines max, rewritten on new evidence. Also serves as the fast-read context layer. |
| Agent rail is persistent | ~30vw. Context chip auto-attaches the current page so the agent always has scope. |
| Keyboard nav | ↑↓ row, Enter open, Space toggle. |
| URL patterns | `/relationships`, `/relationships/{id}`, `/chat?t={thread_id}`. |
| Terminology lock | “Relationships” (not “contacts”). A record refers to itself by name. |
What we explicitly skipped
Documented to prevent re-litigation. Visual and layout decisions considered and dropped on the way to the current system.
| Skipped | Reason |
|---|---|
| Purple accent (Linear, Anthropic-ish) | AI-default color. Dilutes brand. |
| Green accent (Granola, fintech) | Ties us to Granola or old-fintech. |
| Orange / warm amber (Claude) | Too close to Anthropic. |
| Linear-style dark-first | Fights warmth + light-default. |
| Multiple display faces | One face (Switzer) across the hierarchy. |
| `success` / `warning` color tokens | Semantic monochrome + iconography instead. |
| Separate hue tokens for muted variants | Alpha layering (`oklch(0 0 0 / N)`) matches Lightfield. |
| Phosphor Thin (1px) | Too faint at our icon sizes vs Lightfield — switched to Regular. |
| shadcn default 10px radius | Lightfield uses 6px; we matched. |
| Italic emphasis in marketing headlines | Roman only. Avoids the editorial/serif-italic trope and keeps headlines flat. |
| Loud badge variant | Visually indistinguishable from outlined. State-change moments use the count chip's brand-wash + brand-text recipe instead. |
| Right-rail property panel | Data lives in the brief, not a sidebar. |
| Details / Updates / Interactions / Memories tabs | Single page reads as a story, not a folder. |
| Reminders on the page | Live in the agent rail, not the record. |
| Side panel on row click | Open full detail; keep the table the surface. |
| View tabs | Filter chips on the sub-bar instead. |
| Skills dropdown | Skills are agent capabilities, not menu items. |
| Persistent trigger strip | Context chip + cmd-K instead. |
| Sparkle icon / FAB for agent | Agent rail is always-on; no floating CTA needed. |