/* Jason Bakes
   One stylesheet for the whole site. Tokens and every measured ratio come from
   DESIGN.md; nothing here invents a colour. Two registers share this file: the
   home page is brand (ink and kraft bands, display type, photography), the shop
   and cart are product (calm surfaces, one accent, standard controls).

   Corners are square everywhere. The identity is a stamped disc on a box, and a
   rounded corner is the first thing that makes it look like a template. */

/* ---------------------------------------------------------------- font ---- */
/* Archivo Black is the face already cut into the seal, so using it for headings
   introduces no new typeface to the brand. Subset to latin and self-hosted:
   9.7 KB, same origin, no third-party request and no font host to trust. */
@font-face {
  font-family: "Archivo Black";
  src: url("/font/archivo-black-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* -------------------------------------------------------------- tokens ---- */
:root {
  color-scheme: light dark;

  --ground: #E8E8E6;
  --surface: #FFFFFF;
  --sunk: #DEDEDB;   /* hover and disabled only: an --edge border on it is 2.95:1 */
  --ink: #1A1714;
  --muted: #5A5248;
  --line: #D3D1CC;
  --edge: #847F77;
  --accent: #8A5A0B;
  --accent-hover: #6D4708;
  --accent-fill: #F2C14E;
  --kraft: #C58B4E;
  --danger: #8F1D14;
  --success: #2F6B3A;

  /* Text sitting on an ink band. Named so a band never has to borrow a
     dark-theme token and hope. Measured on #1A1714: 14.76:1 and 6.42:1. */
  --on-ink: #EDE9E3;
  --on-ink-muted: #A39A8C;

  /* Fixed on purpose: the primary button is ink on butter in both themes, the
     same two inks as the seal. It should not invert with the page. */
  --btn-face: #F2C14E;
  --btn-text: #1A1714;

  --display: "Archivo Black", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --body: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;

  --step-0: 1.0625rem;
  --step-1: 1.1875rem;
  --step-2: clamp(1.35rem, 1.1rem + 0.9vw, 1.6rem);
  --step-3: clamp(1.75rem, 1.35rem + 1.6vw, 2.3rem);
  --step-4: clamp(2.3rem, 1.6rem + 2.8vw, 3.4rem);

  --gap-1: 0.5rem;
  --gap-2: 0.85rem;
  --gap-3: 1.35rem;
  --gap-4: 2rem;
  --gap-5: 3rem;
  --gap-6: 4.5rem;

  --wrap: 62rem;
  --gutter: 1.5rem;

  /* The sticky masthead's height, and the offset anything anchored has to
     clear. One value, so the two cannot drift apart: they did, and #allergens
     landed 16px behind the header on a phone. */
  --masthead: 5rem;

  --z-sticky: 100;
  --z-backdrop: 200;
  --z-dialog: 300;
  --z-toast: 400;

  --ease: cubic-bezier(0.22, 1, 0.36, 1); /* ease-out-quart */
  --fast: 160ms;
  --mid: 220ms;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ground: #141210;
    --surface: #26221E;
    --sunk: #1D1A17;
    --ink: #EDE9E3;
    --muted: #A39A8C;
    --line: #322D27;
    --edge: #847F77;
    --accent: #F2C14E;
    --accent-hover: #F7D479;
    --accent-fill: #F2C14E;
    --kraft: #C58B4E;
    --danger: #F08A80;
    --success: #7FC98C;
  }
}

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

/* A class that sets display beats the user agent's [hidden] rule, so anything
   toggled from script would stay on screen. This is not optional anywhere the
   page hides a panel by setting .hidden. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

/* The masthead is sticky, so anything reached by a #fragment has to stop clear
   of it. Without this, /shop/#cookies parked the heading behind the header and
   the section looked like it had lost its title. */
:target, [id] { scroll-margin-top: var(--masthead); }

body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--step-0);
  line-height: 1.55;
  text-wrap: pretty;
}

img { max-width: 100%; }
h1, h2, h3, h4 { text-wrap: balance; }

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

.skip {
  position: absolute;
  left: var(--gutter);
  top: -4rem;
  z-index: var(--z-toast);
  background: var(--btn-face);
  color: var(--btn-text);
  padding: 0.75rem 1rem;
  font-weight: 700;
  transition: top var(--fast) var(--ease);
}
.skip:focus { top: 0.75rem; }

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

/* ---------------------------------------------------------- typography ---- */
.display { font-family: var(--display); font-weight: 400; letter-spacing: -0.025em; line-height: 1.04; }
.t-4 { font-size: var(--step-4); }
.t-3 { font-size: var(--step-3); }
.t-2 { font-size: var(--step-2); }
.t-1 { font-size: var(--step-1); }

p { margin: 0; max-width: 68ch; }
.lede { font-size: var(--step-2); line-height: 1.4; max-width: 34ch; }
.muted { color: var(--muted); }

a { color: var(--accent); text-underline-offset: 3px; }
a:hover { color: var(--accent-hover); text-decoration-thickness: 2px; }

/* -------------------------------------------------------------- layout ---- */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.stack { display: flex; flex-direction: column; }
.stack-1 { gap: var(--gap-1); } .stack-2 { gap: var(--gap-2); }
.stack-3 { gap: var(--gap-3); } .stack-4 { gap: var(--gap-4); }
.stack-5 { gap: var(--gap-5); } .stack-6 { gap: var(--gap-6); }

.row { display: flex; align-items: center; gap: var(--gap-3); }
.row-wrap { flex-wrap: wrap; }

/* Full-bleed colour bands. These carry the brand moments; the transactional
   pages stay on the plain ground. */
.band { padding-block: var(--gap-6); }
.band-ink { background: #1A1714; color: var(--on-ink); border-bottom: 1px solid transparent; }
.band-ink .muted { color: var(--on-ink-muted); }
/* The ink band and the dark page ground measure 1.05:1, so in dark mode the
   hero stopped reading as a band at all. A background cannot clear 3:1 against
   near-black and still be ink, so the boundary separates it instead. This rule
   has to come after the .band-ink shorthand above: a media query adds no
   specificity, and an earlier declaration lost to the later border-bottom. */
@media (prefers-color-scheme: dark) {
  .band-ink { border-bottom-color: var(--edge); }
}
.band-kraft { background: var(--kraft); color: #1A1714; }
/* --muted measures 2.62:1 on kraft. Never use it here; kraft carries ink only. */
.band-kraft .muted { color: #423528; }

main { flex: 1 0 auto; }

/* -------------------------------------------------------------- header ---- */
.masthead {
  border-bottom: 1px solid var(--edge);
  background: var(--ground);
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
}

/* An explicit grid with a named breakpoint, not flex-wrap. DESIGN.md records
   why: the wrapping header dropped its second column on content a few pixels
   wider than the test copy, and failed silently. */
.masthead-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--gap-2);
  padding-block: 0.75rem;
  align-items: center;
}
.masthead-seal { display: flex; align-items: center; gap: 0.75rem; color: var(--ink); }
.masthead-seal svg { display: block; width: 44px; height: 44px; }
.masthead-name { font-family: var(--display); font-size: 1.05rem; letter-spacing: -0.02em; }
.masthead a { color: inherit; text-decoration: none; }

.nav { display: grid; grid-template-columns: repeat(4, auto); justify-content: start; align-items: center; gap: 0.35rem; }
.nav a {
  display: flex; align-items: center; min-height: 44px;
  padding-inline: 0.55rem;
  font-size: 1rem;
  border-bottom: 3px solid transparent;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.nav a:hover { background: var(--sunk); }
.nav a[aria-current="page"] { border-bottom-color: var(--accent-fill); font-weight: 700; }

@media (min-width: 26rem) { .nav a { padding-inline: 0.7rem; } }

/* The four nav items have to fit one row at 320px, because a second row makes
   the sticky header taller than the scroll offset below assumes. */
@media (max-width: 25.99rem) { .nav a { padding-inline: 0.35rem; font-size: 0.94rem; } }

@media (min-width: 46rem) {
  .masthead-grid { grid-template-columns: auto 1fr; }
  .nav { justify-content: flex-end; }
}

/* One coloured thing in the header, and it is the way to buy. Every reference
   the client sent does this: Three Bites' nav is entirely monochrome except a
   single rust ORDER button. "Shop" sitting as plain text among four equal links
   made the order path the least visible thing in the bar. */
.nav a.nav-buy {
  background: var(--btn-face);
  color: var(--btn-text);
  font-weight: 700;
  border-bottom-color: transparent;
}
.nav a.nav-buy:hover { background: #E4B23C; }
.nav a.nav-buy[aria-current="page"] { border-bottom-color: var(--ink); }

.cart-link { position: relative; }
.cart-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.5rem; height: 1.5rem; margin-left: 0.45rem; padding-inline: 0.35rem;
  background: var(--ink); color: var(--ground);
  font-size: 0.8rem; font-weight: 700; font-variant-numeric: tabular-nums;
}
.cart-count[data-empty="true"] { background: var(--line); color: var(--muted); }

/* ------------------------------------------------------------- buttons ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  min-height: 48px; padding: 0 1.25rem;
  border: 2px solid transparent;
  font: inherit; font-weight: 700;
  text-decoration: none; cursor: pointer;
  transition: transform var(--fast) var(--ease), background var(--fast) var(--ease),
              border-color var(--fast) var(--ease), color var(--fast) var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn-primary { background: var(--btn-face); color: var(--btn-text); border-color: var(--btn-face); }
.btn-primary:hover { background: #E4B23C; border-color: #E4B23C; }

.btn-secondary { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn-secondary:hover { background: var(--ink); color: var(--ground); }

.btn-quiet { background: transparent; color: var(--ink); border-color: var(--edge); min-height: 44px; }
.btn-quiet:hover { border-color: var(--ink); }

.band-ink .btn-secondary { color: var(--on-ink); border-color: var(--on-ink); }
.band-ink .btn-secondary:hover { background: var(--on-ink); color: #1A1714; }

/* Disabled is not invisible. The old pairing measured 1.35:1, which is a
   button nobody can read the label of, on the one control that tells a shopper
   why they cannot pay yet. */
.btn[disabled], .btn[aria-disabled="true"] {
  cursor: not-allowed;
  background: var(--sunk); color: var(--muted); border-color: var(--edge);
  transform: none;
}
.btn-block { width: 100%; }

/* --------------------------------------------------------------- forms ---- */
.field { display: flex; flex-direction: column; gap: 0.35rem; }
.field > label, .legend { font-weight: 700; font-size: 0.95rem; }
.hint { font-size: 0.9rem; color: var(--muted); }

input[type="text"], input[type="email"], input[type="tel"], input[type="date"],
input[type="number"], select, textarea {
  font: inherit;
  min-height: 48px;
  padding: 0.6rem 0.75rem;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--edge);
  border-radius: 0;
  width: 100%;
}
textarea { min-height: 7rem; resize: vertical; line-height: 1.5; }
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }
input:focus-visible, select:focus-visible, textarea:focus-visible { border-color: var(--accent); }

/* An error message under a field is not enough on its own: the field itself has
   to look wrong, and colour cannot be the only thing saying so. The border
   doubles in weight as well as changing hue. */
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-width: 2px;
  border-color: var(--danger);
}

fieldset { margin: 0; padding: 0; border: 0; }
.legend { display: block; margin-bottom: 0.5rem; }

/* Fulfilment and quantity choices read as real options, not as cards with a
   decorative stripe. The selected one is filled, not merely outlined. */
.choice-set { display: grid; grid-template-columns: 1fr; gap: 0.6rem; }
@media (min-width: 34rem) { .choice-set { grid-template-columns: 1fr 1fr; } }

.choice { position: relative; display: block; }
.choice input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.choice-body {
  display: flex; flex-direction: column; gap: 0.15rem;
  min-height: 48px; padding: 0.7rem 0.85rem;
  background: var(--surface); border: 1px solid var(--edge);
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.choice-title { font-weight: 700; }
.choice-note { font-size: 0.9rem; color: var(--muted); }
.choice input:hover + .choice-body { border-color: var(--ink); }
.choice input:focus-visible + .choice-body { outline: 3px solid var(--accent); outline-offset: 3px; }
.choice input:checked + .choice-body { background: var(--ink); border-color: var(--ink); color: var(--ground); }

/* A shape that is sold out stays on the page so the reader can see it exists.
   It reads as unavailable through weight and a line through the name, never
   through colour alone. */
.choice input:disabled + .choice-body { background: var(--sunk); border-color: var(--line); cursor: not-allowed; }
.choice input:disabled + .choice-body .choice-title { text-decoration: line-through; text-decoration-thickness: 1px; color: var(--muted); }
.choice input:disabled { cursor: not-allowed; }

/* A line in the basket that has gone since it went in. */
.line-gone .line-top { color: var(--muted); }
.line-gone img { filter: grayscale(1); opacity: 0.55; }
.choice input:checked + .choice-body .choice-note { color: var(--ground); opacity: 0.78; }

.qty { display: inline-flex; align-items: stretch; border: 1px solid var(--edge); background: var(--surface); }
.qty button {
  width: 44px; min-height: 44px; border: 0; background: transparent; color: var(--ink);
  font: inherit; font-size: 1.25rem; line-height: 1; cursor: pointer;
}
.qty button:hover { background: var(--sunk); }
.qty input {
  width: 3.25rem; min-height: 44px; text-align: center; border: 0;
  border-inline: 1px solid var(--line); font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- notices ---- */
/* Full borders and a leading rule, never a coloured side stripe. */
.notice {
  display: flex; flex-direction: column; gap: 0.35rem;
  padding: 0.9rem 1rem;
  background: var(--surface);
  border: 1px solid var(--edge);
  border-top: 3px solid var(--ink);
}
.notice-title { font-weight: 700; }

/* Inside a panel the surface and the border are already there. A second
   bordered box within the first is a nested card, so the notice keeps only its
   rule and its words. */
.panel > .notice { background: transparent; border-inline: 0; border-bottom: 0; padding-inline: 0; }
.notice-info { border-top-color: var(--accent); }
.notice-good { border-top-color: var(--success); }
.notice-bad  { border-top-color: var(--danger); }
.notice-bad .notice-title { color: var(--danger); }
.notice-good .notice-title { color: var(--success); }

.error-text { color: var(--danger); font-size: 0.9rem; font-weight: 700; }

/* ------------------------------------------------------------- product ---- */
/* --line measures 1.24:1 on the light ground, which is a seam between panels
   and not a boundary around one. A panel the customer has to find gets --edge
   at 3.24:1; a subordinate panel inside a section gets the seam. */
.panel { background: var(--surface); border: 1px solid var(--edge); padding: var(--gap-3); }

.product { display: grid; grid-template-columns: 1fr; gap: var(--gap-3); align-items: start; }
@media (min-width: 48rem) {
  .product { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: var(--gap-4); }
  .product-alt { grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); }
  .product-alt .product-media { order: 2; }
}
.product-media { display: grid; gap: 0.6rem; position: relative; }

/* A badge on the photograph, the way Milk Bar flags a best seller. Square, ink
   or butter, because the identity is a stamp and a rounded pill is the first
   thing that makes it look like somebody else's template. */
.badge {
  position: absolute;
  top: 0.6rem; left: 0.6rem;
  z-index: 1;
  padding: 0.25rem 0.55rem;
  background: #1A1714;
  color: var(--on-ink);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.badge-low { background: var(--btn-face); color: var(--btn-text); }
.card { position: relative; }
.product-media img { display: block; width: 100%; height: auto; border: 1px solid var(--edge); background: var(--surface); }
.product-media-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }

/* The compact presentation, for a category with many comparable small items.
   Cards are the lazy answer to most problems and the right answer to this one:
   six cookie varieties as full-width feature blocks is a page nobody scrolls.
   Breakpoint-free, so a new product never needs a media query. */
.product-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: var(--gap-3); }
.card { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--edge); }
.card img { display: block; width: 100%; height: 12.5rem; object-fit: cover; border-bottom: 1px solid var(--edge); }
.card-body { flex: 1; padding: var(--gap-3); }
.card-body > :last-child { margin-top: auto; }
.card .btn { align-self: flex-start; }
.card form .btn { align-self: flex-end; }

.price { font-family: var(--display); font-size: var(--step-2); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.price-unit { font-family: var(--body); font-size: 0.95rem; font-weight: 400; color: var(--muted); }

.rule-ink { border: 0; border-top: 2px solid var(--ink); margin: 0; }
.rule-line { border: 0; border-top: 1px solid var(--line); margin: 0; }
.bar { height: 4px; width: 8rem; background: var(--accent-fill); }

/* ---------------------------------------------------------------- cart ---- */
.lines { display: flex; flex-direction: column; gap: 0; margin: 0; padding: 0; list-style: none; }
.line {
  display: grid; grid-template-columns: 72px 1fr; gap: var(--gap-2) var(--gap-3);
  padding-block: var(--gap-3); border-top: 1px solid var(--line);
}
.line img { width: 72px; height: 72px; object-fit: cover; border: 1px solid var(--edge); display: block; }
.line-body { display: flex; flex-direction: column; gap: 0.5rem; }
.line-top { display: flex; justify-content: space-between; gap: var(--gap-2); align-items: baseline; }
.line-controls { display: flex; align-items: center; gap: var(--gap-2); flex-wrap: wrap; }
/* The undo offer sits in the flow, under the lines it belongs to, rather than
   floating over the page where it would cover the basket it is talking about. */
.undo { flex-direction: row; align-items: center; justify-content: space-between; gap: var(--gap-2); flex-wrap: wrap; }

.line-remove { background: none; border: 0; padding: 0.5rem 0; min-height: 44px; font: inherit; color: var(--accent); cursor: pointer; text-decoration: underline; text-underline-offset: 3px; }

.totals { display: flex; flex-direction: column; gap: 0.5rem; }
.total-row { display: flex; justify-content: space-between; gap: var(--gap-2); font-variant-numeric: tabular-nums; }
.total-row-grand { font-family: var(--display); font-size: var(--step-2); letter-spacing: -0.02em; padding-top: 0.6rem; border-top: 2px solid var(--ink); }

.checkout-grid { display: grid; grid-template-columns: 1fr; gap: var(--gap-4); align-items: start; }
@media (min-width: 52rem) {
  .checkout-grid { grid-template-columns: minmax(0, 1fr) 21rem; gap: var(--gap-5); }
  .checkout-aside { position: sticky; top: 5.5rem; }
}

/* -------------------------------------------------------------- footer ---- */
.site-footer { border-top: 1px solid var(--line); padding-block: var(--gap-5); color: var(--muted); font-size: 0.95rem; }
.footer-grid { display: grid; grid-template-columns: 1fr; gap: var(--gap-3); }
@media (min-width: 40rem) { .footer-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gap-4); } }
.footer-grid h2 { font-size: 0.95rem; margin: 0 0 0.35rem; color: var(--ink); }
.footer-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.footer-list a { display: inline-flex; min-height: 44px; align-items: center; }

/* ------------------------------------------------------ sample pricing ---- */
/* Removed the day real prices land. See js/catalogue.js. */
.sample-banner {
  background: var(--accent-fill); color: #1A1714;
  border-bottom: 2px solid var(--ink);
  font-size: 0.95rem;
}
.sample-banner .wrap { padding-block: 0.6rem; }
.sample-banner strong { font-weight: 700; }

/* ---------------------------------------------------------------- slip ---- */
/* The order slip. This is the one place on the site that gets a moment, and it
   is the moment where a customer most wants evidence: they have just handed
   over money and left the payment provider's page.

   Every value on it is read back from the provider server side. The slip is
   hidden until that read lands, so it can never print a shape with nothing in
   it or a total the address bar merely claimed. */
.slip {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--edge);
  padding: var(--gap-3);
  max-width: 26rem;
  font-variant-numeric: tabular-nums;
}

.slip-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--gap-2);
  padding-bottom: 0.6rem;
  border-bottom: 2px solid var(--ink);
}
.slip-mark { display: flex; align-items: center; gap: 0.45rem; font-family: var(--display); font-size: 1.05rem; letter-spacing: -0.02em; color: var(--ink); }
.slip-ref { font-size: 0.85rem; color: var(--muted); }

.slip-lines { list-style: none; margin: 0; padding: 0; }
.slip-line {
  display: grid;
  grid-template-columns: 2.2rem 1fr auto;
  gap: 0 0.75rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--line);
}
.slip-qty { color: var(--muted); }

.slip { display: grid; }
.slip-meta { margin: 0.9rem 0 0; }
.slip-row { display: flex; justify-content: space-between; gap: var(--gap-2); }
.slip-row dt, .slip-row dd { margin: 0; }
.slip-total {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 2px solid var(--ink);
  font-family: var(--display);
  font-size: var(--step-2);
  letter-spacing: -0.02em;
}

/* A rubber stamp, drawn the way one prints: a ruled box, off square, ink only.
   It carries the date so the slip is evidence rather than decoration.

   It sits in the flow at the foot rather than over the total. Absolutely
   positioned it landed on the amount and was clipped by the slip's own edge,
   which is what a stamp does on paper and not what it should do here. */
.slip-stamp {
  justify-self: end;
  margin: var(--gap-3) 0.6rem 0.3rem auto;
  display: grid;
  justify-items: center;
  gap: 0.1rem;
  padding: 0.35rem 0.9rem;
  border: 3px solid var(--ink);
  color: var(--ink);
  transform: rotate(-7deg);
  opacity: 0.9;
}
.slip-stamp-word { font-family: var(--display); font-size: 1.5rem; letter-spacing: 0.06em; line-height: 1; }
.slip-stamp-date { font-size: 0.72rem; letter-spacing: 0.08em; }

/* Printing. Rows arrive in order, then the stamp lands. Every one of these is a
   CSS animation rather than a class-triggered transition, so it still runs in a
   headless renderer and the slip is never left blank. The global
   prefers-reduced-motion rule collapses all of it to an instant. */
@keyframes slip-feed { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@keyframes slip-press { from { opacity: 0; transform: rotate(-7deg) scale(1.9); } to { opacity: 0.9; transform: rotate(-7deg) scale(1); } }

.slip-line, .slip-meta { animation: slip-feed 260ms var(--ease) backwards; }
.slip-line { animation-delay: calc(120ms + var(--row, 0) * 90ms); }
.slip-meta { animation-delay: calc(120ms + var(--rows, 1) * 90ms); }
.slip-stamp { animation: slip-press 320ms cubic-bezier(0.3, 1.4, 0.5, 1) backwards; animation-delay: calc(320ms + var(--rows, 1) * 90ms); }

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