/* The Letterman
   Built to brand/brand-guidelines.md, 2026-09-17. Eight colors, two typefaces,
   one button shape.

   Ground strategy: obsidian is the page, espresso is the step above it. Anything
   you READ sits on an inset cream sheet with obsidian still framing it, so the
   site reads warm and bright without going clinical. Cream is never full-bleed,
   and pure white is not in the palette at all.

   Weighting, from the guidelines: darks 60, browns 20, creams 15, gold 5. Gold
   is a hairline, a rule or a mark. It never fills anything.

   Zero box-shadows. 300px is the only non-zero radius. */

:root {
  /* darks, 60% */
  --obsidian: #17110D;   /* the page */
  --espresso: #24160D;   /* the step above it: nav, ticker, cards, buttons */
  /* browns, 20% */
  --walnut:   #4C2F1C;   /* panels and picture mats */
  --tobacco:  #6B553A;   /* mid-tone rules */
  /* creams, 15% */
  --cream:    #F4EBDD;   /* ink on dark, and the reading sheet */
  --champagne:#E8D9B9;   /* supporting text: kickers, labels, the tagline */
  /* gold, 5% */
  --gold:     #C6B289;   /* hairlines, rules, the fillet. never a fill */
  /* and one restrained accent */
  --burgundy: #3A1D19;   /* a detail rule, never a ground */

  /* derived tints. not new brand colors, just opacity on the eight above */
  --on-dark-muted:  rgba(244, 235, 221, .72);
  --on-cream-muted: rgba(23, 17, 13, .74);
  --hairline:       rgba(244, 235, 221, .22);

  --display: "Anton", "Haettenschweiler", "Arial Narrow", sans-serif;
  --read: "Libre Caslon Text", Georgia, "Times New Roman", serif;

  --radius: 10px;      /* every picture. the hero is the one exception */
  --nav-h: 5.5rem;     /* fixed, so content and anchors can clear it exactly */

  --gutter: clamp(1.25rem, 4vw, 3.5rem);
  --sheet: 1120px;
  --band-y: clamp(3.5rem, 8vw, 7rem);
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: var(--nav-h); }

body {
  margin: 0;
  background: var(--obsidian);
  color: var(--cream);
  font-family: var(--read);
  font-size: clamp(1rem, .96rem + .2vw, 1.0625rem);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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

a { color: inherit; }

/* Antique Gold is 1.6:1 on cream, so it cannot carry a focus ring there. Focus
   is gold on obsidian and obsidian on any cream sheet. */
:focus-visible { outline: 3px solid var(--gold); outline-offset: 3px; }
.sheet :focus-visible { outline-color: var(--obsidian); }

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--cream); color: var(--obsidian);
  padding: .75rem 1.25rem; z-index: 100;
  font-family: var(--display); text-transform: uppercase; letter-spacing: .14em;
}
.skip:focus { left: 0; }

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

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 400;          /* Anton ships one weight. Hierarchy is size only */
  text-transform: uppercase;
  line-height: .96;
  letter-spacing: .01em;
  margin: 0;
}

h2 { font-size: min(clamp(2rem, 1.3rem + 2.6vw, 3.5rem), 16svh); }
h3 { font-size: clamp(1.15rem, 1.05rem + .45vw, 1.5rem); letter-spacing: .03em; }

.kicker {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .75rem;
  letter-spacing: .24em;
  color: var(--champagne);
  margin: 0 0 1rem;
}

.lede {
  font-size: clamp(1.1rem, 1rem + .5vw, 1.375rem);
  line-height: 1.45;
  margin: 0 0 1rem;
}

.muted { color: var(--on-dark-muted); }
.sheet .muted, .sheet.muted { color: var(--on-cream-muted); }

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

   One nav, same markup on all four pages, fixed to the viewport. Two states.

   HOME is clear over the hero and settles into a smoked bar once you scroll,
   which is the behaviour the published site gets from a JS scroll listener.
   Here it runs on a CSS scroll timeline instead, so the build stays free of
   JavaScript.

   THE OTHER THREE have no hero to show through, so there is nothing for a clear
   nav to sit on. They carry `.nav--settled` and stay frozen in the scrolled
   look. Asked for by Dylan, 2026-09-15.

   The fallback matters: white nav text over a white menu panel would be
   unreadable, so browsers without scroll-driven CSS get the smoked bar
   permanently rather than a transparent one.                                   */

.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  height: var(--nav-h);
  display: flex; align-items: center; gap: clamp(1rem, 3vw, 2.5rem);
  padding: 0 var(--gutter);
  border-bottom: 1px solid transparent;
  background: rgba(36, 22, 13, .55);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

@supports (animation-timeline: scroll()) {
  .nav:not(.nav--settled) {
    background: transparent;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    animation: nav-settle linear both;
    animation-timeline: scroll(root block);
    animation-range: 0 140px;
  }
}

@keyframes nav-settle {
  to {
    background: rgba(36, 22, 13, .86);
    -webkit-backdrop-filter: blur(14px) saturate(1.2);
    backdrop-filter: blur(14px) saturate(1.2);
    border-bottom-color: rgba(198, 178, 137, .20);
  }
}

/* The same values the keyframe lands on, held from the first pixel. Kept
   literal rather than shared with the keyframe: a scroll-driven animation and a
   static rule cannot read from one source, and a mismatch would show as the
   home nav changing colour when you click through to a page. */
.nav--settled {
  background: rgba(36, 22, 13, .86);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  border-bottom-color: rgba(198, 178, 137, .20);
}

/* The cursive TL, inline so it inherits the nav's own colour exactly. */
.nav__mark { display: flex; color: var(--cream); }
.nav__mark svg { width: 40px; height: 40px; display: block; }

/* The toggle must stay keyboard focusable, so it is moved out of sight rather
   than display:none. Space toggles it and the ring lands on the burger. */
.nav__toggle {
  position: absolute; width: 1px; height: 1px;
  opacity: 0; margin: 0; pointer-events: none;
}
.nav__burger { display: none; }

.nav__panel { margin-left: auto; }
.nav__links { display: flex; gap: clamp(.9rem, 2.2vw, 2rem); align-items: center; }

.nav__links a:not(.btn) {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .8125rem;
  letter-spacing: .16em;
  text-decoration: none;
  color: var(--cream);
  padding: .35rem 0;
  border-bottom: 1px solid transparent;
}
.nav__links a:not(.btn):hover,
.nav__links a:not(.btn)[aria-current="page"] { border-bottom-color: var(--gold); }
.nav__cta { padding: .7rem 1.4rem; }

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

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 300px;                 /* the only non-zero radius on the site */
  border: 1px solid transparent;
  padding: .95rem 1.75rem;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: .8125rem;
  letter-spacing: .16em;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
}

/* Gold is 5% of the composition, so a button is never filled with it. The
   primary sits a step above the ground in espresso, carries cream lettering and
   is drawn by a gold hairline. The secondary drops the fill entirely. */
/* on obsidian */
.btn--solid { background: var(--espresso); color: var(--cream); border-color: var(--gold); }
.btn--ghost { border-color: var(--cream); color: var(--cream); }
/* on cream */
.sheet .btn--solid { background: var(--obsidian); color: var(--cream); border-color: var(--gold); }
.sheet .btn--ghost { border-color: var(--obsidian); color: var(--obsidian); }
/* Tier keeps its own class so the markup still says what a button is for. */
.btn--tier { background: var(--espresso); color: var(--cream); border-color: var(--gold); }
.sheet .btn--tier { background: var(--obsidian); color: var(--cream); border-color: var(--gold); }

.btn:hover { opacity: .88; }

.actions { display: flex; flex-wrap: wrap; gap: .75rem; }

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

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr;
  overflow: hidden;
  background: var(--obsidian);
}

.hero__media { position: absolute; inset: 0; }
.hero__media picture { display: block; width: 100%; height: 100%; }
.hero__media img { width: 100%; height: 100%; object-fit: cover; }

/* Type over a photo always gets an obsidian scrim beneath it. Guidelines s6 */
.hero__media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(to right, rgba(23,17,13,.92) 0%, rgba(23,17,13,.74) 28%, rgba(23,17,13,.18) 62%, rgba(23,17,13,.06) 100%),
    linear-gradient(to bottom, rgba(23,17,13,.55) 0%, rgba(23,17,13,0) 26%, rgba(23,17,13,0) 62%, rgba(23,17,13,.86) 100%);
}

/* Narrow viewports get a different scrim. The copy column is full width there,
   so a left-to-right wash leaves the buttons on the right sitting over whatever
   the photograph is doing. Below 820px the scrim runs bottom-up instead, which
   is where the copy actually is. */
@media (max-width: 819px) {
  .hero__media::after {
    background:
      linear-gradient(to bottom, rgba(23,17,13,.62) 0%, rgba(23,17,13,.14) 22%, rgba(23,17,13,.54) 42%, rgba(23,17,13,.93) 100%);
  }
}

.hero__inner {
  position: relative; z-index: 10;
  grid-row: 2;
  padding-bottom: clamp(2rem, 5vw, 4rem);
  display: flex; flex-direction: column; justify-content: center;
  padding: var(--nav-h) var(--gutter) 0;
  max-width: 44rem;
}

/* Display size gets a height term, never width alone. house/gates.md */
.hero__mark {
  width: min(660px, 84vw);
  max-height: 16svh;
  height: auto;
  object-fit: contain;
  object-position: left center;
  margin-bottom: clamp(.75rem, 2vw, 1.25rem);
}

/* The tagline. The guidelines give the brand signature a restrained, generously
   spaced uppercase serif, so it takes the reading face rather than Anton, and it
   sits directly under the wordmark it belongs to. */
.hero__tag {
  font-family: var(--read);
  text-transform: uppercase;
  font-size: clamp(.78rem, .68rem + .38vw, 1rem);
  letter-spacing: .34em;
  color: var(--champagne);
  margin: 0 0 .7rem;
}

.hero__lede {
  font-size: clamp(1.05rem, .98rem + .5vw, 1.3rem);
  line-height: 1.45;
  margin: 0 0 clamp(1.25rem, 3vw, 1.85rem);
  max-width: 26ch;
}

/* ---------------------------------------------------------------- ticker */

.ticker {
  border-top: 1px solid var(--tobacco);
  border-bottom: 1px solid var(--tobacco);
  overflow: hidden;
  background: var(--espresso);
}

/* Two identical halves, translated exactly -50%. That is what makes it
   seamless: the second half is under the cursor the instant the first leaves. */
.ticker__track {
  display: flex;
  width: max-content;
  animation: ticker 44s linear infinite;
}
.ticker__half { display: flex; flex: none; }

.ticker__half span {
  font-family: var(--display);
  text-transform: uppercase;
  font-size: clamp(.8rem, .74rem + .3vw, 1rem);
  letter-spacing: .18em;
  padding: .85rem clamp(1.1rem, 2.4vw, 2rem);
  display: inline-flex; align-items: center; gap: clamp(1.1rem, 2.4vw, 2rem);
  white-space: nowrap;
}
.ticker__half span::after { content: "\2733"; color: var(--gold); font-size: .7em; }

@keyframes ticker { to { transform: translateX(-50%); } }

/* ---------------------------------------------------------------- bands */

.band { padding: var(--band-y) var(--gutter); }
.band--cream { background: var(--obsidian); }   /* the frame stays obsidian */

.inner { max-width: var(--sheet); margin-inline: auto; }

/* The inset reading sheet. Cream, but always with obsidian around it. */
.sheet {
  background: var(--cream);
  color: var(--obsidian);
  max-width: var(--sheet);
  margin-inline: auto;
  padding: clamp(2rem, 5vw, 4rem) clamp(1.25rem, 4vw, 3.5rem);
  border-radius: var(--radius);
}
.sheet .kicker { color: var(--obsidian); }
.sheet h2, .sheet h3 { color: var(--obsidian); }

/* The walnut panel. Same device as the cream sheet, in the dark value, so a
   reading section is lifted off the obsidian instead of sitting bare on it.
   Dark Walnut is an approved ground in the current guidelines, section 3: it is
   no longer only a picture mat. */
.sheet--walnut { background: var(--walnut); color: var(--cream); }
.sheet--walnut .kicker { color: var(--champagne); }
.sheet--walnut h2, .sheet--walnut h3 { color: var(--cream); }
.sheet--walnut .muted { color: var(--on-dark-muted); }
.sheet--walnut .btn--solid { background: var(--obsidian); color: var(--cream); border-color: var(--gold); }
.sheet--walnut .btn--ghost { border-color: var(--cream); color: var(--cream); }
.sheet--walnut :focus-visible { outline-color: var(--gold); }
/* A walnut mat is invisible on a walnut panel, so the gold fillet frames alone. */
.sheet--walnut .framed { background: transparent; padding: 0; }
.sheet--walnut .framed > img { border-radius: var(--radius); }

/* ---------------------------------------------------------------- frames

   A small walnut mat with a gold fillet, the way a real frame separates the
   picture from the moulding. Walnut is a close value to obsidian, so the mat
   alone would not read. The fillet is what draws the edge.                    */

.framed {
  background: var(--walnut);
  padding: clamp(.4rem, .8vw, .6rem);
  border-radius: var(--radius);
  margin: 0;
}
.framed > img {
  display: block;
  width: 100%;
  border-radius: calc(var(--radius) - 4px);
  border: 1px solid rgba(198, 178, 137, .42);   /* the fillet */
}
.framed--half { max-width: min(380px, 62%); margin: 0; }
.framed--half > img { height: auto; }

/* About: the framed jacket beside the prose. h2 carries margin:0 from the type
   reset, so the layout has to supply the gap or the heading sits on the photo. */
.about-layout {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.5rem);
  margin-top: clamp(1.75rem, 4vw, 2.75rem);
}
@media (min-width: 860px) {
  .about-layout {
    grid-template-columns: 380px 1fr;
    gap: clamp(2rem, 4vw, 3.5rem);
    align-items: start;
  }
  .about-layout .framed--half { max-width: 380px; }
}

/* ---------------------------------------------------------------- home: the two menus */

.menus { display: grid; gap: clamp(1rem, 2vw, 1.5rem); grid-template-columns: 1fr; }
@media (min-width: 760px) { .menus { grid-template-columns: 1fr 1fr; } }

.menu-card {
  position: relative;
  display: flex; flex-direction: column; justify-content: flex-end;
  min-height: clamp(19rem, 34vw, 26rem);
  padding: clamp(1.5rem, 3vw, 2.25rem);
  overflow: hidden;
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--cream);
  background: var(--espresso);
}
.menu-card img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  object-position: center 82%;   /* the dish sits low in frame; crop to it */
  z-index: 0;                      /* stays under ::after. see the rule below */
}
.menu-card::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top,
    rgba(23,17,13,.93) 0%, rgba(23,17,13,.88) 36%, rgba(23,17,13,.52) 56%,
    rgba(23,17,13,.16) 76%, rgba(23,17,13,.04) 100%);
}
/* The scrim is ::after at z-index auto, so only the TEXT gets lifted above it.
   Including the img here is what made the lettering unreadable. */
.menu-card > *:not(img) { position: relative; z-index: 2; }
.menu-card::after { z-index: 1; }
.menu-card h3 {
  font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.5rem);
  padding-top: .9rem;
  border-top: 1px solid var(--gold);
}
.menu-card p { margin: .5rem 0 0; color: var(--on-dark-muted); max-width: 30ch; }
.menu-card span {
  margin-top: 1.1rem;
  font-family: var(--display); text-transform: uppercase;
  font-size: .75rem; letter-spacing: .18em; color: var(--champagne);
}
.menu-card:hover img { transform: scale(1.03); }
.menu-card img { transition: transform .5s ease; }

/* ---------------------------------------------------------------- private band */

.private { display: grid; gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
@media (min-width: 860px) { .private { grid-template-columns: 1.05fr .95fr; } }
.private__media img { width: 100%; aspect-ratio: 4 / 3; height: auto; object-fit: cover; }

/* ---------------------------------------------------------------- menu page */

.menu-head { padding: calc(var(--nav-h) + clamp(1rem, 3vw, 2rem)) var(--gutter) 0; }
/* the standfirst under a page title sits a step below body lede size */
.menu-head .lede { font-size: clamp(.95rem, .9rem + .3vw, 1.125rem); }
.menu-head h2 { font-size: min(clamp(1.9rem, 1.25rem + 2.2vw, 3rem), 14svh); }

/* Tabs run on :target, not JavaScript, so menu.html#drink deep-links correctly
   from the home page and the browser back button still works. */
.tabs { padding-top: clamp(1.25rem, 3vw, 1.75rem); }

.tabs__list {
  display: flex; gap: .6rem;
  max-width: var(--sheet); margin-inline: auto;
  padding-inline: var(--gutter);
}
.tabs__list a {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 300px; border: 1px solid var(--cream);
  padding: .8rem 1.75rem;
  text-decoration: none;
  font-family: var(--display); text-transform: uppercase;
  font-size: .8125rem; letter-spacing: .16em;
}

/* Food is the default panel, so its tab reads active until Drink is targeted. */
.tabs__list a[href="#food"] { background: var(--cream); color: var(--obsidian); }
.tabs:has(#drink:target) .tabs__list a[href="#food"] { background: transparent; color: var(--cream); }
.tabs:has(#drink:target) .tabs__list a[href="#drink"] { background: var(--cream); color: var(--obsidian); }

#food { display: block; }
#drink { display: none; }
#drink:target { display: block; }
#food:has(~ #drink:target) { display: none; }

/* A band whose only job is to frame the cream sheet in obsidian. */
.band--sheet { padding: clamp(1.25rem, 3vw, 2rem) var(--gutter) var(--band-y); }
/* no page header above it, so the panel carries the top air itself */
main > .band--sheet:first-child { padding-top: calc(var(--nav-h) + clamp(1.5rem, 4vw, 3rem)); }

/* photo features */
.plates {
  display: grid; gap: clamp(.75rem, 1.6vw, 1.25rem);
  grid-template-columns: repeat(2, 1fr);
  margin: 0 0 clamp(2.25rem, 5vw, 3.5rem);
}
@media (min-width: 720px) {
  .plates { grid-template-columns: repeat(4, 1fr); }
  .plates--three { grid-template-columns: repeat(3, 1fr); }
}
.plate { margin: 0; }
.plate img { width: 100%; aspect-ratio: 1 / 1; height: auto; object-fit: cover; border-radius: var(--radius); }

.course { margin: 0 0 clamp(2.25rem, 5vw, 3.5rem); }
.course__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  border-bottom: 2px solid var(--burgundy);   /* the one Deep Burgundy detail */
  padding-bottom: .6rem; margin-bottom: 1.4rem;
}
.course__note { font-size: .875rem; color: var(--on-cream-muted); font-style: italic; margin: 0; }

.items { display: grid; gap: 1.35rem; }
@media (min-width: 720px) { .items { grid-template-columns: 1fr 1fr; gap: 1.35rem clamp(2rem, 4vw, 3.5rem); } }

.item dt {
  display: flex; align-items: baseline; gap: .6rem;
  font-family: var(--display); text-transform: uppercase;
  font-size: 1rem; letter-spacing: .04em;
}
.item dt .dots {
  flex: 1; border-bottom: 1px dotted currentColor; opacity: .4;
  transform: translateY(-.25em);
}
.item dd { margin: .3rem 0 0; font-size: .9375rem; color: var(--on-cream-muted); }
/* the private events list sits on the obsidian ground, not on a sheet */
.band:not(.band--sheet) .item dd { color: var(--on-dark-muted); }

/* ---------------------------------------------------------------- about */


.prose { max-width: 62ch; }
.prose p { margin: 0 0 1.15rem; }


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

.field { display: grid; gap: .4rem; margin-bottom: 1.25rem; }
.field label {
  font-family: var(--display); text-transform: uppercase;
  font-size: .75rem; letter-spacing: .16em;
}
.field input, .field select, .field textarea {
  font-family: var(--read); font-size: 1rem;
  background: transparent; color: inherit;
  border: 0; border-bottom: 1px solid currentColor; border-radius: 0;
  padding: .6rem .1rem;
}
.field textarea { resize: vertical; min-height: 6rem; }
.form-grid { display: grid; gap: 0 clamp(1.5rem, 3vw, 2.5rem); }
@media (min-width: 720px) { .form-grid { grid-template-columns: 1fr 1fr; } }
.form-grid .field--wide { grid-column: 1 / -1; }
/* Netlify's honeypot. `display: none` rather than the `hidden` attribute,
   because `hidden` is a UA-level rule and loses to any author `display`. */
.form-hp { display: none; }

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

   One slim row, three even columns. The previous version used `auto 1fr 1fr`,
   which rendered 56px / 484px / 484px and read as broken spacing.             */

.footer {
  padding: clamp(1.6rem, 3vw, 2.1rem) var(--gutter);
  border-top: 1px solid var(--hairline);
}
/* start, not center: the hours column is a line taller than the address, so
   centring each column pushed the two labels out of line with each other. */
.footer__grid { display: grid; gap: 1.5rem; align-items: start; }
@media (min-width: 760px) {
  .footer__grid {
    display: flex; flex-wrap: wrap; align-items: flex-start;
    justify-content: space-between;
    gap: clamp(2rem, 5vw, 4rem);
  }
}
.footer__mark { color: var(--cream); display: block; padding-top: .1rem; }
.footer__mark svg { width: 40px; height: 40px; display: block; }
.footer h3 {
  font-size: .625rem; letter-spacing: .22em; color: var(--champagne); margin: 0 0 .4rem;
}
.footer p { margin: 0; font-size: .8125rem; line-height: 1.75; color: var(--on-dark-muted); }
.footer a { text-decoration: none; }
.footer a:hover { text-decoration: underline; }

/* Hours read as pairs: the day left, the time hard right, a hairline between
   each. That is the published site's layout, and it sits on the obsidian ground
   rather than taking a panel of its own. Rules go BETWEEN rows, so the first row
   carries none and the block does not close with a dangling line. */
.footer__hours { flex: 0 1 19rem; }
.hours { margin: 0; }
.hours__row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1.5rem;
  padding: .45rem 0;
  border-top: 1px solid var(--hairline);
}
.hours__row:first-child { border-top: 0; padding-top: 0; }
.hours dt { font-size: .8125rem; color: var(--on-dark-muted); }
.hours dd {
  margin: 0; font-size: .8125rem; color: var(--on-dark-muted);
  text-align: right; font-variant-numeric: tabular-nums;
}

/* The glyph inherits the line's colour, the same way the nav monogram does, so
   the mark and the handle can never drift apart. Hover goes gold rather than
   underlined: an underline would run under the icon too. */
.footer__social { margin-top: .45rem; margin-left: -.3rem; }
.footer__social a { display: inline-flex; padding: .3rem; }
.footer__social svg { width: 18px; height: 18px; flex: none; }
.footer__social a:hover { text-decoration: none; color: var(--gold); }

/* ------------------------------------------------- page transition curtain

   Clicking through the nav drops an obsidian curtain over the viewport with the
   figure throwing on it, then fades it out. Zero JavaScript, two halves:

   `@view-transition` hands the navigation to the browser so there is no blank
   flash between two documents. The curtain itself is an ordinary CSS animation
   that runs on load, which is why it works in Firefox too, even though Firefox
   does not do cross-document view transitions yet. Firefox loses the flash-free
   join, nothing else.

   FAIL-SAFE BY CONSTRUCTION. The resting state is invisible and the animation
   is what brings the curtain IN. If the animation never runs, for any reason,
   there is no overlay at all. Built the other way round, visible at rest and
   animated away, any failure strands an opaque panel over the whole site. Do
   not invert this.

   It also fires on a cold load, not only on internal navigation. Nothing in CSS
   can tell those apart, and the mascot has to be live DOM to animate, so it
   cannot live in a view-transition snapshot.                                 */

@view-transition { navigation: auto; }

/* The curtain IS the transition. The browser's default root cross-fade on top
   of it reads as two transitions fighting, so the outgoing page just holds. */
::view-transition-old(root),
::view-transition-new(root) { animation: none; }

.curtain {
  position: fixed; inset: 0; z-index: 200;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: clamp(.9rem, 2vw, 1.5rem);
  background: var(--obsidian);
  pointer-events: none;
  opacity: 0;
}
.curtain__walker { color: var(--cream); width: clamp(120px, 15vw, 180px); }
/* Set as a kicker, the same label treatment as THE FULL LIST and BOOK THE BAR,
   so it reads as part of the system rather than as a stray line of text. Warm
   Champagne on obsidian is 12.9:1. The whole curtain is aria-hidden, which is
   what stops a screen reader announcing this on every single page load. */
.curtain__word {
  margin: 0;
  font-family: var(--display);
  text-transform: uppercase;
  font-size: clamp(.7rem, 1.2vw, .8125rem);
  letter-spacing: .22em;
  color: var(--champagne);
}
/* overflow visible on purpose: the ball leaves the viewBox on release and
   carries on across the curtain, which is the whole point of the throw. */
.curtain__walker svg { width: 100%; height: auto; display: block; overflow: visible; }

/* The shoulder the throw turns on. RAW user-space coordinates from the
   artwork, not offsets from the viewBox corner: measured in Chrome, a
   `transform-box: view-box` origin resolves against user space even when the
   viewBox has a min-x/min-y. Getting this wrong detaches the arm from the
   body, which is exactly what it looks like. */
.curtain__walker svg { --shoulder: 88px 100px; }

/* Same lesson as the reveal below: the animation lives INSIDE no-preference
   rather than being overridden by a later rule, so under reduced motion there
   is nothing left to win on source order. */
@media (prefers-reduced-motion: no-preference) {
  /* The curtain fades on a DELAY rather than on a percentage hold, so the two
     numbers say what they mean and stay independent: wait 500ms, which is the
     exact moment the ball lands, then take 550ms to clear. `both` fill holds it
     opaque through the delay and at zero afterwards. Retiming is these two
     numbers and nothing else. */
  .curtain { animation: curtain-clear 550ms 500ms cubic-bezier(.4, 0, .2, 1) both; }

  .sf-arm-b, .sf-arm-f { transform-box: view-box; transform-origin: var(--shoulder); }

  /* ONE throw, never two. These run a single iteration, not `infinite`: the
     curtain is only alive for 1050ms and a 750ms loop would start a second
     wind-up inside the fade. `both` parks the figure on its final pose, and
     because every 100% keyframe repeats the 66.667% values there is no visible
     change when the iteration ends.

     `step-end` on every one of these is the other half of the point. The throw
     is three held poses that snap, not an interpolated motion: 750ms over three
     frames is 250ms a frame. Ease anything here and it looks smooth again. */
  .sf-arm-b       { animation: sf-throw     750ms step-end both; }
  .sf-arm-f       { animation: sf-guide     750ms step-end both; }
  .sf-ball-held   { animation: sf-ball-held 750ms step-end both; }
  .sf-ball-flight { animation: sf-ball-fly  750ms step-end both; }
}

/* The resting state is already opacity 0, so the end of this animation and the
   fail-safe state are the same value. The hold lives in the delay, not here. */
@keyframes curtain-clear {
  from { opacity: 1 }
  to   { opacity: 0 }
}
/* Three frames, nothing in between: cocked, over the top, follow through.
   Every keyframe below sits on a third so all four animations snap together on
   the same beat. A value at an off-third would put one limb out of step and
   read as a glitch rather than as stop motion.

   The ball is two shapes, not one: the held ball rides inside the arm group so
   it inherits the arm's rotation for free, then hands off to a free-flying copy
   at release. One ball animated along the hand's path would mean hand-tuning
   translate keyframes to match a rotation, which drifts the moment either is
   touched. The 100% entries repeat the 66.667% pose so the third frame holds
   its full beat instead of flashing a synthesised value at the loop point. */
@keyframes sf-throw {
  0%      { rotate: -30deg }
  33.333% { rotate:  35deg }
  66.667% { rotate:  95deg }
  100%    { rotate:  95deg }
}
@keyframes sf-guide {
  0%      { rotate: -10deg }
  33.333% { rotate:  22deg }
  66.667% { rotate:   6deg }
  100%    { rotate:   6deg }
}
@keyframes sf-ball-held {
  0%      { opacity: 1 }
  33.333% { opacity: 0 }
  100%    { opacity: 0 }
}
@keyframes sf-ball-fly {
  0%      { opacity: 0; translate:   0    0 }
  33.333% { opacity: 1; translate:  14px -10px }
  66.667% { opacity: 1; translate:  96px -30px }
  100%    { opacity: 1; translate:  96px -30px }
}

/* ------------------------------------------------- the entrance film (Home, once per session)

   Never cropped: the film is portrait and is contained, centred on obsidian, which
   is close to the dark desk the paper already sits on. Above the curtain (z 200), so
   the cold-load curtain plays out unseen underneath and is long gone by the lift.

   FAIL-SAFE, the same way round as the curtain. The film is display:none unless the
   switch in index.html's <head> adds html.intro-on, so no script means no film, never
   a stranded overlay. The player script removes the class at the push; if it never
   runs, the 12s unlock below hands the page back anyway.                       */

.intro { display: none; position: fixed; inset: 0; z-index: 300; background: var(--obsidian); overflow: hidden; }
html.intro-on .intro, html.intro-lifting .intro { display: block; }
html.intro-on { overflow: hidden; }
html.intro-lifting .intro { opacity: 0; pointer-events: none; transition: opacity 560ms cubic-bezier(.4, 0, .2, 1); }
.intro__stage { position: absolute; inset: 0; transform-origin: 82% 60%; will-change: transform; }
.intro__video { display: block; width: 100%; height: 100%; max-width: none; object-fit: contain; background: var(--obsidian); }
.intro__dark { position: absolute; inset: 0; background: var(--obsidian); opacity: 0; pointer-events: none; }
.intro__skip {
  position: absolute;
  right: calc(clamp(16px, 3vw, 36px) + env(safe-area-inset-right, 0px));
  bottom: calc(clamp(16px, 3vw, 36px) + env(safe-area-inset-bottom, 0px));   /* clear the iPhone home bar */
  min-height: 44px; padding: .7rem 1.3rem;
  border: 1px solid var(--gold); border-radius: 300px;
  background: var(--espresso); color: var(--cream); cursor: pointer;
  font-family: var(--display); font-size: .8125rem; letter-spacing: .22em; text-transform: uppercase;
  transition: opacity .2s ease, background-color .2s ease;
}
.intro__skip:active { transform: scale(.97); }
@media (hover: hover) and (pointer: fine) { .intro__skip:hover { background: var(--walnut); } }
/* gone from the push onward: the lift drops intro-pushing, so it must stay hidden there too */
html.intro-pushing .intro__skip, html.intro-lifting .intro__skip { opacity: 0; transition: none; }

/* The safety net, if the player script never runs: overflow is discrete, so the
   page scrolls again at 12s, and the film itself steps out of the way. */
html.intro-on { animation: intro-unlock 0s 12s forwards; }
html.intro-on .intro { animation: intro-away 0s 12s forwards; }
@keyframes intro-unlock { to { overflow: visible; } }
@keyframes intro-away { to { visibility: hidden; pointer-events: none; } }

/* #main takes focus when the film lifts; it is a landing point, not a control */
#main[tabindex="-1"]:focus { outline: none; }

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

   The reveal lives INSIDE no-preference. Under reduced motion the rule never
   applies at all, so there is no opacity:0 left to win on source order.
   v1 shipped a hero that rendered blank because its guard was a later,
   equal-specificity override instead of a scope.                          */

@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translateY(14px);
    animation: rise .7s cubic-bezier(.22, .61, .36, 1) forwards;
  }
  .reveal:nth-child(2) { animation-delay: .08s; }
  .reveal:nth-child(3) { animation-delay: .16s; }
  .reveal:nth-child(4) { animation-delay: .24s; }
  .reveal:nth-child(5) { animation-delay: .32s; }
  /* Under the film the hero copy waits at its first frame, then rises as the film lifts */
  html.intro-on .reveal { animation-play-state: paused; }
}

@keyframes rise { to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .ticker__track { animation: none; }
  html { scroll-behavior: auto; }
  .menu-card img { transition: none; }
}

/* ---------------------------------------------------------------- small screens

   The nav collapses to a hamburger below 760. The panel is shown by a checkbox
   sibling selector, so the whole thing stays zero JavaScript and the control is
   a real focusable input rather than a div.                                    */

@media (max-width: 760px) {
  :root { --nav-h: 4.5rem; }
  .nav { gap: .75rem; }

  .nav__burger {
    display: flex; flex-direction: column; justify-content: center; align-items: flex-end;
    gap: 5px; margin-left: auto;
    width: 44px; height: 44px;          /* a real 44px tap target */
    cursor: pointer; color: var(--cream);
  }
  .nav__burger span {
    display: block; width: 26px; height: 2px; background: currentColor;
  }
  .nav__toggle:focus-visible + .nav__burger {
    outline: 3px solid var(--gold); outline-offset: 4px;
  }

  .nav__panel {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    margin-left: 0;
    background: var(--obsidian);
    border-top: 1px solid var(--hairline);
    border-bottom: 1px solid var(--hairline);
  }
  .nav__toggle:checked ~ .nav__panel { display: block; }

  .nav__links {
    flex-direction: column; align-items: stretch; gap: 0;
    padding: .5rem var(--gutter) 1.25rem;
  }
  .nav__links a:not(.btn) {
    font-size: .95rem; letter-spacing: .14em;
    padding: .95rem 0; border-bottom: 1px solid var(--hairline);
  }
  .nav__links a:not(.btn):last-of-type { border-bottom: 0; }
  .nav__links a:not(.btn)[aria-current="page"] { color: var(--champagne); }
  /* Reserve rides in the dropdown on phones, full width. */
  .nav__cta { display: inline-flex; margin-top: .9rem; align-self: flex-start; }

  .items { grid-template-columns: 1fr; }
}

@media (max-width: 760px) and (prefers-reduced-motion: no-preference) {
  .nav__toggle:checked ~ .nav__panel { animation: drop .22s ease both; }
}
@keyframes drop { from { opacity: 0; transform: translateY(-6px); } }

/* Short laptops: compress the vertical rhythm so the CTA stays above the fold. */
@media (max-height: 860px) and (min-width: 820px) {
  .hero__mark { max-height: 13svh; }
  .hero__lede { margin-bottom: 1.1rem; font-size: 1.05rem; }
  :root { --band-y: clamp(3rem, 6vw, 5rem); }
}
