/* ============================================================
   PADMA — Specialtea House, Craiova
   Black / white editorial. Photography carries the color.
   Hand-drawn ink layer extracted from the owner's concept PDF.
   ============================================================ */

:root {
  --ink: #0a0a0a;
  --paper: #ffffff;
  --smoke: #6f6f6f;
  --hairline: rgba(10, 10, 10, 0.14);
  --display: "Archivo Black", "Arial Black", sans-serif;
  --sans: "Hanken Grotesk", "Helvetica Neue", Arial, sans-serif;
  --serif: "EB Garamond", Georgia, serif;
  --header-h: 92px;
  --mh-strip: 92px;
  --gutter: clamp(20px, 4vw, 64px);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* The display face for the two places the site shouts: the hero wordmark
   and the daub that heads each menu board. Self-hosted, subset to the
   Latin caps and lowercase plus the Romanian diacritics — 4KB. `block`
   rather than `swap`: the hero's outline copy traces the filled one, so a
   fallback face swapping in mid-flight would show two different words for
   a beat. */
@font-face {
  font-family: "TT Hoves Pro Expanded";
  src: url("../assets/fonts/hoves-exp-extrabold.woff2") format("woff2");
  font-weight: 800;
  font-style: normal;
  font-display: block;
}

/* The boot gate is inline in index.html's <head>, not here — a stylesheet
   still in flight loses the race to Firefox's first paint. */

html { scroll-behavior: smooth; }

body {
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  /* clip, not hidden: hidden makes body a scroll container and breaks
     position: sticky anywhere on the page */
  overflow-x: clip;
}

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

a { color: inherit; text-decoration: none; }

::selection { background: var(--ink); color: var(--paper); }

:focus-visible { outline: 3px solid currentColor; outline-offset: 3px; }

.popup .stroke-link:focus-visible { outline-color: var(--ink); }
.careers-cta .stroke-link:focus-visible { outline-color: var(--paper); }

.skip-link {
  position: fixed;
  top: -100px;
  left: 16px;
  z-index: 100;
  background: var(--paper);
  color: var(--ink);
  font-weight: 800;
  padding: 12px 18px;
  transition: top 0.2s ease;
}
.skip-link:focus-visible { top: 12px; }

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

/* ---------- reveal-on-scroll baseline ---------- */

.js .rv {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.8s ease, transform 0.8s cubic-bezier(0.22, 1, 0.36, 1);
}
.rv.in-view { opacity: 1; transform: none; }
.rv-d1 { transition-delay: 0.12s; }
.rv-d2 { transition-delay: 0.24s; }
.rv-d3 { transition-delay: 0.36s; }

/* ============================================================
   Header
   ============================================================ */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  background: var(--ink);
  color: var(--paper);
  height: var(--header-h);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.logo-eyes {
  width: 74px;
  height: auto;
  color: var(--paper);
  animation: blink 7s infinite;
  transform-origin: 50% 50%;
}

@keyframes blink {
  0%, 90.5%, 93.5%, 100% { transform: scaleY(1); }
  92% { transform: scaleY(0.08); }
}

/* header eyes are JS-scheduled: singles, occasional doubles, click blinks */
.logo-eyes.js-blink { animation: none; }
.logo-eyes.blink { animation: eye-blink 0.3s ease-in-out 1; }
.logo-eyes.blink2 { animation: eye-blink2 0.64s ease-in-out 1; }

@keyframes eye-blink {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(0.08); }
}

@keyframes eye-blink2 {
  0%, 44%, 100% { transform: scaleY(1); }
  22%, 72% { transform: scaleY(0.08); }
}

.site-nav ul {
  list-style: none;
  display: flex;
  gap: clamp(14px, 2.6vw, 44px);
}

.site-nav a {
  font-weight: 700;
  font-size: clamp(13px, 1.1vw, 17px);
  letter-spacing: 0.01em;
  position: relative;
  padding: 4px 2px;
}

.site-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 100%;
  bottom: -2px;
  height: 3px;
  background: var(--paper);
  border-radius: 2px;
  transition: right 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
.site-nav a:hover::after,
.site-nav a:focus-visible::after { right: 0; }

.nav-toggle { display: none; }

/* ro/en switch — label type parked at the header's right edge */
.lang-switch {
  position: absolute;
  top: 50%;
  right: clamp(16px, 2.5vw, 36px);
  transform: translateY(-50%);
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--paper);
  font-family: var(--sans);
  font-weight: 800;
  font-size: 12px;
  letter-spacing: 0.18em;
  cursor: pointer;
  opacity: 0.85;
  transition: opacity 0.2s ease, letter-spacing 0.25s ease;
}
.lang-switch:hover,
.lang-switch:focus-visible { opacity: 1; letter-spacing: 0.24em; }

/* mobile nav */
@media (max-width: 760px) {
  :root { --header-h: 72px; --mh-strip: 94px; }

  /* One row, four flex items: logo | wayfinder | EN | hamburger. The
     wayfinder is a flex item rather than something absolutely centred
     over the bar, so it cannot ride over the language switch however
     long a section name runs — the row simply divides the width. */
  .site-header {
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 6px;
    padding: 0 10px;
  }

  .logo-eyes { width: 40px; flex: none; }

  /* out of its absolute corner and into the row, after the wayfinder */
  .lang-switch {
    position: static;
    transform: none;
    order: 2;
    flex: none;
    min-width: 36px;
    padding: 0;
  }
  .nav-toggle { order: 3; flex: none; }

  .nav-toggle {
    display: grid;
    place-items: center;
    gap: 0;
    background: none;
    border: 0;
    width: 44px;
    height: 44px;
    cursor: pointer;
  }
  .nav-toggle span {
    grid-area: 1 / 1;
    width: 26px;
    height: 3px;
    background: var(--paper);
    border-radius: 2px;
    transition: transform 0.3s ease, opacity 0.3s ease;
  }
  .nav-toggle span:nth-child(1) { transform: translateY(-8px); }
  .nav-toggle span:nth-child(3) { transform: translateY(8px); }
  .nav-open .nav-toggle span:nth-child(1) { transform: rotate(45deg); }
  .nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
  .nav-open .nav-toggle span:nth-child(3) { transform: rotate(-45deg); }

  .site-nav {
    position: fixed;
    inset: var(--header-h) 0 0 0;
    background: var(--ink);
    /* the sheet carries the visit card under the links, so it stacks
       rather than centring a single child */
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 32px;
    overflow-y: auto;
    padding: 24px var(--gutter) calc(26px + env(safe-area-inset-bottom, 0px));
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s;
  }
  .nav-open .site-nav { opacity: 1; visibility: visible; }
  body.nav-open { overflow: hidden; }
  .site-nav ul { flex-direction: column; text-align: center; gap: 30px; }
  .site-nav a { font-size: 26px; }

  /* where/when, on the one screen a thumb reaches without scrolling
     (specificity beats the desktop hide rule below, whatever the order) */
  .site-nav .nav-visit {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
    padding-top: 24px;
    border-top: 1px solid rgba(255, 255, 255, 0.16);
    width: min(340px, 100%);
  }
  .nav-visit-state {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 11.5px;
    font-weight: 800;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--paper);
  }
  .nav-visit-state::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    border: 1.5px solid currentColor;
    transform: translateY(-1px);
  }
  .nav-visit-state.is-open::before { background: currentColor; }
  .nav-visit-state:not(.is-open) { color: #a8a8a8; }
  /* these are links, not nav items — the 26px rule above must not win.
     Padded to a 44px thumb target without looking like buttons. */
  .site-nav .nav-visit-line {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.4;
    color: #e4e4e4;
    padding: 2px 8px;
  }
  .nav-visit-hours {
    font-size: 12.5px;
    line-height: 1.5;
    color: #b4b4b4;
  }
}
/* the visit card belongs to the mobile sheet only */
.nav-visit { display: none; }

/* ============================================================
   Section wayfinder — the mobile topbar's middle: where you are,
   big in the centre, the sections either side small and higher up
   with a curved arrow leaning out of each.

   It is a conveyor, not three labels. Each section is one node
   that travels between slots, so on scrolling down the previous
   one leaves, the current shrinks out to the previous slot, the
   next grows into the middle, and a new next drops in from above.
   Slots are pure transform + opacity, which is why the type can
   change size mid-flight without reflowing the header.
   ============================================================ */

.section-wayfinder { display: none; }

@media (max-width: 760px) {
  .section-wayfinder {
    --sw-x: 72px;          /* how far the side slots sit out; JS tunes it */
    --sw-side: 0.56;       /* side type as a fraction of the centre's */
    display: block;
    position: relative;
    /* the height of what is actually in it. At 40 the box was centred in
       the bar but the content only filled its top 28, so the trio rode
       5.5px high. Sized to the content, the row's own centring lands it. */
    height: 31px;
    /* the row's elastic middle: it takes whatever the logo, the language
       switch and the hamburger leave, and never more */
    order: 1;
    flex: 1 1 auto;
    min-width: 0;
    /* clips the arrivals and departures against the bar's own edges */
    overflow: hidden;
    /* the browser keeps vertical panning; horizontal is ours to read as
       a swipe, without having to cancel the touch by hand */
    touch-action: pan-y;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0s linear 0.3s;
  }
  .section-wayfinder.is-live {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.3s ease;
  }
  /* the sheet is the whole screen; the header behind it stays bare */
  body.nav-open .section-wayfinder {
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, visibility 0s linear 0.2s;
  }

  .sw-item {
    position: absolute;
    top: 0;
    left: 50%;
    color: var(--paper);
    white-space: nowrap;
    transform-origin: 50% 0;
    transform: translate(calc(-50% + var(--x, 0px) + var(--sw-drag, 0px)), var(--y, 0px))
               scale(var(--s, 1));
    opacity: var(--o, 1);
    transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),
                opacity 0.42s ease;
  }
  /* under the finger the strip tracks it directly; the snap back after
     the lift is what gets the easing */
  .section-wayfinder.is-dragging .sw-item { transition: opacity 0.42s ease; }

  .sw-txt {
    display: block;
    font-family: var(--sans);
    font-weight: 800;
    /* one size for every slot — scale does the shrinking, so the type
       animates between sizes instead of snapping */
    font-size: clamp(13px, 3.9vw, 16px);
    line-height: 1;
    letter-spacing: 0;
    text-transform: uppercase;
  }

  /* hangs off the label so it never adds to the item's box, which keeps
     every slot measured from the type alone */
  .sw-arw {
    position: absolute;
    top: 100%;
    left: 50%;
    /* JS sizes it to the gap it has to sit in; 40:28 is the viewBox */
    width: var(--aw, 24px);
    height: calc(var(--aw, 24px) * 0.7);
    margin-top: 1px;
    transform: translateX(-50%);
    opacity: 0;
    transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),
                opacity 0.35s ease;
  }
  .sw-arw path {
    fill: none;
    stroke: currentColor;
    stroke-width: 2.2;
    stroke-linecap: round;
    stroke-linejoin: round;
    /* pathLength is 1 on every path, so one dash the whole length of it
       and an offset of 1 holds it undrawn */
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    transition: stroke-dashoffset 0.4s cubic-bezier(0.22, 1, 0.36, 1);
  }
  /* Which curve shows is the dash's job now, not opacity's. Cross-fading
     the two groups meant that for a third of a second mid-change both
     were half-visible and the arrow read as a smudge of two arrows. Drawn
     on and pulled off, the swap happens while there is nothing to see. */
  .sw-ap, .sw-an { opacity: 1; }
  .is-prev .sw-arw, .is-next .sw-arw { opacity: 0.9; }

  /* in: the shaft runs first and the head lands on the end of it.
     out: the head goes first and the shaft retracts after it — the
     stroke leaves the way a pen would lift, tip last on, tip first off */
  .sw-arw .sw-shaft { transition-delay: 0.12s; }
  .is-prev .sw-ap .sw-shaft,
  .is-next .sw-an .sw-shaft { stroke-dashoffset: 0; transition-delay: 0s; }
  .is-prev .sw-ap .sw-head,
  .is-next .sw-an .sw-head { stroke-dashoffset: 0; transition-delay: 0.15s; }

  /* An arrow reads as a link between two names, so it belongs in the gap
     rather than tucked under its own label. JS measures the gap and sets
     --anx per item (sign included); the arrow rides inside the scaled
     item, so it still travels and shrinks with it. */
  .is-prev .sw-arw, .is-next .sw-arw {
    transform: translateX(-50%) translateX(var(--anx, 0px));
  }

  /* ---- the slots ---- */
  .is-current {
    --x: 0px; --y: 14px; --s: 1; --o: 1;
    pointer-events: none;
  }
  .is-prev  { --x: calc(var(--sw-x) * -1); --y: 1px; --s: var(--sw-side); --o: 0.7; }
  .is-next  { --x: var(--sw-x);            --y: 1px; --s: var(--sw-side); --o: 0.7; }
  .is-prev:hover, .is-prev:focus-visible,
  .is-next:hover, .is-next:focus-visible { --o: 1; }

  /* arriving: parked above its own side, then released into the slot */
  .is-in-prev { --x: calc(var(--sw-x) * -1); --y: -20px; --s: var(--sw-side); --o: 0; pointer-events: none; }
  .is-in-next { --x: var(--sw-x);            --y: -20px; --s: var(--sw-side); --o: 0; pointer-events: none; }

  /* leaving: drifts on outwards and shrinks away. It holds the side
     slot's own height on the way out — lifting it as it goes ran it
     into the bar's top edge and it left as a sliced half-word. */
  .is-out-prev { --x: calc(var(--sw-x) * -1.5); --y: 1px; --s: 0.42; --o: 0; pointer-events: none; }
  .is-out-next { --x: calc(var(--sw-x) * 1.5);  --y: 1px; --s: 0.42; --o: 0; pointer-events: none; }
}

@media (prefers-reduced-motion: reduce) {
  .section-wayfinder, .section-wayfinder.is-live { transition: none; }
  .sw-item, .sw-arw, .sw-ap, .sw-an { transition: none; }
  /* no draw-on, and never a half-drawn arrow: they are simply there */
  .sw-arw path { transition: none; }
}
/* authored `display` outranks the [hidden] UA rule — say it explicitly, so
   the state chips stay invisible until JS has a real answer to show */
.f2-open[hidden],
.nav-visit-state[hidden] { display: none; }

/* ============================================================
   Hero
   ============================================================ */

.hero {
  padding: var(--header-h) 14px 0;
  background: var(--paper);
}

.hero-frame {
  position: relative;
  height: calc(100svh - var(--header-h) - 14px);
  min-height: 480px;
  overflow: hidden;
}

/* Three planes, three depths. Each is driven by --pz: how far it lags the
   scroll. The street lags most and reads as far away; the two of them barely
   lag at all and read as near; the name sits between, which is what puts
   them in front of it. All three overscale so no edge can ever slide in. */
.hero-plane {
  position: absolute;
  inset: 0;
  will-change: transform;
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0);
}
.hero-back  { --pz: 0.24; z-index: 0; }
.hero-front { --pz: -0.06; z-index: 3; }

.hero-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 60%;
}

/* the plate is scaled a touch harder than the cutout, so the inpainted
   ground behind them stays covered at every offset the parallax reaches */
.hero-back img  { transform: scale(1.12); }
.hero-front img { transform: scale(1.05); }

/* phones crop the 3:2 scene to its middle third — recentre that window on
   the two of them. The plate keeps enough overscale (1.06) to cover its
   scroll-parallax travel while the frame top is still on screen. The
   cutout can't ride the same cover crop: the pair spans more of the
   photograph than a phone-shaped window shows, so cover sliced the
   girl's arm off at the window's edge. Instead it keeps its own aspect
   ratio at 90% of the frame's height — nothing crops — anchored to the
   frame bottom so their legs stay grounded, and slid so the pair sits
   a step right of the frame's middle (their centre is 56% across the
   photograph; aligning 54% leaves them slightly right of centre). */
@media (max-width: 760px) {
  .hero-frame img { object-position: 55% 60%; }
  /* the cutout ends at the photograph's bottom edge — mid-shin — so any
     upward lead lifts that raw cut off the frame bottom and shows it.
     Desktop hides this inside the cover crop's overflow; the phone layout
     bottom-anchors the full cutout, so here the pair must not move */
  .hero-front { --pz: 0; }
  /* the plate slides further right than the pair so the lens sign on the
     shopfront (76–84% across the photograph) clears the crop and hangs in
     the sky just above the girl */
  .hero-back img  { transform: scale(1.06); object-position: 81% 60%; }
  .hero-front img {
    position: absolute;
    bottom: 0;
    left: 50%;
    width: auto;
    max-width: none;
    height: 90%;
    transform: translateX(-54%);
  }
}

/* No lens entrance here any more. It used to open the scene out of focus,
   which worked while it played during load — but the boot gate now holds
   the page until the photograph is decoded, and blurring an image the
   visitor has just waited for is backwards. It also read as a white flash:
   `filter: blur()` samples past the element's edges, so 16px of it pulled
   the white page in around all four sides of the frame. The paper lifting
   is the arrival; the letters are the moment. */

/* film grain, over every plane */
.hero-frame::after {
  content: "";
  position: absolute;
  inset: -20px;
  pointer-events: none;
  z-index: 1;
  opacity: 0.11;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain 0.9s steps(4) infinite;
}

@keyframes grain {
  0% { transform: translate(0, 0); }
  25% { transform: translate(-7px, 5px); }
  50% { transform: translate(5px, -6px); }
  75% { transform: translate(-4px, -4px); }
  100% { transform: translate(6px, 6px); }
}

/* The name sits in the middle plane, so the two of them stand in front of
   it. Where they cover it, the second copy — stroke only, no fill — keeps
   drawing, and the word reads straight through them. Both copies share one
   transform variable, so the outline can never drift off its letters. */
.hero-word {
  position: absolute;
  inset: 0;
  z-index: 2;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  font-family: "TT Hoves Pro Expanded", var(--display);
  font-weight: 800;
  /* Above 760 this is the whole story: 15.5vw, so the word holds 65% of
     the screen at every size, up to a cap. Phones and the band below the
     breakpoint are set underneath — see the two queries after this rule. */
  font-size: clamp(min(70px, 19.5vw), 15.5vw, 268px);
  line-height: 0.9;
  /* An expanded face carries the width in the letters, so it wants far
     less between them — at the old 0.13em the word came apart. */
  --track: 0.045em;
  letter-spacing: var(--track);
  /* How far out the word opens before it draws in. A wider word has less
     room left to open into: at the old 0.5em the outer letters cleared the
     frame between 760 and 1050 wide and read as flying in from off-screen
     rather than as a track closing. */
  --spread: 0.42em;
  color: var(--paper);
  user-select: none;
  will-change: transform;
  /* --wy is the parallax, which is 0 until you scroll; --drop is where the
     word rests before any of that — low enough that they cross it at the
     waist rather than the chin. Raising it lowers the word itself, and
     leaves how far it drifts on the way out alone. */
  --drop: 14%;
  transform: translate3d(var(--wx, 0px), calc(var(--wy, 0px) + var(--drop)), 0);
}

/* A phone gets the word at a fixed share of the screen, which is what the
   old floor only managed below 359px. `min(70px, 19.5vw)` meant 19.5vw on
   the narrowest phones and then a flat 70px all the way to 451, where
   15.5vw finally overtook it — so the word sat at a constant 291px across
   the entire range real phones occupy, and its share of the screen fell
   from 81% at 360 to 68% at 430. The bigger the phone, the smaller it
   looked, which is backwards.

   19.5vw is that same 81%, held. The margins scale with it, so the tight
   case stays exactly as tight as it was: at 320 the word still clears the
   frame by 9px a side, because both are now the same fraction of the
   screen they always were there. */
@media (max-width: 480px) {
  .hero-word { font-size: 19.5vw; }
}

/* Between the phones and the breakpoint the two shares have to meet: 81%
   at 480 and the 65% the desktop rule already gives at 761. A straight
   line between them, which is what mixing vw with px draws — 8.64vw+52.1px
   passes through 93.6px at 480 and 117.8px at 760, so both ends join
   without a step. It is the only stretch that is not a fixed share, and
   nothing that size is held in a hand. */
@media (min-width: 481px) and (max-width: 760px) {
  .hero-word { font-size: calc(8.64vw + 52.1px); }
}

/* No trailing-space correction here: with the letters as inline-blocks the
   tracking lands between them and not after the last one, so the word is
   already centred — measured, after a correction put it 8px out. */
.hero-word > span { display: block; }
.hero-word i {
  display: inline-block;
  font-style: normal;
  /* how far this letter starts from where it lands, measured out from the
     middle letter of the five — index 2, the D. Symmetric, so the word
     pulls in from both ends at once and its centre never moves. */
  --sx: calc((var(--i) - 2) * var(--spread, 0.5em));
}

/* The word draws in from a wide track to its own spacing — sharp all the
   way; the blur it used to carry softened every edge mid-flight and is
   gone.

   The tracking is animated as a translate on each letter rather than as
   letter-spacing, which would lay the line out again on every frame and
   would also walk the word off centre as it closed — the trailing space
   belongs to the last letter.

   Held until the webfont is in: `font-display: block` renders nothing
   until then, and a cascade that plays behind invisible text would land
   the whole word at once. Without JS there is no cascade and no gate —
   the letters are simply there. */
.js .hero-word i {
  opacity: 0;
  transform: translateX(var(--sx));
}
.js .hero-frame.is-ready .hero-word i {
  animation: hero-letter 1.25s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: 0.22s;
}
/* The sketch copy starts with the fill and fades with the fill, but its
   tracking closes slower — the outline trails the letters on the way in
   and settles onto them last, pencil catching up to paint. That takes
   the entrance split in two: the draw (transform) on its own clock,
   0.3s longer than the fill's, and the fade on the fill's exact one.
   Restated here because an `animation` shorthand on the edge copy
   replaces the fill's rather than joining it; same specificity as the
   rule above, so it must follow it. */
.js .hero-frame.is-ready .hero-word-edge i {
  animation: hero-draw 1.55s cubic-bezier(0.22, 1, 0.36, 1) 0.22s forwards,
             hero-fade 1.25s cubic-bezier(0.22, 1, 0.36, 1) 0.22s forwards,
             hero-hatch 0.42s steps(1) infinite;
}

/* opacity is up well before the rest: at the wide end the letters are
   spread past reading anyway, and holding them faint through the whole
   close would waste the part of it worth watching */
@keyframes hero-letter {
  38% { opacity: 1; }
  to { opacity: 1; transform: none; }
}
/* the same entrance in two halves, for the copy whose halves run on
   different clocks */
@keyframes hero-draw { to { transform: none; } }
@keyframes hero-fade {
  38% { opacity: 1; }
  to { opacity: 1; }
}

.hero-word-edge {
  z-index: 4;
  color: transparent;
  -webkit-text-stroke: 2.6px rgba(255, 255, 255, 0.92);
  /* the stroke is the same white as the fill, so over the street it simply
     disappears into the letter it traces; only over them does it read */
}

/* The outline is not empty: it carries a brush hatch, clipped to the
   glyphs, laid three ways on the paper captions' own boil cadence. The
   strokes are the same white as the fill, so they inherit the stroke's
   whole trick — over the street they vanish into the letter behind them,
   and they only read where the two of them cover that letter. Clipped on
   each `i` rather than the word: the letters are inline-blocks, and they
   paint their own glyphs, so a clip on the flex parent would have nothing
   to hold on to. */
.hero-word-edge i {
  /* A loaded brush — reworked from graphite at the owner's ask. Three
     passes of one hand at one lay — 58 degrees, where a right hand
     falls — with a lighter pass back across it at a shallow angle, the
     way a brush goes back over its own shading. What makes it read as a
     hand is not the strokes but the difference between them: pressure,
     so each carries its own weight, and a wrist, so none is quite
     straight or quite parallel. Every line identical reads as a screen.

     Laid in strokes rather than in lines, though a brush pulls longer
     and lifts less often than the pencil did, and even its light strokes
     sit solid — the ink floor is up in the generator for exactly that.

     Struck in the stroke's own colour — white, at the weight of the
     -webkit-text-stroke above — so the hatch and the line it fills read
     as one mark and not as two brightnesses. The opacity varies freely
     and still disappears over the letter: white at any alpha over white
     is white. Generated by tools/hero-sketch-tiles.py, not editable by
     hand. */
--sk-a: url("data:image/svg+xml,<svg%20xmlns='http%3A//www.w3.org/2000/svg'%20width='128'%20height='128'><g%20fill='none'%20stroke='%23ffffff'%20stroke-linecap='round'><path%20d='M71-61c6,12,13,23,21,33'%20stroke-width='4.2'%20stroke-opacity='0.72'/><path%20d='M98-19c3,5,6,10,9,15'%20stroke-width='2.8'%20stroke-opacity='0.48'/><path%20d='M122,20c6,12,15,23,23,35'%20stroke-width='4.2'%20stroke-opacity='0.52'/><path%20d='M152,66c6,7,11,15,15,23'%20stroke-width='3.5'%20stroke-opacity='0.79'/><path%20d='M183,115c0,0,1,1,1,2'%20stroke-width='2.7'%20stroke-opacity='0.8'/><path%20d='M69-56c15,22,30,45,43,68'%20stroke-width='3.0'%20stroke-opacity='0.49'/><path%20d='M117,20c9,18,20,35,33,51'%20stroke-width='4.3'%20stroke-opacity='0.6'/><path%20d='M158,84c6,9,11,17,16,26'%20stroke-width='3.1'%20stroke-opacity='0.56'/><path%20d='M60-45c15,19,27,41,37,63'%20stroke-width='4.2'%20stroke-opacity='0.61'/><path%20d='M101,24c7,12,14,25,21,37'%20stroke-width='3.6'%20stroke-opacity='0.53'/><path%20d='M137,87c6,16,16,30,25,44'%20stroke-width='2.7'%20stroke-opacity='0.56'/><path%20d='M53-36c4,6,7,11,11,17'%20stroke-width='3.3'%20stroke-opacity='0.62'/><path%20d='M77,4c11,14,19,30,27,46'%20stroke-width='4.1'%20stroke-opacity='0.48'/><path%20d='M120,77c3,7,8,13,12,19'%20stroke-width='4.2'%20stroke-opacity='0.55'/><path%20d='M137,106c6,9,12,20,18,29'%20stroke-width='3.7'%20stroke-opacity='0.7'/><path%20d='M39-38c12,25,27,49,41,72'%20stroke-width='3.9'%20stroke-opacity='0.76'/><path%20d='M91,53c8,12,15,26,22,39'%20stroke-width='3.0'%20stroke-opacity='0.69'/><path%20d='M129,119c4,8,8,16,14,24'%20stroke-width='3.6'%20stroke-opacity='0.47'/><path%20d='M35-24c10,17,20,33,31,49'%20stroke-width='3.2'%20stroke-opacity='0.61'/><path%20d='M73,37c7,10,13,20,19,30'%20stroke-width='4.2'%20stroke-opacity='0.51'/><path%20d='M95,72c4,7,8,14,12,20'%20stroke-width='2.7'%20stroke-opacity='0.81'/><path%20d='M117,107c8,10,15,21,20,32'%20stroke-width='2.9'%20stroke-opacity='0.5'/><path%20d='M10-22c18,22,34,47,48,72'%20stroke-width='4.4'%20stroke-opacity='0.71'/><path%20d='M70,67c15,18,28,38,39,59'%20stroke-width='3.3'%20stroke-opacity='0.46'/><path%20d='M127,152c0,0,0,0,1,1'%20stroke-width='3.6'%20stroke-opacity='0.57'/><path%20d='M-1-14c12,18,24,37,37,54'%20stroke-width='3.3'%20stroke-opacity='0.47'/><path%20d='M40,47c8,9,14,19,20,30'%20stroke-width='3.4'%20stroke-opacity='0.8'/><path%20d='M77,102c8,10,15,22,22,34'%20stroke-width='3.7'%20stroke-opacity='0.55'/><path%20d='M115,159c0,0,0,1,1,1'%20stroke-width='3.7'%20stroke-opacity='0.61'/><path%20d='M3-6c13,21,26,43,39,64'%20stroke-width='3.0'%20stroke-opacity='0.64'/><path%20d='M48,69c12,27,29,52,46,75'%20stroke-width='2.7'%20stroke-opacity='0.51'/><path%20d='M100,155c3,3,5,7,7,10'%20stroke-width='3.9'%20stroke-opacity='0.58'/><path%20d='M-12,1c8,11,16,22,23,34'%20stroke-width='4.4'%20stroke-opacity='0.78'/><path%20d='M27,60c6,10,13,20,20,29'%20stroke-width='3.8'%20stroke-opacity='0.77'/><path%20d='M59,107c10,15,20,30,29,45'%20stroke-width='3.6'%20stroke-opacity='0.66'/><path%20d='M-13,7c6,9,11,18,16,28'%20stroke-width='4.4'%20stroke-opacity='0.56'/><path%20d='M11,48c14,18,25,38,34,58'%20stroke-width='3.1'%20stroke-opacity='0.6'/><path%20d='M49,113c6,11,13,22,19,33'%20stroke-width='4.4'%20stroke-opacity='0.75'/><path%20d='M77,161c4,5,7,11,10,17'%20stroke-width='4.4'%20stroke-opacity='0.81'/><path%20d='M-39,1c3,6,6,12,10,17'%20stroke-width='4.3'%20stroke-opacity='0.64'/><path%20d='M-24,26c8,11,15,23,21,35'%20stroke-width='2.7'%20stroke-opacity='0.49'/><path%20d='M10,83c11,14,21,31,28,47'%20stroke-width='4.3'%20stroke-opacity='0.67'/><path%20d='M49,148c8,10,14,21,19,32'%20stroke-width='3.2'%20stroke-opacity='0.68'/><path%20d='M-48,12c6,7,10,15,15,23'%20stroke-width='2.9'%20stroke-opacity='0.49'/><path%20d='M-22,52c8,10,14,21,20,31'%20stroke-width='4.5'%20stroke-opacity='0.66'/><path%20d='M9,101c9,13,17,27,25,40'%20stroke-width='3.3'%20stroke-opacity='0.62'/><path%20d='M50,165c5,9,11,17,16,26'%20stroke-width='2.7'%20stroke-opacity='0.53'/><path%20d='M-5-34c9,3,18,7,26,11'%20stroke-width='2.6'%20stroke-opacity='0.46'/><path%20d='M45-13c12,4,24,10,36,15'%20stroke-width='2.7'%20stroke-opacity='0.52'/><path%20d='M97,9c6,3,12,5,17,7'%20stroke-width='3.7'%20stroke-opacity='0.54'/><path%20d='M124,20c21,4,40,15,58,24'%20stroke-width='2.9'%20stroke-opacity='0.4'/><path%20d='M-9-22c17,11,36,18,55,23'%20stroke-width='2.8'%20stroke-opacity='0.5'/><path%20d='M66,9c7,1,13,4,20,8'%20stroke-width='2.3'%20stroke-opacity='0.51'/><path%20d='M112,28c6,3,13,6,20,9'%20stroke-width='3.1'%20stroke-opacity='0.46'/><path%20d='M150,44c7,3,14,6,22,9'%20stroke-width='3.6'%20stroke-opacity='0.41'/><path%20d='M-24,20c14,6,28,11,42,17'%20stroke-width='3.5'%20stroke-opacity='0.37'/><path%20d='M26,40c6,4,13,6,19,8'%20stroke-width='2.3'%20stroke-opacity='0.54'/><path%20d='M59,53c18,10,39,17,58,24'%20stroke-width='3.6'%20stroke-opacity='0.53'/><path%20d='M146,89c8,1,15,5,22,9'%20stroke-width='2.8'%20stroke-opacity='0.47'/><path%20d='M-41,81c11,3,22,7,32,12'%20stroke-width='3.2'%20stroke-opacity='0.39'/><path%20d='M10,101c19,10,39,17,59,23'%20stroke-width='3.3'%20stroke-opacity='0.4'/><path%20d='M81,129c17,7,35,14,52,21'%20stroke-width='2.4'%20stroke-opacity='0.4'/></g></svg>");
  --sk-b: url("data:image/svg+xml,<svg%20xmlns='http%3A//www.w3.org/2000/svg'%20width='128'%20height='128'><g%20fill='none'%20stroke='%23ffffff'%20stroke-linecap='round'><path%20d='M83-66c6,9,11,18,16,27'%20stroke-width='3.5'%20stroke-opacity='0.73'/><path%20d='M110-19c7,11,14,22,19,33'%20stroke-width='2.9'%20stroke-opacity='0.78'/><path%20d='M141,35c8,17,17,33,28,48'%20stroke-width='2.9'%20stroke-opacity='0.68'/><path%20d='M175,93c3,8,8,15,12,22'%20stroke-width='3.7'%20stroke-opacity='0.76'/><path%20d='M71-49c15,20,27,43,39,66'%20stroke-width='4.5'%20stroke-opacity='0.67'/><path%20d='M114,24c5,12,12,24,20,35'%20stroke-width='4.5'%20stroke-opacity='0.49'/><path%20d='M147,81c10,14,18,28,26,43'%20stroke-width='3.6'%20stroke-opacity='0.7'/><path%20d='M61-46c14,16,25,34,33,51'%20stroke-width='3.2'%20stroke-opacity='0.53'/><path%20d='M110,30c3,5,7,11,11,16'%20stroke-width='2.9'%20stroke-opacity='0.57'/><path%20d='M125,52c7,10,13,20,19,30'%20stroke-width='4.2'%20stroke-opacity='0.66'/><path%20d='M162,110c4,5,7,10,10,15'%20stroke-width='3.0'%20stroke-opacity='0.73'/><path%20d='M48-54c8,13,16,27,23,41'%20stroke-width='3.4'%20stroke-opacity='0.77'/><path%20d='M84,9c15,22,28,47,41,71'%20stroke-width='4.1'%20stroke-opacity='0.5'/><path%20d='M131,91c4,6,8,13,12,20'%20stroke-width='3.9'%20stroke-opacity='0.46'/><path%20d='M155,131c1,1,1,2,2,3'%20stroke-width='3.5'%20stroke-opacity='0.63'/><path%20d='M35-31c4,4,7,8,9,13'%20stroke-width='4.3'%20stroke-opacity='0.46'/><path%20d='M57,1c13,23,29,45,45,67'%20stroke-width='3.9'%20stroke-opacity='0.66'/><path%20d='M114,86c10,14,19,28,28,42'%20stroke-width='3.1'%20stroke-opacity='0.67'/><path%20d='M27-24c6,11,14,22,21,32'%20stroke-width='4.2'%20stroke-opacity='0.76'/><path%20d='M58,22c6,8,12,17,17,26'%20stroke-width='3.6'%20stroke-opacity='0.51'/><path%20d='M86,64c14,19,28,39,40,60'%20stroke-width='3.1'%20stroke-opacity='0.55'/><path%20d='M131,130c3,5,6,10,9,15'%20stroke-width='3.6'%20stroke-opacity='0.47'/><path%20d='M11-30c7,9,13,19,19,29'%20stroke-width='4.3'%20stroke-opacity='0.6'/><path%20d='M38,11c7,8,13,18,18,27'%20stroke-width='4.2'%20stroke-opacity='0.52'/><path%20d='M67,54c12,21,26,42,41,61'%20stroke-width='3.7'%20stroke-opacity='0.52'/><path%20d='M119,132c5,5,8,12,12,18'%20stroke-width='3.5'%20stroke-opacity='0.53'/><path%20d='M9-24c14,19,26,40,38,60'%20stroke-width='2.8'%20stroke-opacity='0.75'/><path%20d='M51,42c12,25,28,49,45,71'%20stroke-width='4.5'%20stroke-opacity='0.58'/><path%20d='M113,140c3,5,6,11,9,16'%20stroke-width='3.4'%20stroke-opacity='0.59'/><path%20d='M-3-19c7,10,13,20,18,30'%20stroke-width='2.8'%20stroke-opacity='0.5'/><path%20d='M26,29c6,12,13,24,21,36'%20stroke-width='3.0'%20stroke-opacity='0.63'/><path%20d='M63,92c6,13,14,25,22,37'%20stroke-width='3.2'%20stroke-opacity='0.58'/><path%20d='M99,154c2,4,5,8,7,12'%20stroke-width='3.4'%20stroke-opacity='0.5'/><path%20d='M-12-4c5,6,9,13,12,20'%20stroke-width='4.0'%20stroke-opacity='0.66'/><path%20d='M9,30c4,5,7,12,11,17'%20stroke-width='4.3'%20stroke-opacity='0.75'/><path%20d='M36,73c6,7,11,17,15,25'%20stroke-width='3.3'%20stroke-opacity='0.51'/><path%20d='M62,116c3,6,6,11,10,16'%20stroke-width='4.5'%20stroke-opacity='0.8'/><path%20d='M81,145c4,9,10,18,16,27'%20stroke-width='2.9'%20stroke-opacity='0.48'/><path%20d='M-20-6c13,18,24,38,35,58'%20stroke-width='4.0'%20stroke-opacity='0.57'/><path%20d='M22,63c10,16,20,33,29,49'%20stroke-width='3.1'%20stroke-opacity='0.66'/><path%20d='M67,137c4,9,10,18,16,27'%20stroke-width='4.0'%20stroke-opacity='0.8'/><path%20d='M-39,1c7,17,16,32,28,47'%20stroke-width='4.5'%20stroke-opacity='0.67'/><path%20d='M-5,59c3,7,7,13,11,19'%20stroke-width='3.0'%20stroke-opacity='0.51'/><path%20d='M18,98c4,10,10,19,16,27'%20stroke-width='4.5'%20stroke-opacity='0.74'/><path%20d='M40,136c8,10,14,21,19,32'%20stroke-width='4.3'%20stroke-opacity='0.46'/><path%20d='M70,186c0,1,1,1,1,2'%20stroke-width='4.0'%20stroke-opacity='0.69'/><path%20d='M-45,5c8,9,14,21,20,32'%20stroke-width='4.1'%20stroke-opacity='0.5'/><path%20d='M-11,59c4,6,8,13,12,19'%20stroke-width='4.7'%20stroke-opacity='0.77'/><path%20d='M9,92c11,17,22,34,32,51'%20stroke-width='4.3'%20stroke-opacity='0.49'/><path%20d='M57,169c4,6,8,13,13,20'%20stroke-width='4.7'%20stroke-opacity='0.6'/><path%20d='M8-47c11,7,24,13,36,17'%20stroke-width='2.9'%20stroke-opacity='0.39'/><path%20d='M74-16c19,10,39,19,59,27'%20stroke-width='3.5'%20stroke-opacity='0.35'/><path%20d='M151,20c8,3,15,7,23,10'%20stroke-width='3.3'%20stroke-opacity='0.35'/><path%20d='M189,37c2,1,5,2,8,3'%20stroke-width='3.6'%20stroke-opacity='0.47'/><path%20d='M-20-3c5,3,10,6,16,9'%20stroke-width='4.2'%20stroke-opacity='0.5'/><path%20d='M5,10c9,6,18,11,28,15'%20stroke-width='3.5'%20stroke-opacity='0.5'/><path%20d='M61,40c9,4,18,9,26,13'%20stroke-width='3.5'%20stroke-opacity='0.46'/><path%20d='M111,66c11,7,23,13,35,18'%20stroke-width='2.5'%20stroke-opacity='0.44'/><path%20d='M-21,17c6,2,11,4,16,7'%20stroke-width='4.1'%20stroke-opacity='0.53'/><path%20d='M22,36c9,3,19,7,27,12'%20stroke-width='3.1'%20stroke-opacity='0.52'/><path%20d='M78,60c11,6,23,11,35,15'%20stroke-width='2.5'%20stroke-opacity='0.47'/><path%20d='M143,89c8,4,16,8,25,10'%20stroke-width='3.1'%20stroke-opacity='0.54'/><path%20d='M-43,45c13,4,25,10,37,17'%20stroke-width='2.5'%20stroke-opacity='0.48'/><path%20d='M1,65c22,9,45,19,67,29'%20stroke-width='3.5'%20stroke-opacity='0.42'/><path%20d='M83,101c21,14,45,22,68,30'%20stroke-width='3.1'%20stroke-opacity='0.4'/></g></svg>");
  --sk-c: url("data:image/svg+xml,<svg%20xmlns='http%3A//www.w3.org/2000/svg'%20width='128'%20height='128'><g%20fill='none'%20stroke='%23ffffff'%20stroke-linecap='round'><path%20d='M72-58c13,21,26,42,40,63'%20stroke-width='4.4'%20stroke-opacity='0.47'/><path%20d='M126,27c13,25,28,49,45,71'%20stroke-width='4.3'%20stroke-opacity='0.56'/><path%20d='M58-55c4,6,8,12,11,19'%20stroke-width='3.2'%20stroke-opacity='0.48'/><path%20d='M82-16c8,9,14,19,19,29'%20stroke-width='3.7'%20stroke-opacity='0.69'/><path%20d='M110,28c10,16,21,33,31,49'%20stroke-width='4.2'%20stroke-opacity='0.59'/><path%20d='M155,98c4,10,10,18,16,26'%20stroke-width='3.3'%20stroke-opacity='0.8'/><path%20d='M54-49c15,18,28,38,38,60'%20stroke-width='3.9'%20stroke-opacity='0.72'/><path%20d='M103,29c9,18,20,34,32,49'%20stroke-width='4.4'%20stroke-opacity='0.63'/><path%20d='M151,103c5,8,10,16,15,24'%20stroke-width='3.2'%20stroke-opacity='0.46'/><path%20d='M35-45c10,14,18,30,26,45'%20stroke-width='3.4'%20stroke-opacity='0.58'/><path%20d='M74,20c12,24,27,48,42,70'%20stroke-width='3.6'%20stroke-opacity='0.74'/><path%20d='M127,109c6,11,12,21,19,32'%20stroke-width='3.9'%20stroke-opacity='0.58'/><path%20d='M22-38c3,6,7,11,11,17'%20stroke-width='4.2'%20stroke-opacity='0.54'/><path%20d='M43-5c7,15,17,29,27,42'%20stroke-width='3.5'%20stroke-opacity='0.53'/><path%20d='M83,59c10,12,17,25,24,38'%20stroke-width='4.5'%20stroke-opacity='0.49'/><path%20d='M122,121c6,8,11,16,16,25'%20stroke-width='3.1'%20stroke-opacity='0.77'/><path%20d='M23-18c9,11,17,23,24,36'%20stroke-width='4.3'%20stroke-opacity='0.48'/><path%20d='M57,32c3,7,8,13,12,19'%20stroke-width='3.5'%20stroke-opacity='0.6'/><path%20d='M73,58c5,6,10,13,14,20'%20stroke-width='4.3'%20stroke-opacity='0.64'/><path%20d='M92,86c5,7,10,14,14,22'%20stroke-width='4.6'%20stroke-opacity='0.53'/><path%20d='M122,132c3,6,7,12,11,17'%20stroke-width='3.5'%20stroke-opacity='0.63'/><path%20d='M3-25c6,11,13,21,20,31'%20stroke-width='4.0'%20stroke-opacity='0.55'/><path%20d='M37,28c5,11,13,22,21,31'%20stroke-width='3.0'%20stroke-opacity='0.51'/><path%20d='M70,79c7,10,13,20,20,30'%20stroke-width='3.2'%20stroke-opacity='0.46'/><path%20d='M100,123c7,11,14,22,21,34'%20stroke-width='3.3'%20stroke-opacity='0.49'/><path%20d='M1-2c7,14,16,27,26,39'%20stroke-width='4.5'%20stroke-opacity='0.46'/><path%20d='M37,52c9,12,17,24,24,37'%20stroke-width='4.1'%20stroke-opacity='0.7'/><path%20d='M79,115c5,5,8,11,12,17'%20stroke-width='4.5'%20stroke-opacity='0.5'/><path%20d='M99,145c3,6,8,12,12,18'%20stroke-width='3.5'%20stroke-opacity='0.58'/><path%20d='M-8-8c17,19,30,41,41,64'%20stroke-width='2.6'%20stroke-opacity='0.72'/><path%20d='M37,63c6,13,14,25,22,35'%20stroke-width='3.8'%20stroke-opacity='0.81'/><path%20d='M69,113c7,17,18,32,29,47'%20stroke-width='3.4'%20stroke-opacity='0.78'/><path%20d='M-25,0c12,13,22,28,31,43'%20stroke-width='3.5'%20stroke-opacity='0.58'/><path%20d='M19,62c4,7,9,13,14,20'%20stroke-width='4.3'%20stroke-opacity='0.77'/><path%20d='M49,105c4,5,8,10,11,16'%20stroke-width='4.5'%20stroke-opacity='0.6'/><path%20d='M77,145c5,8,11,16,17,24'%20stroke-width='3.1'%20stroke-opacity='0.81'/><path%20d='M-30,13c13,24,29,47,45,70'%20stroke-width='4.2'%20stroke-opacity='0.67'/><path%20d='M31,108c5,8,10,16,15,24'%20stroke-width='3.9'%20stroke-opacity='0.57'/><path%20d='M60,153c6,11,13,21,20,30'%20stroke-width='2.7'%20stroke-opacity='0.77'/><path%20d='M-48,9c4,6,8,13,12,19'%20stroke-width='2.8'%20stroke-opacity='0.53'/><path%20d='M-21,51c5,10,12,20,19,29'%20stroke-width='2.7'%20stroke-opacity='0.71'/><path%20d='M4,89c13,27,31,52,49,76'%20stroke-width='4.1'%20stroke-opacity='0.51'/><path%20d='M65,184c2,2,3,4,4,6'%20stroke-width='2.9'%20stroke-opacity='0.47'/><path%20d='M-12-9c11,1,22,5,32,10'%20stroke-width='2.9'%20stroke-opacity='0.33'/><path%20d='M32,4c11,4,22,8,33,11'%20stroke-width='3.6'%20stroke-opacity='0.34'/><path%20d='M92,23c29,4,56,13,81,26'%20stroke-width='2.8'%20stroke-opacity='0.45'/><path%20d='M-26,26c15,3,29,9,41,16'%20stroke-width='2.7'%20stroke-opacity='0.34'/><path%20d='M30,47c13,7,28,11,42,16'%20stroke-width='2.5'%20stroke-opacity='0.42'/><path%20d='M86,68c10,3,20,7,30,11'%20stroke-width='2.2'%20stroke-opacity='0.49'/><path%20d='M123,82c5,3,11,5,17,6'%20stroke-width='3.3'%20stroke-opacity='0.45'/><path%20d='M167,98c0,0,0,0,0,1'%20stroke-width='3.3'%20stroke-opacity='0.32'/><path%20d='M-22,39c8,3,16,6,25,8'%20stroke-width='3.1'%20stroke-opacity='0.48'/><path%20d='M22,54c14,3,29,8,42,13'%20stroke-width='2.8'%20stroke-opacity='0.4'/><path%20d='M77,71c8,1,16,4,24,8'%20stroke-width='3.3'%20stroke-opacity='0.34'/><path%20d='M113,83c17,7,36,12,54,17'%20stroke-width='2.8'%20stroke-opacity='0.44'/></g></svg>");
  background-image: var(--sk-a);
  /* in em, so the hatch keeps its grain against the letter rather than
     against the screen. 0.3em puts a stroke at about a pixel and the
     pitch at five, which is the scale at which hatching reads as shading
     rather than as marks — the coarser sizes I tried read as scratches on
     the glyph instead of tone inside it. */
  background-size: 0.34em 0.34em;
  -webkit-background-clip: text;
  background-clip: text;
}

/* The entrance is over, so drop it. A forwards fill beats ordinary
   declarations for good, so the finished state is simply stated with
   the animation gone rather than fought. */
.js .hero-frame.is-drawn .hero-word i {
  animation: none;
  opacity: 1;
  transform: none;
}
/* the outline drops the entrance and keeps the hatch, which carries on
   from where it is: an animation that keeps its name across a change to
   the list is not restarted. Same specificity as the rule above, so it
   has to come after it. */
.js .hero-frame.is-drawn .hero-word-edge i {
  animation: hero-hatch 0.42s steps(1) infinite;
}
@keyframes hero-hatch {
  0% { background-image: var(--sk-a); }
  34% { background-image: var(--sk-b); }
  67% { background-image: var(--sk-c); }
}

/* reduced motion needs no rule of its own here: the block further down
   already stops every .hero-word letter, and the edge copy is one. The
   hatch stays on its first pass — the texture without the redrawing. */

@media (max-width: 760px) {
  .hero-word-edge { -webkit-text-stroke-width: 1.7px; }
}

/* Above both copies of the wordmark: the scrim must tint the fill, the
   outline and the hatch it carries equally, or the word ghosts into view at
   rest where the gradient runs — registration is about tone as much as
   geometry. At z-index 3 it was only above the fill, being the frame's first
   child and so painted under the outline at the same level; that put the
   hatch's white on top of a white the scrim had already taken 22% out of, and
   the marks read as a second, brighter hand over the letter instead of
   vanishing into it. The whole stack, bottom up: street 0, fill 1, them 2,
   outline 3, this 4, caption 5, grain 6. */
/* Under the name, not over it. This wash is for the street and for the
   caption bar's legibility; while it sat above the word it took 13% of
   ink out of the letters and rendered #ffffff as #dfdfdf. The word is
   the one white on the page that has to match the paper exactly. */
.hero-frame::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to top,
    rgba(10, 10, 10, 0.22) 0%,
    rgba(10, 10, 10, 0.105) 46%,
    rgba(10, 10, 10, 0) 78%);
}

/* The caption bar: what we are, where the menu is, and where/when to find
   us — ruled across the foot of the frame like the caption under a print,
   so the photograph keeps the whole picture plane to itself. */
.h-bar {
  position: absolute;
  /* above the scrim: it carries its own, heavier wash already */
  z-index: 5;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 26px;
  padding: 16px var(--gutter);
  border-top: 1px solid rgba(255, 255, 255, 0.42);
  /* the photograph runs bright at the bottom right — the bar carries its own
     wash so the address stays legible wherever the picture lands */
  background: linear-gradient(to top,
    rgba(10, 10, 10, 0.72) 0%,
    rgba(10, 10, 10, 0.34) 64%,
    rgba(10, 10, 10, 0) 100%);
  opacity: 0;
  transform: translateY(16px);
  animation: h-rise 0.7s cubic-bezier(0.16, 1, 0.3, 1) 1s forwards;
}
.h-lede {
  margin: 0;
  flex: 1 1 auto;
  color: var(--paper);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  /* the scrim is deliberately light — the shadow keeps the line readable
     over the bright patches of the photograph */
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.7);
}
.h-cta,
.h-meta {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--paper);
  text-decoration: none;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.7);
}
.h-cta {
  gap: 10px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  transition: gap 0.24s ease;
}
.h-cta:hover,
.h-cta:focus-visible { gap: 16px; }
.h-meta {
  padding-left: 26px;
  border-left: 1px solid rgba(255, 255, 255, 0.34);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  opacity: 0.92;
}
@keyframes h-rise { to { opacity: 1; transform: none; } }

@media (max-width: 760px) {
  /* tracked out here too, just less than the desktop word: the phone has
     five letters to fit across 390px and no room to spare. It also keeps
     the higher rest — the drop is a wide-screen adjustment, and the frame
     is far shorter here, so the same percentage would be a longer fall. */
  /* and it opens less far: the word rests 13px from the frame either side
     here, so a wide open would put the outer letters off the picture while
     they are still faintly visible, which reads as letters flying in from
     off-screen rather than a track closing */
  .hero-word { --track: 0.03em; --drop: 10%; --spread: 0.11em; }
  .h-bar {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: 12px 16px 14px;
  }
  .h-meta { padding-left: 0; border-left: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .h-bar { animation: none; opacity: 1; transform: none; }
}

/* ============================================================
   Ticker — the window sign, in motion
   ============================================================ */

.ticker {
  background: var(--paper);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  overflow: hidden;
  padding: 14px 0;
  margin-top: 14px;
}

.ticker-track {
  display: flex;
  width: max-content;
  gap: 0;
  animation: ticker 36s linear infinite;
  will-change: transform;
}

/* with JS the marquee is scroll-velocity-driven (see main.js) */
.js .ticker-track { animation: none; }

.ticker-track > span {
  white-space: nowrap;
  font-weight: 800;
  font-size: 14px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  padding-right: 3.2em;
}

.ticker-track > span::after {
  content: "✦";
  margin-left: 3.2em;
  font-size: 11px;
  vertical-align: 2px;
}

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

.ticker:hover .ticker-track,
.ticker.paused .ticker-track { animation-play-state: paused; }

/* ============================================================
   Shared section scaffolding
   ============================================================ */

section { scroll-margin-top: var(--header-h); }

.wrap {
  max-width: 1440px;
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

.kicker {
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  margin-bottom: 18px;
}

/* ============================================================
   Simplicity. Depth. Intention.
   ============================================================ */

.simplicity { padding: clamp(80px, 12vh, 150px) 0 0; }

.simplicity-head {
  font-family: var(--sans);
  font-weight: 800;
  font-size: clamp(38px, 6.4vw, 104px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  max-width: 14em;
}

.simplicity-intro {
  max-width: 560px;
  margin-top: 28px;
  font-size: clamp(17px, 1.4vw, 20px);
  color: var(--smoke);
}

.weave {
  display: grid;
  /* Equal tracks, where the weave used to run 1.15 / 0.95 / 0.9. The three
     photographs share one aspect, so an asymmetric grid was the only thing
     making them different heights — the widest column stood 306px tall
     against the narrowest at 240px. Equal columns is what puts them on one
     height without cropping or stretching a single one of them; the weave
     keeps its woven feel from the vertical stagger below, not from the
     column widths. */
  grid-template-columns: 1fr 1fr 1fr;
  gap: clamp(28px, 4vh, 64px) clamp(20px, 3vw, 56px);
  align-items: center;
  margin-top: clamp(48px, 7vh, 90px);
  padding-bottom: clamp(56px, 8vh, 110px);
}

.weave figure { position: relative; margin: 0; }
/* width follows the track, height follows the width — same track, same
   height, and the proportions are never touched */
.weave img { width: 100%; height: auto; }

.weave .wi1 { margin-top: clamp(30px, 5vw, 80px); }
.weave .wi2 { margin-top: clamp(-10px, -1vw, -30px); }
.weave .wc2 { text-align: right; }

.concept-card .kicker { margin-bottom: 10px; }

.concept-card h3 {
  font-weight: 800;
  font-size: clamp(22px, 2vw, 30px);
  line-height: 1.1;
  margin-bottom: 12px;
}

.concept-card p { color: var(--smoke); }

@media (max-width: 900px) {
  .weave { grid-template-columns: 1fr; gap: 30px; }
  .weave .wi1, .weave .wi2 { margin-top: 0; }
  .weave .wc2 { text-align: left; }
}

/* ============================================================
   Drinks
   ============================================================ */

.drinks-hero {
  position: relative;
  height: clamp(420px, 78vh, 760px);
  overflow: hidden;
}

.drinks-hero img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 40%;
}

.drinks-title {
  position: absolute;
  right: clamp(4vw, 8vw, 12vw);
  top: 50%;
  transform: translateY(-50%);
  width: clamp(300px, 38vw, 620px);
}

.drinks-title .swash-art {
  width: 100%;
  height: auto;
  display: block;
  color: var(--ink);
}

.drinks-title .drinks-word {
  position: absolute;
  inset: 0;
  margin: 0;
  display: grid;
  place-items: center;
  font-family: "TT Hoves Pro Expanded", var(--display);
  font-weight: 800;
  /* set as authored — "Drinks" / "Snacks" wear their own case */
  /* one line always: the fit guarantees it, and the measuring pass at
     100px would wrap and report the box's width instead of the word's */
  white-space: nowrap;
  font-size: clamp(48px, 6.4vw, 108px);
  color: var(--paper);
  /* an expanded face carries its width in the letters — the brush face's
     0.01em would push this one apart */
  letter-spacing: -0.005em;
}
.js .drinks-title .drinks-word {
  opacity: 0;
  transition: opacity 0.5s ease 0.75s;
}

.drinks-title.in-view .drinks-word { opacity: 1; }

/* "Află mai multe" on a hand stroke */

.stroke-link {
  position: relative;
  display: inline-block;
  font-weight: 700;
  font-size: 15px;
  padding: 7px 26px 8px 16px;
  white-space: nowrap;
}

.stroke-link .highlight-art {
  position: absolute;
  inset: -4px -8px;
  width: calc(100% + 16px);
  height: calc(100% + 8px);
  z-index: 0;
}

.stroke-link .txt { position: relative; z-index: 1; }

.stroke-link:hover .txt { letter-spacing: 0.02em; }
.stroke-link .txt { transition: letter-spacing 0.25s ease; }

/* ============================================================
   Padma Society
   ============================================================ */

.society { padding: clamp(80px, 12vh, 150px) 0 0; }

.society-head {
  display: grid;
  grid-template-columns: minmax(240px, 1fr) 1.9fr;
  gap: clamp(24px, 4vw, 72px);
  align-items: end;
}

.society-tag-art {
  width: 100%;
  height: auto;
  display: block;
  color: var(--ink);
}

.society-h {
  font-weight: 800;
  font-size: clamp(22px, 2vw, 30px);
  line-height: 1.2;
  margin-bottom: 12px;
}

.society-copy { margin-bottom: 8px; }

.society-intro {
  margin: 0;
  text-align: left;
  font-size: clamp(16px, 1.3vw, 19px);
  color: var(--smoke);
}

@media (max-width: 760px) {
  .society-head { grid-template-columns: 1fr; }
  .society-copy { order: 2; margin-top: 20px; }
  .society-tag-art { order: 1; }
}

.triptych {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(14px, 2vw, 32px);
  margin-top: clamp(48px, 7vh, 90px);
}

.triptych img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  aspect-ratio: 3 / 4;
  filter: grayscale(1) contrast(1.05);
}

.triptych .scribble-art {
  position: absolute;
  left: -2%;
  right: -2%;
  top: 31%;
  width: 104%;
  height: auto;
  color: var(--paper);
  pointer-events: none;
  filter: drop-shadow(0 1px 6px rgba(0, 0, 0, 0.25));
}

.society-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(24px, 3vw, 56px);
  padding: clamp(56px, 8vh, 110px) 0;
}

.society-card { border-top: 3px solid var(--ink); padding-top: 18px; }

.society-card h3 {
  font-weight: 800;
  font-size: clamp(20px, 1.8vw, 26px);
  margin-bottom: 10px;
}

.society-card p { color: var(--smoke); }

@media (max-width: 900px) {
  .society-cards { grid-template-columns: 1fr; gap: 34px; }
  .triptych { grid-template-columns: repeat(3, 1fr); gap: 8px; }
}

/* ============================================================
   Padma Pop-up — the word rises
   ============================================================ */

.popup {
  position: relative;
  padding: clamp(44px, 6.6vh, 96px) 0;
  overflow: hidden;
}

/* One band, not a stack. The stage is the only thing that sets the height;
   the heading and the copy are laid into two opposite corners of it, so the
   section lands inside a single screen and the white between them belongs
   to the composition instead of being left over. */
.popup-body { position: relative; }

.popup-intro {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 4;
  max-width: 58%;
}

.popup-head {
  font-family: var(--display);
  font-size: clamp(34px, 4.6vw, 74px);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.popup-stage {
  position: relative;
  min-height: max(min(clamp(420px, 72vh, 720px), 56vw), 480px);
}

/* The clips are cut out — VP9 with an alpha channel — so they composite
   normally and need no blend trick to sit on the paper. That also makes
   them occlude by their own shape: a letter passing behind the leaf goes
   behind the leaf, not behind the rectangle it happens to sit in. The mp4
   stays as a fallback for anything that cannot take alpha in WebM; it is
   white to its edges, which on white paper looks the same. */
.balloon {
  position: absolute;
  user-select: none;
  pointer-events: none;
}

.balloon img,
.balloon video { display: block; width: 100%; height: auto; }

/* the box is known before the clip or its poster is: the arrival measures
   these positions live, and a letter that resizes underneath it would fly
   to the wrong place */
.balloon-p video  { aspect-ratio: 480 / 664; }
.balloon-a video,
.balloon-a2 video { aspect-ratio: 480 / 568; }
.balloon-d video  { aspect-ratio: 480 / 632; }
.balloon-m video  { aspect-ratio: 480 / 532; }
.balloon-leaf video { aspect-ratio: 480 / 986; }

/* The leaf takes the middle — dead centre of the stage — and the word is
   set around it, P A D to its left and M A to its right, climbing from the
   bottom-left corner to the top-right. The two text blocks hold the corners
   the climb runs between, .popup-intro top-left and .popup-copy
   bottom-right, which is what keeps them out of dead air.

   Everything hangs off the floor, not the ceiling, so the climb keeps its
   shape as the band changes height. M and A clear the copy by a px floor as
   well as a percentage: the copy is roughly constant in px while the stage
   is not, so on a short window the percentage alone would let them settle
   onto the text. Widths are min(% of stage, vh) so a short window shrinks
   the letters with the band instead of pushing them out of the bottom. */
.balloon-p    { left: 0;     bottom: 4%;              width: min(12.6%, 18.3vh); }
.balloon-a    { left: 15.6%; bottom: 22%;             width: min(11.1%, 16.1vh); }
.balloon-d    { left: 29.8%; bottom: 38%;             width: min(9.2%,  13.3vh); }
/* dead centre whatever its width works out to — a fixed left percentage
   drifts off as soon as the vh cap takes over and narrows it */
.balloon-leaf { left: 50%;   bottom: 2%;              width: min(16%,   23.3vh);
                translate: -50%; }
.balloon-m    { left: 63.4%; bottom: max(330px, 55%); width: min(11.1%, 16.1vh); }
.balloon-a2   { left: 81.7%; bottom: max(350px, 65%); width: min(9.8%,  14.2vh); }

/* PADMA needs two A's and the set only has one clip, so the far one is the
   same film mirrored — different size, different drift, inflating last, so
   the pair does not read as a copy and paste. */
.balloon-a2 video,
.balloon-a2 img { transform: scaleX(-1); }

.balloon .float { animation: float 7s ease-in-out infinite; }
/* the drift waits for the arrival: a letter cannot bob and fly at once */
.js .popup-stage.is-holding .balloon .float { animation-play-state: paused; }

/* the leaf sits over the letters while they are coming out from behind it */
.popup-stage.is-holding .balloon-leaf { z-index: 4; }
.balloon-a .float { animation-duration: 8.5s; animation-delay: -2s; }
.balloon-d .float { animation-duration: 6.2s; animation-delay: -1s; }
.balloon-m .float { animation-duration: 7.6s; animation-delay: -3s; }
.balloon-a2 .float { animation-duration: 9.4s; animation-delay: -5s; }
.balloon-leaf .float { animation-duration: 9s; animation-delay: -4s; }

@keyframes float {
  0%, 100% { transform: translateY(-12px) rotate(-1.4deg); }
  50% { transform: translateY(14px) rotate(1.6deg); }
}

.popup-copy {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 3;
  /* the floor matters: a narrower measure makes this block taller, and the
     far letters clear it by a px budget */
  width: clamp(360px, 34vw, 460px);
}

.popup-copy h3 { font-weight: 800; font-size: clamp(22px, 2vw, 30px); margin-bottom: 12px; }
.popup-copy p { color: var(--smoke); margin-bottom: 14px; }

.popup .stroke-link { color: var(--paper); }
.popup .stroke-link .highlight-art { color: var(--ink); }

/* Below about 940 the word set around the leaf runs out of room: the copy
   column reaches far enough in to touch the leaf in the middle, since the
   copy has a floor of 360px while the stage keeps shrinking. This is that
   point with a margin, and under it the whole thing folds into the strip. */
@media (max-width: 980px) {
  /* the climb folds into a single strip under the heading: same lock-up,
     same order, still rising a little to the right. It keeps the whole
     section inside one screen. */
  .popup-intro,
  .popup-copy { position: static; width: auto; max-width: none; }
  .popup-copy { max-width: 620px; }

  .popup-stage {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    min-height: 0;
    /* tighter than it was: the copy runs to more lines now that it makes
       room for the leaf, and this is where that height comes back from */
    margin: 20px 0 24px;
    /* static, so the leaf below hangs off .popup-body and can reach down
       beside the copy rather than being pinned inside the strip */
    position: static;
  }

  /* relative, not static, so a balloon can still be given a z-index */
  .balloon { position: relative; left: auto; bottom: auto; flex: 0 0 auto; }

  /* On a phone the leaf steps out of the word and stands beside the copy,
     so PADMA reads straight through instead of being split in two. The
     letters take the room it leaves. */
  .balloon-leaf {
    position: absolute;
    bottom: 0;
    translate: none;
    width: clamp(74px, 22vw, 132px);
    /* tracks the copy's own right edge rather than the section's: past
       620px the copy stops widening, and a leaf pinned to the section
       would drift off into the margin away from the text it stands by */
    right: max(0px, calc(100% - 620px));
  }
  .popup-copy {
    padding-right: clamp(88px, 26vw, 150px);
    /* At the wide end of the strip the copy goes short and broad while the
       leaf keeps its height, and the leaf — sitting on the bottom of the
       block — would poke out of the top of it into the letters. This keeps
       the block at least as tall as what stands beside it. */
    min-height: calc(clamp(74px, 22vw, 132px) * 2.06);
  }

  .balloon-p  { width: 18.5%; }
  .balloon-a  { width: 17.5%; margin-bottom: 7px; }
  .balloon-d  { width: 17%;   margin-bottom: 15px; }
  .balloon-m  { width: 16.5%; margin-bottom: 23px; }
  .balloon-a2 { width: 16%;   margin-bottom: 31px; }
}

/* ============================================================
   Padma Philosophy — the archival chapter
   ============================================================ */

.philosophy { background: var(--paper); }

.phil-hero {
  position: relative;
  height: clamp(460px, 88vh, 820px);
  overflow: hidden;
}

.phil-hero img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 35%;
}

.phil-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, transparent 55%, rgba(30, 20, 5, 0.28));
  pointer-events: none;
}

.phil-hero h2 {
  position: absolute;
  left: clamp(20px, 6vw, 110px);
  bottom: clamp(40px, 12vh, 120px);
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(40px, 5.6vw, 92px);
  line-height: 1.05;
  color: var(--paper);
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.45);
}

.phil-hero .year { display: block; text-align: center; }

.phil-body { padding: clamp(70px, 10vh, 130px) 0; }

.phil-name {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(44px, 8.2vw, 148px);
  line-height: 1.02;
  text-align: center;
  margin: clamp(30px, 5vh, 60px) 0 clamp(24px, 4vh, 48px);
  letter-spacing: -0.01em;
}

.phil-after {
  font-family: var(--serif);
  font-size: clamp(18px, 1.5vw, 22px);
  line-height: 1.6;
  max-width: 34em;
  margin: 0 auto;
}

.timeline {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(20px, 3vw, 56px);
  margin-top: clamp(48px, 8vh, 90px);
  border-top: 1px solid var(--hairline);
  padding-top: 28px;
}

.timeline h4 {
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(26px, 2.4vw, 38px);
  margin-bottom: 6px;
}

.timeline p { color: var(--smoke); font-size: 15.5px; }

/* the drawn root that replaces the hairline while js is drawing it —
   reduced motion and no-js never add .has-root and keep the rule */
.timeline.has-root { position: relative; border-top-color: transparent; }
.tl-root {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.tl-root path {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.6px;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
}
.tl-root path.va { animation: boil-a 0.42s steps(1) infinite; }
.tl-root path.vb { animation: boil-b 0.42s steps(1) infinite; }
.tl-root path.vc { animation: boil-c 0.42s steps(1) infinite; }
/* a stub sprouts once the line has passed its date */
.tl-stub { opacity: 0; transition: opacity 0.2s ease; }
.tl-stub.on { opacity: 1; }


@media (max-width: 760px) {
  .timeline { grid-template-columns: 1fr; gap: 26px; }
  /* stacked, the root runs down the left margin instead of across */
  .timeline.has-root { padding-left: 30px; }
}

/* ============================================================
   Careers
   ============================================================ */

.careers {
  background: var(--ink);
  color: var(--paper);
  padding: clamp(80px, 12vh, 150px) 0;
}

.careers .kicker { color: #b9b9b9; }

.careers h2 {
  font-family: var(--display);
  font-size: clamp(30px, 4.6vw, 72px);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  max-width: 12em;
}

.careers-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(24px, 3vw, 56px);
  margin-top: clamp(40px, 6vh, 70px);
}

.career-role { border-top: 3px solid var(--paper); padding-top: 16px; }
.career-role h3 { font-weight: 800; font-size: 22px; margin-bottom: 8px; }
.career-role p { color: #b9b9b9; }

.careers-cta { margin-top: clamp(40px, 6vh, 64px); }

.careers-cta .stroke-link { color: var(--ink); padding-right: 34px; }
.careers-cta .stroke-link .highlight-art { color: var(--paper); }

@media (max-width: 900px) {
  .careers-grid { grid-template-columns: 1fr; gap: 30px; }
}

/* ============================================================
   Sketch draw-on machinery
   The filled ink art is revealed through mask strokes whose
   dashoffset animates from 1 → 0 (pathLength="1" on each).
   ============================================================ */

.js .sketch .mask-stroke {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.sketch.in-view .mask-stroke {
  transition: stroke-dashoffset var(--draw-dur, 0.9s) cubic-bezier(0.6, 0.04, 0.35, 1);
  transition-delay: var(--draw-delay, 0s);
  stroke-dashoffset: 0;
}

/* graffiti tag: strokes pop in one after another, like tagging */
.js .society-tag-art path {
  opacity: 0;
  transform: scale(0.94) rotate(-1.2deg);
  transform-origin: center;
  transform-box: fill-box;
}

.society-tag-art.in-view path {
  animation: tag-pop 0.22s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: calc(var(--i) * 70ms);
}

@keyframes tag-pop { to { opacity: 1; transform: none; } }

/* ============================================================
   Off stage
   ============================================================ */

/* JS marks a stage `is-away` when it is more than a screen from the
   viewport (see main.js). Everything ambient inside it holds still until
   it comes back. None of this is visible by definition — the whole point
   is that it is off screen — but on a phone at 4x throttle it was a fifth
   of the frame budget, spent on a grain nobody could see over a wordmark
   nobody was looking at.

   Paused, not stopped: the frame it holds is the frame it resumes from,
   so nothing snaps, and an entrance that was halfway through stays
   halfway through.

   Every rule here has to out-weigh the one that started the animation,
   because those use the `animation` shorthand and it resets play-state to
   running — `.js .hero-frame.is-drawn .hero-word-edge i` and
   `.gallery-strip.ready .g-track` both did exactly that. Hence the extra
   classes on two of these, which otherwise look redundant. */
.is-away .mh-cue,
.hero-frame.is-away::after,
.js .hero-frame.is-away .hero-word-edge i,
.gallery-strip.is-away.ready .g-track,
.is-away .ff-img,
.is-away .ff-trio-slot,
.is-away .mh-tz-note path,
.is-away .mh-lead path,
.is-away .mh-cue path,
.menu-gap.is-away .mg-cue,
.menu-gap.is-away .mg-cue path,
.menu-gap.is-away .mg-note path,
.is-away .pad-note path,
.is-away .gal-arr path,
.is-away .gp-note path {
  animation-play-state: paused;
}

/* ============================================================
   Reduced motion
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hero-frame::after { animation: none; }
  [data-px] { transform: none !important; }
  .rv, .js .rv { opacity: 1; transform: none; transition: none; }
  /* no lens entrance and no drift; the planes simply stack where they are,
     which still reads as depth because the occlusion is real */
  .hero-plane { transform: none; }
  .hero-word { transform: none; }
  .js .hero-word i,
  .js .hero-frame.is-ready .hero-word i {
    animation: none;
    opacity: 1;
    transform: none;
    filter: none;   /* the word is simply in focus, and simply there */
  }
  .logo-eyes, .logo-eyes.blink, .logo-eyes.blink2 { animation: none; }
  .ticker-track { animation: none; }
  .balloon .float { animation: none; }
  .sketch .mask-stroke, .js .sketch .mask-stroke { stroke-dashoffset: 0; transition: none; }
  .society-tag-art path, .js .society-tag-art path { opacity: 1; transform: none; animation: none; }
  .drinks-title .drinks-word, .js .drinks-title .drinks-word { opacity: 1; transition: none; }
  /* No arc, and because the arc is the clock, no turns either — the
     header keeps the picture it opened on. The dots stay: they are the
     only way to the other photograph now, which is more than they were
     worth here before. The extra `.mh-dots` is the weight this needs to
     out-rank the rule that starts the animation, which sets the
     shorthand and sits further down the file. */
  .mh-dots .mh-dot.is-on .mh-sweep path { animation: none; opacity: 0; }
}

/* ============================================================
   3D menu scene — hidden-line café, replaces the static bands
   once WebGL initializes (see js/menu-scene.js)
   ============================================================ */

.menu-scene {
  display: none;
  position: relative;
  height: clamp(560px, 92vh, 880px);
  background: var(--ink);
  overflow: hidden;
}

.menu-scene.ms-on { display: block; }

.ms-canvas,
.ms-canvas canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.ms-step {
  position: absolute;
  top: 22px;
  left: clamp(20px, 4vw, 44px);
  margin: 0;
  color: #b9b9b9;
  z-index: 2;
}

.ms-controls {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(18px, 4vh, 38px);
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
  z-index: 2;
  padding: 0 16px;
}

.ms-btn {
  font-family: var(--sans);
  font-weight: 800;
  font-size: 13px;
  line-height: 1;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--paper);
  background: rgba(10, 10, 10, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.85);
  padding: 14px 20px;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.ms-btn:hover,
.ms-btn:focus-visible { background: var(--paper); color: var(--ink); }
.ms-btn:focus-visible { outline-color: var(--paper); }

.ms-arrow { padding: 14px 16px; }

.ms-count {
  color: #b9b9b9;
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.18em;
  align-self: center;
  min-width: 46px;
  text-align: center;
}

@media (max-width: 760px) {
  .menu-scene { height: clamp(480px, 82vh, 700px); }
  .ms-btn { padding: 12px 14px; font-size: 12px; }
}

/* ============================================================
   Sticky menu head + category card decks (Drinks / Food)
   ============================================================ */

/* the whole section is ink-dark: without its own ground, the fractional
   head height (52vh) meets the integer-rounded body top with a subpixel
   slit that lets the page's white bleed through as a hairline */
.menu-sec { background: var(--ink); }

/* the wrapper's box extends over the pan (sticky travel) — it must be
   click-through, only the head itself catches the pointer */
.menu-headwrap {
  --mh-h: min(64vh, 660px);
  position: relative;
  height: var(--mh-h);
  z-index: 40;
  pointer-events: none;
}
.menu-headwrap .menu-head { pointer-events: auto; }

.menu-head {
  --mh-h: min(64vh, 660px);
  position: sticky;
  top: calc(var(--header-h) + var(--mh-strip) - var(--mh-h));
  height: var(--mh-h);
  z-index: 40;
  overflow: hidden;
  background: var(--ink);
}

.mh-media, .mh-img { position: absolute; inset: 0; }

.mh-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 40%;
  opacity: 0;
  /* long enough to read as a dissolve rather than a cut; both pictures
     are the same counter from different angles, so a quick swap would
     look like a glitch */
  transition: opacity 1.1s ease;
}
.mh-img.is-active { opacity: 1; }

/* which of the header's pictures is up, how long it has left, and the
   way to change it yourself. Still six pixels of paint at the corner —
   what makes it a control is a 24px target around each one and what the
   pointer does on the way in, not anything drawn. The dots exist only
   where there is more than one picture; JS builds them (see main.js). */
.mh-dots {
  position: absolute;
  /* the paint keeps the corner it always had: the targets overhang it
     by the 19px of padding around a 6px dot — but never past the
     viewport edge, so the touch target stays whole on a phone */
  right: max(calc(clamp(14px, 2vw, 26px) - 19px), 0px);
  /* clear of the strip, where they used to sit. The bar that fills that
     band is above them and was quietly eating the clicks — which nobody
     could notice while these were decoration. Above the line they are in
     the photograph they belong to, and they leave with the head when it
     shrinks, the way the cue does: the strip has no room for them and no
     use for a control over a picture it shows 92px of. */
  bottom: calc(var(--mh-strip) + clamp(14px, 2vh, 24px) - 19px);
  z-index: 2;
  display: flex;
  gap: 0;   /* the targets meet — no dead pixel between them to miss on */
}
.mh-dot {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  margin: 0;
  padding: 0;
  background: none;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--paper);
}
.mh-dot > * { grid-area: 1 / 1; }   /* dot and arc share the one cell */
.mh-dot i {
  display: block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.3;
  transition: opacity 0.45s ease;
}
.mh-dot.is-on i { opacity: 0.92; }
/* the quiet dot answers the pointer; the live one is already as bright
   as it gets and has nothing to say back */
.mh-dot:hover i, .mh-dot:focus-visible i { opacity: 0.6; }

/* The arc is the clock (main.js): one turn of the picture, drawn the
   way every other stroke on this page is drawn — a dash the length of
   its own path, walked back to nothing. It closes on the dissolve. */
.mh-sweep {
  width: 16px;
  height: 16px;
  overflow: visible;
  pointer-events: none;
}
.mh-sweep path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-linecap: round;
  /* a dash the length of the whole ring, walked in from nothing */
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  opacity: 0;
}
.mh-dot.is-on .mh-sweep path {
  opacity: 0.3;
  animation: mh-sweep 8s linear infinite;
}
@keyframes mh-sweep {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}
/* Pointing at the dots holds the picture: the arc stops where it stands
   and goes on from there, rather than starting over, so a reader who
   pauses to aim does not lose the turn. All of these have to out-weigh
   the rule above, which sets the shorthand and would otherwise put the
   play-state back to running. */
.mh-dots:has(:focus-visible) .mh-dot.is-on .mh-sweep path,
.is-away .mh-dot.is-on .mh-sweep path {
  animation-play-state: paused;
}
/* only where there is a pointer to rest. A phone keeps :hover on the
   last thing tapped, which would have parked the arc for good on the
   one gesture meant to move it — and :focus-visible, rather than
   :focus-within, is what keeps the tap itself from doing the same */
@media (hover: hover) {
  .mh-dots:hover .mh-dot.is-on .mh-sweep path { animation-play-state: paused; }
}

/* All the open head carries is a cue that there is more below it. It
   goes with the head when the head shrinks — the strip that remains has
   no room for anything. */
.mh-first {
  position: absolute;
  left: clamp(20px, 5vw, 76px);
  bottom: clamp(56px, 10vh, 104px);
  z-index: 2;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
/* the pinned strip is the head's bottom slice — on a phone the cue's
   bottom anchor lands inside it, so the cue leaves when the strip forms */
.menu-head.is-pinned .mh-first { opacity: 0; }
/* the cue: the same hand that annotates the photographs, pointing the
   way the page goes — three jittered strokes cycling like frames
   (menu.js draws them), bobbing gently */
.mh-cue {
  display: block;
  width: 30px;
  height: clamp(48px, 8vh, 68px);
  opacity: 0.85;
  animation: cue-bob 2.3s ease-in-out infinite;
}
.mh-cue svg, .mg-cue svg { display: block; width: 100%; height: 100%; overflow: visible; }
.mh-cue path, .mg-cue path {
  fill: none;
  stroke: var(--paper);
  stroke-width: 2px;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
}
.mh-cue path:nth-child(1), .mg-cue path:nth-child(1) { animation: boil-a 0.48s steps(1) infinite; }
.mh-cue path:nth-child(2), .mg-cue path:nth-child(2) { animation: boil-b 0.48s steps(1) infinite; }
.mh-cue path:nth-child(3), .mg-cue path:nth-child(3) { animation: boil-c 0.48s steps(1) infinite; }
@keyframes cue-bob {
  0%, 100% { transform: translateY(0); opacity: 0.55; }
  55%      { transform: translateY(7px); opacity: 0.9; }
}

@media (max-width: 760px) {
  .mh-first { left: 16px; bottom: 52px; }
  .mh-cue { width: 26px; height: 54px; }
}

/* ============================================================
   The breather between the boards — a dark band with a paper
   signpost, so overshooting the drinks board lands on a landmark
   instead of inside Snacks (menu.js fills the note and arrow)
   ============================================================ */
.menu-gap {
  position: relative;
  z-index: 1;
  display: grid;
  place-content: center;
  justify-items: center;
  row-gap: 26px;
  height: 44vh;
  background: var(--ink);
  color: var(--paper);
}
.mg-note {
  position: relative;
  padding: 15px 26px 16px;
  color: var(--ink);
  transform: rotate(-2.5deg);
}
.mg-note .boil { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.mg-note path {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.4px;
  stroke-linejoin: round;
  opacity: 0;
}
.mg-note path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.mg-note path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.mg-note path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }
.mg-in { position: relative; display: grid; gap: 4px; justify-items: center; }
.mg-in i {
  font-style: normal;
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.7;
}
.mg-in b {
  font-family: var(--display);
  font-size: 30px;
  line-height: 1.1;
  letter-spacing: 0.02em;
}
.mg-cue {
  display: block;
  width: 28px;
  height: 62px;
  opacity: 0.8;
  animation: cue-bob 2.3s ease-in-out infinite;
}
@media (max-width: 760px) {
  /* a phone is where the mis-scroll happens — give the breather a real
     screen's worth of ground */
  .menu-gap { height: 58svh; }
}
@media (prefers-reduced-motion: reduce) {
  .mh-cue, .mg-cue { animation: none; }
}

/* ============================================================
   Head teasers — the annotated frame. Paper notes pinned over the
   open head's photograph, each a doorway into its category; they
   drift up and fade with the head's shrink progress (--hp) so the
   strip's picks read as the same notes, landed.
   ============================================================ */
.mh-teasers {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  transition: opacity 0.45s ease;
}
/* only the active picture's notes show (snacks has two shots) */
.menu-head[data-shot="0"] .mh-teasers:not([data-shot="0"]),
.menu-head[data-shot="1"] .mh-teasers:not([data-shot="1"]) { opacity: 0; }
.menu-head[data-shot="0"] .mh-teasers:not([data-shot="0"]) .mh-tease,
.menu-head[data-shot="1"] .mh-teasers:not([data-shot="1"]) .mh-tease { pointer-events: none; visibility: hidden; transition: visibility 0s linear 0.45s; }

.mh-tease {
  position: absolute;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  pointer-events: auto;
  transform: translate(-50%, -50%)
    translateY(calc(var(--hp, 0) * -46px))
    scale(calc(1 - var(--hp, 0) * 0.12));
  opacity: calc(1 - var(--hp, 0) * 1.55);
}
.mh-tease:focus-visible { outline-color: var(--paper); }
/* past the handoff point the invisible buttons must not catch taps or tabs */
.menu-head.tz-out .mh-tease {
  pointer-events: none;
  visibility: hidden;
  transition: visibility 0s linear 0.2s;
}

/* the note itself: same paper, same ladder as the board notes */
.mh-tz-note {
  position: relative;
  display: block;
  width: max-content;
  max-width: clamp(150px, 14vw, 210px);
  padding: 10px 14px 11px;
  transform: rotate(var(--rot, -3deg));
}
.mh-tz-note .boil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.mh-tz-note path {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.4px;
  stroke-linejoin: round;
  opacity: 0;
}
.mh-tz-note path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.mh-tz-note path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.mh-tz-note path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }

/* hand-drawn leader stroke out to the subject, chevron at its end */
.mh-lead { position: absolute; overflow: visible; pointer-events: none; }
.mh-lead path {
  fill: none;
  stroke: var(--paper);
  stroke-width: 1.4px;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
}
.mh-lead path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.mh-lead path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.mh-lead path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }

/* entrance: flicked in like every other paper scrap, staggered */
.js .menu-head:not(.tz-in) .mh-tz-note,
.js .menu-head:not(.tz-in) .mh-lead { opacity: 0; }
.menu-head.tz-in .mh-tz-note {
  animation: tz-pop 0.38s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.menu-head.tz-in .mh-tease:nth-child(2) .mh-tz-note { animation-delay: 0.16s; }
.menu-head.tz-in .mh-lead { animation: tz-fade 0.3s ease 0.24s both; }
@keyframes tz-pop {
  from { transform: rotate(var(--rot, -3deg)) scale(0.5) translateY(6px); opacity: 0; }
  to   { transform: rotate(var(--rot, -3deg)) scale(1) translateY(0); opacity: 1; }
}
@keyframes tz-fade { from { opacity: 0; } to { opacity: 1; } }

/* hover: the lift the board tiles taught */
@media (hover: hover) {
  .mh-tease:hover .mh-tz-note { transform: rotate(var(--rot, -3deg)) scale(1.05); }
}

@media (max-width: 760px) {
  .mh-tz-note { max-width: min(46vw, 180px); }
}

@media (prefers-reduced-motion: reduce) {
  .menu-head.tz-in .mh-tz-note { animation: tz-fade 0.3s ease both; }
  .menu-head.tz-in .mh-lead { animation: tz-fade 0.3s ease both; }
  .mh-tz-note path, .mh-lead path { animation: none; }
  .mh-tz-note path:nth-child(1), .mh-lead path:nth-child(1) { opacity: 1; }
  .mh-tease { transform: translate(-50%, -50%); }
}

.mh-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(10, 10, 10, 0) 40%, rgba(10, 10, 10, 0.84));
}

.menu-head .drinks-title {
  top: 36%;
  right: 5vw;
  width: clamp(260px, 32vw, 540px);
  transform: translateY(-50%);
  transform-origin: 100% 50%;
  will-change: transform;
  z-index: 2;
}

.drinks-title.in-view .drinks-word.swap { opacity: 0; transition: opacity 0.2s ease 0s; }

/* the ink mass sits high in the svg box — drop the sketch so the word
   centers on it, and run the mark wider than its box so the word has
   paint around it. The brush tapers at both ends and the word was fitted
   to the box rather than to the ink, so DRINKS/SNACKS ran out past the
   stroke at the corners; growing the mark rather than shrinking the word
   keeps the wordmark its own size. */
.menu-head .swash-art { transform: translateY(6%) scale(1.16); }

.mh-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--mh-strip);
  display: flex;
  align-items: center;
  padding: 0 var(--gutter);
  z-index: 3;
}

.mh-rail-clip { flex: 1; min-width: 0; overflow: hidden; }

/* header strip: the PADMA character (the oval wearing the logo eyes)
   squish-reveals three featured products sliding to the right — only
   once the head has pinned into its header form */
.mh-pad {
  position: relative;
  display: flex;
  align-items: center;
  gap: 18px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity 0.3s ease, transform 0.45s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s 0.3s;
}
.menu-head.is-pinned .mh-pad {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 0.3s ease, transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}

/* the wrapper breathes (idle drift + tilt), the svg squishes — kept
   apart so the two transforms don't overwrite each other */
.pad-avatar {
  flex: 0 0 auto;
  display: block;
  animation: pad-idle 5.2s ease-in-out infinite;
}
.pad-mark {
  display: block;
  width: 54px;
  height: 70px;
  transform-origin: 50% 92%;
}
.pad-mark .pad-lens { fill: #050505; stroke: rgba(255, 255, 255, 0.75); stroke-width: 1.6; }
.pad-eyes { color: var(--paper); }
.pad-mark.pulse { animation: pad-squish 0.85s ease-in-out; }
.pad-mark.pulse-r { animation: pad-squish-r 0.85s ease-in-out; }

/* idle: a slow rocking tilt with the faintest lift — alive, not floaty */
@keyframes pad-idle {
  0%, 100% { transform: translateY(0) rotate(-2.6deg); }
  50% { transform: translateY(-2px) rotate(2.6deg); }
}

/* squash and stretch, upright — no lean; the two variants differ only
   in depth so successive squishes aren't identical */
@keyframes pad-squish {
  0%, 100% { transform: scale(1, 1); }
  34% { transform: scale(1.09, 0.89); }
  66% { transform: scale(0.96, 1.06); }
  85% { transform: scale(1.02, 0.985); }
}
@keyframes pad-squish-r {
  0%, 100% { transform: scale(1, 1); }
  34% { transform: scale(1.06, 0.93); }
  66% { transform: scale(0.975, 1.035); }
  85% { transform: scale(1.012, 0.992); }
}

/* speech bubble off the character's top-right corner — it pops out
   after the arrival squish. It lives OUTSIDE the menu head (fixed,
   positioned from the character by JS) so it can rise over the site
   header instead of being clipped by the head and painted under the
   top bar; the head's own stacking context sits below the header. */

.pad-note {
  position: fixed;
  z-index: 65;
  pointer-events: none;
  padding: 7px 10px 8px;
  font-weight: 800;
  font-size: 10.5px;
  letter-spacing: 0.04em;
  white-space: nowrap;
  color: var(--ink);
  opacity: 0;
  /* hidden, not merely transparent — see .ff-note: a raster cached at
     this half scale would ghost back blurry when the note pops in */
  visibility: hidden;
  transform: rotate(-4deg) scale(0.5);
  transform-origin: 0 100%;
  transition: opacity 0.18s ease, transform 0.42s cubic-bezier(0.34, 1.56, 0.64, 1),
    visibility 0s 0.42s;
}
.pad-note.show {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s, 0s, 0s;
  transform: rotate(-4deg) scale(1);
}
/* pulled the moment the page moves, so it never reads as a sticker
   sliding along over the top bar */
.pad-note.gone {
  opacity: 0;
  transform: rotate(-4deg) scale(0.86);
  transition: opacity 0.1s linear, transform 0.1s linear;
}
.pad-note .boil { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.pad-note path {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.4px;
  stroke-linejoin: round;
  opacity: 0;
}
.pad-note path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.pad-note path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.pad-note path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }
.pad-note i { position: relative; font-style: normal; }

/* three featured picks at once, each squished out to the right */
/* the picks group to the left, right after the character — the bubble
   rides above them now, so they need no horizontal reserve */
.pad-cards {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 30px;
  margin-left: 4px;
}
/* desktop: the shrunken swash title parks in the strip's top-right —
   keep the picks out from under it */
@media (min-width: 761px) {
  .pad-cards { margin-right: calc(5vw + clamp(99px, 12.2vw, 205px)); }
}
.pad-card {
  /* never shrink: if the row redistributed space, a sibling growing
     would re-wrap this card's name mid-animation */
  flex: 0 0 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0;
  border: 0;
  background: transparent;
  text-align: left;
  font-weight: 700;
  font-size: 11.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--paper);
  cursor: pointer;
  opacity: 0;
  transform-origin: 0 50%;
  /* width animates so a longer or shorter name slides its neighbours
     along instead of snapping them sideways */
  transition: transform 0.28s ease-in, opacity 0.26s ease-in,
    width 0.46s cubic-bezier(0.45, 0, 0.25, 1);
  overflow: hidden;
}
.pad-card:focus-visible { outline-color: var(--paper); }
/* the strip drops its third pick when three don't fit (padFit) */
.pad-card.pad-off { display: none; }
/* name wraps across rows, price lands underneath it */
/* The label wraps against its own cap, so its line count never depends
   on the neighbouring picks. flex:none matters: as a shrinkable flex
   item it was squeezed while the card's width animated, which re-wrapped
   the name mid-swap - the card clips it instead as it widens. */
.pad-ctext {
  display: grid;
  gap: 3px;
  flex: 0 0 auto;
  max-width: 190px;
}
.pad-cname {
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.pad-cprice { font-style: normal; font-size: 10.5px; letter-spacing: 0.16em; opacity: 0.72; }
.pad-card img {
  height: 52px;
  width: auto;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45));
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.pad-card img.is-card {
  height: 46px;
  width: 46px;
  object-fit: cover;
  border-radius: 8px;
  filter: none;
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.45);
}
.pad-card.out { transform: translateX(42px); opacity: 0; }
.pad-card.in { animation: pad-card-in 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
@keyframes pad-card-in {
  from { transform: translateX(-30px) scale(0.7); opacity: 0; }
  to { transform: translateX(0) scale(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .pad-mark.pulse, .pad-card.in, .pad-avatar { animation: none; }
  .pad-card, .mh-pad, .pad-note { transition: none; }
  .pad-note path { animation: none; }
  .pad-note path:nth-child(1) { opacity: 1; }
}


.menu-noscript { padding: 24px 0; color: var(--smoke); }

[data-px] { will-change: transform; }

@media (max-width: 760px) {
  .menu-headwrap, .menu-head { --mh-h: 52vh; }
  .menu-head .drinks-title { width: clamp(190px, 46vw, 320px); right: 3vw; }
  /* ONE strip row: character, picks, and the shrunken sketch title
     sharing it — the title parks at the right end (it's the
     absolutely-positioned .drinks-title, landed there by onScroll) */
  .mh-bar {
    flex-direction: row;
    align-items: center;
    gap: 0;
    padding: 0 var(--gutter);
  }
  /* The room for the title is reserved on the clip, not on the cards.
     The cards are flex:0 0 auto inside a flex:1 min-width:0 box, so they
     overflow it and are only stopped by its overflow:hidden — a margin on
     them moved nothing, and a long name like Croissante had the last
     picture sitting across it. Held back to where the clip ends, they
     stop with 34px to spare. */
  .mh-rail-clip {
    flex: 1;
    min-width: 0;
    /* keep this factor in step with `end` in onScroll */
    margin-right: calc(3vw + clamp(190px, 46vw, 320px) * 0.72 + 14px);
  }
  .mh-tab { padding: 11px 13px; font-size: 12px; }
  .mh-pad { gap: 12px; }
  .pad-mark { width: 40px; height: 52px; }
  .pad-note { padding: 6px 8px 7px; font-size: 9.5px; }
  /* pictures only — tapping one opens the product itself */
  .pad-cards { gap: 13px; }
  .pad-ctext { display: none; }
  .pad-card { flex: 0 0 auto; }
  .pad-card img { height: 42px; }
  .pad-card img.is-card { height: 38px; width: 38px; }
}


@media (prefers-reduced-motion: reduce) {
  .menu-head .drinks-title { transform: translateY(-50%) !important; }
  .mh-img { transition: none; }
}

/* ============================================================
   Menu cycler — pinned stage, rooms pan horizontally
   ============================================================ */

.menu-body {
  position: relative;
  z-index: 1;
  background: var(--ink);
  color: var(--paper);
}

.cycler-stage {
  position: sticky;
  top: calc(var(--header-h) + var(--mh-strip, 92px));
  /* svh, deliberately, not dvh. dvh does track the auto-hiding toolbar —
     that is the problem, not the feature. The board is sticky and the
     phone retracts its URL bar mid-scroll, so a dvh height re-measures
     the stage while you are moving through it: the pinned board changes
     height under the finger, the giant word behind it re-centres, and
     the scroll position the browser is compensating for lands somewhere
     else. svh is the toolbar-visible height and never moves, so the
     board stays exactly as tall as it started and the page holds still.
     The extra strip that appears once the bar goes is stage-coloured
     ink, so it reads as the board, not as a gap. */
  height: calc(100svh - var(--header-h) - var(--mh-strip, 92px));
  overflow: hidden;
  background-color: var(--ink);
  background-repeat: no-repeat;
  transition: background-image 0.6s ease;
}

.cycler-word {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  pointer-events: none;
  z-index: 0;
}

.cycler-word span {
  font-family: var(--display);
  font-size: clamp(110px, 20vw, 300px);
  line-height: 1;
  white-space: nowrap;
  color: transparent;
  -webkit-text-stroke: 1px rgba(255, 255, 255, 0.12);
  letter-spacing: 0.04em;
  user-select: none;
}

/* pill language for the strip controls too */
.mh-tab { border-radius: 999px; }

.cy-pricelist { list-style: none; margin: 8px 0 4px; }
.cy-pricelist li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
  padding: 9px 2px;
  border-bottom: 1px dashed rgba(255, 255, 255, 0.2);
  font-size: 15px;
}
.cy-pricelist b {
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.12em;
  white-space: nowrap;
}

@media (max-width: 760px) {
  .cycler-stage {
    height: calc(100svh - var(--header-h) - var(--mh-strip));
  }
  /* anchored to the top of the (tall) stage so the sticky pin engages as
     soon as the section starts, not halfway down it */
  .cycler-word { align-items: flex-start; }
  .cycler-word span {
    font-size: 26vw;
    /* the stage is a tall in-flow block here, so the giant word would
       scroll away with it — stick it in the middle of the band under
       the strip instead, and let the drinks drift past it */
    position: sticky;
    top: calc(var(--header-h) + var(--mh-strip) +
      (100svh - var(--header-h) - var(--mh-strip)) / 2 - 0.5em);
  }
  .cy-pricelist li { padding: 7px 2px; font-size: 14px; }
}

/* ============================================================
   Site end — gallery filmstrip + dark footer (avec-inspired)
   ============================================================ */

.site-end { background: var(--ink); color: var(--paper); }

.gallery { padding: clamp(70px, 10vh, 120px) 0 0; }

/* desktop-only pin-and-shrink: the ::after spacer + negative margin
   extend the section's CONTENT box downward (page height unchanged) so
   the sticky pin has travel room while the footer slides over — sticky
   travel ignores padding, and overflow must stay visible or sticky
   won't pin. The pin paints above the footer so hover notes cover its
   text; the shell is click-through, only tiles and the tag catch the
   pointer. Mobile keeps normal flow. */
@media (min-width: 761px) {
  .gallery { margin-bottom: -45vh; }
  .gallery::after { content: ""; display: block; height: 45vh; }
  .gallery-pin {
    position: sticky;
    top: calc(var(--header-h, 92px) + 10px);
    z-index: 3;
    pointer-events: none;
  }
  .gallery-pin .g-pic,
  .gallery-pin .gallery-tag { pointer-events: auto; }
}

/* JS shrinks only the track contents (never the tag), just enough to
   clear the approaching footer; scaling the track instead of the strip
   keeps tiles filling the full width — the band stays endless */

.gallery-tag {
  display: inline-block;
  margin: 0 0 26px clamp(20px, 4vw, 60px);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--smoke);
  text-decoration: none;
  transition: color 0.25s ease;
}
.gallery-tag:hover, .gallery-tag:focus-visible { color: var(--paper); }

/* horizontal marquee clip lives here (not on an ancestor of the sticky
   pin); notes still overflow vertically */
.gallery-strip { position: relative; overflow-x: clip; overflow-y: visible; }

/* sketchy scrub arrows (desktop): tall boiling paper boxes at each end
   of the gallery — hovering one slides the strip that way */
.gal-arr {
  position: absolute;
  top: 50%;
  z-index: 6;
  width: 54px;
  height: 150px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-50%) scale(0.9);
  transition: opacity 0.22s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.gal-arr--l { left: 16px; }
.gal-arr--r { right: 16px; }
.gallery-strip:hover .gal-arr,
.gal-arr:focus-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(-50%) scale(1);
}
.gal-arr .boil { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.gal-arr path {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.4px;
  stroke-linejoin: round;
  opacity: 0;
}
.gal-arr path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.gal-arr path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.gal-arr path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }
.gal-arr span {
  position: relative;
  font-size: 32px;
  font-weight: 800;
  line-height: 1;
  display: inline-block;
}
.gal-arr--l:hover span { animation: arr-lean-l 0.55s ease-in-out infinite alternate; }
.gal-arr--r:hover span { animation: arr-lean-r 0.55s ease-in-out infinite alternate; }
@keyframes arr-lean-l { from { transform: translateX(1px); } to { transform: translateX(-4px); } }
@keyframes arr-lean-r { from { transform: translateX(-1px); } to { transform: translateX(4px); } }
@media (hover: none) { .gal-arr { display: none; } }
.gallery-strip.swipe .gal-arr { display: none; }
@media (prefers-reduced-motion: reduce) {
  .gal-arr path, .gal-arr span { animation: none; }
  .gal-arr path:nth-child(1) { opacity: 1; }
}

.g-track { display: flex; align-items: flex-end; width: max-content; }
.g-half { display: flex; align-items: flex-end; }

.g-pic {
  flex: 0 0 auto;
  display: block;
  margin: 0;
  width: calc(var(--gw, 13) * 1vw);
}
.g-pic img { width: 100%; height: auto; display: block; }
.g-pic:focus-visible { outline: 2px solid var(--paper); outline-offset: -2px; }

/* hover: the room makes way — others dim and step aside, this one
   floats up and tilts toward the pointer like the menu cluster images */
.gp-in { position: relative; display: block; transition: opacity 0.3s ease; }
.g-pic { transform-origin: 50% 100%; }
/* the hover treatment only exists where hover does — mobile Chrome
   applies :hover to whatever lands under the last tap point, and after
   the centring scroll that is a DIFFERENT post ghost-focusing itself */
@media (hover: hover) {
  .gallery-strip:has(.g-pic:hover) .gp-in { opacity: 0.4; }
  .gallery-strip .g-pic:hover .gp-in,
  .gallery-strip .g-pic:focus-visible .gp-in { opacity: 1; }
  .g-pic:hover, .g-pic:focus-visible { z-index: 2; }
  .g-pic:hover,
  .g-pic:focus-visible {
    transform: perspective(700px) rotateX(calc(var(--my, 0) * -7deg)) rotateY(calc(var(--mx, 0) * 9deg)) translateY(-12px) scale(1.14);
  }
  /* neighbors spread to make space (both directions, across halves) */
  .g-pic:hover ~ .g-pic { transform: translateX(14px); }
  .g-pic:has(~ .g-pic:hover) { transform: translateX(-14px); }
  .g-half:has(.g-pic:hover) + .g-half .g-pic { transform: translateX(14px); }
  .g-half:has(+ .g-half .g-pic:hover) .g-pic { transform: translateX(-14px); }
}

/* post note: pfp, likes and caption on a boiling paper tag,
   same recipe as the menu item notes */
.gp-note {
  position: absolute;
  /* overlap at most 26px into the image — or 14% of it, whichever is
     smaller, so short images stay uncovered */
  bottom: max(calc(100% - 26px), 86%);
  right: -34px;
  z-index: 5;
  pointer-events: none;
  width: max-content;
  max-width: min(210px, 130%);
  padding: 12px 15px 13px;
  opacity: 0;
  /* hidden, not merely transparent — see .ff-note: a raster cached at
     this half scale would ghost back blurry under the hover tilt */
  visibility: hidden;
  /* divided by --gs: notes stay natural-size when the track shrinks */
  transform: translate3d(0, 6px, 0) rotate(var(--rot, -3deg)) scale(calc(0.5 / var(--gs, 1)));
  transform-origin: 100% 100%;
  transition: opacity 0.16s ease, transform 0.38s cubic-bezier(0.34, 1.56, 0.64, 1),
    visibility 0s 0.38s;
}
@media (hover: hover) {
  .g-pic:hover .gp-note,
  .g-pic:focus-visible .gp-note {
    opacity: 1;
    visibility: visible;
    transition-delay: 0s, 0s, 0s;
    transform: translate3d(calc(var(--mx, 0) * 16px), calc(var(--my, 0) * 8px), 0)
      rotate(var(--rot, -3deg)) scale(calc(1 / var(--gs, 1)));
  }
}

/* rightmost visible post: the note flips to the left side */
.g-pic.note-left .gp-note {
  right: auto;
  left: -34px;
  transform-origin: 0 100%;
}

/* while the strip is pinned under the header, notes hang below the
   tiles instead (same height-aware overlap, mirrored) */
.gallery-strip.pinned .gp-note {
  bottom: auto;
  top: max(calc(100% - 26px), 86%);
  transform-origin: 100% 0;
}
.gallery-strip.pinned .g-pic.note-left .gp-note { transform-origin: 0 0; }

/* hover-nudge slides, pin-shrink scales — both compose with the
   marquee animation via individual transform properties */
.g-track {
  transform-origin: 0 0;
  transition: translate 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}

.gp-note .boil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.gp-note path {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.4px;
  stroke-linejoin: round;
  opacity: 0;
}
@media (hover: hover) {
  .g-pic:hover .gp-note path:nth-child(1),
  .g-pic:focus-visible .gp-note path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
  .g-pic:hover .gp-note path:nth-child(2),
  .g-pic:focus-visible .gp-note path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
  .g-pic:hover .gp-note path:nth-child(3),
  .g-pic:focus-visible .gp-note path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }
}

.gp-nin { position: relative; display: grid; gap: 4px; color: var(--ink); }
.gp-nrow { display: flex; align-items: center; gap: 7px; }
.gp-nrow .gp-ava {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1.5px solid var(--ink);
}
.gp-likes { font-weight: 800; font-size: 0.92em; letter-spacing: 0.06em; }
.gp-cap {
  font-size: 0.82em;
  font-weight: 600;
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 8;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.gallery-strip.ready .g-track {
  animation: gal-scroll var(--gal-dur, 160s) linear infinite;
}
.gallery-strip.ready:has(.g-pic:hover) .g-track,
.gallery-strip.ready:focus-within .g-track {
  animation-play-state: paused;
}
/* -25% → -75% spans exactly one half, so the loop stays seamless while
   keeping plenty of track on both sides of the viewport (needed for the
   centered pin-shrink) */
@keyframes gal-scroll {
  from { transform: translate3d(-25%, 0, 0); }
  to { transform: translate3d(-75%, 0, 0); }
}

.js .clip-rv { clip-path: inset(100% 0 0 0); }
.clip-rv.in-view {
  clip-path: inset(0 0 0 0);
  transition: clip-path 1s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: var(--d, 0ms);
}

/* gallery tiles animate transform (hover interplay) and clip (reveal)
   together; --d delays only the reveal wipe */
.gallery-strip .g-pic {
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
              clip-path 1s cubic-bezier(0.22, 1, 0.36, 1) var(--d, 0ms);
}
/* once revealed, the clip opens past the box so the note can stick out */
.gallery-strip .g-pic.in-view { clip-path: inset(-180px); }

/* positioned so its content paints above the pinned gallery, but
   transparent — the strip stays visible through the padding gap; the
   shell passes pointer events through to gallery tiles beneath, only
   the actual content catches them */
.footer2 { padding: clamp(60px, 9vh, 110px) 0 0; position: relative; pointer-events: none; }
.footer2 .f2-wrap { pointer-events: auto; }

.f2-wrap {
  max-width: 1440px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.f2-main {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: clamp(30px, 4vw, 80px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.18);
  padding-bottom: clamp(40px, 6vh, 70px);
}

.f2-left ul { list-style: none; }
.f2-left li + li { margin-top: 6px; }

.f2-link {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(34px, 4.2vw, 58px);
  line-height: 1.25;
  color: var(--paper);
  display: inline-flex;
  align-items: center;
  gap: 12px;
}

.f2-arrows {
  position: relative;
  display: inline-block;
  width: 28px;
  height: 24px;
  overflow: hidden;
}
.f2-arrows svg { position: absolute; left: 4px; top: 4px; transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1); }
.f2-arrows svg:nth-child(2) { transform: translateX(-28px); }
.f2-link:hover .f2-arrows svg:nth-child(1),
.f2-link:focus-visible .f2-arrows svg:nth-child(1) { transform: translateX(28px); }
.f2-link:hover .f2-arrows svg:nth-child(2),
.f2-link:focus-visible .f2-arrows svg:nth-child(2) { transform: translateX(0); }
.f2-arrows.rot svg { rotate: -45deg; }

.f2-right {
  border-left: 1px solid rgba(255, 255, 255, 0.18);
  padding-left: clamp(24px, 3vw, 60px);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(30px, 5vh, 56px);
}

.f2-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }

.f2-title {
  display: block;
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-weight: 800;
  color: #9a9a9a;
  margin-bottom: 12px;
}

.f2-text { color: #d9d9d9; }
.f2-text + .f2-text { margin-top: 10px; }

/* address and phone read as facts first, links second */
.f2-inline {
  color: inherit;
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
.f2-inline:hover,
.f2-inline:focus-visible { background-size: 100% 1px; }

/* live opening state — a hand-inked dot, on the site's black-and-white terms */
.f2-open {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin-top: 14px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper);
}
.f2-open::before {
  content: "";
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  transform: translateY(-1px);
}
.f2-open.is-open::before { background: currentColor; }
.f2-open:not(.is-open) { color: #a8a8a8; }

.f2-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 26px 0 30px;
}

.f2-logo {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-family: var(--display);
  font-size: 19px;
  letter-spacing: 0.04em;
  color: var(--paper);
}
.f2-logo .logo-eyes { width: 42px; }

.f2-legal, .f2-copy { color: #9a9a9a; font-size: 13.5px; }

@media (max-width: 900px) {
  .f2-main { grid-template-columns: 1fr; }
  .f2-right { border-left: 0; padding-left: 0; border-top: 1px solid rgba(255, 255, 255, 0.18); padding-top: 30px; }
}

@media (max-width: 760px) {
  .g-pic { width: calc(var(--gw, 13) * 2.3vw); }
  .f2-bottom { flex-direction: column; align-items: flex-start; gap: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .js .clip-rv { clip-path: inset(0); transition: none; }
  .cycler-stage { transition: none; }
  .gallery-strip.ready .g-track { animation: none; }
  .gallery-strip { overflow-x: auto; }
  .gallery-strip .g-pic { transform: none !important; transition: none; clip-path: none !important; }
  .gp-note { transition: none; }
  .gp-note path { animation: none !important; }
  .g-pic:hover .gp-note path:nth-child(1),
  .g-pic:focus-visible .gp-note path:nth-child(1) { opacity: 1; }
}

/* ============================================================
   Semnăturile casei — dark statement + numbered slider
   ============================================================ */

.num-eyebrow {
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  margin-bottom: clamp(22px, 4vh, 40px);
  color: var(--smoke);
}

.signatures {
  background: var(--ink);
  color: var(--paper);
  padding: clamp(80px, 12vh, 150px) 0;
}

.signatures .num-eyebrow { color: #9a9a9a; }

.sig-statement {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(26px, 3.3vw, 52px);
  line-height: 1.32;
  max-width: 21em;
  text-indent: 2.2em;
  margin-bottom: clamp(46px, 8vh, 90px);
}

.sig-statement .raw { opacity: 0.16; transition: opacity 0.35s ease; }
.sig-statement .raw.lit { opacity: 1; }

.sig-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-top: 1px solid rgba(255, 255, 255, 0.18);
  padding-top: 18px;
  margin-bottom: clamp(20px, 3.5vh, 36px);
}

.sig-count {
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.22em;
  color: #cfcfcf;
}
.sig-count b { font-weight: 800; color: var(--paper); }

.sig-arrows { display: flex; gap: 10px; }

/* the pager's arrows are small boiling paper scraps, the gallery scrub
   arrows' pocket-size cousins — never a bare native button */
.cy-arrow {
  position: relative;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--paper);
  font-size: 19px;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* with the drawn paper behind it the glyph flips to ink */
.cy-arrow.has-boil { color: var(--ink); }
.cy-arrow:focus-visible { outline-color: var(--paper); }
.cy-arrow .boil { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.cy-arrow path {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.4px;
  stroke-linejoin: round;
  opacity: 0;
}
.cy-arrow path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.cy-arrow path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.cy-arrow path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }
.cy-arrow span { position: relative; }
.cy-arrow:hover { transform: scale(1.08); }
.cy-arrow:active { transform: scale(0.94); }
@media (prefers-reduced-motion: reduce) {
  .cy-arrow { transition: none; }
  .cy-arrow path { animation: none; }
  .cy-arrow path:nth-child(1) { opacity: 1; }
}

.sig-body {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: clamp(24px, 4vw, 70px);
  align-items: center;
}

.sig-meta { display: grid; }

.sig-slide {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.4s ease, transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}
.sig-slide.is-active { opacity: 1; transform: none; pointer-events: auto; }

.sig-name {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(30px, 3.6vw, 56px);
  line-height: 1.08;
  margin-bottom: 14px;
}

.sig-note { color: #b9b9b9; max-width: 40ch; }

.sig-media {
  position: relative;
  height: clamp(340px, 62vh, 560px);
}

.sig-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  opacity: 0;
  transform: scale(0.985);
  transition: opacity 0.45s ease, transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}
.sig-media img.is-active { opacity: 1; transform: none; }

@media (max-width: 900px) {
  .sig-body { grid-template-columns: 1fr; gap: 20px; }
  .sig-media { order: -1; height: clamp(260px, 52vw, 420px); }
  .sig-statement { text-indent: 1.2em; }
}


/* ============================================================
   Nav scrollspy — the ink hops to the section you're in
   ============================================================ */

.nav-sk { position: relative; }

.nav-sk .nav-ink {
  position: absolute;
  inset: -3px -16px -3px -16px;
  width: calc(100% + 32px);
  height: calc(100% + 6px);
  color: var(--paper);
  pointer-events: none;
}

.nav-sk .txt { position: relative; transition: color 0.2s ease; }

/* hover underline: sketchy line that wipes in and boils like the
   category tags' borders; the plain ::after bar is the no-JS fallback */
.nav-sk::after { display: none; }

.nav-boil {
  position: absolute;
  left: 1px;
  right: 1px;
  bottom: -3px;
  height: 7px;
  width: calc(100% - 2px);
  color: var(--paper);
  pointer-events: none;
  overflow: visible;
  clip-path: inset(0 100% 0 0);
  transition: clip-path 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav-sk:hover .nav-boil,
.nav-sk:focus-visible .nav-boil { clip-path: inset(0 0 0 0); }

.nav-boil path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  opacity: 0;
}
.nav-sk:hover .nav-boil path:nth-child(1),
.nav-sk:focus-visible .nav-boil path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.nav-sk:hover .nav-boil path:nth-child(2),
.nav-sk:focus-visible .nav-boil path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.nav-sk:hover .nav-boil path:nth-child(3),
.nav-sk:focus-visible .nav-boil path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }

/* the swash already marks the current section — no underline there */
.nav-sk.is-here .nav-boil { display: none; }

.nav-sk .mask-stroke {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 0.3s cubic-bezier(0.6, 0.04, 0.35, 1);
}

/* the swash retracts from the old name, then draws onto the new one
   with the same zigzag wipe as the menu headers */
.nav-sk.is-here .mask-stroke {
  stroke-dashoffset: 0;
  transition-duration: 0.55s;
  transition-delay: 0.14s;
}
.nav-sk.is-here .txt { color: var(--ink); transition-delay: 0.3s; }
.nav-sk.is-here::after { display: none; }

@media (prefers-reduced-motion: reduce) {
  .nav-sk .mask-stroke, .nav-sk .txt { transition: none; }
  .nav-boil { transition: none; }
  .nav-sk:hover .nav-boil path, .nav-sk:focus-visible .nav-boil path { animation: none; }
  .nav-sk:hover .nav-boil path:nth-child(1),
  .nav-sk:focus-visible .nav-boil path:nth-child(1) { opacity: 1; }
}

/* ============================================================
   Floating menu field — cutout groups, boiling info notes
   ============================================================ */

@keyframes boil-a { 0%, 32% { opacity: 1; } 33%, 100% { opacity: 0; } }
@keyframes boil-b { 0%, 32% { opacity: 0; } 33%, 65% { opacity: 1; } 66%, 100% { opacity: 0; } }
@keyframes boil-c { 0%, 65% { opacity: 0; } 66%, 100% { opacity: 1; } }

.ff-scroll {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  overflow: hidden;
}

/* tiles keep a generous size and WRAP onto more rows; when the rows
   outgrow one screen the stage goes .is-tall and the page scrolls */
.ff-field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  justify-content: center;
  column-gap: clamp(26px, 3vw, 88px);
  row-gap: clamp(58px, 8.5vh, 110px);
  /* keep the board a block on ultrawides instead of drifting apart */
  max-width: 2000px;
  width: 100%;
  margin: auto;
  padding: 30px var(--gutter) 64px;
  will-change: transform;
}
.ff-field.ff-scatter {
  align-items: flex-start;
  /* generous sides: resting captions overhang their tiles and must
     not touch the stage edge. The deep bottom lets the last row's
     captions (and their hover unfold) finish well before the next
     section's edge arrives. */
  padding: 22px calc(var(--gutter) * 0.9) clamp(120px, 16vh, 210px);
}
/* a handful of tiles that fit one row are simply lined up — the gap is
   generous because the captions are wider than the tiles they hang from */
.ff-field.ff-aligned {
  align-items: center;
  column-gap: clamp(50px, 5.5vw, 118px);
}

.ff-group {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  max-height: 54vh;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  translate: 0 var(--gy, 0px);
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s ease;
}

.ff-img {
  display: block;
  height: auto;
  rotate: var(--r, 0deg);
  filter: drop-shadow(0 16px 26px rgba(0, 0, 0, 0.5));
  /* Two motions, not one, on deliberately mismatched clocks — see the
     note over @keyframes ff-sway for why one is not enough. */
  animation: ff-float 7s ease-in-out infinite alternate,
             ff-sway 4.3s ease-in-out infinite alternate;
}
.ff-group .ff-img { height: 100%; width: auto; }

/* A category with a deep range shows three of it rather than one big one.
   The slots sit on a triangle and trade places on a timer, each arriving
   as a different flavour, so the tile reads as a range instead of a
   portrait of whichever product happens to be first in the data. */
.ff-trio {
  position: relative;
  display: block;
  height: 100%;
  aspect-ratio: 1;
}
.ff-trio-slot {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: 47%;
  aspect-ratio: 1;
  /* centred on its point with transform, so the `translate` property is
     left free for the float — they are separate properties and compose */
  transform: translate(-50%, -50%);
  animation: ff-float 7s ease-in-out infinite alternate;
  transition: left 0.95s cubic-bezier(0.22, 1, 0.36, 1),
              top 0.95s cubic-bezier(0.22, 1, 0.36, 1);
}
/* both pictures fill the slot so a swap between a tall scoop and a wide
   one cannot resize it mid-cross-fade */
.ff-trio-pic {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  rotate: var(--r, 0deg);
  filter: drop-shadow(0 10px 18px rgba(0, 0, 0, 0.5));
  opacity: 0;
  transition: opacity 0.55s ease;
}
.ff-trio-pic.is-on { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .ff-trio-slot { animation: none; transition: none; }
  .ff-trio-pic { transition: none; }
}
.ff-img.is-card {
  border-radius: 12px;
  object-fit: cover;
  filter: none;
  box-shadow: 0 16px 30px rgba(0, 0, 0, 0.5);
}
.ff-group .ff-img.is-card { aspect-ratio: 4 / 3; height: 100%; width: auto; }

@keyframes ff-float {
  from { translate: 0 -8px; }
  to { translate: 0 12px; }
}

/* The bob alone reads as a dropped framerate, and it is worth being
   precise about why, because the compositor is blameless: 15px of travel
   spread over 6-9s is a peak of 0.05px per frame, so the picture needs
   twenty frames to cross a single pixel. It renders every one of those
   frames, perfectly, sub-pixel — there is just nothing in most of them to
   see. Then ease-in-out parks it dead still at both ends of every cycle.

   A tilt fixes it where widening the bob cannot. A wide picture turns a
   fraction of a degree into real travel at its ends (0.8deg across the
   croissant boards is nearly 3px at each tip, in opposite directions),
   and it is exactly the wide flat ones — the croissants, cut with their
   board and pinned to 0deg by the stair — that looked worst. Small round
   tiles barely register it, which is right: they never had the problem.

   The clocks are mismatched on purpose. The bob stalls twice a cycle; at
   4.3s against 7s the sway is mid-swing when that happens, so something
   is always moving and the pair almost never repeats. */
@keyframes ff-sway {
  from { rotate: calc(var(--r, 0deg) - 1deg); }
  to { rotate: calc(var(--r, 0deg) + 1deg); }
}

.ff-listmark {
  margin: auto;
  font-family: var(--display);
  font-size: 110px;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(255, 255, 255, 0.5);
}
.ff-group:has(.ff-listmark) { width: clamp(90px, 8vw, 130px); }
/* "kitchen — opening soon": a teaser, not a door */
.ff-group.ff-soon { cursor: default; }
.ff-group.ff-soon .ff-listmark {
  font-size: 64px;
  letter-spacing: 0.04em;
  -webkit-text-stroke-color: rgba(255, 255, 255, 0.32);
}

.ff-item {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  background: transparent;
  border: 0;
  padding: 0 clamp(4px, 1vw, 18px);
  cursor: pointer;
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s ease;
}
.ff-item .ff-img { height: 100%; width: auto; }
.ff-item .ff-img.is-card { aspect-ratio: 3 / 4; }

/* the drill: the hovered one tilts toward the pointer, siblings step back

   `html:not(.no-hov)` on the hover halves — and only the hover halves, so
   .is-focus and :focus-visible are never affected — is what stops a board
   from arriving already picked. Open a category and the tiles are
   replaced under a pointer that has not moved, so whichever product lands
   where the finger or cursor was is hovered the instant it exists: it
   lifts, its caption unfolds, and every other product dims, all without
   being chosen. menu.js sets the class on each rebuild and drops it on
   the first real pointermove, and leaves it on for good where there is no
   hover to speak of. See the note by the class in menu.js. */
/* Where a mouse can tilt the tiles, promote them ahead of time: the
   pointer keeps the transform changing for as long as it moves, and a
   layer promoted mid-hover is stuck showing its pre-hover raster
   stretched to the hover scale — every caption stroke goes soft until
   the pointer rests. Promoted upfront, the raster follows the scale
   and the boil notes stay inked. Touch screens never tilt, so they
   skip the layer cost. */
@media (hover: hover) {
  .ff-group, .ff-item, .g-pic { will-change: transform; }
}
.ff-group:hover, .ff-group.is-focus, .ff-group:focus-visible,
.ff-item:hover, .ff-item.is-focus, .ff-item:focus-visible { z-index: 6; }
html:not(.no-hov) .ff-group:hover, .ff-group.is-focus, .ff-group:focus-visible {
  transform: perspective(700px) rotateX(calc(var(--my, 0) * -5deg)) rotateY(calc(var(--mx, 0) * 7deg)) scale(1.06);
}
html:not(.no-hov) .ff-item:hover, .ff-item.is-focus, .ff-item:focus-visible {
  transform: perspective(700px) rotateX(calc(var(--my, 0) * -6deg)) rotateY(calc(var(--mx, 0) * 8deg)) scale(1.09);
}
/* The dim works mid-ripple because arrival is a TRANSITION, not an
   animation: a tile lifts its .ff-wait veil when its wave moment comes
   and fades to whatever opacity the board wants right then — 1, or
   this 0.35 if a neighbour is already hovered — so nothing ever lands
   bright and snaps dark the way the old fade-in keyframe did. */
html:not(.no-hov) .ff-field:has(.ff-group:hover) .ff-group:not(:hover):not(.is-focus):not(:focus-visible),
.ff-field:has(.ff-group.is-focus, .ff-group:focus-visible) .ff-group:not(:hover):not(.is-focus):not(:focus-visible),
html:not(.no-hov) .ff-field:has(.ff-item:hover) .ff-item:not(:hover):not(.is-focus):not(:focus-visible),
.ff-field:has(.ff-item.is-focus, .ff-item:focus-visible) .ff-item:not(:hover):not(.is-focus):not(:focus-visible) {
  opacity: 0.35;
}
.ff-group:focus-visible, .ff-item:focus-visible { outline-color: var(--paper); }

/* boiling info note, pointer-responsive */
.ff-note {
  position: absolute;
  top: 2px;
  right: -14px;
  z-index: 7;
  pointer-events: none;
  width: max-content;
  /* narrower on mid-size screens so neighbouring captions don't graze */
  max-width: clamp(168px, 15vw, 240px);
  padding: 10px 14px 11px;
  opacity: 0;
  transform: translate3d(0, 6px, 0) rotate(var(--rot, -3deg)) scale(0.5);
  transform-origin: 100% 0;
  transition: opacity 0.16s ease, transform 0.38s cubic-bezier(0.34, 1.56, 0.64, 1);
  text-align: left;
}
.ff-item .ff-note { top: 6%; right: -8px; }
html:not(.no-hov) .ff-group:hover .ff-note, .ff-group.is-focus .ff-note, .ff-group:focus-visible .ff-note,
html:not(.no-hov) .ff-item:hover .ff-note, .ff-item.is-focus .ff-note, .ff-item:focus-visible .ff-note {
  opacity: 1;
  transform: translate3d(calc(var(--mx, 0) * 14px), calc(var(--my, 0) * 10px), 0)
    rotate(var(--rot, -3deg)) scale(1);
}

.ff-note .boil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.ff-note path {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.4px;
  stroke-linejoin: round;
  opacity: 0;
}
.ff-group:hover .ff-note path:nth-child(1), .ff-group.is-focus .ff-note path:nth-child(1), .ff-group:focus-visible .ff-note path:nth-child(1),
.ff-item:hover .ff-note path:nth-child(1), .ff-item.is-focus .ff-note path:nth-child(1), .ff-item:focus-visible .ff-note path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.ff-group:hover .ff-note path:nth-child(2), .ff-group.is-focus .ff-note path:nth-child(2), .ff-group:focus-visible .ff-note path:nth-child(2),
.ff-item:hover .ff-note path:nth-child(2), .ff-item.is-focus .ff-note path:nth-child(2), .ff-item:focus-visible .ff-note path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.ff-group:hover .ff-note path:nth-child(3), .ff-group.is-focus .ff-note path:nth-child(3), .ff-group:focus-visible .ff-note path:nth-child(3),
.ff-item:hover .ff-note path:nth-child(3), .ff-item.is-focus .ff-note path:nth-child(3), .ff-item:focus-visible .ff-note path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }

.ff-note-in { position: relative; display: grid; gap: 3px; color: var(--ink); }
.ff-note-in i { font-style: normal; }
.ff-nhead { font-weight: 800; font-size: 13.5px; letter-spacing: 0.02em; }
.ff-nrow { font-weight: 700; font-size: 12px; letter-spacing: 0.08em; }
.ff-ndesc {
  font-weight: 500;
  font-size: 11.5px;
  line-height: 1.45;
  opacity: 0.8;
  max-width: 210px;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.ff-panel { margin: auto; width: min(560px, 88%); position: relative; z-index: 1; }

.ff-back {
  position: absolute;
  top: 16px;
  left: var(--gutter);
  z-index: 4;
  padding: 11px 18px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.8);
  background: rgba(10, 10, 10, 0.4);
  color: var(--paper);
  font-weight: 800;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.ff-back:hover, .ff-back:focus-visible { background: var(--paper); color: var(--ink); }
.ff-back:focus-visible { outline-color: var(--paper); }

@media (max-width: 760px) {
  .ff-field { gap: 10px; padding: 14px var(--gutter) 50px; }
  .ff-note { max-width: 200px; }
  .ff-listmark { font-size: 76px; }
}

@media (prefers-reduced-motion: reduce) {
  .ff-img { animation: none; }
  .ff-group, .ff-item, .ff-note { transition: none; }
}

/* ============================================================
   Touch: first tap previews (the hover treatment), second tap acts
   ============================================================ */

@media (hover: none) {
  /* gallery posts */
  .g-pic.is-focus {
    z-index: 2;
    transform: perspective(700px) translateY(-12px) scale(1.14);
  }
  .gallery-strip:has(.g-pic.is-focus) .gp-in { opacity: 0.4; }
  .gallery-strip .g-pic.is-focus .gp-in { opacity: 1; }
  .g-pic.is-focus .gp-note {
    opacity: 1;
    visibility: visible;
    transition-delay: 0s, 0s, 0s;
    transform: rotate(var(--rot, -3deg)) scale(calc(1 / var(--gs, 1)));
  }
  .g-pic.is-focus .gp-note path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
  .g-pic.is-focus .gp-note path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
  .g-pic.is-focus .gp-note path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }
  .g-pic.is-focus ~ .g-pic { transform: translateX(14px); }
  .g-pic:has(~ .g-pic.is-focus) { transform: translateX(-14px); }
  .g-half:has(.g-pic.is-focus) + .g-half .g-pic { transform: translateX(14px); }
  .g-half:has(+ .g-half .g-pic.is-focus) .g-pic { transform: translateX(-14px); }
  .gallery-strip.ready:has(.g-pic.is-focus) .g-track { animation-play-state: paused; }

}

/* touch gallery: native horizontal scroller — the JS drift replaces the
   CSS marquee; top padding (cancelled by margin) keeps the hover notes
   inside the scrollport instead of being clipped by it */
.gallery-strip.swipe {
  overflow-x: auto;
  overflow-y: hidden;
  padding-top: 150px;
  margin-top: -150px;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.gallery-strip.swipe::-webkit-scrollbar { display: none; }
.gallery-strip.swipe .g-track { animation: none; }

/* category hover: the next two drinks fan out from behind the lead,
   tilting toward the side they emerge from */
.ff-group .ff-img { position: relative; z-index: 1; }
.ff-peek {
  position: absolute;
  left: 50%;
  top: 52%;
  height: 72%;
  width: auto;
  /* a wider-than-usual silhouette shrinks instead of sprawling */
  max-width: 74%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) rotate(0deg) scale(0.72);
  transition: transform 0.42s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.22s ease;
  filter: drop-shadow(0 12px 20px rgba(0, 0, 0, 0.45));
}
.ff-peek.is-card {
  border-radius: 10px;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  filter: none;
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.45);
}
.ff-group:hover .ff-peek--l,
.ff-group.is-focus .ff-peek--l,
.ff-group:focus-visible .ff-peek--l {
  opacity: 1;
  transform: translate(-121%, -55%) rotate(-14deg) scale(0.84);
}
.ff-group:hover .ff-peek--r,
.ff-group.is-focus .ff-peek--r,
.ff-group:focus-visible .ff-peek--r {
  opacity: 1;
  transform: translate(21%, -55%) rotate(13deg) scale(0.84);
}

/* The croissants are cut with their board, so each one is a long diagonal
   running low-left to high-right. Fanned out sideways they stack across
   their own grain and read as a pile; sent up-right and down-left they
   carry on the line the front one is already drawing, and the three of
   them make one flight — the same figure the opened category uses. They
   travel further than the side peeks do, because a board is wide and a
   short throw would only half-clear the one in front. */
.ff-group.ff-diag .ff-peek { max-width: 96%; }
.ff-group.ff-diag:hover .ff-peek--l,
.ff-group.ff-diag.is-focus .ff-peek--l,
.ff-group.ff-diag:focus-visible .ff-peek--l {
  transform: translate(-104%, 6%) rotate(-7deg) scale(0.82);
}
.ff-group.ff-diag:hover .ff-peek--r,
.ff-group.ff-diag.is-focus .ff-peek--r,
.ff-group.ff-diag:focus-visible .ff-peek--r {
  /* against the throw, not with it: rotating the top board the way it
     travels tips its far end up and away from the one below, and the two
     stop reading as one flight */
  transform: translate(4%, -116%) rotate(-5deg) scale(0.82);
}

@media (prefers-reduced-motion: reduce) {
  .ff-peek { transition: opacity 0.2s ease; }
}

/* note placement: beside the product image (flipped for the last few),
   bottom-center for category tiles */
.ff-item .ff-note {
  top: 8%;
  left: calc(100% - 10px);
  right: auto;
  transform-origin: 0 0;
}
.ff-item.note-flip .ff-note {
  left: auto;
  right: calc(100% - 10px);
  transform-origin: 100% 0;
}
/* category captions: always on display at the bottom (name + count);
   on hover they travel to the tile's side — same move as products —
   and unfold the list of what's inside */
.ff-group .ff-note {
  top: calc(100% - 6px);
  bottom: auto;
  left: 50%;
  right: auto;
  opacity: 1;
  transform-origin: 50% 0;
  transform: translate3d(-50%, 0, 0) rotate(var(--rot, -3deg)) scale(1);
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
              top 0.32s cubic-bezier(0.22, 1, 0.36, 1),
              left 0.32s cubic-bezier(0.22, 1, 0.36, 1),
              right 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
.ff-group .ff-note path:nth-child(1) { opacity: 1; }
/* flipped tiles anchor the RESTING caption by its right edge (same
   centred look), so the hover travel animates right→right — a jump
   from left-anchored to right-anchored can't interpolate and snaps */
.ff-group.note-flip .ff-note {
  left: auto;
  right: 50%;
  transform: translate3d(50%, 0, 0) rotate(var(--rot, -3deg)) scale(1);
  transform-origin: 100% 0;
}
.ff-group:hover .ff-note,
.ff-group.is-focus .ff-note,
.ff-group:focus-visible .ff-note {
  top: 0;
  /* clear of the peek fanning out from behind the tile — by exactly its
     reach, which menu.js measures per tile into --noff. As a share of the
     tile this was 300px too generous on the widest and still landed on
     the peek of the narrowest, because the reach is the peek's and not
     the tile's. */
  left: calc(100% + var(--noff, 40px));
  right: auto;
  transform: translate3d(calc(var(--mx, 0) * 14px), calc(var(--my, 0) * 10px), 0)
    rotate(var(--rot, -3deg)) scale(1);
}
/* the last column travels leftward so the caption stays on stage */
.ff-group.note-flip:hover .ff-note,
.ff-group.note-flip.is-focus .ff-note,
.ff-group.note-flip:focus-visible .ff-note {
  left: auto;
  right: calc(100% + var(--noff, 40px));
}
.ff-group .ff-ndesc {
  -webkit-line-clamp: 6;
  max-height: 0;
  opacity: 0;
  transition: max-height 0.32s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.25s ease;
}
.ff-group:hover .ff-ndesc,
.ff-group.is-focus .ff-ndesc,
.ff-group:focus-visible .ff-ndesc {
  max-height: 9em;
  opacity: 1;
}

/* products: caption always visible at the bottom (name + price); on
   hover it travels to its side spot and grows to fit the description */
.ff-item .ff-note {
  top: calc(100% - 8px);
  left: 50%;
  right: auto;
  opacity: 1;
  transform: translate3d(-50%, 0, 0) rotate(var(--rot, -3deg)) scale(1);
  transform-origin: 50% 0;
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
              top 0.32s cubic-bezier(0.22, 1, 0.36, 1),
              left 0.32s cubic-bezier(0.22, 1, 0.36, 1),
              right 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
.ff-item .ff-note path:nth-child(1) { opacity: 1; }
.ff-item.note-flip .ff-note {
  left: auto;
  right: 50%;
  transform: translate3d(50%, 0, 0) rotate(var(--rot, -3deg)) scale(1);
}
/* (the old :first/:last-child edge pins are gone — under the column
   layout they hit every column's top/bottom tile and forced the note
   between left- and right-anchoring, which can't interpolate and made
   those hover travels snap; the field's side padding absorbs a centred
   caption's overhang at the edges) */
.ff-item:hover .ff-note,
.ff-item.is-focus .ff-note,
.ff-item:focus-visible .ff-note {
  top: 8%;
  left: calc(100% - 10px);
  right: auto;
  transform: translate3d(calc(var(--mx, 0) * 14px), calc(var(--my, 0) * 10px), 0)
    rotate(var(--rot, -3deg)) scale(1);
}
.ff-item.note-flip:hover .ff-note,
.ff-item.note-flip.is-focus .ff-note,
.ff-item.note-flip:focus-visible .ff-note {
  left: auto;
  right: calc(100% - 10px);
}
.ff-item .ff-ndesc {
  max-height: 0;
  opacity: 0;
  transition: max-height 0.32s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.25s ease;
}
.ff-item:hover .ff-ndesc,
.ff-item.is-focus .ff-ndesc,
.ff-item:focus-visible .ff-ndesc {
  max-height: 7.5em;
  opacity: 1;
}

/* roomier paper so text never rides the wobbly edge */
.ff-note { padding: 13px 16px 14px; }

/* ============================================================
   Mobile: the menu flows top-to-bottom — the field wraps, the
   stage grows with it, and the page scrolls naturally through it
   (no pan, no glide steps; menu.js clears the runway sizing)
   ============================================================ */
@media (max-width: 760px) {
  .cycler-stage {
    position: relative;
    top: auto;
    height: auto;
    /* A board is never shorter than the screen it has. Bagels is one
       product; at 42svh that category came in as a stub with the next
       section already showing under it, and the stop it is supposed to
       land on had almost no room to hold you. The strip comes off as
       well as the header — it is pinned above the board the whole time,
       so this is the space a board actually gets, the same figure the
       wide layout gives .cycler-stage as a fixed height. */
    min-height: calc(100svh - var(--header-h) - var(--mh-strip));
    /* The ghost word behind the board is 26vw and set nowrap, so on a
       phone it is several screens wide by design — it is meant to bleed.
       It was bleeding into the document though: the page measured 428px
       against a 390px screen sitting still at the top, and 639px with a
       category open, which is a sideways scroll on every page of the
       site. Clipped, not hidden: the word is sticky, and `hidden` would
       make this box its scroll container and strand it. `clip` does not
       create one, so the word still holds its place under the strip. */
    overflow-x: clip;
    overflow-y: visible;
  }
  .ff-scroll { position: relative; inset: auto; overflow: visible; }
  .ff-field,
  .ff-field.ff-scatter {
    flex-wrap: wrap;
    justify-content: center;
    align-items: flex-end;
    row-gap: 84px;
    column-gap: 14px;
    width: 100%;
    height: auto;
    margin: 0;
    padding: 34px calc(var(--gutter) * 0.7) 100px;
    transform: none !important;
  }
  /* The back pill reserves its own band in the flow above the field, so
     the field wants no extra padding for it. It needed 96px here only
     while the landing was 94px too deep and the pill was riding sticky
     over a board that had slid up under the strip. */
  /* two-column slots: images keep their seeded size variety inside a
     fixed slot, so bottom captions get room and never collide */
  .ff-group, .ff-item {
    max-height: none;
    min-width: 40vw;
    max-width: calc(100vw - var(--gutter) * 1.4);
    justify-content: center;
  }
  /* the aligned board — food, where every category stands alone — carries a
     wide column gap for the desktop row it was built for. Here that gap is
     the difference between two categories to a row and one. */
  .ff-field.ff-aligned {
    align-items: flex-end;
    column-gap: 14px;
  }
  /* and nothing may take so much of the row that its neighbour is pushed
     off it, whatever shape the picture happens to be. The field is inset
     and padded, so a row is about 347px of a 390px screen and two tiles
     plus their gap have to live inside that, not inside the viewport. */
  .ff-cats .ff-group { max-width: 41vw; }
  /* wide cutouts (the croissant boards) hit the width cap while their
     solver height stands — contain keeps the photo's shape instead of
     squishing it into the slot */
  .ff-cats .ff-group .ff-img { max-width: 100%; object-fit: contain; }

  .ff-note { max-width: min(160px, 44vw); }
  /* the caption NEVER travels sideways here — it keeps its bottom-centre
     spot and unfolds the description there. :hover is included because
     mobile Chrome applies it on tap and it would drag the note off the
     side of the screen */
  .ff-item .ff-note,
  .ff-item:hover .ff-note,
  .ff-item.is-focus .ff-note,
  .ff-item:focus-visible .ff-note,
  .ff-item.note-flip .ff-note,
  .ff-item.note-flip:hover .ff-note,
  .ff-item.note-flip.is-focus .ff-note,
  .ff-item.note-flip:focus-visible .ff-note {
    top: calc(100% - 8px);
    left: 50%;
    right: auto;
    transform: translate3d(-50%, 0, 0) rotate(var(--rot, -3deg)) scale(1);
  }
  .ff-group .ff-note,
  .ff-group:hover .ff-note,
  .ff-group.is-focus .ff-note,
  .ff-group:focus-visible .ff-note,
  .ff-group.note-flip .ff-note,
  .ff-group.note-flip:hover .ff-note,
  .ff-group.note-flip.is-focus .ff-note,
  .ff-group.note-flip:focus-visible .ff-note {
    top: calc(100% - 6px);
    left: 50%;
    right: auto;
    transform: translate3d(-50%, 0, 0) rotate(var(--rot, -3deg)) scale(1);
  }
  /* the unfolding product list is a desktop-hover treat — the phone
     opens the overlay instead */
  .ff-group .ff-ndesc { display: none; }
  /* a tapped product must not perform. Mobile Chrome leaves :hover on
     whatever was tapped, and the caption answered — boiling outline,
     description unfolding — which read as a selection state the tile
     does not have. The caption stays exactly as it rests; the product
     card is where the description lives now. (nth-child(n) only to
     out-specify the boil rules above at equal weight.) */
  .ff-item:hover .ff-note path:nth-child(n),
  .ff-item.is-focus .ff-note path:nth-child(n),
  .ff-item:focus-visible .ff-note path:nth-child(n) { animation: none; }
  .ff-item .ff-ndesc,
  .ff-item:hover .ff-ndesc,
  .ff-item.is-focus .ff-ndesc,
  .ff-item:focus-visible .ff-ndesc { display: none; }
  /* the back pill rides along the products flow instead of
     scrolling away with the stage top — below the following strip.
     It takes its own row here rather than the -47px the wide layout
     pulls back: a phone board starts at the left edge the pill is on,
     so the overlap landed the pill across the first product. It also
     darkens, because sticky means it spends the rest of the category
     passing over pictures. */
  .ff-back {
    position: sticky;
    top: calc(var(--header-h) + var(--mh-strip) + 12px);
    z-index: 10;
    display: inline-block;
    width: max-content;
    margin: 14px 0 8px var(--gutter);
    background: rgba(10, 10, 10, 0.78);
  }
  .ff-back[hidden] { display: none; }
}

/* the stage grows instead of pinning when the field needs more than one
   screen — same top-to-bottom flow the phone uses */
.cycler-stage.is-tall {
  position: relative;
  top: auto;
  height: auto;
  min-height: 60svh;
  overflow: visible;
}
.cycler-stage.is-tall .ff-scroll {
  position: relative;
  inset: auto;
  overflow: visible;
}
.cycler-stage.is-tall .cycler-word { align-items: flex-start; }
.cycler-stage.is-tall .cycler-word span {
  position: sticky;
  top: calc(var(--header-h) + var(--mh-strip) +
    (100svh - var(--header-h) - var(--mh-strip)) / 2 - 0.5em);
}
.cycler-stage.is-tall .ff-back {
  position: sticky;
  top: calc(var(--header-h) + var(--mh-strip) + 14px);
  z-index: 10;
  display: inline-block;
  width: max-content;
  margin: 6px 0 -46px var(--gutter);
}
.cycler-stage.is-tall .ff-back[hidden] { display: none; }

/* ============================================================
   Held category — while one is open the PAGE cannot scroll at
   all; the board scrolls inside the one-screen stage instead.
   `scrollbar-gutter: stable` keeps the classic scrollbar's lane
   reserved, so nothing shifts when the bar disappears.
   ============================================================ */
html.cat-hold {
  overflow: hidden;
  scrollbar-gutter: stable;
  /* Held, the board is the whole screen, so the only paper that can show
     is a strip the board failed to cover — and it should read as board.
     The ground goes to ink for exactly as long as the hold lasts. */
  background: var(--ink);
}
/* out-specifies both the mobile flow and .is-tall (menu.js drops
   is-tall while held anyway): back to a pinned one-screen stage */
.cycler-stage.is-hold {
  position: sticky;
  top: calc(var(--header-h) + var(--mh-strip, 92px));
  /* dvh here, where the stage above deliberately takes svh. The reason
     svh is right there is that the board is scrolled through and a
     retracting toolbar would re-measure it under the finger. Held, the
     page is overflow: hidden — nothing scrolls, so the toolbar cannot
     move and dvh cannot shift. What it does instead is match the screen
     as it actually is at that moment: enter a category with the bar
     already retracted and an svh board is a toolbar shorter than the
     viewport, leaving the strip of page the visitor sees at the bottom.
     Frozen, there is no scroll to close that strip either. */
  height: calc(100dvh - var(--header-h) - var(--mh-strip, 92px));
  min-height: 0;
  overflow: hidden;
}
/* block, not flex: a stretched flex child would be pinned to the
   scroller's own height and the overflow would have nothing to scroll */
.cycler-stage.is-hold .ff-scroll {
  position: absolute;
  inset: 0;
  display: block;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
/* short boards still centre; tall ones grow past 100% and scroll */
.cycler-stage.is-hold .ff-field {
  min-height: 100%;
  height: auto;
  transform: none !important;
}
.cycler-stage.is-hold .cycler-word { align-items: center; }
.cycler-stage.is-hold .cycler-word span { position: static; }
.cycler-stage.is-hold .ff-back {
  position: absolute;
  top: 16px;
  left: var(--gutter);
  z-index: 10;
  display: inline-block;
  width: max-content;
  margin: 0;
  background: rgba(10, 10, 10, 0.55);
}
/* this block outweighs .ff-back:hover, which would leave ink-on-dark —
   restate the paper fill at the same specificity */
.cycler-stage.is-hold .ff-back:hover,
.cycler-stage.is-hold .ff-back:focus-visible { background: var(--paper); }
.cycler-stage.is-hold .ff-back[hidden] { display: none; }

/* composition columns: each holds one to three tiles stacked, and
   carries its own vertical offset so the set reads as arranged */
/* uniform stacks are narrower than a mixed row, so the columns spread
   themselves across the field rather than huddling in the middle */
.ff-field:not(.ff-aligned) { justify-content: space-evenly; }
.ff-col {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* room for the caption that hangs off each tile — scales with the
     screen so a short laptop isn't spending 40% of its height on gaps */
  gap: clamp(54px, 7.6vh, 104px);
}
.ff-field.ff-aligned .ff-col { margin-top: 0 !important; }
/* a wide leaf board breathes — more air between and inside its columns */
.ff-field.ff-spread {
  column-gap: clamp(40px, 4.5vw, 124px);
}
.ff-field.ff-spread .ff-col {
  gap: clamp(64px, 9.4vh, 130px);
}
/* one symmetric band of an open category — a full-width row of columns;
   the field's row-gap holds the bands apart and the held board scrolls
   through them */
.ff-band {
  flex-basis: 100%;
  display: flex;
  justify-content: space-evenly;
  align-items: flex-start;
  column-gap: clamp(26px, 3vw, 88px);
}
/* the "+" categories sit together on their own row at the foot of the
   board — a full-basis flex child so the wrapping field drops it under
   the columns, laid out sideways */
.ff-col.ff-botrow {
  flex-basis: 100%;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  gap: clamp(50px, 5.5vw, 118px);
}

@media (max-width: 760px) {
  /* the phone keeps its own two-up flow — columns and bands dissolve
     into it */
  .ff-col,
  .ff-band {
    display: contents;
  }
}

/* entrance: a ripple. The wave starts under the click and expands at a
   constant speed — each tile's delay is set by JS from its distance to
   the origin, so tiles on the same radius rise together — and every
   tile rises in place, drawn a step in from the origin's direction and
   settling out as it lands. No rotation — a tumbling caption reads as
   broken, not playful.

   The rise deliberately avoids `transform`, which is hover's, and takes
   the independent `translate` and `scale` instead. They multiply in that
   order — translate, then scale, then transform — so this is the same
   matrix the one-property version built, except hover can now write its
   own half of it. Sharing the property meant the animation simply won:
   reaching a tile mid-flight, its 0.35s transition ran invisibly
   underneath, finished, and the tile snapped to full hover size the
   instant the flight ended and let go. */
@keyframes ff-ripple {
  0% {
    translate: var(--dx, 0px) calc(var(--gy, 0px) + var(--dy, 0px));
    scale: 0.4;
  }
  100% { translate: 0 var(--gy, 0px); scale: 1; }
}
/* Opacity is deliberately NOT animated here: a keyframe fade must end
   at 1 and releases onto whatever the board wants with a snap — into a
   dimmed board, every landing tile flashed bright. Arrival is the
   .ff-wait veil lifting per tile instead (JS times it to the wave), so
   opacity is a transition to its live target and the hover dim simply
   works mid-ripple. */
.ff-pop {
  /* both clocks come from the board: menu.js scales them by how many
     products are arriving — 1x for a small board up to 2x for a
     crowded one */
  animation: ff-ripple var(--ripple-t, 0.62s) cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: var(--d, 0ms);
  /* the unveil fade keeps its share of the rise; the class drops on
     landing, handing opacity back to the 0.3s the hover dim eases on */
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), opacity var(--ripple-fade, 0.3s) ease;
}
/* The caption rides along on its tile — it is a child, so it
   needs no transition of its own to follow the flight, and killing the
   transition here is what made a hover mid-flight snap the caption to its
   open position instead of travelling there. Only the boil is held back:
   that is the caption's own idle, and it has no business starting before
   the tile has landed. */
.ff-pop .ff-note path { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .ff-pop { animation: none; }
}

/* born hidden and held hidden until this tile's wave moment — the
   important keeps the veil above the hover-dim selectors, whose
   specificity would otherwise out-rank a lone class */
.ff-wait { opacity: 0 !important; }

/* ============================================================
   Product view — a tapped product pops out of its tile onto a
   big boiling-paper card in the middle of the screen: photo,
   name, price, description, and a paper badge with a hand-drawn
   sketch of what it is served in, drawing itself on.
   ============================================================ */
.pv {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
}
/* `hidden` loses to authored display — spell it out */
.pv[hidden] { display: none; }
.pv-veil {
  position: absolute;
  inset: 0;
  background: radial-gradient(90% 90% at 50% 44%,
    rgba(10, 10, 10, 0.55), rgba(4, 4, 4, 0.86));
  opacity: 0;
  transition: opacity 0.3s ease;
}
.pv.in .pv-veil { opacity: 1; }

.pv-card {
  position: relative;
  width: min(390px, calc(100vw - 44px));
  /* the cutout hangs out of the paper's top — the margin below pulls
     the card down so the pair still reads centred */
  margin-top: 46px;
  padding: 24px 26px 22px;
  color: var(--ink);
  opacity: 0;
  transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.32s ease;
  will-change: transform;
}
.pv.in .pv-card { transform: rotate(-1.2deg); opacity: 1; }
.pv-card > .boil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.pv-card > .boil path {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.4px;
  stroke-linejoin: round;
  opacity: 0;
}
.pv-card > .boil path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.pv-card > .boil path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.pv-card > .boil path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }

.pv-x {
  position: absolute;
  top: -15px;
  right: -13px;
  z-index: 4;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  border: 1.5px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  font-size: 15px;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.pv-x:hover, .pv-x:focus-visible { background: var(--ink); color: var(--paper); }

.pv-media {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: center;
  margin: -92px 0 8px;
  pointer-events: none;
}
.pv-img {
  height: clamp(150px, 26vh, 230px);
  width: auto;
  max-width: 88%;
  object-fit: contain;
  filter: drop-shadow(0 18px 26px rgba(0, 0, 0, 0.45));
}
/* the owed-photo placeholder is white line art for the dark stage —
   inverted, it reads as an ink sketch on the paper instead of vanishing */
.pv-img.is-ph { filter: invert(1) drop-shadow(0 10px 14px rgba(0, 0, 0, 0.18)); }
.pv-img.is-card {
  height: clamp(140px, 24vh, 210px);
  border-radius: 12px;
  object-fit: cover;
  filter: none;
  box-shadow: 0 16px 30px rgba(0, 0, 0, 0.45);
  rotate: -2deg;
}

/* the badge: a smaller paper scrap pinned over the card's corner,
   carrying the sketch */
.pv-badge {
  position: absolute;
  top: -30px;
  left: -18px;
  z-index: 3;
  width: 96px;
  height: 96px;
  rotate: -8deg;
  pointer-events: none;
}
.pv-badge .boil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.pv-badge .boil path {
  fill: var(--paper);
  stroke: var(--ink);
  stroke-width: 1.4px;
  stroke-linejoin: round;
  opacity: 0;
}
.pv-badge .boil path:nth-child(1) { animation: boil-a 0.42s steps(1) infinite; }
.pv-badge .boil path:nth-child(2) { animation: boil-b 0.42s steps(1) infinite; }
.pv-badge .boil path:nth-child(3) { animation: boil-c 0.42s steps(1) infinite; }
.pv-badge .pv-icon {
  position: absolute;
  inset: 6px;
  width: calc(100% - 12px);
  height: calc(100% - 12px);
}

/* the sketch: every line starts undrawn (dash the length of the path,
   pushed fully off) and draws on in tile order once the card lands */
.pv-icon path {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.6px;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1 1;
  stroke-dashoffset: 1;
}
.pv.in .pv-icon path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 0.7s ease var(--d, 0.1s);
}
/* steam and waves never finish — a short dash marches the path forever */
.pv-icon .pv-loop {
  stroke-dasharray: 0.3 0.22;
  stroke-dashoffset: 0;
  opacity: 0;
}
.pv.in .pv-icon .pv-loop {
  opacity: 0.8;
  transition: opacity 0.5s ease calc(var(--d, 0s) + 0.7s);
  animation: pv-run 2.8s linear infinite;
}
/* bubbles and cocoa dots fade in late and bob */
.pv-icon .pv-bub {
  stroke-dasharray: none;
  stroke-dashoffset: 0;
  opacity: 0;
}
.pv.in .pv-icon .pv-bub {
  opacity: 0.85;
  transition: opacity 0.45s ease calc(var(--d, 0s) + 0.6s);
  animation: pv-bob 2.6s ease-in-out var(--d, 0s) infinite;
}
@keyframes pv-run { to { stroke-dashoffset: -1.04; } }
@keyframes pv-bob {
  0%, 100% { translate: 0 0; }
  50% { translate: 0 -4px; }
}

.pv-info {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 7px;
  padding: 2px 4px 0;
}
.pv-name {
  margin: 0;
  font-weight: 800;
  font-size: 19px;
  line-height: 1.25;
  letter-spacing: 0.01em;
}
.pv-meta {
  margin: 0;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.75;
}
.pv-desc {
  margin: 2px 0 0;
  font-size: 13.5px;
  line-height: 1.55;
  opacity: 0.85;
}
/* order/visit pills under the description — ink on the paper card,
   inverting on hover the way the back pill does on the stage */
.pv-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}
.pv-act {
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid rgba(10, 10, 10, 0.55);
  color: var(--ink);
  text-decoration: none;
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  transition: background 0.2s ease, color 0.2s ease;
}
a.pv-act:hover, a.pv-act:focus-visible { background: var(--ink); color: var(--paper); }
a.pv-act:focus-visible { outline-color: var(--ink); }
/* the teahouse aside wears the pill shape but is not a control */
.pv-act.is-off {
  border-style: dashed;
  border-color: rgba(10, 10, 10, 0.35);
  opacity: 0.75;
  cursor: default;
}

@media (max-width: 760px) {
  .pv-card { width: min(390px, calc(100vw - 30px)); padding: 22px 20px 20px; }
  .pv-badge { width: 82px; height: 82px; top: -26px; left: -10px; }
  .pv-img { height: clamp(130px, 24vh, 200px); }
}

@media (prefers-reduced-motion: reduce) {
  .pv-veil, .pv-card, .pv-x { transition: none; }
  .pv.in .pv-card { transform: none; }
  .pv-icon path,
  .pv-icon .pv-loop,
  .pv-icon .pv-bub {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    opacity: 1;
    transition: none;
    animation: none;
  }
  .pv-icon .pv-loop { stroke-dasharray: 0.3 0.22; }
}
