# Convert Experiences — Design System

> The shared visual language for every Convert landing page and marketing surface.
> Vanilla HTML/CSS, no build step, no framework. Inspired by shadcn/ui conventions
> (tokens → semantic layers → component variants).

**Source of truth:** [`tokens.css`](./tokens.css) (synced copy of `/_shared/tokens.css`).
Every color, radius, shadow, and type value below maps to a CSS custom property in that file.
If a value isn't a token, it's tech debt — add it to `tokens.css` rather than hardcoding.

_Last reconciled against `tokens.css` + `_shared/components/*.css`: 2026-06._

---

## How to consume this system

Link the stylesheets in your `<head>`, in this order:

```html
<link rel="stylesheet" href="/_shared/tokens.css">              <!-- tokens + base styles. ALWAYS first. -->
<link rel="stylesheet" href="/_shared/components/buttons.css">  <!-- .btn-* -->
<link rel="stylesheet" href="/_shared/components/cards.css">    <!-- .bento-card, .stat-card, .logo-card -->
<link rel="stylesheet" href="/_shared/components/sections.css"> <!-- section scaffolding -->
```

`tokens.css` MUST load before any inline `<style>` so the custom properties resolve.
Navbar and footer are copy-paste HTML blocks — see `/_shared/components/navbar.html` and `footer.html`.

---

## 1. Foundations

### 1.1 Typography

**Font family:** Geist Sans.

```css
--font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
```

Google Fonts import (weights 300–700):

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&display=swap" rel="stylesheet">
```

#### Type Scale

| Token | Size | Weight | Color | Usage |
|-------|------|--------|-------|-------|
| `--text-hero` | `clamp(24px, 3.8vw, 52px)` | 600 | `--color-text-primary` | H1 hero |
| `--text-section` | `clamp(26px, 3vw, 36px)` | 700 | `--color-text-primary` | Section H2 |
| `--text-card-title` | `20px` | 600 | `--color-text-primary` | Bento card H3 |
| `--text-body-lg` | `18px` | 400 | `--color-text-secondary` | Hero description |
| `--text-body` | `15px` | 400 | `--color-text-secondary` | Card body (line-height 1.7) |
| `--text-ui` | `14px` | 500 | `--color-text-primary` | Nav, buttons, links |
| `--text-small` | `13px` | 400 | `--color-text-secondary` | Trust signals |
| `--text-label` | `11px` | 600 | `--color-text-muted` | Uppercase labels (letter-spacing 0.1em) |
| `--text-caption` | `11.5px` | 400 | `#475569` | Italic captions |

**Letter spacing:** `-1px` on hero H1, `-0.5px` on section H2.

> **Not tokenized (component-local):** the hero stat number renders at ~70px with a violet→ink
> gradient and `letter-spacing: -2px`. It's a one-off component pattern (see §2.7), not a reusable
> type token — don't expect `--text-stat-*` to exist.

---

### 1.2 Color Tokens

The system is **two-layer**: raw primitives (the palette) → semantic aliases (intent).
Always reference semantic aliases in product code where one exists; reach for primitives only
for decorative fills.

#### Primitives

```css
/* Brand — Blue (primary action) */
--color-blue-600: #0066FF;   /* primary action */
--color-blue-700: #0052CC;   /* primary hover */
--color-blue-50:  #EEF4FF;   /* blue card bg tint */
--color-blue-100: #DBEAFE;   /* blue card border */
--color-blue-200: #E0E7FF;   /* stat / logo card gradient end */

/* Brand — Violet (links, accents) */
--color-violet-700: #6D28D9; /* links, icons, kickers — THE accent color */
--color-violet-600: #7C3AED; /* stat gradient top */
--color-violet-800: #5B21B6; /* stat gradient mid */
--color-violet-900: #4C1D95; /* stat gradient bottom / deep violet */
--color-violet-50:  #EDE9FE;
--color-violet-100: #F5F0FF; /* violet card bg tint */
--color-violet-200: #C4B5FD; /* violet card border */

/* Brand — Green (compliance / privacy) */
--color-green-50:  #EDFAF5;  /* green card bg tint */
--color-green-400: #6EE7B7;  /* green card border */
--color-green-500: #10B981;  /* emerald — avatar / accent */
--color-green-600: #059669;  /* code "compiling" state */

/* Accents — avatars, status chips */
--color-orange-50:  #FFF4ED; /* orange chip surface */
--color-orange-200: #FED7AA; /* orange chip border */
--color-orange-500: #F97316; /* orange — avatar accent */
--color-orange-700: #C2410C; /* orange chip text on light surface */

/* Brand — Logo wordmark */
--color-logo: #2A3341;       /* Convert wordmark on light bg; dark end of stat gradient */

/* Surfaces — cream */
--color-surface-cream:       #FAFAF7; /* default cream card bg */
--color-surface-cream-hover: #F5F4EE; /* cream card hover state */

/* Neutrals — Ink scale */
--color-ink-900: #2A3442;    /* primary text, headings */
--color-ink-800: #3C4659;    /* nav links */
--color-ink-700: #586478;    /* secondary text, descriptions */
--color-ink-600: #7E8AA0;    /* muted, uppercase labels */
--color-ink-500: #A6B1C2;    /* placeholder wordmarks */
--color-ink-400: #D0D7E1;    /* default card border */
--color-ink-300: #E7EBF1;    /* section borders, tab bg */
--color-ink-200: #F4F6F9;    /* subtle navy tint */
--color-ink-100: #FFFFFF;    /* surface */

/* Dark (mobile menu only) */
--color-dark-900: #0D1019;
--color-dark-800: #1A1A2E;
```

#### Semantic Aliases

```css
--color-bg:               var(--color-ink-100);
--color-bg-subtle:        var(--color-ink-300);
--color-surface:          var(--color-ink-100);
--color-border:           var(--color-ink-400);
--color-border-subtle:    rgba(207, 217, 230, 0.55);
--color-border-cream:     #E7E3D9;                  /* warm cream-toned border — cards, mocks */
--color-text-primary:     var(--color-ink-900);
--color-text-secondary:   var(--color-ink-700);
--color-text-muted:       var(--color-ink-600);
--color-text-placeholder: var(--color-ink-500);
--color-action-primary:   var(--color-blue-600);
--color-action-hover:     var(--color-blue-700);
--color-link:             var(--color-violet-700);  /* brand link/accent — aligned to .text-link / .kicker */
```

> **Accent color decision (2026-06):** the brand link/accent is **violet-700 (`#6D28D9`)**, not
> violet-600. `--color-link`, the base `<a>` style, `.text-link`, and `.kicker` all resolve to this
> single value. One accent, no ambiguity.

---

### 1.3 Spacing & Layout

```css
--max-width:     1200px;   /* centered content container */
--section-px:    24px;     /* horizontal section padding */
--section-py:    80px;     /* vertical section padding */
--card-gap:      20px;
--navbar-height: 60px;

/* Radius scale */
--radius-sm:      8px;      /* tags, chips, small inner blocks, navbar buttons */
--card-radius:    14px;     /* default cards */
--card-radius-lg: 20px;     /* large cards */
--radius-pill:    999px;    /* fully-rounded pills, tabs, chips */

/* Prose widths — for centered text columns */
--prose-narrow:  540px;    /* forms, focused conversion blocks */
--prose-default: 720px;    /* section subheads, mid-density copy */
--prose-wide:    880px;    /* long-form: author bios, FAQ, pitch */
```

> **No systematic spacing scale yet.** Paddings/gaps are ad-hoc 4px-based values. A `--space-1 … --space-N`
> (4px base) scale is planned but not yet in `tokens.css`. Until then, keep spacing on a 4px grid by hand.

---

### 1.4 Breakpoints

Token references (CSS still uses raw px values in `@media` queries):

```css
--bp-mobile:  640px;   /* shadcn `sm` — primary mobile breakpoint */
--bp-tablet:  960px;
--bp-desktop: 1200px;
```

> **Honest note:** the system is mostly single-breakpoint at **640px**, but a few components diverge.
> Bento grids collapse to one column at **760px** (see `cards.css`), not 640px. When in doubt, check the
> component's own `@media` rule rather than assuming a global breakpoint.

---

### 1.5 Shadows

```css
--shadow-card:        0 8px 24px rgba(0,0,0,0.08);                                      /* default card elevation */
--shadow-card-lift:   0 8px 24px rgba(0,0,0,0.08);                                      /* hover lift — currently identical to --shadow-card */
--shadow-stat:        0 16px 48px rgba(99,102,241,0.22), 0 2px 10px rgba(99,102,241,0.10);
--shadow-stat-hover:  0 24px 60px rgba(99,102,241,0.25), 0 2px 10px rgba(99,102,241,0.10);
--shadow-logo-card:   0 4px 16px rgba(99,102,241,0.08), 0 1px 4px rgba(99,102,241,0.04);
--shadow-btn-primary: 0 8px 24px rgba(0,102,255,0.12);
```

---

## 2. Components

> CSS below mirrors the real files in `/_shared/components/`. It uses tokens (`var(--…)`), never
> hardcoded hex — copy it as-is.

### 2.1 Buttons

Source: `components/buttons.css`. All share `font: 500 14px/1 var(--font-family)`, `cursor: pointer`.

#### `.btn-primary`
```css
.btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--color-blue-600);
  color: #fff;
  border: none;
  border-radius: 12px;
  padding: 14px 28px;
  font: 500 14px/1 var(--font-family);
  cursor: pointer;
  text-decoration: none;
  text-align: center;
  transition: background 0.2s, box-shadow 0.2s;
}
.btn-primary:hover {
  background: var(--color-blue-700);
  box-shadow: var(--shadow-btn-primary);
  opacity: 1;
}
/* Large variant — taller, bigger radius */
.btn-primary--lg { padding: 16px 32px; font-size: 15px; border-radius: 14px; }
```
**Use for:** primary CTAs — `Start 15-Day Free Trial`.

#### `.btn-secondary`
```css
.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: #fff;
  color: #1a1a2e;
  border: 1px solid rgba(0,0,0,0.1);
  border-radius: 12px;
  padding: 14px 28px;
  font: 500 14px/1 var(--font-family);
  cursor: pointer;
  text-decoration: none;
  text-align: center;
  transition: border-color 0.2s ease;
}
.btn-secondary:hover { border-color: rgba(0,0,0,0.25); opacity: 1; }
```
**Use for:** secondary actions — "Ask More Questions", "And Yours?".

#### `.btn-ghost` / `.btn-outline` (navbar)
```css
.btn-ghost {
  padding: 8px 16px; border-radius: 8px;
  font-size: 14px; font-weight: 500;
  color: var(--color-ink-800);
  background: transparent; border: none;
  cursor: pointer; font-family: inherit;
  transition: background 0.15s;
}
.btn-ghost:hover { background: rgba(0,0,0,0.04); }

.btn-outline {
  padding: 8px 16px; border-radius: 8px;
  font-size: 14px; font-weight: 500;
  color: #0a0a0a;
  background: rgba(255,255,255,0.1);
  border: 1px solid #d4d4d4;
  box-shadow: 0 1px 2px rgba(0,0,0,0.05);
  cursor: pointer; font-family: inherit;
  transition: background 0.15s, border-color 0.15s;
}
.btn-outline:hover { background: rgba(0,0,0,0.03); border-color: #bbb; }
```
**Use for:** `.btn-ghost` = "Log In"; `.btn-outline` = "Try For Free" in the navbar.

---

### 2.2 Text Links

Defined in `tokens.css`. Inline-flex with an arrow that slides right on hover.

```css
.text-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-ui);
  font-weight: 500;
  color: var(--color-violet-700);
  transition: opacity 0.2s ease;
}
.text-link .text-link-arrow { display: inline-block; transition: transform 0.2s ease; }
.text-link:hover { opacity: 0.85; }
.text-link:hover .text-link-arrow { transform: translateX(3px); }

/* On dark backgrounds */
.text-link--light { color: rgba(255,255,255,0.45); }
.text-link--light:hover { opacity: 0.7; }
```

**HTML pattern** — the arrow is ALWAYS a `<span>`, never a raw `>` or `→`:
```html
<a href="#" class="text-link">Link label <span class="text-link-arrow">&rarr;</span></a>
```

---

### 2.3 Cards

Source: `components/cards.css`. Three-color system — **2 blue + 1 violet + 1 green per row**.
Apply color via the variant class; never compose ad-hoc.

```css
.bento-card {
  background: #FFFFFF;
  border: 1px solid var(--color-ink-400);
  border-radius: var(--card-radius);
  overflow: hidden;
  transition: box-shadow 0.25s ease, transform 0.4s ease;
}
.bento-card:hover { box-shadow: var(--shadow-card-lift); transform: translateY(-2px); }

/* Color variants — radial gradient: white from top-left (-5% -5%) fading to the tint */
.bento-card--blue   { background: radial-gradient(ellipse at -5% -5%, #fff 25%, var(--color-blue-50)   100%); border-color: var(--color-blue-100); }
.bento-card--violet { background: radial-gradient(ellipse at -5% -5%, #fff 25%, var(--color-violet-100) 100%); border-color: var(--color-violet-200); }
.bento-card--green  { background: radial-gradient(ellipse at -5% -5%, #fff 25%, var(--color-green-50)  100%); border-color: var(--color-green-400); }

/* Stat card — hero stats deck */
.stat-card {
  background: radial-gradient(100.54% 100.54% at 50.42% -0.54%, #FFFFFF 50%, var(--color-blue-200) 100%);
  border: 1.5px solid rgba(165, 180, 252, 0.75);
  border-radius: 36px;
}
/* Logo strip card */
.logo-card {
  background: radial-gradient(100% 100% at 50% 0%, #FFFFFF 50%, var(--color-blue-200) 100%);
  border: 1.5px solid rgba(165, 180, 252, 0.5);
  border-radius: 24px;
  box-shadow: var(--shadow-logo-card);
}

/* Padding wrapper — REQUIRED. Never put padding directly on .bento-card--* */
.bento-content { padding: 28px 28px 32px; }
```

---

### 2.4 Bento Grid Layout

```css
.bento-grid {
  display: grid;
  grid-template-columns: repeat(20, 1fr);   /* 20-col for asymmetric splits */
  grid-template-rows: auto auto;
  gap: var(--card-gap);
}
.bento-grid--simple { grid-template-columns: repeat(2, 1fr); }  /* when 20-col is overkill */
.bento-grid--3      { grid-template-columns: repeat(3, 1fr); }

/* Collapse to a single column on mobile */
@media (max-width: 760px) {
  .bento-grid, .bento-grid--simple, .bento-grid--3 { grid-template-columns: 1fr; }
}
```

Column spans for the 20-col grid are set per page via `nth-child` (e.g. `1 / 8` + `8 / 21` for a
7/13 split). See `new-home/index.html` for the persona-tab variations.

**Card entry animation** (add `.visible` via IntersectionObserver, staggered):
```css
.bento-card { opacity: 0; transform: translateY(20px); }
.bento-card.visible { opacity: 1; transform: translateY(0); transition: opacity 0.4s ease, transform 0.4s ease; }
@media (prefers-reduced-motion: reduce) { .bento-card { opacity: 1; transform: none; } }
```

---

### 2.5 Navbar

```
position: fixed; top: 0; full width; z-index: 100
height: var(--navbar-height) (60px); inner max-width: 1200px; padding: 12px 24px
```

- **Scroll state:** background `rgba(255,255,255,0.72)` + `backdrop-filter: blur(16px) saturate(180%)`,
  border `var(--color-border-subtle)`.
- **Hide on scroll down:** `transform: translateY(-100%)` via `.hidden`.
- **Mobile (< 640px):** hamburger visible, nav links + actions hidden, full-screen dark overlay menu
  (`--color-dark-900`). Height drops to 56px.

Copy the whole block from `/_shared/components/navbar.html` (HTML + `<style>` + `<script>`).

---

### 2.6 Tabs (Persona Selector)

```css
.persona-tab-group {
  display: inline-flex;
  background: var(--color-ink-300);
  border-radius: var(--radius-pill);
  padding: 4px; gap: 4px;
}
.persona-tab {
  padding: 10px 20px; border-radius: var(--radius-pill);
  font-size: 14px; font-weight: 400; color: var(--color-ink-700);
  transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.persona-tab.active {
  background: #FFFFFF; color: var(--color-ink-900); font-weight: 500;
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
}
```
Tabs stick at `top: 0` with `backdrop-filter: blur(12px)` while scrolling.

---

### 2.7 Stat Number (gradient text)

The highlighted hero stat. Radial gradient — violet core (top-centre) fading to logo-ink at the edges.

```css
.hero-stat.is-active .hero-stat__number {
  font-weight: 700;
  background-image: radial-gradient(100% 100% at 50.06% 0.1%,
    var(--color-violet-700) 0%, var(--color-logo) 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  letter-spacing: -2px;
  line-height: 1;
}
```
Non-active stats use flat `var(--color-ink-600)`. Stripe-style 3-up: one stat highlighted at a time,
cycling in sync with the hero H1 chroma word-swap (single shared interval). Active stat gets a tapered
violet accent line top + bottom.

---

### 2.8 Chroma Headline (hero animated text)

```css
.chroma-text {
  background-image: linear-gradient(90deg,
    #2A3442 0%, #2A3442 33%, #0066FF 45%,
    #6D28D9 55%, #0066FF 67%, #647790 80%, transparent 100%);
  background-size: 300% 100%;
  background-position: 100% 0;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
@keyframes chroma-sweep {
  0%   { background-position: 100% 0; filter: blur(2px); opacity: 0; }
  20%  { opacity: 1; }
  100% { background-position: 0% 0; filter: blur(0); opacity: 1; }
}
```

---

### 2.9 Label / Badge

**Uppercase section label** (the `.kicker`, defined in `tokens.css`):
```css
.kicker {
  display: inline-block;
  font-size: var(--text-label);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-violet-700);
  margin-bottom: 16px;
}
```

**Growth toast badges** — pill shape, colored by category:
Activation → blue tint · Retention → violet tint · Conversion → orange/amber tint · Engagement → green tint.

---

### 2.10 Ticker / Scrolling Strip

```css
.ticker-row {
  display: flex; gap: 20px;
  width: max-content;
  animation: ticker-scroll 38s linear infinite;
}
@keyframes ticker-scroll {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}
/* Fade edges */
mask-image: linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%);
```
Items are duplicated for a seamless infinite loop.

---

### 2.11 Integration Card

Square card containing a logo `<img>`, using the base `.bento-card` style with no color variant.
The grid scrolls horizontally with a `mask-image` fade on the sides. Logos live in
`/_shared/integration-logos/`.

---

## 3. Motion & Animation

| Element | Animation | Duration | Easing |
|---------|-----------|----------|--------|
| Hero BG reveal | translateY(24px) + opacity 0→1 | 1.2s | `cubic-bezier(0.22, 1, 0.36, 1)` |
| Chroma text sweep | bg-position + blur | 0.9s | `ease-in-out` |
| Chroma text fade out | opacity + translateY + blur | 0.4s | `ease-out` |
| Bento card entry | opacity + translateY(20px) | 0.4s | `ease` |
| Stat deck hover spread | translateX ± 250px | 0.5s | `cubic-bezier(0.34,1.2,0.64,1)` |
| Pricing receipt slide | translateY(110%) → translateY(10%) | 0.8s | `cubic-bezier(0.34,1.2,0.64,1)` |
| Logo card hover lift | translateY(-2px) | 0.4s | `cubic-bezier(0.34,1.2,0.64,1)` |
| Text link arrow | translateX(3px) | 0.2s | `ease` |
| Navbar scroll | background + blur | 0.4s | `ease` |
| Navbar hide | translateY(-100%) | 0.3s | `cubic-bezier(0.4,0,0.2,1)` |
| Mobile menu slide | translateY(-100%) → 0 | 0.38s | `cubic-bezier(0.4,0,0.2,1)` |

**Reduced motion:** `tokens.css` sets all animation/transition durations to `0.01ms` under
`@media (prefers-reduced-motion: reduce)`. Honor it — never animate around it.

---

## 4. Base Styles (from `tokens.css`)

Applied globally the moment `tokens.css` loads:

```css
body {
  font-family: var(--font-family);
  background: var(--color-bg);
  color: var(--color-text-primary);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  line-height: 1.6;
  margin: 0;
}
a { color: var(--color-link); text-decoration: none; }
a:hover { opacity: 0.85; }
* { box-sizing: border-box; }
*:focus-visible { outline: 2px solid var(--color-action-primary); outline-offset: 2px; border-radius: 4px; }
img, svg { max-width: 100%; height: auto; display: block; }

.container { max-width: var(--max-width); margin: 0 auto; padding: 0 var(--section-px); }
.section { padding: var(--section-py) 0; }
.section--tight { padding: 48px 0; }
```

---

## 5. Usage Rules

1. **`tokens.css` first.** Link it before any inline `<style>`. Every color/radius/shadow uses a token —
   hardcoded hex is tech debt.
2. **One accent color:** violet-700 (`#6D28D9`) for links, kickers, and the `--color-link` alias.
3. **Never add padding directly to `.bento-card--*`** — use the `.bento-content` wrapper (or `.cwv-content`, etc.).
4. **Bento card colors follow the 2-blue / 1-violet / 1-green pattern** per row. Rotate through them.
5. **Radial gradient on bento cards always originates from `-5% -5%`** (top-left) so the white reads cleanly behind text.
6. **The text-link arrow is always `<span class="text-link-arrow">&rarr;</span>`** — never a raw `>` or `→`.
7. **Primary CTA copy is `Start 15-Day Free Trial`** across all sections.
8. **Max-width containers are `1200px`**, centered with `margin: 0 auto` (use `.container`).
9. **Check a component's own `@media` rule** before assuming the 640px global breakpoint — some (bento grids) collapse at 760px.

---

## Assets

| Asset | Path |
|-------|------|
| Wordmark (dark on light) | `/_shared/assets/logo-blue.svg` |
| Wordmark (white on dark) | `/_shared/assets/logo-white.svg` |
| Square icon (dark / white) | `/_shared/assets/icon-blue.svg` · `icon-white.svg` |
| Footer block | `/_shared/components/footer.html` |
| Integration logos | `/_shared/integration-logos/` |
| Third-party / customer logos | `/_shared/third party logos/` |
| LP starter template | `/_shared/lp-template.html` |
</content>
</invoke>
