mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* feat(design-systems): add structured tokens for cursor brand Introduces design-systems/cursor/tokens.css + components.html, the third structured brand after default and kami. Brings the prose DESIGN.md into the schema-checked form so agents picking the cursor design system get the structured token channel (PR-D #1544) instead of having to re-derive Cursor's warm-cream palette, three-voice typography (CursorGothic / jjannon / berkeleyMono), oklab borders, and depth-based focus from prose every turn. Why now: PR-D made the structured channel default-on, but it only materially helps the 2 brands shipping tokens.css today. To expand batch-design-system-test (#1515) coverage beyond default + kami, we are hand-authoring 5 high-profile brands (cursor first as template, then apple / stripe / airbnb / vercel) before the bulk derive script (PR-B). These five also serve as the byte-identical oracle the future derive script must reproduce. Brand-specific schema decisions, captured in the tokens.css header: - Surface: 3-tier cream ladder (#f2f1ed → #ebeae5 → #e6e5e0). --surface-warm binds to a real intermediate tier, not aliased. - Foreground: 4-tier ramp via rgba(38,37,30,a) at 1.0 / 0.9 / 0.55 / 0.4 — Cursor genuinely has the richer ramp. - Borders: oklab origins shipped as rgba fallbacks per DESIGN.md §Agent Prompt Guide rule 3. - --tracking-display: -0.03em (the 72px hero value normalized to em). Smaller display sizes scale tracking proportionally inline. - --elev-raised: signature 28/70px diffused atmospheric shadow. - --focus-ring: depth-only (0 4px 12px), never the cool-blue halo. - --ease-standard: ease (per DESIGN.md), not the schema cubic-bezier. - --danger: warm crimson #cf2d56, used for the brand-signature hover-text-shift on buttons. components.html demonstrates the full surface (hero with 72px CursorGothic + jjannon serif lead, three-voice typography, warm- surface CTA with crimson hover, accent-orange pill CTA used once, oklab-fallback bordered cards, AI timeline signature element with the warm pastel state colors, jjannon-serif input with 4-12 depth focus). :root paste is byte-identical to tokens.css. Verified: - pnpm guard — 13/13 checks pass, including all 8 design-system sub-checks now reporting "3 brands" (default + kami + cursor): - token-fixture sync: 3 brand pairs aligned - A1/A2/B-slot required tokens: all 3 brands declare 26 A1 + 26 A2 + 4 B-slot - unknown token allowlist: 185 declarations across 3 brands all match shared schema or brand extensions - flag parity: 146 prose-only brands byte-identical, 3 structured diverge as expected Co-authored-by: Cursor <cursoragent@cursor.com> * fix(design-systems): correct h2/h3 line-height in cursor components fixture Per DESIGN.md §Hierarchy, 36px section headings use line-height 1.20 and 26px sub-headings use 1.25. The shared h1/h2/h3 rule was locking all three to --leading-tight (1.10), baking wrong vertical rhythm into the oracle fixture. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(design-systems): add structured tokens for apple, stripe, airbnb, vercel brands Batch follow-up to #1652 (cursor). Adds tokens.css + components.html for the remaining four high-profile brands, using cursor as the format/template oracle. Each brand encodes its specific identity: - apple: SF Pro tight-compressed display (--leading-tight 1.05), system-blue accent, SF Symbols grid, frosted glass surfaces - stripe: sohne-var weight-300 anti-convention signature, --leading-tight 1.10 (matches all heading spec values exactly), indigo accent - airbnb: Cereal weight-700 display with Rausch coral, --leading-tight 1.20 with per-size overrides (h2→1.18, h3→1.25) - vercel: Geist 600 compressed display, --leading-tight 1.10 with per-size overrides (h2→1.20, h3→1.33 per DESIGN.md §Typography) pnpm guard: 13/13 checks pass; flag-parity now shows 6 structured brands and 143 prose-only brands. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
1373 lines
56 KiB
HTML
1373 lines
56 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Airbnb — reference components</title>
|
||
<meta
|
||
name="description"
|
||
content="Reference fixture for design-systems/airbnb. Every visible
|
||
value comes from tokens.css; the page itself follows Airbnb's
|
||
rules — Canvas White surfaces, a single Rausch accent capped
|
||
at two visible uses per viewport, Cereal at weight 500 for
|
||
body, the signature 14-20px soft-circle radii, the three-
|
||
layer booking-panel elevation, and the 2px Ink Black focus
|
||
ring that survives full-bleed photography."
|
||
/>
|
||
|
||
<style>
|
||
/* :root paste — sourced verbatim from design-systems/airbnb/tokens.css. */
|
||
:root {
|
||
/* ─── Surface (3 levels) ────────────────────────────────────────
|
||
* Canvas White is both the page and the card — Airbnb refuses a
|
||
* tonal shift between the two. Cards earn their edges from the
|
||
* Hairline Gray border and from full-bleed photography. The
|
||
* tertiary tier (--surface-warm) is Soft Cloud, used only for
|
||
* footer backgrounds, map-view wrappers, and middle-of-range
|
||
* date cells in the date picker. */
|
||
--bg: #ffffff;
|
||
--surface: #ffffff;
|
||
--surface-warm: #f7f7f7;
|
||
|
||
/* ─── Foreground ramp (4 levels) ────────────────────────────────
|
||
* Ink Black (#222222) carries roughly ninety percent of every
|
||
* page — every heading, every body paragraph, every nav label,
|
||
* every price. The brand's near-black is never true #000000.
|
||
* Charcoal (--fg-2) is a one-step-down emphasis tier reserved
|
||
* for focused-input text. Ash Gray (--muted) is the secondary
|
||
* label — "Cottage rentals" subtitles, muted footer links.
|
||
* Mute Gray (--meta) is reserved for disabled buttons and
|
||
* low-priority metadata. */
|
||
--fg: #222222;
|
||
--fg-2: #3f3f3f;
|
||
--muted: #6a6a6a;
|
||
--meta: #929292;
|
||
|
||
/* ─── Border (2 levels) ─────────────────────────────────────────
|
||
* Hairline Gray (#dddddd) is the workhorse — every card-to-card
|
||
* divider, every amenity-row separator, every footer-column
|
||
* rule. The soft tier is a lighter hairline used to break up
|
||
* dense list interiors without competing with the primary card
|
||
* edge. */
|
||
--border: #dddddd;
|
||
--border-soft: #ebebeb;
|
||
|
||
/* ─── Accent ────────────────────────────────────────────────────
|
||
* Rausch coral-pink — the single signature color. Reserve for
|
||
* primary CTAs (Reserve, Search, Add dates), the active-tab
|
||
* underline, the wishlist heart fill, and price emphasis. Hard
|
||
* cap of two visible uses per viewport; if a third Rausch
|
||
* element shows up, neutralize one. Plus Magenta (#92174d) and
|
||
* Luxe Purple (#460479) are kept inline at their product-tier
|
||
* surfaces, not promoted to shared accent slots. */
|
||
--accent: #ff385c;
|
||
--accent-on: #ffffff;
|
||
--accent-hover: #e31c5f; /* hand-picked, between Rausch and Deep Rausch */
|
||
--accent-active: #e00b41; /* Deep Rausch — DESIGN.md §2 pressed-state value */
|
||
|
||
/* ─── Semantic ──────────────────────────────────────────────────
|
||
* Error Red is explicit from DESIGN.md §2; success and warn are
|
||
* not specified by the brand and are bound to desaturated values
|
||
* that survive the white canvas without competing with Rausch.
|
||
* Keep total semantic-color pixels well under five percent of
|
||
* any page — the brand almost never renders status. */
|
||
--success: #008a05; /* hand-picked warm green — available/in-stock */
|
||
--warn: #c47700; /* burnt amber — never tailwind yellow */
|
||
--danger: #c13515; /* DESIGN.md §2 Error Red */
|
||
|
||
/* ─── Typography — fonts ────────────────────────────────────────
|
||
* Airbnb Cereal VF is the proprietary face that carries every
|
||
* label from 8px superscript to 28px section heading. The
|
||
* documented fallback chain renders acceptably on macOS/iOS
|
||
* where system-ui resolves to San Francisco. Display and body
|
||
* share the same stack — the visual identity comes from the
|
||
* family itself, never from typeface mixing. Mono is a generic
|
||
* stack for kbd, tabular metrics, and the rare code label. */
|
||
--font-display:
|
||
"Airbnb Cereal VF", "Airbnb Cereal App", Circular,
|
||
-apple-system, system-ui, "Helvetica Neue", Roboto, sans-serif;
|
||
--font-body:
|
||
"Airbnb Cereal VF", "Airbnb Cereal App", Circular,
|
||
-apple-system, system-ui, "Helvetica Neue", Roboto, sans-serif;
|
||
--font-mono:
|
||
ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Monaco,
|
||
Consolas, monospace;
|
||
|
||
/* ─── Typography — type scale (px) ──────────────────────────────
|
||
* Derived from DESIGN.md §3 Hierarchy table. The system clusters
|
||
* tightly between 11 and 22px (the conversational range) and
|
||
* jumps to 28px for section headings; the higher tiers are used
|
||
* sparingly for the Guest Favorite rating lockup (44–56px) and
|
||
* the rare hero numeral. No 400-regular: Cereal's body weight
|
||
* is 500, which any consuming component should set explicitly. */
|
||
--text-xs: 12px; /* micro / footer disclaimer */
|
||
--text-sm: 14px; /* button default, link, caption */
|
||
--text-base: 16px; /* body medium, button large, subtitle bold */
|
||
--text-lg: 20px; /* listing title */
|
||
--text-xl: 22px; /* subsection heading */
|
||
--text-2xl: 28px; /* section heading */
|
||
--text-3xl: 44px; /* Guest Favorite rating, hero numeral */
|
||
--text-4xl: 56px; /* display ceiling — Guest Favorite max */
|
||
|
||
/* ─── Typography — leading & tracking ───────────────────────────
|
||
* Tight for display (1.20 — chiseled headlines), generous for
|
||
* body (1.43 — magazine reading comfort). Display tracking
|
||
* compresses negatively at sizes ≥20px; body and caption hold
|
||
* at zero. The two-mode rhythm is what gives Airbnb pages
|
||
* their travel-magazine feel rather than a utility-app feel. */
|
||
--leading-body: 1.43;
|
||
--leading-tight: 1.2;
|
||
--tracking-display: -0.02em; /* applied at sizes ≥20px only */
|
||
|
||
/* ─── Spacing — base scale ──────────────────────────────────────
|
||
* 8px base unit per DESIGN.md §5. The brand uses fine-grained
|
||
* off-grid values (5.5, 10, 11, 18.5, 22) for pixel-perfect
|
||
* icon alignment, but the shared scale stays on the standard
|
||
* 4px grid — off-grid spacing belongs inline at the component
|
||
* level, not in shared tokens. */
|
||
--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 rhythm ────────────────────────────────────────────
|
||
* Section padding ranges 48–64px on desktop, 24–32px on mobile
|
||
* per DESIGN.md §5. We bind the upper bound on desktop because
|
||
* Airbnb's content-dense pages benefit from generous breathing
|
||
* room between content blocks; the lower phone bound keeps the
|
||
* vertical rhythm tight on small screens. */
|
||
--section-y-desktop: 64px;
|
||
--section-y-tablet: 48px;
|
||
--section-y-phone: 32px;
|
||
|
||
/* ─── Radius ────────────────────────────────────────────────────
|
||
* The brand's signature soft-circle geometry. 14px (--radius-md)
|
||
* is the workhorse — every listing card photograph, every
|
||
* amenity badge, every generic content container lands here.
|
||
* 20px (--radius-lg) is reserved for the booking panel and hero
|
||
* photo frames; 8px (--radius-sm) is the small radius for
|
||
* buttons, dropdowns, and inputs; --radius-pill (9999px) is the
|
||
* system's signature round geometry, applied to every circular
|
||
* icon button, every avatar, and the wishlist heart. */
|
||
--radius-sm: 8px;
|
||
--radius-md: 14px;
|
||
--radius-lg: 20px;
|
||
--radius-pill: 9999px;
|
||
|
||
/* ─── Elevation ─────────────────────────────────────────────────
|
||
* Airbnb forbids single drop shadows; the system uses stacked
|
||
* layered shadows or no shadow at all. Listing cards sit flat
|
||
* on the canvas. Booking panels, modals, and dropdowns use the
|
||
* signature three-layer stack — a 2% hairline ring, a 4% short
|
||
* blur, and a 10% medium blur — which reads as one cohesive
|
||
* lift while giving the perimeter a premium anti-aliased edge. */
|
||
--elev-flat: none;
|
||
--elev-ring: 0 0 0 1px var(--border);
|
||
--elev-raised:
|
||
rgba(0, 0, 0, 0.02) 0 0 0 1px,
|
||
rgba(0, 0, 0, 0.04) 0 2px 6px 0,
|
||
rgba(0, 0, 0, 0.1) 0 4px 8px 0;
|
||
|
||
/* ─── Focus ring ────────────────────────────────────────────────
|
||
* 2px solid Ink Black, NOT the schema's accent-tinted default.
|
||
* DESIGN.md §6 mandates this exact treatment so focus indicators
|
||
* read cleanly against full-bleed colorful photography — a
|
||
* Rausch-tinted ring would disappear against warm sunset shots
|
||
* or sea-blue beach overlays. Pair with a 4px white separator
|
||
* ring at circular-icon buttons that float on images. */
|
||
--focus-ring: 0 0 0 2px var(--fg);
|
||
|
||
/* ─── Motion ────────────────────────────────────────────────────
|
||
* DESIGN.md did not capture transition timings (static
|
||
* extraction scope). We bind defaults consistent with the
|
||
* brand's tactile feel: 150ms for micro-states (the famous
|
||
* `transform: scale(0.92)` pressed-button rebound) and 200ms
|
||
* for general state changes. Standard easing is the schema
|
||
* default — short, purposeful, no overshoot. */
|
||
--motion-fast: 150ms;
|
||
--motion-base: 200ms;
|
||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||
|
||
/* ─── Layout ────────────────────────────────────────────────────
|
||
* Detail pages cap at 1280px per DESIGN.md §5; the homepage
|
||
* listing grid lets the canvas breathe to 1760–1920px on
|
||
* ultra-wide. We bind the detail-page width as the shared
|
||
* default because that is the geometry agents will most often
|
||
* generate (rooms, experiences, host profiles). Gutters
|
||
* tighten progressively: 40px desktop, 24px tablet, 16px
|
||
* phone per DESIGN.md §8 small-mobile clause. */
|
||
--container-max: 1280px;
|
||
--container-gutter-desktop: 40px;
|
||
--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: 500; /* Cereal's body weight — DESIGN.md §3 */
|
||
line-height: var(--leading-body);
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
/* ─── 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 ─────────────────────────────────────────────
|
||
* Display sizes ≥20px compress tracking negatively per
|
||
* DESIGN.md §3 ("Negative tracking on display type only").
|
||
* Body and caption sizes hold at zero tracking for
|
||
* readability. Weights: 500 body, 600 emphasis, 700 display
|
||
* — no 400 anywhere, no italic anywhere. */
|
||
h1, h2, h3 {
|
||
font-family: var(--font-display);
|
||
line-height: var(--leading-tight);
|
||
margin: 0;
|
||
}
|
||
h1 {
|
||
font-size: var(--text-2xl);
|
||
font-weight: 700;
|
||
letter-spacing: var(--tracking-display);
|
||
}
|
||
h2 {
|
||
font-size: var(--text-xl);
|
||
font-weight: 500;
|
||
/* 22px Subsection Heading: line-height 1.18 per DESIGN.md §Typography.
|
||
Overrides the shared 1.20 (--leading-tight) set on h1/h2/h3 above. */
|
||
line-height: 1.18;
|
||
letter-spacing: var(--tracking-display);
|
||
}
|
||
h3 {
|
||
font-size: var(--text-base);
|
||
font-weight: 600;
|
||
/* 16px Subtitle/Body size: line-height 1.25 per DESIGN.md §Typography.
|
||
Overrides the shared 1.20 (--leading-tight) set on h1/h2/h3 above. */
|
||
line-height: 1.25;
|
||
}
|
||
p { margin: 0; }
|
||
|
||
.lede {
|
||
font-size: var(--text-base);
|
||
line-height: var(--leading-body);
|
||
color: var(--fg);
|
||
}
|
||
.body-muted { color: var(--muted); }
|
||
.body-meta { color: var(--meta); font-size: var(--text-sm); }
|
||
.body-sm { font-size: var(--text-sm); }
|
||
|
||
/* The single uppercase moment in the system, per DESIGN.md §3
|
||
* Principles ("No all-caps except at 8px"). We elevate this
|
||
* to a 12px eyebrow for fixture legibility, with tracking
|
||
* well above the craft 0.06em floor. Used once per section. */
|
||
.eyebrow {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-xs);
|
||
font-weight: 700;
|
||
line-height: 1.33;
|
||
color: var(--fg);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
.stack-2 > * + * { margin-block-start: var(--space-2); }
|
||
.stack-3 > * + * { margin-block-start: var(--space-3); }
|
||
.stack-4 > * + * { margin-block-start: var(--space-4); }
|
||
.stack-6 > * + * { margin-block-start: var(--space-6); }
|
||
|
||
/* ─── Buttons ────────────────────────────────────────────────
|
||
* Primary CTA is filled Rausch on Canvas White text. Active/
|
||
* pressed uses Deep Rausch as the published brand value
|
||
* (DESIGN.md §4 Primary CTA) combined with the famous
|
||
* `transform: scale(0.92)` rebound that gives the brand its
|
||
* tactile feel. Secondary is a Hairline Gray outlined pill
|
||
* over the canvas. Both share the 2px Ink Black focus ring
|
||
* — see the #Bind 6 rationale in tokens.css. */
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: var(--space-2);
|
||
padding: 14px 24px;
|
||
border: 1px solid transparent;
|
||
border-radius: var(--radius-sm);
|
||
font-family: var(--font-display);
|
||
font-weight: 500;
|
||
font-size: var(--text-base);
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
text-decoration: none;
|
||
transition:
|
||
transform var(--motion-fast) var(--ease-standard),
|
||
background-color var(--motion-fast) var(--ease-standard),
|
||
border-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:active {
|
||
transform: scale(0.96); /* approaching DESIGN.md §4's scale(0.92) */
|
||
}
|
||
.btn-primary {
|
||
background: var(--accent);
|
||
color: var(--accent-on);
|
||
}
|
||
.btn-primary:hover {
|
||
background: var(--accent-hover);
|
||
}
|
||
.btn-primary:active {
|
||
background: var(--accent-active);
|
||
transform: scale(0.92);
|
||
}
|
||
.btn-secondary {
|
||
background: var(--surface);
|
||
color: var(--fg);
|
||
border-color: var(--border);
|
||
border-radius: var(--radius-lg); /* 20px pill — DESIGN.md §4 Secondary */
|
||
}
|
||
.btn-secondary:hover {
|
||
border-color: var(--fg);
|
||
}
|
||
|
||
/* Compact button — listing-card price row, secondary nav links */
|
||
.btn-sm {
|
||
padding: 10px 16px;
|
||
font-size: var(--text-sm);
|
||
}
|
||
|
||
/* Circular icon button — the brand's signature geometry
|
||
* (DESIGN.md §4 Icon-Only Circular Button). Used for back,
|
||
* share, favorite, and carousel controls. The 4px white
|
||
* separator ring on `:hover` is what separates the button
|
||
* from colorful photography backgrounds. */
|
||
.btn-icon {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
padding: 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-pill);
|
||
background: var(--surface);
|
||
color: var(--fg);
|
||
cursor: pointer;
|
||
transition:
|
||
transform var(--motion-fast) var(--ease-standard),
|
||
box-shadow var(--motion-fast) var(--ease-standard);
|
||
}
|
||
.btn-icon:hover {
|
||
box-shadow: 0 0 0 4px var(--surface), var(--elev-ring);
|
||
}
|
||
.btn-icon:active {
|
||
transform: scale(0.92);
|
||
}
|
||
.btn-icon:focus-visible {
|
||
outline: none;
|
||
box-shadow: var(--focus-ring);
|
||
}
|
||
.btn-icon svg { width: 16px; height: 16px; }
|
||
|
||
/* ─── Inputs ─────────────────────────────────────────────────
|
||
* Generic text input per DESIGN.md §4 Text Input — 1px
|
||
* Hairline Gray edge, 8px radius, 14px·16px padding. Focus
|
||
* switches the edge to Ink Black and adds the 2px outer
|
||
* ring. Error state swaps to Error Red on both border and
|
||
* helper text. */
|
||
.field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-2);
|
||
}
|
||
.field label {
|
||
font-size: var(--text-sm);
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
}
|
||
.field input,
|
||
.field select {
|
||
padding: 14px 16px;
|
||
border-radius: var(--radius-sm);
|
||
border: 1px solid var(--border);
|
||
background: var(--surface);
|
||
color: var(--fg);
|
||
font-family: inherit;
|
||
font-weight: 500;
|
||
font-size: var(--text-base);
|
||
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 {
|
||
border-color: var(--fg);
|
||
box-shadow: var(--focus-ring);
|
||
color: var(--fg-2);
|
||
}
|
||
.field input::placeholder { color: var(--muted); }
|
||
.field-help {
|
||
font-size: var(--text-xs);
|
||
color: var(--muted);
|
||
}
|
||
.field-error .field-help { color: var(--danger); }
|
||
.field-error input { border-color: var(--danger); }
|
||
|
||
/* Search pill — three-segment "Where / When / Who" with a
|
||
* Rausch circular submit button at the right edge. DESIGN.md
|
||
* §4 Search Bar. The 32px pill radius is one of the brand's
|
||
* signature radii. */
|
||
.search-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0;
|
||
padding: 8px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-pill);
|
||
background: var(--surface);
|
||
box-shadow:
|
||
rgba(0, 0, 0, 0.04) 0 2px 6px 0;
|
||
max-width: 100%;
|
||
}
|
||
.search-segment {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 6px var(--space-5);
|
||
min-width: 0;
|
||
cursor: pointer;
|
||
}
|
||
.search-segment + .search-segment {
|
||
border-left: 1px solid var(--border);
|
||
}
|
||
.search-segment-label {
|
||
font-size: var(--text-xs);
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
line-height: 1.2;
|
||
}
|
||
.search-segment-value {
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
color: var(--muted);
|
||
line-height: 1.3;
|
||
}
|
||
.search-submit {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 48px;
|
||
height: 48px;
|
||
margin-left: var(--space-2);
|
||
border: none;
|
||
border-radius: var(--radius-pill);
|
||
background: var(--accent);
|
||
color: var(--accent-on);
|
||
cursor: pointer;
|
||
transition:
|
||
background-color var(--motion-fast) var(--ease-standard),
|
||
transform var(--motion-fast) var(--ease-standard);
|
||
}
|
||
.search-submit:hover { background: var(--accent-hover); }
|
||
.search-submit:active {
|
||
background: var(--accent-active);
|
||
transform: scale(0.92);
|
||
}
|
||
.search-submit:focus-visible {
|
||
outline: none;
|
||
box-shadow: var(--focus-ring);
|
||
}
|
||
.search-submit svg { width: 16px; height: 16px; }
|
||
|
||
/* ─── Cards ──────────────────────────────────────────────────
|
||
* Two card shapes: the listing card (no border, no shadow,
|
||
* 14px image radius, text directly below the photo per
|
||
* DESIGN.md §4 Listing Card) and the booking-panel card
|
||
* (1px Hairline border, 20px radius, three-layer shadow
|
||
* stack — the system's most recognizable elevation). */
|
||
.listing {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-3);
|
||
}
|
||
.listing-photo {
|
||
position: relative;
|
||
aspect-ratio: 4 / 3;
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
background: var(--surface-warm);
|
||
}
|
||
/* Photograph placeholder — gentle linear gradient inside the
|
||
* 14px radius. In production this slot is a 4:3 muscache CDN
|
||
* image with loading="lazy"; the fixture renders a tonal
|
||
* surface so the card geometry is still legible without art. */
|
||
.listing-photo::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
linear-gradient(
|
||
135deg,
|
||
var(--surface-warm) 0%,
|
||
var(--border) 60%,
|
||
var(--surface-warm) 100%
|
||
);
|
||
}
|
||
.listing-photo .btn-icon {
|
||
position: absolute;
|
||
top: var(--space-3);
|
||
right: var(--space-3);
|
||
z-index: 1;
|
||
}
|
||
.listing-photo .listing-badge {
|
||
position: absolute;
|
||
top: var(--space-3);
|
||
left: var(--space-3);
|
||
z-index: 1;
|
||
}
|
||
.listing-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-1);
|
||
}
|
||
.listing-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: var(--space-2);
|
||
}
|
||
.listing-title {
|
||
font-size: var(--text-base);
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
}
|
||
.listing-subtitle {
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
color: var(--muted);
|
||
}
|
||
.listing-price {
|
||
font-size: var(--text-base);
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
}
|
||
.listing-price .per {
|
||
font-weight: 500;
|
||
color: var(--muted);
|
||
}
|
||
.listing-rating {
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
color: var(--fg);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* Booking panel — the brand's signature sticky elevation.
|
||
* Three-layer shadow + Hairline border + 20px radius +
|
||
* 24px padding. DESIGN.md §4 Detail Page Booking Panel. */
|
||
.booking-panel {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: var(--elev-raised);
|
||
padding: var(--space-6);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-4);
|
||
max-width: 370px;
|
||
}
|
||
.booking-headline {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: var(--space-2);
|
||
}
|
||
.booking-price {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-xl);
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
letter-spacing: var(--tracking-display);
|
||
}
|
||
.booking-per {
|
||
font-size: var(--text-base);
|
||
font-weight: 500;
|
||
color: var(--muted);
|
||
}
|
||
.booking-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
}
|
||
.booking-grid-cell {
|
||
padding: var(--space-3) var(--space-4);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
cursor: pointer;
|
||
background: var(--surface);
|
||
}
|
||
.booking-grid-cell + .booking-grid-cell {
|
||
border-left: 1px solid var(--border);
|
||
}
|
||
.booking-grid-cell.full {
|
||
grid-column: 1 / -1;
|
||
border-top: 1px solid var(--border);
|
||
border-left: none;
|
||
}
|
||
.booking-cell-label {
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
color: var(--fg);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
}
|
||
.booking-cell-value {
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
color: var(--muted);
|
||
}
|
||
.booking-disclaimer {
|
||
font-size: var(--text-xs);
|
||
color: var(--muted);
|
||
text-align: center;
|
||
line-height: var(--leading-body);
|
||
}
|
||
|
||
/* ─── Badges ─────────────────────────────────────────────────
|
||
* Two shapes: the rounded brand badge (14px radius, used on
|
||
* listing photos as "NEW" or "Guest favorite" markers) and
|
||
* the dotted status badge (pill, used in metadata strips).
|
||
* Both stay under DESIGN.md §3's "no all-caps except 8px"
|
||
* rule by using sentence case at 12px·600. */
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
padding: 6px var(--space-3);
|
||
border-radius: var(--radius-pill);
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-xs);
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
}
|
||
.badge-rausch {
|
||
background: var(--accent);
|
||
color: var(--accent-on);
|
||
border-radius: var(--radius-md);
|
||
}
|
||
.badge-soft {
|
||
background: var(--surface);
|
||
color: var(--fg);
|
||
border-radius: var(--radius-md);
|
||
box-shadow: var(--elev-ring);
|
||
}
|
||
.badge-status {
|
||
background: var(--surface-warm);
|
||
color: var(--fg);
|
||
}
|
||
.badge-status .badge-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: var(--radius-pill);
|
||
background: var(--success);
|
||
}
|
||
|
||
/* ─── Links ──────────────────────────────────────────────────
|
||
* Body links inherit Ink Black, NOT Rausch. Rausch is
|
||
* reserved for buttons and the active-tab indicator;
|
||
* promoting it to link color would dilute the accent and
|
||
* trip the two-uses-per-viewport cap. Underline appears on
|
||
* hover at generous offset. */
|
||
a {
|
||
color: var(--fg);
|
||
text-decoration: underline;
|
||
text-underline-offset: 3px;
|
||
text-decoration-thickness: 1px;
|
||
text-decoration-color: var(--border);
|
||
transition: text-decoration-color var(--motion-fast) var(--ease-standard);
|
||
}
|
||
a:hover {
|
||
text-decoration-color: var(--fg);
|
||
}
|
||
a:focus-visible {
|
||
outline: none;
|
||
box-shadow: var(--focus-ring);
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
|
||
/* ─── Kbd ────────────────────────────────────────────────────
|
||
* Hairline outline + Soft Cloud fill + tabular mono. Used
|
||
* for the rare keyboard shortcut hint. */
|
||
kbd {
|
||
display: inline-block;
|
||
font-family: var(--font-mono);
|
||
font-size: var(--text-xs);
|
||
padding: 2px 6px;
|
||
border-radius: var(--radius-sm);
|
||
border: 1px solid var(--border);
|
||
background: var(--surface-warm);
|
||
color: var(--fg);
|
||
line-height: 1.2;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* ─── Distinctive: Guest Favorite Award lockup ───────────────
|
||
* The brand's most recognizable moment (DESIGN.md §4
|
||
* Signature Components). A centered rating numeral at
|
||
* 44–56px·700 flanked by two laurel wreath SVGs. Below the
|
||
* lockup: a 12px·700 uppercase label with 0.32px tracking
|
||
* (we widen to 0.06em — the craft floor) and a 14px·500
|
||
* Ash Gray sub-label. Full-width block, no border, sits
|
||
* directly on the canvas. */
|
||
.award {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
gap: var(--space-3);
|
||
padding-block: var(--space-8);
|
||
}
|
||
.award-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-5);
|
||
color: var(--fg);
|
||
}
|
||
.award-rating {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-3xl);
|
||
font-weight: 700;
|
||
color: var(--fg);
|
||
line-height: 1;
|
||
letter-spacing: var(--tracking-display);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.award-laurel {
|
||
width: 48px;
|
||
height: 64px;
|
||
color: var(--fg);
|
||
flex-shrink: 0;
|
||
}
|
||
.award-laurel.right { transform: scaleX(-1); }
|
||
.award-label {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-xs);
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
color: var(--fg);
|
||
line-height: 1;
|
||
}
|
||
.award-sub {
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
color: var(--muted);
|
||
max-width: 44ch;
|
||
line-height: var(--leading-body);
|
||
}
|
||
|
||
/* Tri-tab category picker (DESIGN.md §4 Tri-Tab) — the brand's
|
||
* other instantly-recognizable nav moment. We render with
|
||
* type-only icons (no 3D illustrations) because the fixture
|
||
* stays raster-free, but the underline + label rhythm holds. */
|
||
.tabs {
|
||
display: inline-flex;
|
||
gap: var(--space-8);
|
||
padding-block-start: var(--space-3);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.tab {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
padding-block-end: var(--space-3);
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-base);
|
||
font-weight: 500;
|
||
color: var(--muted);
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
position: relative;
|
||
text-decoration: none;
|
||
}
|
||
.tab-glyph {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--fg);
|
||
}
|
||
.tab[aria-selected="true"] {
|
||
color: var(--fg);
|
||
}
|
||
.tab[aria-selected="true"]::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: -1px;
|
||
height: 2px;
|
||
background: var(--fg);
|
||
border-radius: var(--radius-pill);
|
||
}
|
||
.tab-new {
|
||
position: absolute;
|
||
top: -4px;
|
||
right: -10px;
|
||
padding: 2px 6px;
|
||
border-radius: var(--radius-md);
|
||
background: var(--fg);
|
||
color: var(--accent-on);
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
line-height: 1.2;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* ─── Section-specific layout ──────────────────────────────── */
|
||
.hero-grid {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
|
||
gap: var(--space-12);
|
||
align-items: start;
|
||
}
|
||
@media (max-width: 1023px) {
|
||
.hero-grid { grid-template-columns: 1fr; gap: var(--space-8); }
|
||
}
|
||
.hero-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: var(--space-3);
|
||
margin-block-start: var(--space-6);
|
||
}
|
||
|
||
.listings-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: var(--space-6);
|
||
margin-block-start: var(--space-8);
|
||
}
|
||
@media (max-width: 1023px) {
|
||
.listings-grid { grid-template-columns: repeat(2, 1fr); }
|
||
}
|
||
@media (max-width: 639px) {
|
||
.listings-grid { grid-template-columns: 1fr; gap: var(--space-4); }
|
||
}
|
||
|
||
.form-row {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.2fr) minmax(0, 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);
|
||
}
|
||
.row-between {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: var(--space-3);
|
||
}
|
||
.row-cluster {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
flex-wrap: wrap;
|
||
}
|
||
.divider-soft {
|
||
height: 1px;
|
||
background: var(--border-soft);
|
||
border: none;
|
||
margin: 0;
|
||
}
|
||
.nav-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-block: var(--space-4);
|
||
gap: var(--space-4);
|
||
}
|
||
.nav-logo {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-lg);
|
||
font-weight: 700;
|
||
color: var(--accent);
|
||
letter-spacing: var(--tracking-display);
|
||
}
|
||
.icon { width: 16px; height: 16px; flex-shrink: 0; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<main class="container">
|
||
<!-- ════════════════════════════════════════════════════════════
|
||
NAV — exercises: .nav-row, .nav-logo (Rausch wordmark),
|
||
.tabs (tri-tab picker, DESIGN.md §4 signature). Active
|
||
"Homes" tab gets the 2px Ink Black underline; "Experiences"
|
||
carries the small Ink Black NEW pill from §4.
|
||
═══════════════════════════════════════════════════════════════ -->
|
||
<nav class="nav-row" aria-label="Primary">
|
||
<span class="nav-logo">airbnb</span>
|
||
<div class="tabs" role="tablist">
|
||
<button class="tab" role="tab" aria-selected="true">
|
||
<span class="tab-glyph" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||
stroke-width="1.5" stroke-linecap="round"
|
||
stroke-linejoin="round" width="24" height="24">
|
||
<path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6h-6v6H4a1 1 0 0 1-1-1z" />
|
||
</svg>
|
||
</span>
|
||
Homes
|
||
</button>
|
||
<button class="tab" role="tab" aria-selected="false">
|
||
<span class="tab-glyph" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||
stroke-width="1.5" stroke-linecap="round"
|
||
stroke-linejoin="round" width="24" height="24">
|
||
<ellipse cx="12" cy="9" rx="6" ry="7" />
|
||
<path d="M9 16l-1 5 4-2 4 2-1-5" />
|
||
</svg>
|
||
</span>
|
||
Experiences
|
||
<span class="tab-new" aria-label="New">New</span>
|
||
</button>
|
||
<button class="tab" role="tab" aria-selected="false">
|
||
<span class="tab-glyph" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||
stroke-width="1.5" stroke-linecap="round"
|
||
stroke-linejoin="round" width="24" height="24">
|
||
<path d="M6 16a6 6 0 1 1 12 0z" />
|
||
<path d="M12 6V3M5 18h14M11 19v2h2v-2" />
|
||
</svg>
|
||
</span>
|
||
Services
|
||
<span class="tab-new" aria-label="New">New</span>
|
||
</button>
|
||
</div>
|
||
<div class="row-cluster">
|
||
<button class="btn btn-secondary btn-sm" type="button">Become a host</button>
|
||
<button class="btn-icon" type="button" aria-label="Menu">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||
stroke-width="1.75" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true">
|
||
<path d="M4 7h16M4 12h16M4 17h16" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- ════════════════════════════════════════════════════════════
|
||
HERO — exercises: .eyebrow, h1, .lede, .search-pill (the
|
||
three-segment Where/When/Who with the Rausch circular
|
||
submit, DESIGN.md §4), .btn-primary, .btn-secondary,
|
||
kbd, .badge-status (success dot).
|
||
═══════════════════════════════════════════════════════════════ -->
|
||
<section data-od-id="hero">
|
||
<div class="hero-grid">
|
||
<div class="stack-4">
|
||
<p class="eyebrow">Reference fixture · airbnb</p>
|
||
<h1 style="max-width: 18ch">
|
||
Find a place where the photograph is the interface.
|
||
</h1>
|
||
<p class="lede body-muted" style="max-width: 56ch">
|
||
A token system distilled from Airbnb's 2026 rules — Canvas
|
||
White surfaces, a single Rausch coral accent, generous
|
||
soft-circle radii, and Cereal at one family. The fixture
|
||
you are reading uses the same token block agents paste
|
||
into every artifact, so the rules survive the paste.
|
||
</p>
|
||
|
||
<div class="search-pill" role="search" aria-label="Find a stay">
|
||
<div class="search-segment">
|
||
<span class="search-segment-label">Where</span>
|
||
<span class="search-segment-value">Search destinations</span>
|
||
</div>
|
||
<div class="search-segment">
|
||
<span class="search-segment-label">When</span>
|
||
<span class="search-segment-value">Add dates</span>
|
||
</div>
|
||
<div class="search-segment">
|
||
<span class="search-segment-label">Who</span>
|
||
<span class="search-segment-value">Add guests</span>
|
||
</div>
|
||
<button class="search-submit" type="submit" aria-label="Search">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||
stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true">
|
||
<circle cx="11" cy="11" r="7" />
|
||
<path d="M21 21l-4.3-4.3" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="hero-actions">
|
||
<a href="./tokens.css" class="btn btn-primary">
|
||
View the token block
|
||
<svg
|
||
class="icon"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.75"
|
||
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-secondary">Read the brand</a>
|
||
</div>
|
||
</div>
|
||
|
||
<aside class="booking-panel" aria-label="Token status">
|
||
<div class="booking-headline">
|
||
<span class="booking-price">56 tokens</span>
|
||
<span class="booking-per">in :root</span>
|
||
</div>
|
||
<div class="booking-grid" role="group">
|
||
<div class="booking-grid-cell">
|
||
<span class="booking-cell-label">Last reviewed</span>
|
||
<span class="booking-cell-value">
|
||
<time datetime="2026-05-14">May 14, 2026</time>
|
||
</span>
|
||
</div>
|
||
<div class="booking-grid-cell">
|
||
<span class="booking-cell-label">Schema</span>
|
||
<span class="booking-cell-value">v0.1 · A1/A2/B</span>
|
||
</div>
|
||
<div class="booking-grid-cell full">
|
||
<span class="booking-cell-label">Accent budget</span>
|
||
<span class="booking-cell-value">≤ 2 visible Rausch uses per viewport</span>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="btn btn-primary" style="width: 100%">
|
||
Inspect tokens
|
||
</button>
|
||
<p class="booking-disclaimer">
|
||
You won't be charged — this is a static fixture. Press
|
||
<kbd>⌘</kbd> <kbd>K</kbd> to jump to the token grep.
|
||
</p>
|
||
<div class="row-between" style="margin-block-start: var(--space-2)">
|
||
<span class="badge badge-status">
|
||
<span class="badge-dot" aria-hidden="true"></span>
|
||
Tokens passing schema
|
||
</span>
|
||
<span class="listing-rating" aria-label="Token coverage">
|
||
<svg
|
||
width="14"
|
||
height="14"
|
||
viewBox="0 0 24 24"
|
||
fill="currentColor"
|
||
aria-hidden="true"
|
||
style="vertical-align: -2px; margin-right: 4px"
|
||
>
|
||
<path d="M12 2l2.9 6.9 7.1.6-5.4 4.7 1.7 7L12 17l-6.3 4.2 1.7-7L2 9.5l7.1-.6z" />
|
||
</svg>
|
||
4.92
|
||
</span>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ════════════════════════════════════════════════════════════
|
||
LISTINGS — exercises: .listing (4:3 photo, no border, no
|
||
shadow, 14px radius), .listing-photo with floated circular
|
||
favorite button, .badge-rausch, .listing-rating, links.
|
||
Three cards in a 3-col grid that reflows to 2 then 1.
|
||
═══════════════════════════════════════════════════════════════ -->
|
||
<section data-od-id="listings">
|
||
<div class="stack-3">
|
||
<p class="eyebrow">What the fixture exercises</p>
|
||
<h2 style="max-width: 32ch">
|
||
Three listings, every token resolved through var(--*).
|
||
</h2>
|
||
<p class="body-muted" style="max-width: 56ch">
|
||
No raw hex outside the :root paste. No second type family.
|
||
No drop shadow on the listing cards — separation comes from
|
||
the photograph and the 24px gutter between them.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="listings-grid">
|
||
<article class="listing" aria-label="Cottage in the Cotswolds">
|
||
<div class="listing-photo">
|
||
<span class="listing-badge badge badge-soft">Guest favorite</span>
|
||
<button class="btn-icon" type="button" aria-label="Save to wishlist">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||
stroke-width="1.75" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true">
|
||
<path d="M12 21s-7-4.5-7-10a4 4 0 0 1 7-2.7A4 4 0 0 1 19 11c0 5.5-7 10-7 10z" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<div class="listing-meta">
|
||
<div class="listing-row">
|
||
<span class="listing-title">Cotswolds, England</span>
|
||
<span class="listing-rating">
|
||
<svg
|
||
width="12"
|
||
height="12"
|
||
viewBox="0 0 24 24"
|
||
fill="currentColor"
|
||
aria-hidden="true"
|
||
style="vertical-align: -1px; margin-right: 3px"
|
||
>
|
||
<path d="M12 2l2.9 6.9 7.1.6-5.4 4.7 1.7 7L12 17l-6.3 4.2 1.7-7L2 9.5l7.1-.6z" />
|
||
</svg>
|
||
4.97
|
||
</span>
|
||
</div>
|
||
<span class="listing-subtitle">Cottage rentals · 2 hours by train</span>
|
||
<span class="listing-subtitle">Apr 12 – 17</span>
|
||
<span class="listing-price">
|
||
$182 <span class="per">/ night</span>
|
||
</span>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="listing" aria-label="Loft in Lisbon">
|
||
<div class="listing-photo">
|
||
<span class="listing-badge badge badge-rausch">New</span>
|
||
<button class="btn-icon" type="button" aria-label="Save to wishlist">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||
stroke-width="1.75" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true">
|
||
<path d="M12 21s-7-4.5-7-10a4 4 0 0 1 7-2.7A4 4 0 0 1 19 11c0 5.5-7 10-7 10z" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<div class="listing-meta">
|
||
<div class="listing-row">
|
||
<span class="listing-title">Lisbon, Portugal</span>
|
||
<span class="listing-rating">
|
||
<svg
|
||
width="12"
|
||
height="12"
|
||
viewBox="0 0 24 24"
|
||
fill="currentColor"
|
||
aria-hidden="true"
|
||
style="vertical-align: -1px; margin-right: 3px"
|
||
>
|
||
<path d="M12 2l2.9 6.9 7.1.6-5.4 4.7 1.7 7L12 17l-6.3 4.2 1.7-7L2 9.5l7.1-.6z" />
|
||
</svg>
|
||
4.81
|
||
</span>
|
||
</div>
|
||
<span class="listing-subtitle">Loft apartments · Alfama</span>
|
||
<span class="listing-subtitle">May 03 – 09</span>
|
||
<span class="listing-price">
|
||
$96 <span class="per">/ night</span>
|
||
</span>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="listing" aria-label="Yacht tour in Mykonos">
|
||
<div class="listing-photo">
|
||
<span class="listing-badge badge badge-soft">Experience</span>
|
||
<button class="btn-icon" type="button" aria-label="Save to wishlist">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||
stroke-width="1.75" stroke-linecap="round"
|
||
stroke-linejoin="round" aria-hidden="true">
|
||
<path d="M12 21s-7-4.5-7-10a4 4 0 0 1 7-2.7A4 4 0 0 1 19 11c0 5.5-7 10-7 10z" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<div class="listing-meta">
|
||
<div class="listing-row">
|
||
<span class="listing-title">Mykonos, Greece</span>
|
||
<span class="listing-rating">
|
||
<svg
|
||
width="12"
|
||
height="12"
|
||
viewBox="0 0 24 24"
|
||
fill="currentColor"
|
||
aria-hidden="true"
|
||
style="vertical-align: -1px; margin-right: 3px"
|
||
>
|
||
<path d="M12 2l2.9 6.9 7.1.6-5.4 4.7 1.7 7L12 17l-6.3 4.2 1.7-7L2 9.5l7.1-.6z" />
|
||
</svg>
|
||
4.89
|
||
</span>
|
||
</div>
|
||
<span class="listing-subtitle">Small Group Yacht Tour</span>
|
||
<span class="listing-subtitle">4 hours · Unlimited fruit</span>
|
||
<span class="listing-price">
|
||
$148 <span class="per">/ person</span>
|
||
</span>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ════════════════════════════════════════════════════════════
|
||
GUEST FAVORITE AWARD — the brand's signature distinctive
|
||
component. Centered rating numeral at 44px·700 flanked by
|
||
two laurel wreath SVGs at 48×64. Below, an uppercase 12px·700
|
||
"GUEST FAVORITE" label and a 14px·500 Ash Gray sub-line.
|
||
Full-width block, no border, sits directly on the canvas
|
||
per DESIGN.md §4 Signature Components.
|
||
═══════════════════════════════════════════════════════════════ -->
|
||
<section data-od-id="award">
|
||
<div class="award" aria-label="Guest Favorite award">
|
||
<div class="award-row">
|
||
<svg
|
||
class="award-laurel"
|
||
viewBox="0 0 48 64"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
aria-hidden="true"
|
||
>
|
||
<path d="M40 60 C 14 56, 6 38, 12 16" />
|
||
<path d="M30 56 C 18 50, 14 44, 14 36" />
|
||
<path d="M18 22 C 8 22, 6 14, 12 8" />
|
||
<path d="M20 30 C 8 30, 6 24, 10 18" />
|
||
<path d="M22 38 C 12 38, 8 32, 12 26" />
|
||
<path d="M26 46 C 16 46, 10 40, 14 34" />
|
||
<path d="M30 52 C 22 52, 16 48, 18 42" />
|
||
</svg>
|
||
<span class="award-rating">4.92</span>
|
||
<svg
|
||
class="award-laurel right"
|
||
viewBox="0 0 48 64"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
aria-hidden="true"
|
||
>
|
||
<path d="M40 60 C 14 56, 6 38, 12 16" />
|
||
<path d="M30 56 C 18 50, 14 44, 14 36" />
|
||
<path d="M18 22 C 8 22, 6 14, 12 8" />
|
||
<path d="M20 30 C 8 30, 6 24, 10 18" />
|
||
<path d="M22 38 C 12 38, 8 32, 12 26" />
|
||
<path d="M26 46 C 16 46, 10 40, 14 34" />
|
||
<path d="M30 52 C 22 52, 16 48, 18 42" />
|
||
</svg>
|
||
</div>
|
||
<p class="award-label">Guest favorite</p>
|
||
<p class="award-sub">
|
||
One of the most loved tokensets on Open Design, according
|
||
to agents who paste it.
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ════════════════════════════════════════════════════════════
|
||
FORM — exercises: .field (text input, focus rings the
|
||
border to Ink Black and adds the 2px outer ring per
|
||
DESIGN.md §4 Text Input), .field-error (Error Red),
|
||
.btn-primary, .btn-secondary, kbd. The right column re-
|
||
uses the .booking-panel pattern as a price summary.
|
||
═══════════════════════════════════════════════════════════════ -->
|
||
<section data-od-id="form">
|
||
<div class="form-row">
|
||
<div class="stack-4">
|
||
<p class="eyebrow">Form components</p>
|
||
<h2 style="max-width: 24ch">
|
||
Inputs inherit the same tokens.
|
||
</h2>
|
||
<p class="body-muted" style="max-width: 52ch">
|
||
Focus rings, borders, and placeholder color all derive
|
||
from --fg and --border. Submit reuses .btn-primary
|
||
unchanged. No new token introduced for this section —
|
||
if a value here is not in the :root paste above, it
|
||
does not belong in the artifact at all.
|
||
</p>
|
||
<hr class="divider-soft" />
|
||
<p class="body-meta">
|
||
Full reference at <a href="./tokens.css">tokens.css</a>
|
||
· brand at <a href="./DESIGN.md">DESIGN.md</a>.
|
||
</p>
|
||
</div>
|
||
|
||
<form class="form" onsubmit="event.preventDefault();">
|
||
<div class="field">
|
||
<label for="email">Travel notes email</label>
|
||
<input
|
||
id="email"
|
||
type="email"
|
||
placeholder="you@somewhere.travel"
|
||
autocomplete="email"
|
||
required
|
||
/>
|
||
<p class="field-help">
|
||
We'll send a single dispatch a season — never more.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label for="destination">Where</label>
|
||
<input
|
||
id="destination"
|
||
type="text"
|
||
placeholder="Search destinations"
|
||
autocomplete="off"
|
||
/>
|
||
</div>
|
||
|
||
<div class="field field-error">
|
||
<label for="dates">Dates</label>
|
||
<input
|
||
id="dates"
|
||
type="text"
|
||
placeholder="Add dates"
|
||
aria-invalid="true"
|
||
/>
|
||
<p class="field-help">Pick a check-in and check-out before you continue.</p>
|
||
</div>
|
||
|
||
<div class="row-cluster" style="margin-block-start: var(--space-2)">
|
||
<button type="submit" class="btn btn-primary">Send the dispatch</button>
|
||
<button type="button" class="btn btn-secondary">Skip for now</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
</body>
|
||
</html>
|