/* ============================================================
   WOODY'S — pages.css
   Page-specific styles: HOME poster hero · MENU · ITEM ·
   EVENTS · ABOUT · VISIT · 404.
   Surface law (§1.3) holds everywhere: ink on light, bone on
   dark, no opacity-dimmed text (size/weight only).
   ============================================================ */

/* ---------- Shared page primitives ---------- */
.section-head{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  flex-wrap:wrap;
  gap:var(--sp-4);
  margin-bottom:var(--sp-6);
}
.section-head__title{
  font-family:var(--font-display);
  font-size:var(--fs-h1);
  font-weight:400;                 /* Graduate ships one weight — see base §7 */
  text-transform:uppercase;
  letter-spacing:.015em;
  line-height:1.04;
}
.section-head__link{
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.02em;
  font-size:var(--fs-sm);
}

/* page titles letter-split into .ltr spans pop twice as fast (§C3) */
.page-title{ --stagger:.05s; }

/* generic responsive card grid (events / this-week / gallery) */
.card-grid{
  display:grid;
  gap:var(--sp-5);
  grid-template-columns:repeat(3,1fr);
}
@media (max-width:1024px){ .card-grid{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:600px){ .card-grid{ grid-template-columns:1fr; } }

/* inline icon artwork inside card media wells — ink on band-a,
   contained (the components rule stretches svg to cover for photos) */
.card__media .card__art{ width:52%; height:auto; object-fit:contain; }

/* hover-image flourish (home + about + item) — JS follows the cursor
   with transform-only writes (§1.8.10); this block only paints the frame */
.hoverword{ position:relative; cursor:default; }
.hoverimg{
  position:fixed;
  top:0; left:0;
  z-index:var(--z-hoverimg);
  width:clamp(8rem,16vw,13rem);
  pointer-events:none;
  opacity:0;
  transform:translate(-50%,-50%) rotate(-4deg);
  transition:opacity .15s ease-out;
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-card);
  background:var(--bone);
}
.hoverimg.is-on{ opacity:1; }

/* ============================================================
   HOME — loader inner content (curtain chrome lives in components)
   ============================================================ */
/* The mark on the ink ground. Capped at 400px because the master is 488px
   wide and nothing larger exists — see assets/img/logo/README.md. */
#loader__logo{ width:min(72vw, 400px); height:auto; display:block; }

/* ============================================================
   HOME — poster hero (§C1): full viewport, the real logo at its
   honest size over a display line that carries the scale, fold
   strip, marquee edge
   ============================================================ */
.hero{
  position:relative;
  min-height:100svh;
  display:grid;
  grid-template-rows:1fr auto auto;
  isolation:isolate;                     /* keeps the scrim's z-index local */
}
/* Photo backdrop. Absolute so it never becomes a grid row. */
.hero__bg{
  position:absolute;
  inset:0;
  z-index:0;
}
.hero__bg img{
  width:100%;
  height:100%;
  object-fit:cover;
  object-position:center 42%;            /* holds the TV row + bar in frame */
}
/* Scrim. The photo is a dim room with very bright TVs in it, so a flat wash
   is not enough — this darkens the whole frame and leans harder top and
   bottom, where the display line and the fold copy sit. Contrast of bone
   over the composite is measured, not assumed (see docs/STATUS.md). */
.hero::before{
  content:'';
  position:absolute;
  inset:0;
  z-index:1;
  pointer-events:none;
  background:
    linear-gradient(to bottom,
      rgba(12,10,9,.80) 0%,
      rgba(12,10,9,.55) 38%,
      rgba(12,10,9,.62) 62%,
      rgba(12,10,9,.86) 100%),
    rgba(12,10,9,.28);
}
/* Everything else must clear the scrim (z-index:1). The wordmark wrap
   (sticky, z 600), the starburst (absolute, z 60) and the marquee (relative,
   z 400) already carry their own positioning and sit above it — do NOT blanket
   `position:relative` onto the hero's children here, it overrides the
   starburst's `position:absolute` and its right-anchor collapses. Only the
   fold is statically positioned and needs lifting. */
.hero__fold{ position:relative; z-index:2; }

/* Solid CTA flips to bone fill on the photo — an ink pill on a dark frame
   keeps its bone label but loses its shape entirely. Ghost pills already
   flip via the band--dark rule in components.css. */
.hero.band--dark .hero__cta .pill:not(.pill--ghost){
  background:var(--bone);
  color:var(--ink);
  border-color:var(--bone);
}
.can-hover .hero.band--dark .hero__cta .pill:not(.pill--ghost):hover{
  background:var(--gold);
  border-color:var(--gold);
  color:var(--ink);
}
.hero__wm-wrap{
  position:sticky;
  top:.75rem;
  z-index:var(--z-wordmark);
  align-self:start;
  justify-self:center;
  display:grid;
  justify-items:center;
  gap:clamp(.5rem, 2vh, 1.25rem);
  padding:4.5rem 0 0;
  transform:scale(var(--wm-s,1));
  transform-origin:top center;
  will-change:transform;
  transition:none;                       /* JS writes --wm-s per frame */
}
/* The logo is a raster lifted off the menu artwork: 488px is all the detail
   that exists, so it is capped there and only ever scaled DOWN. It now carries
   the hero alone — the display line under it and the OPEN LATE starburst were
   both removed, so the photo and the mark do the work. */
.hero__logo{
  width:min(88vw, 488px);
  height:auto;
}

/* ---------- Hero logo: idle hop (D43) ----------
   A double hop that rocks left then right, once every 5s, ONLY while the
   wordmark is at full size.

   The gate is `body:not(.wm-shrunk)`. woody.js already toggles `wm-shrunk` on
   the body the moment the scroll shrink takes the scale below 0.98 — it was
   being set and never consumed, so this needs no JS at all. Scroll a pixel and
   the animation stops; scroll back to the top and it resumes.

   It animates the IMG, not `.hero__wm-wrap`. The wrap's transform belongs to
   the scroll shrink (`scale(var(--wm-s))`, rewritten per frame by JS) — putting
   a keyframe transform there would fight it and the logo would jump.

   The whole hop happens in the first 20% of a 5s cycle (~1s) and the remaining
   4s is deliberately dead air. Per-keyframe timing functions do the work: ease
   out on the way up, ease in on the way down, so it reads as weight rather than
   as a slide. The 2s delay keeps it clear of the page's own reveal.

   prefers-reduced-motion needs no rule here — base.css already clamps every
   animation to .001ms, which flattens this to nothing.

   ⚠️ THE SIDEWAYS TRAVEL IS A VARIABLE BECAUSE IT OVERFLOWED ON A PHONE.
   The logo is `min(88vw,488px)`, so on a 393px screen it is 346px wide and
   already only 16px from each edge. At full desktop values the left hop put its
   left edge at −6px — clipped off-screen — and the document width flickered
   378→382 while it played. (No scrollbar appeared, so a scrollWidth check alone
   would have missed it; it was caught by measuring the LOGO's own rect at the
   hop peaks.) The rotation displaces the far corners more than the translate
   does, so both are dialled back together under 700px. If either value is ever
   raised, re-measure the rect at 2250ms and 2750ms, not just scrollWidth. */
.hero__logo{ --hop-x:3%; --hop-r:3deg; }
@media (max-width:700px){
  .hero__logo{ --hop-x:.9%; --hop-r:1.4deg; }
}
@keyframes wm-hop{
  0%   { transform:translate3d(0,0,0) rotate(0deg);
         animation-timing-function:cubic-bezier(.34,.8,.4,1); }
  5%   { transform:translate3d(calc(var(--hop-x) * -1),-8%,0) rotate(calc(var(--hop-r) * -1));
         animation-timing-function:cubic-bezier(.6,.04,.9,.5); }
  10%  { transform:translate3d(calc(var(--hop-x) * -.67),0,0) rotate(calc(var(--hop-r) * -.33));
         animation-timing-function:cubic-bezier(.34,.8,.4,1); }
  15%  { transform:translate3d(calc(var(--hop-x) * .83),-6%,0) rotate(var(--hop-r));
         animation-timing-function:cubic-bezier(.6,.04,.9,.5); }
  20%  { transform:translate3d(0,0,0) rotate(0deg); }
  100% { transform:translate3d(0,0,0) rotate(0deg); }
}
body.is-home:not(.wm-shrunk) .hero__logo{
  animation:wm-hop 5s 2s linear infinite;
  transform-origin:50% 90%;   /* pivots off its feet, not its middle */
}
.hero__fold{
  display:flex;
  justify-content:space-between;
  align-items:center;
  flex-wrap:wrap;
  gap:var(--sp-4) var(--sp-6);
  padding-inline:clamp(1.25rem,4vw,4rem);
  margin-bottom:.75rem;
}
/* fold rises ~1.4s (revealed@800 + .6s delay); dead-man keeps it
   visible if woody.js dies after the bootstrap set .js (§1.10) */
.js body:not(.revealed) .hero__fold{
  opacity:0;
  animation:ltr-bail 0s 4.5s linear forwards;
}
body.revealed .hero__fold{ animation:rise-in var(--dur-reveal) ease-out .6s both; }
.hero__tagline{
  max-width:38ch;
  font-size:var(--fs-lead);
  line-height:1.4;
  /* Overrides the global `pretty` (base.css) for this block only. `pretty`
     only prevents a one-word orphan, which the tagline's last line dodges by
     being two words -- measured 465/437/494/127px, a 367px spread ending in a
     runt. `balance` evens the same 4 lines to a 99px spread at identical block
     height. Worth it here because the fold is 4 lines of large type over a
     photo; leave body copy on `pretty`, which is the right default at length. */
  text-wrap:balance;
}
.hero__cta{ display:flex; flex-wrap:wrap; gap:var(--sp-3); }

/* ============================================================
   HOME — Band A "WEEKLY EVENTS" (the bar's standing schedule)
   Was the 6-card drink rail; now three recurring nights on the
   shared .card-grid, so the only rule left is the band's rhythm.
   The `.rail` scoped rules went with the cards — a three-card
   rail is a scrollbar that never scrolls. (.rail itself is still
   live on menu/item/.)
   ============================================================ */
.weekly{ padding-block:clamp(var(--sp-8),8vw,var(--sp-10)); }

/* ============================================================
   HOME — Band (dark) "GAMES" (photo-backed, five options as tiles)
   Was ".joint" (a flat maroon band of general bar copy), then briefly
   a photo band carrying one huge paragraph — which covered the very
   photo it sat on. The list is the pitch: one line of copy, then the
   five things you can play. Backdrop and scrim mirror the hero's
   (.hero__bg above): absolutely-positioned <picture>, ink wash over
   it, content lifted clear. band--dark stays on the section, so
   maroon is still the floor if the photo 404s.
   ============================================================ */
.games{
  position:relative;
  isolation:isolate;                     /* keeps the scrim's z-index local */
  padding-block:clamp(var(--sp-8),8vw,var(--sp-10));
}
.games__bg{
  position:absolute;
  inset:0;
  z-index:0;
}
.games__bg img{
  width:100%;
  height:100%;
  object-fit:cover;
  object-position:center 45%;            /* holds the cabinet row in frame */
}
/* Scrim. Now that the copy is one line and the tiles span the full width,
   this is a flat-ish top-to-bottom wash rather than the left-weighted ramp
   the paragraph needed. Contrast of bone over the composite is measured
   per-pixel (see docs/STATUS.md) — re-measure if the copy or tiles change. */
.games::before{
  content:'';
  position:absolute;
  inset:0;
  z-index:1;
  pointer-events:none;
  background:
    linear-gradient(to bottom,
      rgba(12,10,9,.74) 0%,
      rgba(12,10,9,.60) 45%,
      rgba(12,10,9,.72) 100%),
    rgba(12,10,9,.22);
}
/* Everything after the backdrop clears the scrim. */
.games .section-head,
.games__lead,
.games__list{ position:relative; z-index:2; }

.games__lead{
  font-size:var(--fs-lead);
  line-height:1.4;
  max-width:52ch;
}
/* One sentence per line, by structure rather than by hoping the measure breaks
   in the right place. Blocks, not <br>, so each sentence still balances its own
   wrap on a narrow phone where 40 characters cannot fit on one line anyway. */
.games__line{
  display:block;
  text-wrap:balance;
}
.games__list{
  /* one knob for the icon square, because the "& more" tile fakes its icon with
     a glyph and the two must agree or that tile's label drops a line */
  --games-icon:clamp(2.25rem,3.2vw,2.75rem);
  display:grid;
  grid-template-columns:repeat(6,1fr);
  gap:var(--sp-3);
  margin:var(--sp-7) 0 0;
  padding:0;
  list-style:none;
}
/* Outlined, not filled: six solid tiles would punch six holes in the photograph
   this band exists to show. The scrim carries legibility instead. */
.games__item{
  display:grid;
  justify-items:center;
  align-content:start;
  gap:var(--sp-3);
  padding:var(--sp-5) var(--sp-3);
  border:var(--stroke) solid currentColor;
  border-radius:var(--radius-card);
  text-align:center;
}
.games__icon{
  width:var(--games-icon);
  height:auto;
}
/* The "& more" tile's ampersand stands in for an icon. Boxed to the SAME square
   the SVGs occupy so all six labels sit on one line; Graduate because a mono
   ampersand next to five drawn marks reads as a typo rather than a mark. */
.games__amp{
  display:grid;
  place-items:center;
  width:var(--games-icon);
  height:var(--games-icon);
  font-family:var(--font-display);
  font-size:calc(var(--games-icon) * .95);
  font-weight:400;                       /* Graduate ships one weight — base §7 */
  line-height:1;
}
.games__label{
  font-size:var(--fs-sm);
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.03em;
  line-height:1.2;
}
@media (max-width:900px){
  .games__list{ grid-template-columns:repeat(3,1fr); }
}
@media (max-width:560px){
  .games__list{ grid-template-columns:repeat(2,1fr); gap:var(--sp-3); }
}

/* ============================================================
   HOME — Band B (dark) "HOUSE PICKS" (one dish, one cocktail)
   Was ".thisweek", three invented event cards. A split panel with a
   centre rule rather than a third card row: the two bands above are
   already a card grid and a tile row. Everything is bone on the dark
   band — no card surfaces — so the two halves read as one unit that
   has been divided, which is the point of the split.
   ============================================================ */
.picks{ padding-block:clamp(var(--sp-8),8vw,var(--sp-10)); }
.picks__split{
  display:grid;
  grid-template-columns:1fr 1fr;
  /* Five named rows — eyebrow, art, name, note, price — so the two halves can
     share them via subgrid below. Without this the halves keep their own rows
     and a two-line name on one side drops that side's note and price out of
     line with the other, which is exactly what a split panel must not do. */
  grid-template-rows:auto auto auto auto auto;
  /* no gap — the divider IS the seam, and a gap would double it */
  gap:0;
}
.picks__pick{
  display:grid;
  grid-row:1 / -1;
  grid-template-rows:subgrid;            /* no subgrid → own rows, i.e. today's look */
  justify-items:center;
  align-content:start;
  gap:var(--sp-4);
  padding:clamp(var(--sp-5),3vw,var(--sp-7));
  text-align:center;
}
/* currentColor, so the rule re-inks with the surface law if this block is
   ever moved onto a light band */
.picks__pick + .picks__pick{ border-left:var(--stroke) solid currentColor; }

.picks__eyebrow{
  font-size:var(--fs-sm);
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.08em;
}
/* Deliberately large. This is the one band on the page whose job is to make a
   single plate and a single glass look worth crossing town for, and at 7.5rem
   the art read as a menu bullet rather than a spotlight. */
.picks__art{
  width:clamp(6.5rem,12vw,10.5rem);
  margin-block:var(--sp-2);
}
.picks__art svg{ width:100%; height:auto; display:block; }
.picks__name{
  font-family:var(--font-display);
  font-weight:400;                       /* Graduate ships one weight — base §7 */
  font-size:var(--fs-h1);
  line-height:1.04;
  text-transform:uppercase;
  letter-spacing:.02em;
  text-wrap:balance;
}
.picks__note{
  font-size:var(--fs-sm);
  line-height:1.55;
  max-width:38ch;
  margin-top:calc(var(--sp-2) * -1);     /* the note belongs to the name above it */
}
.picks__price{
  font-size:var(--fs-lead);
  font-weight:700;
  letter-spacing:.02em;
}
/* Centred under the split, not inside either half — it belongs to the whole
   band, and hanging it off one side would read as that side's button. */
.picks__foot{
  display:flex;
  justify-content:center;
  margin-top:clamp(var(--sp-6),4vw,var(--sp-8));
}
/* Stacked, the vertical rule has nothing to divide — it becomes the seam
   between the two halves instead. */
@media (max-width:760px){
  .picks__split{
    grid-template-columns:1fr;
    grid-template-rows:none;
  }
  /* MUST reset both, or the halves stay side by side: `grid-row:1 / -1` pins
     them to the same row band, so the second one is auto-placed into a new
     implicit COLUMN rather than stacking. Measured at 393px — two 156px and
     210px columns, not a stack. Row alignment is moot once stacked anyway. */
  .picks__pick{
    grid-row:auto;
    grid-template-rows:none;
  }
  .picks__pick + .picks__pick{
    border-left:0;
    border-top:var(--stroke) solid currentColor;
  }
}

/* ============================================================
   ON TAP  (index.html, band A) — D31
   The owner's tap list as a wrapped run of .tag pills. No new
   component: .tags already solves wrap-and-fill for names of very
   uneven length ("Coors" next to "Firestone Cali Squeeze Blood
   Orange"), and on a light band .tag's ink border needs no override.
   Only two things are added here — a little more air between pills
   than the default .tags gap, because 17 of them at the default read
   as one block of text rather than a list, and a note under it.
   ============================================================ */
.taps{ padding-block:clamp(var(--sp-8),8vw,var(--sp-10)); }
.taps__list{ gap:var(--sp-3); }
.taps__note{
  margin-top:var(--sp-5);
  font-style:italic;
}

/* ============================================================
   REVIEWS  (index.html, band B / ink) — D42
   One honest line: the score at display scale, the stars, the count.
   No quote cards yet — Google's review text is not retrievable and
   invented ones are not an option, so the band is deliberately built
   as a ROW that a .card-grid can drop under without a redesign.
   Baseline-aligned rather than centred, so it does not read as a
   second House-picks (the other ink band, two sections up).
   ============================================================ */
.reviews{ padding-block:clamp(var(--sp-8),8vw,var(--sp-10)); }
.reviews__stat{
  display:flex;
  align-items:baseline;
  flex-wrap:wrap;
  gap:var(--sp-4) var(--sp-5);
}
/* Bigger than --fs-h1 on purpose: the number IS the argument, and this
   site's voice is large display type. Graduate ships one weight, so no
   font-weight here — asking for 700 synthesises a smeared fake bold. */
.reviews__score{
  font-family:var(--font-display);
  font-size:clamp(3.5rem, 10vw, 7rem);
  line-height:.9;
  letter-spacing:.01em;
}
/* align-self:baseline puts the stars on the score's baseline; the flex
   default would hang them off the top of a 7rem number. */
.reviews__stars{
  display:flex;
  gap:.22em;
  font-size:clamp(1.5rem, 3.5vw, 2.5rem);   /* stars size off this */
  align-self:baseline;
}
.reviews__star{
  width:1em;
  height:1em;
  display:block;
  flex:0 0 auto;
}
.reviews__count{
  font-size:var(--fs-lead);
  line-height:1.4;
}
@media (max-width:600px){
  /* Stacked, the baseline row collapses to a left-aligned column — a 7rem
     number and a star row on one line will not fit 393px. */
  .reviews__stat{ align-items:flex-start; gap:var(--sp-3); }
  .reviews__stars{ align-self:flex-start; }
}

/* ============================================================
   MENU  (menu/index.html)
   ============================================================ */
.menu-head{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  justify-content:space-between;
  gap:var(--sp-4);
  margin-bottom:var(--sp-6);
}
/* .menu-tools held the sort pill and the density toggle (removed D24); .chips /
   .chip were the category jump-nav (removed D26, along with the scroll-spy that
   drove them). The head is the title alone — space-between left-aligns a lone
   child on its own and needs no new rule. */

body.menu .menu-section{ padding-block:clamp(var(--sp-7),7vw,var(--sp-9)); }
/* ≤600: first content must clear the fixed corner pills (~2.75rem tall) */
/* Mobile top-clearance for the fixed corner chrome. Bumped from 2.5rem when
   the brand pill became the logo: the logo's bottom edge sits at ~71px vs the
   pill's 60px, which left the menu title nearly touching. (The about eyebrow
   was the other case this covered; that page is gone — see DECISIONS D18.) */
@media (max-width:600px){
  body.menu .menu-section{ padding-top:calc(var(--sp-7) + 3.25rem); }
  /* /events/ and /visit/ needed the SAME clearance and never got it. Both use
     the generic .section, whose mobile padding-top resolves to 64px, which puts
     their titles at y=64 against a brand whose bottom edge is y=71 — the logo
     sat ON "WHAT'S ON" and "FIND WOODY'S". Measured -7px on both, and confirmed
     pre-existing on prod before the per-page grounds went in (D39); the ink
     ground just made it obvious, because the logo's white outline landed on a
     dark page. Same expression as the menu rule above on purpose, so all three
     pages clear the chrome by the same 29px. */
  body.events .section,
  body.visit .section{ padding-top:calc(var(--sp-7) + 3.25rem); }
}

/* Kitchen hours under the title. Reuses the footer's .hours table so
   woody.js's is-today marker works here for free; the caption is the label. */
.menu-kitchen{ margin:calc(var(--sp-5) * -1) 0 var(--sp-6); }
.menu-kitchen .hours{ width:auto; }
.menu-kitchen .hours td:last-child{ padding-left:var(--sp-5); }
.menu-kitchen__label{
  text-align:left;
  font-size:var(--fs-sm);
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.03em;
  padding-bottom:var(--sp-1);
}

/* The sheet — one bone surface carrying the whole list, the way the printed
   menu is one sheet. 35 dense rows of ink directly on the gold page is hard
   reading; this also lets the leaders and prices sit on a calm ground. */
.menu-sheet{
  background:var(--bone);
  color:var(--ink);
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-card);
  padding:clamp(var(--sp-5),4vw,var(--sp-8));
}
/* Two balanced columns like the print piece. Multicol, not a 2-col grid: grid
   would make every row as tall as its taller category and leave holes. */
.menu-cols{
  columns:2;
  column-gap:clamp(var(--sp-7),6vw,var(--sp-9));
}
.mcat{
  break-inside:avoid;
  /* PADDING, not margin — multicol truncates margins at a column break, so a
     margin-bottom here silently disappears on whichever block ends a column. */
  padding-bottom:var(--sp-7);
}
.mcat:last-child{ padding-bottom:0; }

body.menu .menu-cat{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:var(--sp-3);
  padding-bottom:var(--sp-2);
  margin-bottom:var(--sp-3);
  border-bottom:var(--stroke) solid var(--ink);
  scroll-margin-top:6rem;             /* clear the fixed chrome on anchor jump */
}
body.menu .menu-cat__name{
  font-family:var(--font-display);
  font-size:var(--fs-h2);
  font-weight:400;                    /* Graduate ships one weight — base §7 */
  text-transform:uppercase;
  letter-spacing:.02em;
  line-height:1.05;
  /* Accent only (D27): the NAME takes brand red, the rule under it stays ink so
     the page's structure reads neutral. Measured 4.81:1 on bone — the worst
     pair in the pinned palette, and it still clears AA for small text, let
     alone this display size. Do not reuse red at body size without re-checking. */
  color:var(--red);
}
body.menu .menu-cat__note{ font-size:var(--fs-sm); }

/* Item rows — name, dotted leader, price, exactly like the printed sheet.
   The leader is an empty aria-hidden span that eats the free space; it is
   decoration, so it must never be announced. */
.mlist{ list-style:none; margin:0; padding:0; }
.mitem{
  display:flex;
  align-items:baseline;
  gap:.45em;
  margin:0;
  padding-block:.28em;
  font-size:var(--fs-sm);
  line-height:1.35;
}
.mitem__name{ flex:0 1 auto; }
.mitem__lead{
  flex:1 1 auto;
  min-width:1.5em;
  border-bottom:2px dotted currentColor;
  transform:translateY(-.32em);       /* drop the dots onto the baseline */
}
/* Prices in rust — the one thing everyone scans for, so it gets its own colour
   without shouting over the red heads. 7.10:1 on bone, clears AA for small bold
   text with room. The dotted leader stays ink so the row still reads as a row. */
.mitem__price{
  flex:0 0 auto;
  font-weight:700;
  white-space:nowrap;
  color:var(--rust);
}

/* Sub-copy under a category head, and the trailing "add cheese +$0.50" lines */
.mnote{
  font-size:var(--fs-sm);
  line-height:1.45;
  font-style:italic;
  margin-bottom:var(--sp-3);
}
.mnote--foot{ margin:var(--sp-3) 0 0; }
/* Sauce lists read as one run-on line, so they get their own weight */
.msauces{
  font-size:var(--fs-sm);
  line-height:1.45;
  margin-bottom:var(--sp-3);
}
.msauces b{ font-weight:700; text-transform:uppercase; letter-spacing:.04em; }
.mlist--extras{ margin-top:var(--sp-2); }
/* Name-only rows, for a list that genuinely has no prices — the tap list (D31).
   A dotted leader running to an empty price cell reads as a missing price, which
   is worse than no leader at all. */
.mlist--plain .mitem{ padding-block:.22em; }

/* Boxed offers — the platter and the extra-sauce charge, ruled off like the
   print piece's outlined callouts */
.mcallout{
  margin-top:var(--sp-4);
  padding:var(--sp-3) var(--sp-4);
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-card);
}
.mcallout .mitem{ padding-block:0; font-weight:700; }
.mcallout .mnote{ margin:.2em 0 0; }

.menu-legal{
  display:grid;
  gap:var(--sp-2);
  margin-top:var(--sp-6);
  max-width:70ch;
}

/* One column on a phone: two columns of leader rows at this width leaves
   nothing but dots between a two-word name and its price. */
@media (max-width:760px){
  .menu-cols{ columns:1; }
}

/* ============================================================
   ITEM  (menu/item/index.html) — duplicate-me template
   ============================================================ */
.item{
  display:grid;
  grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);
  gap:clamp(var(--sp-6),5vw,var(--sp-8));
  align-items:start;
}
.item__media{
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-card);
  overflow:hidden;
  aspect-ratio:4 / 3;
  background:var(--band-a);
  display:grid;
  place-items:center;
}
.item__media img,
.item__media svg{ width:100%; height:100%; object-fit:cover; }
.item__media .card__art{ width:52%; height:auto; object-fit:contain; }
.item__info{ display:grid; gap:var(--sp-4); }
/* Sized to the column, not to --fs-display: Graduate sets wider than Space
   Mono, and at display scale a name like "[BOURBON OLD FASHIONED]" broke
   mid-word ("BOUR / BON OL / D"). This fits realistic item names on word
   breaks; overflow-wrap stays as the guard for pathological ones (§C4). */
.item__title{
  font-family:var(--font-display);
  font-size:clamp(2.25rem, 4.2vw, 4.25rem);
  line-height:1;
  font-weight:400;
  text-transform:uppercase;
  letter-spacing:.01em;
  overflow-wrap:anywhere;
}
/* price stays mono on purpose — a bar menu wants a tabular price column */
.item__price{ font-size:var(--fs-h2); font-weight:700; }
.item__pour{ font-size:var(--fs-lead); line-height:1.5; }
@media (max-width:812px){ .item{ grid-template-columns:1fr; } }

/* the two split sections (§C4/§E6): sticky back-pill containment ends
   with .item-hero, before the related rail begins */
body.item-detail .item-hero.section{
  padding-block:clamp(var(--sp-6),5vw,var(--sp-8)) clamp(var(--sp-7),6vw,var(--sp-9));
}
body.item-detail .item-related.section{
  padding-block:0 clamp(var(--sp-8),8vw,var(--sp-10));
}

body.item-detail .item-back{
  position:sticky;
  top:4rem;
  z-index:var(--z-sticky);
  margin-bottom:var(--sp-6);
}
body.item-detail .item__gallery{ display:grid; gap:var(--sp-4); }
body.item-detail .item__media{ position:relative; }            /* anchor the caption chip */
body.item-detail .item__media-note{
  position:absolute;
  inset:auto var(--sp-4) var(--sp-4) var(--sp-4);
  padding:.5em 1em;
  background:var(--ink);
  color:var(--bone);
  border-radius:var(--radius-pill);
  text-align:center;
  font-weight:700; text-transform:uppercase; letter-spacing:.03em;
  font-size:var(--fs-sm);
}
body.item-detail .item__divider{ width:100%; height:1.4rem; }
body.item-detail .item__thumbs{ display:grid; grid-template-columns:repeat(3,1fr); gap:var(--sp-3); }
body.item-detail .item__thumb{
  aspect-ratio:1;
  display:grid; place-items:center;
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-card);
  background:var(--band-a);
  overflow:hidden;
}
body.item-detail .item__thumb .card__art{ width:56%; height:auto; }
body.item-detail .item__eyebrow{
  font-size:var(--fs-sm);
  text-transform:uppercase; letter-spacing:.03em; font-weight:700;
}
body.item-detail .item__pour-block{ display:grid; gap:var(--sp-2); }
/* price + HOUSE POUR starburst share a row (§E7) */
body.item-detail .item__price-row{
  display:flex;
  align-items:center;
  gap:var(--sp-5);
  flex-wrap:wrap;
}
body.item-detail .item__price-row .badge--star{
  position:static;
  width:6.5rem; height:6.5rem;
  flex:0 0 auto;
}
body.item-detail .item__cta{ display:flex; flex-wrap:wrap; gap:var(--sp-3); align-items:center; }

/* ============================================================
   EVENTS  (events/index.html)
   ============================================================ */
.event-card .card__media{ aspect-ratio:16 / 10; }
.datechip{
  display:inline-flex;
  align-items:center;
  padding:.3em .7em;
  background:var(--ink);
  color:var(--bone);
  border-radius:var(--radius-pill);
  font-weight:700;
  letter-spacing:.04em;
  font-size:var(--fs-sm);
}
.event-card .card__body{ gap:var(--sp-3); }
/* Head row. It used to hold the intro on the left and a spinning OPEN LATE
   starburst in the right void (§C9); the starburst was removed 2026-08-27 (D37,
   owner) along with the two rules that sized it here and hid it under 600px.
   The flex row stays: with one child, space-between just left-aligns it, and the
   row keeps the head's bottom margin. `.badge--star` itself is NOT dead — the
   menu/item/ template still uses it, so its component CSS stays in
   components.css. */
body.events .events__head{
  display:flex;
  justify-content:space-between;
  align-items:end;
  gap:var(--sp-5);
  margin-bottom:clamp(var(--sp-6), 5vw, var(--sp-8));
}
body.events .events__intro{
  display:grid;
  gap:var(--sp-4);
  max-width:64ch;
}

/* ============================================================
   VISIT  (visit/index.html)
   ============================================================ */
/* two-column layout lives on .visit-grid, NOT on body.visit (§C5) */
main .visit-grid{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1.2fr);
  gap:clamp(2rem,5vw,4rem);
  align-items:start;
}
@media (max-width:812px){ main .visit-grid{ grid-template-columns:1fr; } }

.visit__info{ display:grid; gap:var(--sp-6); }
/* Content headings are h2 as of 2026-08-27 (D41) — the page used to jump
   h1 -> h3, which is a heading skip. The footer's own h3s are unchanged and
   now sit correctly under these. */
.visit__block h2{
  font-size:var(--fs-sm);
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.03em;
}
.visit__hours{ font-size:var(--fs-body); }
.visit__hours td{ padding:.25em 0; border:0; }
.visit__hours td:last-child{ text-align:right; padding-left:var(--sp-4); }
.mapblock{
  position:relative;
  min-height:22rem;
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-card);
  overflow:hidden;
  background:var(--band-a);
  display:grid;
  place-items:center;
}
/* The Google Maps embed (D34). Absolutely filling, because .mapblock is a grid
   with place-items:center — a plain in-flow iframe would be centred at its own
   intrinsic size instead of covering the panel. .mapblock is already
   position:relative and overflow:hidden, so the frame is clipped to the card's
   rounded corners.
   NOTE: nothing may be layered over the bottom of this frame. Google's
   attribution and terms links live there, and covering them breaks the Maps
   terms of service — which is why the old .mapblock__note pill was removed
   rather than kept for decoration. */
.mapblock__frame{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  border:0;
  display:block;
}
/* contact + social tags reveal as one staggered cluster (§F10) */
body.visit .visit__cluster{ display:grid; gap:var(--sp-6); }

/* `.visit__intro` went with the intro paragraph on 2026-08-27 (D40). It was
   `max-width:58ch; margin-bottom:clamp(--sp-6,5vw,--sp-8); font-size:--fs-lead;
   line-height:1.5` — recorded here rather than left in the file as a rule with
   no element, in case the copy ever comes back. */
body.visit .visit__block{ display:grid; gap:var(--sp-3); align-content:start; }
body.visit .visit__addr,
body.visit .visit__contact{ font-size:var(--fs-lead); line-height:1.5; }
body.visit .visit__contact a{ font-weight:700; }
body.visit.can-hover .visit__contact a:hover{
  background:linear-gradient(var(--ink),var(--ink)) no-repeat 0 100% / 100% .12em;
}
body.visit .mapblock{ align-self:stretch; }              /* match the info column height */

/* ============================================================
   404 — "86'D". Scoped to body.notfound (a bare .nf-style class on
   <body> would turn the whole page into a centering grid).
   ============================================================ */
body.notfound .nf{
  min-height:60vh;
  display:grid;
  place-items:center;
  text-align:center;
  padding-block:clamp(var(--sp-8),9vw,var(--sp-10));
}
body.notfound .nf__inner{
  display:grid;
  justify-items:center;
  gap:var(--sp-5);
}
body.notfound .nf__star{
  width:clamp(3.25rem,8vw,5rem);
  height:auto;
}
/* star pops on reveal then spins (same recipe as .badge--star, §B11) */
body.revealed .nf__star{
  animation:pop var(--dur-pop) linear both,
            spin var(--dur-spin) linear .4s infinite;
}
body.notfound .nf__kicker{ font-size:var(--fs-sm); }
body.notfound .nf__code{ line-height:.82; }              /* tighten the giant glyph */
body.notfound .nf__lead{ font-size:var(--fs-lead); line-height:1.5; max-width:40ch; }
body.notfound .nf__cta{
  display:flex;
  flex-wrap:wrap;
  gap:var(--sp-3);
  justify-content:center;
}
body.notfound .nf__foot{ font-size:var(--fs-sm); }
