/* ============================================================
   WOODY'S — components.css
   Corner-pill nav · full-screen overlay menu · frost + scroll
   lock · loader · pills ·
   cards + badges · marquee (bone strip) · rail + chunky
   scrollbar · tags · vtitle · reveal states · footer.
   Surface law (§1.3): text on LIGHT surfaces is ink, text on
   DARK surfaces is bone. Solid pills are ALWAYS ink fill / bone
   label. No opacity-dimmed text. Signature stroke = 3px ink.
   ============================================================ */

/* ---------- Wobble filter helpers ---------- */
.wobble{ filter:url(#wobble); }
.wobble-strong{ filter:url(#wobble-strong); }

/* ============================================================
   PILL BUTTON — ink fill, bone label, pop + blink on hover
   ============================================================ */
.pill{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:.5em;
  min-height:2.75rem;
  padding:.55em 1.2em;
  background:var(--ink);
  color:var(--bone);               /* label is ALWAYS bone on ink (§1.3.3) */
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-pill);
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.02em;
  line-height:1;
  transition:transform .12s ease-out, background .15s ease-out, color .15s ease-out;
}
.pill__label{ display:inline-block; }

/* ghost pill — transparent; ink on light surfaces, bone on dark */
.pill--ghost{
  background:transparent;
  color:var(--ink);
  border-color:var(--ink);
}
.band--b .pill--ghost, .band--dark .pill--ghost, footer .pill--ghost{
  color:var(--bone);
  border-color:var(--bone);
}

/* hover tells — one-shot pop on the pill, blink on the label */
.can-hover .pill:hover{ animation:pop var(--dur-pop) linear; }
.can-hover .pill:hover .pill__label{ animation:blink var(--dur-blink) linear infinite; }
.can-hover .pill--ghost:hover{ background:var(--ink); color:var(--bone); }
.can-hover .band--b .pill--ghost:hover,
.can-hover .band--dark .pill--ghost:hover,
.can-hover footer .pill--ghost:hover{ background:var(--bone); color:var(--dark); }

.pill--sm{ min-height:2.25rem; padding:.4em .9em; font-size:var(--fs-sm); }
.pill--lg{ min-height:3.25rem; padding:.7em 1.6em; font-size:var(--fs-lead); }

/* touch invert — works on every surface, so it stays last (§B10) */
.pill:active{
  transform:translateY(1px) scale(.98);
  background:var(--bone);
  color:var(--ink);
  border-color:var(--ink);
}

/* ============================================================
   CORNER NAV — fixed logo (left) + pill (right) (§B5)
   ============================================================ */
.corner-nav .pill{
  position:fixed;
  z-index:var(--z-nav);
  top:1rem;
  transition:transform .12s ease-out,
             background .15s ease-out, color .15s ease-out;
}
/* Icon-only since the label became a hamburger. Squared off so it reads as a
   button rather than a stretched text pill, keeping the 2.75rem min-height the
   other pills use so the corner chrome stays on one rhythm. The hover blink
   moves from .pill__label (which this button no longer has) to the icon. */
.corner-menu{ right:1rem; width:2.75rem; padding:0; }
.corner-menu__icon{ width:1.4rem; height:1.4rem; display:block; }
.can-hover .corner-menu:hover .corner-menu__icon{ animation:blink var(--dur-blink) linear infinite; }

/* brand = the real logo. Width is capped well under its 488px native
   size, so it is always downscaled and never soft. */
.corner-brand{
  position:fixed;
  z-index:var(--z-nav);
  top:1rem;
  left:1rem;
  display:block;
  width:clamp(76px, 8vw, 108px);
  transition:transform .15s ease-out;
}
.corner-brand img{ width:100%; height:auto; }
.can-hover .corner-brand:hover{ transform:scale(1.04) rotate(-1.5deg); }

/* home: the giant hero wordmark owns the top — corner brand pops in
   only once scrolled past the hero (body.wm-docked, set by woody.js) */
body.is-home .corner-brand{
  opacity:0;
  visibility:hidden;
  pointer-events:none;
  transition:opacity .2s ease-out, visibility 0s linear .2s;
}
body.is-home.wm-docked .corner-brand{
  opacity:1;
  visibility:visible;
  pointer-events:auto;
  animation:pop var(--dur-pop) linear;
  transition:opacity .2s ease-out, visibility 0s;
}

/* On home the MENU pill sits over the photo hero until the page docks. A solid
   ink pill loses its shape entirely against a dark frame — the bone label ends
   up floating with no button under it. Outline it in bone over a dark wash
   while it is on the photo, then hand back to the default pill once it docks
   onto the light bands. */
body.is-home:not(.wm-docked) .corner-menu{
  background:color-mix(in srgb, var(--ink) 70%, transparent);
  border-color:var(--bone);
  color:var(--bone);
}
.can-hover body.is-home:not(.wm-docked) .corner-menu:hover{
  background:var(--bone);
  color:var(--ink);
}

/* ============================================================
   FULL-SCREEN OVERLAY MENU — frosted bone, z 950 (§B6)
   ============================================================ */
.overlay-menu{
  position:fixed;
  inset:0;
  z-index:var(--z-menu);
  display:flex;
  flex-direction:column;
  gap:var(--sp-6);
  padding:1rem clamp(1.5rem,6vw,4rem) clamp(1.5rem,4vw,2.5rem);
  background:var(--bone);
  background:color-mix(in srgb, var(--bone) 90%, transparent);
  color:var(--ink);
  visibility:hidden;
  opacity:0;
  transition:opacity var(--dur-frost) ease-out,
             visibility 0s linear var(--dur-frost);
  overflow:auto;
  overscroll-behavior:contain;
}
body.menu-open .overlay-menu{
  visibility:visible;
  opacity:1;
  transition:opacity var(--dur-frost) ease-out,
             visibility 0s;
}
.overlay-menu__top{
  display:flex;
  justify-content:space-between;
  align-items:center;
  gap:var(--sp-4);
}
.overlay-menu__mark{ width:clamp(96px, 12vw, 136px); height:auto; }
.overlay-menu__list{
  display:grid;
  gap:clamp(.25rem, 1.2vh, .75rem);
  margin-block:auto;
}
.overlay-menu__link{
  width:max-content;
  max-width:100%;
  color:var(--ink);
  font-family:var(--font-display);
  font-weight:400;                 /* Graduate ships one weight — see base §7 */
  text-transform:uppercase;
  letter-spacing:.015em;
  font-size:clamp(2.5rem, 9vw, 6.5rem);
  line-height:1.02;
}
/* Staggered entrance while open. Uses rise-in-Y — movement only, NO opacity —
   and that is the whole fix for the stranded-faded link (D28).
   It used to be `rise-in ... both`, which animates opacity AND holds its end
   frame. That made the link's resting opacity the property of a FILLED
   ANIMATION rather than of the cascade, so anything that disturbed that
   animation — a restart, a delay's backwards fill, a :hover latched while the
   overlay closed — left the link sitting faded with nothing to restore it. It
   reads exactly like "the blink paused instead of going back to normal".
   Now nothing animates these links' opacity except the blink itself, and there
   is no opacity declaration on .overlay-menu__link. So the instant the pointer
   leaves and the blink is removed, opacity is 1 by cascade — unconditionally,
   whatever frame it stopped on. That is also what makes it safe for the blink
   to reach 0 again. Do NOT put an opacity-animating entrance back on these. */
body.menu-open .overlay-menu__link{
  animation:rise-in-y var(--dur-reveal) ease-out both;
  animation-delay:calc(var(--i)*.06s);
}
.can-hover .overlay-menu__link:hover{
  animation:rise-in-y var(--dur-reveal) ease-out both,
            blink var(--dur-blink) linear infinite;
  animation-delay:calc(var(--i)*.06s), 0s;
}
.overlay-menu__link[aria-current="page"]{
  background:linear-gradient(var(--ink),var(--ink)) no-repeat 0 100%/100% .14em;
}
.overlay-menu__foot p + p{ margin-top:var(--sp-1); }

/* ============================================================
   FROST + SCROLL LOCK (§B6/§B7)
   ============================================================ */
#page{
  transition:filter var(--dur-frost) ease-out;
}
body.menu-open #page{ filter:blur(30px); pointer-events:none; }
body.menu-open{ overflow:hidden; }

/* ============================================================
   LOADER CURTAIN — ink ground, the logo, dead-man bail (§B16)
   ============================================================ */
#loader{
  position:fixed;
  inset:0;
  z-index:var(--z-loader);
  display:grid;
  place-items:center;
  background:var(--ink);
  animation:loader-bail 0s 4s forwards;   /* self-hides even if JS dies */
}
body.loaded #loader{
  opacity:0;
  visibility:hidden;
  transition:opacity .4s ease-out, visibility 0s linear .4s;
}

/* ============================================================
   CARD + BADGE — bone surfaces, ink stroke; the tell is the badge
   ============================================================ */
.card{
  position:relative;
  display:flex;
  flex-direction:column;
  background:var(--bone);
  color:var(--ink);
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-card);
  overflow:hidden;
}
.card__media{
  position:relative;
  aspect-ratio:4 / 3;
  display:grid;
  place-items:center;
  border-bottom:var(--stroke) solid var(--ink);
  background:var(--band-a);
  overflow:hidden;
}
.card__media img,
.card__media svg{ width:100%; height:100%; object-fit:cover; }
.card__body{ display:grid; gap:var(--sp-2); padding:var(--sp-4); }
.card__title{
  font-family:var(--font-display);
  font-weight:400; text-transform:uppercase; letter-spacing:.02em;
  font-size:var(--fs-h2); line-height:1.08;
}
.card__meta{
  display:flex;
  justify-content:space-between;
  align-items:baseline;
  gap:var(--sp-3);
  font-size:var(--fs-sm);
}
.card__price{ font-weight:700; }
.card__blurb{ font-size:var(--fs-sm); line-height:1.5; }

.badge{
  position:absolute;
  top:var(--sp-4);
  right:var(--sp-4);
  z-index:var(--z-badge);
  display:inline-grid;
  place-items:center;
  padding:.4em .8em;
  background:var(--ink);
  color:var(--bone);
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-pill);
  font-size:var(--fs-sm);
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.02em;
}
/* the hover tell: badge bounces on card hover — ALL cards (§B11) */
.can-hover .card:hover .badge{ animation:bounce var(--dur-bounce) ease-out infinite; }
/* touch: plain badges bounce always */
@media (hover:none){
  .badge:not(.badge--star){ animation:bounce var(--dur-bounce) ease-out infinite; }
}

/* rotating starburst badge — pops in on reveal, then spins.
   No fill of its own, so the artwork re-inks per surface law:
   ink on light surfaces, bone on dark. */
.badge--star{
  width:5.5rem; height:5.5rem;
  padding:0;
  background:transparent;
  border:0;
  border-radius:0;
  color:var(--ink);
}
.band--b .badge--star, .band--dark .badge--star, footer .badge--star{ color:var(--bone); }
body.revealed .badge--star{
  animation:pop var(--dur-pop) linear both,
            spin var(--dur-spin) linear .4s infinite;
}
.badge--star svg,
.badge--star img{ width:100%; height:100%; }

/* ============================================================
   MARQUEE — bone strip, ink text, 2.75rem tall (§B2)
   ============================================================ */
.marquee{
  position:relative;
  z-index:var(--z-marquee);
  display:flex;
  align-items:center;
  height:var(--marquee-h);
  overflow:hidden;
  background:var(--bone);
  color:var(--ink);
  border-top:var(--stroke) solid var(--ink);
  border-bottom:var(--stroke) solid var(--ink);
  -webkit-user-select:none;
  user-select:none;
}
.marquee__track{ display:flex; width:max-content; align-items:center; }
.marquee__row{
  display:flex;
  flex:0 0 auto;
  align-items:center;
  white-space:nowrap;
  animation:marquee 20s linear infinite;   /* duration overwritten by JS: width/60 */
  will-change:transform;
}
.marquee__item{
  display:inline-flex;
  align-items:center;
  gap:.6em;
  padding-inline:var(--sp-5);
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.03em;
  font-size:var(--fs-lead);
}
/* Every item carries an icon now, referenced from the <symbol> set defined
   once inside the marquee. `.marquee__glass` went with the old hand-inlined
   glass — the descendant selector covers every icon without naming any. */
.marquee__item svg{ width:1.2em; height:1.2em; flex:0 0 auto; }
.marquee__dot{ font-weight:700; }
/* pause on hover for readability (desktop) */
.can-hover .marquee:hover .marquee__row{ animation-play-state:paused; }

/* ============================================================
   HORIZONTAL RAIL — snap + chunky ink scrollbar (§B12)
   ============================================================ */
.rail{
  display:flex;
  gap:var(--sp-5);
  overflow-x:auto;
  overflow-y:hidden;
  scroll-snap-type:x mandatory;
  -webkit-overflow-scrolling:touch;
  padding-bottom:var(--sp-4);
}
.rail > *{
  flex:0 0 auto;
  scroll-snap-align:start;
  width:min(20rem, 78vw);
}
.rail::-webkit-scrollbar{ height:1.5rem; }
.rail::-webkit-scrollbar-track{
  background:transparent;
  border-radius:var(--radius-pill);
}
.rail::-webkit-scrollbar-thumb{
  background:var(--ink);
  border-radius:var(--radius-pill);
  border:.35rem solid transparent;
  background-clip:padding-box;
}
/* Firefox only — the unconditional form kills Chromium's custom bar */
@supports (-moz-appearance:none){
  .rail{ scrollbar-color:var(--ink) transparent; }
}

/* The menu's card grid (#grid) lived here. It was a JS density toggle
   (view-1/2/3 + a blur pulse), then plain 3->2->1 columns after D24, and it
   is gone entirely with D25: the menu is a two-column LIST on a bone sheet
   now, styled in pages.css under MENU. Nothing else used #grid. */

/* The menu's category chips and their scroll-spy .is-active tell lived here.
   Removed 2026-08-14 (D26) — the menu is one two-column sheet now, so there was
   nothing left to jump past. */

/* ============================================================
   TAGS
   ============================================================ */
.tags{ display:flex; flex-wrap:wrap; gap:var(--sp-2); }
.tag{
  display:inline-flex;
  align-items:center;
  gap:.4em;
  padding:.3em .8em;
  border:var(--stroke) solid var(--ink);
  border-radius:var(--radius-pill);
  font-size:var(--fs-sm);
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.02em;
  line-height:1;
}
.tag svg,
.tag img{ width:1.1em; height:1.1em; flex:0 0 auto; }
.can-hover .tag:hover{ background:var(--ink); color:var(--bone); }

/* ============================================================
   GLOBAL INTERACTIVE TELLS (§B14)
   ============================================================ */
.can-hover .section-head__link:hover{ animation:blink var(--dur-blink) linear infinite; }

/* ============================================================
   IO SCROLL-REVEAL STATES (§B15) — JS adds .reveal-pre only when
   motion is allowed, so no-JS / reduced-motion always see content
   ============================================================ */
.reveal-pre{ opacity:0; transform:translateY(14px) scale(.98); }
.reveal-in{
  animation:rise-in var(--dur-reveal) ease-out both;
  animation-delay:calc(var(--rvl-i,0)*.07s);
}

/* ============================================================
   VERTICAL SECTION TITLE
   ============================================================ */
.vtitle{
  display:grid;
  justify-items:center;
  gap:.04em;
  font-family:var(--font-display);
  font-weight:400;
  text-transform:uppercase;
  font-size:var(--fs-h2);
  line-height:1;
  letter-spacing:0;
}
.vtitle span{ display:block; }
.vtitle--rot{ transform:rotate(-90deg); transform-origin:center; }

/* ============================================================
   FOOTER — dark band (set in base), 4 -> 2 -> 1 columns (§B13)
   ============================================================ */
.site-footer{ margin-top:auto; }
.site-footer__grid{
  display:grid;
  grid-template-columns:repeat(4,1fr);
  gap:var(--sp-7);
  padding-block:var(--sp-9) var(--sp-7);
}
.site-footer__col h3{
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.03em;
  font-size:var(--fs-sm);
  margin-bottom:var(--sp-3);
}
.site-footer__col li + li{ margin-top:var(--sp-2); }
/* .social is excluded: the underline tell is drawn as a background gradient, so
   on a bordered icon button it lands as a stray bar across the bottom edge. */
.can-hover .site-footer__col a:not(.pill):not(.social):hover{
  background:linear-gradient(var(--bone),var(--bone)) no-repeat 0 100%/100% .12em;
}

/* Social icon links (footer "Find us"). Icon-only, so the accessible name is on
   the <a> and the glyph is aria-hidden. 44x44 is the same touch target the
   corner hamburger uses. Marks are drawn in the house line style rather than
   Meta's brand SVGs — the site ships no third-party artwork. */
.socials{ display:flex; gap:var(--sp-3); flex-wrap:wrap; }
.socials li + li{ margin-top:0; }        /* they sit in a row, not a stack */
.social{
  display:grid;
  place-items:center;
  width:2.75rem;
  height:2.75rem;
  border:var(--stroke) solid currentColor;
  border-radius:var(--radius-pill);
}
.social svg{ width:1.375rem; height:1.375rem; display:block; }
/* Inverts like the rest of the site's pills: bone fill, surface-coloured mark. */
.can-hover .social:hover{ background:var(--bone); color:var(--dark); }
.hours{ font-size:var(--fs-sm); }
.hours td{ padding:.18em 0; border:0; vertical-align:top; }
.hours td:last-child{ text-align:right; padding-left:var(--sp-3); }
/* today's row — bold + marker underline (bone on dark, ink in main) */
.hours .is-today td{
  font-weight:700;
  background:linear-gradient(var(--bone),var(--bone)) no-repeat 0 100%/100% .14em;
}
main .hours .is-today td{
  background-image:linear-gradient(var(--ink),var(--ink));
}
.site-footer__mark{
  padding-block:var(--sp-6);
  border-top:var(--stroke) solid var(--bone);
  display:grid;
  justify-items:center;
}
.site-footer__mark-link{ display:block; }
/* capped at the logo's 488px native width — it is a raster lifted off the
   menu artwork, so stretching it to the footer width would visibly soften it */
.site-footer__mark img{
  width:min(100%, 488px);
  height:auto;
  transition:transform .25s ease-out;
}
.can-hover .site-footer__mark-link:hover img{ transform:scale(1.03) rotate(-1deg); }
.site-footer__base{
  display:flex;
  flex-wrap:wrap;
  gap:var(--sp-3) var(--sp-5);
  justify-content:space-between;
  align-items:center;
  padding-block:var(--sp-4) var(--sp-8);
  font-size:var(--fs-sm);
}
.site-footer__divider{ width:100%; height:auto; margin-block:var(--sp-4); opacity:.9; }

/* ---- The Standard Systems sign-off ----
   The builder's tag, shipped on every SS build. Surround is Graduate caps so it
   speaks in the site's own voice; the token stays LOWERCASE Space Mono because
   the lowercase letters and the trailing underscore ARE the wordmark - hence the
   explicit text-transform:none, which the uppercase parent would otherwise eat.
   Contrast on the maroon footer: 4.62 at rest for the surround (AA normal text),
   9.81 for the token. */
.site-footer__credit{
  font-family:var(--font-display);
  font-size:clamp(1.25rem, 2.6vw, 1.875rem);
  line-height:1.2;
  letter-spacing:.02em;
  text-transform:uppercase;
  text-align:center;
  color:color-mix(in srgb, var(--bone) 62%, transparent);
  padding-block:var(--sp-2) var(--sp-3);
}
.standard-link{
  font-family:var(--font-mono);
  font-weight:700;
  text-transform:none;
  color:var(--bone);
}
/* Standard Systems brand gradient, cyan -> magenta, clipped to the glyphs.
   These two hexes are the ONLY colours in this stylesheet that are not Woody's:
   they are the BUILDER's mark, ported verbatim from Barrels Up and Castro's
   Angels so the tag reads identically across every SS site. The palette grep in
   CLAUDE.md rule 3 should expect them. Hover contrast on maroon: 4.93 cyan end,
   3.06 magenta end - both clear AA at this size/weight (large text). */
.can-hover .standard-link:hover,
.standard-link:active,
.standard-link:focus-visible{
  background-image:linear-gradient(to right, #38BDF8, #D946EF);
  -webkit-background-clip:text;
  background-clip:text;
  -webkit-text-fill-color:transparent;
  color:transparent;
}

/* ============================================================
   RESPONSIVE — breakpoints 1200 / 1024 / 812 / 600 / 480 / 380
   ============================================================ */
@media (max-width:1200px){
  .marquee__item{ font-size:var(--fs-body); }
}
@media (max-width:1024px){
  .site-footer__grid{ gap:var(--sp-6); }
}
@media (max-width:812px){
  .site-footer__grid{ grid-template-columns:repeat(2,1fr); }
}
@media (max-width:600px){
  .rail > *{ width:min(18rem, 82vw); }
}
@media (max-width:480px){
  .site-footer__grid{ grid-template-columns:1fr; }
}
@media (max-width:380px){
  .pill{ padding:.5em 1em; }
  .marquee__item{ padding-inline:var(--sp-4); }
}
