# Elite Tents — Design System v1.0

**Source-of-truth document for the Webflow build.**
Last updated: 2026-05-08 · Companion files: [`mockup.html`](mockup.html), [`design-system.html`](design-system.html), [`design/wireframes/`](design/wireframes/)

This document is the agreement: what colors, type, components, motion, and layout decisions Elite Tents will use across every page. The Webflow Variables panel will be populated from §3 (Color) and §11 (Mapping). The mockup.html file is the implementation reference — when this document and the mockup disagree, this document wins and the mockup gets updated.

---

## Table of contents

1. [Brand foundation](#1-brand-foundation)
2. [Audience and tone](#2-audience-and-tone)
3. [Color system](#3-color-system)
4. [Typography](#4-typography)
5. [Spacing and rhythm](#5-spacing-and-rhythm)
6. [Radii, hairlines, shadows](#6-radii-hairlines-shadows)
7. [Component library](#7-component-library)
8. [Motion](#8-motion)
9. [Page wireframes](#9-page-wireframes)
10. [Mobile breakpoint](#10-mobile-breakpoint)
11. [Webflow Variable mapping](#11-webflow-variable-mapping)
12. [Photography and content direction](#12-photography-and-content-direction)
13. [What we deliberately don't do](#13-what-we-deliberately-dont-do)

---

## 1. Brand foundation

**Archetype: Editorial Luxury × Editorial Split.**

Think of a high-end print catalog brought into the browser. Confident serif headlines with restrained italic accents, generous whitespace, hairline dividers, multi-layer tinted shadows, a quiet film-grain texture, concentric border radii on every nested surface. Photography does the heavy lifting; chrome stays out of the way.

Reference touchstones (visual mood, not literal copying):

| Brand | What we borrow |
|---|---|
| Aesop | Restrained typography, italic accents, ample whitespace |
| Cereal Magazine | Editorial rhythm, hairlines, cream/espresso palette |
| & Other Stories | Soft luxury, photography-forward, gentle motion |
| Apple Newsroom | Confident hero layouts, system trust, clear hierarchy |

**What we're not:** flashy, animated-for-its-own-sake, dark-themed, neon, glassmorphism-heavy, dense-with-text, or trying to win design awards. Wedding clients and corporate event planners need familiarity to trust the brand; the design earns that with quality details, not novelty.

**Core principle for every decision:** *modern tried-and-true patterns over experimental ones.* If a pattern would feel out of place on a 5-star resort site, it's out of place here.

---

## 2. Audience and tone

**Primary audience.** Brides, grooms, and wedding planners booking outdoor weddings of 50–500 guests in the Lower Mainland (Greater Vancouver, Fraser Valley, Whistler, Sea-to-Sky). Average wedding budget $40K–$120K with ~$8K–$25K of that on tent rental. Decision-making typically 6–14 months before the event.

**Secondary audience.** Corporate event coordinators booking quarterly off-sites, product launches, fundraisers, film/TV catered shoots. Budgets variable; lead times shorter (2–8 weeks).

**Tertiary audience.** Returning clients, referral-network venue partners (golf courses, vineyards, farms).

**Voice attributes.**

| Yes | No |
|---|---|
| Considered, present-tense, unhurried | Salesy, urgent, exclamation-heavy |
| Specific (sizes, guest counts, materials) | Generic ("perfect for any occasion") |
| Confident without superlatives | Award-list namedropping |
| Warm and human | Corporate or formal |
| Quietly proud (since 2011, BC-built) | Boastful or scarcity-driven |

**Voice in practice — a short rewrite test.**

> ❌ "AMAZING tents for ANY occasion! Book now before dates fill up!"
> ✅ "Tents that hold the quiet moments, not just the loud ones."
> ❌ "The very best wedding tents you can rent in BC!"
> ✅ "Premium tent rentals for weddings, celebrations, and corporate gatherings across the Lower Mainland."

The hero headline in the live mockup ("Tents that hold the quiet moments, not just the loud ones.") sets the tone. Italic phrase carries the emotional payload; the rest is plain.

---

## 3. Color system

The palette is small, deliberate, and designed for high contrast on cream. Every color has a defined role; nothing is decorative.

### Full palette

| Token | Hex | RGB | Role |
|---|---|---|---|
| `--cream` | `#F5EFE6` | 245 239 230 | Page background, card surfaces. Warm-neutral, never pure white |
| `--cream-2` | `#EDE4D2` | 237 228 210 | Card-on-card, subtle elevation, button hovers on cream |
| `--cream-3` | `#E3D8C1` | 227 216 193 | Photo placeholders, deepest cream tier |
| `--navy` *(espresso)* | `#3A2818` | 58 40 24 | Primary text, dark CTAs, dark panes (My Event submit) |
| `--navy-deep` | `#23180F` | 35 24 15 | Mockup bar, modal overlay tint, deepest dark |
| `--navy-soft` | `#4E3828` | 78 56 40 | Reserved for future hover states on espresso |
| `--clay` | `#B85A3B` | 184 90 59 | Primary CTA fill, italic accents, eyebrow rules, focus rings |
| `--clay-dark` | `#964730` | 150 71 48 | Primary CTA hover only |
| `--champagne` | `#C9B08A` | 201 176 138 | Dark-pane accents (champagne CTA, hairlines on espresso) |
| `--champagne-light` | `#E0CFAF` | 224 207 175 | Champagne CTA hover, tertiary text on espresso |
| `--ink` | `#2A1F18` | 42 31 24 | Body copy on cream (slightly darker than navy) |
| `--muted` | `#6B5D52` | 107 93 82 | Secondary text, descriptions |
| `--faint` | `#928574` | 146 133 116 | Tertiary text, captions, micro-labels |

> **Why two near-blacks?** `--ink` is for body text where reading-comfort matters; `--navy` is for dark surfaces and CTA fills. They're close enough that on cream they read identically, but `--ink` resists eye fatigue better at body sizes. Don't use `--navy` for paragraphs.

### Approved color pairs

The wedding-tent context demands very high contrast. Computed WCAG ratios (real values, not estimates):

| Foreground | Background | Use case | Contrast | WCAG |
|---|---|---|---|---|
| `--ink` | `--cream` | Body copy, headlines | 14.05:1 | ✅ AAA |
| `--navy` | `--cream` | Dark text on light pane | 12.28:1 | ✅ AAA |
| `--cream` | `--navy` | Tab active, dark pane text | 12.28:1 | ✅ AAA |
| `--champagne-light` | `--navy` | Body on espresso pane | 9.16:1 | ✅ AAA |
| `--navy` | `--champagne` | Champagne CTA text | 6.72:1 | ✅ AAA |
| `--champagne` | `--navy` | Eyebrows on espresso | 6.72:1 | ✅ AAA |
| `--muted` | `--cream` | Body-secondary on cream | 5.55:1 | ✅ AAA |
| `--cream` | `--clay` | Primary CTA text (13px med) | 4.03:1 | ⚠️ AA Large/Bold only |
| `--clay` | `--cream` | Clay accent text | 4.03:1 | ⚠️ AA Large only |
| `--faint` | `--cream` | Captions, micro-labels | 3.15:1 | ⚠️ AA Large only |

> **The two ⚠️ pairs are the only watch items.** Both pass AA Large (3:1 minimum for ≥18pt or ≥14pt bold). The primary CTA uses cream-on-clay at 13px weight 500 — that's bold enough to meet AA Large. The eyebrow + caption tiers using `--clay` and `--faint` are sized 9-11px UPPERCASE which falls under "decorative micro-text" and is exempt from the strict body-text spec, but be aware that body-sized clay text on cream does not pass AA. **Never use `--clay` for paragraph copy.** Same for `--faint`.

Computed via WCAG 2.1 relative-luminance formula with the actual hex values from the palette. Re-run any time the palette changes — the script in `scripts/capture-wireframes.js` neighborhood is a single `node -e` block, see commit history for the formula.

**Rules:**
- **CTA color is `--clay`** — this is the only fill we use for the primary action button on light surfaces. Champagne is for dark-pane CTAs only.
- **Italic emphasis uses `--clay` on cream**, **`--champagne` on espresso**. Never both on the same surface.
- **Never tint shadows or borders with `--clay` or `--champagne`** — they'd push the design toward "warm and crafty." Tint shadows with `rgba(58,40,24,*)` (espresso at low alpha). Borders use the `--border` token below.
- **Never use a pure-black outline or shadow.** Even at low alpha, true black reads as dirt. We use the espresso tint instead.

### Hairline tokens (derived from palette)

| Token | Value | Use |
|---|---|---|
| `--border` | `rgba(58,40,24,0.10)` | Standard hairline divider on cream |
| `--border-strong` | `rgba(58,40,24,0.20)` | Form field borders, secondary buttons |
| `--ring-hair` | `0 0 0 1px rgba(58,40,24,0.06)` | Outer card "ring" beneath shadow stack |
| `--ring-clay` | `0 0 0 1px rgba(184,90,59,0.30)` | Selected/added card state ring |

On espresso surfaces, hairlines invert to `rgba(245,239,230,0.12)` (cream at low alpha).

---

## 4. Typography

Two families, both variable fonts via Google Fonts. Fraunces does headlines, hero italics, drop caps, and product names. Geist does everything else.

### Fraunces (serif, display + italic)

**Source.** [fonts.google.com/specimen/Fraunces](https://fonts.google.com/specimen/Fraunces)
**Axes used.** `opsz` 9–144 (auto), `wght` 300–500, `ital` 0/1
**Webflow variable.** `Type/Serif`

**Why this font.** Fraunces was designed for editorial display — the optical-size axis means headlines render with refined contrast at large sizes and stay legible at small. The italic has the right amount of swash without going florid. Weight 300 (the only weight we use for headlines) is light enough to feel airy on cream without disappearing.

**Where it appears.** All headings (h1–h4), product names, eyebrow accents in italic, drop caps, "[em]" emphasis spans inside paragraphs.

**Settings.** `font-feature-settings: "ss01" 1, "ss02" 1` enables stylistic alternates that read better at display sizes. `letter-spacing: -0.02em` on headings; tighter (`-0.025em`) at the largest hero size.

### Geist (sans, body)

**Source.** [fonts.google.com/specimen/Geist](https://fonts.google.com/specimen/Geist)
**Axes used.** `wght` 400, 500, 600
**Webflow variable.** `Type/Sans`

**Why this font.** Geist is Vercel's open-source neutral grotesque. Numerals are properly tabular at the variable level (no need for a separate mono). Reads cleanly at small sizes (the eyebrow + caption sizes do most of the work in this design). Doesn't have personality — which is correct, because Fraunces carries personality.

**Where it appears.** Body copy, captions, eyebrows, button labels, form labels, navigation.

**Settings.** Always paired with `font-variant-numeric: tabular-nums` for any numeric display (phone numbers, prices, counts, dates).

### Type scale

Every type level used in the site, with the exact spec.

| Level | Family | Size | Weight | Line-height | Letter-spacing | Notes |
|---|---|---|---|---|---|---|
| Hero h1 | Fraunces | clamp(48px, 6.4vw, 92px) | 300 | 0.96 | -0.02em | Italic `<em>` for emphasis phrase |
| h2 | Fraunces | clamp(28px, 3.4vw, 44px) | 300 | 1.05 | -0.025em | Section openers |
| h3 | Fraunces | 22–30px (ctx) | 300–400 | 1.05–1.15 | -0.01em to -0.02em | Card names, panel titles |
| h4 | Fraunces | 18–20px | 400 | 1.2 | -0.01em | Product names, list titles |
| Body | Geist | 16px | 400 | 1.6 | normal | Default body copy |
| Body-large | Geist | 18px | 400 | 1.55 | normal | Hero subhead |
| Body-small | Geist | 14.5px | 400–500 | 1.55 | normal | Form values, list items |
| Caption | Geist | 13px | 400 | 1.55 | normal | Card descriptions, helper text |
| Eyebrow | Geist | 11px | 500 | normal | 0.24em | UPPERCASE, leading hairline rule, clay color |
| Micro-eyebrow | Geist | 9–10px | 500 | normal | 0.18–0.22em | UPPERCASE labels (form labels, tags) |

**Heading rules.**
- `text-wrap: balance` on every heading — prevents ragged single-word last lines.
- `text-wrap: pretty` on every paragraph — improves orphan/widow handling.
- Italic `<em>` inside a heading is colored `--clay` on cream, `--champagne` on espresso. This is the brand's signature accent.
- Body italic is rare. If it appears, it's a real emphasis, not styling.
- Drop caps via `.dropcap::first-letter` — Fraunces italic at 3.4em line-height 0.85, color `--clay`. One per long-form section, no more.

**Numbers.**
- Always `tabular-nums` on dynamic counters (cart count, badge), prices/sizes, phone numbers.
- The trust-stat strip (`0+`, `500+`, `<1`) uses Fraunces 300 italic-em for the modifier and tabular-nums on the number.

---

## 5. Spacing and rhythm

**Base unit: 8px.** Every spacing value is a multiple.

### Section padding scale

| Tier | Value | Use |
|---|---|---|
| xs | 8px | Inside chips, tag pills |
| sm | 12–16px | Card internal, button padding |
| md | 22–24px | Card body padding, form field padding |
| lg | 32–36px | Card-to-card gap, section internal |
| xl | 56–64px | Section vertical padding (typical) |
| 2xl | 80–96px | Macro-whitespace between hero sections |
| 3xl | 128px | Reserved — only for highest-impact transitions |

**Container max-width.** `--max-w: 1440px`. Content centers within. Side padding tapers from 56px (desktop) to 32px (1180px breakpoint) to 20px (mobile).

**Vertical rhythm.** A page section pattern is: `eyebrow` (clay, 11px UPPERCASE with hairline) → 14–28px gap → `h2` → 10px gap → `sub` (muted, 14px) → 28–36px gap → content.

**Editorial luxury principle.** When in doubt, add more whitespace, not less. The trust strip on Home was originally 32px from the section above; we doubled it to 80px and the page improved.

---

## 6. Radii, hairlines, shadows

### Radius scale

| Value | Use |
|---|---|
| 8px | Hamburger button, small icon containers |
| 10px | Filter pills (active state badge), small chips |
| 12px | Form fields, content blocks, mid-size buttons |
| 14px | Quote chips, gallery images, my-event CTA |
| 16px | Inner card surface (Double-Bezel inner core) |
| 18px | Hero glass card, quote-panel inner |
| 22–24px | Outer card shell (Double-Bezel outer), modals, panels |
| 999px | All capsule buttons, badges, social icons, tag pills |

### Concentric border radius (load-bearing)

The Double-Bezel pattern is the most distinctive structural detail of the system. **Outer radius = inner radius + padding.**

```
┌─────────────────────────┐  ← .bezel-out: border-radius 22px
│  ┌─────────────────────┐│  ← 6px padding gap (the "bezel")
│  │                     ││  ← .bezel-in: border-radius 16px
│  │  inner core content ││    (22 - 6 = 16)
│  │                     ││
│  └─────────────────────┘│
└─────────────────────────┘
```

The math is non-negotiable. Any nested rounded surface must follow it.

Applied today on:
- Collection cards (outer 22 / pad 6 / inner 16)
- Filter sidebar card (outer 22 / pad 6 / inner 16)
- Quote wizard panel (outer 24 / pad 6 / inner 18)
- Help panel (outer 22 / no inner — single layer)

### Hairline dividers

Always 1px. Never 2px. We control the visual weight via opacity, not thickness.

| Token | Value | Where |
|---|---|---|
| `--border` | `rgba(58,40,24,0.10)` | Section dividers, form field borders, default hairlines |
| `--border-strong` | `rgba(58,40,24,0.20)` | Step-indicator circles (resting), social icon borders |
| Champagne hairline | linear-gradient (transparent → `rgba(201,176,138,0.55)` → transparent) | Top of nav bar — luxury packaging cue |
| Espresso pane hairline | `rgba(245,239,230,0.12)` | Dividers inside espresso (My Event submit pane, footer) |

### Shadow stack — multi-layer tinted, never pure black

Single-layer shadows look digital. We layer 2–3 transparent shadows tinted with the espresso color so they read as natural depth on cream.

| Token | Value | Use |
|---|---|---|
| `--shadow-card` | `0 1px 1px rgba(58,40,24,0.04), 0 4px 10px rgba(58,40,24,0.04), 0 14px 32px rgba(58,40,24,0.06)` | Card resting state |
| `--shadow-card-hover` | `0 1px 1px rgba(58,40,24,0.05), 0 8px 20px rgba(58,40,24,0.08), 0 28px 56px rgba(58,40,24,0.10)` | Card hover (lift) |
| `--shadow-pop` | `0 12px 28px rgba(58,40,24,0.14), 0 30px 80px rgba(35,24,15,0.14)` | Modal, success card |
| `--shadow-bubble` | `0 8px 24px rgba(58,40,24,0.18), 0 24px 48px rgba(58,40,24,0.10)` | Help bubble, toast |

**Inset highlights** (for the Double-Bezel inner core — simulates edge refraction):

| Token | Value | Use |
|---|---|---|
| `--inset-highlight` | `inset 0 1px 0 rgba(255,255,255,0.5)` | Cream/light surfaces — top edge sheen |
| `--inset-highlight-dark` | `inset 0 1px 0 rgba(255,255,255,0.08)` | Espresso/dark surfaces |

The inset highlight is what makes the Double-Bezel inner core feel like a separate piece of material. Don't omit it.

### Image outlines

Every photo gets `outline: 1px solid rgba(0,0,0,0.1); outline-offset: -1px;`. **Pure black at low opacity, never tinted neutral.** Tinted (slate, zinc, near-black) outlines pick up the surface color underneath them and read as dirt on the image edge.

Applied to:
- Collection card images
- My Event item thumbnails
- Gallery images
- Hero background image (composited with the gradient overlay)
- Featured tents bento images

---

## 7. Component library

Every component is rendered in its actual visual form in [`design-system.html`](design-system.html). This section captures the rules and behavior.

### Buttons

Four variants. All are `border-radius: 999px` (capsule).

#### Primary — `.btn-primary`
Clay fill (`--clay`), cream text, "Button-in-Button" trailing icon (28×28 nested circle with arrow, alpha-tinted background). On hover: clay-dark fill, lift 1px, shadow deepens, **inner icon translates `(2px, -1px)`** — the trailing-arrow micro-interaction. On press: scale-0.96.

Use this for the single primary action on any view (Request a quote, Send my inquiry, etc.).

#### Ghost — `.btn-ghost`
Transparent + 1px navy border, navy text. Hover: navy fill, cream text. Press: scale-0.96.

Use for secondary actions on cream surfaces (Browse the collection, Cancel, Back).

#### Ghost-light — `.btn-ghost-light`
Transparent + 1px cream-with-alpha border, cream text. For dark backgrounds (hero CTAs over the bokeh photo, espresso pane).

#### Champagne — `.btn-champagne`
Champagne fill, navy text, with the same Button-in-Button trailing icon pattern. Used exclusively on espresso surfaces (My Event "Send my inquiry" CTA).

**All buttons obey:**
- `scale(0.96)` on `:active` — never below 0.95
- Specified `transition` properties (background, transform, box-shadow) — **never `transition: all`**
- Minimum 40×40px hit area; if visible size is smaller, extend with a `::after` pseudo-element

### Form fields

Two variants — light pane and dark pane.

**Light-pane field** (`.me-field`, `.quote-field`)
- Cream surface, 1px `--border` outline, 12px radius
- Top label: 9px UPPERCASE 0.22em, color `--faint`
- Input value: 14.5px, weight 500, color `--ink`
- `:focus-within` → border becomes `--clay`, adds a 3px glow `rgba(184,90,59,0.10)` ring

**Dark-pane field** (`.me-contact-field`)
- Espresso surface (`rgba(245,239,230,0.06)` over navy), 1px `rgba(245,239,230,0.14)` border
- Label: same 9px UPPERCASE but champagne
- Input: cream text, champagne placeholder at 0.35 alpha
- `:focus-within` → border becomes `--champagne`, surface lightens to 0.10

**Both:**
- `min-width: 0` on the cell — critical for mobile (prevents browser-default input min-content overflow)
- Padding 11–14px / 14–18px
- Label-on-top layout, never floating-label

### Cards

#### Collection card (Double-Bezel showcase)

Outer `.bezel-out` 22px radius / 6px padding / inner `.bezel-in` 16px radius. Photo (4:3 aspect) at top with image outline + tag pill (cream fill, clay text, 9px UPPERCASE, no backdrop blur — pure cream is intentional; blur was profiled too expensive in lists of 9+ items). Name (Fraunces 22px), hairline-divided spec row (sizes / guests, with clay micro-icons), description (caption), "Add to my event" CTA at the bottom.

**Add CTA states:**
- Resting: ghost-style with clay border + clay text, "Add to my event" + arrow
- Hover: clay fill, cream text, shadow lift
- Pressed: scale-0.96
- Added: navy fill, cream text, navy border, **arrow cross-fades to checkmark** via grid-stack (both icons in DOM, opacity+scale+blur cross-fade in cubic-bezier(0.2, 0, 0, 1))

The arrow→check cross-fade is the system's most polished micro-interaction. Both icons stay in the DOM (`display: grid; place-items: center`) so we get true enter+exit animations.

#### Featured bento card (Home)

Three cards: one wide hero card + two stacked. Hero card has full-bleed photo with bottom-up navy gradient + Fraunces title overlay. Stack cards have side-by-side photo+text (60/40 split). All three lift 2px on hover with the shadow-hover stack.

#### Item row (My Event)

Hairline-divided rows inside a single rounded container — **no card-on-card stacking on the items list**. 76×64px image thumbnail + name/tag column + remove button column. Clean and editorial; the dark pane on the right does the visual heavy lifting.

### Filter pill

Two states.

**Resting.** Padding 10×14, 10px radius, ink text, `font-size: 13px`. Hover: cream-2 background, +2px left padding.
**Active.** Navy fill, cream text, inset highlight. Counter on the right shows the matching item count.

### Step indicator (Quote wizard)

4 numbered circles connected by a horizontal hairline. Resting: 32px, cream fill, border-strong, muted text. Active: navy fill + 6px alpha-tinted halo + scale 1.08. Done: clay fill (so completed steps are visibly different from current).

The hairline runs *behind* the circles (z-index 0). When animating between steps, only the current step's circle scales and recolors — the line and other circles stay still.

### Glass card (Hero quote)

`background: rgba(245,239,230,0.06)` + `backdrop-filter: blur(14px) saturate(140%)`. 1px alpha border, 18px radius, inset highlight on top edge. Inside: italic Fraunces quote text, 5-star row in champagne, attribution in eyebrow micro-caption.

**Safari note.** `backdrop-filter` requires the `-webkit-backdrop-filter` prefix. Both are set; verify Safari renders the blur (older versions fall back to no-blur, which still reads as a tinted glass card).

### Help bubble + panel

56×56 navy circle, fixed bottom-right, with a 9×9 champagne dot indicator at top-right. Click expands to a 380×540 panel with a navy header (title + sub) and scrollable Q&A body. Used today as static FAQ; designed to hold an AI chat in a future iteration without UI change.

### Toast

Pill-shaped (999 radius), navy fill with `--shadow-bubble`, fixed bottom-center, fades in from below. Carries a small champagne checkmark icon and a label. Auto-dismisses after 2s.

### Mobile menu

Slide-in from right. 86% width (max 360px), cream surface, `-10px 0 60px` shadow. Items are big serif (28px Fraunces 300), hairline-divided, with `→` arrow that translates 4px on hover/active. A divider separates nav from the contact block (phone, email, location).

**Architectural note (load-bearing).** The mobile menu is moved from `nav_bar` to `document.body` via JS on load — `position: fixed` inside `position: sticky` clips to the sticky element's height in Chrome. Keep the `body.appendChild` line.

---

## 8. Motion

**Library.** [Motion One](https://motion.dev/) loaded via CDN (`@10.18.0`, UMD build) — same animation API as Framer Motion, vanilla JS, ~12KB. Webflow port: register as a footer custom code script.

**Why Motion One.** Spring physics, scroll-triggered reveals (`inView`), and gestures, with the smallest payload that still gives us proper springs. No competing CSS animation should fire on the same property at the same time — that's where the iOS WebKit reverse-direction wobble came from in the early v7 builds.

### Easing curves

| Token | Value | Use |
|---|---|---|
| `--ease` | `cubic-bezier(0.32, 0.72, 0, 1)` | Default — buttons, hovers, state changes (interruptible) |
| `--ease-out` | `cubic-bezier(0.16, 1, 0.3, 1)` | Reveals, image zooms, view transitions (single-shot) |
| `--ease-in` | `cubic-bezier(0.4, 0, 1, 1)` | Exits (rare — most exits use the default ease) |
| Motion One spring | type:"spring", stiffness:200, damping:24 | Stagger items on scroll-in, panel slide-in |

### Standard durations

| Range | Use |
|---|---|
| 0.2s | Color/border transitions on hover |
| 0.25s | Most interactive state — buttons, hovers, focus rings |
| 0.30s | Cross-fades, icon swaps (with cubic-bezier(0.2, 0, 0, 1)) |
| 0.45s | View transitions, mobile menu slide |
| 0.6–0.95s | Reveals on scroll-in, image zooms |
| 0.9s | Hover image zoom (slow + smooth) |

### Motion principles

1. **Split + stagger entrances.** Don't animate a single container — break into semantic chunks (eyebrow, headline, sub, CTAs) and stagger ~100ms between each.
2. **Subtle exits.** Use a small fixed `translateY(8px)` instead of full element height. Exits should be softer than entrances.
3. **Interruptible CSS transitions for state, keyframes only for one-shot sequences.** Hover → focus → press should be smoothly interruptible. Use keyframes only for a one-time scroll-cue bounce or grain animation.
4. **Specify properties — never `transition: all`.** Always list the exact properties (`transition: background 0.25s var(--ease), transform 0.25s var(--ease)`).
5. **Honor `prefers-reduced-motion`.** All animations and transitions get reduced to 0.01ms; `[data-reveal]` and `[data-stagger-item]` get forced to opacity:1 transform:none.
6. **`will-change` only on `transform`, `opacity`, `filter`.** Never `will-change: all`. Only add when there's first-frame stutter.

### Specific motion patterns in the system

| Pattern | Description |
|---|---|
| Hero stagger | Hero content children fade up + blur-out with 100ms delays via `data-stagger`/`data-stagger-item` |
| Scroll reveal | Sections marked `data-reveal` fade up + blur-out when 20% in view |
| Carousel cross-fade | Reviews + hero quotes use grid-stack — both slides occupy same cell, opacity + translateY drives the swap |
| Hover image zoom | `transform: scale(1.04)` over 0.9s ease-out on photo cards |
| Add → check icon swap | grid-stacked SVGs cross-fade with opacity + scale (0.25→1) + blur (4px→0) on `.added` class |
| Step indicator advance | Active circle scales 1.08 with 6px halo; completed circle goes clay |
| Tab switch | Old view fades + translateY(8px) out; new view fades in; 0.45s --ease |

### Things motion should NOT do

- No magnetic-pull on CTAs (tested in earlier v7, removed — too aggressive).
- No parallax on the hero photo (clean lift only, scaled 1.06→1.03 over 8s).
- No tilt on collection card images beyond 4° (that's already at the edge).
- No bouncy springs on UI state changes (only on stagger reveals where it reads as confidence, not playfulness).
- No animations that block scroll (e.g. forced full-screen transitions on tab change).

---

## 9. Page wireframes

Five views captured at desktop (1440×900) and mobile (390×844 @ 2x). All wireframes live in [`design/wireframes/`](design/wireframes/).

### Home — `design/wireframes/home-{desktop,mobile}.png`

**Sections, top to bottom:**

1. **Hero** — full-bleed bokeh-blurred wedding photo (`photos/gallery/WeddingDay-363.jpg`), espresso gradient overlay. Hero content top-left: micro-trust row → headline → sub → CTA pair (primary + ghost-light). Glass quote card bottom-right with rotating reviews. Scroll cue at bottom-center, fades on scroll.
2. **Trust stats strip** — 4 numerical stats (`0+`, `0`, `500+`, `<1`) each with eyebrow caption. Hairline divider above. Macro-whitespace (80px top padding).
3. **Featured tents bento** — eyebrow "THE COLLECTION" → h2 "Three structures, every shape of celebration." → asymmetric grid: 1 wide hero card (Marquee) + 2 stacked cards (Frame, Clear-span). "See all 9 items →" link top-right.
4. **Reviews carousel** — eyebrow "REAL CUSTOMERS · 5-STAR REVIEWS" → centered single rotating editorial quote (Fraunces italic), large `"` mark, 5-star row, attribution. Dots below for manual control.
5. **Contact strip** — phone / email / location row, social icons. Hairline divider above.

**Mobile changes:** hero glass card hidden; bento collapses to single column; trust strip becomes 2×2 grid; everything stacks. Hero photo stays full-bleed, padding tightens.

### Collection — `design/wireframes/collection-{desktop,mobile}.png`

**Two-column layout** at desktop: 280px filter sidebar (Double-Bezel card with category pills) + 1fr content area.

**Sidebar:** "CATEGORIES" eyebrow → 6 filter pills (All collection, Tents, Furniture, Flooring & stages, Atmosphere, Climate) with item counts on the right. "Clear filters" ghost button at the bottom.

**Content:** eyebrow → h2 "Everything we bring, in one place." → sub → 3-column grid of Double-Bezel product cards. Each card: photo + tag + name + spec row + description + Add CTA. 9 cards total at launch.

**Mobile:** filter collapses to a horizontal pill row above the grid (single Double-Bezel card spanning full width). Grid becomes 1 column.

### My Event — `design/wireframes/my-event-{desktop,mobile}.png`

**Two-pane layout** at desktop: 1.25fr items pane (cream gradient bg) + 1fr submit pane (espresso).

**Items pane:** eyebrow "YOUR INQUIRY" → h2 "Tell us about your event." → sub → numbered section "1. WHAT YOU NEED" with item rows or empty state. Empty state: clay tent icon + "Nothing added yet" + sub + "Browse the collection" primary CTA. Below items: dashed "+ Add more from the catalog". Then "2. EVENT BASICS" section with date, guest count, location, event type fields in a 2-column grid. Then "3. ANYTHING ELSE" textarea.

**Submit pane (espresso):** big serif "Your quote" → "REPLY WITHIN 1 BUSINESS DAY" tag. Three labeled summary blocks: "ITEMS YOU PICKED" (list of cart items), "DATE & EVENT BASICS", "YOUR CONTACT" (2-col fields for name, email). Champagne CTA "Send my inquiry" with trailing icon. Footer note: "We respond within 1 business day." Subtle radial-light pseudo at top for warmth.

**Mobile:** stacks to single column — items pane on top, submit pane below.

### Gallery — `design/wireframes/gallery-{desktop,mobile}.png`

Eyebrow + h2 + sub at top.

**Grid:** 4-column masonry-style grid using CSS grid with `.wide` (span 2) and `.tall` (row span 2) modifiers. Each image gets a 14px radius, image outline, hover zoom (1.05) + brightness (1.04). Click → full-screen lightbox with espresso backdrop blur.

**Mobile:** 2-column grid, smaller row height (150px), `.tall` reduced to 1 row.

### Request a Quote — `design/wireframes/quote-{desktop,mobile}.png`

Centered 920px column. Eyebrow + h2 + sub at top. Step indicator (4 numbered circles) below.

**Single Double-Bezel quote panel** with:
- Step header: "STEP 1 of 4" eyebrow + "What kind of event are we tenting?" h3
- Step body content (varies per step):
  - Step 1: 3×2 chip grid of event types (Wedding, Corporate, Private, Fundraiser, Film & TV, Something else)
  - Step 2: date + guest count + location (form fields)
  - Step 3: tent / furniture / atmosphere preferences
  - Step 4: contact + final notes
- Step nav: "Back" ghost (left) + meta caption ("Step 1 of 4 · About 5 minutes total") + "Continue" primary (right). Hairline above.

**Mobile:** chips → single column, fields stack, step indicator becomes circles-only (labels hidden).

### Cross-page chrome (consistent on every view)

- **Mockup-bar** (top, espresso) — the dev banner. Removed in production.
- **Nav** (sticky, cream with backdrop-blur) — logo + tabs + phone + primary CTA. Champagne hairline at the top edge.
- **Mobile nav** — logo + hamburger only. Tabs hidden, phone in mobile menu.
- **Help bubble** (bottom-right, fixed) — present on all views.

---

## 10. Mobile breakpoint

**Single breakpoint at 860px.** Below this, the site converts to a horizontal swipe-snap navigation pattern.

### What changes at ≤860px

- **Nav:** tabs hidden, hamburger replaces them, phone hidden (in mobile menu), primary CTA becomes the "nav-cta-mobile" smaller variant.
- **Body layout:** `.app-main` becomes a horizontal-scroll snap container (`scroll-snap-type: x mandatory`), each `.view` becomes `flex: 0 0 100vw` and `scroll-snap-align: start`. Tab tap → JS `scrollIntoView`. Swipe between views.
- **All grid layouts → single column.** Featured bento, collection grid, gallery grid all stack.
- **Hero quote card hidden** to keep hero focused.
- **Form 2-column grids stay 2-column on mobile** but with `min-width: 0` on cells to prevent overflow.
- **Filter sidebar in Collection** — collapses to a horizontal pill row above the grid.
- **My Event** — stacks panes vertically.

### iOS-specific fixes (load-bearing — don't remove)

- `100dvh` everywhere instead of `100vh` — Safari address bar shrinks dynamic viewport, not large viewport.
- `safe-area-inset-top` and `safe-area-inset-bottom` paddings on `.app` for notch/island/home-indicator clearance.
- `viewport-fit=cover` in the `<meta viewport>` to enable safe-area-inset.
- 1px scroll trick on body (`min-height: calc(100% + 1px)` + `scrollTo(0, 1)` on init) — makes Safari think the page is scrolling, collapses the bottom toolbar.
- `overscroll-behavior: contain` on `.app-main` to stop iOS edge-swipe-back gesture.
- `touch-action: pan-x pan-y` on `.app-main` to explicitly allow both axes (iOS bug with default `auto` inside `overflow: hidden` parents).

### Smaller breakpoint at ≤380px

Only fontsize tweaks for the mockup-bar and the nav CTA — most layouts already work at this size.

---

## 11. Webflow Variable mapping

Use this table when populating Webflow Site Settings → Variables. Group names follow Webflow's recommended structure (`Color/`, `Type/`, `Size/`, `Radius/`, `Shadow/`).

> **Naming convention.** Webflow Variables follow a `Group / Name` path style with spaces around the slash. Compound names use spaces (`Cream 2`, `Espresso Deep`), never hyphens. The CSS custom property is `--navy` for historical reasons but the Webflow Variable is `Color / Espresso` to match the brand language used everywhere else in the design.

### Colors

| Token (CSS) | Webflow Variable | Value |
|---|---|---|
| `--cream` | `Color / Cream` | `#F5EFE6` |
| `--cream-2` | `Color / Cream 2` | `#EDE4D2` |
| `--cream-3` | `Color / Cream 3` | `#E3D8C1` |
| `--navy` | `Color / Espresso` | `#3A2818` |
| `--navy-deep` | `Color / Espresso Deep` | `#23180F` |
| `--navy-soft` | `Color / Espresso Soft` | `#4E3828` |
| `--clay` | `Color / Clay` | `#B85A3B` |
| `--clay-dark` | `Color / Clay Dark` | `#964730` |
| `--champagne` | `Color / Champagne` | `#C9B08A` |
| `--champagne-light` | `Color / Champagne Light` | `#E0CFAF` |
| `--ink` | `Color / Ink` | `#2A1F18` |
| `--muted` | `Color / Muted` | `#6B5D52` |
| `--faint` | `Color / Faint` | `#928574` |
| `--border` | `Color / Border` | `rgba(58,40,24,0.10)` |
| `--border-strong` | `Color / Border Strong` | `rgba(58,40,24,0.20)` |

### Typography

| Token | Webflow Variable | Value |
|---|---|---|
| `--serif` | `Type / Serif` | Fraunces |
| `--sans` | `Type / Sans` | Geist |
| h1 | `Type / Size / H1` | `clamp(48px, 6.4vw, 92px)` |
| h2 | `Type / Size / H2` | `clamp(28px, 3.4vw, 44px)` |
| h3 | `Type / Size / H3` | `22px–28px (context)` |
| Body | `Type / Size / Body` | `16px` |
| Body small | `Type / Size / Body Small` | `14.5px` |
| Caption | `Type / Size / Caption` | `13px` |
| Eyebrow | `Type / Size / Eyebrow` | `11px` |

### Spacing

| Token | Webflow Variable | Value |
|---|---|---|
| Section xl pad | `Space / XL` | `80–96px` |
| Section pad | `Space / LG` | `56–64px` |
| Stack pad | `Space / MD` | `32px` |
| Card pad | `Space / SM` | `22–24px` |
| Field pad | `Space / XS` | `13–16px` |
| Max width | `Layout / Max` | `1440px` |
| Mobile breakpoint | (inherent in Webflow) | `860px` |

### Radii

| Token | Webflow Variable | Value |
|---|---|---|
| Capsule | `Radius / Pill` | `999px` |
| Outer card | `Radius / Outer` | `22px` |
| Inner card | `Radius / Inner` | `16px` |
| Field | `Radius / MD` | `12px` |
| Quote panel inner | `Radius / Panel Inner` | `18px` |

### Shadows

| Token | Webflow Variable | Value |
|---|---|---|
| `--ring-hair` | `Shadow / Ring Hair` | `0 0 0 1px rgba(58,40,24,0.06)` |
| `--shadow-card` | `Shadow / Card` | (multi-layer; see §6) |
| `--shadow-card-hover` | `Shadow / Card Hover` | (multi-layer; see §6) |
| `--shadow-pop` | `Shadow / Pop` | (multi-layer; see §6) |
| `--shadow-bubble` | `Shadow / Bubble` | (multi-layer; see §6) |
| `--inset-highlight` | `Shadow / Inset Highlight` | `inset 0 1px 0 rgba(255,255,255,0.5)` |
| `--inset-highlight-dark` | `Shadow / Inset Highlight Dark` | `inset 0 1px 0 rgba(255,255,255,0.08)` |

### Motion / Easing

Webflow's interaction panel doesn't accept variables for easing curves directly, but the values should be standardized. Register them as Variables anyway so they're documented in one place and available for any custom-code interactions.

| Token | Webflow Variable | Value |
|---|---|---|
| `--ease` | `Motion / Ease` | `cubic-bezier(0.32, 0.72, 0, 1)` |
| `--ease-out` | `Motion / Ease Out` | `cubic-bezier(0.16, 1, 0.3, 1)` |
| `--ease-in` | `Motion / Ease In` | `cubic-bezier(0.4, 0, 1, 1)` |

---

## 12. Photography and content direction

Mike (the client) edits photos through the Webflow Editor post-launch. These guidelines keep the site visually consistent as he swaps content.

### Hero photography

- **Format:** landscape, 16:9 minimum, 1920×1080 minimum resolution. Compressed to <250KB at WebP/AVIF.
- **Subject:** real Elite Tents events. Wedding receptions inside or under tents, golden-hour exteriors, sunset clear-spans. Avoid empty-tent setup shots for the hero.
- **Mood:** warm, inhabited, slightly-blurred. The hero applies a 5px blur for a bokeh effect, so source images don't need to be tack-sharp — they need composition and warmth.
- **What to avoid:** harsh midday sun, vehicles or staff in frame, brand competitors' tents, low-light underexposed shots, heavy color casts (especially blue-shifted).

### Featured + collection cards

- **Format:** 4:3 (1200×900 typical). Compressed to <100KB.
- **Subject:** the product itself, well-lit, ideally in context (a tent in a real venue, not on a parking lot). Furniture shots should be product-isolated on a neutral background.
- **Frame:** include some context but center the subject. Cards crop to fit; subjects need to survive a center-weighted square crop.
- **Tone:** match the cream/warm palette where possible. Cool/blue-shifted images clash with the page background.

### Gallery photography

- **Format:** mixed, but all should be high-quality (≥1200px on the long edge), <150KB.
- **Subject:** real events, ideally a mix of: setup details (rope, beam, lighting close-ups), wide event shots, dusk/evening with tent lighting, alternative tent types (so the gallery sells variety, not just marquees).

### Copy guidelines (for Mike)

- **Headlines** — Fraunces 300 italic for the emphasis phrase, regular for the rest. Italic part should be a 2–4 word emotional fragment. Examples: "*quiet moments*, not just the loud ones", "Three structures, every *shape* of celebration", "Five fields. *One business day* to first reply."
- **Body copy** — present-tense, specific, never gushing. Use real numbers (capacity, sizes, lead times) in place of adjectives ("seats 80 to 250" is better than "spacious").
- **CTAs** — verbs, not adjectives. "Request a quote", "Browse the collection", "Send my inquiry". Never "Click here" or "Learn more".
- **Eyebrows** — 2–4 words UPPERCASE. Sets context for the section: "REAL CUSTOMERS · 5-STAR REVIEWS", "SINCE 2011", "FROM REAL EVENTS".

### What's off-brand

- All-caps body copy or stacked CAPS headlines
- Multiple competing fonts beyond Fraunces + Geist
- Saturated full-bleed color blocks (e.g. red callouts, yellow highlights)
- Stock photography that's obviously not Elite Tents
- Animated GIFs, video that auto-loops with sound
- Pop-up overlays gating content
- Cookie banners, chat widgets, or other dark-pattern UX (other than the help bubble, which is opt-in only)

---

## 13. What we deliberately don't do

A premium agency-tier rule of thumb: name what you're avoiding. Both ‟high-end visual design" and ‟design-taste" frameworks treat absence as a design choice. These are the patterns we've evaluated and rejected for Elite Tents specifically — write them down so they don't accidentally sneak back in during the Webflow build or future content updates.

### Layout and chrome

| Pattern | Why we're not using it |
|---|---|
| Floating "fluid island" nav (detached pill, mt-6, w-max) | Wedding clients expect a stable, full-width nav with the logo at the top-left. Floating nav reads as a tech/SaaS signal, not an event-rental one. |
| Sticky CTA banner that follows scroll | Pushy, breaks editorial cadence. Our help bubble + sticky phone bar (mobile) cover the equivalent function without intrusion. |
| Hero section sized with `h-screen` | Catastrophic layout jump on iOS Safari. We use `min-h-[100dvh]` (or fixed `min-height: 560px` for the desktop hero). |
| Centered hero h1 over background image | Banned at our layout-variance level. We use Editorial Split — content left, glass quote right. |
| 3 equal-width feature cards in a row on Home | Generic AI-template signal. Home featured tents uses asymmetric bento (1 wide + 2 stacked). The 3-column Collection grid is a *product catalog*, which is the correct context for an equal grid. |
| Z-Axis Cascade with rotated cards | Too playful for the wedding context. Our Double-Bezel sits flat, perfectly aligned. |

### Motion

| Pattern | Why we're not using it |
|---|---|
| Magnetic-pull buttons (cursor-tracking transform) | Tested and rejected by Arlo earlier in v7. Reads as "trying too hard" on a service-business site. We kept the button-in-button trailing icon translate on hover (`(2px, -1px)`) which gives motion without invasion. |
| Particle explosions, ripple click effects | Wrong tone. Our success state is a quiet modal with a Fraunces headline and a check-in-circle icon. |
| Liquid swipe page transitions | Mobile uses scroll-snap horizontal navigation between views (clean 0.45s slide). No goo. |
| Continuous infinite "perpetual motion" elements (lava-lamp gradient, kinetic marquees, perpetually shimmering metrics) | Wedding-tent rentals are a slow-decision purchase. Restless backgrounds raise cognitive load. Our trust strip is static; the only timed motion is the 7s testimonial carousel + 8s hero photo zoom. |
| Animated cursors / hover-image-trails | Banned outright — outdated, breaks accessibility, performance regression on touch. |
| Scroll-hijack horizontal galleries on desktop | Confuses desktop users expecting standard vertical scroll. Mobile horizontal swipe between *views* is fine because it's the navigation primitive, not a content gimmick. |
| Text-fill gradients on large headlines | Reads as 2018 SaaS landing page. Our headlines use Fraunces 300 with a single italic clay accent — that's the entire decoration. |

### Color and texture

| Pattern | Why we're not using it |
|---|---|
| Pure `#000000` for any role | We use `--navy` (`#3A2818`) for body text and `--navy-deep` (`#23180F`) for the deepest darks. Pure black on a cream surface reads cold and digital. |
| Purple/blue "AI accent glow" gradients | Banned per design-taste rules and tonally wrong. Our only accent is `--clay` (`#B85A3B`). |
| Vantablack cards with backdrop-blur-2xl | Wrong vibe archetype — that's Ethereal Glass / SaaS / AI. We're Editorial Luxury / Lifestyle. |
| Tinted neutral image outlines (slate-200, zinc-300, near-black) | Picks up surface color, reads as dirt on the image edge. We use `rgba(0,0,0,0.1)` — pure black at low opacity, the only correct value. |
| Single-layer harsh drop shadows (`shadow-md`, `0 2px 4px black`) | Looks digital. Our shadows are 3-layer multi-tinted with espresso (`rgba(58,40,24,*)`). |
| Saturated full-bleed color blocks (red callouts, yellow highlights) | Off-brand. Color hierarchy is cream → cream-2 → cream-3 → espresso, with clay used only for italic emphasis and CTA fills. |

### Typography

| Pattern | Why we're not using it |
|---|---|
| Inter, Roboto, Open Sans, Helvetica | All banned per design-taste rules. We use Geist (sans) + Fraunces (serif). |
| Serif font on a SaaS-style dashboard | We are explicitly editorial — Fraunces is correct here. The rule applies to dashboards, which we're not building. |
| Oversized H1 (>92px) | The hero clamp tops out at 92px. Larger feels like a SaaS unicorn pitch. |
| All-caps body copy or stacked all-caps headlines | Off-brand. UPPERCASE is reserved for eyebrow + micro-caption tier (9-11px with 0.18-0.24em letter-spacing). |
| Multiple competing fonts beyond Fraunces + Geist | Hard limit. Two families, full stop. |

### Content and copy

| Pattern | Why we're not using it |
|---|---|
| Generic placeholder names ("John Doe", "Sarah Chan") | All testimonials use real customer names from the existing client roster (see `testimonials.json`). |
| Filler verbs ("Elevate", "Unleash", "Seamless", "Next-Gen") | Feels manufactured. Our copy uses concrete language ("seats 80 to 250", "engineered clear-spans", "since 2011"). |
| Fake exact-99% statistics ("99.99% uptime", "100% satisfaction") | We use organic numbers from the actual business: 47 reviews, since 2011, "<1 business day to first reply." |
| Generic "Click here" or "Learn more" CTAs | Verbs only: "Request a quote", "Browse the collection", "Send my inquiry". |
| Emoji as decoration anywhere | Hard banned. We use Lucide line icons + Phosphor-style hairline strokes. |

### What we *did* keep from these frameworks

- ✅ Double-Bezel (concentric outer/inner radii) on every nested surface
- ✅ Button-in-Button trailing-icon CTAs
- ✅ Macro-whitespace (80–96px between major sections)
- ✅ Eyebrow tag pattern preceding every section heading
- ✅ Multi-layer tinted shadows tied to background hue
- ✅ Custom cubic-bezier easing curves (no `linear`, no `ease-in-out`)
- ✅ GPU-only animations (transform + opacity exclusively)
- ✅ Image outlines at `rgba(0,0,0,0.1)` with `outline-offset: -1px`
- ✅ Backdrop-blur restricted to fixed elements (nav + glass card only)
- ✅ Grain overlay as fixed `pointer-events: none` pseudo at z-index 1100
- ✅ Tabular-nums on every dynamic numeric display
- ✅ Asymmetric Editorial Split hero (content left, glass card right)
- ✅ Asymmetric featured bento (1 wide + 2 stacked, never 3 equal)
- ✅ `min-h-[100dvh]` + `safe-area-inset` for mobile viewport stability

---

## Document maintenance

This document and `mockup.html` are the two canonical sources of truth. When they disagree:

1. **First check the live site at `elite-tents.webflow.io`.** If it has shipped, the live site is canon.
2. **If not yet shipped,** this document wins. Update the mockup to match.
3. **If both pre-shipping documents disagree,** flag it in the next session — don't silently pick one.

The `design-system.html` companion is a rendered specimen — it can drift from this doc during iteration, but should be regenerated when major decisions change.

When the site ships and Mike takes over content, this document goes from "agreement before build" to "guard rails for future content." Mike won't read it; Arlo (or whoever builds future updates) will.

— end of document —
