/* ===========================================================================
   FutureToast Studios — workshop draft
   Colour-blocked bands: amber / paper / navy / ink. No JS, no third parties.

   Two systems hold the page together:
     · one spacing scale (--s1 … --s8). Every gap, pad and margin is a step on
       it, so nothing is a one-off clamp that nearly matches its neighbour.
     · three breakpoints (600 / 900 / 1200). The whole composition changes at
       each one instead of one grid at a time.
   =========================================================================== */

@font-face {
  font-family: 'Fraunces';
  src: url(assets/fraunces-latin.9ec8233c.woff2) format('woff2');
  font-weight: 600 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Figtree';
  src: url(assets/figtree-latin.8330490a.woff2) format('woff2');
  font-weight: 300 800;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}

:root {
  /* amber does three jobs and needs three values: a field you look across,
     an accent you look at, and a line on cream that has to stay visible. */
  --amber:       #F1AE48;
  --amber-press: #E08F22;
  --amber-field: #E69A37;
  --amber-line:  #B7771C;   /* 3.36:1 on paper — a hover underline is a state */
  --amber-deep:  #96570A;

  --paper:       #FAF3E6;
  --paper-edge:  #D6C7A6;
  --ink:         #2C2521;
  --ink-deep:    #0E0A06;
  --ink-soft:    #59472F;
  --amber-ink:   #2E1F0C;
  --navy:        #22355A;
  --navy-deep:   #101B2F;
  --navy-line:   #2A3856;
  --navy-soft:   #BCC7DB;
  --foot-soft:   #A89A84;
  --foot-edge:   #3A2F20;
  --foot-rule:   #5A4B37;

  --ring: var(--amber);

  --serif: 'Fraunces', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --sans:  'Figtree', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;

  --gutter: clamp(20px, 4.4vw, 56px);

  --s1: 8px;
  --s2: 12px;
  --s3: 16px;
  --s4: 24px;
  --s5: 32px;
  --s6: clamp(40px, 4vw, 56px);
  --s7: clamp(56px, 6vw, 80px);
  --s8: clamp(72px, 9vw, 128px);
  --band-y: var(--s8);

  /* one shadow recipe for every app capture: a contact layer and an ambient
     layer, same direction, re-inked for the ground it sits on. */
  --lift-paper: 0 2px 4px -2px rgba(28, 22, 16, .30),
                0 18px 36px -18px rgba(28, 22, 16, .26);
  --lift-navy:  0 2px 4px -2px rgba(0, 0, 0, .45),
                0 18px 36px -18px rgba(0, 0, 0, .50);

  /* one easing family, two curves. Both stay inside 0..1 on the Y axis, so
     nothing on this page can overshoot, bounce or wobble. `ease` appears
     nowhere. */
  --ease-out:   cubic-bezier(.22, .61, .36, 1);   /* entrances and reveals */
  --ease-quick: cubic-bezier(.32, .72, 0, 1);     /* hover, focus, press */

  --dur-micro:  120ms;   /* colour and underline colour only */
  --dur-state:  170ms;   /* button lift, nav underline, background swap */
  --dur-row:    420ms;   /* header bar, small rows, footer items */
  --dur-enter:  560ms;   /* headings, lede, prose */
  --dur-device: 640ms;   /* the slabs and the app captures — the one long one */

  /* the load sequence, in one place: a 70ms step, five steps at most */
  --d0: 0ms;
  --d1: 70ms;
  --d2: 140ms;
  --d3: 210ms;
  --d4: 280ms;

  /* travel. Small on purpose — a long slide reads cheap. */
  --rise-sm: 8px;
  --rise:    12px;
  --rise-md: 14px;
  --rise-lg: 18px;
  --settle:  .985;   /* scale from-state: images and slabs ONLY, never type */
  --lift:    -2px;   /* hover / focus lift on .btn */
  --press:   1px;    /* the existing :active depress */
}

/* --- reset ---------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  background: var(--ink-deep);   /* what an overscroll past the footer reveals */
}
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
  overscroll-behavior-y: none;
}
img, svg { display: block; max-width: 100%; }
img { height: auto; }
h1, h2, h3 { margin: 0; font-family: var(--serif); font-weight: 700; }
p { margin: 0; }
ol, ul, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }
a { color: inherit; }
::selection { background: var(--amber); color: var(--ink); }

/* 404 only: the page is shorter than a laptop viewport, so without this the ink
   footer floats and leaves a strip of paper under it. */
body.short-page { min-height: 100svh; display: flex; flex-direction: column; }
body.short-page main { flex: 1 0 auto; display: flex; flex-direction: column; }
body.short-page main > .band { flex: 1 0 auto; }

.wrap {
  width: 100%;
  max-width: 1240px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* grid children must be allowed to shrink below their intrinsic width, or a
   414px screenshot drags the whole track past the viewport on a phone. */
.hero-grid > *, .pantry-grid > *, .games-grid > *, .nf-grid > *,
.work-head > *, .shots > *, .service > *, .fact > *, .spec-row > * { min-width: 0; }

/* --- focus + skip --------------------------------------------------------- */
:focus-visible {
  outline: 3px solid var(--ring);
  outline-offset: 3px;
  border-radius: 2px;
}
/* the ring is declared per band rather than patched per section, so a new
   light band cannot ship with an amber ring on amber. */
.site-head, .hero, .pantry, .work, .notfound, .skip { --ring: var(--ink); }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  background: var(--ink);
  color: var(--paper);
  padding: 12px 18px;
  font-weight: 600;
  text-decoration: none;
}
.skip:focus { left: 8px; top: 8px; }

/* --- type scale ----------------------------------------------------------- */
h1 {
  font-size: clamp(44px, 6.4vw, 76px);
  line-height: 0.96;
  letter-spacing: -0.026em;
  max-width: 8.5em;
  text-wrap: balance;
}
h2 {
  font-size: clamp(34px, 4.2vw, 54px);
  line-height: 1.04;
  letter-spacing: -0.02em;
  max-width: 13em;
  text-wrap: balance;
}
h3 {
  font-size: clamp(20px, 1.6vw, 24px);
  line-height: 1.18;
  letter-spacing: -0.012em;
  text-wrap: balance;
}
.lede {
  font-size: clamp(19px, 1.45vw, 22px);
  line-height: 1.55;
  max-width: 44ch;
  text-wrap: pretty;
}
.body-lg {
  font-size: clamp(18px, 1.25vw, 20px);
  line-height: 1.6;
  max-width: 46ch;
  text-wrap: pretty;
}
.minor-note {
  font-size: 15.5px;
  max-width: 48ch;
  text-wrap: pretty;
}

/* --- buttons + small links ------------------------------------------------ */
.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s5);
}
.btn {
  display: inline-block;
  padding: 14px 24px;
  border: 2px solid var(--ink);
  border-radius: 999px;
  font-weight: 600;
  font-size: 16px;
  line-height: 1.2;
  text-decoration: none;
  transition: background-color var(--dur-state) var(--ease-quick),
              color            var(--dur-state) var(--ease-quick),
              border-color     var(--dur-state) var(--ease-quick),
              transform        var(--dur-state) var(--ease-quick);
}
/* hover deepens the primary instead of hollowing it out — the old rule made
   "See Pantry" turn into the secondary button on hover. */
.btn-ink { background: var(--ink); color: var(--paper); }
.btn-ink:hover { background: var(--ink-deep); border-color: var(--ink-deep); }
.btn-outline { background: transparent; color: var(--ink); }
.btn-outline:hover { background: var(--ink); color: var(--paper); }
.btn-amber { background: var(--amber); color: var(--ink); }
.btn-amber:hover { background: var(--amber-press); }
/* 2px up on hover against 1px down on press: three pixels of total state
   travel — enough to feel like a physical control, too little to watch move.
   Focus gets the identical lift; a keyboard user is not a second-class one. */
.btn:hover, .btn:focus-visible { transform: translateY(var(--lift)); }
.btn:active { transform: translateY(var(--press)); transition-duration: 100ms; }

.minor-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1) 22px;
  margin-top: var(--s4);
  font-size: 15.5px;
}
.minor-links a, .minor-note a {
  color: var(--ink-soft);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  /* no lift on inline text — a 1px nudge on 15.5px type shimmers */
  transition: color                 var(--dur-micro) var(--ease-quick),
              text-decoration-color var(--dur-micro) var(--ease-quick);
}
.minor-links a:hover, .minor-links a:focus-visible,
.minor-note a:hover,  .minor-note a:focus-visible { color: var(--ink); }

/* --- header --------------------------------------------------------------- */
.site-head {
  background: var(--paper);
  border-bottom: 2px solid var(--ink);
}
.head-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px 28px;
  min-height: 96px;
  padding-block: var(--s3);
}
.brand {
  display: inline-flex;
  align-items: center;
  color: var(--ink);
  text-decoration: none;
  flex-shrink: 0;
  transition: opacity var(--dur-state) var(--ease-quick);
}
/* focus deliberately does NOT take the dim: lowering the contrast of the thing
   a keyboard user just landed on is the opposite of feedback. The ring is the
   stronger signal and it is already there. */
.brand:hover { opacity: .78; }
.brand-logo { width: 105px; height: 64px; }
.brand-foot .brand-logo { width: 72px; height: 44px; }
.head-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 26px;
  font-size: 16px;
  font-weight: 600;
}
.head-nav a {
  position: relative;
  text-decoration: none;
  padding-block: 4px;
  border-bottom: 2px solid transparent;
}
/* the underline grows from the left rather than snapping on. The bar sits
   exactly over the transparent border it replaces, so the box model is
   untouched at both header states — including the taller tap target below
   600px, where the padding changes but the border does not. */
.head-nav a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  background: var(--amber-line);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-state) var(--ease-quick);
}
.head-nav a:hover::after, .head-nav a:focus-visible::after { transform: scaleX(1); }

/* --- bands ---------------------------------------------------------------- */
.band { padding-block: var(--band-y); }

/* hero — amber ------------------------------------------------------------- */
.hero {
  background: var(--amber-field);
  position: relative;
  z-index: 1;
  padding-block: var(--s7) var(--s6);
}
.hero-grid {
  display: grid;
  gap: var(--s7);
  align-items: center;
}
.hero-copy .lede { margin-top: var(--s4); color: var(--amber-ink); }

/* the slab: a slice-shaped block that carries the app and dips into the
   section below it. The top padding is set so the screenshot clears the sweep
   of the arch rather than being buried in it. */
.slab {
  background: var(--navy);
  border-radius: clamp(72px, 10vw, 128px) clamp(72px, 10vw, 128px) 24px 24px;
  padding: clamp(44px, 4.4vw, 60px) clamp(24px, 2.8vw, 40px) clamp(24px, 2.6vw, 36px);
  overflow: hidden;
  max-width: 430px;
  margin-inline: 0;      /* stacked: left edge lines up with the h1 */
  width: 100%;
}
.slab img {
  width: 100%;
  max-width: 308px;
  height: auto;
  margin-inline: auto;
  border-radius: 26px;
  border: 1px solid rgba(188, 199, 219, 0.40);
  box-shadow: var(--lift-navy);
}

/* pantry — paper ----------------------------------------------------------- */
.pantry { background: var(--paper); }
.pantry-grid {
  display: grid;
  gap: var(--s7);
  margin-bottom: var(--s7);
}

.app-id {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: var(--s5);
}
.app-icon {
  width: 62px;
  height: 62px;
  flex: none;
}
.app-id-text { display: flex; flex-direction: column; line-height: 1.25; }
.app-id-text strong { font-size: 19px; font-weight: 700; }
.app-id-text span { font-size: 14.5px; color: var(--ink-soft); letter-spacing: .01em; }

.pantry-copy .body-lg { margin-top: var(--s4); }

/* the four facts sit full width under the copy, as a ruled block */
.facts { border-top: 2px solid var(--ink); display: grid; }
.fact {
  padding-block: var(--s4);
  border-bottom: 1px solid var(--paper-edge);
}
.fact dt { font-weight: 700; font-size: 17px; margin-bottom: 4px; }
.fact dd {
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: 46ch;
  text-wrap: pretty;
}

/* the phones: an uneven pair standing on one baseline, so the captions read
   as a row and the shorter shot hangs from the bottom instead of floating. */
.shots { display: grid; row-gap: var(--s6); }
/* 414px is the captures' own width: the pair may shrink, never upscale. */
.shot { margin: 0; max-width: 414px; }
/* hero slab: the three services, set like the games spec table */
.slab-list { list-style: none; margin: 0; padding: 0; border-top: 2px solid var(--amber); color: var(--paper); }
.slab-list li {
  display: grid;
  grid-template-columns: 2.4em minmax(0, 1fr);
  column-gap: var(--s3);
  padding-block: 18px;
  border-bottom: 1px solid var(--navy-line);
}
.slab-list .num { font-family: var(--serif); color: var(--amber); font-size: 15px; padding-top: 4px; }
.slab-item { display: grid; row-gap: 4px; min-width: 0; }
.slab-item strong { font-family: var(--serif); font-weight: 600; font-size: clamp(18px, 1.5vw, 22px); text-wrap: balance; }
.slab-item span { font-size: 15px; line-height: 1.5; color: var(--navy-soft); }
.slab-note { margin: var(--s4) 0 0; font-size: 14.5px; letter-spacing: .01em; color: var(--navy-soft); }
.facts-lead { margin: var(--s6) 0 var(--s2); }

.phone {
  border-radius: clamp(16px, 2vw, 22px);
  overflow: hidden;
  box-shadow: 0 0 0 1px rgba(28, 22, 16, .14), var(--lift-paper);
}
.phone img { width: 100%; height: auto; }
.shot figcaption {
  margin-top: var(--s2);
  font-size: 14.5px;
  line-height: 1.45;
  letter-spacing: .01em;
  color: var(--ink-soft);
  text-wrap: balance;
}

/* games — navy ------------------------------------------------------------- */
.games {
  background: var(--navy-deep);
  color: var(--paper);
}
.games-grid {
  display: grid;
  gap: var(--s7);
}
.games .body-lg { margin-top: var(--s4); color: var(--navy-soft); }
.games .minor-note { margin-top: var(--s4); color: var(--navy-soft); }
.games .minor-note a { color: var(--amber); }
.games .minor-note a:hover, .games .minor-note a:focus-visible { color: var(--paper); }

.spec { border-top: 2px solid var(--amber); }
.spec-row {
  display: grid;
  gap: 6px 24px;
  padding-block: 18px;
  border-bottom: 1px solid var(--navy-line);
}
.spec dt { font-size: 14.5px; letter-spacing: .01em; color: var(--navy-soft); }
.spec dd {
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(18px, 1.5vw, 22px);
  text-wrap: balance;
}

/* client work — paper ------------------------------------------------------ */
.work { background: var(--paper); }
.work-head { display: grid; row-gap: var(--s6); }

.services { margin-top: var(--s6); border-top: 2px solid var(--ink); }
.facts-case, .facts-how { margin-top: var(--s6); }
.service {
  display: grid;
  row-gap: var(--s2);
  column-gap: var(--s5);
  padding-block: var(--s5);
  border-bottom: 1px solid var(--paper-edge);
}
.num {
  display: none;             /* the ordered list carries the sequence when the
                                three-column rail is not there to hold it */
  font-family: var(--serif);
  font-weight: 600;
  font-size: 19px;
  color: var(--amber-deep);
}
.service p {
  color: var(--ink-soft);
  font-size: 16px;
  line-height: 1.6;
  max-width: 46ch;
  text-wrap: pretty;
}

/* contact + footer — ink --------------------------------------------------- */
.contact {
  background: var(--ink-deep);
  color: var(--paper);
  padding-bottom: var(--s7);
}
.contact-mark {
  width: 84px;
  height: auto;              /* 240x187 exactly; a fixed 66px squashed it 0.8% */
  margin-left: -2px;         /* seats the slice's curve on the heading's stem */
  margin-bottom: var(--s4);
}
.contact h2 { max-width: 12em; }
.mailto {
  margin-top: var(--s5);
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(25px, 5vw, 60px);
  line-height: 1.05;
  letter-spacing: -0.02em;
}
.mailto a {
  color: var(--amber);
  text-decoration: underline;
  text-decoration-thickness: clamp(2px, .34vw, 4px);
  text-underline-offset: clamp(5px, .8vw, 11px);
  /* the biggest interactive target on the site, and the only conversion point
     on the band: the thick underline crossfades with the text rather than
     lagging behind it. An amber-to-cream hard cut at 60px is a jolt. */
  transition: color                 var(--dur-state) var(--ease-quick),
              text-decoration-color var(--dur-state) var(--ease-quick);
}
.mailto a:hover, .mailto a:focus-visible { color: var(--paper); }
.contact .minor-note { margin-top: var(--s4); color: var(--foot-soft); }

.site-foot {
  background: var(--ink-deep);
  color: var(--foot-soft);
  padding-bottom: var(--s6);
}
/* the rule belongs to the content column, not to the wrap's padding box —
   every other rule on the page starts and ends at the content edge. */
.foot-grid {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px 40px;
  padding-top: var(--s6);
}
.foot-grid::before {
  content: '';
  position: absolute;
  inset: 0 var(--gutter) auto;
  border-top: 1px solid var(--foot-edge);
}
.foot-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  font-size: 15px;
  margin-right: auto;
}
.foot-links a {
  color: var(--foot-soft);
  text-decoration: underline;
  text-decoration-color: var(--foot-rule);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  /* two properties move at once here; transitioning them together is what
     stops the text and its underline arriving at different moments. */
  transition: color                 var(--dur-state) var(--ease-quick),
              text-decoration-color var(--dur-state) var(--ease-quick);
}
.foot-links a:hover, .foot-links a:focus-visible {
  color: var(--amber);
  text-decoration-color: var(--amber);
}
.copy { font-size: 14.5px; }

/* --- 404 ------------------------------------------------------------------ */
.notfound {
  background: var(--amber-field);
  min-height: 74svh;
  display: flex;
  align-items: center;
  padding-block: var(--s8);
}
.nf-grid { display: grid; gap: var(--s7); align-items: center; width: 100%; }
.notfound .lede { margin-top: var(--s4); color: var(--amber-ink); }
.notfound h1 { max-width: 15ch; }
/* the 404 borrows the hero's one signature move rather than inventing a
   second one: the same navy slab, holding the mark instead of the app.
   Below 600 the copy fills the band on its own and the slab would only add
   scroll, so it starts at the same 600 threshold as the other two-up pairs. */
.nf-slab { display: none; }

/* ===========================================================================
   Three breakpoints. Each one changes the whole composition.
   =========================================================================== */

/* 600 — the two-up pairs come in ------------------------------------------- */
@media (min-width: 600px) {
  /* the pair spans the content column without either capture being upscaled,
     and subgrid puts both captures on one baseline and both captions on the
     next, however many lines a caption runs to. */
  .shots {
    grid-template-columns: repeat(2, minmax(0, 414px));
    grid-template-rows: auto auto;
    justify-content: space-between;
    column-gap: var(--s4);
    row-gap: var(--s2);
  }
  .shot { grid-row: 1 / span 2; display: grid; grid-template-rows: subgrid; }
  .shot:first-child { grid-column: 1; }
  .shot-low { grid-column: 2; }
  .shot .phone { align-self: end; }
  .shot figcaption { margin-top: 0; }
  .spec-row {
    grid-template-columns: minmax(96px, 22%) minmax(0, 1fr);
    align-items: baseline;
  }

  /* stacked under the copy, left edge on the h1 — the same move the hero's
     slab makes when it stacks. Without it 600-899 was a bare amber field. */
  .nf-slab {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--navy);
    border-radius: clamp(72px, 10vw, 128px) clamp(72px, 10vw, 128px) 24px 24px;
    padding: var(--s5);
    max-width: clamp(200px, 30vw, 260px);
    width: 100%;
    margin-inline: 0;
    aspect-ratio: 17 / 20;
  }
  /* holds the mark at ~half the slab's width, the proportion it has on desktop */
  .nf-mark { width: min(140px, 74%); }
}

@media (max-width: 599px) {
  .head-bar {
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    min-height: 0;
    padding-block: 0;
  }
  .brand { padding-block: var(--s3); }
  .brand-logo { width: 84px; height: auto; }   /* 105x64 kept exactly */
  .head-nav {
    width: 100%;
    gap: 0 22px;
    padding-block: 2px;
    border-top: 1px solid var(--paper-edge);
    font-size: 15.5px;
  }
  /* 44px hit boxes on every small link at phone width */
  .head-nav a { padding-block: 12px 8px; }
  .minor-links { gap: 0 22px; }
  .minor-links a, .foot-links a { display: inline-block; padding-block: 10px; }
  /* one column beats four links falling where they land */
  .foot-links { display: grid; justify-items: start; gap: 0; width: 100%; margin-right: 0; }
  .mailto a { display: inline-block; padding-block: 9px; }
  .phone {
    box-shadow: 0 0 0 1px rgba(28, 22, 16, .14),
                0 1px 3px -1px rgba(28, 22, 16, .28),
                0 10px 20px -12px rgba(28, 22, 16, .24);
  }
  h1 br { display: none; }
}

/* 600-899 — still a touch width. The nav's 44px hit boxes were scoped to the
   phone block below, which left every tablet showing 36px targets: iPad
   portrait is 768-834 and is touch-first. The link's transparent 2px border
   is what ::after sits against, so the underline moves with the padding and
   nothing else in the bar does — .head-bar is held at 96px by the 64px logo
   plus its own 16px padding, and a 48px nav still clears it. */
@media (min-width: 600px) and (max-width: 899px) {
  .head-nav a { padding-block: 12px 8px; }
}

/* 900 — the page becomes two-column ---------------------------------------- */
@media (min-width: 900px) {
  .hero { padding-bottom: 0; }
  .hero-grid { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); }
  .slab { margin-bottom: -108px; margin-inline: auto 0; }
  .pantry { padding-top: calc(var(--band-y) + 74px); }

  .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s7); }

  .games-grid { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); align-items: start; }

  /* the section intro and the service copy are the same voice at the same
     size, so they share a column edge. */
  .work-head {
    grid-template-columns: 68px minmax(0, 0.85fr) minmax(0, 1.45fr);
    column-gap: var(--s5);
    align-items: end;
  }
  .work-head h2 { grid-column: 1 / 3; }

  .service {
    grid-template-columns: 68px minmax(0, 0.85fr) minmax(0, 1.45fr);
    align-items: baseline;
    row-gap: 0;
  }
  .num { display: block; }

  .nf-grid { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); }
  /* two-up: the slab hangs on the right content margin, like the hero's */
  .nf-slab {
    padding: var(--s6);
    max-width: 340px;
    margin-inline: auto 0;
  }
  .nf-mark { width: clamp(120px, 12.5vw, 180px); }
}

@media (max-width: 899px) {
  h1 br, h2 br { display: none; }
}

/* 1200 — the Pantry block splits ------------------------------------------- */
@media (min-width: 1200px) {
  .pantry-grid { grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr); align-items: start; }
  .pantry-copy .btn-row { margin-top: var(--s6); }
}
/* ===========================================================================
   MOTION
   The page settling into place, not performing. Nothing here loops, spins,
   overshoots or counts, and only transform and opacity ever move — so no
   rule below can reflow the page, shift a layout or cost a frame of CLS.

   Two mechanisms carry all of it:

     1. ABOVE THE FOLD — time-based @keyframes with `animation-fill-mode:
        backwards`. Genuinely one-way: runs once per pageview, never repeats.
     2. BELOW THE FOLD — CSS scroll-driven `view()` timelines.

   ONE RULE GOVERNS EVERY SCROLL REVEAL. It opens as the element's own top
   crosses the bottom edge of the window, and it reads as arrived about 0.3 of
   a viewport height later — 240 to 320px of scroll on a 900px window, so that
   every reveal on the page finishes with its top between 64% and 74% of the
   window. That number is the whole point of this pass. At half of it every
   reveal landed inside the last inch of the screen, several of them below it,
   and a third of the page's motion was never seen at all: measured at
   1440x900, nine elements reached opacity 1.00 before they had crossed the
   fold. Now none does — every reveal is still travelling when it appears,
   with 67% to 100% of its fade happening on screen.

   THE OTHER HALF OF THAT RULE IS WHERE IT STOPS. Landing at ~70% keeps the
   motion in the lower third, where the eye is, and keeps it out of the part
   of the screen people read from. Swept down and back up at eleven window
   sizes, nothing whose top is in the upper 60% of the window is ever below
   0.99 opacity. Between 60% and 75% the floor is 0.82, which is the honest
   price of the change and is the bottom quarter of the screen.

   WHERE THE STAGGER COMES FROM. Almost everywhere, the scroll itself: two
   stacked blocks 140px apart arrive 140px apart, which is a beat of roughly a
   third of a second at reading speed and needs no authoring. A designed offset
   is added in exactly three places, and only because the siblings there share a
   top edge and would otherwise fire as one movement: the two captures, which
   stand on a common baseline; the two facts in a row at 900+; and the heading
   and its paragraph in the work intro at 900+. Nothing on the page is a cascade
   more than three steps deep, and where a container names the timeline, each
   child's start is set to the scroll position at which that child appears —
   so a container range behaves exactly like an independent one and cannot
   push its own tail off the bottom of the screen, which is what a fixed
   stagger on a tall parent does.

   PROGRESSIVE ENHANCEMENT. Every at-rest style in this stylesheet is the
   final, VISIBLE state. Each hidden from-state lives in a @keyframes block and
   is reached only through a rule inside `@supports (animation-timeline:
   view())` — itself inside `@media (prefers-reduced-motion: no-preference)`.
   A browser with no scroll-driven animations never sees an `opacity: 0` or a
   translate, and renders this page exactly as it rendered before this pass.

   WHY THE RANGES ARE ANCHORED TO `cover`. A range written against `entry`
   scales with the subject's own height, so a 62px row would reveal over 28px
   of scroll and a 700px figure over 315px — the same percentage reading as two
   different animations. `cover 0%` starts at the same instant as `entry 0%`
   but its length is (subject + viewport), which is dominated by the viewport.
   That is also what lets one percentage hold at every window size: the travel
   it buys scales with the viewport, so a reveal lands at the same place on
   screen in a 700px window and in a 1600px one. The two blocks at the very
   bottom of the page are the exception, and the reason is written out where
   they are: a cover range needs scroll that no longer exists down there.

   A SCROLL-DRIVEN REVEAL HAS NO DURATION, and it is worth saying so rather
   than implying one. Its length is pixels of scroll, so what a visitor
   perceives is that length divided by how fast they are scrolling. At ~280px,
   a reading-speed scroll of 400-600px/s settles it in about 470-700ms, which
   is the budget these entrances are written to; a hard flick compresses it to
   ~140ms and a slow crawl stretches it past a second. Only the fast end is
   worth defending against, and the defence is length: at the old 135px the
   same flick was a ~65ms flicker across 17px prose, which is exactly what a
   cheap page looks like.

   REVERSIBILITY, stated plainly rather than papered over. Scroll-driven
   timelines are bidirectional: scrub the scroll back and the animation scrubs
   back with it. The only true one-way trigger is IntersectionObserver, which
   needs JavaScript, which the CSP here forbids — correctly. So the reversal is
   engineered out of sight instead: every range opens at the moment the element
   first appears and closes while it is still below the reading area, and
   `--ease-out` on a progress timeline front-loads the travel so it is most of
   the way arrived earlier still. Content is fully settled everywhere in the
   reading area; the only thing that can move on the way back up is already
   sliding off the bottom of the screen.
   =========================================================================== */

/* Every from-state on the page lives in these eight blocks and nowhere else.
   A keyframe nothing references is inert, so they cost nothing where the
   @supports guard below does not match. */
@keyframes ft-rise-sm { from { opacity: 0; transform: translateY(var(--rise-sm)); } to { opacity: 1; transform: none; } }
@keyframes ft-rise    { from { opacity: 0; transform: translateY(var(--rise));    } to { opacity: 1; transform: none; } }
@keyframes ft-rise-md { from { opacity: 0; transform: translateY(var(--rise-md)); } to { opacity: 1; transform: none; } }
@keyframes ft-rise-lg { from { opacity: 0; transform: translateY(var(--rise-lg)); } to { opacity: 1; transform: none; } }
/* scale belongs to images and the navy slabs only. Scaling 76px Fraunces or a
   60px email address re-rasterises the glyphs mid-flight and goes soft on
   exactly the elements that should look sharpest. */
@keyframes ft-settle    { from { opacity: 0; transform: translateY(var(--rise))    scale(var(--settle)); } to { opacity: 1; transform: none; } }
@keyframes ft-settle-sm { from { opacity: 0; transform: translateY(var(--rise-sm)) scale(var(--settle)); } to { opacity: 1; transform: none; } }
@keyframes ft-settle-lg { from { opacity: 0; transform: translateY(var(--rise-lg)) scale(var(--settle)); } to { opacity: 1; transform: none; } }
/* the one pure fade. It exists for the single element on the site that
   animates INSIDE another animating element, and it is what keeps that from
   being a special case — see .nf-mark. */
@keyframes ft-fade { from { opacity: 0; } to { opacity: 1; } }

/* the browser's own interpolation for the three nav links and the two hero
   buttons — cancellable by touching the wheel, which is what separates it
   from scroll-jacking. The margin keeps a section heading off the top edge. */
html { scroll-behavior: smooth; }
#pantry, #games, #work, #contact { scroll-margin-top: var(--s4); }

@media (prefers-reduced-motion: no-preference) {

  /* --- the load sequence -------------------------------------------------
     `backwards` fill means the from-state exists only while the animation is
     pending or running. If the animation never runs, the element sits at its
     normal cascade value, which is visible — there is no window in which a
     delayed element can be stranded. The stylesheet is render-blocking, so if
     these rules applied at all, the animations ran.

     THE HEADER IS NOT IN THIS SEQUENCE, and that is a decision rather than an
     omission. It is the frame the page settles into, so it should already be
     drawn when the settling starts; fading it in bought the least of anything
     here and cost the most, because it put the studio's own logo behind a
     420ms fade and pushed every word below it back a step. Making it static
     returns that step to the h1, which now starts at the first frame the
     stylesheet is live for — three beats to compose the hero instead of five,
     and no moment where the largest element on the screen is invisible. */

  /* the two pages share one arrival, beat for beat, which is most of what
     makes them feel like one site */
  .hero-copy h1,       .nf-copy h1       { animation: ft-rise-lg var(--dur-enter) var(--ease-out) var(--d0) backwards; }
  .hero-copy .lede,    .nf-copy .lede    { animation: ft-rise-md var(--dur-enter) var(--ease-out) var(--d1) backwards; }
  /* the row arrives as one unit — staggering two adjacent pills against each
     other is the exact texture that reads as a template */
  .hero-copy .btn-row, .nf-copy .btn-row { animation: ft-rise    520ms           var(--ease-out) var(--d2) backwards; }

  /* the page's most expensive-looking object gets the longest curve and the
     only scale. It rides with the lede rather than behind the buttons: it is
     the likeliest thing on the page to be the browser's largest paint, and
     every millisecond it spends at opacity 0 is charged to that measurement.
     The origin matters: at 900+ the hero slab hangs 108px into the paper band
     below it, and a centre origin would make that seam breathe by ~3px. At the
     bottom edge the scale contributes nothing there. Scaling the block also
     scales its --lift-navy shadow, which is the honest version of the shadow
     deepening as it lands. */
  .slab, .nf-slab {
    transform-origin: 50% 100%;
    animation: ft-settle-lg var(--dur-device) var(--ease-out) var(--d1) backwards;
  }
  /* the 404's one moment of character: the slab arrives, and the toast becomes
     present inside it a beat later. No spin — the mark has motion lines drawn
     into it already and does not need animating.
     IT TAKES OPACITY AND NOTHING ELSE, and that is the rule rather than a
     preference. This is the only element on the site with an animating
     ancestor, and a transform inside a transform multiplies: the slab's
     18px translate and .985 scale about its bottom edge already carry every
     child of it 26.8px, measured — exactly what the hero slab does to the
     screenshot it holds, which is the same move and has always looked right.
     A second travel token declared here would be added on top of that, for a
     total no token in the system authorises. So nothing nested declares its
     own travel; its ancestor's motion is its motion, and the beat that makes
     it read as a separate arrival is the delay on the fade. */
  .nf-mark { animation: ft-fade var(--dur-row) var(--ease-out) var(--d3) backwards; }
  /* on the 404 the footer is inside the first viewport, so a scroll reveal
     would never fire and it would be the one static thing on a settling page */
  .short-page .foot-grid { animation: ft-rise-sm var(--dur-row) var(--ease-out) var(--d4) backwards; }

  /* --- the scroll reveals ----------------------------------------------- */
  @supports (animation-timeline: view()) {

    /* shared: each reveal runs the length of its own range, on the entrance
       curve. `animation-duration: auto` is written as a longhand rather than
       through the shorthand because the shorthand's initial duration differs
       between CSS Animations 1 and 2, and this is not a thing to leave open. */
    .pantry-copy > *,
    .shot .phone,
    .shot figcaption,
    .facts .fact,
    .games-copy > *,
    .spec .spec-row,
    .work-head > *,
    .services .service,
    .contact > .wrap > *,
    body:not(.short-page) .foot-grid {
      animation-duration: auto;
      animation-fill-mode: both;
      animation-timing-function: var(--ease-out);
    }

    /* Pantry copy — the identity row, the heading and the paragraph come down
       the column on one timeline, each opening as it reaches the fold. The
       button row and the two small links below them sit 500px and 575px down a
       586px container: on the parent's timeline their steps would have to be
       34% and 39% to open on time, and every frame of them would then play out
       under the bottom edge of the window. They carry their own timelines
       instead, which is what the stacked blocks further down the page do. */
    .pantry-copy { view-timeline-name: --tl-pantry; view-timeline-axis: block; }
    .pantry-copy > *              { animation-name: ft-rise-md; animation-timeline: --tl-pantry; }
    .pantry-copy > :nth-child(1)  { animation-range: cover  1% cover 21%; }
    .pantry-copy > :nth-child(2)  { animation-range: cover  8% cover 28%; }
    .pantry-copy > :nth-child(3)  { animation-range: cover 17% cover 38%; }
    .pantry-copy > :nth-child(4),
    .pantry-copy > :nth-child(5)  { animation-timeline: view(); animation-range: cover  0% cover 32%; }
    .pantry-copy h2               { animation-name: ft-rise-lg; }

    /* the captures: a small rise and a whisper of scale, landing with their
       shadow. The transform goes on .phone rather than .shot because .shot is
       a subgrid item from 600 up. Stacked (below 600) each one owns its
       timeline; the 600 rule further down pairs them. */
    .shot .phone {
      animation-name: ft-settle-lg;
      animation-timeline: view();
      animation-range: cover  0% cover 21%;
    }
    /* the caption keeps its own timeline at every width, so it always arrives
       as it appears rather than firing off-screen from a container range —
       which, for something sitting at the very bottom of a 700px figure pair,
       is what a shared timeline would do. Translate only: 14.5px type is never
       put through a scale. */
    .shot figcaption {
      animation-name: ft-rise-sm;
      animation-timeline: view();
      animation-range: cover  0% cover 33%;
    }

    /* four ruled rows, stacked at this width, so the scroll is the stagger */
    .facts .fact {
      animation-name: ft-rise-md;
      animation-timeline: view();
      animation-range: cover  0% cover 28%;
    }

    /* the navy band: the colour block does the visual work, the copy only has
       to settle into it */
    .games-copy { view-timeline-name: --tl-games; view-timeline-axis: block; }
    .games-copy > *             { animation-name: ft-rise-md; animation-timeline: --tl-games; }
    .games-copy > :nth-child(1) { animation-range: cover  2% cover 26%; }
    .games-copy > :nth-child(2) { animation-range: cover 10% cover 35%; }
    .games-copy > :nth-child(3) { animation-range: cover 24% cover 48%; }
    .games-copy h2              { animation-name: ft-rise-lg; }

    /* the spec sits beside that copy from 900 up and enters at the same
       moment, so its rows start a beat behind the heading and step down.
       The 2px amber rule and the 1px separators do not move: a border cannot
       be scaled without a pseudo-element faking it, and a rule un-drawing
       itself on the way back up is the most conspicuous reversal available. */
    .spec { view-timeline-name: --tl-spec; view-timeline-axis: block; }
    .spec .spec-row                { animation-name: ft-rise; animation-timeline: --tl-spec; }
    .spec .spec-row:nth-child(1)   { animation-range: cover  1% cover 28%; }
    .spec .spec-row:nth-child(2)   { animation-range: cover  8% cover 35%; }
    .spec .spec-row:nth-child(3)   { animation-range: cover 15% cover 42%; }

    /* stacked at this width; the 900 rule below switches them to a container */
    .work-head > * {
      animation-name: ft-rise-md;
      animation-timeline: view();
      animation-range: cover  0% cover 31%;
    }
    .work-head h2 { animation-name: ft-rise-lg; }

    /* each numbered row reveals as ONE unit. Sub-staggering the numeral, the
       heading and the paragraph would be three moving parts inside 280px —
       fussy, and the first thing a visitor would consciously notice. */
    .services .service {
      animation-name: ft-rise-md;
      animation-timeline: view();
      animation-range: cover  0% cover 29%;
    }

    /* THE CLOSING BAND IS ANCHORED TO `entry`, NOT `cover`, AND THIS IS THE
       ONE PLACE THAT EXCEPTION IS CORRECT.

       A `cover` range is (subject + viewport) long, so reaching a quarter of
       it costs a quarter of a viewport height of scrolling. Everything above
       is far enough from the end of the document to have that scroll
       available. This band is not: the page runs out underneath it. Measured
       in Chrome, the furthest the contact band's cover timeline can ever
       progress is 27% at an 1800px-tall window, against a range that needed
       26% — and the footer's was 8% against 22%, which left the copyright line
       sitting permanently at 0.44 opacity, 4.5px low, on a tall desktop.

       An `entry` range is the subject's OWN height long, so its cost in scroll
       does not grow with the viewport. Reachable progress here is 1 + (document
       left below the band ÷ the band's height) = 168%, at any window size, which
       is why the percentages below are allowed past 100%.

       The other thing the bottom of a document cannot give you is a uniform
       landing height. There are 581px of page left under this band and 329px
       between its first element and its last, so they cannot all finish ~280px
       after they appear — the last one would need scroll that does not exist.
       Each is given the travel its own remaining scroll can pay for instead,
       longest for the mark and tapering to the closing line: measured at
       1440x900 they land with their tops at 67%, 67%, 74% and 84% of the
       window. Every one opens within ~15px of the moment it appears, so all
       four are in flight the whole time they are on screen, and none of them
       is still moving by the time it reaches the reading area. Because an
       entry range is a fixed number of pixels rather than a share of the
       viewport, a short window makes this band's travel a larger fraction of
       the screen; at 1600x700 the mark finishes at 59% rather than 67%, which
       is the one place on the page where a reveal ends above the lower third.
       It ends there at 0.99 opacity, so what is left is arithmetic, not
       something anyone can see. */
    .contact > .wrap { view-timeline-name: --tl-contact; view-timeline-axis: block; }
    .contact > .wrap > *             { animation-name: ft-rise-md; animation-timeline: --tl-contact; }
    .contact > .wrap > .contact-mark { animation-name: ft-settle;  animation-range: entry  4% entry 96%; }
    .contact > .wrap > h2            { animation-name: ft-rise-lg; animation-range: entry 34% entry 120%; }
    .contact > .wrap > .mailto       {                             animation-range: entry 70% entry 142%; }
    .contact > .wrap > .minor-note   { animation-name: ft-rise-sm; animation-range: entry 92% entry 140%; }

    /* THE FOOTER REVEALS ONLY WHERE THERE IS ROOM TO SEE IT HAPPEN — below
       900, where the links stack and .foot-grid grows to 171px at 768 and
       355px at 390, with 211px and 395px of document under it. On a desktop
       it is ~100px tall with ~156px under it, which means that even at the
       very bottom of the page its top edge is still at 81% of the window:
       there is no scroll position from which a reveal there could be watched,
       and measurement agreed — all three items completed over 45px of scroll
       with none of it in flight on screen. So on a desktop the footer is
       simply drawn. By the time it is on screen the closing statement above it
       has finished arriving, and a band that is already at rest when it
       appears is not the same thing as a band that forgot to move.
       Where it does reveal it moves as ONE block rather than three. The three
       items span the whole height of the band at these widths, so staggering
       them would put the copyright line back in exactly the starved position
       the entry anchor was introduced to get it out of. */
    @media (max-width: 899px) {
      body:not(.short-page) .foot-grid {
        animation-name: ft-rise-sm;
        animation-timeline: view();
        animation-range: entry 0% entry 88%;
      }
    }

    /* 600 — the captures become a pair, so they need a designed stagger: they
       stand on one baseline, so the shorter one's top starts 158px lower and
       its own timeline would open it that much later — which reads as two
       unrelated events rather than a pair landing. One timeline on .shots
       gives each of them a start matched to where it actually is. */
    @media (min-width: 600px) {
      .shots { view-timeline-name: --tl-shots; view-timeline-axis: block; }
      .shot .phone             { animation-timeline: --tl-shots; }
      .shot:first-child .phone { animation-range: cover  1% cover 22%; }
      .shot-low .phone         { animation-range: cover 13% cover 34%; }
    }

    /* 900 — two blocks stop being stacked and become rows, and a shared
       timeline starts being the right answer for them: the members of a row
       share a top edge, so only a designed 2% offset separates them. Below
       this width each element owns its own timeline and the scroll separates
       them for free. */
    @media (min-width: 900px) {
      .facts { view-timeline-name: --tl-facts; view-timeline-axis: block; }
      .facts .fact                { animation-timeline: --tl-facts; }
      .facts .fact:nth-child(1)   { animation-range: cover  1% cover 25%; }
      .facts .fact:nth-child(2)   { animation-range: cover  3% cover 27%; }
      .facts .fact:nth-child(3)   { animation-range: cover 16% cover 40%; }
      .facts .fact:nth-child(4)   { animation-range: cover 18% cover 42%; }

      .work-head { view-timeline-name: --tl-work; view-timeline-axis: block; }
      .work-head > *      { animation-timeline: --tl-work; }
      .work-head h2       { animation-range: cover  3% cover 33%; }
      .work-head .body-lg { animation-range: cover  5% cover 35%; }
    }
  }
}

/* --- reduced motion: a real kill switch ------------------------------------
   `animation: none` rather than a near-zero duration, because a near-zero
   duration is not an off switch — it does not reset animation-delay, so a
   delayed entrance renders as a blank element, and it makes no defined promise
   about a progress-based timeline, where duration is not what drives the
   animation at all. The shorthand resets name, duration, delay, fill AND
   animation-timeline together, so every element lands on its at-rest cascade
   value — which, by the construction above, IS the final visible state.
   Nothing can be stranded hidden and nothing can be scrubbed by scroll.

   Transitions are held to 60ms and restricted to paint properties. That one
   declaration is what stops .head-nav a::after from sliding its scaleX and the
   buttons from sliding their lift, while leaving colour and underline feedback
   alive. Reduced motion should mean no movement, not no response.

   The transform reset is scoped to .btn on purpose: .head-nav a::after uses
   scaleX(0) as its RESTING state, and a blanket `transform: none !important`
   would pin all three nav underlines permanently on. That is a legibility
   regression dressed up as an accessibility fix. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition-property: color, background-color, border-color,
                         text-decoration-color, opacity !important;
    transition-duration: 60ms !important;
    transition-delay: 0s !important;
    transition-timing-function: var(--ease-quick) !important;
  }
  html { scroll-behavior: auto !important; }
  .btn:hover, .btn:focus-visible, .btn:active { transform: none !important; }
}

/* dark variant ----------------------------------------------------------- */
/* Every colour above is a token, so dark mode is a token swap plus explicit
   colours for the sections that already sit on dark grounds (their text was
   "paper", which is now the dark ground). Amber fields keep dark text. */
@media (prefers-color-scheme: dark) {
  :root {
    --paper:       #14110E;
    --paper-edge:  #3A3128;
    --ink:         #F3EADB;
    --ink-deep:    #0A0806;
    --ink-soft:    #C9B99F;
    --amber-field: #D98F2E;
    --amber-line:  #F1AE48;
    --lift-paper:  0 2px 4px -2px rgba(0, 0, 0, .60), 0 14px 32px -14px rgba(0, 0, 0, .55);
  }
  .games, .contact, .site-foot, .slab-list, .slab-item strong { color: #F3EADB; }
  .games .minor-note a:hover, .games .minor-note a:focus-visible,
  .mailto a:hover, .mailto a:focus-visible { color: #F3EADB; }
  .hero, .notfound { color: #2E1F0C; }
  .hero h1, .notfound h1 { color: #2E1F0C; }
  .hero .btn-outline, .notfound .btn-outline { color: #2E1F0C; border-color: #2E1F0C; }
  .hero .btn-outline:hover, .notfound .btn-outline:hover { background: #2E1F0C; color: #F3EADB; }
  .hero .btn-ink, .notfound .btn-ink { background: #2E1F0C; color: #F3EADB; border-color: #2E1F0C; }
  .hero .btn-ink:hover, .notfound .btn-ink:hover { background: #0A0806; border-color: #0A0806; }
  .phone { box-shadow: 0 0 0 1px rgba(255, 255, 255, .10), var(--lift-paper); }
}

/* layout round 2 (Wyatt's picks: hero 2, tiles 4, pantry 2, how-we-work 3, tail 5) */
:root { --tile: #F3EAD9; }

/* hero plate: plain soft-cornered navy, one phone that cycles through two real screens */
.slab { border-radius: 28px; padding: clamp(22px, 2.6vw, 34px); }
.hero-phone {
  position: relative;
  width: 100%;
  max-width: 296px;
  margin-inline: auto;
  aspect-ratio: 414 / 666;
  border-radius: 26px;
  overflow: hidden;
  background: #0B0A09;
  border: 1px solid rgba(188, 199, 219, .40);
  box-shadow: var(--lift-navy);
}
.hero-phone img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; max-width: none; margin: 0;
  object-fit: cover; object-position: top center;
  border: 0; border-radius: 0; box-shadow: none;
  opacity: 0;
  animation: ft-cycle-b 12s linear infinite;
}
.hero-phone img:first-child { opacity: 1; animation-name: ft-cycle-a; }
@keyframes ft-cycle-a { 0%, 42% { opacity: 1; } 50%, 92% { opacity: 0; } 100% { opacity: 1; } }
@keyframes ft-cycle-b { 0%, 42% { opacity: 0; } 50%, 92% { opacity: 1; } 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .hero-phone img { animation: none; }
  .hero-phone img:first-child { opacity: 1; }
}

/* services as four tiles, one amber */
.tiles { display: grid; gap: var(--s3); margin-top: var(--s6); list-style: none; padding: 0; }
.tile {
  display: grid; row-gap: var(--s2); align-content: start;
  padding: var(--s5);
  border-radius: 20px;
  background: var(--tile);
  border: 1px solid var(--paper-edge);
}
.tile p { color: var(--ink-soft); font-size: 16px; line-height: 1.6; max-width: 46ch; text-wrap: pretty; }
.tile.accent { background: var(--amber); border-color: transparent; color: var(--amber-ink); }
.tile.accent p { color: var(--amber-ink); }
.tile.accent a { color: inherit; font-weight: 600; text-underline-offset: 3px; }
@media (min-width: 600px) { .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s4); } }

/* pantry: screens on the left, words on the right */
@media (min-width: 900px) {
  .pantry-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); }
  .pantry-grid .shots { order: -1; }
}

/* game: an emblem and "TBD", nothing more */
.game-emblem { justify-self: center; width: min(240px, 70%); color: var(--navy-soft); }
.game-emblem svg { width: 100%; height: auto; display: block; }
.games-copy .minor-note { max-width: 40ch; }

@media (prefers-color-scheme: dark) {
  :root { --tile: #1E1915; }
  .tile { border-color: #3A3128; }
}

/* round 3: abstract hero animation (app → website), tighter work/pantry seam */
.hero-anim { width: 100%; max-width: 320px; margin-inline: auto; aspect-ratio: 320 / 300; }
.hero-anim svg { width: 100%; height: auto; display: block; overflow: visible; }
.sa, .sb, .sa-el, .sb-el { animation-duration: 12s; animation-timing-function: var(--ease-out); animation-iteration-count: infinite; animation-fill-mode: both; }
.sa    { animation-name: ft-scene-a; }
.sb    { animation-name: ft-scene-b; }
.sa-el { animation-name: ft-el-a; }
.sb-el { animation-name: ft-el-b; }
.sa-el:nth-of-type(3), .sb-el:nth-of-type(3) { animation-delay: .10s; }
.sa-el:nth-of-type(4), .sb-el:nth-of-type(4) { animation-delay: .20s; }
.sa-el:nth-of-type(5), .sb-el:nth-of-type(5) { animation-delay: .30s; }
.sa-el:nth-of-type(6), .sb-el:nth-of-type(6) { animation-delay: .40s; }
.sa-el:nth-of-type(7), .sb-el:nth-of-type(7) { animation-delay: .50s; }
@keyframes ft-scene-a { 0% { opacity: 0; } 4% { opacity: 1; } 45% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 0; } }
@keyframes ft-scene-b { 0%, 50% { opacity: 0; } 54% { opacity: 1; } 95% { opacity: 1; } 100% { opacity: 0; } }
@keyframes ft-el-a { 0% { opacity: 0; transform: translateY(10px); } 7% { opacity: 1; transform: none; } 44% { opacity: 1; } 49% { opacity: 0; } 100% { opacity: 0; } }
@keyframes ft-el-b { 0%, 50% { opacity: 0; transform: translateY(10px); } 57% { opacity: 1; transform: none; } 94% { opacity: 1; } 99% { opacity: 0; } 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .sa, .sb, .sa-el, .sb-el { animation: none; }
  .sa, .sa-el { opacity: 1; transform: none; }
  .sb { display: none; }
}

/* the work band and the Pantry band are both paper, so their two full paddings
   read as a hole. Tint Pantry as its own surface and close the seam. */
.work { padding-bottom: var(--s7); }
.pantry { background: var(--tile); padding-top: var(--s7); }

/* round 4: air under the hero copy; the animation floats on the amber instead
   of sitting in a box; nothing hangs into the next band any more */
.hero { padding-block: var(--s7) var(--s8); }
.slab { background: transparent; box-shadow: none; padding: 0; border-radius: 0; max-width: 440px; }
.hero-anim { max-width: 440px; }
@media (min-width: 900px) {
  .slab { margin-bottom: 0; align-self: center; }
  .hero-grid { align-items: center; }
}

/* round 5: bigger animation */
.slab, .hero-anim { max-width: 560px; }
@media (min-width: 900px) { .hero-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }

/* "what we make" — portfolio rows: the work, not the pitch */
.make { background: var(--tile); }
.made { display: grid; gap: var(--s6); margin-top: var(--s6); border-top: 2px solid var(--ink); padding-top: var(--s6); }
.made-item { display: grid; gap: var(--s4); align-items: start; }
.made-icon { width: 72px; height: 72px; border-radius: 16px; box-shadow: var(--lift-paper); }
.made-icon-tbd { display: grid; place-items: center; color: var(--ink-soft); box-shadow: none; }
.made-icon-tbd svg { width: 72px; height: 72px; }
.made-body { display: grid; gap: var(--s2); min-width: 0; }
.made-what { color: var(--ink-soft); max-width: 46ch; text-wrap: pretty; }
.made-role { display: grid; gap: var(--s1); margin-top: var(--s2); }
.made-role > div { display: grid; gap: 2px; padding-top: var(--s2); border-top: 1px solid var(--paper-edge); }
.made-role dt { font-size: 13.5px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-soft); }
.made-role dd { font-size: 16px; }
.made-link { margin-top: var(--s2); font-weight: 600; }
.made-link a { color: var(--amber-deep); text-underline-offset: 3px; }
@media (min-width: 600px) {
  .made-item { grid-template-columns: 72px minmax(0, 1fr); column-gap: var(--s4); }
  .made-role { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s4); }
}
@media (min-width: 900px) { .made { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s7); } }
@media (prefers-color-scheme: dark) {
  .made-role > div { border-top-color: #3A3128; }
  .made-link a { color: var(--amber); }
}

/* apps hub: the shelf (games live on their own page) */
.shelf { background: var(--navy-deep); color: var(--paper); }
.shelf .work-head h2, .shelf .work-head .body-lg { color: var(--paper); }
.shelf-card {
  display: grid; gap: var(--s4); align-items: center;
  margin-top: var(--s6); padding: var(--s5);
  border: 1px solid var(--navy-line); border-radius: 20px;
  background: rgba(255,255,255,.04); text-decoration: none; color: inherit;
  transition: background-color var(--dur-state) var(--ease-quick),
              transform var(--dur-state) var(--ease-quick);
}
.shelf-card:hover { background: rgba(255,255,255,.08); transform: translateY(var(--lift)); }
.shelf-mark { color: var(--navy-soft); }
.shelf-mark svg { width: 72px; height: 72px; }
.shelf-body { display: grid; gap: 4px; min-width: 0; }
.shelf-body strong { font-family: var(--serif); font-weight: 600; font-size: clamp(20px, 1.8vw, 26px); }
.shelf-body span { color: var(--navy-soft); font-size: 16px; }
@media (min-width: 600px) { .shelf-card { grid-template-columns: 72px minmax(0, 1fr); } }

/* the game's own page */
.game-page { background: var(--navy-deep); }
.game-page .site-head { background: var(--navy-deep); border-bottom-color: var(--navy-line); }
.game-page .head-nav a { color: var(--navy-soft); }
.game-hero { background: var(--navy-deep); color: var(--paper); }
.game-hero-grid { display: grid; gap: var(--s7); align-items: center; }
.game-hero .kicker {
  font-size: 13.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--amber); margin-bottom: var(--s3);
}
.game-hero .lede { color: var(--navy-soft); margin-top: var(--s4); }
.game-facts { background: var(--navy); color: var(--paper); }
.game-facts .spec dd { color: var(--paper); }
.game-note { margin-top: var(--s6); color: var(--navy-soft); }
@media (min-width: 900px) {
  .game-hero-grid { grid-template-columns: minmax(0, 1.2fr) minmax(0, .8fr); }
  .game-facts .spec { columns: 2; column-gap: var(--s7); }
}


/* text that sits on the fixed navy bands must NOT use --paper: that token flips
   to near-black in dark mode, which made these invisible. */
:root { --on-navy: #F3EADB; }
.shelf { color: var(--on-navy); }
.shelf .work-head h2, .shelf .work-head .body-lg { color: var(--on-navy); }
.game-hero, .game-facts { color: var(--on-navy); }
.game-hero h1, .game-facts .spec dd { color: var(--on-navy); }
.game-facts .spec dt, .game-hero .lede, .game-note, .shelf-body span { color: var(--navy-soft); }


/* ── sticky header ───────────────────────────────────────────────────────── */
.site-head {
  position: sticky;
  top: 0;
  z-index: 50;
}
/* the bar is 96px because the logo is a 3-line lockup; that is too much of a
   phone viewport to keep pinned, so it tightens below 900. */
@media (max-width: 899px) {
  .head-bar { padding-block: 10px; }
  .brand-logo { width: auto; height: 40px; }
}
@media (max-width: 599px) {
  .brand-logo { height: 34px; }
  .head-nav { gap: var(--s2); }
  .head-nav a { font-size: 14.5px; }
}
@media (prefers-reduced-motion: no-preference) {
  .site-head { transition: box-shadow var(--dur-state) var(--ease-quick); }
}


/* ── match the studio's band rhythm: amber, paper, tile, ink ─────────────── */
.apps-band { background: var(--paper); }
.shelf { background: var(--tile); color: var(--ink); }
.shelf .work-head h2, .shelf .work-head .body-lg { color: inherit; }
.shelf .work-head .body-lg { color: var(--ink-soft); }
.shelf-card {
  border-color: var(--paper-edge);
  background: rgba(44, 37, 33, .03);
}
.shelf-card:hover { background: rgba(44, 37, 33, .06); }
.shelf-mark { color: var(--ink-soft); }
.shelf-body span { color: var(--ink-soft); }
@media (prefers-color-scheme: dark) {
  .shelf-card { border-color: #3A3128; background: rgba(255,255,255,.04); }
  .shelf-card:hover { background: rgba(255,255,255,.07); }
}


/* the sticky bar must stay shallow on phones: the stacked layout at <600 made
   it 136px, a fifth of a 740px viewport, once the header became sticky. */
@media (max-width: 599px) {
  .head-bar {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--s2);
    min-height: 0;
    padding-block: 8px;
  }
  .brand-logo { height: 30px; }
  .head-nav { flex-wrap: nowrap; gap: 10px; }
  .head-nav a { font-size: 13px; letter-spacing: 0; white-space: nowrap; padding-block: 10px; }
}
@media (max-width: 379px) {
  .head-nav a { font-size: 12.5px; }
  .head-nav { gap: 8px; }
}

/* ── product showcase: each product brings its own accent ────────────────── */
:root { --pantry: #043B2B; --pantry-ink: #E7F2EC; }
.eyebrow { font-size: 13px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: var(--s4); }
.eyebrow-light { color: var(--ink-soft); }

.showcase {
  display: grid; gap: var(--s7);
  border-radius: 24px; overflow: hidden;
  background: var(--pantry); color: var(--pantry-ink);
  padding: var(--s7) var(--s6) 0;
}
.showcase-copy { display: grid; gap: var(--s3); align-content: start; }
.showcase-icon { width: 64px; height: 64px; border-radius: 14px; box-shadow: var(--lift-navy); }
.showcase h2 { color: var(--pantry-ink); margin-top: var(--s2); }
.showcase-what { color: rgba(231,242,236,.78); max-width: 44ch; text-wrap: pretty; font-size: 17.5px; line-height: 1.6; }
.showcase-meta { display: grid; gap: var(--s2); margin-top: var(--s4); border-top: 1px solid rgba(231,242,236,.22); padding-top: var(--s4); }
.showcase-meta > div { display: grid; grid-template-columns: minmax(84px, 26%) minmax(0, 1fr); gap: var(--s3); align-items: baseline; }
.showcase-meta dt { font-size: 12.5px; letter-spacing: .06em; text-transform: uppercase; color: rgba(231,242,236,.6); }
.showcase-meta dd { font-size: 16px; }
.showcase-links { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s4); margin-top: var(--s5); padding-bottom: var(--s7); }
.btn-pantry { background: var(--pantry-ink); color: var(--pantry); border-color: var(--pantry-ink); }
.btn-pantry:hover { background: #fff; border-color: #fff; }
.showcase-links .plain-link { color: rgba(231,242,236,.85); font-weight: 600; text-underline-offset: 4px; }
.showcase-shots { position: relative; display: flex; justify-content: center; align-items: flex-end; gap: var(--s4); padding-inline: var(--s3); }
.showcase-shots .phone { width: 52%; max-width: 230px; box-shadow: 0 -8px 40px -12px rgba(0,0,0,.6); }
.showcase-shots .phone-back { width: 44%; max-width: 196px; margin-bottom: var(--s6); opacity: .92; }

/* ── games: cinematic card ───────────────────────────────────────────────── */
.cinema {
  display: grid; overflow: hidden; border-radius: 24px;
  background: #0B1220; color: #E8ECF5; text-decoration: none;
  border: 1px solid #22304C;
  transition: transform var(--dur-state) var(--ease-quick), border-color var(--dur-state) var(--ease-quick);
}
.cinema:hover { transform: translateY(var(--lift)); border-color: #38496F; }
.cinema-art { display: block; }
.cinema-art svg { width: 100%; height: 100%; min-height: 180px; display: block; }
.cinema-body { display: grid; gap: var(--s2); padding: var(--s6); align-content: center; }
.cinema-body strong { font-family: var(--serif); font-weight: 600; font-size: clamp(22px, 2.2vw, 30px); }
.cinema-body > span { color: #9FB0CC; max-width: 46ch; text-wrap: pretty; }
.cinema-go { font-weight: 600; color: var(--amber); }

@media (min-width: 900px) {
  .showcase { grid-template-columns: minmax(0, 1fr) minmax(0, .9fr); align-items: end; padding: var(--s8) var(--s7) 0; }
  .cinema { grid-template-columns: minmax(0, 1fr) minmax(0, .9fr); align-items: stretch; }
  .cinema-art svg { min-height: 240px; }
}
@media (prefers-color-scheme: dark) {
  .showcase { background: #062A1F; }
  .cinema { background: #080E1A; }
}

/* game page: oversized headline, compact metadata, a little atmosphere */
.game-hero { position: relative; overflow: hidden; }
.game-hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(60% 80% at 78% 18%, rgba(241,174,72,.14), transparent 60%),
    radial-gradient(70% 60% at 10% 100%, rgba(42,56,86,.55), transparent 65%);
}
.game-hero > * { position: relative; }
.game-hero h1 { font-size: clamp(46px, 7.4vw, 92px); letter-spacing: -0.03em; max-width: 14ch; }
.game-facts .spec-row { padding-block: 14px; }
.game-facts .spec dd { font-size: clamp(17px, 1.3vw, 20px); }

/* ═══ library hero: a title, one line, and the catalogue itself ══════════ */
h2.eyebrow { font-family: var(--sans); font-weight: 500; line-height: 1.4; }
.lib-hero { background: var(--paper); padding-block: var(--s7) var(--s6); }
.lib-grid { display: grid; gap: var(--s6); align-items: end; }
.lib-copy .eyebrow { margin-bottom: var(--s3); }
.lib-copy h1 { font-size: clamp(54px, 8vw, 108px); letter-spacing: -0.035em; line-height: .95; }
.lib-copy .lede { margin-top: var(--s4); color: var(--ink-soft); max-width: 36ch; }

/* the shelf list: the same card language as the rest of the site (tile,
   hairline border, soft lift), rows that read like an app list, not a table */
.catalogue {
  justify-self: stretch; width: 100%; max-width: 480px;
  background: var(--tile); border: 1px solid var(--paper-edge); border-radius: 20px;
  padding: var(--s2) var(--s2) var(--s2);
  box-shadow: 0 1px 0 rgba(28,22,16,.04), 0 20px 40px -30px rgba(28,22,16,.5);
}
.cat-label {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: var(--s2) var(--s3) var(--s2); margin: 0;
  font-size: 12px; letter-spacing: .09em; text-transform: uppercase; color: var(--ink-soft);
}
.cat-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.cat-row {
  display: grid; grid-template-columns: 44px minmax(0, 1fr) auto 14px; align-items: center;
  gap: var(--s3); padding: 10px var(--s3); border-radius: 14px;
  color: var(--ink); text-decoration: none;
  transition: background-color var(--dur-state) var(--ease-quick);
}
.cat-list li + li .cat-row { box-shadow: 0 -1px 0 var(--paper-edge); }
.cat-row:hover { background: var(--paper); box-shadow: none; }
.cat-list li:hover + li .cat-row { box-shadow: none; }
.cat-row:focus-visible { outline: 2px solid var(--amber-deep); outline-offset: 1px; }
.cat-icon { width: 44px; height: 44px; border-radius: 11px; box-shadow: 0 4px 10px -6px rgba(28,22,16,.5); }
.cat-icon-game { display: grid; place-items: center; background: var(--navy-deep); color: var(--navy-soft); }
.cat-icon-game svg { width: 30px; height: 30px; }
.cat-text { display: grid; gap: 1px; min-width: 0; }
.cat-title { font-family: var(--serif); font-weight: 600; font-size: 18px; line-height: 1.25; }
.cat-kind { font-size: 13.5px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cat-status {
  font-size: 12.5px; font-weight: 600; white-space: nowrap; padding: 4px 10px; border-radius: 999px;
  background: rgba(44,37,33,.07); color: var(--ink-soft);
}
.cat-live { background: rgba(46,139,87,.14); color: #1F6B42; }
.cat-go { width: 8px; height: 8px; border-right: 2px solid var(--ink-soft); border-top: 2px solid var(--ink-soft);
  transform: rotate(45deg); opacity: .5; transition: transform var(--dur-state) var(--ease-quick), opacity var(--dur-state) var(--ease-quick); }
.cat-row:hover .cat-go { transform: translateX(3px) rotate(45deg); opacity: 1; }
@media (max-width: 599px) {
  .cat-row { grid-template-columns: 40px minmax(0, 1fr) auto; }
  .cat-icon { width: 40px; height: 40px; }
  .cat-go { display: none; }
}
@media (min-width: 900px) {
  .lib-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s7); align-items: center; }
  .catalogue { justify-self: end; }
}

/* ═══ apps: compact cards, so the shelf can hold more than one ═══════════ */
.apps-band { padding-top: var(--s5); }
.app-grid { display: grid; gap: var(--s4); }
.app-card {
  position: relative; overflow: hidden; isolation: isolate;
  background: var(--pantry); color: var(--pantry-ink);
  border-radius: 22px; padding: var(--s6) var(--s5);
  min-height: 320px;
  transition: transform var(--dur-state) var(--ease-quick), box-shadow var(--dur-state) var(--ease-quick);
}
.app-card-copy { position: relative; z-index: 1; display: grid; gap: var(--s3); max-width: 31ch; }
.app-id { display: flex; align-items: center; gap: var(--s3); }
.app-icon { width: 48px; height: 48px; border-radius: 11px; box-shadow: 0 6px 14px -6px rgba(0,0,0,.5); }
.app-wordmark { width: auto; height: 34px; }
.app-what { color: rgba(231,242,236,.82); font-size: 16.5px; line-height: 1.55; text-wrap: pretty; }
.app-chips { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; padding: 0; margin: 0; }
.app-chips li {
  font-size: 13px; padding: 4px 11px; border-radius: 999px;
  background: rgba(231,242,236,.1); color: rgba(231,242,236,.9);
}
.app-links { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s4); margin-top: var(--s2); }
.app-links .plain-link { color: rgba(231,242,236,.85); font-weight: 600; text-underline-offset: 4px; }

/* the phone sits low in the corner and comes up to meet the pointer */
.app-peek {
  position: absolute; z-index: 0; right: var(--s5); bottom: 0; width: 38%; max-width: 190px;
  border-radius: 20px 20px 0 0; overflow: hidden;
  box-shadow: 0 0 0 1px rgba(255,255,255,.12), 0 -12px 34px -14px rgba(0,0,0,.6);
  transform: translateY(44%) rotate(-4deg); transform-origin: 50% 100%;
  transition: transform 520ms var(--ease-out);
}
.app-peek img { width: 100%; height: auto; display: block; }
.app-card:hover { transform: translateY(-3px); box-shadow: 0 22px 40px -24px rgba(4,59,43,.7); }
.app-card:hover .app-peek, .app-card:focus-within .app-peek { transform: translateY(26%) rotate(0deg); }

.app-next {
  display: grid; align-content: center; gap: var(--s3);
  border: 1.5px dashed var(--paper-edge); border-radius: 22px;
  padding: var(--s6) var(--s5); color: var(--ink-soft);
}
.app-next p { max-width: 30ch; font-size: 15.5px; line-height: 1.55; text-wrap: pretty; }
.app-next strong { display: block; color: var(--ink); font-family: var(--serif); font-weight: 600; font-size: 19px; margin-bottom: 4px; }
.app-next-mark { width: 30px; height: 30px; position: relative; }
.app-next-mark::before, .app-next-mark::after {
  content: ""; position: absolute; left: 50%; top: 50%; background: var(--paper-edge);
  width: 22px; height: 2px; transform: translate(-50%, -50%);
}
.app-next-mark::after { transform: translate(-50%, -50%) rotate(90deg); }

@media (max-width: 599px) {
  .app-card { padding-bottom: 210px; }
  .app-peek { width: 46%; right: 50%; translate: 50% 0; transform: translateY(30%) rotate(-3deg); }
  .app-card:hover .app-peek { transform: translateY(18%) rotate(0deg); }
}
@media (min-width: 700px) {
  .app-grid { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); }
  .app-card-copy { max-width: 58%; }
}

/* the card arrives once, as a card: a short rise with a slight tilt settling */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .app-card, .app-next {
      animation: ft-card-in linear both;
      animation-timeline: view(); animation-range: entry 0% entry 70%;
    }
    .app-next { animation-range: entry 8% entry 78%; }
    .catalogue { animation: ft-card-in 640ms var(--ease-out) 160ms backwards; }
  }
}
@keyframes ft-card-in {
  from { opacity: 0; transform: translateY(26px) rotate(-1.2deg) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .app-card, .app-peek, .app-next, .catalogue { transition: none; animation: none; }
}
@media (prefers-color-scheme: dark) {
  .app-card { background: #062A1F; }
  .catalogue { border-color: #3A3128; box-shadow: none; }
  .cat-list li + li .cat-row { box-shadow: 0 -1px 0 #3A3128; }
  .cat-row:hover { background: rgba(255,255,255,.05); }
  .cat-status { background: rgba(255,255,255,.07); }
  .cat-live { background: rgba(76,195,138,.14); color: #6FD3A2; }
  .cat-icon-game { background: #0B1220; }
  .app-next { border-color: #4A3F33; }
  .app-next-mark::before, .app-next-mark::after { background: #5A4B37; }
}

/* ═══ contact form: no JavaScript, so the browser does the validating ════ */
/* The band is near-black in both themes, so the controls use fixed colours
   rather than tokens that flip. */
.contact-grid { display: grid; gap: var(--s6); align-items: start; }
.contact > .wrap > .contact-grid { animation: none; }
.contact-intro .minor-note { max-width: 40ch; }
.contact-alt { margin-top: var(--s4); font-size: 15px; color: var(--foot-soft); }
.contact-alt a { color: var(--amber); font-weight: 600; text-underline-offset: 4px; }

.enquiry {
  --f-text: #F3EADB; --f-soft: #A89A84; --f-line: #4A3F33; --f-fill: rgba(255,255,255,.045);
  display: grid; gap: var(--s4);
  background: #17120D; border: 1px solid #2E261D; border-radius: 22px;
  padding: var(--s5); color: var(--f-text);
}
.q { border: 0; margin: 0; padding: 0; min-width: 0; }
.q legend, .field-label {
  display: block; padding: 0; margin-bottom: var(--s2);
  font-size: 15px; font-weight: 600; color: var(--f-text);
}
.field-label em, .q legend em { font-style: normal; font-weight: 400; color: var(--f-soft); margin-left: 4px; font-size: 13.5px; }
.field { display: grid; min-width: 0; }
.field-hint { margin: -4px 0 var(--s2); font-size: 14px; line-height: 1.5; color: var(--f-soft); }
.pair { display: grid; gap: var(--s4); }

.field input, .field textarea, .select select {
  width: 100%; font: inherit; font-size: 16px; color: var(--f-text);   /* 16px: iOS zooms below it */
  background: var(--f-fill); border: 1px solid var(--f-line); border-radius: 12px;
  padding: 12px 14px; line-height: 1.4;
  transition: border-color var(--dur-state) var(--ease-quick), background-color var(--dur-state) var(--ease-quick);
}
.field textarea { resize: vertical; min-height: 9.5em; }
.field input:hover, .field textarea:hover, .select select:hover { border-color: #6A5A44; }
.field input:focus, .field textarea:focus, .select select:focus {
  outline: none; border-color: var(--amber); background: rgba(255,255,255,.07);
  box-shadow: 0 0 0 3px rgba(241,174,72,.22);
}
/* only after someone has actually touched a field, never on page load */
.field input:user-invalid, .field textarea:user-invalid, .select select:user-invalid { border-color: #E07A5F; }

/* selects: drawn chevron, since the CSP will not load a data: image */
.select { position: relative; display: block; }
.select select { appearance: none; -webkit-appearance: none; padding-right: 40px; cursor: pointer; }
.select select option { color: #1B1510; background: #F3EADB; }
.select::after {
  content: ""; position: absolute; right: 16px; top: 50%; width: 8px; height: 8px;
  border-right: 2px solid var(--f-soft); border-bottom: 2px solid var(--f-soft);
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}

/* the first question is chips, so the form starts with a tap, not a blank box */
.pills { display: flex; flex-wrap: wrap; gap: 8px; }
.pills label { position: relative; cursor: pointer; }
.pills input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.pills span {
  display: inline-block; padding: 9px 16px; border-radius: 999px; font-size: 15px;
  border: 1px solid var(--f-line); background: var(--f-fill); color: var(--f-text);
  transition: background-color var(--dur-state) var(--ease-quick), border-color var(--dur-state) var(--ease-quick),
              color var(--dur-state) var(--ease-quick), transform var(--dur-state) var(--ease-quick);
}
.pills label:hover span { border-color: #6A5A44; }
.pills input:checked + span { background: var(--amber); border-color: var(--amber); color: #1B1510; font-weight: 600; }
.pills input:focus-visible + span { outline: 2px solid var(--amber); outline-offset: 3px; }
.pills input:active + span { transform: scale(.97); }

/* the trap: off-screen for people, still a real field for a bot */
.hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }

.send-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3) var(--s4); margin-top: var(--s2); }
.send-row .btn { border: 0; cursor: pointer; font: inherit; font-weight: 600; }
.send-row p { font-size: 13.5px; color: var(--f-soft); }

@media (min-width: 600px) {
  .pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .enquiry { padding: var(--s6); }
}
@media (min-width: 900px) {
  .contact-grid { grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); gap: var(--s7); }
  .contact-intro { position: sticky; top: 120px; }
}
@media (prefers-reduced-motion: reduce) {
  .pills span, .field input, .field textarea, .select select { transition: none; }
}

/* ── the two static pages the form lands on ── */
.landed { min-height: 62vh; display: grid; align-content: center; }
.landed h1 { font-size: clamp(40px, 6vw, 72px); max-width: 14ch; }
.landed .lede { margin-top: var(--s4); max-width: 42ch; color: var(--ink-soft); }
.landed .btn-row { margin-top: var(--s5); }
.field input::placeholder { color: #7A6C58; }

/* the game page's one-field sign-up: same form, narrower */
.enquiry-slim { max-width: 560px; }
