/* ============================================================================
   SANTHOM PAY — DESIGN SYSTEM
   Syro-Malabar Diocese of Melbourne
   ============================================================================

   A single stylesheet, organised as: tokens → base → primitives → components
   → sections → motion → responsive → overrides. Every page draws from the same
   component vocabulary, so a new page inherits the system by using the classes
   rather than by inventing styles.

   Three constraints shaped the system, and each one is load-bearing:

   1. ZERO EXTERNAL REQUESTS. No CDN, no web fonts, no fetched images. This keeps
      the app fast on parish-hall wi-fi, keeps a strict CSP achievable, and means
      the only third-party script in the entire product is Stripe's card iframe.
      Type is therefore a curated system stack; illustration is inline SVG.

   2. WCAG AA THROUGHOUT. Body text sits at 16px+ with a ~1.65 line height, and
      every foreground/background pair below was contrast-checked. Status is never
      communicated by colour alone — badges always carry a word.

   3. MOTION IS INFORMATION. Animation explains change: this is interactive, this
      just loaded, this arrived. Nothing bounces, spins, or auto-plays without a
      pause affordance, and the whole layer collapses under prefers-reduced-motion.

   ========================================================================= */

/* ============================================================================
   1. TOKENS
   ========================================================================= */

:root {
  color-scheme: light;

  /* ── Spacing — strict 8px system, with a 4px half-step for optical fixes ── */
  --s-1: 0.25rem;   /*  4px */
  --s-2: 0.5rem;    /*  8px */
  --s-3: 0.75rem;   /* 12px */
  --s-4: 1rem;      /* 16px */
  --s-5: 1.5rem;    /* 24px */
  --s-6: 2rem;      /* 32px */
  --s-7: 2.5rem;    /* 40px */
  --s-8: 3rem;      /* 48px */
  --s-9: 4rem;      /* 64px */
  --s-10: 5rem;     /* 80px */
  --s-11: 6rem;     /* 96px */
  --s-12: 8rem;     /* 128px */

  /* ── Radii ───────────────────────────────────────────────────────────── */
  --r-xs: 6px;
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-xl: 20px;
  --r-2xl: 28px;
  --r-full: 999px;

  /* ── Neutrals — warm, not clinical. A faith community platform should not
        feel like a bank terminal, and pure greys read cold on screen. ────── */
  --ink-900: #14100f;
  --ink-800: #241d1b;
  --ink-700: #3a322e;
  --ink-600: #554b45;
  --ink-500: #6b615b;
  --ink-400: #8c817a;
  --ink-300: #a89d95;

  --surface-0: #ffffff;
  --surface-1: #faf8f6;
  --surface-2: #f4f0ec;
  --surface-3: #ebe5df;

  --border: #e7e1db;
  --border-strong: #d5ccc4;

  /* ── Brand — deep garnet. Rooted in liturgical red but desaturated and
        darkened until it reads as considered rather than loud. ──────────── */
  --brand-050: #fdf6f7;
  --brand-100: #f7e9ec;
  --brand-200: #edd0d7;
  --brand-400: #b04d63;
  --brand-500: #97354b;
  --brand-600: #7a2438;
  --brand-700: #5e1a2a;
  --brand-800: #45121f;

  /* ── Accent — brass, from the standing lamp. Used for emphasis, never
        for large fills; gold at scale looks cheap. ──────────────────────── */
  --gold-100: #f9f0df;
  --gold-200: #f0dfba;
  --gold-500: #c9973f;
  --gold-600: #a87a2c;
  --gold-700: #85601f;

  /* ── Semantic ────────────────────────────────────────────────────────── */
  --ok-100: #e4f0e9;
  --ok-600: #2c6b4c;
  --ok-700: #1f5138;

  --warn-100: #fbf0da;
  --warn-600: #8a5c11;
  --warn-700: #6b470c;

  --danger-100: #fbe9e7;
  --danger-600: #9c2f26;
  --danger-700: #7a231c;

  --info-100: #e6eff7;
  --info-600: #2a5578;
  --info-700: #1f4058;

  /* ── Elevation — layered, soft, warm-tinted. A single hard shadow always
        reads cheap; two stacked with different spreads reads considered. ── */
  --shadow-xs: 0 1px 2px rgba(20, 16, 15, 0.04);
  --shadow-sm: 0 1px 2px rgba(20, 16, 15, 0.04), 0 2px 6px -2px rgba(20, 16, 15, 0.06);
  --shadow-md: 0 2px 4px rgba(20, 16, 15, 0.04), 0 8px 20px -6px rgba(20, 16, 15, 0.10);
  --shadow-lg: 0 4px 8px rgba(20, 16, 15, 0.04), 0 18px 40px -12px rgba(20, 16, 15, 0.16);
  --shadow-xl: 0 8px 16px rgba(20, 16, 15, 0.05), 0 32px 64px -20px rgba(20, 16, 15, 0.22);
  --shadow-brand: 0 8px 24px -10px rgba(122, 36, 56, 0.5);

  --ring: 0 0 0 3px rgba(122, 36, 56, 0.22);

  /* ── Typography ──────────────────────────────────────────────────────────
        A serif display face against a sans interface face. The pairing is the
        point: a geometric-sans-everywhere page is the default look of every
        SaaS product, and a serif headline is what makes this read as an
        institution rather than a startup. Both are system stacks — see the
        zero-external-request constraint at the top of this file. ─────────── */
  --font-display: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua",
                  "Hoefler Text", Georgia, "Times New Roman", serif;
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI",
               system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;

  /* Fluid type scale. clamp() means no breakpoint jumps in headline sizes. */
  --t-xs: 0.75rem;
  --t-sm: 0.875rem;
  --t-base: 1rem;
  --t-md: 1.0625rem;
  --t-lg: 1.1875rem;
  --t-xl: clamp(1.25rem, 1.15rem + 0.4vw, 1.4rem);
  --t-2xl: clamp(1.5rem, 1.3rem + 0.9vw, 1.9rem);
  --t-3xl: clamp(1.85rem, 1.5rem + 1.6vw, 2.6rem);
  --t-4xl: clamp(2.2rem, 1.6rem + 2.8vw, 3.6rem);
  --t-5xl: clamp(2.6rem, 1.7rem + 4vw, 4.4rem);

  /* ── Motion ──────────────────────────────────────────────────────────── */
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
  --dur-fast: 140ms;
  --dur: 240ms;
  --dur-slow: 460ms;

  /* ── Layout ──────────────────────────────────────────────────────────── */
  --wrap: 1200px;
  --wrap-tight: 880px;
  --wrap-prose: 68ch;
  --nav-h: 72px;

  /* ── Aliases kept so existing page markup keeps working ──────────────── */
  --page: var(--surface-1);
  --surface: var(--surface-0);
  --surface-sunk: var(--surface-2);
  --ink: var(--ink-900);
  --ink-2: var(--ink-600);
  --ink-3: var(--ink-500);
  --muted: var(--ink-400);
  --accent: var(--brand-600);
  --accent-deep: var(--brand-700);
  --accent-soft: var(--brand-100);
  --accent-line: var(--brand-200);
  --gold: var(--gold-600);
  --gold-soft: var(--gold-100);
  --rule: var(--border);
  --rule-strong: var(--border-strong);
  --shadow: var(--shadow-md);
  --radius: var(--r-md);
  --radius-lg: var(--r-lg);
  --ok: var(--ok-600);
  --ok-soft: var(--ok-100);
  --warn: var(--warn-600);
  --warn-soft: var(--warn-100);
  --danger: var(--danger-600);
  --danger-soft: var(--danger-100);
  --info: var(--info-600);
  --info-soft: var(--info-100);
  --wash: rgba(20, 16, 15, 0.04);

  /* Frosted panel recipe, so glass is defined once rather than per component. */
  --glass-bg: rgba(255, 255, 255, 0.72);
  --glass-border: rgba(255, 255, 255, 0.65);
  --glass-blur: saturate(1.6) blur(14px);
}

/* ── Dark theme ────────────────────────────────────────────────────────────
   Not an inversion. Surfaces stay warm, the brand lightens enough to hold
   contrast on dark, and shadows give way to borders — a dark surface cannot
   show a shadow, so elevation has to be expressed as a lighter edge. ────── */
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) { color-scheme: dark; }
}

:root[data-theme="dark"],
:root:where(:not([data-theme="light"])) {
  --dark-defs: initial;
}

@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    --ink-900: #f8f5f2;
    --ink-800: #ede8e3;
    --ink-700: #dcd5ce;
    --ink-600: #c2b8b0;
    --ink-500: #a89d95;
    --ink-400: #8c817a;
    --ink-300: #6f645d;

    --surface-0: #1b1817;
    --surface-1: #121010;
    --surface-2: #232020;
    --surface-3: #2d2928;

    --border: #322d2b;
    --border-strong: #453e3b;

    --brand-050: #241315;
    --brand-100: #2e1a1e;
    --brand-200: #4a2830;
    --brand-400: #d2879a;
    --brand-500: #dd9aaa;
    --brand-600: #e8adba;
    --brand-700: #f0c4cd;
    --brand-800: #f7dde3;

    --gold-100: #2a2116;
    --gold-200: #3d2f1d;
    --gold-500: #d9b062;
    --gold-600: #e4c17c;
    --gold-700: #eed093;

    --ok-100: #1a2b22;   --ok-600: #7cc39c;   --ok-700: #99d4b2;
    --warn-100: #2c2415; --warn-600: #dcb26a; --warn-700: #e8c78d;
    --danger-100: #2e1a18; --danger-600: #e79b93; --danger-700: #f0b6b0;
    --info-100: #17232d;  --info-600: #8fb6d6;  --info-700: #accbe4;

    --shadow-xs: none;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
    --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
    --shadow-xl: 0 24px 56px rgba(0, 0, 0, 0.65);
    --shadow-brand: 0 8px 24px -10px rgba(232, 173, 186, 0.3);
    --ring: 0 0 0 3px rgba(232, 173, 186, 0.35);

    --glass-bg: rgba(27, 24, 23, 0.72);
    --glass-border: rgba(255, 255, 255, 0.08);
    --wash: rgba(255, 255, 255, 0.05);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --ink-900: #f8f5f2;
  --ink-800: #ede8e3;
  --ink-700: #dcd5ce;
  --ink-600: #c2b8b0;
  --ink-500: #a89d95;
  --ink-400: #8c817a;
  --ink-300: #6f645d;

  --surface-0: #1b1817;
  --surface-1: #121010;
  --surface-2: #232020;
  --surface-3: #2d2928;

  --border: #322d2b;
  --border-strong: #453e3b;

  --brand-050: #241315;
  --brand-100: #2e1a1e;
  --brand-200: #4a2830;
  --brand-400: #d2879a;
  --brand-500: #dd9aaa;
  --brand-600: #e8adba;
  --brand-700: #f0c4cd;
  --brand-800: #f7dde3;

  --gold-100: #2a2116;
  --gold-200: #3d2f1d;
  --gold-500: #d9b062;
  --gold-600: #e4c17c;
  --gold-700: #eed093;

  --ok-100: #1a2b22;   --ok-600: #7cc39c;   --ok-700: #99d4b2;
  --warn-100: #2c2415; --warn-600: #dcb26a; --warn-700: #e8c78d;
  --danger-100: #2e1a18; --danger-600: #e79b93; --danger-700: #f0b6b0;
  --info-100: #17232d;  --info-600: #8fb6d6;  --info-700: #accbe4;

  --shadow-xs: none;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
  --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.5);
  --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
  --shadow-xl: 0 24px 56px rgba(0, 0, 0, 0.65);
  --shadow-brand: 0 8px 24px -10px rgba(232, 173, 186, 0.3);
  --ring: 0 0 0 3px rgba(232, 173, 186, 0.35);

  --glass-bg: rgba(27, 24, 23, 0.72);
  --glass-border: rgba(255, 255, 255, 0.08);
  --wash: rgba(255, 255, 255, 0.05);
}

/* ============================================================================
   2. BASE
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + var(--s-4));
}

body {
  margin: 0;
  background: var(--surface-1);
  color: var(--ink-900);
  font-family: var(--font-sans);
  font-size: var(--t-md);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.018em;
  margin: 0 0 var(--s-3);
  color: var(--ink-900);
  text-wrap: balance;
}

h1 { font-size: var(--t-4xl); }
h2 { font-size: var(--t-3xl); }
h3 { font-size: var(--t-xl); }
h4 { font-size: var(--t-lg); font-family: var(--font-sans); font-weight: 650; letter-spacing: -0.005em; }
h5 { font-size: var(--t-base); font-family: var(--font-sans); font-weight: 650; }

p { margin: 0 0 var(--s-4); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a {
  color: var(--brand-600);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color var(--dur-fast) var(--ease-out);
}

a:hover { color: var(--brand-700); }

/* Focus is never removed. It is the only way a keyboard user knows where they
   are, and this platform is used by people who do not use a mouse. */
:focus-visible {
  outline: 2px solid var(--brand-600);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

img, svg, video { max-width: 100%; display: block; }

hr { border: 0; border-top: 1px solid var(--border); margin: var(--s-6) 0; }

::selection { background: var(--brand-200); color: var(--ink-900); }

/* ============================================================================
   3. LAYOUT PRIMITIVES
   ========================================================================= */

.wrap { width: min(100% - var(--s-6), var(--wrap)); margin-inline: auto; }
.wrap-narrow { width: min(100% - var(--s-6), var(--wrap-tight)); margin-inline: auto; }
.prose { max-width: var(--wrap-prose); }

.page-body { min-height: 60vh; padding: var(--s-8) 0 var(--s-11); }

/* Section rhythm. Landing sections breathe; app pages stay tighter. */
.section { padding: var(--s-11) 0; position: relative; }
.section-sm { padding: var(--s-9) 0; }

/* A results band opens directly under the panel that filters it, so the two are
   one interaction and a 64px trench between them reads as a page break. The
   bottom keeps its full measure — that gap is the end of the page, not a join.

   Measured, not guessed: with --s-9 top the first event card began at y=628 on a
   768px viewport, entirely below the fold on a laptop. */
.section-sm.section-results { padding-top: var(--s-6); }
.section-tint { background: var(--surface-2); }
.section-line { border-top: 1px solid var(--border); }

.stack > * + * { margin-top: var(--s-4); }
.stack-sm > * + * { margin-top: var(--s-2); }
.stack-lg > * + * { margin-top: var(--s-6); }
.stack-xl > * + * { margin-top: var(--s-8); }

.row { display: flex; gap: var(--s-4); align-items: center; flex-wrap: wrap; }
.row-between { display: flex; gap: var(--s-4); align-items: center; justify-content: space-between; flex-wrap: wrap; }
.row-tight { gap: var(--s-2); }
.grow { flex: 1 1 auto; }

.grid { display: grid; gap: var(--s-5); }
.grid-cards { grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); }
.grid-stats { grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr)); }

.filter-grid {
  grid-template-columns: minmax(220px, 2fr) minmax(160px, 1fr) minmax(180px, 1fr) auto;
  align-items: end;
  gap: var(--s-4);
}

/* .field carries a bottom margin for stacked forms, which is right everywhere except
   here: the row aligns on `end`, so that margin lifts every input 24px above the submit
   button and the row reads as broken. The gap already provides the spacing a grid needs. */
.filter-grid .field { margin-bottom: 0; }

/* A <fieldset> is the right element for a group of related controls — it names the group
   in the accessibility tree — but its default border, padding and inline-size behaviour
   are from 1996. Reset to a plain block and let the surrounding .card provide the frame. */
.filter-set { border: 0; padding: 0; margin: 0; min-inline-size: 0; }
.filter-set > legend { padding: 0; margin-bottom: var(--s-4); }

/* A page whose first element is a full-bleed band drops the top padding, so the band meets
   the masthead instead of floating below a strip of page surface. */
.page-body-flush { padding-top: 0; }

/* Available to a screen reader, absent from the page.
   For a <caption> that names what a table contains: sighted readers already have the heading
   above it, and a reader hearing "table" with no name has to infer it from the first cell.
   Not display:none and not visibility:hidden — both remove it from the accessibility tree,
   which is the one place it needs to exist. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The hero's default rhythm is tuned for the marketing page, where the band IS the screen.
   On a booking page the same padding pushes the ticket panel and its price to the fold on a
   laptop, which is the one thing a buyer came to see. Tighter top and bottom, same design.

   Two class names on purpose: .hero is declared later in the file, in section 12, so a
   single-class selector here would lose on source order despite being the more specific
   intent. */
.hero.hero-compact { padding: var(--s-8) 0 var(--s-7); }

/* A heading that leads a content section inside a card. .card-title is --t-lg, which is
   right for a compact card header but leaves only 2px between a section heading and the
   body text under it — a difference nobody reads as hierarchy. --t-2xl is the step the
   scale already provides between the two.

   Both class names again: .card-title is declared later, in section 5, so a single-class
   selector loses on source order — the same trap as .hero.hero-compact above. */
.card-title.card-title-lg { font-size: var(--t-2xl); }

/* The panel that follows the reader holds the price and the primary action, and it was
   rendering with exactly the same background, border and shadow as the prose card beside
   it — measured identical. A booking page's ticket panel should sit above its content, not
   level with it. Existing tokens only: a firmer edge and the next shadow step, no new
   colour, and both themes get it because both define the tokens.

   Two class names because .card sets border-color and box-shadow later in this file. */
.card.sticky-panel { border-color: var(--border-strong); box-shadow: var(--shadow-md); }

/* ── Event detail page ───────────────────────────────────────────────────────
   Everything below is scoped to a class the event page adds. Nothing here
   redefines a shared component, because .figure, .figure-value and .hero-title
   are all on the landing page too and that page must not change. */

/* A figure holding PROSE rather than a metric.

   .figure-value is 2.2rem of display type, and it is right for what it was built
   for: "1,240", "99.9%" — a number that IS the content. The event page reuses
   .figure for an organiser's name, a long date and a venue. At 2.2rem
   "Riverside Community Group" wraps to three lines of display type and outweighs
   the h1 beside it, and the six values under "Event details" become a wall with
   no hierarchy left between them.

   Body scale and the sans face instead, so the eye reads label → value and moves
   on. A separate modifier rather than a change to .figure-value: the landing
   page's statistics use that rule and are correct as they are. */
.figure-sm .figure-value {
  font-family: var(--font-sans);
  font-size: var(--t-md);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: normal;
}

/* An event title is content of unknown length, not a marketing headline. --t-5xl
   reaches 4.4rem, which a three-word title carries and a real one — "St Thomas
   Parish Annual Community Lunch" — does not: it becomes four lines that push the
   ticket panel's price below the fold on a laptop.

   --t-3xl tops out at 2.6rem and keeps a clear step over the --t-2xl section
   headings under it. Scoped to .hero-compact, which is the booking pages' hero. */
.hero-compact .hero-title { font-size: var(--t-3xl); margin-bottom: var(--s-4); }
.hero-compact .hero-lede { font-size: var(--t-md); margin-bottom: var(--s-5); }

/* The hero figures sat 40px apart in a row that had room for far less once the
   values came down to body scale; 40px of air between three short labels reads
   as three unrelated things rather than one summary line. */
.hero-compact .hero-figures { gap: var(--s-4) var(--s-7); padding-top: var(--s-4); }

/* Two columns of facts, always. .grid-2's auto-fit needs 280px per column and the
   right-hand content column of .grid-split is narrower than that below about
   1200px, so six facts were stacking into six full-width rows on most laptops.
   Fixed at two, collapsing to one where two genuinely will not fit. */
.detail-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-5) var(--s-4); }

@media (max-width: 560px) {
  .detail-facts { grid-template-columns: 1fr; }
}

/* ── Ticket panel ──────────────────────────────────────────────────────────
   .tier carries its own 16px padding inside a .card-pad that already has 24px,
   so every tier's text started 16px to the right of the panel's heading, price
   and button. Pulling the row out by its own inline padding puts the text back
   on the card's edge while the hover surface still extends past it. */
.tier-list .tier {
  padding-inline: var(--s-3);
  margin-inline: calc(var(--s-3) * -1);
  gap: var(--s-3) var(--s-4);
  align-items: flex-start;
}

/* Price above quantity, both hard right. As flat siblings of the name these were
   wrapping items in a flex row: the price floated to wherever there was room and
   the "Up to N" hint fell onto its own line. One column keeps them together and
   gives the panel a single alignment edge down the right. */
.tier-buy {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s-2);
  flex: 0 0 auto;
  text-align: right;

  /* .tier still wraps, and it should: in the 464px sticky panel a tier name and a
     tier description have no room to share a line with a price and a stepper. But
     a wrapped flex line starts at the LEFT, which put the price 202px inside the
     panel's right edge — measured — while the same price sat flush right whenever
     the panel was full width. Two different alignments for one component.

     An auto left margin eats the free space on whichever line this lands on, so
     the price and the stepper meet the panel's right edge in both layouts. */
  margin-left: auto;
}

/* .hint carries a top margin for use under a field. Inside a gapped column that
   margin is applied twice over. */
.tier-buy .hint { margin-top: 0; }

/* Tiers are the panel's content, not a list appended to it: the same --s-4 that
   separates the heading from the body separates them from the total. */
.tier-list { margin-block: var(--s-4) var(--s-5); }

/* The total and the action it commits you to. .stack-sm's 8px put the button
   against the amount with no pause between reading a figure and pressing a
   button — the one place on this page where a pause is worth having. */
.buy-actions .btn { margin-top: var(--s-4); }
.buy-actions .hint { text-align: center; }

@media (max-width: 900px) {
  .filter-grid { grid-template-columns: 1fr; }
  .filter-grid .row { justify-content: stretch; }
  .filter-grid .btn { flex: 1 1 auto; }
}

.grid-split {
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: var(--s-7);
  align-items: start;
}

@media (max-width: 960px) {
  .grid-split { grid-template-columns: 1fr; }
  .grid-split .sticky-panel { position: static !important; }
}

.sticky-panel { position: sticky; top: calc(var(--nav-h) + var(--s-4)); }

/* ── Section headings ────────────────────────────────────────────────────── */

.section-head { max-width: 46rem; margin-bottom: var(--s-7); }
.section-head.is-centred { margin-inline: auto; text-align: center; }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--font-sans);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brand-600);
  margin: 0 0 var(--s-3);
}

.eyebrow::before {
  content: "";
  width: 20px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--brand-600), var(--gold-500));
}

.section-head.is-centred .eyebrow { justify-content: center; }

.lede {
  font-size: var(--t-lg);
  color: var(--ink-600);
  line-height: 1.6;
  margin: 0;
  max-width: 46rem;
}

.section-head.is-centred .lede { margin-inline: auto; }

.page-head { margin-bottom: var(--s-7); }
.page-head h1 { margin-bottom: var(--s-2); }

/* ── Listing header ──────────────────────────────────────────────────────
   The line above a grid of results. It was a bare .eyebrow — 14px, uppercase,
   brand-coloured — doing the job of a section heading, which left the listing
   band opening with nothing the eye could land on.

   The count is the heading, at reading size and weight, with the qualifier
   ("coming up", "found") demoted beside it. A rule fills the remaining width so
   the band reads as a titled section rather than a caption floating over cards. */
.listing-head {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}

.listing-head h2 {
  font-family: var(--font-sans);
  font-size: var(--t-lg);
  font-weight: 650;
  letter-spacing: -0.005em;
  margin: 0;
  flex: 0 0 auto;
}

.listing-head .listing-qualifier { font-size: var(--t-sm); color: var(--ink-500); flex: 0 0 auto; }

/* Decorative and inert to assistive technology: the heading beside it already
   says what the section is. */
.listing-head::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
}

.crumbs { font-size: var(--t-sm); color: var(--ink-500); margin-bottom: var(--s-3); }
.crumbs a { color: var(--ink-500); text-decoration: none; }
.crumbs a:hover { color: var(--brand-600); text-decoration: underline; }

/* ============================================================================
   3b. COMPONENTS — ICONS
   ========================================================================= */

/* _Icon.cshtml emits width="24" height="24" on every glyph and there was no
   rule here at all, so an icon rendered 24px in every context and — the part
   that actually cost layout — sat on the TEXT BASELINE as an inline element.

   A 24px baseline-aligned SVG inside a 14px/23px line box makes the line 47px
   tall, because the glyph's bottom lands on the baseline and the descender
   space is added underneath it. Two such lines in a listing card measured 98px
   for two short strings. That is where "the cards feel empty" came from: not
   spacing tokens, an unstyled icon.

   `flex: 0 0 auto` stops an icon being squashed inside a flex row; the button
   rule below already said so for buttons, and it belongs to icons generally. */
.icon {
  flex: 0 0 auto;
  vertical-align: middle;
}

/* Icons paired with small text — meta lines, hints, inline labels. 24px next to
   14px text reads as an icon with a caption rather than a labelled icon. */
.icon-sm { width: 16px; height: 16px; }

/* ============================================================================
   4. COMPONENTS — BUTTONS
   ========================================================================= */

.btn {
  --btn-bg: transparent;
  --btn-fg: var(--ink-900);
  --btn-border: transparent;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  /* 44px minimum target: used one-handed at a parish hall door. */
  min-height: 44px;
  padding: var(--s-3) var(--s-5);
  border-radius: var(--r-md);
  border: 1px solid var(--btn-border);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font: inherit;
  font-size: var(--t-base);
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  transition: background-color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              color var(--dur) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}

.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; transform: none; }

.btn svg { flex: 0 0 auto; }

.btn-primary {
  --btn-bg: var(--brand-600);
  --btn-fg: #fff;
  box-shadow: var(--shadow-sm);
}

:root[data-theme="dark"] .btn-primary { --btn-fg: var(--surface-0); }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .btn-primary { --btn-fg: var(--surface-0); }
}

.btn-primary:hover:not(:disabled) {
  --btn-bg: var(--brand-700);
  transform: translateY(-2px);
  box-shadow: var(--shadow-brand);
}

.btn-primary:active:not(:disabled) { transform: translateY(0); }

/* A single sheen sweep on the primary action only. Restraint is the point —
   if every button glints, none of them reads as primary. */
.btn-primary::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 -130%;
  width: 55%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.28), transparent);
  transform: skewX(-18deg);
  pointer-events: none;
}

.btn-primary:hover:not(:disabled)::after { transition: left var(--dur-slow) var(--ease-out); left: 150%; }

.btn-secondary {
  --btn-bg: var(--surface-0);
  --btn-fg: var(--ink-800);
  --btn-border: var(--border-strong);
  box-shadow: var(--shadow-xs);
}

.btn-secondary:hover:not(:disabled) {
  --btn-bg: var(--surface-2);
  --btn-border: var(--ink-300);
  transform: translateY(-2px);
  box-shadow: var(--shadow-sm);
}

.btn-ghost { --btn-fg: var(--brand-600); }
.btn-ghost:hover:not(:disabled) { --btn-bg: var(--brand-100); }

.btn-danger { --btn-bg: var(--danger-600); --btn-fg: #fff; }
.btn-danger:hover:not(:disabled) { --btn-bg: var(--danger-700); transform: translateY(-2px); }

.btn-lg { min-height: 54px; padding: var(--s-4) var(--s-6); font-size: var(--t-md); border-radius: var(--r-lg); }
.btn-sm { min-height: 36px; padding: var(--s-2) var(--s-3); font-size: var(--t-sm); border-radius: var(--r-sm); }
.btn-block { width: 100%; }

/* Arrow nudges forward on hover — a directional cue on "continue" actions. */
.btn .btn-arrow { transition: transform var(--dur) var(--ease-out); }
.btn:hover .btn-arrow { transform: translateX(3px); }

/* ============================================================================
   5. COMPONENTS — CARDS
   ========================================================================= */

.card {
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-5);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}

.card-flush { padding: 0; overflow: hidden; }
.card-pad { padding: var(--s-5); }
.card-lg { padding: var(--s-6); border-radius: var(--r-xl); }

/* Opt-in lift. Not every card is interactive, and a static card that rises on
   hover promises a click that never happens. */
.card-hover:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: var(--border-strong);
}

.card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
  flex-wrap: wrap;
}

.card-title {
  margin: 0;
  font-size: var(--t-lg);
  font-family: var(--font-sans);
  font-weight: 650;
  letter-spacing: -0.008em;
}

/* Frosted panel. Used only over busy backgrounds where the blur genuinely
   separates layers — glass over a flat surface is decoration, not design. */
.glass {
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-lg);
}

/* ============================================================================
   6. COMPONENTS — BADGES
   ========================================================================= */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: var(--s-1) var(--s-3);
  border-radius: var(--r-full);
  font-size: var(--t-xs);
  font-weight: 650;
  letter-spacing: 0.005em;
  white-space: nowrap;
  border: 1px solid transparent;
  line-height: 1.5;
}

.badge-ok      { background: var(--ok-100); color: var(--ok-700); border-color: color-mix(in srgb, var(--ok-600) 26%, transparent); }
.badge-warn    { background: var(--warn-100); color: var(--warn-700); border-color: color-mix(in srgb, var(--warn-600) 26%, transparent); }
.badge-danger  { background: var(--danger-100); color: var(--danger-700); border-color: color-mix(in srgb, var(--danger-600) 26%, transparent); }
.badge-info    { background: var(--info-100); color: var(--info-700); border-color: color-mix(in srgb, var(--info-600) 26%, transparent); }
.badge-neutral { background: var(--surface-2); color: var(--ink-600); border-color: var(--border); }
.badge-accent  { background: var(--brand-100); color: var(--brand-700); border-color: var(--brand-200); }

/* Dot prefix gives a second, non-colour signal at a glance. */
.badge-dot::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}

/* ============================================================================
   7. COMPONENTS — FORMS
   ========================================================================= */

.field { display: block; margin-bottom: var(--s-5); }

.field label, .label {
  display: block;
  font-size: var(--t-sm);
  font-weight: 650;
  color: var(--ink-700);
  margin-bottom: var(--s-2);
  letter-spacing: -0.003em;
}

.hint { font-size: var(--t-sm); color: var(--ink-500); margin-top: var(--s-2); }

.input, .select, .textarea {
  width: 100%;
  min-height: 46px;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  background: var(--surface-0);
  color: var(--ink-900);
  font: inherit;
  font-size: var(--t-base);
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}

.textarea { min-height: 120px; resize: vertical; line-height: 1.6; }

.input::placeholder, .textarea::placeholder { color: var(--ink-400); }

.input:hover:not(:focus), .select:hover:not(:focus), .textarea:hover:not(:focus) {
  border-color: var(--ink-300);
}

.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--brand-600);
  box-shadow: var(--ring);
}

.input[aria-invalid="true"], .select[aria-invalid="true"] { border-color: var(--danger-600); }

.field-error { color: var(--danger-600); font-size: var(--t-sm); margin-top: var(--s-2); font-weight: 600; }

/* asp-validation-summary ALWAYS emits its div — when the model is valid it renders
   class="validation-summary-valid" wrapping an empty <ul>. With .alert-danger on it that was a
   bare red 50px box sitting above the card form on every clean page load, which on a payment
   screen reads as "something is already wrong".

   Hidden rather than restyled: an error summary with nothing in it has nothing to say. The
   framework swaps the class to validation-summary-errors as soon as there is something, and the
   box appears then. */
.validation-summary-valid { display: none !important; }

/* ── Required marker ─────────────────────────────────────────────────────
   Every field on the checkout was required and none of them said so, which
   leaves "required" to be discovered by submitting and being refused.

   A word, not a bare asterisk. An asterisk alone needs a legend elsewhere on
   the page to decode it, and screen readers either skip it or read "star".
   .req-mark hides the glyph from assistive technology and the .sr-only text
   beside it says the word — so it is announced as "Email required". */
.req-mark {
  color: var(--danger-600);
  font-weight: 700;
  margin-left: 2px;
}

/* The counterpart: fields that are genuinely optional say so, in the same
   place, so a blank one is not read as an oversight. */
.optional-mark {
  color: var(--ink-500);
  font-weight: 400;
  font-size: var(--t-xs);
  margin-left: var(--s-2);
}

.checkline { display: flex; gap: var(--s-3); align-items: flex-start; }
/* 24px is the WCAG 2.5.8 minimum. The <label> wraps the box and its text, so the
   real target is the whole row — but the box is what a thumb aims at. */
.checkline input[type="checkbox"] { width: 24px; height: 24px; margin-top: 1px; accent-color: var(--brand-600); flex: 0 0 auto; }

/* The quantity box. Tabular figures so the width does not jump between 1 and 2
   places. font-weight because the number a buyer is choosing should read as the
   value it is, not as placeholder text. */
.qty-input {
  width: 92px;
  min-width: 92px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 650;
}

/* Search field with a leading icon slot. */
.field-icon { position: relative; }
.field-icon .input { padding-left: calc(var(--s-4) + 26px); }
.field-icon > svg {
  position: absolute;
  left: var(--s-4);
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-400);
  pointer-events: none;
}

/* ============================================================================
   8. COMPONENTS — TABLES
   ========================================================================= */

/* Wide tables scroll inside their own box; the page body never scrolls sideways. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.table { width: 100%; border-collapse: collapse; font-size: var(--t-base); }

.table th {
  text-align: left;
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-500);
  font-weight: 700;
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--border-strong);
  white-space: nowrap;
  background: var(--surface-2);
}

.table td { padding: var(--s-4); border-bottom: 1px solid var(--border); vertical-align: middle; }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr { transition: background-color var(--dur-fast) var(--ease-out); }
.table tbody tr:hover { background: var(--surface-2); }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }

.mono { font-family: var(--font-mono); font-size: 0.92em; letter-spacing: 0.02em; }

/* ============================================================================
   9. COMPONENTS — STATS
   ========================================================================= */

.stat {
  position: relative;
  overflow: hidden;
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s-5);
  transition: box-shadow var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}

.stat:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); border-color: var(--border-strong); }

/* Accent hairline drawn across the top on hover. */
.stat::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: linear-gradient(90deg, var(--brand-600), var(--gold-500));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-slow) var(--ease-out);
}

.stat:hover::before { transform: scaleX(1); }

.stat-label {
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-500);
  font-weight: 700;
  margin-bottom: var(--s-2);
}

.stat-value {
  font-family: var(--font-display);
  font-size: 2.1rem;
  font-weight: 600;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.stat-note { font-size: var(--t-sm); color: var(--ink-500); margin-top: var(--s-2); }

/* ============================================================================
   10. COMPONENTS — ALERTS & EMPTY STATES
   ========================================================================= */

.alert {
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-md);
  border: 1px solid;
  border-left-width: 3px;
  font-size: var(--t-base);
}

.alert-ok     { background: var(--ok-100); border-color: var(--ok-600); color: var(--ok-700); }
.alert-warn   { background: var(--warn-100); border-color: var(--warn-600); color: var(--warn-700); }
.alert-danger { background: var(--danger-100); border-color: var(--danger-600); color: var(--danger-700); }
.alert-info   { background: var(--info-100); border-color: var(--info-600); color: var(--info-700); }

.alert strong { display: block; margin-bottom: var(--s-1); }

.empty {
  text-align: center;
  padding: var(--s-9) var(--s-5);
  color: var(--ink-500);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-xl);
  background: var(--surface-0);
  transition: border-color var(--dur) var(--ease-out);
}

.empty:hover { border-color: var(--brand-200); }
.empty h3 { color: var(--ink-700); }

/* ============================================================================
   11. NAVIGATION
   ========================================================================= */

.masthead {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}

/* The bar earns its border and shadow only once there is content behind it. */
.masthead.is-stuck { border-bottom-color: var(--border); box-shadow: var(--shadow-sm); }

.masthead-inner {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-height: var(--nav-h);
  padding-block: var(--s-2);
}

.brand { display: inline-flex; align-items: center; gap: var(--s-3); text-decoration: none; color: var(--ink-900); flex: 0 0 auto; }
.brand:hover { color: var(--ink-900); }

.brand-mark {
  width: 40px; height: 40px;
  flex: 0 0 auto;
  color: var(--brand-600);
  transition: transform var(--dur-slow) var(--ease-spring);
}

.brand:hover .brand-mark { transform: rotate(-6deg) scale(1.06); }

.brand-text { display: flex; flex-direction: column; line-height: 1.12; }

.brand-name { font-family: var(--font-display); font-size: 1.28rem; font-weight: 600; letter-spacing: -0.02em; }
.brand-sub { font-size: 0.66rem; color: var(--ink-500); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600; }

.masthead-nav { display: flex; gap: var(--s-1); align-items: center; margin-left: auto; flex-wrap: wrap; }

.navlink {
  position: relative;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  color: var(--ink-600);
  text-decoration: none;
  font-size: var(--t-base);
  font-weight: 550;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}

.navlink:hover { color: var(--ink-900); background: var(--wash); }

.navlink::after {
  content: "";
  position: absolute;
  left: 50%; right: 50%; bottom: 2px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--brand-600), var(--gold-500));
  transition: left var(--dur) var(--ease-out), right var(--dur) var(--ease-out);
}

.navlink:hover::after,
.navlink[aria-current="page"]::after { left: var(--s-3); right: var(--s-3); }
.navlink[aria-current="page"] { color: var(--brand-600); }

.icon-btn {
  /* Never shrink. As a flex child it is the item most willing to give up space, and a
     40px control squeezed to 14px is both unusable and wide enough to push the row past
     the viewport. Crowding must be resolved by what the bar contains, not by deforming
     its controls. */
  flex: 0 0 auto;
  width: 40px; height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-600);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

.icon-btn:hover { background: var(--wash); color: var(--ink-900); transform: translateY(-1px); }

/* ── Organisation portal context bar ─────────────────────────────────────── */

.portal-bar {
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  padding: var(--s-2) 0;
  font-size: var(--t-sm);
}

.portal-bar .wrap { display: flex; gap: var(--s-4); align-items: center; flex-wrap: wrap; }
.portal-org { font-weight: 650; display: inline-flex; align-items: center; gap: var(--s-2); }

.subnav { display: flex; gap: var(--s-1); flex-wrap: wrap; margin-left: auto; }

.subnav a {
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  color: var(--ink-600);
  text-decoration: none;
  font-weight: 600;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.subnav a:hover { background: var(--surface-0); color: var(--ink-900); }
.subnav a[aria-current="page"] { background: var(--brand-600); color: #fff; }
:root[data-theme="dark"] .subnav a[aria-current="page"] { color: var(--surface-0); }

/* ============================================================================
   12. HERO
   ========================================================================= */

.hero {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  padding: var(--s-11) 0 var(--s-10);
  border-bottom: 1px solid var(--border);
}

.hero-inner { position: relative; z-index: 1; }

/* Decorative mesh. Three very low-opacity blurred shapes drifting on long
   cycles — depth, not animation. Inert to pointer events, behind content. */
.mesh { position: absolute; inset: 0; z-index: -1; pointer-events: none; overflow: hidden; }

.mesh span {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.5;
}

.mesh span:nth-child(1) {
  width: 520px; height: 520px;
  top: -220px; right: -120px;
  background: radial-gradient(circle, color-mix(in srgb, var(--brand-500) 40%, transparent), transparent 70%);
  animation: drift-a 24s ease-in-out infinite alternate;
}

.mesh span:nth-child(2) {
  width: 420px; height: 420px;
  bottom: -240px; left: -140px;
  background: radial-gradient(circle, color-mix(in srgb, var(--gold-500) 34%, transparent), transparent 70%);
  animation: drift-b 30s ease-in-out infinite alternate;
}

.mesh span:nth-child(3) {
  width: 340px; height: 340px;
  top: 30%; left: 45%;
  background: radial-gradient(circle, color-mix(in srgb, var(--brand-400) 22%, transparent), transparent 70%);
  animation: drift-c 27s ease-in-out infinite alternate;
}

@keyframes drift-a { to { transform: translate3d(-50px, 40px, 0) scale(1.14); } }
@keyframes drift-b { to { transform: translate3d(60px, -34px, 0) scale(1.1); } }
@keyframes drift-c { to { transform: translate3d(-40px, -50px, 0) scale(1.18); } }

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: var(--s-9);
  align-items: center;
}

@media (max-width: 1000px) {
  .hero-grid { grid-template-columns: 1fr; gap: var(--s-7); }
}

.hero-title { font-size: var(--t-5xl); margin-bottom: var(--s-5); letter-spacing: -0.03em; }
.hero-title span { display: block; }

/* Gradient on the emphasis line only. A fully gradient headline is the single
   most over-used effect in SaaS design and hurts contrast; one line earns it. */
.hero-title .accent-line {
  background: linear-gradient(100deg, var(--brand-600) 10%, var(--gold-600) 90%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* Fallback for anything that cannot clip to text — never a transparent heading. */
  -webkit-text-fill-color: transparent;
}

@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .hero-title .accent-line { color: var(--brand-600); -webkit-text-fill-color: currentColor; }
}

.hero-lede { font-size: var(--t-lg); color: var(--ink-600); max-width: 34rem; margin-bottom: var(--s-6); }

.hero-cta { display: flex; gap: var(--s-3); flex-wrap: wrap; margin-bottom: var(--s-7); }

.hero-figures { display: flex; flex-wrap: wrap; gap: var(--s-7); padding-top: var(--s-5); border-top: 1px solid var(--border); }

.figure { display: flex; flex-direction: column; }

.figure-value {
  font-family: var(--font-display);
  font-size: 2.2rem;
  font-weight: 600;
  line-height: 1;
  color: var(--ink-900);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.figure-label {
  font-size: var(--t-xs);
  color: var(--ink-500);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  margin-top: var(--s-2);
}

/* ── Hero visual: a floating product vignette ────────────────────────────── */

.hero-visual { position: relative; }

.hero-panel {
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-2xl);
  box-shadow: var(--shadow-xl);
  padding: var(--s-5);
  transform: perspective(1400px) rotateY(-7deg) rotateX(3deg);
  transition: transform var(--dur-slow) var(--ease-out);
}

.hero-visual:hover .hero-panel { transform: perspective(1400px) rotateY(-3deg) rotateX(1deg); }

@media (max-width: 1000px) {
  .hero-panel { transform: none; }
  .hero-visual:hover .hero-panel { transform: none; }
}

.hero-panel-head {
  display: flex; align-items: center; gap: var(--s-2);
  padding-bottom: var(--s-4); margin-bottom: var(--s-4);
  border-bottom: 1px solid var(--border);
}

.hero-panel-dots { display: flex; gap: 5px; }
.hero-panel-dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--border-strong); display: block; }

/* Small floating chips around the panel, offset on long slow cycles. */
.float-chip {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  font-size: var(--t-sm);
  font-weight: 650;
  white-space: nowrap;
}

.float-chip svg { color: var(--brand-600); }
.float-chip-1 { top: -22px; left: -26px; animation: bob 7s ease-in-out infinite; }
.float-chip-2 { bottom: -20px; right: -18px; animation: bob 8.5s ease-in-out infinite reverse; }

@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }

@media (max-width: 1000px) { .float-chip { display: none; } }

/* ============================================================================
   13. LANDING SECTIONS
   ========================================================================= */

/* ── Feature cards ───────────────────────────────────────────────────────── */

.feature {
  position: relative;
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--s-6);
  overflow: hidden;
  transition: transform var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}

.feature:hover { transform: translateY(-5px); box-shadow: var(--shadow-lg); border-color: var(--border-strong); }

/* Radial wash that fades in from the icon corner on hover. */
.feature::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(520px circle at 12% 0%, var(--brand-050), transparent 62%);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
  pointer-events: none;
}

.feature:hover::before { opacity: 1; }

.feature > * { position: relative; }

.feature-icon {
  width: 52px; height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-md);
  background: linear-gradient(140deg, var(--brand-100), var(--gold-100));
  border: 1px solid var(--brand-200);
  color: var(--brand-600);
  margin-bottom: var(--s-4);
  transition: transform var(--dur) var(--ease-spring);
}

.feature:hover .feature-icon { transform: scale(1.08) rotate(-4deg); }

.feature h3 { font-size: var(--t-lg); font-family: var(--font-sans); font-weight: 650; margin-bottom: var(--s-2); }
.feature p { color: var(--ink-500); font-size: var(--t-base); margin: 0; }

/* ── Workflow timeline ───────────────────────────────────────────────────── */

.timeline { position: relative; display: grid; gap: var(--s-7); margin: 0; padding: 0; list-style: none; }

/* Connecting spine, drawn behind the markers. */
.timeline::before {
  content: "";
  position: absolute;
  left: 23px; top: 12px; bottom: 12px;
  width: 2px;
  background: linear-gradient(180deg, var(--brand-200), var(--gold-200), transparent);
}

.tl-item { display: grid; grid-template-columns: 48px 1fr; gap: var(--s-5); align-items: start; }

.tl-marker {
  width: 48px; height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--surface-0);
  border: 2px solid var(--brand-200);
  color: var(--brand-600);
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 600;
  position: relative;
  z-index: 1;
  transition: transform var(--dur) var(--ease-spring),
              border-color var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}

.tl-item:hover .tl-marker { transform: scale(1.1); border-color: var(--brand-600); background: var(--brand-100); }

.tl-body h3 { font-size: var(--t-lg); font-family: var(--font-sans); font-weight: 650; margin-bottom: var(--s-2); }
.tl-body p { color: var(--ink-500); margin: 0; }

@media (min-width: 900px) {
  .timeline { grid-template-columns: repeat(4, 1fr); gap: var(--s-5); }
  .timeline::before { left: 24px; right: 24px; top: 23px; bottom: auto; width: auto; height: 2px;
                      background: linear-gradient(90deg, var(--brand-200), var(--gold-200), var(--brand-200)); }
  .tl-item { grid-template-columns: 1fr; gap: var(--s-4); }
}

/* ── Testimonials ────────────────────────────────────────────────────────── */

.quote-card {
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--s-6);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}

.quote-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }

.quote-mark { font-family: var(--font-display); font-size: 3rem; line-height: 0.6; color: var(--brand-200); }
.quote-text { font-size: var(--t-md); color: var(--ink-700); margin: 0; flex: 1; }

.quote-by { display: flex; align-items: center; gap: var(--s-3); padding-top: var(--s-4); border-top: 1px solid var(--border); }

.quote-avatar {
  width: 42px; height: 42px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(140deg, var(--brand-100), var(--gold-100));
  border: 1px solid var(--brand-200);
  color: var(--brand-600);
  font-weight: 700;
  font-size: var(--t-sm);
  flex: 0 0 auto;
}

.quote-name { font-weight: 650; font-size: var(--t-base); }
.quote-role { font-size: var(--t-sm); color: var(--ink-500); }

/* Placeholder marker. Deliberately visible: this content is illustrative and
   must be replaced with real, attributable quotes before the site is published.
   Making it obvious in the UI is the only reliable way to stop that being
   forgotten — a code comment never survives a copy-paste to production. */
.placeholder-flag {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  background: var(--warn-100);
  border: 1px dashed var(--warn-600);
  color: var(--warn-700);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* ── Pricing ─────────────────────────────────────────────────────────────── */

.price-grid { display: grid; gap: var(--s-5); grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); align-items: start; }

.price-card {
  position: relative;
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--s-6);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}

.price-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }

/* The recommended tier is raised by border and a ribbon, not by a loud fill —
   emphasis should not cost legibility. */
.price-card.is-featured { border-color: var(--brand-600); box-shadow: var(--shadow-lg); }

.price-ribbon {
  position: absolute;
  top: calc(-1 * var(--s-3));
  left: 50%;
  transform: translateX(-50%);
  padding: var(--s-1) var(--s-4);
  border-radius: var(--r-full);
  background: linear-gradient(100deg, var(--brand-600), var(--brand-500));
  color: #fff;
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
}

.price-name { font-size: var(--t-lg); font-weight: 650; font-family: var(--font-sans); margin: 0; }
.price-amount { font-family: var(--font-display); font-size: 2.6rem; font-weight: 600; line-height: 1; letter-spacing: -0.02em; }
.price-unit { font-size: var(--t-sm); color: var(--ink-500); font-family: var(--font-sans); font-weight: 500; }

.price-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); flex: 1; }
.price-list li { display: flex; gap: var(--s-3); align-items: flex-start; font-size: var(--t-base); color: var(--ink-600); }
.price-list svg { color: var(--ok-600); flex: 0 0 auto; margin-top: 3px; }

/* ── FAQ accordion ───────────────────────────────────────────────────────── */

.faq { display: grid; gap: var(--s-3); }

/* Built on <details>: keyboard operable, screen-reader announced and expandable
   before a single line of JavaScript runs. Re-implementing this with divs and
   click handlers is how accordions become inaccessible. */
.faq-item {
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}

.faq-item[open] { border-color: var(--brand-200); box-shadow: var(--shadow-sm); }

.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5);
  cursor: pointer;
  font-weight: 650;
  font-size: var(--t-md);
  list-style: none;
  transition: background-color var(--dur-fast) var(--ease-out);
}

.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary:hover { background: var(--surface-2); }

.faq-chevron {
  flex: 0 0 auto;
  color: var(--brand-600);
  transition: transform var(--dur) var(--ease-out);
}

.faq-item[open] .faq-chevron { transform: rotate(180deg); }

.faq-answer { padding: 0 var(--s-5) var(--s-5); color: var(--ink-600); }
.faq-answer p:last-child { margin-bottom: 0; }

/* ── CTA band ────────────────────────────────────────────────────────────── */

.cta-band {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-2xl);
  padding: var(--s-9) var(--s-7);
  text-align: center;
  background: linear-gradient(135deg, var(--brand-700), var(--brand-600) 55%, var(--brand-500));
  color: #fff;
  box-shadow: var(--shadow-xl);
}

.cta-band h2 { color: #fff; }
.cta-band p { color: rgba(255, 255, 255, 0.86); max-width: 44rem; margin-inline: auto; }

/* Subtle radial texture so the gradient does not read as a flat slab. */
.cta-band::before {
  content: "";
  position: absolute;
  inset: -50%;
  background: radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.16), transparent 45%),
              radial-gradient(circle at 75% 80%, rgba(201, 151, 63, 0.28), transparent 50%);
  pointer-events: none;
}

.cta-band > * { position: relative; }

.cta-band .btn-primary { --btn-bg: #fff; --btn-fg: var(--brand-700); }
.cta-band .btn-primary:hover:not(:disabled) { --btn-bg: var(--brand-050); --btn-fg: var(--brand-800); }
.cta-band .btn-secondary { --btn-bg: transparent; --btn-fg: #fff; --btn-border: rgba(255, 255, 255, 0.5); }
.cta-band .btn-secondary:hover:not(:disabled) { --btn-bg: rgba(255, 255, 255, 0.12); --btn-fg: #fff; --btn-border: #fff; }

/* ============================================================================
   14. FOOTER
   ========================================================================= */

.foot {
  border-top: 1px solid var(--border);
  background: var(--surface-0);
  padding: var(--s-9) 0 var(--s-6);
  font-size: var(--t-base);
  color: var(--ink-500);
  position: relative;
}

.foot::before {
  content: "";
  position: absolute;
  inset: -1px 0 auto 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--brand-200), var(--gold-200), var(--brand-200), transparent);
}

.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
  gap: var(--s-7);
  padding-bottom: var(--s-7);
  border-bottom: 1px solid var(--border);
}

@media (max-width: 860px) { .foot-grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-6); } }
@media (max-width: 520px) { .foot-grid { grid-template-columns: 1fr; } }

.foot-title {
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-weight: 700;
  color: var(--ink-700);
  margin-bottom: var(--s-4);
}

.foot-links { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
.foot-links a { color: var(--ink-500); text-decoration: none; transition: color var(--dur-fast) var(--ease-out); }
.foot-links a:hover { color: var(--brand-600); }

.foot-bottom {
  padding-top: var(--s-5);
  display: flex;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
  font-size: var(--t-sm);
}

/* ============================================================================
   15. DOMAIN COMPONENTS
   ========================================================================= */

/* ── Event cards ─────────────────────────────────────────────────────────── */

.event-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}

.event-card:hover,
.event-card:focus-within { transform: translateY(-6px); box-shadow: var(--shadow-lg); border-color: var(--border-strong); }

.event-banner {
  height: 140px;
  display: flex;
  align-items: flex-start;
  padding: var(--s-3) var(--s-4);
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}

.cat-art {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  color: var(--brand-600);
  transition: transform var(--dur-slow) var(--ease-out);
}

/* Per-category tint, so a listing reads as a set of distinct things rather
   than one shape repeated in one colour. */
.event-banner[data-category="Liturgical"] { background: #f0ecf6; } .event-banner[data-category="Liturgical"] .cat-art { color: #6b4fa0; }
.event-banner[data-category="Feast"]      { background: var(--brand-100); } .event-banner[data-category="Feast"] .cat-art { color: var(--brand-600); }
.event-banner[data-category="Retreat"]    { background: #e8f0ea; } .event-banner[data-category="Retreat"] .cat-art { color: #2f6b4f; }
.event-banner[data-category="Youth"]      { background: #e9f0f6; } .event-banner[data-category="Youth"] .cat-art { color: #2a5578; }
.event-banner[data-category="Family"]     { background: var(--gold-100); } .event-banner[data-category="Family"] .cat-art { color: var(--gold-600); }
.event-banner[data-category="Fundraiser"] { background: #f7ede4; } .event-banner[data-category="Fundraiser"] .cat-art { color: #a8562a; }
.event-banner[data-category="Formation"]  { background: #eeece7; } .event-banner[data-category="Formation"] .cat-art { color: #5d5a4e; }
.event-banner[data-category="Social"]     { background: #f4eaef; } .event-banner[data-category="Social"] .cat-art { color: #8a4468; }

:root[data-theme="dark"] .event-banner,
:root[data-theme="dark"] .event-banner[data-category] { background: var(--surface-2); }

.event-card:hover .cat-art { transform: scale(1.07); }

.detail-banner { height: 220px; border-radius: var(--r-xl); border: 1px solid var(--border); }
.detail-banner .cat-art { transition: none; }
@media (max-width: 640px) { .detail-banner { height: 160px; } }

.event-date-chip {
  position: relative;
  z-index: 1;
  background: var(--surface-0);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  padding: var(--s-2) var(--s-3);
  text-align: center;
  line-height: 1.05;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease-spring);
}

.event-card:hover .event-date-chip { transform: translateY(-2px) scale(1.04); }

.event-date-chip .d { display: block; font-size: 1.3rem; font-weight: 700; font-family: var(--font-display); }
.event-date-chip .m { display: block; font-size: 0.64rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-500); font-weight: 700; }

.event-card-body { padding: var(--s-5); display: flex; flex-direction: column; gap: var(--s-2); flex: 1 1 auto; }

.event-card-title { font-family: var(--font-display); font-size: var(--t-xl); font-weight: 600; line-height: 1.25; margin: 0; }

.event-card-title a {
  color: var(--ink-900);
  text-decoration: none;
  background-image: linear-gradient(var(--brand-600), var(--brand-600));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0 1.5px;
  transition: background-size var(--dur-slow) var(--ease-out), color var(--dur) var(--ease-out);
}

.event-card:hover .event-card-title a { background-size: 100% 1.5px; color: var(--brand-600); }

.event-org { font-size: var(--t-sm); color: var(--ink-500); }

/* ── Meta list ───────────────────────────────────────────────────────────
   Icon-and-text lines: when, where, who. Used by the listing card and
   available to any page that needs the same treatment, which is why it is a
   named component rather than a rule scoped to .event-card.

   Each ROW is the flex container. .event-meta was a flex COLUMN whose children
   were therefore blockified, leaving the icon inline inside each row and
   baseline-aligned — 47px of line box for 14px of text. Making the row itself a
   centred flex line is what collapses it to the height of its content.

   The icon is sized by the context rather than by a class on every call site,
   so no markup has to pass a size and no page can forget to. */
.meta-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  font-size: var(--t-sm);
  color: var(--ink-600);
}

.meta-item {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
}

.meta-item .icon {
  width: 16px;
  height: 16px;
  color: var(--ink-400);
}

/* Long venue names truncate rather than wrapping to a second line and changing
   the card's height — a grid of cards that are all different heights for want of
   one long string reads as broken alignment. The full value stays in the title
   attribute and in the accessibility tree. */
.meta-item > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.event-meta { font-size: var(--t-sm); color: var(--ink-600); display: flex; flex-direction: column; gap: var(--s-1); }

.event-card-foot {
  margin-top: auto;
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
}

.price { font-weight: 700; font-size: var(--t-lg); font-variant-numeric: tabular-nums; }
.price small { font-weight: 500; color: var(--ink-500); font-size: var(--t-xs); display: block; text-transform: uppercase; letter-spacing: 0.06em; }

/* ── Featured carousel ───────────────────────────────────────────────────── */

.carousel { position: relative; }

/* Without JS this is a plain horizontally scrollable strip — every slide still
   reachable, nothing hidden. .is-enhanced switches it to transform-driven. */
.carousel-viewport { overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; border-radius: var(--r-2xl); }
.carousel.is-enhanced .carousel-viewport { overflow: hidden; }

.carousel-track { display: flex; }
.carousel.is-enhanced .carousel-track { transition: transform var(--dur-slow) var(--ease-out); will-change: transform; }

.carousel-slide { flex: 0 0 100%; min-width: 100%; scroll-snap-align: start; padding: 2px; }

.feature-card {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-2xl);
  overflow: hidden;
  box-shadow: var(--shadow-md);
  min-height: 340px;
}

.feature-art { position: relative; overflow: hidden; background: var(--brand-100); }

.feature-art[data-category="Liturgical"] { background: #f0ecf6; } .feature-art[data-category="Liturgical"] .cat-art { color: #6b4fa0; }
.feature-art[data-category="Feast"] .cat-art { color: var(--brand-600); }
.feature-art[data-category="Retreat"] { background: #e8f0ea; } .feature-art[data-category="Retreat"] .cat-art { color: #2f6b4f; }
.feature-art[data-category="Youth"] { background: #e9f0f6; } .feature-art[data-category="Youth"] .cat-art { color: #2a5578; }
.feature-art[data-category="Family"] { background: var(--gold-100); } .feature-art[data-category="Family"] .cat-art { color: var(--gold-600); }
.feature-art[data-category="Fundraiser"] { background: #f7ede4; } .feature-art[data-category="Fundraiser"] .cat-art { color: #a8562a; }
.feature-art[data-category="Formation"] { background: #eeece7; } .feature-art[data-category="Formation"] .cat-art { color: #5d5a4e; }
.feature-art[data-category="Social"] { background: #f4eaef; } .feature-art[data-category="Social"] .cat-art { color: #8a4468; }

:root[data-theme="dark"] .feature-art { background: var(--surface-2); }

.feature-body { padding: var(--s-6); display: flex; flex-direction: column; gap: var(--s-3); }
.feature-title { font-size: var(--t-2xl); line-height: 1.2; margin: var(--s-1) 0 0; }
.feature-title a { color: var(--ink-900); text-decoration: none; }
.feature-title a:hover { color: var(--brand-600); text-decoration: underline; }
.feature-summary { color: var(--ink-600); margin: 0; }

.feature-meta { display: grid; gap: var(--s-2); margin: var(--s-2) 0 0; }
.feature-meta > div { display: grid; grid-template-columns: 96px 1fr; gap: var(--s-3); }
.feature-meta dt { font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-500); font-weight: 700; padding-top: 3px; }
.feature-meta dd { margin: 0; font-size: var(--t-base); }

.feature-foot {
  margin-top: auto;
  padding-top: var(--s-5);
  border-top: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-4); flex-wrap: wrap;
}

.carousel-controls { display: flex; gap: var(--s-2); }

.carousel-btn {
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-full);
  border: 1px solid var(--border-strong);
  background: var(--surface-0);
  color: var(--ink-600);
  cursor: pointer;
  transition: background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}

.carousel-btn:hover { background: var(--brand-600); border-color: var(--brand-600); color: #fff; transform: scale(1.06); }
:root[data-theme="dark"] .carousel-btn:hover { color: var(--surface-0); }

.carousel-dots { display: flex; justify-content: center; gap: var(--s-2); margin-top: var(--s-5); position: relative; }

.carousel-dot {
  width: 9px; height: 9px; padding: 0;
  border-radius: var(--r-full);
  border: 0;
  background: var(--border-strong);
  cursor: pointer;
  transition: width var(--dur-slow) var(--ease-out), background-color var(--dur) var(--ease-out);
}

/* Current dot stretches into a bar — position readable without relying on
   colour difference alone. */
.carousel-dot.is-current { width: 28px; background: var(--brand-600); }

.carousel-dot::before {
  content: "";
  position: absolute;
  width: 34px; height: 34px;
  transform: translate(-50%, -50%);
}

@media (max-width: 860px) {
  .feature-card { grid-template-columns: 1fr; min-height: 0; }
  .feature-art { height: 160px; }
  .feature-body { padding: var(--s-5); }
  .feature-meta > div { grid-template-columns: 84px 1fr; }
}

/* ── Category rail ───────────────────────────────────────────────────────── */

.rail {
  display: flex;
  gap: var(--s-4);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding: var(--s-1) var(--s-1) var(--s-5);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

.rail-card {
  flex: 0 0 244px;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}

.rail-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-lg); border-color: var(--border-strong); color: inherit; }

.rail-art { height: 104px; position: relative; overflow: hidden; background: var(--surface-2); }
.rail-card:hover .cat-art { transform: scale(1.08); }

.rail-card[data-category="Liturgical"] .cat-art { color: #6b4fa0; }
.rail-card[data-category="Feast"] .cat-art      { color: var(--brand-600); }
.rail-card[data-category="Retreat"] .cat-art    { color: #2f6b4f; }
.rail-card[data-category="Youth"] .cat-art      { color: #2a5578; }
.rail-card[data-category="Family"] .cat-art     { color: var(--gold-600); }
.rail-card[data-category="Fundraiser"] .cat-art { color: #a8562a; }
.rail-card[data-category="Formation"] .cat-art  { color: #5d5a4e; }
.rail-card[data-category="Social"] .cat-art     { color: #8a4468; }

.rail-body { padding: var(--s-4); display: flex; flex-direction: column; gap: var(--s-1); flex: 1; }
.rail-title { font-size: var(--t-base); margin: 0; font-family: var(--font-sans); font-weight: 650; }
.rail-desc { font-size: var(--t-sm); color: var(--ink-500); margin: 0; flex: 1; }
.rail-count { font-size: var(--t-xs); font-weight: 700; color: var(--brand-600); margin-top: var(--s-2); text-transform: uppercase; letter-spacing: 0.06em; }

/* ── Ticket tiers ────────────────────────────────────────────────────────── */

.tier {
  display: flex;
  gap: var(--s-4);
  align-items: center;
  padding: var(--s-4);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
  border-radius: var(--r-sm);
  transition: background-color var(--dur-fast) var(--ease-out);
}

.tier:hover { background: var(--surface-2); }
.tier:last-child { border-bottom: 0; }
.tier-name { font-weight: 650; }
.tier-desc { font-size: var(--t-sm); color: var(--ink-500); }

/* ── Quantity stepper ────────────────────────────────────────────────────
   booking.js wraps the number input in .row.row-tight with a − and a + button.
   Those are .btn-sm at 36px against a 52px input, so the three controls that
   form ONE control sat at two different heights, and 36px is under the 44px
   comfortable-target size.

   Matching them to the input fixes both at once. Scoped to the stepper so
   .btn-sm keeps its size everywhere else. */
.tier .row-tight .btn {
  /* 52px, not 44: that is the .input's own height, and the three controls read as
     one control only if they share an edge. 44 would have cleared the target-size
     guidance while still leaving an 8px step beside the box. */
  min-height: 52px;
  min-width: 52px;
  padding: 0;
  font-size: var(--t-lg);
  line-height: 1;
}

/* .qty-input itself is declared with the form controls in section 8 — see there.
   It is deliberately NOT restated here: a second rule for the same component is
   how two widths end up fighting, which is exactly what happened when this block
   first tried to set one and lost to the original's min-width. */

/* Once .grid-split stacks, the sidebar lands under the form — which on a phone
   means meeting a card field before ever seeing the amount. This modifier lifts
   the summary above it at the stacked width only; side by side, source order
   already reads correctly left to right.

   A modifier rather than a change to .grid-split, because the event page uses
   the same grid and wants its ticket panel where it is. Opt in, per page. */
@media (max-width: 960px) {
  .grid-split.summary-first > :last-child { order: -1; }

  /* align-items must be restated. .grid-split sets `start`, which in a column
     flex container controls the CROSS axis — so the summary shrank to its own
     content width and sat narrower than the form beneath it. */
  .grid-split.summary-first {
    display: flex;
    flex-direction: column;
    align-items: stretch;
  }
}

/* ── Booking journey progress ────────────────────────────────────────────
   Four page loads presented as one journey. The checkout previously opened as
   a long form with no indication of how much was left, which is the difference
   between "a guided booking" and "a developer's form".

   Numbers and ticks, not a bar: a bar shows proportion, and what somebody
   filling in a form wants is which named step they are on and what follows.

   State is carried by the marker's shape AND its text (see the .sr-only spans
   in the partial), never by colour alone. */

.journey { margin-bottom: var(--s-6); }

.journey-list {
  display: flex;
  align-items: flex-start;
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: none;
}

.journey-step {
  position: relative;
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  text-align: center;
  min-width: 0;
}

/* The connector. Drawn from each step back towards the previous one, so the
   first step has none and the line always sits behind the markers. */
.journey-step + .journey-step::before {
  content: "";
  position: absolute;
  top: 15px;
  right: 50%;
  left: -50%;
  height: 2px;
  background: var(--border);
  z-index: 0;
}

.journey-step.is-done::before,
.journey-step.is-current::before { background: var(--brand-600); }

.journey-marker {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--r-full);
  border: 2px solid var(--border);
  background: var(--surface-0);
  color: var(--ink-500);
  font-size: var(--t-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  transition: background-color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              color var(--dur) var(--ease-out);
}

.journey-marker .icon { width: 16px; height: 16px; }

.journey-step.is-done .journey-marker {
  background: var(--brand-600);
  border-color: var(--brand-600);
  color: #fff;
}

/* The current step is the only ring, which is what makes it findable at a
   glance without reading any of the labels. */
.journey-step.is-current .journey-marker {
  border-color: var(--brand-600);
  color: var(--brand-700);
  box-shadow: 0 0 0 4px var(--brand-100);
}

.journey-label {
  font-size: var(--t-sm);
  color: var(--ink-500);
  line-height: 1.3;
  min-width: 0;
}

.journey-step.is-current .journey-label { color: var(--ink-900); font-weight: 650; }
.journey-step.is-done .journey-label { color: var(--ink-600); }

/* At 375px four labels cannot sit side by side without wrapping to three lines
   each. The markers stay — they are the progress — and the labels reduce to the
   current one, which is the only label that answers "where am I". */
@media (max-width: 560px) {
  .journey-label { font-size: var(--t-xs); }
  .journey-step:not(.is-current) .journey-label { display: none; }
  .journey-step:not(.is-current) { flex: 0 0 auto; }
  .journey-step.is-current { flex: 1 1 auto; }
}

/* ── Order summary ───────────────────────────────────────────────────────
   The panel beside a checkout form. Rows of label/value with one emphasised
   total, and that is the whole component — it is deliberately not a table:
   at 375px a four-column table either scrolls sideways or shrinks the total
   to the size of a footnote, and the total is the one number that has to
   survive. Two elements per row, wrapping independently.

   Everything below is scoped under .summary. Nothing here is a token and
   nothing touches :root, so the landing page is untouched. */

.summary-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-4);
  padding: var(--s-2) 0;
  font-size: var(--t-base);
}

.summary-row dt, .summary-row-label { color: var(--ink-500); margin: 0; }

.summary-row dd, .summary-row-value {
  margin: 0;
  text-align: right;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.summary-rule { border: 0; border-top: 1px solid var(--border); margin: var(--s-3) 0; }

/* The total. Larger and heavier than everything above it, because the amount a
   buyer is about to be charged should not need looking for. */
.summary-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-4);
  padding-top: var(--s-3);
  border-top: 2px solid var(--ink-900);
}

.summary-total-label { font-weight: 650; font-size: var(--t-md); }

.summary-total-value {
  font-family: var(--font-display);
  font-size: var(--t-2xl);
  font-weight: 650;
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

/* ── Success state ───────────────────────────────────────────────────────
   The head of the confirmation page. A completed payment deserves a moment
   that reads as an outcome rather than as one more page of the form.

   The tick is aria-hidden and the heading carries the meaning, so nothing here
   depends on the icon or its colour being perceived. */

.success-head { text-align: center; }

.success-mark {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin: 0 auto var(--s-4);
  border-radius: var(--r-full);
  background: var(--ok-100);
  color: var(--ok-700);
  border: 2px solid var(--ok-600);
}

.success-mark .icon { width: 32px; height: 32px; }

/* One gentle scale-in. Motion is a nicety on a success screen and nothing
   depends on it, so it is dropped entirely for reduced-motion — not merely
   shortened. */
@media (prefers-reduced-motion: no-preference) {
  .success-mark { animation: success-pop var(--dur-slow) var(--ease-spring) both; }

  @keyframes success-pop {
    from { transform: scale(0.6); opacity: 0; }
    to   { transform: scale(1);   opacity: 1; }
  }
}

.success-head .lede { margin-inline: auto; }

/* ── Payment assurance line ──────────────────────────────────────────────
   Sits under the card-details heading. Deliberately a .hint and not a banner:
   it states one true fact (nothing is stored) and makes no claim about
   encryption, compliance or a secure gateway, none of which this deployment
   provides. */
.pay-assurance {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: 0;
}

.pay-assurance .icon { width: 16px; height: 16px; flex: 0 0 auto; }

/* ── Review step ─────────────────────────────────────────────────────────
   Scoped to classes only the review page adds. Every component underneath is
   one the journey already owns — .event-banner from the listing card, .meta-list
   from the same, .summary-total from the payment step, .figure-sm from the event
   page. Nothing shared is redefined. */

/* The event's artwork, sized for a summary rather than a page header. The event
   page's .detail-banner is 220px because there the artwork IS the hero; here it
   identifies a card the buyer scrolls past on the way to a button, and 220px of
   illustration above the amount due buries the amount. */
.review-banner {
  height: 132px;
  border-bottom: 1px solid var(--border);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}

/* Static. .cat-art carries a hover scale for the listing cards, where the whole
   card is a link and the movement answers a pointer. This card is not clickable,
   so the same transform would promise a click that never happens — the rule the
   design system already states for .card-hover. */
.review-banner .cat-art { transition: none; }

@media (max-width: 640px) {
  .review-banner { height: 104px; }
}

/* Date, venue and organiser. .meta-item truncates to one line by design, which is
   right in a grid of listing cards that must stay the same height and wrong here:
   this is the confirmation of what somebody is paying for, and a venue that ends
   in an ellipsis is a venue they cannot check. */
.review-meta { gap: var(--s-3); font-size: var(--t-md); }
.review-meta .meta-item > span { overflow: visible; text-overflow: clip; white-space: normal; }
.review-meta .meta-item { align-items: flex-start; }
.review-meta .meta-item .icon { margin-top: 2px; }

/* Two columns of buyer details, collapsing once they genuinely will not fit.
   .grid-2's auto-fit needs 280px and an email address routinely exceeds it, so
   the pair was breaking to one column on a laptop while the card stayed wide. */
.review-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-5) var(--s-4); }

/* An email is one long unbreakable token; without this it widens the grid track
   and pushes the card into a horizontal scroll on a phone. */
.review-facts .figure-value { overflow-wrap: anywhere; }

@media (max-width: 560px) {
  .review-facts { grid-template-columns: 1fr; }
}

/* ── Attendee list ───────────────────────────────────────────────────────
   Still an <ol>: the names are ordered, one per place. The browser's own marker
   sat in the list's outside gutter — beyond the card's padding — so the names
   hung on an indent that lined up with nothing else on the page.

   The counter is generated, not typed, so the numbering cannot drift from the
   list it labels. */
.attendee-list {
  list-style: none;
  counter-reset: attendee;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.attendee-item {
  counter-increment: attendee;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

/* Same disc as the journey marker, one step down in size, so "place 2 of 3" and
   "step 2 of 4" read as the same idea in the same visual language. */
.attendee-item::before {
  content: counter(attendee);
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-full);
  background: var(--surface-0);
  border: 1px solid var(--border-strong);
  font-size: var(--t-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink-600);
}

.attendee-item > span { min-width: 0; overflow-wrap: anywhere; font-weight: 600; }

/* ── Pay panel ───────────────────────────────────────────────────────────
   The amount and the button that agrees to it. Same treatment the event page's
   ticket panel gets — a firmer edge and the next shadow step, no new colour —
   because this is the one card on the page that is about to take money. */
.pay-panel { border-color: var(--border-strong); box-shadow: var(--shadow-md); }

/* .summary-total's 2px rule already separates the figure from what is above it,
   so the card's own .stack margin on top of that reads as a gap rather than a
   division. */
.pay-panel .summary-total { margin-top: var(--s-2); }

/* The actions, given room to be the end of the page rather than one more row.

   align-items:stretch rather than a min-height on the secondary button: .btn-lg's
   rendered height is its min-height plus its own padding and line-height (54 → 58
   as it stands), so any number hard-coded here is right until the button's padding
   changes. Stretching makes the row itself the authority, and the pair reads as one
   control group whatever .btn-lg becomes. */
.pay-actions {
  margin-top: var(--s-5);
  padding-top: var(--s-5);
  border-top: 1px solid var(--border);
  align-items: stretch;
}

/* On a phone the primary action goes full width and leads, with Back beneath it.
   A two-up row at 375px leaves "Continue to payment" about 180px wide and puts
   the destructive-feeling option under the thumb's resting position. */
@media (max-width: 520px) {
  .pay-actions { flex-direction: column-reverse; align-items: stretch; gap: var(--s-3); }
  .pay-actions .btn { width: 100%; }
}

/* ── Booking reference ───────────────────────────────────────────────────
   The one string somebody reads down a phone or shows at the door. It was one
   of six equally-weighted .figure values, which made the single most useful
   thing on the confirmation page exactly as prominent as "Booked".

   Monospace and letter-spaced so characters that look alike are told apart when
   read aloud, and user-select:all so one click selects the whole reference
   rather than half of it. */
.ref-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  padding: var(--s-5);
  border: 1px solid var(--brand-200);
  border-radius: var(--r-lg);
  background: var(--brand-100);
}

.ref-panel .stat-label { color: var(--brand-700); }

.ref-code {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--t-2xl);
  font-weight: 700;
  letter-spacing: 0.14em;
  line-height: 1.1;
  color: var(--ink-900);
  user-select: all;
  -webkit-user-select: all;
  word-break: break-all;
}

/* ── Card entry ──────────────────────────────────────────────────────────
   Grouped inputs for expiry and security code. Two columns down to 360px and
   then one: an expiry field squeezed to 40% of a small phone is a field people
   mistype, and a mistyped expiry reads as a declined card. */

.card-fields { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-4); }

@media (max-width: 360px) {
  .card-fields { grid-template-columns: 1fr; }
}

/* Card and security-code numbers, in a face where 0 and O differ. Letter
   spacing at 0.04em so sixteen digits stay scannable in groups without a
   script reformatting what somebody is halfway through typing. */
.input-card {
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
}

/* The demo banner. Deliberately loud: the one thing worse than a demo till that
   looks fake is one that does not. */
.demo-note {
  display: flex;
  gap: var(--s-3);
  align-items: flex-start;
  padding: var(--s-4);
  border-radius: var(--r-md);
  background: var(--warn-100);
  border: 1px dashed var(--warn-600);
  color: var(--warn-700);
  font-size: var(--t-sm);
}

.demo-note svg { flex: 0 0 auto; margin-top: 2px; }
.demo-note code {
  font-family: var(--font-mono);
  font-weight: 700;
  white-space: nowrap;
}

/* ── QR ticket ───────────────────────────────────────────────────────────── */

.ticket {
  background: var(--surface-0);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  max-width: 420px;
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}

.ticket:hover { transform: translateY(-3px); box-shadow: var(--shadow-xl); }

.ticket-head { padding: var(--s-5); background: linear-gradient(135deg, var(--brand-700), var(--brand-600)); color: #fff; }
.ticket-head h2 { color: #fff; margin: 0 0 var(--s-1); font-size: var(--t-xl); }
.ticket-head .org { font-size: var(--t-sm); opacity: 0.9; }

.ticket-qr { padding: var(--s-6); text-align: center; background: #fff; border-bottom: 1px dashed var(--border-strong); position: relative; }

/* Perforation notches, so it reads as a physical stub. */
.ticket-qr::before, .ticket-qr::after {
  content: "";
  position: absolute;
  bottom: -11px;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--surface-1);
  border: 1px solid var(--border-strong);
}

.ticket-qr::before { left: -12px; }
.ticket-qr::after  { right: -12px; }

/* The QR stays pure black on white in every theme — a tinted or inverted code
   is a code that will not scan. */
.ticket-qr img { width: 232px; height: 232px; image-rendering: pixelated; background: #fff; margin-inline: auto; }

.ticket-body { padding: var(--s-5); display: grid; gap: var(--s-3); }
.ticket-row { display: flex; justify-content: space-between; gap: var(--s-4); font-size: var(--t-base); }
.ticket-row dt { color: var(--ink-500); margin: 0; }
.ticket-row dd { margin: 0; font-weight: 600; text-align: right; }
.ticket-code { font-family: var(--font-mono); font-size: var(--t-md); letter-spacing: 0.1em; word-break: break-all; }

/* ── Gate scanner ────────────────────────────────────────────────────────── */

.scanner-shell { max-width: 560px; margin-inline: auto; }
.scanner-view { position: relative; background: #000; border-radius: var(--r-xl); overflow: hidden; aspect-ratio: 3 / 4; }
.scanner-view video { width: 100%; height: 100%; object-fit: cover; display: block; }

.scanner-reticle {
  position: absolute;
  inset: 18%;
  border: 3px solid rgba(255, 255, 255, 0.85);
  border-radius: var(--r-lg);
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.35);
  pointer-events: none;
}

/* Full-bleed verdict. A volunteer at a noisy door must read the answer at
   arm's length in under a second, so it takes the whole panel and always
   carries a word as well as a colour. */
.scan-verdict { border-radius: var(--r-xl); padding: var(--s-6) var(--s-5); text-align: center; border: 2px solid; }
.scan-verdict h2 { font-size: var(--t-3xl); margin: 0 0 var(--s-1); color: inherit; }
.scan-verdict p { margin: 0; font-size: var(--t-md); }
.scan-verdict .who { margin-top: var(--s-3); font-size: var(--t-lg); font-weight: 650; }

.scan-admitted  { background: var(--ok-100); border-color: var(--ok-600); color: var(--ok-700); }
.scan-duplicate { background: var(--warn-100); border-color: var(--warn-600); color: var(--warn-700); }
.scan-rejected  { background: var(--danger-100); border-color: var(--danger-600); color: var(--danger-700); }

.scan-log { font-size: var(--t-sm); }
.scan-log li { padding: var(--s-2) 0; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; gap: var(--s-4); }
.scan-log li:last-child { border-bottom: 0; }

.offline-note {
  background: var(--warn-100);
  color: var(--warn-700);
  border: 1px solid var(--warn-600);
  border-radius: var(--r-md);
  padding: var(--s-3) var(--s-4);
  font-size: var(--t-sm);
  font-weight: 600;
}

/* ── Steps (legacy landing block) ────────────────────────────────────────── */

.steps { list-style: none; margin: var(--s-6) 0 0; padding: 0; display: grid; gap: var(--s-6); grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
.step { position: relative; }

.step-number {
  display: inline-flex; align-items: center; justify-content: center;
  width: 48px; height: 48px;
  border-radius: 50%;
  background: linear-gradient(140deg, var(--brand-100), var(--gold-100));
  color: var(--brand-700);
  border: 1px solid var(--brand-200);
  font-family: var(--font-display);
  font-size: 1.25rem; font-weight: 600;
  margin-bottom: var(--s-4);
}

.step-title { font-size: var(--t-lg); margin: 0 0 var(--s-2); font-family: var(--font-sans); font-weight: 650; }
.step-text { color: var(--ink-500); margin: 0; }

/* ============================================================================
   16. MOTION
   ========================================================================= */

/* Applied by JS only after IntersectionObserver is confirmed available and the
   visitor has not asked for reduced motion. Without .js-motion, everything is
   simply already in place — an invisible site is far worse than an unanimated one. */
.js-motion .reveal {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
  transition-delay: calc(var(--i, 0) * 70ms);
}

.js-motion .reveal.is-visible { opacity: 1; transform: none; }

@keyframes rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

.js-motion .page-body > .wrap,
.js-motion .page-body > .wrap-narrow { animation: rise var(--dur-slow) var(--ease-out) both; }

.js-motion .hero-line { animation: rise 0.6s var(--ease-out) both; animation-delay: calc(var(--d, 0) * 90ms); }

.alert { animation: rise var(--dur) var(--ease-out) both; }

@keyframes verdict-in { 0% { transform: scale(0.94); opacity: 0; } 60% { transform: scale(1.02); } 100% { transform: scale(1); opacity: 1; } }
.scan-verdict { animation: verdict-in var(--dur) var(--ease-spring) both; }

/* "3 left" pulses four times then stops — emphasis, never the only signal. */
@keyframes pulse-ring {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn-600) 40%, transparent); }
  50%      { box-shadow: 0 0 0 6px color-mix(in srgb, var(--warn-600) 0%, transparent); }
}

.badge-warn.is-scarce { animation: pulse-ring 2.4s var(--ease-out) 4; }

/* ============================================================================
   17. UTILITIES
   ========================================================================= */

.muted { color: var(--ink-500); }
.small { font-size: var(--t-sm); }
.tiny { font-size: var(--t-xs); }
.strong { font-weight: 650; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -9999px; top: 0;
  background: var(--brand-600);
  color: #fff;
  padding: var(--s-3) var(--s-5);
  z-index: 100;
  border-radius: 0 0 var(--r-md) 0;
}

.skip-link:focus { left: 0; color: #fff; }

/* ============================================================================
   18. RESPONSIVE
   ========================================================================= */

@media (max-width: 700px) {
  :root { --nav-h: 64px; }
  body { font-size: var(--t-base); }
  .brand-sub { display: none; }
  .section { padding: var(--s-9) 0; }
  .page-body { padding: var(--s-6) 0 var(--s-9); }
  .hero { padding: var(--s-9) 0 var(--s-8); }
  .card { padding: var(--s-4); }
  .card-lg { padding: var(--s-5); }
  .feature { padding: var(--s-5); }
  .cta-band { padding: var(--s-7) var(--s-5); }
  .hero-figures { gap: var(--s-5); }
  .stat-value { font-size: 1.8rem; }
}

@media print {
  .masthead, .foot, .portal-bar, .btn, .skip-link, .mesh, .carousel-controls, .carousel-dots { display: none !important; }
  body { background: #fff; font-size: 12pt; }
  .ticket { box-shadow: none; border: 1px solid #999; }
  .card, .stat, .event-card { box-shadow: none; border: 1px solid #ccc; }

  /* The confirmation page is the record of a booking until passes are issued, so printing it has
     to produce the booking rather than the website. Anything that only makes sense on screen —
     the progress indicator, breadcrumbs, the action row — comes off. */
  .no-print, .journey, .crumbs { display: none !important; }

  /* Never a page break through the reference or the ticket table: a booking reference split
     across two sheets is a reference nobody can read back. */
  .ref-panel, .table, .success-head { break-inside: avoid; page-break-inside: avoid; }

  /* Ink. The brand wash and the tinted success disc cost a lot of toner and carry no meaning on
     paper, where the heading already says what happened. */
  .ref-panel { background: #fff !important; border: 1px solid #999 !important; }
  .success-mark { display: none !important; }
  .success-head { text-align: left; }

  /* Badges lose their fill but must stay legible — status is text, and on paper it is the only
     way "Confirmed" is conveyed at all. */
  .badge { background: #fff !important; border: 1px solid #999 !important; color: #000 !important; }
}

/* ============================================================================
   19. REDUCED MOTION — the single switch that disables the whole motion layer
   ========================================================================= */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* Critical: animated elements must LAND VISIBLE, not sit at their opening
     frame forever. Vestibular safety must never cost someone the content. */
  .js-motion .reveal { opacity: 1 !important; transform: none !important; }
  .js-motion .hero-line { animation: none !important; opacity: 1 !important; transform: none !important; }

  .mesh span { animation: none !important; }
  .float-chip { animation: none !important; }
  .hero-panel { transform: none !important; }
  .carousel.is-enhanced .carousel-track { transition: none !important; }
  .hero-art { transform: translateY(-50%) !important; }
}

/* ============================================================================
   20. PUBLIC WEBSITE (/welcome)
   ========================================================================= */

/* The public landing page. Everything here extends the system above rather than
   forming a second one: the sections reuse .wrap, .section, .section-head,
   .eyebrow, .lede, .timeline, .faq, .cta-band, .alert, .reveal and the form
   primitives, and the colour, type, spacing, shadow and easing tokens are the
   ones declared at the top of this file — which is what gives the page a dark
   theme for free.

   Prefixed .wl- so it is obvious which rules belong to the public site and which
   belong to the application. The handful of tokens below are scoped to .wl-body
   rather than :root for the same reason: the landing page wants larger radii and
   a dark canvas, and neither should leak into the portal.

   Placed after the reduced-motion switch above, so anything animated here
   carries its own reduced-motion rule at the end of the section. */

.wl-body {
  --wl-r-sm: 12px;
  --wl-r-md: 16px;
  --wl-r-lg: 22px;
  --wl-r-xl: 28px;
  --wl-r-2xl: 36px;

  /* Hero canvas. Fixed rather than tokenised light/dark: this panel is dark in
     both themes, and a "dark surface" that inverts is not a canvas. */
  --wl-canvas-1: #140f11;
  --wl-canvas-2: #1d1417;
  --wl-canvas-3: #241a1e;

  background: var(--surface-1);
}

/* Filtering hides cards with the hidden attribute, and the UA's [hidden] rule
   loses to any class that sets display. Without this, "hidden" cards stay put. */
[hidden] { display: none !important; }

/* ── Buttons ─────────────────────────────────────────────────────────────── */

.wl-btn {
  --wl-bg: transparent;
  --wl-fg: var(--ink-900);
  --wl-bd: transparent;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 50px;
  padding: var(--s-3) var(--s-5);
  border-radius: var(--wl-r-sm);
  border: 1px solid var(--wl-bd);
  background: var(--wl-bg);
  color: var(--wl-fg);
  font: inherit;
  font-size: var(--t-base);
  font-weight: 650;
  letter-spacing: -0.005em;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  transition: background-color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              color var(--dur) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}

.wl-btn:active { transform: translateY(1px); }
.wl-btn-lg { min-height: 58px; padding: var(--s-4) var(--s-6); font-size: var(--t-md); border-radius: var(--wl-r-md); }
/* 44px, not 40: this is the "Register" button on every event card, so it is the
   most-tapped control on the page and it sits inside a dense grid where a miss
   lands on the card behind it. The label still reads small — only the box grew. */
.wl-btn-sm { min-height: 44px; padding: var(--s-2) var(--s-4); font-size: var(--t-sm); border-radius: 10px; }
.wl-btn-block { width: 100%; }

.wl-btn-primary { --wl-bg: var(--brand-600); --wl-fg: #fff; box-shadow: var(--shadow-sm); }
:root[data-theme="dark"] .wl-btn-primary { --wl-fg: var(--surface-0); }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .wl-btn-primary { --wl-fg: var(--surface-0); }
}
.wl-btn-primary:hover { --wl-bg: var(--brand-700); transform: translateY(-2px); box-shadow: var(--shadow-brand); }
:root[data-theme="dark"] .wl-btn-primary:hover { --wl-bg: var(--brand-500); }

/* A single sheen sweep on the primary action only. If every button glints, none
   of them reads as primary. */
.wl-btn-primary::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 -130%;
  width: 55%;
  transform: skewX(-18deg);
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.3), transparent);
  pointer-events: none;
}
.wl-btn-primary:hover::after { transition: left var(--dur-slow) var(--ease-out); left: 150%; }

.wl-btn-ghost { --wl-bg: var(--surface-0); --wl-fg: var(--ink-800); --wl-bd: var(--border-strong); box-shadow: var(--shadow-xs); }
.wl-btn-ghost:hover { --wl-bg: var(--surface-2); --wl-bd: var(--ink-300); transform: translateY(-2px); box-shadow: var(--shadow-sm); }

.wl-arrow { display: inline-flex; transition: transform var(--dur) var(--ease-out); }
.wl-btn:hover .wl-arrow { transform: translateX(4px); }
.wl-arrow svg { width: 19px; height: 19px; }

/* ── Pills ───────────────────────────────────────────────────────────────── */

.wl-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-3);
  border-radius: var(--r-full);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.6;
  white-space: nowrap;
  border: 1px solid transparent;
}

.wl-pill-brand { background: var(--brand-100); color: var(--brand-700); border-color: var(--brand-200); }
.wl-pill-flat  { background: var(--surface-2); color: var(--ink-600); border-color: var(--border); }
.wl-pill-warn  { background: var(--warn-100); color: var(--warn-700); border-color: color-mix(in srgb, var(--warn-600) 28%, transparent); }
.wl-pill-dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }

/* ── Scroll progress + fixed bar ─────────────────────────────────────────── */

.wl-progress {
  position: fixed;
  top: 0; left: 0;
  height: 3px;
  width: 100%;
  z-index: 120;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--brand-600), var(--gold-500));
  pointer-events: none;
}

/* Two states. Over the dark hero the bar is invisible with light contents; once
   the page scrolls it becomes a frosted light bar. Without that, a dark hero
   would force dark chrome on the entire document. */
.wl-nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  border-bottom: 1px solid transparent;
  transition: background-color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}

.wl-nav.is-stuck {
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom-color: var(--border);
  box-shadow: var(--shadow-sm);
}

.wl-nav-in { display: flex; align-items: center; gap: var(--s-5); min-height: var(--nav-h); }
.wl-nav-wrap { margin-left: auto; }

/* The brand yields before the controls do. With both flex children locked at
   flex:0 0 auto and a 24px gap, the bar needed ~448px of content box on a phone
   and got ~398px — which pushed the hamburger off-screen entirely and sliced the
   CTA in half. Nothing about that was visible in a document-overflow check,
   because .wl-nav is fixed and a fixed element's overflow never reaches
   documentElement.scrollWidth. So the brand now shrinks and truncates: whatever
   else changes, the controls stay on screen and tappable. */
.wl-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  text-decoration: none;
  color: #fff;
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  min-height: 44px;
}
.wl-nav.is-stuck .wl-brand { color: var(--ink-900); }
.wl-brand:hover { color: #fff; }
.wl-nav.is-stuck .wl-brand:hover { color: var(--ink-900); }

.wl-mark { width: 42px; height: 42px; flex: 0 0 auto; color: #fff; transition: transform var(--dur-slow) var(--ease-spring); }
.wl-nav.is-stuck .wl-mark { color: var(--brand-600); }
.wl-brand:hover .wl-mark { transform: rotate(-8deg) scale(1.07); }

.wl-brand-txt { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
.wl-brand-name { font-family: var(--font-display); font-size: 1.32rem; font-weight: 600; letter-spacing: -0.022em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wl-brand-sub { font-size: 0.62rem; letter-spacing: 0.16em; text-transform: uppercase; font-weight: 700; opacity: 0.62;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.wl-links { display: flex; align-items: center; gap: var(--s-1); list-style: none; margin: 0; padding: 0; }

.wl-links a:not(.wl-btn) {
  position: relative;
  display: block;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  text-decoration: none;
  font-size: var(--t-base);
  font-weight: 550;
  white-space: nowrap;
  color: rgba(255, 255, 255, 0.82);
  transition: color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}

.wl-nav.is-stuck .wl-links a:not(.wl-btn) { color: var(--ink-600); }
.wl-links a:not(.wl-btn):hover { color: #fff; background: rgba(255, 255, 255, 0.12); }
.wl-nav.is-stuck .wl-links a:not(.wl-btn):hover { color: var(--ink-900); background: var(--wash); }

.wl-links a:not(.wl-btn)::after {
  content: "";
  position: absolute;
  left: 50%; right: 50%; bottom: 3px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--brand-400), var(--gold-500));
  transition: left var(--dur) var(--ease-out), right var(--dur) var(--ease-out);
}

/* aria-current is a boolean here, not "page": this is a position within one
   document, not a separate page. */
.wl-links a:not(.wl-btn)[aria-current="true"]::after { left: var(--s-3); right: var(--s-3); }
.wl-links a:not(.wl-btn)[aria-current="true"] { color: #fff; }
.wl-nav.is-stuck .wl-links a:not(.wl-btn)[aria-current="true"] { color: var(--brand-600); }

.wl-nav-actions { display: flex; align-items: center; gap: var(--s-2); flex: 0 0 auto; }

.wl-icon-btn {
  width: 44px; height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 11px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

.wl-nav.is-stuck .wl-icon-btn { border-color: var(--border); background: var(--surface-0); color: var(--ink-600); }
.wl-icon-btn:hover { background: rgba(255, 255, 255, 0.18); transform: translateY(-1px); }
.wl-nav.is-stuck .wl-icon-btn:hover { background: var(--surface-2); color: var(--ink-900); }

.wl-nav-cta { --wl-bg: #fff; --wl-fg: var(--brand-700); }
.wl-nav-cta:hover { --wl-bg: var(--brand-050); --wl-fg: var(--brand-800); transform: translateY(-2px); }
.wl-nav.is-stuck .wl-nav-cta { --wl-bg: var(--brand-600); --wl-fg: #fff; }
.wl-nav.is-stuck .wl-nav-cta:hover { --wl-bg: var(--brand-700); --wl-fg: #fff; }
:root[data-theme="dark"] .wl-nav.is-stuck .wl-nav-cta { --wl-fg: var(--surface-0); }

.wl-burger { display: none; }

/* The panel's own copy of the primary action, revealed only once the bar's copy
   is hidden on the narrowest screens. Exactly one of the two is ever displayed,
   so assistive technology never sees a duplicated "Get started".

   Note the :not(.wl-btn) on every .wl-links a rule above. Without it those rules
   win at (0,1,1) against this one at (0,1,0) whatever the source order, and the
   panel CTA renders as a second "Get started" in the desktop nav row. */
.wl-menu-cta { display: none; }

/* Sign in, beside Get started. Outlined rather than a second filled button: two
   solid buttons side by side read as two primary actions, and only one of them is.

   THE COLOURS ARE STATED TWICE, ONCE PER BACKGROUND, and they have to be. The bar
   is transparent over the dark hero and light glass once stuck, so it is two
   different surfaces wearing one class. currentColor cannot bridge them: .wl-nav
   sets no colour of its own, so it inherits --ink-900 from the body and the label
   comes out dark on the dark hero — invisible, which is exactly what happened.
   The nav's own anchors solve it the same way, at .wl-links a:not(.wl-btn). */
.wl-nav-signin {
  --wl-bg: transparent;
  --wl-fg: #fff;
  --wl-bd: rgba(255, 255, 255, 0.34);
}

/* Matches the hover wash on the neighbouring nav anchors, so the two feel like
   one control strip rather than a link next to a button. */
.wl-nav-signin:hover {
  --wl-bg: rgba(255, 255, 255, 0.14);
  --wl-fg: #fff;
  --wl-bd: rgba(255, 255, 255, 0.55);
  transform: translateY(-2px);
}

/* Stuck: the bar becomes --glass-bg, so the label switches from hard white to the
   ink token and the border to a real one. Tokens, not literals, because --glass-bg
   and --ink-800 both invert with the theme — in dark mode the bar is dark and
   --ink-800 is light, in light mode both flip. A hard-coded colour here would be
   correct in exactly one of the two. */
.wl-nav.is-stuck .wl-nav-signin {
  --wl-fg: var(--ink-800);
  --wl-bd: var(--border-strong);
}

.wl-nav.is-stuck .wl-nav-signin:hover {
  --wl-bg: var(--wash);
  --wl-fg: var(--ink-900);
  --wl-bd: var(--ink-300);
}

/* The panel copy. The panel is --glass-bg, the same surface as the stuck bar, and
   it is never shown over the hero — so it only ever needs the ink pairing, in
   either theme. */
.wl-menu-signin {
  display: none;
  --wl-bg: transparent;
  --wl-fg: var(--ink-800);
  --wl-bd: var(--border-strong);
}

.wl-menu-signin:hover { --wl-bg: var(--wash); --wl-fg: var(--ink-900); }

@media (max-width: 1080px) {
  .wl-burger { display: inline-flex; }
  .wl-nav-wrap { margin-left: auto; }

  .wl-links {
    position: absolute;
    top: 100%; left: 0; right: 0;
    display: none;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-1);
    padding: var(--s-4) var(--s-5) var(--s-5);
    background: var(--glass-bg);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-lg);

    /* Six anchors plus a full-width CTA is taller than a phone held sideways, and
       a panel that runs off the bottom of a fixed header cannot be scrolled to —
       the items below the fold are simply unreachable. dvh over vh so the panel
       does not sit under a mobile browser's collapsing address bar; the vh line
       is the fallback for anything that does not know dvh. overscroll-behavior
       stops a flick inside the panel from scrolling the page behind it. */
    max-height: calc(100vh - var(--nav-h));
    max-height: calc(100dvh - var(--nav-h));
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .wl-links.is-open { display: flex; }
  .wl-links a:not(.wl-btn) { color: var(--ink-700); padding: var(--s-3) var(--s-4); font-size: var(--t-md); }
  .wl-links a:not(.wl-btn)::after { display: none; }
  .wl-links a:not(.wl-btn)[aria-current="true"] { color: var(--brand-600); background: var(--brand-100); }
  .wl-cta-text { display: none; }
}

/* ── Header at phone widths ───────────────────────────────────────────────────
   Three independent guards, because the bar must survive a long brand name, a
   narrow screen and a large system font at the same time:
     1. the strapline goes (the same width the application bar drops .brand-sub)
     2. the gap tightens, returning 12px to the content box
     3. the brand truncates rather than shoving the controls off-screen
   Measured content box at 320px is 288px; the bar needs ~260px after (1)-(3). */

@media (max-width: 700px) {
  .wl-nav-in { gap: var(--s-3); }
  .wl-brand-sub { display: none; }
  .wl-brand { gap: var(--s-2); }
  .wl-mark { width: 38px; height: 38px; }
  .wl-brand-name { font-size: 1.18rem; }
}

/* Below this the bar cannot hold brand + theme + CTA + hamburger without
   crowding the tap targets, so the CTA moves into the panel where it gets a
   full-width row instead of a 40px sliver. */
@media (max-width: 420px) {
  .wl-nav-cta { display: none; }
  .wl-menu-cta { display: inline-flex; width: 100%; margin-top: var(--s-2); }

  /* Sign in moves into the panel with the CTA, for the same reason and at the
     same width — the bar has room for the brand, the theme toggle and the
     hamburger, and nothing else. */
  .wl-nav-signin { display: none; }
  .wl-menu-signin { display: inline-flex; width: 100%; margin-top: var(--s-2); }

  .wl-nav-in { gap: var(--s-2); }
  /* The icon buttons deliberately do NOT shrink here. Dropping them to 40px was
     what bought room for the CTA, and the CTA is gone at this width anyway — so
     the space goes back to the two controls that are left, both of which are the
     only way to reach navigation on a phone. They stay at the 44px minimum. */
}

/* ── Hero ────────────────────────────────────────────────────────────────── */

.wl-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: calc(var(--nav-h) + var(--s-11)) 0 var(--s-12);
  color: #f7f2ef;
  background:
    radial-gradient(1100px 620px at 12% -12%, rgba(154, 52, 80, 0.5), transparent 62%),
    radial-gradient(900px 520px at 88% 4%, rgba(201, 151, 63, 0.26), transparent 58%),
    linear-gradient(175deg, var(--wl-canvas-1) 0%, var(--wl-canvas-2) 55%, var(--wl-canvas-3) 100%);
}

/* A fine grid dissolving downward: structure without a background image. */
.wl-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  opacity: 0.5;
  background-image: linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
                    linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 20%, transparent 78%);
          mask-image: radial-gradient(120% 90% at 50% 0%, #000 20%, transparent 78%);
}

.wl-orb { position: absolute; border-radius: 50%; filter: blur(90px); z-index: -1; pointer-events: none; opacity: 0.55; }
.wl-orb-1 { width: 560px; height: 560px; top: -220px; right: -100px;
  background: radial-gradient(circle, rgba(184, 85, 108, 0.75), transparent 68%);
  animation: wl-drift-a 26s ease-in-out infinite alternate; }
.wl-orb-2 { width: 460px; height: 460px; bottom: -200px; left: -120px;
  background: radial-gradient(circle, rgba(201, 151, 63, 0.55), transparent 68%);
  animation: wl-drift-b 32s ease-in-out infinite alternate; }

@keyframes wl-drift-a { to { transform: translate3d(-60px, 50px, 0) scale(1.16); } }
@keyframes wl-drift-b { to { transform: translate3d(70px, -40px, 0) scale(1.12); } }

.wl-hero-grid { display: grid; grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr); gap: var(--s-10); align-items: center; }

.wl-hero-title { font-size: var(--t-5xl); color: #fff; margin-bottom: var(--s-5); letter-spacing: -0.034em; }
.wl-hero-title span { display: block; }

/* Gradient on the emphasis line only. A fully gradient headline is the most
   over-used effect in the genre and it costs contrast; one line earns it. */
.wl-shine {
  background: linear-gradient(102deg, #f3c9d3 4%, #e9aeba 32%, #e9c78c 74%, #c9973f 98%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .wl-shine { color: #e9aeba; -webkit-text-fill-color: currentColor; }
}

.wl-hero-lede { font-size: clamp(1.05rem, 1rem + 0.35vw, 1.28rem); color: rgba(255, 255, 255, 0.76);
  max-width: 35rem; margin-bottom: var(--s-7); line-height: 1.6; }

.wl-hero-cta { display: flex; gap: var(--s-3); flex-wrap: wrap; margin-bottom: var(--s-7); }
.wl-hero .wl-btn-primary { --wl-bg: #fff; --wl-fg: var(--brand-700); }
.wl-hero .wl-btn-primary:hover { --wl-bg: var(--brand-050); --wl-fg: var(--brand-800); box-shadow: 0 14px 34px -12px rgba(0, 0, 0, 0.6); }
.wl-hero .wl-btn-ghost { --wl-bg: rgba(255, 255, 255, 0.07); --wl-fg: #fff; --wl-bd: rgba(255, 255, 255, 0.26); }
.wl-hero .wl-btn-ghost:hover { --wl-bg: rgba(255, 255, 255, 0.14); --wl-bd: rgba(255, 255, 255, 0.5); }

.wl-announce {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1);
  border-radius: var(--r-full);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.9);
  font-size: var(--t-sm);
  font-weight: 600;
  margin-bottom: var(--s-6);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.wl-announce b { background: #fff; color: var(--brand-700); padding: 0.22rem var(--s-3);
  border-radius: var(--r-full); font-size: var(--t-xs); letter-spacing: 0.04em; text-transform: uppercase; }
.wl-announce span { padding-right: var(--s-3); }

.wl-hero-trust { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-6); list-style: none; margin: 0; padding: 0;
  color: rgba(255, 255, 255, 0.62); font-size: var(--t-sm); font-weight: 550; }
.wl-hero-trust li { display: inline-flex; align-items: center; gap: var(--s-2); }
.wl-hero-trust svg { width: 19px; height: 19px; color: var(--gold-500); }

/* ── Hero product vignette ───────────────────────────────────────────────── */

.wl-stage { position: relative; }

.wl-panel {
  position: relative;
  border-radius: var(--wl-r-2xl);
  padding: var(--s-5);
  background: linear-gradient(165deg, rgba(255, 255, 255, 0.13), rgba(255, 255, 255, 0.05));
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 40px 90px -30px rgba(0, 0, 0, 0.75);
  backdrop-filter: blur(22px) saturate(1.3);
  -webkit-backdrop-filter: blur(22px) saturate(1.3);
  transform: perspective(1500px) rotateY(-9deg) rotateX(4deg);
  transition: transform var(--dur-slow) var(--ease-out);
}

.wl-stage:hover .wl-panel { transform: perspective(1500px) rotateY(-3deg) rotateX(1deg); }

.wl-panel-bar { display: flex; align-items: center; gap: var(--s-1);
  padding-bottom: var(--s-4); margin-bottom: var(--s-4); border-bottom: 1px solid rgba(255, 255, 255, 0.12); }
.wl-panel-bar i { width: 9px; height: 9px; border-radius: 50%; background: rgba(255, 255, 255, 0.28); display: block; }
.wl-panel-bar span { margin-left: auto; font-size: 0.72rem; letter-spacing: 0.09em; text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45); font-weight: 700; }

/* The inner card stays light in both themes: it is a picture of the product,
   not a surface of this page. */
.wl-pcard { background: rgba(255, 255, 255, 0.96); border-radius: var(--wl-r-md); padding: var(--s-4); color: #15100f; }
.wl-pcard-top { display: flex; gap: var(--s-4); align-items: center; }
.wl-pcard-top > span:last-child { display: flex; flex-direction: column; gap: 2px; }

.wl-datechip { flex: 0 0 auto; width: 54px; text-align: center; border-radius: 12px; padding: var(--s-1) 0;
  background: linear-gradient(160deg, var(--brand-600), var(--brand-500)); color: #fff; line-height: 1.05;
  box-shadow: 0 6px 14px -6px rgba(122, 36, 56, 0.7); }
:root[data-theme="dark"] .wl-datechip { background: linear-gradient(160deg, #7a2438, #97354b); }
.wl-datechip b { display: block; font-family: var(--font-display); font-size: 1.4rem; font-weight: 600; }
.wl-datechip i { display: block; font-style: normal; font-size: 0.6rem; letter-spacing: 0.12em; font-weight: 700; opacity: 0.86; }

.wl-pcard-title { font-family: var(--font-display); font-size: 1.12rem; font-weight: 600; line-height: 1.25; }
.wl-pcard-sub { font-size: 0.82rem; color: #6d625b; }
.wl-pcard-split { display: flex; justify-content: space-between; align-items: center;
  margin-top: var(--s-3); padding-top: var(--s-3); border-top: 1px solid #ece5df; }
.wl-pcard-price { font-weight: 750; font-size: 1.05rem; }
.wl-mini-btn { background: #7a2438; color: #fff; border-radius: 9px; padding: 0.42rem var(--s-3); font-size: 0.78rem; font-weight: 700; }

.wl-flow { list-style: none; margin: var(--s-4) 0 0; padding: 0; display: grid; gap: var(--s-2); }
.wl-flow li { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4); border-radius: 13px;
  background: rgba(255, 255, 255, 0.07); border: 1px solid rgba(255, 255, 255, 0.1);
  font-size: 0.88rem; font-weight: 600; color: rgba(255, 255, 255, 0.9); }
.wl-flow li em { font-style: normal; margin-left: auto; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase; color: #8fd6ae; }

.wl-tick { width: 26px; height: 26px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  background: rgba(124, 195, 156, 0.16); border: 1px solid rgba(124, 195, 156, 0.5); color: #8fd6ae; flex: 0 0 auto; }
.wl-tick svg { width: 15px; height: 15px; }

.wl-chip-float {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  background: var(--surface-0);
  color: var(--ink-900);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  font-size: var(--t-sm);
  font-weight: 650;
  white-space: nowrap;
}

.wl-chip-float svg { width: 19px; height: 19px; color: var(--brand-600); }
.wl-chip-a { top: -20px; left: -28px; animation: wl-bob 7s ease-in-out infinite; }
.wl-chip-b { bottom: -22px; right: -16px; animation: wl-bob 8.5s ease-in-out infinite reverse; }

@keyframes wl-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }

@media (max-width: 1000px) {
  .wl-hero { padding: calc(var(--nav-h) + var(--s-9)) 0 var(--s-10); }
  .wl-hero-grid { grid-template-columns: 1fr; gap: var(--s-8); }
  .wl-panel { transform: none; }
  .wl-stage:hover .wl-panel { transform: none; }
  .wl-chip-float { display: none; }
}

/* ── Capability strip ────────────────────────────────────────────────────── */

.wl-strip { border-block: 1px solid var(--border); background: var(--surface-0); padding: var(--s-7) 0; }
.wl-strip-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-5); }
.wl-strip-item { display: flex; flex-direction: column; gap: var(--s-1); padding-left: var(--s-4); border-left: 2px solid var(--brand-200); }

.wl-strip-num {
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 1.4rem + 1.2vw, 2.6rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.03em;
  background: linear-gradient(120deg, var(--brand-600), var(--gold-600));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .wl-strip-num { color: var(--brand-600); -webkit-text-fill-color: currentColor; }
}

.wl-strip-lab { font-size: var(--t-sm); color: var(--ink-500); line-height: 1.5; }
.wl-strip-lab b { display: block; color: var(--ink-800); font-weight: 650; font-size: var(--t-base); }

@media (max-width: 900px) { .wl-strip-grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-6); } }
@media (max-width: 520px) { .wl-strip-grid { grid-template-columns: 1fr; } }

/* ── Problem / solution ──────────────────────────────────────────────────── */

.wl-split { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: var(--s-6); align-items: stretch; }

.wl-pcol {
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--wl-r-xl);
  padding: var(--s-7);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}

.wl-pcol:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }

/* The resolved column is raised by an edge and a hairline, not a loud fill — the
   comparison has to stay readable rather than become a poster. */
.wl-pcol.is-win { border-color: var(--brand-200); box-shadow: var(--shadow-md); position: relative; overflow: hidden; }
.wl-pcol.is-win::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--brand-600), var(--gold-500)); }

.wl-pcol-head { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3);
  padding-bottom: var(--s-5); margin-bottom: var(--s-5); border-bottom: 1px solid var(--border); }

.wl-vs { align-self: center; width: 50px; height: 50px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; background: var(--surface-0); border: 1px solid var(--border-strong);
  color: var(--brand-600); box-shadow: var(--shadow-sm); flex: 0 0 auto; }

.wl-plist { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-5); }
.wl-plist li { display: grid; grid-template-columns: 42px 1fr; gap: var(--s-4); align-items: start; }
.wl-plist li > span:last-child { display: flex; flex-direction: column; gap: 2px; }
.wl-plist b { font-weight: 650; font-size: var(--t-base); }
.wl-plist li > span:last-child > span { color: var(--ink-500); font-size: var(--t-sm); line-height: 1.6; }

.wl-plist-ic { width: 42px; height: 42px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 12px; background: linear-gradient(145deg, var(--brand-100), var(--gold-100));
  border: 1px solid var(--brand-200); color: var(--brand-600); flex: 0 0 auto; }
.wl-plist-ic svg { width: 21px; height: 21px; }

/* The "before" column is stated, not scolded — muted rather than marked with
   error colours. It describes what most organisations do now. */
.wl-plist.is-dull .wl-plist-ic { background: var(--surface-2); border-color: var(--border); color: var(--ink-500); }

@media (max-width: 940px) {
  .wl-split { grid-template-columns: 1fr; }
  .wl-vs { transform: rotate(90deg); }
}

/* ── Feature grid ────────────────────────────────────────────────────────── */

.wl-bento { display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--s-5); }
.wl-audiences { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr)); gap: var(--s-5); }

.wl-cell {
  position: relative;
  overflow: hidden;
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--wl-r-xl);
  padding: var(--s-6);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}

.wl-cell::after { content: ""; position: absolute; inset: 0; pointer-events: none; opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
  background: radial-gradient(560px circle at 15% 0%, var(--brand-050), transparent 62%); }

.wl-cell:hover { transform: translateY(-6px); box-shadow: var(--shadow-lg); border-color: var(--border-strong); }
.wl-cell:hover::after { opacity: 1; }
.wl-cell:hover .wl-icon { transform: scale(1.08) rotate(-5deg); }
.wl-cell > * { position: relative; z-index: 1; }
.wl-cell h3 { font-family: var(--font-sans); font-weight: 650; font-size: var(--t-lg); margin: 0; }
.wl-cell p { color: var(--ink-500); font-size: var(--t-base); margin: 0; }

.wl-icon { width: 52px; height: 52px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 15px; background: linear-gradient(145deg, var(--brand-100), var(--gold-100));
  border: 1px solid var(--brand-200); color: var(--brand-600); transition: transform var(--dur) var(--ease-spring); }

/* Spans come from WelcomeFeatureCard.Span, so only the values that model actually
   uses are declared — a .span3 nothing selects is weight for nothing. */
.span2 { grid-column: span 2; }
.span4 { grid-column: span 4; }
.span6 { grid-column: span 6; }

@media (max-width: 1000px) {
  .wl-bento { grid-template-columns: repeat(2, 1fr); }
  .span2, .span4, .span6 { grid-column: span 2; }
}
@media (max-width: 620px) {
  .wl-bento { grid-template-columns: 1fr; }
  .span2, .span4, .span6 { grid-column: span 1; }
}

/* Miniature interfaces. They are what stop the cards reading as an icon set with
   captions — and they are drawn in markup so they theme themselves and cost no
   image request. */
.wl-mini { margin-top: auto; border-radius: var(--wl-r-md); border: 1px solid var(--border); background: var(--surface-2); padding: var(--s-3); overflow: hidden; }

.wl-mini-cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.wl-mini-cal i { aspect-ratio: 1; border-radius: 5px; background: var(--surface-0); border: 1px solid var(--border); display: block; }
.wl-mini-cal i.on { background: linear-gradient(150deg, var(--brand-600), var(--brand-500)); border-color: transparent;
  box-shadow: 0 3px 8px -3px rgba(122, 36, 56, 0.6); }
.wl-mini-cal i.dim { opacity: 0.45; }

.wl-mini-rows { display: grid; gap: var(--s-2); }
.wl-mini-row { display: flex; justify-content: space-between; align-items: center; font-size: 0.78rem; color: var(--ink-600); font-weight: 600; }
.wl-mini-row b { font-variant-numeric: tabular-nums; color: var(--ink-900); }
.wl-mini-row.total { border-top: 1px dashed var(--border-strong); padding-top: var(--s-2); color: var(--ink-900); }

.wl-mini-qr { display: grid; grid-template-columns: repeat(9, 1fr); gap: 3px; max-width: 150px; margin-inline: auto; }
.wl-mini-qr i { aspect-ratio: 1; border-radius: 2px; background: var(--border-strong); display: block; }
.wl-mini-qr i.on { background: var(--ink-900); }

.wl-mini-bars { display: flex; align-items: flex-end; gap: 8px; height: 82px; }
.wl-mini-bars i { flex: 1; border-radius: 5px 5px 0 0; background: linear-gradient(180deg, var(--brand-400), var(--brand-600)); display: block; opacity: 0.85; }

.wl-mini-people { display: flex; }
.wl-av { width: 36px; height: 36px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.7rem; font-weight: 750; color: var(--brand-700);
  background: linear-gradient(145deg, var(--brand-100), var(--gold-100));
  border: 2px solid var(--surface-0); margin-left: -10px; }
.wl-av:first-child { margin-left: 0; }

/* ── Product tour ────────────────────────────────────────────────────────── */

.wl-tour { display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); gap: var(--s-8); align-items: start; }
.wl-tour-tabs { display: grid; gap: var(--s-3); list-style: none; margin: 0; padding: 0; }

.wl-tour-tab {
  width: 100%;
  display: grid;
  grid-template-columns: 44px 1fr;
  gap: var(--s-4);
  text-align: left;
  align-items: start;
  padding: var(--s-4);
  border-radius: var(--wl-r-md);
  border: 1px solid var(--border);
  background: var(--surface-0);
  color: var(--ink-900);
  font: inherit;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}

.wl-tour-tab:hover { transform: translateX(4px); border-color: var(--brand-200); }
.wl-tour-tab[aria-selected="true"] { border-color: var(--brand-600); box-shadow: var(--shadow-md); }
.wl-tour-tab[aria-selected="true"] .wl-tour-n { background: var(--brand-600); color: #fff; border-color: var(--brand-600); }
:root[data-theme="dark"] .wl-tour-tab[aria-selected="true"] .wl-tour-n { color: var(--surface-0); }

.wl-tour-n { width: 44px; height: 44px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); background: var(--surface-2); color: var(--ink-500);
  font-family: var(--font-display); font-size: 1.05rem; font-weight: 600;
  transition: background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out); }

.wl-tour-tab b { display: block; font-size: var(--t-md); font-weight: 650; margin-bottom: 2px; }
.wl-tour-tab span span { display: block; color: var(--ink-500); font-size: var(--t-sm); line-height: 1.55; }

.wl-tour-stage { position: relative; overflow: hidden; border-radius: var(--wl-r-2xl); border: 1px solid var(--border);
  background: var(--surface-0); box-shadow: var(--shadow-lg); padding: var(--s-7); min-height: 400px; }
.wl-tour-stage::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 200px; pointer-events: none;
  background: radial-gradient(420px circle at 75% 0%, var(--brand-050), transparent 70%); }

.wl-tour-panel { display: none; position: relative; }
.wl-tour-panel.is-on { display: block; }
.js-motion .wl-tour-panel.is-on { animation: wl-fade-up 0.45s var(--ease-out) both; }

@keyframes wl-fade-up { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

.wl-tour-panel .wl-pill { margin-bottom: var(--s-4); }
.wl-tour-panel h3 { font-size: var(--t-2xl); margin-bottom: var(--s-2); }
.wl-tour-panel > p { color: var(--ink-600); margin-bottom: var(--s-5); }

.wl-tour-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); }
.wl-tour-list li { display: grid; grid-template-columns: 22px 1fr; gap: var(--s-3); align-items: start;
  color: var(--ink-600); font-size: var(--t-base); }
.wl-tour-list svg { width: 20px; height: 20px; color: var(--ok-600); margin-top: 3px; }

@media (max-width: 980px) {
  .wl-tour { grid-template-columns: 1fr; gap: var(--s-6); }
  .wl-tour-stage { min-height: 0; }
}

/* ── Event discovery ─────────────────────────────────────────────────────── */

.wl-filters { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-6); }

.wl-filter {
  min-height: 44px;
  padding: var(--s-2) var(--s-4);
  border-radius: var(--r-full);
  border: 1px solid var(--border-strong);
  background: var(--surface-0);
  color: var(--ink-600);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: 650;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

.wl-filter:hover { border-color: var(--brand-400); color: var(--ink-900); transform: translateY(-2px); }
.wl-filter[aria-pressed="true"] { background: var(--brand-600); border-color: var(--brand-600); color: #fff; box-shadow: var(--shadow-brand); }
:root[data-theme="dark"] .wl-filter[aria-pressed="true"] { color: var(--surface-0); }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .wl-filter[aria-pressed="true"] { color: var(--surface-0); }
}

.wl-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr)); gap: var(--s-5); }

.wl-ecard {
  display: flex;
  flex-direction: column;
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--wl-r-xl);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}

.wl-ecard:hover, .wl-ecard:focus-within { transform: translateY(-7px); box-shadow: var(--shadow-lg); border-color: var(--border-strong); }

.wl-ecard-art { height: 150px; position: relative; overflow: hidden; background: var(--surface-2);
  border-bottom: 1px solid var(--border); display: flex; align-items: flex-start; padding: var(--s-3) var(--s-4); }
.wl-ecard:hover .cat-art { transform: scale(1.08); }

/* Per-motif tint, so a set of cards reads as distinct things rather than one
   shape repeated in one colour. */
.wl-ecard-art[data-motif="gather"] { background: var(--brand-100); } .wl-ecard-art[data-motif="gather"] .wl-art { color: var(--brand-600); }
.wl-ecard-art[data-motif="learn"]  { background: #eeece7; }          .wl-ecard-art[data-motif="learn"] .wl-art  { color: #5d5a4e; }
.wl-ecard-art[data-motif="give"]   { background: #f7ede4; }          .wl-ecard-art[data-motif="give"] .wl-art   { color: #a8562a; }
.wl-ecard-art[data-motif="play"]   { background: #e8f0ea; }          .wl-ecard-art[data-motif="play"] .wl-art   { color: #2f6b4f; }
.wl-ecard-art[data-motif="stage"]  { background: #f0ecf6; }          .wl-ecard-art[data-motif="stage"] .wl-art  { color: #6b4fa0; }
.wl-ecard-art[data-motif="family"] { background: var(--gold-100); }  .wl-ecard-art[data-motif="family"] .wl-art { color: var(--gold-600); }

/* Dark theme drops the tints: a pale wash that worked on white becomes a glare
   panel on a dark surface. The motif keeps its own colour. */
:root[data-theme="dark"] .wl-ecard-art,
:root[data-theme="dark"] .wl-ecard-art[data-motif] { background: var(--surface-2); }
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .wl-ecard-art,
  :root:where(:not([data-theme="light"])) .wl-ecard-art[data-motif] { background: var(--surface-2); }
}

.wl-ecard-chip { position: relative; z-index: 1; background: var(--surface-0); border: 1px solid var(--border-strong);
  border-radius: 11px; padding: var(--s-1) var(--s-2); text-align: center; line-height: 1.05;
  box-shadow: var(--shadow-sm); transition: transform var(--dur) var(--ease-spring); }
.wl-ecard:hover .wl-ecard-chip { transform: translateY(-2px) scale(1.05); }
.wl-ecard-chip b { display: block; font-family: var(--font-display); font-size: 1.28rem; font-weight: 600; }
.wl-ecard-chip i { display: block; font-style: normal; font-size: 0.6rem; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--ink-500); font-weight: 700; }

.wl-ecard-body { padding: var(--s-5); display: flex; flex-direction: column; gap: var(--s-2); flex: 1; }
.wl-ecard-title { font-family: var(--font-display); font-size: 1.24rem; font-weight: 600; line-height: 1.25; margin: 0; }
/* The padding/negative-margin pair grows the tap target from 27px to 45px without
   moving a pixel of the layout: the padding is what a thumb hits, the negative
   margin gives the space straight back to the grid. The underline still tracks
   the text because it is painted from the background edge, not the box edge. */
.wl-ecard-title a { color: var(--ink-900); text-decoration: none;
  display: inline-block; padding-block: 9px; margin-block: -9px;
  background-image: linear-gradient(var(--brand-600), var(--brand-600)); background-repeat: no-repeat;
  background-position: 0 calc(100% - 9px); background-size: 0 1.5px;
  transition: background-size var(--dur-slow) var(--ease-out), color var(--dur) var(--ease-out); }
.wl-ecard:hover .wl-ecard-title a { background-size: 100% 1.5px; color: var(--brand-600); }

.wl-ecard-meta { font-size: var(--t-sm); color: var(--ink-600); display: flex; flex-direction: column; gap: var(--s-1); }
.wl-ecard-meta span { display: inline-flex; align-items: center; gap: var(--s-2); }
.wl-ecard-meta svg { width: 18px; height: 18px; color: var(--ink-400); flex: 0 0 auto; }

.wl-ecard-foot { margin-top: auto; padding-top: var(--s-4); border-top: 1px solid var(--border);
  display: flex; justify-content: space-between; align-items: center; gap: var(--s-3); }

.wl-price { font-weight: 750; font-size: var(--t-lg); font-variant-numeric: tabular-nums; }
.wl-price small { display: block; font-size: var(--t-xs); font-weight: 550; color: var(--ink-500);
  text-transform: uppercase; letter-spacing: 0.07em; }

/* ── Quote carousel ──────────────────────────────────────────────────────── */

.wl-carousel { position: relative; }

/* Without JS this is a plain horizontally scrollable strip — every slide still
   reachable, nothing hidden. .is-enhanced switches it to transform-driven. */
.wl-viewport { overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; border-radius: var(--wl-r-2xl); }
.wl-carousel.is-enhanced .wl-viewport { overflow: hidden; }
.wl-track { display: flex; }
.wl-carousel.is-enhanced .wl-track { transition: transform var(--dur-slow) var(--ease-out); will-change: transform; }
.wl-slide { flex: 0 0 100%; min-width: 100%; scroll-snap-align: start; padding: 3px; }

.wl-quote {
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: var(--wl-r-2xl);
  padding: clamp(1.75rem, 1.2rem + 2vw, 3.25rem);
  box-shadow: var(--shadow-md);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-6);
  align-items: start;
  margin: 0;
}

.wl-quote-mark { font-family: var(--font-display); font-size: 5.5rem; line-height: 0.62;
  background: linear-gradient(150deg, var(--brand-400), var(--gold-500));
  -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent; }

@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .wl-quote-mark { color: var(--brand-400); -webkit-text-fill-color: currentColor; }
}

.wl-quote blockquote { margin: 0 0 var(--s-5); font-family: var(--font-display);
  font-size: clamp(1.2rem, 1.05rem + 0.8vw, 1.7rem); line-height: 1.45; color: var(--ink-800); letter-spacing: -0.012em; }
.wl-quote figcaption { display: flex; align-items: center; gap: var(--s-3); padding-top: var(--s-4); border-top: 1px solid var(--border); }
.wl-quote-av { width: 46px; height: 46px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  background: linear-gradient(145deg, var(--brand-100), var(--gold-100)); border: 1px solid var(--brand-200);
  color: var(--brand-600); font-weight: 750; font-size: var(--t-sm); flex: 0 0 auto; }
.wl-quote-role { font-weight: 650; }
.wl-quote-org { font-size: var(--t-sm); color: var(--ink-500); }

@media (max-width: 700px) {
  .wl-quote { grid-template-columns: 1fr; gap: var(--s-4); }
  .wl-quote-mark { font-size: 3.4rem; }
}

.wl-car-ctrl { display: flex; gap: var(--s-2); }
.wl-car-btn { width: 46px; height: 46px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-full); border: 1px solid var(--border-strong); background: var(--surface-0);
  color: var(--ink-600); cursor: pointer;
  transition: background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out), transform var(--dur) var(--ease-out); }
.wl-car-btn:hover { background: var(--brand-600); border-color: var(--brand-600); color: #fff; transform: scale(1.07); }
:root[data-theme="dark"] .wl-car-btn:hover { color: var(--surface-0); }

/* The dot a thumb hits and the dot an eye sees are two different rectangles. The
   button carries the 44px target (WCAG 2.5.5) and stays transparent; ::before
   draws the 9px pip. Expanding the pip itself would have made the control look
   like a row of buttons, and overlaying an invisible 44px box on a 9px pip 8px
   from its neighbour would have put half of each target inside the next one. */
.wl-dots { display: flex; justify-content: center; gap: 0; margin-top: var(--s-6); }
.wl-dot {
  min-width: 32px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: 0; background: none; cursor: pointer;
}
.wl-dot::before {
  content: "";
  width: 9px; height: 9px;
  border-radius: var(--r-full);
  background: var(--border-strong);
  transition: width var(--dur-slow) var(--ease-out), background-color var(--dur) var(--ease-out);
}
/* The current dot stretches into a bar — position readable without relying on a
   colour difference alone. */
.wl-dot.is-current::before { width: 30px; background: var(--brand-600); }

/* ── Pricing ─────────────────────────────────────────────────────────────── */

.wl-tiers { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: var(--s-5); align-items: start; }

.wl-tier { position: relative; background: var(--surface-0); border: 1px solid var(--border);
  border-radius: var(--wl-r-xl); padding: var(--s-7); display: flex; flex-direction: column; gap: var(--s-4);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out); }
.wl-tier:hover { transform: translateY(-6px); box-shadow: var(--shadow-lg); }
.wl-tier.is-best { border-color: var(--brand-600); box-shadow: var(--shadow-lg); }

/* Gradient ring, drawn by masking out the card's interior. Behind @supports on
   purpose: without mask compositing the pseudo-element would paint a solid
   gradient over the tier's contents. The brand border above is the fallback and
   already reads as the featured tier. */
@supports ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
  .wl-tier.is-best::before {
    content: "";
    position: absolute;
    inset: -1px;
    border-radius: inherit;
    padding: 1px;
    pointer-events: none;
    background: linear-gradient(150deg, var(--brand-600), var(--gold-500));
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
  }
}

.wl-ribbon { position: absolute; top: calc(-1 * var(--s-3)); left: 50%; transform: translateX(-50%);
  padding: var(--s-1) var(--s-4); border-radius: var(--r-full);
  background: linear-gradient(100deg, var(--brand-600), var(--brand-500)); color: #fff;
  font-size: var(--t-xs); font-weight: 750; letter-spacing: 0.06em; text-transform: uppercase;
  white-space: nowrap; box-shadow: var(--shadow-sm); }

.wl-tier-name { font-family: var(--font-sans); font-size: var(--t-lg); font-weight: 650; margin: 0; }
.wl-tier-cost { font-family: var(--font-display); font-size: 2.9rem; font-weight: 600; line-height: 1; letter-spacing: -0.03em; }
.wl-tier-unit { font-size: var(--t-sm); color: var(--ink-500); font-weight: 550; }
.wl-tier ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-3); flex: 1; }
.wl-tier li { display: grid; grid-template-columns: 20px 1fr; gap: var(--s-3); align-items: start; color: var(--ink-600); font-size: var(--t-base); }
.wl-tier li svg { width: 20px; height: 20px; color: var(--ok-600); margin-top: 3px; }

/* ── Forms ───────────────────────────────────────────────────────────────── */

.wl-form-card { background: var(--surface-0); border: 1px solid var(--border); border-radius: var(--wl-r-2xl);
  padding: clamp(1.5rem, 1rem + 2vw, 2.75rem); box-shadow: var(--shadow-md);
  margin-top: var(--s-9); max-width: 900px; margin-inline: auto; }
.wl-form-card.is-flush { margin-top: 0; max-width: none; }
.wl-form-title { font-size: var(--t-2xl); margin-bottom: var(--s-2); }
.wl-form-card .alert { margin-bottom: var(--s-5); }
.wl-fgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); gap: 0 var(--s-5); }
.wl-form-card .field:last-of-type { margin-bottom: var(--s-6); }
.wl-form-card .checkline { align-items: flex-start; }

/* ── Contact ─────────────────────────────────────────────────────────────── */

.wl-contact-grid { display: grid; grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr); gap: var(--s-8); align-items: start; }
@media (max-width: 940px) { .wl-contact-grid { grid-template-columns: 1fr; } }

.wl-dl { display: grid; gap: var(--s-5); margin: var(--s-5) 0 0; }
.wl-dl > div { display: grid; gap: var(--s-1); }
.wl-dl dt { display: flex; align-items: center; gap: var(--s-2); font-size: var(--t-xs); text-transform: uppercase;
  letter-spacing: 0.1em; font-weight: 750; color: var(--ink-500); }
.wl-dl dt svg { width: 17px; height: 17px; color: var(--brand-600); }
.wl-dl dd { margin: 0; font-size: var(--t-md); color: var(--ink-800); font-weight: 650; }

/* ── Footer ──────────────────────────────────────────────────────────────── */

.wl-foot { border-top: 1px solid var(--border); background: var(--surface-0); padding: var(--s-9) 0 var(--s-6);
  color: var(--ink-500); position: relative; font-size: var(--t-base); }
.wl-foot::before { content: ""; position: absolute; inset: -1px 0 auto 0; height: 2px;
  background: linear-gradient(90deg, transparent, var(--brand-200), var(--gold-200), var(--brand-200), transparent); }
.wl-foot .wl-brand { color: var(--ink-900); }
.wl-foot .wl-mark { color: var(--brand-600); }

.wl-foot-grid { display: grid; grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr)); gap: var(--s-7);
  padding-bottom: var(--s-7); border-bottom: 1px solid var(--border); }
@media (max-width: 860px) { .wl-foot-grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-6); } }
@media (max-width: 520px) { .wl-foot-grid { grid-template-columns: 1fr; } }

.wl-foot-t { font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.1em; font-weight: 750;
  color: var(--ink-700); margin-bottom: var(--s-4); }
/* gap drops from 12px to 4px and the links take the difference back as padding:
   the rhythm of the column is unchanged, but each link is now a 25px target
   instead of a 21px one (WCAG 2.5.8) and there is no dead gutter between them. */
.wl-foot-l { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-1); }
.wl-foot-l a { color: var(--ink-500); text-decoration: none; display: inline-block;
  padding-block: 2px; transition: color var(--dur-fast) var(--ease-out); }
.wl-foot-l a:hover { color: var(--brand-600); }

.wl-foot-b { padding-top: var(--s-5); display: flex; justify-content: space-between; gap: var(--s-4);
  flex-wrap: wrap; font-size: var(--t-sm); }
.wl-foot-b span { display: inline-flex; align-items: center; gap: var(--s-2); }
.wl-foot-b svg { width: 18px; height: 18px; flex: 0 0 auto; }

/* ── Responsive ──────────────────────────────────────────────────────────── */

@media (max-width: 700px) {
  .wl-pcol { padding: var(--s-5); }
  .wl-tier { padding: var(--s-6); }
  .wl-tour-stage { padding: var(--s-5); }
  .wl-form-card { margin-top: var(--s-7); }
}

/* ── Reduced motion ──────────────────────────────────────────────────────── */

/* This section sits after the global switch above, so anything it animates has
   to opt out here. */
@media (prefers-reduced-motion: reduce) {
  .wl-orb, .wl-chip-float { animation: none !important; }
  .wl-panel { transform: none !important; }
  .wl-carousel.is-enhanced .wl-track { transition: none !important; }
  .js-motion .wl-tour-panel.is-on { animation: none !important; opacity: 1 !important; transform: none !important; }
  .wl-progress { display: none; }
  .wl-btn:hover, .wl-filter:hover, .wl-cell:hover, .wl-ecard:hover,
  .wl-tier:hover, .wl-tour-tab:hover, .wl-pcol:hover, .wl-car-btn:hover { transform: none !important; }
}
