/*
 * munchterm plus: the shared design system. Owned by the foundation.
 *
 * Every page links this first, then one stylesheet per flow (home, join, account, payment,
 * cancel, misc). This file holds everything more than one page uses: tokens, reset, type,
 * layout, the Shell, the sticky action bar, buttons, forms, notices, cards, the pass card,
 * chips, tiles, rails, menus, the step indicator, status screens, loading states and print.
 * Flow files hold only their own page classes, prefixed with the flow name (`home-`, `join-`,
 * `acct-`, `pay-`, `cancel-`, `misc-`). A class a second flow needs moves here.
 *
 * It is built to read as a continuation of munchterm.co.uk, so the values below come from the
 * storefront theme rather than from taste. Sources, for re-syncing by hand:
 *
 *   colour    config/settings_data.json -> current.color_schemes. scheme-1 is the default light
 *             scheme (#fafafa ground, #000000cf body text, #b34545 link hover). The pink scheme
 *             (scheme-605fa5a9...) is the announcement bar and every pink band: #ff6363 ground,
 *             #fafafa text, and a white button with pink text. scheme-41a7320b... is the #f1f1f1
 *             grey of the header's bottom row and the footer.
 *   accents   sections/header-group.json: smoking_accent_color #ff6363.
 *   type      type_heading_font inter_n7, type_body_font nunito_sans_n4, type_subheading_font
 *             nunito_n4. The theme's h1 is 56px with display-tight line height.
 *   shape     every radius setting in the theme is 0. Square is the look. Do not round things.
 *   buttons   primary: filled, no border. secondary: 2px border, transparent fill
 *             (primary_button_border_width 0, secondary_button_border_width 2).
 *   icons     the munchterm icon pack (_apps/icons): filled pixel drawings on a 24px grid in
 *             currentColor (app/components/Icon.tsx). admin may set a pack icon or upload pixel
 *             art per icon; an upload renders pixelated (.icon--image).
 *             pixel art, so 24px or a whole multiple (48, 72) only, never 16 or 20 (.icon).
 *
 * Rules that apply everywhere:
 *   - mobile first. The base rules are the phone layout; min-width queries widen it.
 *   - tap targets at least 44px (48px for buttons). Inputs at least 16px so iOS does not zoom.
 *   - no text-transform: uppercase. The storefront sets type_case_* to none; the brand is lowercase.
 *   - state is never colour alone: always a word and an icon too.
 *   - motion is opacity and transform only, 150 to 250ms, and off under prefers-reduced-motion.
 *   - env(safe-area-inset-*) on anything pinned to a screen edge.
 *
 * Why these are files in public/ and not imported modules: see the comment above `links` in
 * app/root.tsx. The design system is documented in _ops/munchterm-plus/13-plus-redesign.md.
 */

:root {
  /* --- colour --------------------------------------------------------- */
  --pink: #ff6363;
  --pink-hover: #e65959;
  --pink-deep: #b34545;
  --pink-wash: #fff0f0;
  --blue: #63b1ff;
  --blue-deep: #000f9f;
  --blue-wash: #eef6ff;

  --ground: #fafafa;
  --surface: #ffffff;
  --surface-sunk: #f3f3f3;
  --surface-grey: #f1f1f1;
  --ink: #000000;
  --ink-body: rgba(0, 0, 0, 0.81);
  --ink-muted: rgba(0, 0, 0, 0.6);
  --line: rgba(0, 0, 0, 0.08);
  --line-strong: rgba(0, 0, 0, 0.16);
  --dark: #1f1f1f;
  --on-dark: rgba(255, 255, 255, 0.92);
  --on-dark-muted: rgba(255, 255, 255, 0.64);
  /* Text on pink: the shop's own #fafafa (the pink scheme in settings_data.json), for exact
     parity with munchterm.co.uk, by owner decision. It is 2.8:1 on #ff6363, below WCAG AA for
     small text, so labels on pink stay short and bold (buttons, the announcement, the pass, the
     price band, chips). A white button on pink takes pink text, as in the shop. */
  --on-pink: #fafafa;

  /* States. Amber is "action needed", red is "ended" or a real error. Never colour alone. */
  --positive: #2e7d32;
  --positive-wash: #e8f3e9;
  --warning: #8a5100;
  --warning-bright: #ffb040;
  --warning-wash: #fff6e8;
  --critical: #c62828;
  --critical-wash: #fdecec;
  --info: #1f1f1f;
  --info-wash: #f3f3f3;

  /* --- type ----------------------------------------------------------- */
  --font-heading: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-body: "Nunito Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-sub: "Nunito", "Nunito Sans", -apple-system, BlinkMacSystemFont, sans-serif;

  --size-display: 2.5rem;
  --size-h1: 2rem;
  --size-h2: 1.5rem;
  --size-h3: 1.125rem;
  --size-body: 1rem;
  --size-lede: 1.125rem;
  --size-small: 0.875rem;
  --size-tiny: 0.75rem;

  /* --- space ---------------------------------------------------------- */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;
  --s8: 64px;

  --gutter: 16px;
  --wide: 1120px;
  --narrow: 640px;
  --tap: 48px;
  --tap-min: 44px;
  --header-h: 56px;
  /* What the sticky header actually covers: its row plus its 1px bottom rule. Anything pinned
     under the header, or scrolled to, clears this. */
  --header-total: calc(var(--header-h) + 1px);
  /* The phone action bar's height (one row of 48px buttons plus padding), and the stacked bar's
     (a summary line above the button). The footer makes this much room under itself. */
  --actionbar-h: 80px;
  --actionbar-h-stacked: 112px;

  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);

  /* --- motion --------------------------------------------------------- */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --fast: 150ms;
  --slow: 250ms;
}

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

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-padding-top: calc(var(--header-total) + var(--s4));
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
  color: var(--ink-body);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

img,
svg {
  display: block;
}

/* Fluid media shrink to fit. Icons never do (below), so they are left out of these. */
img:where(:not(.icon)),
svg:where(:not(.icon)) {
  max-width: 100%;
}

img:where(:not(.icon)) {
  height: auto;
}

/* Icons (app/components/Icon.tsx) are pixel art on a 24px grid, and render at 24px or a whole
   multiple (48, 72), never anything between: at 16 or 20 a drawn pixel falls between screen
   pixels and goes soft. So the box is pinned here, !important on purpose: no page rule, flex
   parent or `img` rule can squash, stretch or scale an icon. Next to text an icon stays 24 and
   the layout makes room for it (align-items: center, a 24px column, a taller chip).
   Built in, they are inline SVG in currentColor. Uploaded in admin (a `plus_icon`), they are
   an <img>: square, never stretched, and scaled nearest-neighbour so the pixels stay hard, as
   the storefront theme renders its pixel art (blocks/_collection-link.liquid,
   blocks/flavour-finder.liquid). A test (components.test.ts) holds every stylesheet to this. */
.icon {
  flex: none;
  width: 24px !important;
  height: 24px !important;
  min-width: 24px !important;
  min-height: 24px !important;
  max-width: none !important;
  max-height: none !important;
}

.icon--48 {
  width: 48px !important;
  height: 48px !important;
  min-width: 48px !important;
  min-height: 48px !important;
}

.icon--72 {
  width: 72px !important;
  height: 72px !important;
  min-width: 72px !important;
  min-height: 72px !important;
}

/* An uploaded SVG, inlined so it draws in currentColor. The drawing fills the icon box. */
.icon--svg {
  display: block;
}

.icon--svg > svg {
  width: 100%;
  height: 100%;
  shape-rendering: crispEdges;
}

.icon--image {
  aspect-ratio: 1;
  object-fit: contain;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

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

/* Zero specificity, so a list's own class can set its margin (`.ticks`, `.rows`) and win. */
:where(ul[class], ol[class]) {
  margin: 0;
  padding: 0;
  list-style: none;
}

fieldset {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

dl,
dd {
  margin: 0;
}

/* --- type --------------------------------------------------------------- */

h1,
h2,
h3,
h4 {
  margin: 0 0 var(--s3);
  color: var(--ink);
  font-family: var(--font-heading);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

h1 { font-size: var(--size-h1); }
h2 { font-size: var(--size-h2); }
h3 { font-size: var(--size-h3); letter-spacing: -0.01em; }
h4 { font-size: var(--size-body); letter-spacing: -0.01em; }

.display {
  font-size: var(--size-display);
  line-height: 1;
  letter-spacing: -0.035em;
}

p {
  margin: 0 0 var(--s4);
  text-wrap: pretty;
}

p:last-child {
  margin-bottom: 0;
}

a {
  color: inherit;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

a:hover {
  color: var(--pink-deep);
}

strong {
  color: var(--ink);
  font-weight: 700;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin: 0 0 var(--s3);
  color: var(--ink-muted);
  font-family: var(--font-sub);
  font-size: var(--size-small);
}

.lede {
  font-size: var(--size-lede);
  line-height: 1.5;
}

.muted { color: var(--ink-muted); }
.small { font-size: var(--size-small); }
.tiny { font-size: var(--size-tiny); }
.num { font-variant-numeric: tabular-nums; }
.center { text-align: center; }

/* A visible focus ring is not optional here. This app handles someone's money. */
:focus-visible {
  outline: 2px solid var(--pink);
  outline-offset: 2px;
}

.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:focus {
  position: fixed;
  z-index: 100;
  top: var(--s2);
  left: var(--s2);
  width: auto;
  height: auto;
  margin: 0;
  padding: var(--s3) var(--s4);
  clip: auto;
  background: var(--ink);
  color: #fff;
}

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

.wrap {
  width: 100%;
  max-width: calc(var(--wide) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: calc(var(--gutter) + var(--safe-left)) calc(var(--gutter) + var(--safe-right));
}

.wrap--narrow {
  max-width: calc(var(--narrow) + 2 * var(--gutter));
}

.main {
  flex: 1 0 auto;
}

/* A page's own padding. `.page--flush` for pages whose first section is full bleed. */
.page {
  padding-block: var(--s5) var(--s8);
}

.page--flush {
  padding-top: 0;
}

.stack > * + * { margin-top: var(--s4); }
.stack-sm > * + * { margin-top: var(--s2); }
.stack-lg > * + * { margin-top: var(--s6); }

.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
}

.section {
  padding-block: var(--s7);
}

.section--sunk { background: var(--surface-sunk); }

.section--pink {
  background: var(--pink);
  color: var(--on-pink);
}

.section--dark {
  background: var(--ink);
  color: var(--on-dark);
}

.section--pink :is(h1, h2, h3, strong),
.section--dark :is(h1, h2, h3, strong) {
  color: inherit;
}

.section--dark .muted { color: var(--on-dark-muted); }

.section-head {
  display: grid;
  gap: var(--s3);
  margin-bottom: var(--s5);
}

.section-head h1,
.section-head h2,
.section-head h3 {
  margin-bottom: 0;
}

.section-head__body {
  margin-top: var(--s2);
  color: var(--ink-muted);
}

.section--pink .section-head__body,
.section--dark .section-head__body,
.section--pink .eyebrow,
.section--dark .eyebrow {
  color: inherit;
  opacity: 0.85;
}

/* Two columns from 900px: a head on the left, the content on the right. */
.split {
  display: grid;
  gap: var(--s5);
}

.divider {
  height: 1px;
  margin-block: var(--s5);
  border: 0;
  background: var(--line);
}

/* --- the storefront header --------------------------------------------- */

.announce {
  background: var(--pink);
  color: var(--on-pink);
  font-family: var(--font-sub);
  font-size: var(--size-small);
  text-align: center;
  padding: 10px calc(var(--gutter) + var(--safe-right)) 10px calc(var(--gutter) + var(--safe-left));
  line-height: 1.4;
}

.announce a {
  color: inherit;
}

.header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(250, 250, 250, 0.94);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--line);
}

.header__inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  min-height: var(--header-h);
}

.header__side {
  display: flex;
  align-items: center;
  gap: var(--s1);
}

.header__side--end {
  justify-content: flex-end;
}

.header__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding-inline: var(--s2);
  margin-inline: calc(-1 * var(--s2));
  color: var(--ink);
  font-size: var(--size-small);
  text-decoration: none;
}

.header__link:hover {
  color: var(--pink-deep);
}

/* The label next to an icon is hidden on the smallest phones and comes back at 400px. */
.header__label {
  display: none;
}

.brand {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--ink);
  text-decoration: none;
}

/* The lockup is 1069x256, the shop's header logo ratio: 27px tall on phones, 36px from 900px,
   exactly the storefront's logo heights. The box is sized to that ratio in CSS (113x27, 150x36)
   rather than left to the file, so the header never jumps while it loads and a swapped admin
   logo of another shape is fitted inside the same box instead of stretching the header. At
   320px wide the header's side links are icons only, so 113px always fits. */
.brand__logo {
  height: 27px;
  width: calc(27px * 1069 / 256);
  max-width: 44vw;
  object-fit: contain;
}

/* The ecosystem bar: shop, my munchterm, plus. A quiet segmented control under the header, on the
   shop's #f1f1f1 grey like the storefront header's bottom row. Equal segments (a third each on a
   phone, so every one is a wide 44px target), hairlines between them, and the current one marked
   by a pink rule sitting on the bar's bottom edge, like a tab. Never filled: this is wayfinding,
   not a call to action. */
.ecosystem {
  background: var(--surface-grey);
  border-bottom: 1px solid var(--line);
}

.ecosystem__list {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  max-width: 440px;
}

.ecosystem__list.wrap {
  padding-inline: var(--safe-left) var(--safe-right);
}

.ecosystem__list > li + li {
  border-left: 1px solid var(--line);
}

/* Once the bar is narrower than the screen, close its ends so it reads as one control. */
@media (min-width: 441px) {
  .ecosystem__list > li:first-child {
    border-left: 1px solid var(--line);
  }

  .ecosystem__list > li:last-child {
    border-right: 1px solid var(--line);
  }
}

.ecosystem__link {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding-inline: var(--s2);
  color: var(--ink-muted);
  font-family: var(--font-sub);
  font-size: var(--size-small);
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--fast) var(--ease), background-color var(--fast) var(--ease);
}

.ecosystem__link:hover {
  background: rgba(0, 0, 0, 0.03);
  color: var(--ink);
}

.ecosystem__link--current {
  color: var(--ink);
  font-weight: 700;
}

.ecosystem__link--current::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--pink);
}

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

.footer {
  margin-top: auto;
  background: var(--dark);
  color: var(--on-dark);
  /* --actionbar-room is the pinned action bar's height when a page has one (see the action bar
     below), so the bar never covers the footer's last lines. */
  padding-block: var(--s7) calc(var(--s6) + var(--safe-bottom) + var(--actionbar-room, 0px));
  font-size: var(--size-small);
}

.footer__logo {
  height: 24px;
  width: calc(24px * 1069 / 256);
  object-fit: contain;
  object-position: left center;
  margin-bottom: var(--s6);
}

/* Two columns on a phone (three link groups in two short rows, not one long list), three from
   600px. */
.footer__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5) var(--s4);
}

.footer h2 {
  color: #fff;
  font-size: var(--size-body);
  margin-bottom: var(--s2);
}

.footer ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.footer li a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--on-dark);
  text-decoration: none;
}

.footer li a:hover {
  color: var(--pink);
}

.footer__small {
  margin-top: var(--s6);
  padding-top: var(--s5);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  color: var(--on-dark-muted);
  font-size: var(--size-tiny);
}

/* --- sticky action bar (app/components/ActionBar.tsx) -------------------- */

/* On phones the bar is pinned to the bottom, over the page, clear of the home indicator.
   Variants (the `sticky` prop): `--phone` pinned on phones, back in the page flow from 900px;
   `--phone-only` pinned on phones, gone from 900px (the page shows the same action inline, in a
   `.desk-only` wrapper); `--always` pinned at every width. `--stacked` puts the summary above
   the button instead of beside it.

   Room for the bar: it is fixed, so it covers whatever is under it at the end of the page. That
   is always the footer (the bar can sit mid-form, so a spacer where it is rendered would open a
   gap in the middle of the page instead). So the page, not the bar, makes the room: when a
   pinned bar is on the page, the footer grows by the bar's height (--actionbar-room, used in
   .footer above). One rule for every page; no flow has to remember it. */
.actionbar {
  position: fixed;
  z-index: 30;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(255, 255, 255, 0.97);
  border-top: 1px solid var(--line-strong);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  backdrop-filter: saturate(1.4) blur(10px);
  padding: var(--s3) calc(var(--gutter) + var(--safe-right)) calc(var(--s3) + var(--safe-bottom)) calc(var(--gutter) + var(--safe-left));
}

body:has(.actionbar) {
  --actionbar-room: var(--actionbar-h);
}

body:has(.actionbar--stacked) {
  --actionbar-room: var(--actionbar-h-stacked);
}

.actionbar__inner {
  display: flex;
  align-items: center;
  gap: var(--s3);
  max-width: var(--wide);
  margin-inline: auto;
}

.actionbar__summary {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--ink);
  font-size: var(--size-small);
  line-height: 1.3;
}

.actionbar__summary strong {
  display: block;
  font-family: var(--font-heading);
  font-size: var(--size-body);
}

.actionbar__actions {
  flex: 1 1 auto;
  display: flex;
  gap: var(--s2);
}

.actionbar__summary + .actionbar__actions {
  flex: 0 1 auto;
}

.actionbar__actions > * {
  flex: 1 1 auto;
}

/* Beside a summary the button keeps its label on one line ("pay £3.99 now") and the summary
   wraps instead. */
.actionbar__summary + .actionbar__actions .btn {
  white-space: nowrap;
}

/* Two rows: what it costs (and a note) on one line, then the button full width. */
.actionbar--stacked .actionbar__inner {
  flex-direction: column;
  align-items: stretch;
  gap: var(--s2);
}

.actionbar--stacked .actionbar__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--s2);
  color: var(--ink-muted);
}

.actionbar--stacked .actionbar__summary strong {
  display: inline;
  color: var(--ink);
}

.actionbar--stacked .actionbar__actions {
  flex: none;
}

/* The inline twin of a `--phone-only` bar's action: hidden on phones, where the bar carries it,
   so there is only ever one primary button on screen. Shown from 900px (below). `.desk-only` is
   a block wrapper; `.bar-twin` goes on the button itself when it sits in a row of other buttons
   (the pass footer, a notice) and must keep its own display from 900px. */
.desk-only {
  display: none;
}

@media (max-width: 899.98px) {
  .bar-twin {
    display: none !important;
  }

  /* A row that held only the twin goes with it, so it leaves no empty gap behind. */
  :is(.pass__footer, .notice__action):not(:has(> :not(.bar-twin))) {
    display: none;
  }
}

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

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: var(--tap);
  padding: var(--s3) var(--s5);
  border: 2px solid transparent;
  border-radius: 0;
  background: var(--pink);
  color: var(--on-pink);
  font-family: var(--font-heading);
  font-size: var(--size-body);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease), border-color var(--fast) var(--ease);
  -webkit-appearance: none;
  appearance: none;
}

.btn:hover {
  background: var(--pink-hover);
  color: var(--on-pink);
}

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.6;
  cursor: default;
}

/* Busy (SubmitButton sets aria-busy): a small square turning, beside the busy label. */
.btn[aria-busy="true"] {
  opacity: 1;
  cursor: progress;
}

.btn__spinner {
  flex: none;
  width: 12px;
  height: 12px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* The dark button: equal weight to the pink one, for neutral decisions ("cancel membership"). */
.btn--dark {
  background: var(--ink);
  color: #fff;
}

.btn--dark:hover {
  background: #333;
  color: #fff;
}

/* The theme's secondary button: 2px border, no fill. Same size and tap target as its primary
   neighbour on purpose ("keep it" next to "cancel" is a real choice). */
.btn--ghost {
  background: transparent;
  border-color: var(--ink);
  color: var(--ink);
}

.btn--ghost:hover {
  background: rgba(0, 0, 0, 0.04);
  border-color: var(--ink);
  color: var(--ink);
}

/* On a pink or dark band: the shop's pink scheme button, white with pink text. */
.btn--light {
  background: var(--on-pink);
  color: var(--pink);
}

.btn--light:hover {
  background: #fff;
  color: var(--pink-deep);
}

.section--dark .btn--ghost,
.section--pink .btn--ghost {
  border-color: currentColor;
  color: inherit;
}

.btn--block {
  display: flex;
  width: 100%;
}

.btn--sm {
  min-height: var(--tap-min);
  padding: var(--s2) var(--s4);
  font-size: var(--size-small);
}

.btn-row {
  display: grid;
  gap: var(--s3);
}

/* A button that reads as a link, for "log out" in a line of text. */
.link-button {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.link-button:hover {
  color: var(--pink-deep);
}

/* A small "more" link with an arrow, beside a section head: "every item and its odds". */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  min-height: var(--tap-min);
  color: var(--ink);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.link-arrow:hover {
  color: var(--pink-deep);
}

.back-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  min-height: var(--tap-min);
  color: var(--ink-muted);
  font-size: var(--size-small);
  text-decoration: none;
}

.back-link:hover {
  color: var(--ink);
}

/* A link that leaves plus, labelled as such ("opens viva wallet"). The 24px icon is centred on
   the note, so a one line note sits on the icon's middle and a wrapped one is framed by it. */
.handoff {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: 24px;
  color: var(--ink-muted);
  font-size: var(--size-small);
  line-height: 1.4;
}

/* --- forms ---------------------------------------------------------------- */

.form {
  display: grid;
  gap: var(--s5);
}

.fieldset legend {
  padding: 0;
  margin-bottom: var(--s1);
  color: var(--ink);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--size-h3);
}

.fieldset__hint {
  margin-bottom: var(--s3);
  color: var(--ink-muted);
  font-size: var(--size-small);
}

.field {
  display: grid;
  gap: var(--s1);
}

.field__label {
  color: var(--ink);
  font-weight: 700;
  font-size: var(--size-small);
}

/* 16px or more, always: anything smaller makes iOS zoom the page on focus. */
.input {
  width: 100%;
  min-height: var(--tap);
  padding: var(--s3);
  border: 1px solid var(--line-strong);
  border-radius: 0;
  background: var(--surface);
  font-size: max(16px, 1rem);
}

.input:focus-visible {
  border-color: var(--pink);
}

/* Radio cards (app/components/RadioCard.tsx). The whole card is the tap target. Neutral on
   purpose: white, a grey hairline, ink text. No category colours. The chosen card says so with
   an ink border and an ink tick, so the choice reads the same in any colour vision. */
.radio-cards {
  display: grid;
  gap: var(--s2);
}

.radio-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: 72px;
  padding: var(--s4);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  cursor: pointer;
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}

.radio-card:hover {
  border-color: var(--ink-muted);
}

.radio-card__input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.radio-card__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  background: var(--surface-sunk);
  color: var(--ink);
}

.radio-card__text {
  flex: 1;
  display: grid;
  gap: 2px;
  min-width: 0;
}

.radio-card__label {
  color: var(--ink);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.0625rem;
}

.radio-card__detail {
  color: var(--ink-muted);
  font-size: var(--size-small);
  line-height: 1.4;
}

/* The tick: an empty square, filled ink with a white check when chosen. 24px, and the check (the
   pack's check, _apps/icons, as Icon.tsx draws it) is drawn at 24 over the whole box
   (border-box), so it sits on the icon grid like the rest. */
.radio-card__tick {
  flex: none;
  width: 24px;
  height: 24px;
  border: 1px solid var(--line-strong);
  background: var(--surface) center / 24px 24px no-repeat border-box;
}

/* Chosen: the border thickens to 2px of ink (1px border plus a 1px inset ring, so nothing
   shifts), and the tick fills. */
.radio-card:has(.radio-card__input:checked) {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

.radio-card:has(.radio-card__input:checked) .radio-card__tick {
  border-color: var(--ink);
  background-color: var(--ink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff' shape-rendering='crispEdges'%3E%3Cpath d='M18 6H20V8H18ZM16 8H18V10H16ZM14 10H16V12H14ZM4 12H6V14H4ZM12 12H14V14H12ZM6 14H8V16H6ZM10 14H12V16H10ZM8 16H10V18H8Z'/%3E%3C/svg%3E");
}

.radio-card:has(.radio-card__input:focus-visible) {
  outline: 2px solid var(--pink);
  outline-offset: 2px;
}

/* Chips: a small single-select answer (cancel reasons). Wrap on to as many lines as needed. */
.chip-choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

.chip-choice {
  position: relative;
  display: inline-flex;
}

.chip-choice input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.chip-choice span {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: var(--s2) var(--s4);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  color: var(--ink);
  font-size: var(--size-small);
  cursor: pointer;
  transition: border-color var(--fast) var(--ease), background-color var(--fast) var(--ease);
}

.chip-choice input:checked + span {
  border-color: var(--ink);
  background: var(--ink);
  color: #fff;
}

.chip-choice input:focus-visible + span {
  outline: 2px solid var(--pink);
  outline-offset: 2px;
}

/* A checkbox with its words beside it (the join consent uses its own `.consent`). */
.check {
  display: flex;
  gap: var(--s3);
  align-items: flex-start;
  min-height: var(--tap-min);
  cursor: pointer;
  color: var(--ink);
  line-height: 1.5;
}

.check input {
  flex: none;
  width: 22px;
  height: 22px;
  margin: 1px 0 0;
  accent-color: var(--pink);
}

/* An inline form error, under the field or above the button. */
.error {
  margin: 0;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--critical);
  border-left-width: 4px;
  background: var(--critical-wash);
  color: var(--critical);
}

/* --- notices (app/components/Notice.tsx) ---------------------------------- */

.notice {
  display: flex;
  gap: var(--s3);
  align-items: flex-start;
  padding: var(--s4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 4px solid var(--info);
}

.notice__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  color: var(--info);
}

.notice__body {
  flex: 1;
  min-width: 0;
}

.notice__title {
  margin: 0 0 2px;
  color: var(--ink);
  font-family: var(--font-heading);
  font-weight: 700;
}

.notice__text {
  font-size: var(--size-small);
}

.notice__text p { margin: 0; }

.notice__action {
  margin-top: var(--s3);
}

.notice--positive { border-left-color: var(--positive); }
.notice--positive .notice__icon { color: var(--positive); }
.notice--warning { border-left-color: var(--warning-bright); background: var(--warning-wash); }
.notice--warning .notice__icon { color: var(--warning); }
.notice--critical { border-left-color: var(--critical); background: var(--critical-wash); }
.notice--critical .notice__icon { color: var(--critical); }
.notice--info { background: var(--info-wash); }

/* The legacy notice markup (<Icon/><p><strong/>text</p>) still reads right until routes move
   to <Notice>. */
.notice > .icon {
  margin-top: 2px;
}

.notice > p { margin: 0; }

.notice > p > strong {
  display: block;
  font-family: var(--font-heading);
}

/* --- cards ------------------------------------------------------------------ */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  padding: var(--s5) var(--s4);
}

.card--sunk {
  background: var(--surface-sunk);
  border-color: transparent;
}

.card--flush {
  padding: 0;
}

.card__title {
  margin-bottom: var(--s2);
}

/* --- the pass (app/components/PassCard.tsx) ----------------------------------- */

/* A membership pass: square, the plus mark top left, the state word + icon top right. Tones map
   to membership states; each also has its word and icon. No corner ornament: it sat behind the
   facts row on phones and cost legibility, and calm is the premium signal. */
.pass {
  position: relative;
  padding: var(--s5) var(--s4);
  background: var(--pink);
  color: var(--on-pink);
}

.pass__top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s6);
}

/* The mark sits on a white tile so the artwork reads on any tone. */
.pass__mark {
  display: inline-flex;
  padding: 6px 8px;
  background: #fff;
}

/* Ticked in site settings ("small mark without its white tile"): the mark sits straight on the card. */
.pass__mark--plain {
  padding: 0;
  background: none;
}

.pass__mark img {
  height: 28px;
  width: calc(28px * 623 / 256);
  object-fit: contain;
}

.pass__state {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  min-height: 32px;
  padding: var(--s1) var(--s2) var(--s1) var(--s1);
  background: rgba(255, 255, 255, 0.2);
  color: inherit;
  font-family: var(--font-sub);
  font-size: var(--size-small);
  line-height: 1;
  white-space: nowrap;
}

.pass__name {
  margin: 0 0 var(--s1);
  opacity: 0.85;
  font-size: var(--size-small);
}

.pass__headline {
  color: inherit;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.5rem;
  line-height: 1.15;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.pass__headline :is(h1, h2) {
  margin: 0;
  color: inherit;
  font-size: inherit;
}

.pass__body {
  margin-top: var(--s3);
  max-width: 44ch;
}

.pass__body dl {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s5);
  margin-top: var(--s4);
}

.pass__body dt {
  font-size: var(--size-tiny);
  opacity: 0.85;
}

.pass__body dd {
  font-family: var(--font-heading);
  font-weight: 700;
}

.pass__footer {
  display: grid;
  gap: var(--s2);
  margin-top: var(--s5);
}

.pass--warning {
  background: #2b2116;
  color: #fff;
}

.pass--warning .pass__state {
  background: var(--warning-bright);
  color: #2b2116;
}

.pass--ending {
  background: var(--dark);
  color: var(--on-dark);
}

.pass--ended {
  background: var(--surface-sunk);
  color: var(--ink);
}

.pass--ended .pass__state {
  background: var(--surface);
  color: var(--ink);
}

.pass--pending {
  background: var(--surface);
  color: var(--ink);
  border: 2px dashed var(--line-strong);
}

.pass--pending .pass__state {
  background: var(--warning-wash);
  color: var(--warning);
}

/* --- chips and badges ---------------------------------------------------------- */

.chip,
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 var(--s2);
  background: var(--surface-sunk);
  color: var(--ink-body);
  font-family: var(--font-sub);
  font-size: var(--size-tiny);
  line-height: 1;
  white-space: nowrap;
}

.chip--accent,
.badge--accent {
  background: var(--pink);
  color: var(--on-pink);
}

.chip--dark,
.badge--dark {
  background: var(--ink);
  color: #fff;
}

.chip--positive,
.badge--positive {
  background: var(--positive-wash);
  color: var(--positive);
}

.chip--warning,
.badge--warning {
  background: var(--warning-wash);
  color: var(--warning);
}

.chip--critical,
.badge--critical {
  background: var(--critical-wash);
  color: var(--critical);
}

/* Rarity: common is plain; anything rarer is the pink chip with a small white square. */
.chip--rarity.chip--plain {
  background: var(--surface);
  border: 1px solid var(--line-strong);
}

.chip__dot {
  width: 6px;
  height: 6px;
  background: currentColor;
}

/* --- product tiles and rails ------------------------------------------------------ */

.tiles {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s3);
}

.tile {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  min-width: 0;
}

/* A drop close to its claim-by date: amber, "action needed", with the days left in words on the
   tile. Red is kept for things that have ended. */
.tile--urgent {
  border-color: var(--warning-bright);
  box-shadow: inset 0 0 0 1px var(--warning-bright);
}

.tile__media {
  position: relative;
  aspect-ratio: 1;
  background: #fff;
  display: grid;
  place-items: center;
  padding: 14%;
  border-bottom: 1px solid var(--line);
}

.tile__media img:where(:not(.icon)) {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* A sealed stash drop (Plus 79): an ink square with the gift, nothing about the item. */
.tile__sealed {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--dark);
  color: var(--on-dark);
}

.tile__media .chip,
.tile__media .badge {
  position: absolute;
  top: var(--s2);
  left: var(--s2);
}

.tile__number {
  position: absolute;
  top: var(--s2);
  right: var(--s2);
  color: var(--ink-muted);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--size-tiny);
}

/* The munchterm plus badge on a plus drop: 24px, on the grid, like every icon. */
.tile__badge {
  position: absolute;
  top: var(--s2);
  right: var(--s2);
  display: flex;
  color: var(--ink);
}

.tile__placeholder {
  width: 56%;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  background: var(--pink-wash);
  color: var(--pink);
}

.tile__body {
  display: grid;
  gap: var(--s1);
  padding: var(--s3);
}

.tile__title {
  color: var(--ink);
  font-size: var(--size-small);
  line-height: 1.35;
}

.tile__meta {
  color: var(--ink-muted);
  font-size: var(--size-tiny);
}

.tile__action {
  margin-top: var(--s1);
}

.tile__action .btn {
  width: 100%;
}

.tile__meta--urgent {
  color: var(--warning);
  font-weight: 700;
}

/* A rail: tiles in one horizontal row that scrolls and snaps on phones, and becomes a normal
   grid from 900px. It bleeds to the screen edge so the next tile peeks in as a hint. */
.rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(148px, 42%);
  gap: var(--s3);
  margin-inline: calc(-1 * var(--gutter));
  padding: 0 var(--gutter) var(--s2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
  scrollbar-width: none;
}

.rail::-webkit-scrollbar {
  display: none;
}

.rail > * {
  scroll-snap-align: start;
}

/* --- ticks, facts, menus ------------------------------------------------------------ */

.ticks {
  display: grid;
  gap: var(--s2);
  margin: 0 0 var(--s4);
  padding: 0;
  list-style: none;
}

/* The tick sits in a 24px column, and the line is 24px tall, so the first line of every item
   centres on its tick and a wrapped item hangs under the text, not the tick. */
.ticks li {
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: var(--s3);
  align-items: start;
  color: var(--ink);
  line-height: 24px;
}

.ticks .icon {
  color: var(--pink);
}

.section--dark .ticks li,
.section--pink .ticks li {
  color: inherit;
}

/* A definition list as label/value rows. */
.facts > div {
  display: flex;
  gap: var(--s4);
  justify-content: space-between;
  align-items: baseline;
  padding-block: var(--s3);
  border-bottom: 1px solid var(--line);
}

.facts > div:last-child {
  border-bottom: 0;
}

.facts dt {
  color: var(--ink-muted);
  font-size: var(--size-small);
}

.facts dd {
  text-align: right;
  color: var(--ink);
}

/* A settings-style list of full-width rows, each one tap target. */
.menu {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
}

.menu li + li {
  border-top: 1px solid var(--line);
}

.menu a,
.menu button,
.menu__row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: 64px;
  padding: var(--s3) var(--s4);
  border: 0;
  background: none;
  color: var(--ink);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.menu__row {
  min-height: 56px;
  cursor: default;
}

.menu a:hover,
.menu button:hover {
  background: var(--ground);
  color: var(--ink);
}

.menu__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  background: var(--surface-sunk);
  color: var(--ink);
}

.menu__text {
  flex: 1;
  display: grid;
  gap: 2px;
  min-width: 0;
}

.menu__label {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.9375rem;
}

.menu__detail {
  color: var(--ink-muted);
  font-size: var(--size-small);
  line-height: 1.35;
}

.menu__chev {
  flex: none;
  display: flex;
  color: var(--ink-muted);
}

/* --- rows and disclosures ------------------------------------------------------------ */

/* A list of tappable rows (payments, drop history): title and a quiet line on the left, an
   amount or chip on the right, a chevron when the row is a link. 64px tall, so easy to hit. */
.rows {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
}

.rows > li + li {
  border-top: 1px solid var(--line);
}

.row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: 64px;
  padding: var(--s3) var(--s4);
  color: var(--ink);
  text-decoration: none;
}

.row--link:hover {
  background: var(--ground);
  color: var(--ink);
}

.row__main {
  flex: 1;
  display: grid;
  gap: 2px;
  min-width: 0;
}

.row__title {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.9375rem;
}

.row__sub {
  color: var(--ink-muted);
  font-size: var(--size-small);
}

.row__end {
  display: grid;
  justify-items: end;
  gap: var(--s1);
}

.row__amount {
  font-family: var(--font-heading);
  font-weight: 700;
}

.row__chev {
  flex: none;
  display: flex;
  color: var(--ink-muted);
}

/* A <details> disclosure for "the rest" (older payments, drop history): a full-width tappable
   summary with a chevron that turns. Works with no JavaScript. */
.more {
  margin-top: var(--s4);
}

.more > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  min-height: var(--tap);
  padding: 0 var(--s4);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.9375rem;
  cursor: pointer;
  list-style: none;
}

.more > summary::-webkit-details-marker {
  display: none;
}

.more > summary .icon {
  transform: rotate(90deg);
  transition: transform var(--fast) var(--ease);
}

.more[open] > summary .icon {
  transform: rotate(-90deg);
}

.more > .rows {
  border-top: 0;
}

/* --- step indicator (app/components/StepIndicator.tsx) ------------------------------ */

.stepper {
  margin-bottom: var(--s4);
}

.stepper__count {
  margin: 0;
  color: var(--ink-muted);
  font-family: var(--font-sub);
  font-size: var(--size-small);
}

.stepper__label {
  color: var(--ink);
}

/* The named steps only have room from 600px. */
.stepper__list {
  display: none;
}

.stepper__step {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--ink-muted);
  font-size: var(--size-small);
}

.stepper__num {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--line-strong);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--size-tiny);
}

/* Done steps are quiet (ink outline), so the filled square is only ever where you are. */
.stepper__step--done .stepper__num {
  border-color: var(--ink);
  color: var(--ink);
}

.stepper__step--current {
  color: var(--ink);
  font-weight: 700;
}

.stepper__step--current .stepper__num {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}

/* --- status screens (app/components/StatusScreen.tsx) ------------------------------ */

.status {
  text-align: center;
  padding-block: var(--s7);
}

.status__icon {
  display: inline-grid;
  place-items: center;
  width: 72px;
  height: 72px;
  margin-bottom: var(--s5);
  background: var(--pink-wash);
  color: var(--pink);
}

.status__icon--ok {
  background: var(--pink);
  color: var(--on-pink);
}

.status__icon--bad {
  background: var(--critical-wash);
  color: var(--critical);
}

.status__icon--wait,
.status__icon--info {
  background: var(--surface-sunk);
  color: var(--ink);
}

.status h1 {
  margin-bottom: var(--s3);
}

.status p {
  max-width: 36ch;
  margin-inline: auto;
}

.status .btn-row {
  max-width: 360px;
  margin: var(--s5) auto 0;
}

/* --- loading ------------------------------------------------------------------------ */

/* The waiting indicator: three squares, because everything here is square. */
.dots {
  display: inline-flex;
  gap: 6px;
}

.dots span {
  width: 10px;
  height: 10px;
  background: currentColor;
  animation: dot 1.2s infinite ease-in-out;
}

.dots span:nth-child(2) { animation-delay: 0.15s; }
.dots span:nth-child(3) { animation-delay: 0.3s; }

@keyframes dot {
  0%, 80%, 100% { opacity: 0.2; transform: translateY(0); }
  40% { opacity: 1; transform: translateY(-4px); }
}

/* An indeterminate progress bar: a pink block sliding along a grey line. Pair it with words
   ("confirming your payment"); on its own it says nothing. */
.progress {
  position: relative;
  height: 4px;
  overflow: hidden;
  background: var(--surface-sunk);
}

.progress::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 40%;
  background: var(--pink);
  animation: progress 1.6s var(--ease) infinite;
}

@keyframes progress {
  from { transform: translateX(-100%); }
  to { transform: translateX(250%); }
}

/* A skeleton block for secondary data only. Never for the status card. */
.skeleton {
  display: block;
  min-height: 1em;
  background: linear-gradient(90deg, var(--surface-sunk) 0%, #fafafa 50%, var(--surface-sunk) 100%) 0 0 / 200% 100%;
  animation: shimmer 1.4s linear infinite;
}

@keyframes shimmer {
  to { background-position: -200% 0; }
}

/* --- long-form text ------------------------------------------------------------------ */

.prose {
  max-width: 68ch;
}

.prose h1 {
  font-size: var(--size-h1);
  margin-bottom: var(--s5);
}

.prose h2 {
  margin-top: var(--s7);
  font-size: var(--size-h2);
}

.prose h3 {
  margin-top: var(--s5);
}

.prose a {
  color: var(--pink-deep);
}

.prose blockquote {
  margin: var(--s5) 0;
  padding: var(--s4);
  border-left: 4px solid var(--pink);
  background: var(--surface);
}

.prose ul,
.prose ol {
  padding-left: var(--s5);
}

.prose li {
  margin-bottom: var(--s2);
}

.prose table {
  display: block;
  width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  margin-block: var(--s4);
  font-size: var(--size-small);
}

.prose th,
.prose td {
  padding: var(--s2) var(--s3);
  border: 1px solid var(--line-strong);
  text-align: left;
  vertical-align: top;
}

/* --- bigger screens ------------------------------------------------------------------- */

@media (min-width: 400px) {
  .header__label {
    display: inline;
  }
}

@media (min-width: 600px) {
  :root {
    --gutter: 24px;
  }

  .tiles {
    grid-template-columns: repeat(3, 1fr);
  }

  .btn-row {
    grid-auto-flow: column;
    justify-content: start;
  }

  .status .btn-row {
    justify-content: center;
  }

  .card,
  .pass {
    padding-inline: var(--s5);
  }

  .footer__grid {
    grid-template-columns: repeat(3, 1fr);
  }

  .section-head {
    grid-template-columns: 1fr auto;
    align-items: end;
  }

  .stepper__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2) var(--s5);
    margin-top: var(--s2);
  }

  .stepper__count {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
  }
}

@media (min-width: 900px) {
  :root {
    --gutter: 40px;
    --size-display: 4rem;
    --size-h1: 2.75rem;
    --size-h2: 2rem;
    --size-h3: 1.25rem;
    --header-h: 72px;
  }

  .brand__logo {
    height: 36px;
    width: calc(36px * 1069 / 256);
  }

  .page {
    padding-top: var(--s6);
  }

  .section {
    padding-block: var(--s8);
  }

  .split {
    grid-template-columns: 1fr 1.4fr;
    gap: var(--s7);
    align-items: start;
  }

  .tiles {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--s4);
  }

  .rail {
    grid-auto-flow: row;
    grid-template-columns: repeat(4, 1fr);
    grid-auto-columns: auto;
    gap: var(--s4);
    margin-inline: 0;
    padding: 0;
    overflow: visible;
  }

  .pass {
    padding: var(--s6);
  }

  .pass__headline {
    font-size: 2rem;
  }

  /* The action bar returns to the page flow on a desk: there is no thumb zone to serve. A
     `--phone-only` bar goes, and its inline `.desk-only` twin shows instead. Either way it no
     longer covers the footer. */
  body:has(.actionbar) {
    --actionbar-room: 0px;
  }

  body:has(.actionbar--always) {
    --actionbar-room: var(--actionbar-h);
  }

  body:has(.actionbar--always.actionbar--stacked) {
    --actionbar-room: var(--actionbar-h-stacked);
  }

  .actionbar--phone-only {
    display: none;
  }

  .desk-only {
    display: block;
  }

  .actionbar--phone {
    position: static;
    padding: 0;
    background: none;
    border: 0;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .actionbar--phone .actionbar__inner {
    max-width: none;
  }

  .actionbar--phone .actionbar__actions {
    flex: 0 1 auto;
  }

}

/* --- print ------------------------------------------------------------------------------ */

@page {
  margin: 16mm 14mm;
}

@media print {
  /* A printed receipt is a document, not a web page. */
  .no-print,
  .announce,
  .header,
  .ecosystem,
  .footer,
  .actionbar,
  .skip-link {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
    font-size: 11pt;
  }

  .page {
    padding: 0;
  }

  .card,
  .pass {
    border: 1px solid #000;
    background: #fff;
    color: #000;
  }

  a {
    text-decoration: none;
  }
}

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

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

  /* Still, not blank: the progress bar shows as a full quiet line, the skeleton as flat grey. */
  .progress::after {
    width: 100%;
    transform: none;
    opacity: 0.35;
  }

  .skeleton {
    background: var(--surface-sunk);
  }
}
