/* =========================================================================
   UDDER NONSENSE — design system
   "Rubber-Hose Title Card": 1930s golden-age animation MOTION + LINEWORK,
   wearing the camp's own palette and type. See DESIGN.md.

   Sections:
     1. Fonts
     2. Tokens
     3. Reset + base
     4. 1930s motion vocabulary   <- wave 2: compose these, don't invent new ones
     5. Components
     6. Landing / title card
     7. The Herd (badge wall)
     8. Responsive
     9. prefers-reduced-motion
   ========================================================================= */

/* ============================== 1. FONTS ============================== */

@font-face {
  font-family: 'Retro Star';
  src: url('/fonts/RetroStar.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Alegreya Sans';
  src: url('/fonts/AlegreyaSans-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Alegreya Sans';
  src: url('/fonts/AlegreyaSans-400i.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'Alegreya Sans';
  src: url('/fonts/AlegreyaSans-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Alegreya Sans';
  src: url('/fonts/AlegreyaSans-700.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Alegreya Sans';
  src: url('/fonts/AlegreyaSans-800.woff2') format('woff2');
  font-weight: 800; font-style: normal; font-display: swap;
}

/* ============================== 2. TOKENS ============================= */

@property --iris-r {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}

:root {
  /* -- Palette. Brand deck slide 6. These seven are the whole product. ---- */
  --midnight:    #1A1A1A;
  --milk:        #FFFFFF;
  --udder-pink:  #F2D4D0;
  --dusty-rose:  #C9A09B;
  --cream:       #FBF6F1;
  --playa-dust:  #E8E8E8;
  --grey-water:  #888888;

  /* Derived neutral. Grey Water is only 3.5:1 on white and Dusty Rose only
     2.3:1 — neither is safe for small text on a light surface. --ink-soft is
     Midnight warmed toward Dusty Rose and lands at 5.35:1 on Cream, so
     captions can be quiet without failing AA. See DESIGN.md §4. */
  --ink-soft:    #6B6560;

  /* -- Type -------------------------------------------------------------- */
  --font-display: 'Retro Star', 'Arial Black', sans-serif;
  --font-body:    'Alegreya Sans', 'Optima', 'Avenir', sans-serif;

  /* 1.25 major third, fluid */
  --step--1: clamp(0.78rem, 0.76rem + 0.10vw, 0.83rem);
  --step-0:  clamp(1.00rem, 0.97rem + 0.14vw, 1.06rem);
  --step-1:  clamp(1.20rem, 1.14rem + 0.30vw, 1.33rem);
  --step-2:  clamp(1.45rem, 1.34rem + 0.55vw, 1.66rem);
  --step-3:  clamp(1.75rem, 1.56rem + 0.95vw, 2.07rem);
  --step-4:  clamp(2.10rem, 1.79rem + 1.55vw, 2.59rem);
  --step-5:  clamp(2.45rem, 1.95rem + 2.50vw, 3.24rem);
  --step-6:  clamp(2.90rem, 1.90rem + 5.00vw, 5.20rem);

  /* -- 8-pt spacing ------------------------------------------------------ */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px; --s-5: 24px;
  --s-6: 32px; --s-7: 40px; --s-8: 48px; --s-9: 64px; --s-10: 80px;
  --s-11: 96px; --s-12: 128px;

  /* -- Ink -------------------------------------------------------------- */
  --rule:       4px;
  --rule-hair:  2px;
  --radius-card: 26px;
  --radius-in:   16px;
  --radius-pill: 999px;

  /* -- Motion ------------------------------------------------------------ */
  /* Anticipation + overshoot. This IS rubber-hose animation — the pull-back
     before the move is deliberate. Kept to <=6% overshoot. */
  --ease-hose:      cubic-bezier(.34, -.42, .28, 1.44);
  --ease-out-quart: cubic-bezier(.25, 1, .5, 1);
  --ease-step:      steps(3, end);

  --dur-quick: 160ms;
  --dur-base:  280ms;
  --dur-flip:  560ms;
  --dur-iris:  1150ms;
}

/* ============================ 3. RESET + BASE ========================= */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font-body);
  /* Alegreya Sans ships oldstyle figures by default; digits here are codes and
     years, which must read unambiguously. */
  font-variant-numeric: lining-nums;
  font-feature-settings: 'lnum' 1;
  font-size: var(--step-0);
  line-height: 1.55;
  color: var(--midnight);
  background: var(--midnight);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  line-height: 0.94;
  letter-spacing: 0.005em;
  margin: 0;
}

p { margin: 0 0 var(--s-4); }
img, svg { max-width: 100%; display: block; }
button, input, textarea, select { font: inherit; color: inherit; }

:where(a) { color: inherit; text-underline-offset: 3px; text-decoration-thickness: 2px; }

/* Focus: Midnight carries the contrast (16:1), Udder Pink carries the brand.
   Pink alone is 1.3:1 on cream and would not be a visible focus indicator. */
:focus-visible {
  outline: 3px solid var(--midnight);
  outline-offset: 3px;
  box-shadow: 0 0 0 8px var(--udder-pink);
  border-radius: 6px;
}
.on-midnight :focus-visible,
.masthead :focus-visible {
  outline-color: var(--udder-pink);
  box-shadow: 0 0 0 8px rgba(242, 212, 208, 0.28);
}

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

/* ==================== 4. 1930s MOTION VOCABULARY ====================== *
 * Reusable primitives — LINEWORK ONLY.
 *
 * The owner used the live site and asked for every ambient animation to come
 * out: "it looks like you tried to animate stuff which appears to just cause a
 * lot of weird jitter — scroll bars appear/disappear, everything jumpy."
 *
 * So the film grain, the gate weave, the boiling ink filter, the squash-and-
 * stretch and the error shake are GONE, not softened. The 1930s look now comes
 * entirely from static ink: heavy strokes, offset shadows, the double rule, the
 * dot clusters and the Holstein print. What is left that moves is (a) opacity,
 * (b) colour, (c) hover states that do not reflow, (d) the contained badge
 * flip, (e) the iris, which is a fixed pointer-events:none overlay outside
 * document flow, and (f) the RC game, where motion IS the product.
 *
 * THE RULE FOR ANYTHING ADDED HERE LATER: no animation may change an element's
 * layout position OR its scrollable overflow. `.weave` was a ±0.6px translate
 * on `.shell` — a full-viewport box — and a transform DOES extend the
 * scrollable overflow rectangle, which is exactly why the scrollbars flickered
 * at 10 fps on every page. See DESIGN.md §5.                               */

/* -- Vignette — the lens never lit the corners evenly ------------------- *
 * Two strengths. On a Cream page a deep vignette just makes the paper look
 * grey and dirty, so the light default is subtle; the Midnight title card
 * gets the full falloff, where it reads as a projector gate.               */
.vignette {
  position: fixed;
  inset: 0;
  z-index: 79;
  pointer-events: none;
  background: radial-gradient(
    ellipse 92% 88% at 50% 44%,
    rgba(26, 26, 26, 0) 66%,
    rgba(26, 26, 26, 0.07) 86%,
    rgba(26, 26, 26, 0.20) 100%
  );
}
body.surface-dark .vignette {
  background: radial-gradient(
    ellipse 78% 76% at 50% 46%,
    rgba(26, 26, 26, 0) 48%,
    rgba(26, 26, 26, 0.22) 78%,
    rgba(26, 26, 26, 0.62) 100%
  );
}

/* -- Iris-in — the 1930s cartoon open ---------------------------------- *
 * The one page-level animation left. It is `position: fixed` with
 * `pointer-events: none`, so it is outside document flow and cannot move,
 * reflow or overflow anything; it only masks. udder.js removes it from the DOM
 * once it has opened.                                                       */
.iris {
  position: fixed;
  inset: 0;
  z-index: 95;
  pointer-events: none;
  background: var(--midnight);
  --iris-r: 0px;
  -webkit-mask: radial-gradient(circle at 50% 42%,
    transparent 0, transparent var(--iris-r), #000 calc(var(--iris-r) + 2px));
  mask: radial-gradient(circle at 50% 42%,
    transparent 0, transparent var(--iris-r), #000 calc(var(--iris-r) + 2px));
  animation: iris-in var(--dur-iris) var(--ease-out-quart) forwards;
}
@keyframes iris-in {
  from { --iris-r: 0px; }
  to   { --iris-r: 170vmax; }
}
/* Reserved for wave 2 page transitions. */
.iris[data-state='closing'] { animation: iris-out 700ms var(--ease-out-quart) forwards; }
@keyframes iris-out {
  from { --iris-r: 170vmax; }
  to   { --iris-r: 0px; }
}

/* -- The double rule. A crisp static stroke, not a boiling one. --------- *
 * This used to carry `filter: url(#boil)` — an feTurbulence +
 * feDisplacementMap with a SMIL-animated seed, which re-rasterised the frame
 * every 440ms on every page. Removed with the rest of the ambient motion; the
 * ink is drawn once and stays where it is put.                             */
.ink-rule {
  position: absolute;
  inset: 12px;
  pointer-events: none;
  border: var(--rule) solid var(--midnight);
  border-radius: var(--radius-in);
}
.ink-rule--inner {
  inset: 22px;
  border-width: var(--rule-hair);
  border-radius: 10px;
}

/* -- Entrance. Opacity ONLY: it cannot move layout, so it cannot jitter. - */
@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.enter {
  opacity: 0;
  animation: fade-in 420ms var(--ease-out-quart) forwards;
  animation-delay: var(--delay, 0ms);
}

/* ============================ 5. COMPONENTS =========================== */

/* -- Layout ------------------------------------------------------------- */
.shell { position: relative; z-index: 1; min-height: 100dvh; }
.wrap  { width: min(100% - var(--s-6), 76rem); margin-inline: auto; }
.wrap--narrow { width: min(100% - var(--s-6), 46rem); }

/* -- Eyebrow / micro-label ---------------------------------------------- */
.eyebrow {
  font-family: var(--font-body);
  font-weight: 800;
  font-size: var(--step--1);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  margin: 0 0 var(--s-4);
  color: var(--midnight);
}
.on-midnight .eyebrow { color: var(--udder-pink); }

/* -- Star ornament (brand deck slide 10) -------------------------------- */
.star { display: inline-block; width: 0.85em; height: 0.85em; vertical-align: -0.06em; }
.rule-star .star { width: 22px; height: 22px; }

/* -- Rule with a star in it --------------------------------------------- */
.rule-star {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: var(--s-5) 0;
  color: var(--midnight);
}
.rule-star::before,
.rule-star::after {
  content: '';
  flex: 1;
  height: var(--rule-hair);
  background: currentColor;
  opacity: 0.85;
}
.on-midnight .rule-star { color: var(--dusty-rose); }

/* -- Buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 52px;
  padding: var(--s-3) var(--s-6);
  font-family: var(--font-display);
  font-size: var(--step-1);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--cream);
  background: var(--midnight);
  border: var(--rule) solid var(--midnight);
  border-radius: var(--radius-pill);
  box-shadow: 6px 6px 0 var(--udder-pink);
  cursor: pointer;
  transition:
    transform var(--dur-base) var(--ease-hose),
    box-shadow var(--dur-base) var(--ease-hose);
}
.btn:hover {
  transform: translate3d(-2px, -2px, 0);
  box-shadow: 9px 9px 0 var(--udder-pink);
}
.btn:active {
  transform: translate3d(3px, 3px, 0) scale(0.97, 1.03);
  box-shadow: 2px 2px 0 var(--udder-pink);
  transition-duration: 90ms;
}

.btn--ghost {
  color: var(--midnight);
  background: transparent;
  box-shadow: none;
  min-height: 44px;
  font-size: var(--step-0);
  padding: var(--s-2) var(--s-4);
}
.btn--ghost:hover { background: var(--udder-pink); transform: none; box-shadow: none; }

.on-midnight .btn--ghost { color: var(--cream); border-color: var(--cream); }
.on-midnight .btn--ghost:hover { background: var(--cream); color: var(--midnight); }

/* -- Fields ------------------------------------------------------------- */
.field { margin: 0 0 var(--s-5); }
.field label {
  display: block;
  font-weight: 800;
  font-size: var(--step--1);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-bottom: var(--s-2);
}
.field .hint {
  font-size: var(--step--1);
  color: var(--ink-soft);
  margin: var(--s-2) 0 0;
}
.input {
  width: 100%;
  min-height: 56px;              /* >= 44px touch target */
  padding: var(--s-3) var(--s-4);
  font-size: 1.0625rem;          /* >= 16px so iOS never zooms on focus */
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--midnight);
  background: var(--milk);
  border: var(--rule) solid var(--midnight);
  border-radius: var(--radius-in);
  transition: box-shadow var(--dur-quick) ease;
}
.input::placeholder { color: var(--grey-water); letter-spacing: 0.12em; }
/* No hover transform here. A 1px lift under the pointer is exactly the
   "everything jumpy" the owner reported; a field is not a button. */

/* -- Plaque (a light surface on the midnight field) --------------------- */
.plaque {
  position: relative;
  background: var(--cream);
  border-radius: var(--radius-card);
  padding: var(--s-7) var(--s-6);
}

/* -- Dot cluster ornament (brand deck slides 3 / 10) -------------------- */
.dots { position: absolute; pointer-events: none; color: var(--midnight); }
.dots--tr { top: var(--s-6); right: var(--s-6); opacity: 0.9; }
.dots--bl { bottom: var(--s-6); left: var(--s-6); transform: rotate(180deg); opacity: 0.9; }

/* -- Error / notice ----------------------------------------------------- */
.moo-note {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  margin: 0 0 var(--s-5);
  background: var(--milk);
  border: var(--rule) solid var(--midnight);
  border-radius: var(--radius-in);
  box-shadow: 4px 4px 0 var(--udder-pink);
  font-weight: 600;
}
.moo-note .star { flex: none; margin-top: 0.28em; }

/* A refusal used to announce itself by SHAKING. It announces itself by
   inverting instead: same box, Midnight field, Dusty Rose shadow. Colour and
   words only — nothing moves. (No red: the palette has seven values and none of
   them is one. Inversion is how this brand raises its voice.) */
.moo-note--bad {
  color: var(--cream);
  background: var(--midnight);
  box-shadow: 4px 4px 0 var(--dusty-rose);
}
.moo-note--bad .star { color: var(--udder-pink); }

/* ======================= 6. LANDING / TITLE CARD ====================== */

.title-stage {
  display: grid;
  place-items: center;
  min-height: 100dvh;
  padding: var(--s-8) var(--s-4) var(--s-7);
}

/* Holstein gutters — the cow print stays black-on-white, per the law. */
.gutter {
  position: absolute;
  top: 0;
  width: 74px;
  height: 100%;
  z-index: 0;
  opacity: 0.92;
}
.gutter--l { left: 0; }
.gutter--r { right: 0; }
@media (max-width: 1180px) { .gutter { display: none; } }

.title-card {
  position: relative;
  width: min(100%, 40rem);
  padding: clamp(var(--s-8), 5.5vw, var(--s-11)) clamp(var(--s-7), 4.5vw, var(--s-10));
  background: var(--cream);
  border-radius: var(--radius-card);
  text-align: center;
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, 0.85);
}

.wordmark {
  font-family: var(--font-display);
  font-size: var(--step-6);
  line-height: 0.86;
  text-transform: uppercase;
  margin: var(--s-4) 0 var(--s-5);
  color: var(--midnight);
  /* Pink outline exactly as the deck sets the wordmark (slides 22, 25). */
  text-shadow:
     5px  0   0 var(--udder-pink), -5px  0   0 var(--udder-pink),
     0    5px 0 var(--udder-pink),  0   -5px 0 var(--udder-pink),
     4px  4px 0 var(--udder-pink), -4px -4px 0 var(--udder-pink),
     4px -4px 0 var(--udder-pink), -4px  4px 0 var(--udder-pink),
     3px  5px 0 var(--udder-pink), -3px -5px 0 var(--udder-pink);
}
.wordmark span { display: block; }
.wordmark span:last-child { margin-top: -0.06em; }

.herd-pill {
  display: inline-block;
  padding: var(--s-2) var(--s-5) calc(var(--s-2) + 2px);
  font-family: var(--font-display);
  font-size: var(--step-2);
  text-transform: uppercase;
  color: var(--milk);
  background: var(--midnight);
  border-radius: var(--radius-pill);
}

.code-form { margin-top: var(--s-6); text-align: left; }
.code-form .btn { width: 100%; }

/* -- THE BOX OFFICE: the front door's two windows ----------------------- *
 * A members' window and a ticket window, open at the same time, divided by a
 * hairline rule with the word set into it (the same device as `.rule-star`).
 * Neither is behind a tab or a disclosure: the owner could not find the way in,
 * so hiding either one would only move the problem.
 *
 * Stacked under 60rem, side by side above it — which is also what gets the
 * whole card above the fold on a 1440x900 laptop.                          */
.wrap--door { width: min(100% - var(--s-6), 64rem); }
.title-card--door {
  width: min(100%, 62rem);
  /* Shorter than the portrait title card: this one has two forms in it. */
  padding: clamp(var(--s-7), 3.4vw, var(--s-9)) clamp(var(--s-6), 3.6vw, var(--s-9));
}
.marquee { margin-bottom: var(--s-5); }
.marquee .rule-star { margin-bottom: 0; }

/* The portrait title card has empty corners for the deck's dot clusters. This
   one does not: the left window's button reaches the bottom-left corner, so the
   cluster there is tucked down and quietened rather than left to collide with
   it. The top-right one keeps its full weight. */
.title-card--door .dots--bl {
  width: 52px;
  height: 52px;
  bottom: var(--s-2);
  left: var(--s-2);
  opacity: 0.5;
}

.doors { display: grid; gap: var(--s-2); }
.door { text-align: left; }
.door .field:last-of-type { margin-bottom: var(--s-5); }

.door-head {
  margin: 0 0 var(--s-4);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--step-2);
  text-transform: uppercase;
  line-height: 1;
  color: var(--midnight);
}
.door-split {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: var(--s-4) 0;
  color: var(--dusty-rose);
}
.door-split::before,
.door-split::after {
  content: '';
  flex: 1;
  background: currentColor;
  height: var(--rule-hair);
}
.door-split span {
  font-weight: 800;
  font-size: var(--step--1);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Two windows. The divider turns on its side with them. */
@media (min-width: 60rem) {
  .doors {
    grid-template-columns: 1fr auto 1fr;
    gap: var(--s-6);
    align-items: start;
  }
  .door-split {
    flex-direction: column;
    align-self: stretch;
    margin: 0;
    gap: var(--s-4);
  }
  .door-split::before,
  .door-split::after {
    height: auto;
    width: var(--rule-hair);
  }
}

.btn--wide { width: 100%; }
/* The second window is the quieter one, but it is still a door: it keeps the
   full-weight rule and the display face, and gives up only the pink shadow. */
.door .btn--ghost {
  min-height: 52px;
  font-family: var(--font-display);
  font-size: var(--step-1);
  border: var(--rule) solid var(--midnight);
}

/* A name, an email and a password are prose, not a code: the invite-slot's
   uppercase + wide tracking would make every one of them unreadable. */
.input--plain {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 500;
}
.input--plain::placeholder { letter-spacing: 0; text-transform: none; }

.card-foot {
  margin: var(--s-6) 0 0;
  font-size: var(--step--1);
  color: var(--ink-soft);
  text-align: center;
  text-wrap: pretty;
}
.card-foot strong { color: var(--midnight); }

.stage-foot {
  position: relative;
  z-index: 1;
  margin-top: var(--s-6);
  text-align: center;
  font-size: var(--step--1);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dusty-rose);
}

/* ====================== 7. THE HERD (BADGE WALL) ====================== */

.masthead {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--midnight);
  color: var(--cream);
  padding:
    calc(var(--s-3) + env(safe-area-inset-top)) 0 var(--s-3);
}
.masthead .wrap {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  flex-wrap: wrap;
}
.masthead-mark {
  font-family: var(--font-display);
  font-size: var(--step-1);
  text-transform: uppercase;
  color: var(--milk);
  text-decoration: none;
  line-height: 1;
}
.masthead-nav { display: flex; gap: var(--s-2); margin-left: auto; align-items: center; }
.navlink {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--s-2) var(--s-4);
  font-weight: 800;
  font-size: var(--step--1);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dusty-rose);
  text-decoration: none;
  border-radius: var(--radius-pill);
  transition: color var(--dur-quick) ease, background var(--dur-quick) ease;
}
.navlink:hover { color: var(--midnight); background: var(--udder-pink); }
.navlink[aria-current='page'] { color: var(--midnight); background: var(--udder-pink); }
.navlink[aria-disabled='true'] { color: var(--grey-water); pointer-events: none; }

/* The decorative Holstein rail under the masthead. */
.rail { height: 22px; width: 100%; display: block; }

.herd { background: var(--cream); padding-bottom: var(--s-11); }

.herd-head { padding: var(--s-9) 0 var(--s-6); }
.herd-title {
  font-size: var(--step-5);
  color: var(--midnight);
  text-shadow:
     2px  0 0 var(--udder-pink), -2px  0 0 var(--udder-pink),
     0  2px 0 var(--udder-pink),  0  -2px 0 var(--udder-pink);
}
.herd-sub { margin: var(--s-4) 0 0; color: var(--ink-soft); max-width: 42ch; }

.year-tabs { display: flex; gap: var(--s-2); align-items: center; margin: var(--s-5) 0 0; flex-wrap: wrap; }
.year-tab {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--s-2) var(--s-5);
  font-family: var(--font-display);
  font-size: var(--step-0);
  border: var(--rule-hair) solid var(--midnight);
  border-radius: var(--radius-pill);
  background: transparent;
  text-decoration: none;
}
.year-tab[aria-current='true'] { background: var(--midnight); color: var(--cream); }
.year-tab--ghost { border-style: dashed; color: var(--ink-soft); }

/* -- My stall: a Midnight plaque, so it never reads as another badge ---- */
.stall {
  position: relative;
  background: var(--midnight);
  color: var(--cream);
  border-radius: var(--radius-card);
  padding: var(--s-6);
  margin: var(--s-6) 0 var(--s-9);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-6);
  align-items: center;
}
.stall-avatar { width: 96px; height: 96px; flex: none; }
img.stall-avatar {
  border-radius: 50%;
  object-fit: cover;
  border: 5px solid var(--cream);
  background: var(--milk);
}
.stall h2 { font-size: var(--step-2); color: var(--milk); margin-bottom: var(--s-2); }
.stall p { color: var(--dusty-rose); font-size: var(--step--1); margin-bottom: var(--s-4); }
.stall code {
  font-family: var(--font-body);
  font-weight: 800;
  letter-spacing: 0.14em;
  color: var(--udder-pink);
}
.upload-row { display: flex; gap: var(--s-3); flex-wrap: wrap; align-items: center; }
.file-btn {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--s-2) var(--s-4);
  border: var(--rule-hair) dashed var(--cream);
  border-radius: var(--radius-in);
  cursor: pointer;
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.file-btn:hover { background: rgba(251, 246, 241, 0.12); }
.file-btn input { position: absolute; opacity: 0; width: 1px; height: 1px; }

/* -- The wall ----------------------------------------------------------- */
.wall {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--s-8) var(--s-6);
  list-style: none;
  padding: 0;
  margin: 0;
}

.badge { perspective: 1400px; transform: rotate(var(--tilt, 0deg)); }

/* The card body is a LINK to the member's profile. The corner control is a
   separate button that flips in place — see the note in src/ui/herd.ts. */
.badge { position: relative; }

.badge-link {
  display: block;
  width: 100%;
  text-align: inherit;
  text-decoration: none;
  color: inherit;
  border-radius: var(--radius-card);
}
/* The global focus ring rounds to 6px, which squares off a card's corners.
   Follow the card face instead so the keyboard ring traces the badge. */
.badge-link:focus-visible { border-radius: var(--radius-in); }
/* The flip is the ONE piece of transform motion kept on the wall: it is
   contained inside a card whose box never changes size, so it cannot move a
   neighbour. The squash-and-stretch that used to fire on hover as well is gone
   — two animations on one gesture is what read as jumpy. */
.badge-flip {
  position: relative;
  transform-style: preserve-3d;
  transition: transform var(--dur-flip) var(--ease-out-quart);
  aspect-ratio: 4 / 5;
}
/* Hovering the CARD no longer flips it — the card's job is now "go to the
   profile", and flipping under an aiming cursor read as a bug. Hover on the
   corner control peeks the back instead, which is what that control means.
   `:has()` rather than a sibling selector so the link can stay first in the
   DOM and therefore first in the tab order. */
@media (hover: hover) and (pointer: fine) {
  .badge:has(.flip-btn:hover) .badge-flip { transform: rotateY(180deg); }
}
.badge[data-flipped='true'] .badge-flip { transform: rotateY(180deg); }

/* Hover on the card body grows the pink offset instead. box-shadow is ink
   only: it never contributes to layout OR to scrollable overflow, so it cannot
   do what the deleted `.weave` transform did. See DESIGN.md §9d. */
@media (hover: hover) and (pointer: fine) {
  .badge-link:hover .badge-face { box-shadow: 9px 9px 0 var(--udder-pink); }
}

.badge-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--s-5) var(--s-4);
  background: var(--milk);
  border: var(--rule) solid var(--midnight);
  border-radius: var(--radius-in);
  overflow: hidden;
  box-shadow: 5px 5px 0 var(--udder-pink);
}
.badge-back {
  transform: rotateY(180deg);
  background: var(--midnight);
  color: var(--cream);
  border-color: var(--midnight);
  align-items: flex-start;
  justify-content: flex-start;
  padding-top: var(--s-8);
  text-align: left;
}

.badge-crown {
  position: absolute;
  top: var(--s-4);
  left: 0;
  right: 0;
  font-weight: 800;
  font-size: 0.62rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-align: center;
}
.badge-back .badge-crown { color: var(--dusty-rose); }
.badge-photo { margin-top: var(--s-4); }

.badge-photo {
  width: 108px;
  height: 108px;
  border-radius: 50%;
  object-fit: cover;
  border: 5px solid var(--midnight);
  background: var(--milk);
  flex: none;
}
.badge-name {
  font-family: var(--font-display);
  font-size: var(--step-2);
  text-transform: uppercase;
  text-align: center;
  line-height: 0.95;
  margin: var(--s-4) 0 var(--s-1);
  color: var(--midnight);
  overflow-wrap: anywhere;
}
.badge-playa {
  font-style: italic;
  font-size: var(--step--1);
  color: var(--ink-soft);
  text-align: center;
  margin: 0;
}
.badge-line {
  margin: var(--s-3) 0 0;
  padding-top: var(--s-3);
  border-top: var(--rule-hair) solid var(--playa-dust);
  font-size: var(--step--1);
  color: var(--ink-soft);
  text-align: center;
  align-self: stretch;
}
.badge-line b { color: var(--midnight); font-weight: 800; }

.badge-qa { margin: 0; width: 100%; }
.badge-qa dt {
  font-weight: 800;
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dusty-rose);
  margin-bottom: var(--s-1);
}
.badge-qa dd {
  margin: 0 0 var(--s-4);
  font-size: var(--step--1);
  line-height: 1.45;
  color: var(--milk);
  overflow-wrap: anywhere;
  /* Long answers get clamped rather than pushing the stamp out of the card. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.badge-stamp {
  margin-top: auto;
  flex: none;
  align-self: stretch;
  padding-top: var(--s-3);
  border-top: var(--rule-hair) solid var(--dusty-rose);
  font-size: 0.66rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dusty-rose);
}

/* The flip control. A real button, sibling of the card link, sitting over the
   card's top-right corner — the crown is centred so that corner is free at
   every width, and bottom-right collided with the cow-name line on narrow
   cards. It stays put when the card flips, so it is also how you flip back. */
.flip-btn {
  position: absolute;
  right: var(--s-3);
  top: var(--s-3);
  z-index: 2;
  /* 44px hit area, 26px of ink — the touch target is the padding box, so the
     control can look like a stamp and still be thumb-sized. */
  width: 44px;
  height: 44px;
  padding: 9px;
  display: grid;
  place-items: center;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--midnight);
  font-family: var(--font-body);
  font-weight: 800;
  font-size: 0.78rem;
  line-height: 1;
}
.flip-btn span {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--udder-pink);
  border: var(--rule-hair) solid transparent;
  transition: border-color var(--dur-quick) var(--ease-out-quart);
}
.flip-btn:hover span,
.flip-btn[aria-pressed='true'] span { border-color: var(--midnight); }
.flip-btn:focus-visible { border-radius: 50%; }

.empty-wall {
  grid-column: 1 / -1;
  padding: var(--s-9);
  text-align: center;
  border: var(--rule-hair) dashed var(--grey-water);
  border-radius: var(--radius-card);
  color: var(--ink-soft);
}

.herd-foot {
  padding: var(--s-8) 0 calc(var(--s-8) + env(safe-area-inset-bottom));
  background: var(--midnight);
  color: var(--dusty-rose);
  text-align: center;
  font-size: var(--step--1);
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

/* ============================ 8. RESPONSIVE =========================== */

@media (max-width: 640px) {
  .wall { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-6) var(--s-3); }

  /* Two columns at 375px means ~160px badges — scale the card's furniture down
     with it rather than letting the type overflow. */
  /* A 4:5 card at ~163px wide is only ~204px tall — not enough for crown +
     photo + a two-line name + the cow-name rule. Give mobile badges more
     height rather than clipping their content. */
  .badge-flip { aspect-ratio: 2 / 3; }
  .badge-face {
    padding: var(--s-6) var(--s-2) var(--s-3);
    border-width: 3px;
    box-shadow: 4px 4px 0 var(--udder-pink);
  }
  .badge-back { padding: var(--s-7) var(--s-3) var(--s-3); }
  .badge-playa { font-size: 0.72rem; }
  .badge-photo { width: 76px; height: 76px; border-width: 4px; }
  .badge-name { font-size: 1.02rem; letter-spacing: 0; }
  .badge-crown { font-size: 0.5rem; letter-spacing: 0.18em; top: var(--s-3); }
  .badge-line { font-size: 0.72rem; overflow-wrap: anywhere; }
  .badge-qa dt { font-size: 0.58rem; }
  .badge-qa dd { font-size: 0.75rem; -webkit-line-clamp: 4; line-clamp: 4; margin-bottom: var(--s-3); }
  .badge-stamp { font-size: 0.55rem; letter-spacing: 0.12em; }
  /* Keep the 44px target on a ~160px card; only the ink shrinks. */
  .flip-btn { right: 0; top: 0; font-size: 0.66rem; }
  .flip-btn span { width: 22px; height: 22px; }

  .stall { grid-template-columns: 1fr; text-align: left; padding: var(--s-5); }
  .stall-avatar { width: 76px; height: 76px; }

  /* The masthead was wrapping into three stacked rows (~160px of a 812px
     screen). Wordmark on top, the whole nav on one compact row below. */
  .masthead { padding-top: calc(var(--s-2) + env(safe-area-inset-top)); padding-bottom: var(--s-2); }
  .masthead .wrap { gap: var(--s-2); }
  .masthead-mark { font-size: var(--step-0); }
  /* WAVE 2 integration: the nav carries five destinations now, not three.
     `nowrap` was squeezing each pill until its LABEL broke across two lines
     ("THE / HERD"). Pills stay on one line and the ROW wraps instead. */
  .masthead-nav {
    width: 100%; margin-left: 0;
    justify-content: center; flex-wrap: wrap;
    column-gap: var(--s-1); row-gap: var(--s-2);
  }
  .navlink { padding: var(--s-2) var(--s-3); font-size: 0.68rem; letter-spacing: 0.08em; white-space: nowrap; }
  .masthead .btn--ghost { font-size: 0.72rem; padding: var(--s-2) var(--s-4); letter-spacing: 0.06em; white-space: nowrap; }

  .herd-head { padding-top: var(--s-7); }
  .title-card { border-radius: 20px; }
  .ink-rule { inset: 8px; border-radius: 14px; }
  .ink-rule--inner { inset: 16px; border-radius: 9px; }

  /* 16px floor for every reading tier once the screen is a phone. --step--1 is
     ~12.5px, which is fine as a stamp on a badge and not fine as the sentence
     that explains which code to type. */
  /* `.field .hint` wins over a bare `.hint` on specificity, so match it. */
  .field .hint, .hint, .card-foot { font-size: 1rem; }
  .field label { font-size: 0.8125rem; }
  .door-head { font-size: var(--step-1); }

  /* THE BOX OFFICE ON A PHONE.
     Two things stop fitting at 390px and both were visible in the render:
     "NONSENSE" is eight wide glyphs of a heavy display face, and its 5px pink
     offset was crossing the inner hairline; and the deck's corner dot clusters
     sit exactly where the wordmark and the first button now are. On a phone the
     card has no spare corners, so the ornament stands down and the type fits. */
  .title-card--door .wordmark {
    font-size: clamp(2rem, 10.2vw, 3rem);
    text-shadow:
       3px  0   0 var(--udder-pink), -3px  0   0 var(--udder-pink),
       0    3px 0 var(--udder-pink),  0   -3px 0 var(--udder-pink),
       2px  2px 0 var(--udder-pink), -2px -2px 0 var(--udder-pink),
       2px -2px 0 var(--udder-pink), -2px  2px 0 var(--udder-pink);
  }
  .title-card--door .dots { display: none; }

  /* "Back into the barn" is eighteen characters of a wide display face and was
     breaking across two lines in a 262px pill. One notch down and it holds. */
  .door .btn { font-size: 1.05rem; letter-spacing: 0; padding-inline: var(--s-4); }
}

/* Only the genuinely narrow phones get a single column. 375px keeps two. */
@media (max-width: 359px) {
  .wall { grid-template-columns: 1fr; }
  .badge-flip { aspect-ratio: 4 / 5; }
  .badge-name { font-size: var(--step-1); }
  .badge-photo { width: 96px; height: 96px; }
}

/* ====================== 9. PREFERS-REDUCED-MOTION ===================== *
 * The overrides for grain, gate weave and the boiling filter are GONE with the
 * things they switched off — there is nothing left to switch off. The query
 * stays for the motion that DOES remain: the iris, the badge flip, the button
 * press and the entrance fade.                                             */

@media (prefers-reduced-motion: reduce) {
  .iris { display: none !important; }

  .badge-flip { transition: none !important; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: var(--dur-quick) !important;
    transition-property: opacity, color, background-color, border-color !important;
    scroll-behavior: auto !important;
  }

  .enter { opacity: 1; animation: none !important; }

  .btn:hover,
  .btn:active { transform: none !important; }
}
