/* Generated by scripts/sync-site.mjs from src/components/coming-soon/coming-soon.css (sha256 79a6f4abe9b59703cb767e8d26687de2af27bbb0159cc1f1fd71c38823fa9bc3). Do not edit: change the source, then run `node scripts/sync-site.mjs`. */
/*
 * The coming-soon design (docs/design-and-brand.md sections 12 and 13). Plain
 * CSS on the design variables only, shared by both builds: the app imports it,
 * and scripts/sync-site.mjs copies it verbatim into the static site. Type uses
 * the section 4 scale, spacing the section 6 steps (0.25rem multiples from
 * 1, 2, 3, 4, 6, 8, 12, 16, 24), radius --radius, motion section 7.
 *
 * Round 5 (the founder, 2026-09-28): hero with a hand of portrait cards, then
 * for gift seekers, occasions, for vendors, contact and the footer, each
 * section introduced by its heading alone and alternating sides and grounds.
 */
/* The page is the container the hero and the hand respond to (round 5
   audit): container queries measure rem against the root font size, so under
   text-only zoom the hand becomes the phone's stack and the hero one column
   rather than running off the page, as media queries (which ignore that zoom)
   let it. At 100% text the page is the viewport's width, so the switches fall
   where the media queries put them. card-fan.js reads the same rule. */
.cs {
  container: cs / inline-size;
  width: 100%;
  color: var(--foreground);
  font-family: var(--font-geist-sans), system-ui, sans-serif;
}

/* A zero-specificity reset, so each element's own margin wins. */
.cs :where(p, h2, h3, ul) {
  margin: 0;
}

/* Every in-page target clears the top of the viewport by the same step. */
.cs :where([id]) {
  scroll-margin-top: 1.5rem;
}

/* The page's column: the same 72rem as the header. Grounds (the vendors'
   muted band, the contact band) run full width outside it. */
.cs-hero,
.cs-occasions-inner {
  width: 100%;
  max-width: 72rem;
  margin-inline: auto;
  padding-inline: 1.5rem;
}

/* The full-width grounds (the audience sections, the contact band) carry the
   page's side padding themselves, so their content is inset from their own
   ground (the detector reads only an element's own physical padding); their
   inner column drops its padding and is capped at 72rem less that padding,
   so the text stays on the page's column. */
.cs-audience,
.cs-contact {
  padding: 5rem 1.5rem;
}

.cs-audience-inner,
.cs-contact-inner {
  width: 100%;
  max-width: 69rem;
  margin-inline: auto;
}

.cs a {
  color: var(--primary);
  text-underline-offset: 0.2em;
}

/* Section lines (section 6, round 4): every section after the hero, and the
   footer, starts with one full-width hairline. */
.cs-audience,
.cs-occasions,
.cs-contact {
  border-top: 1px solid var(--border);
}

/* ---- The hero ------------------------------------------------------------ */

.cs-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* The hand's list already holds the peek's room above its cards, so the
     stacked hero needs only a small gap (round 5 critique). */
  gap: 1.5rem;
  padding-block: 2rem 4rem;
}

.cs-intro {
  max-width: 42rem;
}

/* "Coming soon": the one status line section 11 allows above a heading.
   Sentence case, near-black; the gold bar is decoration. One quiet line, lg
   at weight 600 (round 5 critique: at 2xl bold it competed with the h1 on a
   phone). */
.cs-soon {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 1.125rem;
  line-height: 1.75rem;
  font-weight: 600;
  color: var(--foreground);
}

.cs-soon-bar {
  flex: none;
  width: 0.5rem;
  height: 1.25rem;
  border-radius: calc(var(--radius) - 4px);
  background: var(--deco-gold);
}

.cs-headline {
  margin: 1rem 0 0;
  overflow-wrap: anywhere;
  font-size: 1.875rem;
  line-height: 2.25rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  text-wrap: balance;
  /* Two lines at every size, "Thoughtful gifting, / made easier." */
  max-width: 11em;
}

/* The founder's two accent lines (round 5), below the headline rather than
   above it (an eyebrow above a heading is banned, section 11). xl, weight 600,
   in the accent: the one place it colours text that is not a link (section 5,
   amended 2026-09-28), 7.01:1 on the page ground. Each starts its own line
   (and may wrap on a phone). */
.cs-hero-lead {
  margin-top: 1rem;
  font-size: 1.25rem;
  line-height: 1.75rem;
  font-weight: 600;
  color: var(--primary);
  text-wrap: balance;
}

.cs-hero-lead span {
  display: block;
}

.cs-lede {
  /* About 65 characters of Geist at 18px (section 4). */
  max-width: 36rem;
  margin-top: 1rem;
  font-size: 1.125rem;
  line-height: 1.75rem;
  text-wrap: pretty;
}

.cs-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2rem;
}

/* On a phone the two actions stack at one width (round 5 critique); side by
   side from 40rem at their own widths. */
.cs-actions > a {
  flex: 1 1 100%;
  justify-content: center;
}

@container cs (min-width: 40rem) {
  .cs-actions > a {
    flex: 0 0 auto;
  }
}

/* The primary action in the accent, and the secondary as an outline (section 3,
   Button: `default` and `outline`). */
.cs-cta,
.cs-cta-secondary {
  display: inline-flex;
  align-items: center;
  min-height: 3rem;
  padding: 0.75rem 1.5rem;
  /* The same 1px edge on both, so the filled and the outline buttons are one
     height (round 5 critique: 52 and 54px on a phone). */
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-size: 1.125rem;
  line-height: 1.75rem;
  font-weight: 500;
  text-decoration: none;
}

.cs .cs-cta {
  background: var(--primary);
  color: var(--primary-foreground);
}

/* Its edge is the muted foreground, 6.51:1 on the page (round 5 review: on
   --border it measured 1.29:1, under the 3:1 a control's boundary needs). */
.cs .cs-cta-secondary {
  border: 1px solid var(--muted-foreground);
  background: var(--card);
  color: var(--foreground);
}

@media (hover: hover) and (pointer: fine) {
  .cs-cta,
  .cs-cta-secondary {
    transition: background-color 150ms ease-out;
  }
  .cs .cs-cta:hover {
    background: color-mix(in oklch, var(--primary) 84%, var(--foreground));
  }
  .cs .cs-cta-secondary:hover {
    background: var(--muted);
  }
}

/* ---- The hand (section 12) ------------------------------------------------
   Below 40rem, a stack you swipe: a scroll-snap row of portrait cards, the
   next one showing at the edge; every card is visible, so one tap opens it (a
   legible hand needs more width than a phone has). From 40rem, a hand of four
   portrait cards fanned from a common pivot below them, back to front, the
   last fully in view. The states come from src/lib/card-fan.js as data
   attributes on each item: data-peek (hover, focus or touch) and data-hint
   (the "View card" label after a touch peek). */
.cs-fan {
  position: relative;
  min-width: 0;
}

.cs-fan-list {
  display: flex;
  gap: 1rem;
  padding: 0.5rem 1.5rem 1rem;
  margin-inline: -1.5rem;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 1.5rem;
  overscroll-behavior-x: contain;
}

.cs-fan-item {
  position: relative;
  flex: 0 0 70%;
  max-width: 16rem;
  scroll-snap-align: start;
}

.cs-fan-face {
  position: relative;
}

/* The whole card is the button: a transparent layer over the face, so the
   markup stays valid (a button may hold only phrasing content). The face is
   aria-hidden; the button is named. */
.cs-fan-open {
  position: absolute;
  inset: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* "View card", after a touch peek: on the face, so it travels with it. It
   sits inside the aria-hidden face; the button's own name says what it does.
   At the foot of the card, over the preview QR, never over the index: at the
   top right it hid the name that tells the cards apart ("A rum s"). */
.cs-fan-hint {
  position: absolute;
  right: 0.75rem;
  bottom: 0.75rem;
  padding: 0.25rem 0.5rem;
  border-radius: calc(var(--radius) - 2px);
  background: var(--primary);
  color: var(--primary-foreground);
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 500;
  opacity: 0;
}

.cs-fan-item[data-hint] .cs-fan-hint {
  opacity: 1;
}

.cs-fan-caption {
  margin-top: 1rem;
}

@container cs (min-width: 40rem) {
  .cs-fan-list {
    position: relative;
    display: block;
    /* One card's height, plus the room a peek rises into, so a raised card
       never covers the actions above it in the one-column layout. */
    height: 23rem;
    width: 31rem;
    max-width: 100%;
    padding: 0;
    /* Centred under the intro while the hero is one column, so the outer
       cards' turn stays inside the column (round 3 critique). */
    margin: 0 auto;
    overflow: visible;
  }

  /* A playing card's index is narrow, to fit the strip a hand leaves showing:
     it wraps between words ("A beach / bar"), only in the hand. */
  .cs-fan-item .gcf-business {
    max-width: 6rem;
  }

  /* In the hand the giver's message shows in full, in the issuer's place
     (the founder, 2026-09-28: never truncated), and the card keeps its 5:8
     shape: the face is set a step smaller there, the message at xs, the
     amount closer to it and the code smaller. The dialog and the phone's
     stack show the card at its own sizes. */
  .cs-fan-item .gcf-message {
    margin-top: 0.25rem;
    font-size: 0.75rem;
    /* At least 1.35 at xs (section 4, amended 2026-09-28): 16.5px. */
    line-height: 1.375;
  }

  /* The room the message needs comes from the foot of the face, so every card
     keeps 5:8 (round 5 review: Rum Tour grew 3px). */
  .cs-fan-item .gcf-bottom {
    padding-top: 0.25rem;
  }

  .cs-fan-item .gcf-bottom .gcf-preview {
    margin-bottom: 0;
  }

  .cs-fan-item .gcf-amount {
    margin-top: 0.5rem;
  }

  .cs-fan-item .gcf-qr {
    width: 3rem;
    height: 3rem;
  }

  /* The front card is the one fully in view: its name may take the card's
     width ("Crystal Palace / Restaurant" on two lines, not three). */
  .cs-fan-item:last-child .gcf-business {
    max-width: none;
  }

  /* Each item holds its place: its turn about the hand's pivot never changes,
     so the button over it (the hit area) never moves out from under the
     pointer. Only the face travels, along the card's own axis. The pivot is
     one and a half card heights below each card (origin 50% 250%). */
  .cs-fan-item {
    --r: 0deg;
    position: absolute;
    bottom: 0;
    left: 50%;
    width: 10.5rem;
    max-width: none;
    margin-left: -5.25rem;
    transform: rotate(var(--r));
    transform-origin: 50% 250%;
    pointer-events: none;
  }

  .cs-fan-item:nth-child(1) {
    --r: -15deg;
    z-index: 1;
  }

  .cs-fan-item:nth-child(2) {
    --r: -5deg;
    z-index: 2;
  }

  .cs-fan-item:nth-child(3) {
    --r: 5deg;
    z-index: 3;
  }

  .cs-fan-item:nth-child(4) {
    --r: 15deg;
    z-index: 4;
  }

  /* Only the buttons take the pointer. A raised item or its face would
     otherwise sit over the later cards and take their pointer too. */
  .cs-fan-face {
    --lift: 0rem;
    pointer-events: none;
    transform: translateY(var(--lift));
    /* Back to rest: ease-in-out, movement between two resting states
       (section 7), 250ms: the rise is well under 200px. */
    transition: transform 250ms cubic-bezier(0.77, 0, 0.175, 1);
  }

  .cs-fan-open {
    pointer-events: auto;
  }

  /* The stacking order changes at once, both ways (the round-2 audit: a
     delayed drop left a returning card's hit area over the next card). */
  .cs-fan-item[data-peek] {
    z-index: 10;
  }

  /* The peek: the card rises out of the hand along its own axis. 250ms
     ease-out. */
  .cs-fan-item[data-peek] .cs-fan-face {
    --lift: -6rem;
    transition: transform 250ms cubic-bezier(0.23, 1, 0.32, 1);
  }

  /* While peeked, the hit area is the lifted face plus the card's own strip of
     the hand below it (about two thirds of its width shows between it and
     the next card; the strip takes three fifths, so it never reaches the
     next card): the pointer that raised it stays on it, so it cannot flicker,
     and the later cards keep theirs. The strip is the button's own
     pseudo-element, so it is the button to the pointer. */
  .cs-fan-item[data-peek] .cs-fan-open {
    top: -6rem;
    bottom: 6rem;
  }

  .cs-fan-item[data-peek] .cs-fan-open::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 0;
    width: 60%;
    height: 6rem;
  }

  /* The outer cards' turn carries their lower corners below the list's box;
     the caption clears them. */
  .cs-fan-caption {
    margin-top: 3rem;
    /* Under the centred hand while the hero is one column (round 5 critique);
       at its start from 72rem, beside the intro. */
    text-align: center;
  }

  /* The front card is already in view, so it opens without a peek; on a
     hovering pointer, and only where motion is welcome, it rises a little. */
  @media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
    .cs-fan-item:last-child:hover .cs-fan-face {
      --lift: -0.5rem;
      transition: transform 150ms cubic-bezier(0.23, 1, 0.32, 1);
    }
  }
}

/* ---- The open card: a native <dialog> ------------------------------------
   The card is the only surface: the dialog is a transparent frame with no
   border or shadow, over a near-opaque scrim of the page's own ground. A
   bordered dialog around the bordered card was a card inside a card
   (section 3; the detector's nested-cards). */
.cs-dialog {
  width: min(22rem, calc(100% - 2rem));
  max-width: none;
  max-height: calc(100% - 2rem);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--foreground);
  overflow: auto;
  font-family: var(--font-geist-sans), system-ui, sans-serif;
}

.cs-dialog::backdrop {
  background: color-mix(in oklab, var(--background) 96%, transparent);
}

/* The content fills the box, so only a click on the backdrop reaches the
   dialog element itself (card-fan.js closes on that). */
.cs-dialog-body {
  display: grid;
  gap: 1rem;
  padding: 1.5rem;
}

.cs-dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.cs-dialog-note {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--muted-foreground);
}

.cs-dialog-close {
  flex: none;
  min-height: 2.75rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
  font-weight: 500;
  cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
  .cs-dialog-close {
    transition: background-color 150ms ease-out;
  }
  .cs-dialog-close:hover {
    background: var(--muted);
  }
}

.cs-dialog-panel {
  display: grid;
  gap: 1rem;
  justify-items: center;
}

/* Portrait at full size, and never taller than the viewport leaves room for:
   the card is 5:8, so its width is capped by the dynamic viewport height
   (iOS Safari's toolbar changes it; never vh). */
.cs-dialog-panel .cs-tilt {
  /* The head (the note and Close) and the frame take about 7rem. */
  max-width: min(100%, calc((100dvh - 7rem) * 5 / 8));
}

/* A short viewport (a phone on its side): the note goes, since the card's own
   Preview label says the same, and the frame tightens, so the card keeps its
   5:8 shape whole without the dialog scrolling. */
@media (max-height: 30rem) {
  .cs-dialog {
    width: auto;
  }

  .cs-dialog-note {
    display: none;
  }

  /* Close beside the card rather than above it: a phone on its side has
     width to spare and no height. */
  .cs-dialog-body {
    grid-template-columns: auto auto;
    align-items: start;
    gap: 0.5rem;
    padding: 0.5rem;
  }

  .cs-dialog-head {
    order: 2;
  }

  .cs-dialog-panel .cs-tilt {
    width: calc((100dvh - 3rem) * 5 / 8);
    max-width: none;
  }
}

.cs-dialog-panel[hidden] {
  display: none;
}

/* Opening: where the browser has view transitions, card-fan.js grows the card
   from its place in the fan into the dialog (a shared view-transition-name).
   Elsewhere the dialog enters with opacity and a scale from 0.96. Both 300ms
   ease-out, the section 7 allowance for a dialog; closing is 200ms, ease-in. */
::view-transition-group(cs-fan-card) {
  animation-duration: 300ms;
  animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}

/* Closing is an exit: ease-in (card-fan.js marks the root while it runs). */
/* One card, not two, while it grows: the old snapshot (the hand's smaller
   type) fades out in the first 90ms rather than crossfading for the whole
   300ms (round 5, Emil Kowalski's review). */
::view-transition-old(cs-fan-card) {
  animation-duration: 90ms;
}

/* The new snapshot is opaque from the start, so the growing card is never
   see-through, opening or closing (round 5, Emil Kowalski's re-check). */
::view-transition-new(cs-fan-card) {
  animation-name: none;
}

/* Closing is faster than opening: 200ms, the dialog around the card too
   (round 5 review). */
:root.cs-fan-closing::view-transition-group(cs-fan-card),
:root.cs-fan-closing::view-transition-old(root),
:root.cs-fan-closing::view-transition-new(root) {
  animation-duration: 200ms;
}

:root.cs-fan-closing::view-transition-group(cs-fan-card) {
  animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
}

/* The dialog around the card eases as the card does: out to open, in to
   close (round 5 confirmation). The root group's keyframes are identical, so
   it has no animation, and the transition ends with the card rather than
   the browser's default 250ms later. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 300ms;
  animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}

:root.cs-fan-closing::view-transition-old(root),
:root.cs-fan-closing::view-transition-new(root) {
  animation-timing-function: cubic-bezier(0.4, 0, 1, 1);
}

::view-transition-group(root) {
  animation: none;
}

@media (prefers-reduced-motion: no-preference) {
  .cs-dialog,
  .cs-dialog::backdrop {
    opacity: 0;
    transition:
      opacity 200ms cubic-bezier(0.4, 0, 1, 1),
      transform 200ms cubic-bezier(0.4, 0, 1, 1),
      overlay 200ms allow-discrete,
      display 200ms allow-discrete;
  }

  .cs-dialog {
    transform: scale(0.96);
  }

  .cs-dialog[open],
  .cs-dialog[open]::backdrop {
    opacity: 1;
    transition-duration: 300ms;
    transition-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
  }

  .cs-dialog[open] {
    transform: none;
  }

  @starting-style {
    .cs-dialog[open],
    .cs-dialog[open]::backdrop {
      opacity: 0;
    }
    .cs-dialog[open] {
      transform: scale(0.96);
    }
  }

  /* During a view transition the transition itself is the motion. */
  .cs-fan-travel .cs-dialog,
  .cs-fan-travel .cs-dialog::backdrop {
    transition: none;
  }
}

/* ---- Section headings and the two audience sections ---------------------- */
.cs-section-title {
  /* A long word breaks rather than widening a phone at 200% text. */
  overflow-wrap: break-word;
  font-size: 1.875rem;
  line-height: 2.25rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.cs-section-lead {
  max-width: 36rem;
  margin-top: 1rem;
  font-size: 1.125rem;
  line-height: 1.75rem;
  text-wrap: pretty;
}

.cs-caption {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 500;
  color: var(--muted-foreground);
}

/* The vendors' ground: the muted band, full width, so the two audiences read
   as two places (section 6). */
.cs-audience--vendors {
  background: var(--muted);
}

.cs-audience-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 3rem;
  /* The card lines up with the heading, not the middle of the copy. */
  align-items: start;
}

/* The benefits lead (sections 8 and 9, round 4): a bold phrase on its own
   line and one line beneath, no marker, no number, no icon. */
.cs-benefits {
  display: grid;
  gap: 1.25rem;
  max-width: 34rem;
  margin-top: 2rem;
  padding: 0;
  list-style: none;
}

.cs-benefits li {
  display: grid;
  gap: 0.25rem;
}

.cs-benefit-phrase {
  font-size: 1.125rem;
  line-height: 1.75rem;
  font-weight: 600;
}

.cs-benefit-line {
  font-size: 1rem;
  line-height: 1.5rem;
  color: var(--muted-foreground);
}

.cs-section-note {
  margin-top: 1.5rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--muted-foreground);
}

/* A link inside a sentence: inline, so it never stretches the line (round 5
   critique: 34px between a note's lines); its 44px target comes from padding
   that the line box ignores. */
.cs-section-note a {
  padding-block: 0.875rem;
  margin-block: -0.875rem;
}

.cs-audience-visual {
  display: grid;
  gap: 1rem;
  justify-items: start;
  margin: 0;
}

.cs-tilt {
  width: 100%;
  /* src/lib/card-tilt.js writes the transform on this one element while it
     moves (a critically damped spring: no bounce, ease-in-out to rest), and
     removes it when the card is at rest. */
  transform-style: preserve-3d;
}

.cs-give {
  max-width: 20rem;
}

/* ---- For vendors (round 5) -------------------------------------------------
   The heading with the postcard large beside it, then the benefits in two
   columns with the closing line in the sixth cell: the list is a subgrid of
   the section's grid, so the note sits in the grid the list makes without
   joining the list. On a phone: heading, postcard, benefits, note. */
.cs-vendors {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2rem;
  width: 100%;
  max-width: 69rem;
  margin-inline: auto;
}

.cs-postcard-art {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
}

.cs-vendors-grid {
  display: grid;
  gap: 1.25rem 3rem;
}

.cs-vendors-grid .cs-benefits {
  max-width: none;
  margin-top: 0;
}

/* The section's one action, so body size rather than a footnote's (round 5
   critique). */
.cs .cs-vendors-note {
  margin-top: 0;
  font-size: 1rem;
  line-height: 1.5rem;
}

/* ---- Occasions ------------------------------------------------------------ */
.cs-occasions {
  /* Physical padding: the detector reads only that against the hairline. */
  padding-top: 5rem;
  padding-bottom: 5rem;
}

/* One feature (a trip to Barbados) and four compact rows (round 5, with
   festive gifting): not equal tiles. On a phone, the feature first, then the
   rows. */
.cs-occasion-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
  margin-block: 2rem;
  padding: 0;
  list-style: none;
}

.cs-occasion {
  display: grid;
  /* Art and text side by side while both fit 7.5rem; stacked when they do
     not (200% text on a phone), so a word like "Birthdays" never widens the
     page (round 4 audit). */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr));
  gap: 0.75rem 1rem;
  align-items: center;
}

.cs-occasion--feature {
  grid-template-columns: minmax(0, 1fr);
  row-gap: 1rem;
  align-items: start;
}

/* The illustration's frame: one hairline, and the texture (section 5), on
   which no text sits. */
.cs-occasion-frame {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card) url("/textures/dots.svg") 0 0 / 1rem 1rem repeat;
}

.cs-occasion--feature .cs-occasion-frame {
  background-image: url("/textures/waves.svg");
  background-size: 4.5rem 1.5rem;
}

.cs-occasion-art {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 8 / 5;
}

.cs-occasion-text {
  display: grid;
  gap: 0.25rem;
}

.cs-occasion-title {
  margin: 0;
  font-size: 1.125rem;
  line-height: 1.75rem;
  font-weight: 600;
}

.cs-occasion-line {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--muted-foreground);
}

/* ---- Contact --------------------------------------------------------------- */
/* A faint sand ground (section 5: a decorative colour as a faint tint, mixed
   in oklab so it does not swing through green). */
.cs-contact {
  background: color-mix(in oklab, var(--deco-sand) 35%, var(--background));
}

.cs-contact-inner {
  display: grid;
  gap: 3rem;
}

/* The form: labels always visible above their fields, every control at
   least 44px, errors in the destructive colour under their field (5.56:1 on
   the page ground, 5.18:1 on the contact band), and one live status line for the outcome. */
.cs-form {
  display: grid;
  gap: 1.25rem;
  max-width: 34rem;
}

.cs-field {
  display: grid;
  gap: 0.5rem;
}

.cs-field label,
.cs-check label {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 500;
}

/* The field's edge must read at 3:1 against the band (WCAG 1.4.11): the
   hairline --input measures 1.2:1 there, so fields take the muted-foreground,
   6.1:1 on the band. */
.cs-field input,
.cs-field textarea {
  width: 100%;
  min-height: 2.75rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--muted-foreground);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--foreground);
  font: inherit;
  font-size: 1rem;
  line-height: 1.5rem;
}

.cs-field textarea {
  resize: vertical;
}

.cs-field [aria-invalid="true"] {
  border-color: var(--destructive);
}

.cs-field-hint {
  font-size: 0.75rem;
  line-height: 1rem;
  color: var(--muted-foreground);
}

.cs-field-error {
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--destructive);
}

.cs-field-error:empty {
  display: none;
}

.cs-check {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 2.75rem;
}

.cs-check input {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  accent-color: var(--primary);
}

.cs-form-status:focus {
  outline: none;
}

.cs-form-status:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.cs-check label {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  font-size: 1rem;
  line-height: 1.5rem;
  font-weight: 400;
}

.cs-form-actions {
  display: grid;
  gap: 0.75rem;
  justify-items: start;
}

.cs-form .cs-cta {
  cursor: pointer;
  font-family: inherit;
}

.cs-form .cs-cta:disabled {
  cursor: progress;
  background: color-mix(in oklch, var(--primary) 70%, var(--background));
}

.cs-form-privacy {
  max-width: 30rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--muted-foreground);
}

.cs-form-status {
  min-height: 1.5rem;
  font-size: 1rem;
  line-height: 1.5rem;
  font-weight: 500;
}

/* Out of sight and out of the tab order; aria-hidden in the markup. */
.cs-form-honey {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---- Footer ---------------------------------------------------------------- */
/* Full width, so its hairline runs edge to edge like every section's; its
   side padding keeps the content on the page's column. */
.cs-footer {
  display: grid;
  gap: 0.75rem;
  width: 100%;
  padding-block: 3rem 4rem;
  padding-inline: max(1.5rem, calc((100% - 69rem) / 2));
  border-top: 1px solid var(--border);
  font-family: var(--font-geist-sans), system-ui, sans-serif;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--muted-foreground);
}

.cs-footer :where(p) {
  margin: 0;
  max-width: 36rem;
}

.cs-footer-links {
  display: flex;
  flex-wrap: wrap;
  column-gap: 1.5rem;
}

.cs-footer a {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  color: var(--primary);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* 17.5rem wide, so about 6rem (95px) tall: the tagline's glyphs are 51 of the
   logo's 500 units, so 9.7px. Sized by width and capped at the column, so
   200% text never pushes it past a phone's edge. */
.cs-footer-logo {
  display: block;
  width: 17.5rem;
  max-width: 100%;
  height: auto;
  margin-block-end: 0.75rem;
}

/* ---- Wider layouts --------------------------------------------------------- */
@media (min-width: 48rem) {
  .cs-hero,
  .cs-occasions-inner {
    padding-inline: 2rem;
  }

  .cs-audience,
  .cs-contact {
    padding: 5rem 2rem;
  }

  .cs-audience-inner,
  .cs-contact-inner {
    max-width: 68rem;
  }

  .cs-hero {
    padding-block: 3rem 5rem;
  }

  .cs-footer {
    padding-block: 3rem 5rem;
    padding-inline: max(2rem, calc((100% - 68rem) / 2));
  }

  .cs-headline {
    font-size: 2.25rem;
    line-height: 2.5rem;
  }

  .cs-section-title {
    font-size: 2.25rem;
    line-height: 2.5rem;
  }

  /* Gift seekers side by side: the copy, and the card on the right. */
  .cs-audience-inner {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: 4rem;
  }

  /* For vendors: the heading beside the postcard, then two columns of
     benefits with the note in the sixth cell. */
  .cs-vendors {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    column-gap: 4rem;
    row-gap: 3rem;
    align-items: start;
    max-width: 68rem;
  }

  /* The heading is centred against the postcard: at the top, or at the foot,
     of an otherwise empty column it read as orphaned (round 5 reviews). */
  .cs-vendors-title {
    align-self: center;
  }

  .cs-vendors-grid {
    grid-column: 1 / -1;
  }

  /* The two-column grid only where subgrid exists (round 5 audit: without it
     the note would overlap the list); otherwise one column, list then note. */
  @supports (grid-template-columns: subgrid) {
    .cs-vendors-grid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      grid-template-rows: repeat(3, auto);
    }

    .cs-vendors-grid .cs-benefits {
      grid-column: 1 / -1;
      grid-row: 1 / -1;
      grid-template-columns: subgrid;
      grid-template-rows: subgrid;
      /* The section's gaps, not the list's own (it put the second column 14px
         inside the grid line, round 5 review). */
      gap: 1.25rem 3rem;
      align-items: start;
    }

    .cs .cs-vendors-note {
      grid-column: 2;
      grid-row: 3;
      align-self: start;
    }
  }

  /* The feature on the left across four rows, the rows on the right. */
  .cs-occasion-list {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    column-gap: 3rem;
    row-gap: 1.5rem;
  }

  .cs-occasion--feature {
    grid-row: span 4;
  }

  /* Four rows about as tall together as the feature beside them. */
  .cs-occasion {
    grid-template-columns: min(9rem, 36%) minmax(0, 1fr);
  }

  .cs-occasion--feature {
    grid-template-columns: minmax(0, 1fr);
  }

  .cs-contact-inner {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    column-gap: 4rem;
    align-items: start;
  }
}

/* The first viewport in two columns, the hand beside the intro, from 72rem:
   the asymmetric composition of section 4, the headline at 5xl in about five
   of twelve columns (never narrower than its two lines need). */
@container cs (min-width: 72rem) {
  .cs-hero {
    grid-template-columns: minmax(min(32rem, 100%), 5fr) minmax(0, 7fr);
    align-items: end;
    column-gap: 3rem;
  }

  .cs-fan-list {
    margin: 0;
  }

  .cs-fan-caption {
    text-align: start;
  }

  .cs-headline {
    font-size: 3rem;
    line-height: 3rem;
  }

}

/* Press feedback (round 5, Emil Kowalski): every pressable control answers
   at once, a 0.97 scale over 160ms ease-out, so it feels heard. Here, after
   the hover rules, so its transition list includes theirs. Nothing moves under
   reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .cs-cta,
  .cs-cta-secondary,
  .cs-dialog-close {
    transition:
      transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
      background-color 150ms ease-out;
  }

  .cs-cta:active,
  .cs-cta-secondary:active,
  .cs-dialog-close:active {
    transform: scale(0.97);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* card-tilt.js never attaches here; this is the belt to its braces. */
  .cs-tilt {
    transform: none !important;
  }

  /* Nothing travels: a peek is the focus ring and the raised stacking order,
     and the dialog simply appears. */
  .cs-fan-face,
  .cs-fan-item[data-peek] .cs-fan-face {
    --lift: 0rem;
    transition: none;
  }

  .cs-fan-item[data-peek] .cs-fan-open {
    top: 0;
    bottom: 0;
    outline: 2px solid var(--ring);
    outline-offset: 2px;
  }

  .cs-fan-item[data-peek] .cs-fan-open::after {
    content: none;
  }
}

/* Headings for screen readers only (the dialog's title, which the card
   itself shows). */
.cs-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
