/*
 * Iceland Eclipse 2026 — coupon storefront.
 *
 * Colour comes entirely from `tokens.css`, which is GENERATED from
 * theme/tokens.mjs by scripts/build.mjs. Do not write a colour literal in this
 * file: the contrast test only knows about tokens, so a hard-coded colour is
 * an untested colour. The single exception is the hero artwork, which is the
 * brand's key visual and deliberately does not follow the theme (same rule as
 * chi-web's EclipseHero.tsx and chi-app's eclipse intro).
 *
 * Mobile-first: the base rules ARE the phone layout, and media queries only
 * ever add. Both themes share every rule here — they differ in tokens, never
 * in structure, which is what stops them drifting apart.
 */

/* ---------------------------------------------------------------- reset -- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchored category headings must clear the sticky nav. */
  scroll-padding-top: 5.5rem;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  background: var(--page);
  color: var(--text);
  font-family:
    system-ui,
    -apple-system,
    'Segoe UI',
    Roboto,
    'Helvetica Neue',
    Arial,
    sans-serif;
  font-size: 1rem;
  line-height: 1.55;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  /* Icelandic place names and long product names must never force a scrollbar. */
  overflow-wrap: break-word;
}

img,
svg {
  display: block;
  max-width: 100%;
}

button,
input,
select {
  font: inherit;
  color: inherit;
}

/* ------------------------------------------------------------ typography -- */

/*
 * Display type is tight, uppercase and letterspaced — the same geometry as the
 * ICELAND ECLIPSE wordmark, which is drawn artwork and is never typeset.
 * Archivo Black is the brand's display face; see docs/theming.md for the
 * drop-in slot. Until then the system stack carries it, which costs nothing
 * and cannot cause a font-swap layout shift.
 */
h1,
h2,
h3 {
  line-height: 1.1;
  font-weight: 800;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.25rem, 9vw, 4rem);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

h2 {
  font-size: clamp(1.5rem, 5vw, 2rem);
}

h3 {
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: 0;
}

.eyebrow {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.lede {
  font-size: 1.0625rem;
  color: var(--text-muted);
  max-width: 46ch;
}

/* -------------------------------------------------------------- ambient -- */

/*
 * Glass only reads as glass if there is something behind it. On a flat
 * near-black page a blurred panel just looks like a slightly different grey.
 *
 * So the page gets a fixed wash of the brand's own light — corona orange, aqua
 * and electric blue, the three colours the hero is lit by — at low enough
 * opacity that it never lifts a surface out of its tested contrast range.
 * Purely decorative and non-interactive.
 */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(
      48% 38% at 8% 14%,
      color-mix(in srgb, var(--action) 13%, transparent),
      transparent 70%
    ),
    radial-gradient(
      42% 34% at 92% 30%,
      color-mix(in srgb, var(--accent) 9%, transparent),
      transparent 72%
    ),
    radial-gradient(
      58% 42% at 46% 96%,
      color-mix(in srgb, var(--info) 11%, transparent),
      transparent 74%
    );
}

/* ---------------------------------------------------------------- layout -- */

.wrap {
  width: 100%;
  max-width: 72rem;
  margin-inline: auto;
  padding-inline: 1.25rem;
}

main {
  flex: 1;
  /* Room for the sticky summary bar so it can never cover the last card. */
  padding-bottom: var(--summary-clearance, 1rem);
}

/* ------------------------------------------------------------ skip link -- */

.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -4rem;
  z-index: 100;
  padding: 0.75rem 1rem;
  background: var(--action);
  color: var(--action-ink);
  border-radius: var(--radius);
  font-weight: 700;
  text-decoration: none;
  transition: top 120ms ease;
}

/*
 * Both pseudo-classes on purpose. `:focus-visible` is the intent, but a skip
 * link is only ever reachable by Tab or by clicking it directly — and if you
 * clicked it, you wanted it — so `:focus` costs nothing and removes any
 * dependency on `:focus-visible` support or on the transition having run.
 */
.skip-link:focus,
.skip-link:focus-visible {
  top: 0.5rem;
}

/* ---------------------------------------------------------------- focus -- */

/*
 * A single focus treatment used everywhere. The ring is the action colour,
 * which clears 3:1 against every surface in both themes, plus an offset in the
 * page colour so it stays visible on top of a card as well as on the page.
 */
:where(a, button, input, select, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--action);
  outline-offset: 2px;
  border-radius: 6px;
}

/* --------------------------------------------------------------- header -- */

.site-header {
  padding-block: 1.25rem 0.5rem;
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

/* The anchor must wrap the wordmark rather than sitting on the text baseline,
   otherwise its hit area collapses to the line box and the logo is a ~19px
   tall target. */
.site-header .wrap > a {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
}

.wordmark {
  display: block;
  width: 8.5rem;
  height: auto;
  color: var(--text);
}

.wordmark path {
  fill: currentColor;
}

/* --------------------------------------------------------- theme switch -- */

.theme-switch {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
}

.theme-switch button {
  display: grid;
  place-items: center;
  /* 44px touch target even though the visual pill is smaller. */
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0 0.75rem;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.theme-switch button[aria-pressed='true'] {
  background: var(--card);
  color: var(--text);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.25);
}

.theme-switch svg {
  width: 1.125rem;
  height: 1.125rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

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

.hero {
  position: relative;
  isolation: isolate;
  padding-block: 2rem 2.5rem;
  overflow: hidden;
}

/*
 * The canvas eclipse belongs to the night. In Daylight the hero is a quiet
 * static composition instead — faster, and far easier to read outdoors, which
 * is the entire reason that theme exists.
 */
.hero-scene {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /*
   * DESIGN EXCEPTION: the nebula wash is the brand's key visual, ported
   * verbatim from chi-web's EclipseHero.tsx. It is artwork, not chrome, so it
   * deliberately does not follow the theme tokens.
   */
  background:
    radial-gradient(62% 42% at 70% 98%, rgba(52, 190, 152, 0.2), transparent 74%),
    radial-gradient(48% 32% at 16% 88%, rgba(46, 96, 190, 0.26), transparent 76%),
    radial-gradient(72% 52% at 50% 4%, rgba(24, 34, 74, 0.62), transparent 78%),
    radial-gradient(34% 26% at 92% 28%, rgba(255, 152, 80, 0.1), transparent 74%);
}

.hero-scene::after {
  /* Vignette, as upstream — keeps the type readable over the artwork. */
  content: '';
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 210px 80px rgb(6 6 10 / 0.7);
}

/*
 * Scrim behind the copy. The eclipse is pushed to the trailing edge, but the
 * corona still bleeds far enough to sit under the headline, and a bright
 * corona behind large type is exactly the case where contrast quietly fails.
 * Painted above the canvas and below the text.
 */
.hero-scene::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    100deg,
    var(--page) 0%,
    color-mix(in srgb, var(--page) 82%, transparent) 34%,
    transparent 68%
  );
}

#eclipse-canvas {
  width: 100%;
  height: 100%;
}

[data-theme='daylight'] .hero-scene {
  /* Low Arctic sun over ice, mist and stone. */
  background:
    radial-gradient(58% 40% at 78% 8%, rgb(255 236 200 / 0.85), transparent 70%),
    radial-gradient(70% 46% at 20% 96%, rgb(198 226 232 / 0.75), transparent 72%),
    linear-gradient(180deg, rgb(226 236 247 / 0.9), transparent 60%);
}

[data-theme='daylight'] .hero-scene::after {
  box-shadow: none;
}

[data-theme='daylight'] #eclipse-canvas {
  display: none;
}

.hero-daylight {
  display: none;
}

[data-theme='daylight'] .hero-daylight {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.hero-body {
  display: grid;
  gap: 1rem;
  justify-items: start;
  min-height: 15rem;
}

.hero .meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  font-size: 0.8125rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.hero .meta .sep {
  color: var(--action);
}

/* ------------------------------------------------------------------ nav -- */

.category-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  padding: 0.75rem 0;
  /* No full-bleed bar or border: the nav floats as its own pill so it reads as
     a control rather than a page division. */
  background: linear-gradient(
    to bottom,
    var(--page) 55%,
    color-mix(in srgb, var(--page) 0%, transparent)
  );
}

.category-nav ul {
  display: flex;
  gap: 0.375rem;
  list-style: none;
  /*
   * CENTRED. This previously had `margin: 0`, which out-specified `.wrap`'s
   * `margin-inline: auto` and pinned the whole bar to the left edge on wide
   * screens.
   */
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  padding: 0.3125rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  backdrop-filter: blur(14px);
  box-shadow:
    0 10px 30px -14px color-mix(in srgb, var(--page) 90%, transparent),
    inset 0 1px 0 color-mix(in srgb, var(--text) 8%, transparent);
  overflow-x: auto;
  scrollbar-width: none;
  /* Chips scroll horizontally on a phone; the page itself never does. */
  -webkit-overflow-scrolling: touch;
}

.category-nav ul::-webkit-scrollbar {
  display: none;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  white-space: nowrap;
  min-height: 2.75rem;
  padding: 0 1rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
  transition:
    background 160ms ease,
    color 160ms ease;
}

@media (hover: hover) {
  .chip:hover {
    background: color-mix(in srgb, var(--text) 8%, transparent);
    color: var(--text);
  }
}

.chip[aria-current='true'],
.chip[aria-pressed='true'] {
  background: linear-gradient(140deg, var(--action), var(--action-to));
  border-color: transparent;
  color: var(--action-ink);
}

.chip .icon {
  width: 1.125rem;
  height: 1.125rem;
}

/* ----------------------------------------------------------------- grid -- */

.product-grid {
  display: grid;
  gap: 1.125rem;
  /* Clears the floating nav pill above it. */
  padding-top: 1.75rem;
  grid-template-columns: 1fr;
  list-style: none;
  padding: 0;
  margin: 0;
}

@media (min-width: 34rem) {
  .product-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 60rem) {
  .product-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ----------------------------------------------------------------- card -- */

/*
 * A card is a pack FAMILY, not a SKU. Rendering one card per SKU produced four
 * near-identical Food cards differing only by a number; the pack size is a
 * property of the purchase, so it moved inside.
 */
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  /*
   * Opaque by default. The translucent version is opted into below, only where
   * `backdrop-filter` is supported — without the blur, a see-through card is
   * just unreadable rather than glassy.
   */
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--text) 12%, var(--border));
  border-radius: 20px;
  overflow: hidden;
  isolation: isolate;
  box-shadow:
    /* Specular top edge — the highlight that sells the material. */
    inset 0 1px 0 color-mix(in srgb, var(--text) 14%, transparent),
    0 20px 44px -24px color-mix(in srgb, var(--page) 92%, transparent);
  transition:
    transform 220ms cubic-bezier(0.2, 0.7, 0.2, 1),
    border-color 220ms ease,
    box-shadow 220ms ease;
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .card {
    /* Keep this percentage in step with `cardGlass` in theme/tokens.mjs — that
       token is the flattened value the contrast test checks. */
    background: color-mix(in srgb, var(--card) 82%, transparent);
    -webkit-backdrop-filter: blur(20px) saturate(150%);
    backdrop-filter: blur(20px) saturate(150%);
  }
}

@media (hover: hover) {
  .card:hover {
    transform: translateY(-4px);
    border-color: color-mix(in srgb, var(--action) 50%, var(--border));
    /* Corona glow — the hero's light, reaching the catalogue. */
    box-shadow:
      inset 0 1px 0 color-mix(in srgb, var(--text) 22%, transparent),
      0 26px 52px -22px color-mix(in srgb, var(--page) 88%, transparent),
      0 0 40px -6px color-mix(in srgb, var(--action) 32%, transparent);
  }
}

/* -------- media -------- */

.card-media {
  position: relative;
  aspect-ratio: 3 / 2;
  background: var(--surface);
}

.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
 * Melts the photograph into the card instead of leaving a hard cut. The scrim
 * is the card colour, so it works in both themes from one rule.
 */
.card-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    transparent 45%,
    color-mix(in srgb, var(--card) 55%, transparent) 78%,
    var(--card) 100%
  );
}

/* -------- body -------- */

.card-body {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  padding: 0 1.125rem 1.125rem;
  /* Pull the copy up over the scrim so the image and text feel like one piece. */
  margin-top: -2.25rem;
  position: relative;
  z-index: 1;
}

.card h3 {
  font-size: 1.25rem;
  letter-spacing: -0.015em;
}

.card .desc {
  color: var(--text-muted);
  font-size: 0.9375rem;
  text-wrap: pretty;
}

.card .note {
  color: var(--text-faint);
  font-size: 0.8125rem;
}

/* -------- pack size selector -------- */

.tiers {
  border: 0;
  margin: 0.375rem 0 0;
  padding: 0;
  min-inline-size: 0; /* fieldset defaults would stop it shrinking in a grid */
}

/* Visible, not screen-reader-only: a row of bare numbers needs a question. */
.tiers legend {
  padding: 0;
  margin-bottom: 0.375rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

.pill {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.0625rem;
  min-width: 3.25rem;
  min-height: 2.75rem;
  padding: 0.25rem 0.625rem;
  line-height: 1.1;
  border: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--text) 6%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--text) 10%, transparent);
  color: var(--text-muted);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition:
    background 160ms ease,
    color 160ms ease,
    border-color 160ms ease;
}

@media (hover: hover) {
  .pill:hover {
    border-color: var(--action);
    color: var(--text);
  }
}

/* The best-value tier is flagged before you select it — that is the point. */
.pill.is-best::after {
  content: '';
  position: absolute;
  top: -2px;
  right: -2px;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--card);
}

.pill-n {
  font-size: 1rem;
  font-weight: 800;
}

/*
 * The unit, always visible. It used to be screen-reader-only, so a sighted
 * visitor saw "5 / 10 / 15 / 20" with no idea what the numbers counted.
 */
.pill-u {
  font-size: 0.5625rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.75;
}

/*
 * How many of THIS pack size are already in the order. Shown on every pill at
 * once, so you can see you have two 5-packs without selecting the 5-pack.
 */
.pill-count {
  position: absolute;
  top: -0.4rem;
  right: -0.4rem;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.25rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  border: 2px solid var(--card);
  font-size: 0.6875rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* The best-value dot and a count would collide, so the count wins. */
.pill.is-best:has(.pill-count:not([hidden]))::after {
  display: none;
}

.pill-flag {
  position: absolute;
  inset: auto auto -1.4rem 50%;
  transform: translateX(-50%);
  font-size: 0.5625rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}

.pills:has(.pill-flag) {
  margin-bottom: 1.25rem;
}

/*
 * Selected + focused pill states are GENERATED per tier into tokens.css,
 * because CSS cannot correlate a label with a radio that is not its sibling
 * without naming both. See `renderTierStateCss()` in scripts/build.mjs.
 *
 * `:has()` is used only for that highlight. The tier PANEL switches with a
 * plain `:checked +` sibling selector, so choosing a pack size still works
 * where `:has()` is unsupported — you just lose the highlight.
 *
 * JavaScript also sets `.is-on`, which covers the same ground for browsers
 * without `:has()`.
 */
.pill.is-on,
.pill[data-on='true'] {
  background: linear-gradient(140deg, var(--action), var(--action-to));
  border-color: transparent;
  color: var(--action-ink);
}

/* -------- tier panels -------- */

.tier-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.tier-panel {
  display: none;
  gap: 0.125rem;
  margin-top: 0.75rem;
}

/* The whole no-JS mechanism: the checked radio reveals its own panel. */
.tier-radio:checked + .tier-panel {
  display: grid;
}

/* Keyboard focus on the hidden radio has to show on its visible label. */
.tier-radio:focus-visible + .tier-panel {
  outline: none;
}

/* What you actually get, above the price. */
.tier-contents {
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}

.price {
  font-size: 1.75rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}

.price-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.125rem 0.625rem;
  font-size: 0.875rem;
  margin-bottom: 0.25rem;
}

.unit-price {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.savings {
  color: var(--accent);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------- tier actions -- */

.tier-actions {
  margin-top: 0.25rem;
}

/*
 * Once a size is in the order the button becomes a stepper, so you can adjust
 * it where you are rather than scrolling to the order summary.
 */
.stepper {
  display: grid;
  grid-template-columns: 2.75rem 1fr 2.75rem;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1875rem;
  border: 1px solid var(--action);
  border-radius: 999px;
}

.stepper button {
  width: 2.5rem;
  height: 2.5rem;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--action) 16%, transparent);
  color: var(--action);
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}

@media (hover: hover) {
  .stepper button:hover {
    background: color-mix(in srgb, var(--action) 30%, transparent);
  }
}

.stepper-value {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0.3125rem;
  color: var(--action);
  font-weight: 700;
}

.stepper-value output {
  font-size: 1.125rem;
  font-variant-numeric: tabular-nums;
}

.stepper-unit {
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.8;
}

/*
 * The running total for this family. This is the line that answers the actual
 * question — "how many coupons do I have here, and what do they cost" — which
 * a per-tier count never could.
 */
.family-total {
  display: grid;
  gap: 0.0625rem;
  margin-top: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}

.family-total strong {
  color: var(--accent);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
}

.family-total span {
  color: var(--text-muted);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------- buttons -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  padding: 0 1.25rem;
  border: 1px solid transparent;
  border-radius: 999px;
  /* A ramp rather than a flat fill — a full-width slab of one orange was the
     single heaviest thing on the page. */
  background: linear-gradient(140deg, var(--action), var(--action-to));
  color: var(--action-ink);
  font-weight: 700;
  letter-spacing: 0.01em;
  text-decoration: none;
  cursor: pointer;
  transition:
    filter 160ms ease,
    transform 160ms ease;
}

.btn:hover {
  filter: brightness(1.06);
}

.btn:active {
  transform: translateY(1px);
}

/* Already in the order: step back to an outline so the card stops shouting. */
.btn.is-added {
  background: transparent;
  border-color: var(--action);
  color: var(--action);
}

.btn-quiet {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--text);
}

.btn[disabled],
.btn[aria-disabled='true'] {
  background: var(--disabled);
  border-color: var(--border);
  color: var(--disabled-ink);
  cursor: not-allowed;
  filter: none;
}

.card .btn {
  width: 100%;
}

/* ------------------------------------------------------------- quantity -- */

.qty {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.1875rem;
}

.qty button {
  width: 2.5rem;
  height: 2.5rem;
  border: 0;
  border-radius: calc(var(--radius) - 4px);
  background: var(--surface);
  color: var(--text);
  font-size: 1.25rem;
  font-weight: 700;
  cursor: pointer;
}

.qty button:hover {
  background: var(--card-raised);
}

.qty input {
  width: 3rem;
  height: 2.5rem;
  text-align: center;
  border: 0;
  background: transparent;
  font-weight: 700;
  /* Number spinners in a 3ch box are unusable on touch. */
  -moz-appearance: textfield;
  appearance: textfield;
}

.qty input::-webkit-outer-spin-button,
.qty input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* --------------------------------------------------------------- states -- */

.state {
  padding: 1.25rem;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  color: var(--text-muted);
  text-align: center;
}

.state.is-error {
  border-style: solid;
  border-color: var(--danger);
  color: var(--danger);
  text-align: start;
}

[hidden] {
  display: none !important;
}

/* -------------------------------------------------------------- summary -- */

.summary {
  position: sticky;
  bottom: 0;
  z-index: 30;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  backdrop-filter: blur(20px) saturate(150%);
  border-top: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
  padding: 0.875rem 0 calc(0.875rem + env(safe-area-inset-bottom));
  box-shadow: 0 -8px 24px rgb(0 0 0 / 0.18);
}

.summary .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1rem;
  /* At 320px the total plus two buttons will not fit on one line, so the row
     is allowed to wrap rather than forcing a horizontal scrollbar. */
  flex-wrap: wrap;
}

.summary .actions {
  display: flex;
  gap: 0.5rem;
  flex: 1 1 auto;
  justify-content: flex-end;
}

/* Below ~360px the buttons take the full row and split it evenly, which also
   keeps both of them comfortably above the 44px touch target. */
@media (max-width: 22.5rem) {
  .summary .total {
    flex: 1 0 100%;
  }

  .summary .actions > .btn {
    flex: 1 1 0;
  }
}

.summary .total {
  display: grid;
}

.summary .total .label {
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.summary .total .amount {
  font-size: 1.25rem;
  font-weight: 800;
}

.order-list {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  display: grid;
  gap: 0.75rem;
}

.order-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border);
}

.order-line .name {
  display: grid;
  gap: 0.0625rem;
  min-width: 0;
}

.order-line .line-title {
  font-weight: 700;
}

/* The summary used to show a quantity and a total but never what it was. */
.order-line .line-sub {
  font-size: 0.8125rem;
  color: var(--text-muted);
}

.line-controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.order-line .line-total {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

/*
 * Desktop has room to show the order inline, so the bar stops being sticky
 * rather than permanently occupying a strip of a large screen.
 */
@media (min-width: 60rem) {
  .summary {
    position: static;
    border-radius: var(--radius);
    border: 1px solid var(--border-strong);
    box-shadow: none;
    margin-block: 2rem;
  }
}

/* ------------------------------------------------------------- checkout -- */

.notice {
  display: grid;
  gap: 0.5rem;
  padding: 1.125rem;
  border: 1px solid var(--border-strong);
  border-left: 4px solid var(--action);
  border-radius: var(--radius);
  background: var(--surface);
}

.notice strong {
  color: var(--text);
}

.notice p {
  color: var(--text-muted);
}

/* ------------------------------------------------------------------ faq -- */

.faq {
  display: grid;
  gap: 0.5rem;
}

.faq details {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
}

.faq summary {
  padding: 0.875rem 1.125rem;
  min-height: 2.75rem;
  font-weight: 700;
  cursor: pointer;
}

.faq details > *:not(summary) {
  padding: 0 1.125rem 1.125rem;
  color: var(--text-muted);
}

/* --------------------------------------------------------------- footer -- */

.site-footer {
  margin-top: 3rem;
  padding-block: 2rem;
  border-top: 1px solid var(--border);
  color: var(--text-faint);
  font-size: 0.875rem;
}

.site-footer .wrap {
  display: grid;
  gap: 0.75rem;
}

.site-footer a {
  color: var(--text);
}

.site-footer .links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.25rem;
}

/* Inline text links default to the line box, which is well under the 24x24
   CSS px that WCAG 2.2 AA (2.5.8) asks for. Padded out to a full 44px so they
   are comfortable with gloves on, which is the actual use case here. */
.site-footer .links a {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
}

/* ---------------------------------------------------------------- utils -- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.stack {
  display: grid;
  gap: 1rem;
}

.section {
  padding-block: 2.5rem;
}

/* ---------------------------------------------------- reduced motion --- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* The icon sprite defines symbols only; it must not occupy layout. */
.icon-sprite {
  position: absolute;
}

/* ------------------------------------------------------------ native checkout
   The checkout panel: buyer details collected on this page before handing the
   browser to the payment provider's hosted card page, and the status panel
   shown on return. One overlay class serves both. */
.co-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(3, 3, 8, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.co-panel {
  width: 100%;
  max-width: 420px;
  max-height: calc(100vh - 32px);
  overflow: auto;
  background: var(--card-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 24px;
  color: var(--text);
}
.co-title {
  margin: 0 0 8px;
  font-size: 1.4rem;
}
.co-summary {
  margin: 0 0 4px;
  color: var(--text-muted);
  font-size: 0.95rem;
}
.co-total {
  margin: 0 0 16px;
  font-weight: 700;
}
.co-label {
  display: block;
  margin: 12px 0 4px;
  font-size: 0.9rem;
  color: var(--text-muted);
}
.co-input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  font-size: 1rem;
}
.co-input:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 1px;
}
.co-note {
  margin: 14px 0 0;
  font-size: 0.85rem;
  color: var(--text-faint);
}
.co-error {
  margin: 12px 0 0;
  color: #ff8080;
  font-size: 0.9rem;
}
.co-actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}
.co-actions .co-cancel {
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--text);
}
