Files
Z User e45b68bb8b feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)
Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
2026-10-27 22:50:50 +00:00

1019 lines
39 KiB
HTML
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Inspired by Stripe — reference components</title>
<meta
name="description"
content="Reference fixture for design-systems/stripe. Every visible
value comes from tokens.css; the page itself follows Stripe's
rules — sohne-var with ss01 globally, weight 300 at display
sizes, deep navy (#061b31) headings instead of black, blue-
tinted multi-layer shadows on elevated surfaces, conservative
4-8px radius, and Stripe Purple capped at ≤2 visible uses
per screen."
/>
<style>
/* :root paste — sourced verbatim from
design-systems/stripe/tokens.css. Keep this block in sync
when tokens.css changes. The agent prompt instructs the
Designer panelist to paste this block as the FIRST thing in
their <style>, then reference everything below via var(...). */
:root {
--bg: #ffffff;
--surface: #ffffff;
--surface-warm: #f6f9fc;
--fg: #061b31;
--fg-2: #273951;
--muted: #64748d;
--meta: var(--muted);
--border: #e5edf5;
--border-soft: var(--border);
--accent: #533afd;
--accent-on: #ffffff;
--accent-hover: #4434d4;
--accent-active: #2e2b8c;
--success: #15be53;
--warn: #9b6829;
--danger: #ea2261;
--font-display:
"sohne-var", "Söhne", "Sohne",
"SF Pro Display", -apple-system, BlinkMacSystemFont,
system-ui, "Helvetica Neue", Arial, sans-serif;
--font-body:
"sohne-var", "Söhne", "Sohne",
"SF Pro Display", -apple-system, BlinkMacSystemFont,
system-ui, "Helvetica Neue", Arial, sans-serif;
--font-mono:
"SourceCodePro", "Source Code Pro",
ui-monospace, "SF Mono", "JetBrains Mono",
Menlo, Monaco, Consolas, monospace;
--text-xs: 12px;
--text-sm: 14px;
--text-base: 16px;
--text-lg: 18px;
--text-xl: 22px;
--text-2xl: 32px;
--text-3xl: 48px;
--text-4xl: 56px;
--leading-body: 1.40;
--leading-tight: 1.10;
--tracking-display: -0.02em;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-12: 48px;
--section-y-desktop: 96px;
--section-y-tablet: 64px;
--section-y-phone: 40px;
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-pill: 9999px;
--elev-flat: none;
--elev-ring: 0 0 0 1px var(--border);
--elev-raised:
rgba(50, 50, 93, 0.25) 0px 30px 45px -30px,
rgba(0, 0, 0, 0.10) 0px 18px 36px -18px;
--focus-ring:
0 0 0 2px var(--accent),
0 0 0 5px color-mix(in oklab, var(--accent), transparent 75%);
--motion-fast: 150ms;
--motion-base: 200ms;
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
--container-max: 1080px;
--container-gutter-desktop: 32px;
--container-gutter-tablet: 24px;
--container-gutter-phone: 16px;
}
/* ─── Reset (minimal) ───────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--fg);
font-family: var(--font-body);
font-size: var(--text-base);
font-weight: 300; /* Stripe's signature: 300 is body weight */
line-height: var(--leading-body);
/* `"ss01"` is non-negotiable on every sohne-var element
(DESIGN.md §3 Principles). Inheriting from <body> applies
it to every descendant by default; specific components
that need `tnum` for financial tables override locally. */
font-feature-settings: "ss01";
-webkit-font-smoothing: antialiased;
}
strong { font-weight: 400; } /* never 600/700 in sohne-var per DESIGN.md §7 */
/* ─── Layout primitives ─────────────────────────────────────── */
.container {
max-width: var(--container-max);
margin-inline: auto;
padding-inline: var(--container-gutter-desktop);
}
section {
padding-block: var(--section-y-desktop);
}
section + section {
border-top: 1px solid var(--border);
}
@media (max-width: 1023px) {
.container { padding-inline: var(--container-gutter-tablet); }
section { padding-block: var(--section-y-tablet); }
}
@media (max-width: 639px) {
.container { padding-inline: var(--container-gutter-phone); }
section { padding-block: var(--section-y-phone); }
}
/* ─── Typography ────────────────────────────────────────────── */
h1, h2, h3 {
font-family: var(--font-display);
font-weight: 300; /* Stripe's anti-convention signature */
line-height: var(--leading-tight);
letter-spacing: var(--tracking-display);
color: var(--fg);
margin: 0;
}
h1 {
font-size: var(--text-4xl);
line-height: 1.03; /* DESIGN.md §3: display hero ratio */
letter-spacing: -0.025em; /* exact -1.4px at 56px */
}
h2 {
font-size: var(--text-2xl);
}
h3 {
font-size: var(--text-xl);
letter-spacing: -0.01em; /* relaxed at 22px per DESIGN.md §3 */
}
p { margin: 0; }
.lede {
font-size: var(--text-lg);
line-height: var(--leading-body);
color: var(--muted);
max-width: 56ch;
}
.body-muted { color: var(--muted); }
.body-meta { color: var(--meta); font-size: var(--text-sm); }
.body-sm { font-size: var(--text-sm); }
/* `.eyebrow` is the only place uppercase + tracking is used —
small label, deep-navy on lavender wash. tracking is well
above the craft 0.06em floor. */
.eyebrow {
display: inline-block;
font-size: var(--text-xs);
font-weight: 400;
line-height: 1;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.stack-3 > * + * { margin-block-start: var(--space-3); }
.stack-4 > * + * { margin-block-start: var(--space-4); }
.stack-6 > * + * { margin-block-start: var(--space-6); }
.stack-8 > * + * { margin-block-start: var(--space-8); }
/* ─── Buttons ───────────────────────────────────────────────
* Stripe buttons are conservative — 4px radius, 8/16 padding,
* weight-400 sohne-var with ss01. Hover shifts background
* colour; never transforms. Primary uses --accent / --accent-on;
* ghost uses the brand purple as text colour against a
* transparent shell with a purple-tinted border. */
.btn {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: 8px 16px;
border-radius: var(--radius-sm);
font-family: var(--font-display);
font-feature-settings: "ss01";
font-weight: 400;
font-size: var(--text-base);
line-height: 1;
cursor: pointer;
border: 1px solid transparent;
background: transparent;
color: var(--fg);
text-decoration: none;
transition:
background-color var(--motion-fast) var(--ease-standard),
border-color var(--motion-fast) var(--ease-standard),
color var(--motion-fast) var(--ease-standard),
box-shadow var(--motion-fast) var(--ease-standard);
}
.btn:focus-visible {
outline: none;
box-shadow: var(--focus-ring);
}
.btn-primary {
background: var(--accent);
color: var(--accent-on);
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:active { background: var(--accent-active); }
/* Ghost / outlined — DESIGN.md §4 spec verbatim.
The hover wash is the only place we tint --accent with
transparency inline; it's a once-per-page rule rather than
a cross-component pattern, so it stays inline rather than
being promoted to a token. */
.btn-ghost {
background: transparent;
color: var(--accent);
border-color: color-mix(in oklab, var(--accent), white 65%);
}
.btn-ghost:hover {
background: color-mix(in oklab, var(--accent), transparent 95%);
border-color: color-mix(in oklab, var(--accent), white 55%);
}
/* Compact button for nav and toolbar — 14px / 4px radius */
.btn-sm {
padding: 6px 12px;
font-size: var(--text-sm);
}
/* ─── Inputs ────────────────────────────────────────────── */
.field {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.field label {
font-size: var(--text-sm);
font-weight: 400;
color: var(--fg-2); /* dark-slate label per DESIGN.md §4 */
}
.field input,
.field select,
.field textarea {
padding: 10px 12px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--surface);
color: var(--fg);
font-family: inherit;
font-feature-settings: "ss01";
font-size: var(--text-base);
font-weight: 300;
line-height: 1.4;
outline: none;
transition:
border-color var(--motion-fast) var(--ease-standard),
box-shadow var(--motion-fast) var(--ease-standard);
}
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
border-color: var(--accent);
box-shadow: var(--focus-ring);
}
.field input::placeholder { color: var(--muted); }
.field-help {
font-size: var(--text-xs);
color: var(--muted);
}
/* ─── Cards ─────────────────────────────────────────────────
* Two card flavours:
* .card hairline border, no shadow — for grids and
* form panels where the border IS the edge.
* .card-elevated no border, blue-tinted multi-layer shadow —
* for featured / hero cards where Stripe's
* trademark elevation does the work.
* Both share radius and padding. Hover on .card-elevated
* intensifies the shadow; .card stays static (border doesn't
* pulse). */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.card-elevated {
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--space-8);
display: flex;
flex-direction: column;
gap: var(--space-4);
box-shadow: var(--elev-raised);
transition: box-shadow var(--motion-base) var(--ease-standard);
}
.card-elevated:hover {
/* Intensify the near layer; far stays the same so the lift
reads as "drawing closer", not "growing larger". */
box-shadow:
rgba(50, 50, 93, 0.25) 0px 30px 45px -30px,
rgba(0, 0, 0, 0.14) 0px 24px 48px -16px;
}
.card-title {
font-family: var(--font-display);
font-feature-settings: "ss01";
font-weight: 300;
font-size: var(--text-xl);
line-height: 1.12;
letter-spacing: -0.01em;
color: var(--fg);
}
/* ─── Badges ────────────────────────────────────────────────
* Success badge per DESIGN.md §4. Pill radius is wrong here —
* Stripe uses 4px on badges, never 9999px. */
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 1px 6px;
border-radius: var(--radius-sm);
font-size: var(--text-xs);
font-weight: 300;
line-height: 1.6;
font-feature-settings: "ss01";
}
.badge-success {
color: var(--success);
background: color-mix(in oklab, var(--success), transparent 80%);
border: 1px solid
color-mix(in oklab, var(--success), transparent 60%);
}
.badge-muted {
color: var(--muted);
background: var(--surface-warm);
border: 1px solid var(--border);
}
.badge-accent {
color: var(--accent);
background: color-mix(in oklab, var(--accent), white 88%);
border: 1px solid
color-mix(in oklab, var(--accent), white 70%);
}
.badge-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
/* ─── Links ─────────────────────────────────────────────────
* Default link is Stripe Purple. Underline appears only on
* hover, at a generous offset so the rule reads as a
* deliberate gesture. */
a {
color: var(--accent);
text-decoration: none;
font-feature-settings: "ss01";
}
a:hover {
text-decoration: underline;
text-underline-offset: 3px;
text-decoration-thickness: 1px;
}
/* ─── Kbd ───────────────────────────────────────────────────
* SourceCodePro at 12px, hairline border, navy text. Stripe's
* kbd is small and dense — no padding inflation. */
kbd {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 500;
padding: 2px 6px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--surface-warm);
color: var(--fg-2);
}
/* ─── Distinctive brand element: gradient halo ──────────────
* Stripe's signature hero decoration is a soft pastel wash
* behind the headline — built here from the brand purple
* mixed at varying transparencies (no raw hex outside :root).
* The halo sits in a layered radial-gradient and lets the
* navy headline read as "floating in twilight". */
.halo {
position: absolute;
inset: -10% -20% -20% -10%;
pointer-events: none;
background:
radial-gradient(
60% 50% at 15% 30%,
color-mix(in oklab, var(--accent), transparent 70%) 0%,
color-mix(in oklab, var(--accent), transparent 100%) 65%
),
radial-gradient(
45% 40% at 85% 20%,
color-mix(in oklab, var(--danger), transparent 75%) 0%,
color-mix(in oklab, var(--danger), transparent 100%) 70%
),
radial-gradient(
55% 45% at 70% 80%,
color-mix(in oklab, var(--accent), transparent 80%) 0%,
color-mix(in oklab, var(--accent), transparent 100%) 70%
);
filter: blur(40px);
z-index: 0;
}
/* ─── Code block ────────────────────────────────────────────
* DESIGN.md §3: SourceCodePro at 12px / weight 500 / 2.00
* line-height. The 2.00 leading is intentional — Stripe's
* payment-integration snippets need to read like prose, not
* like a terminal dump. */
.code {
background: var(--surface-warm);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-5);
font-family: var(--font-mono);
font-size: var(--text-xs);
font-weight: 500;
line-height: 2.0;
color: var(--fg);
white-space: pre;
overflow-x: auto;
}
.code .k { color: var(--accent); } /* keyword */
.code .s { color: var(--success); } /* string */
.code .c { color: var(--muted); } /* comment */
.code .n { color: var(--fg-2); } /* identifier */
/* ─── Metric (tabular financial rendering) ──────────────────
* DESIGN.md §3 "Caption Tabular" — `tnum` for any number in
* a data display so columns of figures align. The values use
* deep navy; the labels use slate. */
.metric {
display: flex;
flex-direction: column;
gap: 4px;
}
.metric-value {
font-family: var(--font-display);
font-feature-settings: "tnum", "ss01";
font-weight: 300;
font-size: var(--text-2xl);
line-height: 1.1;
letter-spacing: -0.01em;
color: var(--fg);
}
.metric-label {
font-size: var(--text-sm);
color: var(--muted);
}
/* ─── Section-specific layout ───────────────────────────── */
.hero {
position: relative;
overflow: hidden;
}
.hero-inner {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 1.3fr 1fr;
gap: var(--space-12);
align-items: end;
}
@media (max-width: 1023px) {
.hero-inner { grid-template-columns: 1fr; gap: var(--space-8); }
}
.hero h1 {
max-width: 14ch;
margin: 0;
}
.hero-actions {
display: flex;
gap: var(--space-3);
margin-block-start: var(--space-8);
}
.features-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-5);
margin-block-start: var(--space-8);
}
@media (max-width: 1023px) {
.features-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 639px) {
.features-grid { grid-template-columns: 1fr; }
}
/* Pricing-style featured card row — exercises .card-elevated
alongside .card so the elevation contrast is visible. */
.pricing-grid {
display: grid;
grid-template-columns: 1fr 1.1fr 1fr;
gap: var(--space-5);
align-items: stretch;
margin-block-start: var(--space-8);
}
@media (max-width: 1023px) {
.pricing-grid { grid-template-columns: 1fr; }
}
.pricing-price {
font-family: var(--font-display);
font-feature-settings: "tnum", "ss01";
font-weight: 300;
font-size: var(--text-3xl);
line-height: 1.05;
letter-spacing: -0.02em;
color: var(--fg);
}
.pricing-price small {
font-size: var(--text-sm);
color: var(--muted);
margin-inline-start: var(--space-2);
font-feature-settings: "ss01";
}
.pricing-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.pricing-list li {
position: relative;
padding-inline-start: var(--space-5);
font-size: var(--text-sm);
color: var(--fg-2);
line-height: 1.5;
}
.pricing-list li::before {
content: "";
position: absolute;
left: 0;
top: 0.45em;
width: 12px;
height: 7px;
border-left: 1.5px solid var(--accent);
border-bottom: 1.5px solid var(--accent);
transform: rotate(-45deg);
transform-origin: 0 100%;
}
.metric-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-8);
}
@media (max-width: 767px) {
.metric-row { grid-template-columns: repeat(2, 1fr); gap: var(--space-6); }
}
.form-row {
display: grid;
grid-template-columns: 1.2fr 1fr;
gap: var(--space-12);
align-items: start;
}
@media (max-width: 1023px) {
.form-row { grid-template-columns: 1fr; }
}
.form {
display: flex;
flex-direction: column;
gap: var(--space-4);
max-width: 460px;
padding: var(--space-6);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--elev-raised);
}
.form-grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-3);
}
.form-actions {
display: flex;
gap: var(--space-3);
margin-block-start: var(--space-2);
align-items: center;
}
.icon { width: 14px; height: 14px; flex-shrink: 0; }
.row-between {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
}
</style>
</head>
<body>
<main class="container">
<!-- ════════════════════════════════════════════════════════════
HERO — exercises: gradient .halo (Stripe's signature
pastel-wash behind the headline, built entirely from
color-mix() over --accent and --danger), h1 at 56px /
weight 300 / -0.025em (the brand's "whisper authority"),
.lede in slate, .btn-primary + .btn-ghost from DESIGN.md
§4 verbatim. Asymmetric grid on desktop; single column on
tablet.
═══════════════════════════════════════════════════════════════ -->
<section class="hero" data-od-id="hero">
<div class="halo" aria-hidden="true"></div>
<div class="hero-inner">
<div class="stack-6">
<span class="eyebrow">Payments · Infrastructure</span>
<h1>Financial infrastructure designed for the internet.</h1>
<p class="lede">
The reference fixture pastes Stripe's token block into a
single <code style="font-family:var(--font-mono);font-size:0.92em;color:var(--fg-2)">&lt;style&gt;</code>
and renders a recognisable Stripe page without authoring
any additional values — every colour, type size, and
shadow below resolves through <code style="font-family:var(--font-mono);font-size:0.92em;color:var(--fg-2)">var(--*)</code>.
</p>
<div class="hero-actions">
<a href="./tokens.css" class="btn btn-primary">
Start now
<svg
class="icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
</a>
<a href="./DESIGN.md" class="btn btn-ghost">Contact sales</a>
</div>
</div>
<aside class="card-elevated" aria-label="Payment summary">
<div class="row-between">
<span class="eyebrow">Payment intent</span>
<span class="badge badge-success">
<span class="badge-dot" aria-hidden="true"></span>
Succeeded
</span>
</div>
<div class="metric">
<div
class="metric-value"
style="font-size: var(--text-3xl); letter-spacing: -0.02em"
>
$4,280.<span style="color: var(--muted)">00</span>
</div>
<div class="metric-label">
Charged via card_1NqK… · 2026-05-14
</div>
</div>
<div
style="
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-3);
padding-block-start: var(--space-3);
border-top: 1px solid var(--border);
"
>
<div class="metric">
<div class="metric-label">Net</div>
<div
class="metric-value"
style="font-size: var(--text-base); color: var(--fg-2)"
>
$4,154.88
</div>
</div>
<div class="metric">
<div class="metric-label">Fee</div>
<div
class="metric-value"
style="font-size: var(--text-base); color: var(--fg-2)"
>
$125.12
</div>
</div>
</div>
<p class="body-sm body-muted">
Press <kbd>⌘</kbd> <kbd>K</kbd> to inspect this token block
in the design-system viewer.
</p>
</aside>
</div>
</section>
<!-- ════════════════════════════════════════════════════════════
METRICS — exercises: .metric pattern with `tnum`, deep-
navy values, slate labels. Numbers describe the fixture
itself, not invented claims.
═══════════════════════════════════════════════════════════════ -->
<section data-od-id="metrics">
<div class="stack-3">
<span class="eyebrow">Schema in numbers</span>
<h2 style="max-width: 24ch">
Fifty-six tokens carry every visible value below.
</h2>
</div>
<div class="metric-row" style="margin-block-start: var(--space-8)">
<div class="metric">
<div class="metric-value">56</div>
<div class="metric-label">tokens in :root</div>
</div>
<div class="metric">
<div class="metric-value">0</div>
<div class="metric-label">brand-C extensions</div>
</div>
<div class="metric">
<div class="metric-value">3</div>
<div class="metric-label">elevation levels (flat · ring · raised)</div>
</div>
<div class="metric">
<div class="metric-value">300</div>
<div class="metric-label">default sohne-var weight</div>
</div>
</div>
</section>
<!-- ════════════════════════════════════════════════════════════
FEATURES — exercises: h2, .card (hairline border), h3 with
tighter tracking, body text, .features-grid responsive
ladder. Each card describes a real property of this
fixture (no "feature one/two/three" filler).
═══════════════════════════════════════════════════════════════ -->
<section data-od-id="features">
<div class="stack-3">
<span class="eyebrow">What this fixture exercises</span>
<h2 style="max-width: 30ch">
Every component renders through <code style="font-family:var(--font-mono);font-size:0.92em;color:var(--fg-2)">var(--*)</code> —
no raw hex, no off-token type.
</h2>
</div>
<div class="features-grid">
<article class="card">
<h3 class="card-title">Shadow as identity</h3>
<p class="body-muted body-sm">
The trademark blue-tinted multi-layer shadow lives at
<code style="font-family:var(--font-mono);font-size:0.92em;color:var(--fg-2)">--elev-raised</code>.
Rebinding it to a neutral drop shadow erases Stripe's
atmosphere even when every colour is right.
</p>
<a href="./tokens.css" class="body-sm">Inspect tokens →</a>
</article>
<article class="card">
<h3 class="card-title">Light weight as voice</h3>
<p class="body-muted body-sm">
Display sizes run at weight 300 with negative tracking.
Stripe's headlines feel like they don't need to shout;
weight 600 would erase the brand instantly.
</p>
<a href="./DESIGN.md" class="body-sm">Read the spec →</a>
</article>
<article class="card">
<h3 class="card-title">Accent discipline</h3>
<p class="body-muted body-sm">
<code style="font-family:var(--font-mono);font-size:0.92em;color:var(--fg-2)">--accent</code>
appears at most twice per screen — primary CTA and link
colour. Decorative ruby / magenta gradients live in the
hero halo, not on interactive elements.
</p>
<a href="./tokens.css" class="body-sm">Inspect accent →</a>
</article>
</div>
</section>
<!-- ════════════════════════════════════════════════════════════
PRICING — exercises: .card-elevated with the trademark
blue-tinted shadow, alongside two .card siblings so the
elevation hierarchy is immediately visible. Tabular numerals
on the price; check-mark feature list keyed to --accent.
═══════════════════════════════════════════════════════════════ -->
<section data-od-id="pricing">
<div class="stack-3">
<span class="eyebrow">Pricing pattern</span>
<h2 style="max-width: 26ch">
Featured tier lifts via shadow alone — no colour shift, no
border change.
</h2>
</div>
<div class="pricing-grid">
<article class="card">
<div class="stack-3">
<h3 class="card-title">Starter</h3>
<p class="body-muted body-sm">
For new accounts integrating their first payment flow.
</p>
</div>
<div class="pricing-price">
$0<small>/month</small>
</div>
<ul class="pricing-list">
<li>Standard processing rates</li>
<li>Dashboard access</li>
<li>Email support</li>
</ul>
<a href="./tokens.css" class="btn btn-ghost btn-sm">Start free</a>
</article>
<article class="card-elevated" aria-label="Featured tier">
<div class="row-between">
<span class="eyebrow">Most popular</span>
<span class="badge badge-accent">Featured</span>
</div>
<h3 class="card-title">Growth</h3>
<div class="pricing-price">
$79<small>/month</small>
</div>
<ul class="pricing-list">
<li>Volume processing discounts</li>
<li>Subscriptions &amp; invoicing</li>
<li>Fraud monitoring</li>
<li>Priority support</li>
</ul>
<a href="./tokens.css" class="btn btn-primary btn-sm">Choose Growth</a>
</article>
<article class="card">
<div class="stack-3">
<h3 class="card-title">Scale</h3>
<p class="body-muted body-sm">
Custom pricing for high-volume merchants and platforms.
</p>
</div>
<div class="pricing-price">
Custom
</div>
<ul class="pricing-list">
<li>Negotiated processing rates</li>
<li>Dedicated solutions engineer</li>
<li>Custom data residency</li>
</ul>
<a href="./DESIGN.md" class="btn btn-ghost btn-sm">Contact sales</a>
</article>
</div>
</section>
<!-- ════════════════════════════════════════════════════════════
CODE PREVIEW — exercises: SourceCodePro at 12px / weight
500 / line-height 2.00 (DESIGN.md §3). Keyword tokens use
--accent; strings use --success; comments use --muted.
═══════════════════════════════════════════════════════════════ -->
<section data-od-id="code">
<div class="form-row">
<div class="stack-4">
<span class="eyebrow">Integration preview</span>
<h2 style="max-width: 18ch">
Two lines of code, one charge.
</h2>
<p class="body-muted lede">
The monospace face is <code style="font-family:var(--font-mono);font-size:0.95em">SourceCodePro</code>
at 12px / weight 500 / line-height 2.00 — generous leading
so payment snippets read like prose, not like terminal
output.
</p>
<div>
<a href="./tokens.css" class="btn btn-ghost btn-sm">Inspect mono token</a>
</div>
</div>
<pre class="code"><span class="c">// Create a PaymentIntent on your server.</span>
<span class="k">const</span> <span class="n">intent</span> = <span class="k">await</span> <span class="n">stripe</span>.<span class="n">paymentIntents</span>.<span class="n">create</span>({
<span class="n">amount</span>: <span class="k">4280</span>,
<span class="n">currency</span>: <span class="s">"usd"</span>,
<span class="n">automatic_payment_methods</span>: { <span class="n">enabled</span>: <span class="k">true</span> },
});
<span class="c">// Return the client secret to the browser.</span>
<span class="k">res</span>.<span class="n">json</span>({ <span class="n">clientSecret</span>: <span class="n">intent</span>.<span class="n">client_secret</span> });</pre>
</div>
</section>
<!-- ════════════════════════════════════════════════════════════
FORM — exercises: .field, input :focus-visible (purple halo
via --focus-ring), .btn-primary (reused), .btn-ghost,
.field-help. The form panel itself uses .card-elevated so
the trademark shadow frames the inputs.
═══════════════════════════════════════════════════════════════ -->
<section data-od-id="form">
<div class="form-row">
<div class="stack-4">
<span class="eyebrow">Form components</span>
<h2 style="max-width: 22ch">
Inputs inherit the same tokens.
</h2>
<p class="body-muted lede">
Focus rings, borders, placeholder colour — all derive
from <code style="font-family:var(--font-mono);font-size:0.92em;color:var(--fg-2)">--accent</code>
and <code style="font-family:var(--font-mono);font-size:0.92em;color:var(--fg-2)">--border</code>.
The submit button reuses <code style="font-family:var(--font-mono);font-size:0.92em;color:var(--fg-2)">.btn-primary</code>
unchanged. The panel itself lifts via the trademark
blue-tinted shadow.
</p>
</div>
<form class="form" onsubmit="event.preventDefault();">
<div class="row-between">
<h3 class="card-title" style="font-size: var(--text-lg)">
Contact sales
</h3>
<span class="badge badge-muted">Secure</span>
</div>
<div class="form-grid-2">
<div class="field">
<label for="first">First name</label>
<input
id="first"
type="text"
placeholder="Jamie"
autocomplete="given-name"
/>
</div>
<div class="field">
<label for="last">Last name</label>
<input
id="last"
type="text"
placeholder="Liu"
autocomplete="family-name"
/>
</div>
</div>
<div class="field">
<label for="email">Work email</label>
<input
id="email"
type="email"
placeholder="jamie@acme.dev"
autocomplete="email"
required
/>
<p class="field-help">
We'll send the integration brief and nothing else.
</p>
</div>
<div class="field">
<label for="volume">Monthly processing volume</label>
<select id="volume">
<option>Under $80k</option>
<option>$80k – $500k</option>
<option>$500k – $5M</option>
<option>Over $5M</option>
</select>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
Start now
</button>
<button type="button" class="btn btn-ghost">
Skip for now
</button>
</div>
</form>
</div>
</section>
</main>
</body>
</html>