/* ============================================================
   HUCKLEBERRY × Normal-is-Boring system — warm-adapted layer
   Sits ON TOP of normal-is-boring.css (load that first).
   - Loads the free fallback fonts the base file names.
   - Overrides the cool teal/blue accent -> Huckleberry honey.
   - Adds the page-composition components (nav, hero, stacks,
     index, dark panel, marquee, closer, footer).
   ============================================================ */

/* Font loading now happens via the <link> tag in each page's <head>
   (see docs/2026-08-02-nib-site-build-design.md §5) — the @import that
   used to live here duplicated that request with a different axis set. */

/* ---- Warm-adapt: keep the system, swap the temperature ----
   Converged brand system (wiki/brand-guide.md, 2026-08-06). All brand
   values are declared here and the base system's semantic aliases are
   repointed at them, so normal-is-boring.css stays vendored/upgradeable. */
:root{
  /* Brand palette */
  --hb-cream:#FBF6EC;          /* Cream / Paper — page canvas        */
  --hb-honey:#B0730A;          /* fine accents, links, signature dot */
  --hb-gold:#FFC94B;           /* highlight FILLS & CTAs only        */
  --hb-sage:#A3B18A;           /* secondary accent — rules, tags     */
  --hb-band:#EFECDE;           /* sage wash on cream — section band  */
  --hb-ink:#1F1F1F;            /* Ink / Espresso                     */

  --nib-teal:var(--hb-honey);  /* signature dot -> honey             */
  --nib-blue:#8A4B12;          /* strong accent -> roast/rust        */
  --nib-coral:#C25A3F;         /* warm brick (kept, retuned)         */
  --accent:var(--nib-teal);
  --accent-strong:var(--nib-blue);

  /* Repoint the base system's semantic aliases at the brand values.
     --nib-white stays true white: it is still needed for text on dark
     panels and the black page frame. */
  --canvas:var(--hb-cream);
  --ink:var(--hb-ink);

  /* wire the fallback families the base file already references */
  --font-display:"Fraunces","Cormorant Garamond",Georgia,serif;
  --font-sans:"Karla","Archivo","Helvetica Neue",Arial,sans-serif;

  /* RETIRED — pending owner review (2026-08-11)
     Was: --font-serif:"Playfair Display","Cormorant Garamond",Georgia,serif;
     Playfair Display retired: the brief calls for one display face. Aliased
     rather than removed so pages not yet converted on this branch fall back
     to Karla instead of to browser default serif. */
  --font-serif: var(--font-sans);

  --fs-body:    17px;
  --fs-body-sm: 16px;
  --lh-body:    1.6;

  /* Accent text. Gold is 1.4:1 on cream and can never carry text; it is a
     fill behind ink only (ink on gold = 10.76:1). Roast is 6.3:1. */
  --hb-roast:   #8A4B12;

  /* Content sits on a left axis rather than the page centre. */
  --hb-axis: clamp(20px, 8vw, 128px);
}
.nib-display{font-optical-sizing:auto}
/* overflow-x MUST be `clip`, never `hidden`.
   `hidden` turns body into a scroll container, which silently kills
   `position:sticky` for every descendant — the hero pin never pins, so the
   whole scroll-scrub animation runs while the hero scrolls away off-screen
   and the visitor sees no effect at all. `clip` gives the same protection
   against sideways scrollbars without establishing a scroll container.
   This was the root cause of "the animation doesn't work". */
body{-webkit-font-smoothing:antialiased;overflow-x:clip}

/* The vendored base paints <html> with canvas-invert ("black frame shows
   during transitions"). On this site that means a full-screen black flash
   on every load before the body paints — clearly visible in the owner's
   screen recording. The page is cream, so html should be cream too. */
html{background:var(--canvas)}

/* Neither stylesheet ever reset link decoration, so every <a> was picking up
   the browser default underline — including the solid CTA buttons and the
   category rows. The design already carries its own affordances (.hb-tlink
   has a border-bottom, .hb-nav-link.here has an ::after rule for the active
   page, which only means anything if links aren't all underlined by default).
   Prose links that set text-decoration inline are unaffected. */
a{text-decoration:none}
.hb-accent{color:var(--accent)}
.hb-serif{font-family:var(--font-serif)}

/* ---- Primary CTA: gold fill ----
   Gold is a FILL colour only — ink on gold measures ~10.6:1 so it is safe
   for buttons, but gold as text on cream would fail contrast badly, so it
   is never used that way. Secondary CTAs keep the outlined .nib-btn. */
.nib-btn--solid{background:var(--hb-gold);color:var(--hb-ink);border-color:var(--hb-gold)}
.nib-btn--solid:hover{background:var(--hb-ink);color:var(--hb-cream);border-color:var(--hb-ink)}

/* progressive-enhancement: content visible without JS */
.nib-reveal{opacity:1;transform:none}
.js .nib-reveal{opacity:0;transform:translateY(24px);
  transition:opacity var(--dur) var(--ease),transform var(--dur) var(--ease)}
.js .nib-reveal.is-visible{opacity:1;transform:none}
.rd1{transition-delay:.08s}.rd2{transition-delay:.16s}.rd3{transition-delay:.24s}.rd4{transition-delay:.32s}

/* ============================================================
   NAV
   ============================================================ */
.hb-nav{position:fixed;inset:0 0 auto 0;z-index:60;display:flex;align-items:center;
  justify-content:space-between;padding:20px var(--gutter);
  transition:background var(--dur-fast) var(--ease),padding var(--dur-fast) var(--ease),
             border-color var(--dur-fast) var(--ease);border-bottom:1px solid transparent}
/* Cream-tinted, not white — a white bar would read as a visible seam
   against the cream canvas once the nav goes sticky. */
.hb-nav.scrolled{background:rgba(251,246,236,.88);backdrop-filter:blur(14px) saturate(1.1);
  padding-top:13px;padding-bottom:13px;border-bottom-color:var(--line)}
.hb-logo{height:44px;width:auto;transition:height var(--dur-fast) var(--ease)}
.hb-nav.scrolled .hb-logo{height:36px}
/* Live open/closed pill — mirrors the Google Business "Open now" cue.
   Status is computed in the shop's timezone (see hb-data.js), so it reads
   correctly for visitors anywhere. */
.hb-open{display:inline-flex;align-items:center;gap:7px;margin-left:14px;flex:none;
  font-family:var(--font-sans);font-size:var(--fs-2xs);font-weight:var(--fw-medium);
  letter-spacing:var(--tracking-wide);text-transform:uppercase;white-space:nowrap;
  padding:5px 11px;border-radius:var(--radius-pill);border:1px solid var(--line)}
/* The nav CTA must never wrap to two lines — it collides with the pill. */
.hb-nav .nib-btn{white-space:nowrap}
.hb-open::before{content:"";width:7px;height:7px;border-radius:var(--radius-pill);flex:none;background:currentColor}
.hb-open.is-open{color:#1f7a3d;border-color:rgba(31,122,61,.32);background:rgba(31,122,61,.07)}
.hb-open.is-closed{color:var(--ink-muted);background:rgba(25,25,25,.04)}
.hb-nav-left{display:flex;align-items:center;min-width:0}
.hb-nav-right{display:flex;align-items:center;gap:clamp(16px,2.6vw,38px)}
.hb-nav-link{font-family:var(--font-sans);font-size:var(--fs-xs);font-weight:500;
  text-transform:uppercase;letter-spacing:var(--tracking-wide);color:var(--ink);
  position:relative;transition:color var(--dur-fast)}
.hb-nav-link:hover{color:var(--accent)}
.hb-nav-link.here::after{content:"";position:absolute;left:0;right:0;bottom:-6px;height:1px;background:var(--accent)}
.hb-nav-toggle{display:none;background:none;border:0;padding:6px;cursor:pointer;
  width:26px;height:20px;position:relative}
.hb-nav-toggle-bars,.hb-nav-toggle-bars::before,.hb-nav-toggle-bars::after{
  content:"";position:absolute;left:6px;right:6px;height:2px;background:var(--ink);
  transition:transform var(--dur-fast) var(--ease),opacity var(--dur-fast) var(--ease),top var(--dur-fast) var(--ease)}
.hb-nav-toggle-bars{top:9px}
.hb-nav-toggle-bars::before{top:-7px}
.hb-nav-toggle-bars::after{top:7px}
.hb-nav-toggle.open .hb-nav-toggle-bars{background:transparent}
.hb-nav-toggle.open .hb-nav-toggle-bars::before{top:0;transform:rotate(45deg)}
.hb-nav-toggle.open .hb-nav-toggle-bars::after{top:0;transform:rotate(-45deg)}
.hb-drawer{position:fixed;inset:0;z-index:55;background:var(--canvas-alt);
  transform:translateY(-100%);transition:transform var(--dur) var(--ease);
  display:flex;flex-direction:column;justify-content:center;gap:6px;padding:var(--gutter)}
.hb-drawer.open{transform:none}
.hb-drawer a{font-family:var(--font-display);text-transform:uppercase;font-weight:var(--fw-light);
  font-size:clamp(40px,11vw,72px);line-height:1;letter-spacing:var(--tracking-tight);
  color:var(--ink);padding:12px 0;border-bottom:1px solid var(--line)}
.hb-drawer a .nib-italic{color:var(--accent)}

/* ============================================================
   RETIRED — pending owner review (2026-08-11)
   Previous hero: three-line display heading plus an overlapping
   two-photo stack driven by nib.js. Superseded by the scroll-scrub
   hero below. Kept until the owner picks between master and
   redesign-v2; delete only after that call.

   .hb-hero itself is not kept here — the new hero reuses that class
   name. Its retired declaration was:
   .hb-hero{padding-top:clamp(120px,20vh,220px);padding-bottom:clamp(40px,7vw,90px)}

   .hb-eyebrow, .hb-actions, and .hb-tlink are NOT retired below —
   they're shared with about.html, contact.html, menu.html, and the
   new hero markup, and stay live.
   ============================================================ */
.retired .hb-hero-top{display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  flex-wrap:wrap;padding-bottom:clamp(24px,5vw,56px)}
.retired .hb-hero-h{margin:0;display:flex;flex-direction:column;gap:.02em}
.retired .hb-hero-h .line{overflow:hidden;display:block}
.retired .hb-hero-h .line>span{display:block;transform:translateY(0)}
.retired .js .hb-hero-h .line>span{transform:translateY(104%);transition:transform var(--dur-slow) var(--ease)}
.retired .js .hb-hero.in .line>span{transform:translateY(0)}
.retired .hb-hero-h .nib-italic{text-transform:none;padding-left:.12em}
.retired .hb-lastword{display:inline-flex;align-items:flex-start;gap:.12em}
.retired .hb-lastword .dot{width:.16em;height:.16em;min-width:10px;min-height:10px;border-radius:999px;
  background:var(--accent);margin-top:.62em;flex:none}
.retired .hb-hero-foot{display:grid;grid-template-columns:1.15fr .85fr;gap:clamp(24px,5vw,64px);
  align-items:end;padding-top:clamp(30px,5vw,60px)}

/* ============================================================
   RETIRED — OVERLAPPING IMAGE STACK  (core NIB move)
   ============================================================ */
.retired .hb-stack{position:relative;display:grid;grid-template-columns:repeat(12,1fr);align-items:end;
  margin-top:clamp(30px,5vw,70px)}
.retired .hb-stack .big{grid-column:1/9;grid-row:1;aspect-ratio:4/3}
.retired .hb-stack .small{grid-column:8/13;grid-row:1;align-self:end;transform:translateY(9%);
  z-index:2;aspect-ratio:3/4;border:8px solid var(--canvas)}
.retired .hb-stack .cap{grid-column:1/6;grid-row:1;align-self:start;z-index:3;margin:14px 0 0 14px}
.retired .hb-stack img{width:100%;height:100%;object-fit:cover;display:block;transition:opacity .3s var(--ease)}
.retired .hb-stack .hb-figcap{transition:opacity .3s var(--ease)}
.retired .hb-figcap--small{position:absolute;left:14px;bottom:12px}

/* Editorial "contact sheet" ticker — the non-typical stand-in for
   slideshow dots/arrows. Text-only, numbered, matches the site's
   existing numbered-index motif (.hb-chap-n / .hb-ixrow .n).
   Sits bottom-LEFT: the .small figure overhangs bottom-right with
   z-index 2, so a right-aligned ticker renders behind it and is
   invisible at desktop widths. */
.retired .hb-stack-n{position:absolute;left:4px;bottom:-30px;z-index:3;font-family:var(--font-sans);
  font-size:var(--fs-xs);letter-spacing:.06em;color:var(--ink-muted)}
@media(max-width:860px){.retired .hb-stack-n{position:static;text-align:left;margin-top:14px}}

/* Phone-only directions button. Hidden on desktop, where the same link is
   already in the closer section and there is no room pressure. 44px is the
   minimum comfortable touch target. */
.hb-nav-icon{display:none;width:44px;height:44px;flex:none;
  align-items:center;justify-content:center;
  border:1px solid var(--line);border-radius:var(--radius-pill);
  color:var(--ink);background:none;
  transition:border-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
.hb-nav-icon svg{width:19px;height:19px;fill:currentColor;display:block}
.hb-nav-icon:hover,.hb-nav-icon:focus-visible{border-color:var(--accent-strong);color:var(--accent-strong)}

.hb-eyebrow{display:inline-flex;align-items:center;gap:12px}

/* .nib-label has no flex and no gap, and .nib-dot is a bare inline-block, so
   a dot placed inside a label sits flush against the first letter ("•Come
   say hi"). .hb-eyebrow happened to avoid it with its own inline-flex+gap;
   every other label carrying a dot did not. Fixed here, not in the vendored
   base. Margin rather than :has()+gap so it cannot double up. */
.nib-label > .nib-dot{margin-right:12px;vertical-align:middle;position:relative;top:-1px}
.hb-actions{display:flex;gap:20px;align-items:center;flex-wrap:wrap}
.hb-tlink{font-family:var(--font-sans);font-size:var(--fs-sm);letter-spacing:.03em;color:var(--ink);
  display:inline-flex;align-items:baseline;gap:.5em;padding-bottom:3px;
  border-bottom:1px solid rgba(25,25,25,.3);
  transition:color var(--dur-fast) var(--ease),border-color var(--dur-fast),gap var(--dur-fast) var(--ease)}
.hb-tlink .arw{color:var(--ink-muted);transition:transform var(--dur-fast) var(--ease),color var(--dur-fast)}
/* Roast, not honey. --accent is #B0730A = 3.67:1 on cream, which fails AA
   for 14px link text; --accent-strong is 6.3:1. Honey remains correct on
   display-scale type, where the 3:1 large-text threshold applies. */
.hb-tlink:hover,.hb-tlink:focus-visible{color:var(--accent-strong);border-color:var(--accent-strong);gap:.85em}
.hb-tlink:hover .arw,.hb-tlink:focus-visible .arw{transform:translateX(3px);color:var(--accent-strong)}

/* ============================================================
   HERO — static composition (default)
   This is what renders with no JS, with reduced motion, and on
   mobile. hb-hero.js upgrades it to a scrub; nothing here depends
   on JS running. Failure mode is "no animation", never "no content".
   ============================================================ */
.hb-hero{position:relative;padding-top:clamp(104px,14vh,152px);padding-bottom:clamp(40px,7vw,88px)}
.hb-hero__pin{padding-inline:var(--hb-axis)}

.hb-hero__head{display:flex;justify-content:space-between;align-items:baseline;
  gap:16px;flex-wrap:wrap;margin-bottom:clamp(16px,3vw,32px)}

/* The giant mark is the real logo lettering (2019x733 crop of the stacked
   lockup), NOT type. It must be the same letterforms as the header logo, or
   the shrink-into-header reads as serif capitals vanishing and a script logo
   appearing — which is exactly what it did when this was set in Fraunces. */
.hb-hero__word{margin:0;transform-origin:left top;line-height:0}
.hb-hero__word picture{display:block;line-height:0}
.hb-hero__word img{display:block;width:min(92vw,1180px);height:auto}

/* Phones get the full stacked lockup — mark above the script — not the mark
   alone. It reads as the complete brand and carries far more weight in the
   opening frame, which is what the empty space around it needed. Sized by
   WIDTH: the lockup is 1.21 (wider than tall), so sizing by height would
   push it past the screen edges. Matches the <source> in the markup. */
@media (max-width:700px){
  .hb-hero__word img{width:min(88vw,460px);height:auto;max-height:54svh;
    object-fit:contain}
}

/* "Bakes that linger" lives INSIDE the hero and is revealed as the wordmark
   shrinks away, so it arrives together with the lineup instead of sitting in
   a section of its own. Visible by default — only the scrub hides it. */
.hb-hero__linger{font-family:var(--font-display);font-weight:var(--fw-light);
  font-size:clamp(34px,6vw,96px);line-height:1.04;
  letter-spacing:var(--tracking-tight);color:var(--ink);
  margin:clamp(20px,3vw,32px) 0 0;max-width:14ch}

/* The spans inside carry .nib-display / .nib-italic, which set their own
   font-size (--fs-display-lg, up to 142px). That silently beat every size
   set on the heading above — so the heading rendered at full display scale
   regardless, pushing the drinks and the buttons out of the pinned area
   entirely. Inherit so the heading's own sizing actually governs. */
.hb-hero__linger .nib-display,
.hb-hero__linger .nib-italic{font-size:inherit;line-height:inherit;
  letter-spacing:inherit}
.hb-hero__linger .nib-italic{text-transform:none;padding-left:.1em;
  color:var(--accent-strong)}
.hb-hero__linger .dot{display:inline-block;width:.11em;height:.11em;
  border-radius:50%;background:var(--accent-strong);margin-left:.07em}

/* Restored at the owner's request: the descriptive paragraph sits under
   "Bakes that linger" in the hero. It briefly lived in a separate section
   and was briefly removed altogether; this is its home. */
.hb-hero__sub{max-width:52ch;margin:clamp(16px,2.4vw,28px) 0 0;
  font-size:var(--fs-body);line-height:var(--lh-body)}

/* RETIRED — pending owner review (2026-08-11)
   "Bakes that linger" briefly had its own section after the hero. The owner
   asked for it to appear INSIDE the hero alongside the drinks as the opening
   animation resolves instead, so it is now .hb-hero__linger above. Prefixed
   so it cannot match; kept rather than deleted per the no-delete rule. */
.retired .hb-statement-h{font-family:var(--font-display);font-weight:var(--fw-light);
  font-size:var(--fs-display-lg);line-height:var(--lh-tight);
  letter-spacing:var(--tracking-tight);margin:0;
  display:flex;flex-direction:column;gap:.02em}
.retired .hb-statement-h .line{display:block;overflow:hidden}
.retired .hb-statement-h .nib-italic{text-transform:none;padding-left:.12em}
.retired .hb-statement-sub{max-width:52ch;margin:clamp(24px,4vw,40px) 0 0;
  font-size:var(--fs-body);line-height:var(--lh-body)}

.hb-hero__line{display:flex;gap:24px;margin-top:clamp(32px,5vw,56px);
  list-style:none;padding:0;--hb-card-h:clamp(240px,42vh,420px)}

/* The image lives in its own clipping frame so the caption can sit BELOW it
   without being cut off. Putting height + overflow:hidden on the <figure>
   itself clips the caption to nothing — the image consumes 100% of the
   figure's height, so a flow-positioned caption starts exactly at the
   clipped edge. The frame also keeps Task 5's drift (scale 1.06) clipped;
   without it the image would visibly overflow while drifting.

   .hb-card had no explicit width, so it shrink-wrapped to max-content —
   below ~710px of viewport height the figcaption (uppercase, tracked-out)
   is wider than the 9:16 image, so the card and its frame stopped agreeing
   on width and the five cards rendered five different widths. Deriving
   both the frame height and the card width from one token keeps them
   locked together at every viewport size. */
.hb-card{margin:0;flex:none;width:calc(var(--hb-card-h) * 9 / 16)}
.hb-card__frame{position:relative;overflow:hidden;
  height:var(--hb-card-h);aspect-ratio:9/16}
.hb-card img{width:100%;height:100%;object-fit:cover;display:block}
.hb-card figcaption{font-family:var(--font-sans);font-size:var(--fs-2xs);
  letter-spacing:var(--tracking-wide);text-transform:uppercase;
  color:var(--ink-muted);margin-top:8px}

/* Marks a hero card as one of this month's specials. Ink on cream text,
   NOT gold: gold is reserved for things you can click, and a badge is not
   clickable. Ink also reads harder against a photo than gold would.
   Sits inside .hb-card__frame, which is already position:relative and
   overflow:hidden, so it rides along with the drift and the entry
   transform rather than floating free. */
.hb-card-badge{position:absolute;top:10px;left:10px;z-index:2;
  padding:5px 9px;border-radius:var(--radius-pill);
  background:var(--ink);color:var(--canvas);
  font-family:var(--font-sans);font-size:var(--fs-2xs);font-weight:var(--fw-medium);
  letter-spacing:var(--tracking-wide);text-transform:uppercase;line-height:1;
  pointer-events:none}

/* Phone-only; the media query below turns it on. */
.hb-line-count{display:none;font-family:var(--font-sans);font-size:var(--fs-2xs);
  letter-spacing:var(--tracking-wide);color:var(--ink-muted);margin-top:12px}

.hb-hero__actions{display:flex;align-items:center;gap:24px;flex-wrap:wrap;
  margin-top:clamp(28px,4vw,44px)}

/* Cards overflow the right edge on purpose — the lineup runs off the page
   rather than sitting centred in it. Horizontal scroll is the fallback on
   any viewport too narrow to show all five. */
@media (max-width:1100px){
  /* Scrollbar hidden: on a phone it sits as a grey slab under the drinks and
     reads as a rendering fault. The half-visible card at the right edge is
     what signals the strip scrolls, so nothing is lost by removing it. */
  .hb-hero__line{overflow-x:auto;scroll-snap-type:x mandatory;
    scrollbar-width:none;-ms-overflow-style:none;
    -webkit-overflow-scrolling:touch}
  .hb-hero__line::-webkit-scrollbar{display:none}
  .hb-card{scroll-snap-align:start}
}

/* ============================================================
   HERO — scrub upgrade
   Only applies when hb-hero.js has added .hb-hero--scrub, which it does
   only on wide viewports without reduced-motion. Every property here is
   transform or opacity, so it stays on the compositor.
   ============================================================ */
/* 200vh against a 100vh pin gives a full viewport of scrub. The earlier
   160vh (60vh of throw) resolved so fast it did not read as an animation
   at all — the owner reported seeing no effect. */
/* svh, not vh. On phones vh includes the space the URL bar occupies, so the
   pin jumps every time that bar shows or hides. svh is the stable "small"
   viewport height, which is what lets the scrub run on mobile at all. */
.hb-hero--scrub{height:200svh;padding-top:0;padding-bottom:0;--p:0}
/* `safe center` matters here. Plain `center` overflows equally off BOTH
   ends when the content is taller than the pin, so the top of the
   composition disappears behind the fixed header with no way to reach it.
   `safe` falls back to flex-start in exactly that case, so content is
   clipped only at the bottom, never hidden above the viewport. */
.hb-hero--scrub .hb-hero__pin{position:sticky;top:0;height:100svh;
  display:flex;flex-direction:column;justify-content:safe center;align-items:center;
  overflow:hidden;padding-top:clamp(88px,12svh,132px)}

/* Phones: the lineup stays a horizontal strip and everything shrinks to fit
   one small screen, but the pin and the wordmark hand-off still run. */
@media (max-width:1100px){
  /* align-self:stretch, not width:100%. The pin centres its children, so a
     flex item sizes to its content — a five-card strip, far wider than the
     screen. Combined with the negative bleed margins below, width:100% made
     it wider still and pushed the whole PAGE sideways, which is where the
     stray horizontal scrollbar came from. Stretch fills exactly the pin's
     inline size and the cards scroll inside it. */
  .hb-hero--scrub .hb-hero__line{align-self:stretch;width:auto;
    justify-content:flex-start}
  .hb-hero--scrub .hb-hero__word img{width:88vw}
  .hb-hero--scrub .hb-hero__linger{font-size:clamp(28px,8.5vw,48px)}
  .hb-hero--scrub .hb-hero__sub{font-size:var(--fs-body-sm);max-width:40ch}
}

/* The hero is centred at the owner's request — the rest of the page keeps
   its left axis. Deliberate exception to the "nothing centred" rule: this
   is the one screen where the composition is a single focal column, and it
   matches the reference site. */
.hb-hero--scrub .hb-hero__linger{text-align:center;max-width:none}
.hb-hero--scrub .hb-hero__sub{text-align:center}
.hb-hero--scrub .hb-hero__head{width:100%}

/* Out of flow while scrubbing. transform:scale() shrinks the wordmark
   visually but its layout box stays full size, which left a viewport-tall
   hole above "Bakes that linger" once it had shrunk away. Absolute removes
   it from flow entirely — the same thing the reference site does. */
/* Centred in the pin, not pinned to a fixed height near the top — at
   top:14svh the wordmark ran straight through the eyebrow row. Filling the
   pin and centring both axes keeps it clear of the header and the eyebrow
   at every window height. */
.hb-hero--scrub .hb-hero__word{position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;pointer-events:none}
.hb-hero--scrub .hb-hero__word img{transform-origin:left top}

.hb-hero--scrub .hb-hero__word img{
  transform:translate(calc(var(--dx,0px) * var(--p)), calc(var(--dy,0px) * var(--p)))
            scale(calc(1 - (1 - var(--s,.1)) * var(--p)));
  opacity:calc(1 - (var(--p) - .8) / .2)}

.hb-hero--scrub .hb-hero__word img{will-change:transform}

/* Reduced motion gets a clean, static hero: no giant wordmark at all, and no
   pin. The wordmark only earns its space by animating — sitting there
   full-screen and motionless it is just an obstacle between the visitor and
   the content. Hiding it also means the pin has nothing to reveal, so the
   scrub is skipped in JS and there is no dead scroll range.
   The header logo is untouched, so the brand is never absent. */
/* RETIRED — owner decision, 2026-08-12: the hero animation now runs for
   everyone and does not consult prefers-reduced-motion. These rules gave
   reduced-motion visitors a static hero with no giant wordmark. Prefixed so
   they cannot match; restore by removing `.retired` if reconsidered.
   See docs/go-live-checklist.md. */
@media (prefers-reduced-motion: reduce){
  .retired .hb-hero__word{display:none}
  .retired .hb-hero .hb-card{transform:none}
  .retired .hb-hero .hb-hero__linger{transform:none;opacity:1}
}

.hb-hero--scrub .hb-card{
  opacity:var(--e,1);
  transform:translateY(calc((1 - var(--e,1)) * 48px))}

/* "Bakes that linger" and its paragraph fade up as the wordmark leaves,
   landing with the cards. The paragraph trails the heading slightly so they
   arrive in reading order rather than together. Hidden ONLY while the scrub
   is running — the static composition shows both outright, so a no-JS
   visitor always sees them. */
.hb-hero--scrub .hb-hero__linger{
  opacity:clamp(0, calc((var(--p) - .35) / .3), 1);
  transform:translateY(calc((1 - clamp(0, calc((var(--p) - .35) / .3), 1)) * 28px))}
.hb-hero--scrub .hb-hero__sub{
  opacity:clamp(0, calc((var(--p) - .45) / .3), 1);
  transform:translateY(calc((1 - clamp(0, calc((var(--p) - .45) / .3), 1)) * 20px))}

/* The counter belongs to the lineup, so it arrives with it. It was sitting
   there reading "1 / 4" while the drinks it counts were still invisible. */
.hb-hero--scrub .hb-line-count{
  opacity:clamp(0, calc((var(--p) - .55) / .25), 1)}

/* Cards translate down 48px while entering, overlapping the actions row.
   At --e:0 they are invisible (opacity:0) but still hit-testable, which
   silently swallows clicks on the Order CTA for the first ~15% of the
   scroll. Nothing inside a card is interactive, so opting them out of
   hit-testing entirely is free. */
.hb-hero--scrub .hb-card{pointer-events:none}

/* The lineup is sized for the static composition, which is taller than the
   100vh pin. The squeeze is worst at p=0, where the wordmark is still full
   size — by p=1 it has shrunk into the header and freed ~139px. Buy the
   room back from the vertical rhythm rather than the cards, which are the
   point of the lineup. Measured at 1200x760, p=0: 626px into a 669px box. */
.hb-hero--scrub .hb-hero__line{--hb-card-h:clamp(200px,32svh,380px);margin-top:28px}

/* Phone sizing lives here, AFTER the desktop rhythm above — the earlier
   mobile block sets the same custom property, and at equal specificity the
   later rule wins regardless of the media query, so phones would otherwise
   inherit desktop-sized cards. */
@media (max-width:1100px){
  /* Bigger than a thumbnail — the drinks are the point of the lineup. The
     strip bleeds to both screen edges so a card is always half-visible at
     the right, which is what tells people it scrolls; scroll-padding keeps
     the first card on the same axis as the text above it. */
  .hb-hero--scrub .hb-hero__line{--hb-card-h:clamp(170px,27svh,250px);margin-top:20px;
    margin-inline:calc(var(--hb-axis) * -1);
    padding-inline:var(--hb-axis);scroll-padding-inline:var(--hb-axis);
    gap:10px}
}

/* Short windows, desktop and laptop. The hero is pinned to the viewport
   HEIGHT, so height is the constraint that matters here, not width — a wide
   but short laptop window is exactly where the composition ran out of room
   and the drinks and buttons fell off the bottom. */
@media (min-width:701px) and (max-height:860px){
  .hb-hero--scrub .hb-hero__linger{font-size:clamp(30px,4.4vw,64px)}
  .hb-hero--scrub .hb-hero__sub{font-size:var(--fs-body-sm);max-width:62ch;
    margin-top:12px}
  .hb-hero--scrub .hb-hero__line{--hb-card-h:clamp(150px,25svh,240px);
    margin-top:20px}
  .hb-hero--scrub .hb-hero__actions{margin-top:18px}
}
@media (min-width:701px) and (max-height:700px){
  .hb-hero--scrub .hb-hero__linger{font-size:clamp(28px,3.6vw,50px)}
  .hb-hero--scrub .hb-hero__line{--hb-card-h:clamp(130px,22svh,190px)}
}

/* Short PHONES. This counterpart to the two desktop rules above was missing,
   and the phone composition is the taller of the two — the wordmark, heading,
   paragraph, a full-width card, the counter and the actions all stack. On a
   360x640 handset that overflows the 100svh pin, and because the pin is
   overflow:hidden with justify-content:safe center (which only protects the
   START edge) the overflow is clipped at the BOTTOM and cannot be scrolled to.
   The last child is .hb-hero__actions — the Order button. So the primary
   call to action becomes invisible and unreachable on small phones.
   Everything here buys vertical room back. */
@media (max-width:700px) and (max-height:760px){
  .hb-hero--scrub .hb-hero__pin{padding-top:clamp(72px,10svh,96px)}
  .hb-hero--scrub .hb-hero__linger{font-size:clamp(24px,7vw,36px)}
  .hb-hero--scrub .hb-hero__sub{font-size:var(--fs-body-sm);margin-top:8px;
    max-width:34ch}
  .hb-hero--scrub .hb-hero__line{--hb-card-h:clamp(140px,26svh,200px);margin-top:12px}
  .hb-hero--scrub .hb-hero__actions{margin-top:12px}
  .hb-hero--scrub .hb-line-count{margin-top:6px}
}
/* Shorter still — drop the paragraph during the scrub. It is still present in
   the static composition below the fold, so nothing is lost; this only trades
   it against the Order button, and the button wins. */
@media (max-width:700px) and (max-height:660px){
  .hb-hero--scrub .hb-hero__sub{display:none}
  .hb-hero--scrub .hb-hero__line{--hb-card-h:clamp(120px,22svh,170px)}
}

/* Phones: one big drink at a time, swiped, rather than a row of tall
   slivers. Sized by WIDTH here (the desktop rule sizes by height), at a
   squarer 4:5 so each photo actually reads — 9:16 at this width would be
   taller than the screen. The next card stays part-visible so the swipe is
   discoverable. */
@media (max-width:700px){
  /* One card per screen, aligned to the same axis as the text above it, so
     it reads as a single photo you swipe rather than a strip you scroll. A
     76%-wide card with a quarter of the next one showing still looked like a
     strip — the whole point of this treatment is that it does not. */
  .hb-hero__line{gap:var(--hb-axis)}
  .hb-card{width:calc(100vw - var(--hb-axis) * 2);scroll-snap-align:center}
  /* 36svh: the cards are invisible at the start of the scrub but still
     reserve their height. That reserved block is both the gap under the
     logo and the reason the composition was overflowing the pin on shorter
     phones — measured at roughly 55px over on a 700px-tall screen. */
  .hb-card__frame{height:auto;aspect-ratio:4/5;max-height:36svh}
  .hb-card figcaption{margin-top:10px}
  .hb-line-count{display:block;margin-top:10px}

  /* The eyebrow row is pulled OUT OF FLOW on phones and faded in once the
     scroll starts.
     Two problems, one fix. Visually the owner did not want it in the
     opening frame. Structurally it was the victim of a flex-centring
     overflow: the pin centres its children, so when the content is taller
     than the pin it spills off BOTH ends — the eyebrow was pushed up behind
     the fixed header and clipped, which is why only half of it showed.
     Absolute positioning removes its height from the flow entirely, so the
     rest of the composition fits, and it sits just clear of the header. */
  .hb-hero--scrub .hb-hero__head{
    position:absolute;top:clamp(88px,12svh,132px);
    left:var(--hb-axis);right:var(--hb-axis);width:auto;
    flex-direction:column;align-items:center;gap:4px;text-align:center;
    opacity:clamp(0, calc((var(--p) - .08) / .22), 1)}

  /* Horizontal pan, driven by the page's vertical scroll. One card is
     visible at a time, so the strip travels four steps to show all five.
     A step is one card plus one gap = (100vw - 2*axis) + axis = 100vw - axis.
     Manual side-swiping is turned off while this runs — two things moving
     the same strip at once fight each other — so overflow goes visible and
     the pin's own overflow:hidden does the clipping. */
  .hb-hero--scrub .hb-hero__line{
    overflow:visible;scroll-snap-type:none;
    /* A step is one card plus one gap. The card is 100vw - 2*axis (it is
       centred by the padding), so a step is that plus --hb-line-gap. Four
       steps bring the fifth card to where the first started - the centre
       of the screen. This previously read (100vw - axis), which assumed the
       gap was the axis; it is 10px, so each step overshot by 21px and the
       last card finished 64px left of centre.
       Phones show all five cards (owner's call, 2026-09-30) — this used to
       hide the fifth and pan three steps. */
    transform:translateX(calc(var(--pan,0) * -4 *
      (100vw - var(--hb-axis) * 2 + var(--hb-line-gap, 10px))))}
  .hb-hero--scrub .hb-card{scroll-snap-align:none}
}
.hb-hero--scrub .hb-hero__sub{margin-top:16px}
.hb-hero--scrub .hb-hero__actions{margin-top:24px}

/* Only hide the nav logo while a scrub is actually running — never by
   default, or a no-JS visitor loses the header logo entirely. */
.hb-hero--scrub-active .hb-nav .hb-logo{opacity:var(--navlogo,1)}

/* On phones the header logo stays visible throughout. The fade exists so the
   giant wordmark appears to BECOME the header logo, but that hand-off needs
   room to read; on a small screen it just meant no logo in the header for
   the first 80% of the scroll, which looked like the header was missing.
   Visible from the start, the mark shrinking toward it still reads as
   convergence — and there is always a logo up there. */
@media (max-width:1100px){
  .hb-hero--scrub-active .hb-nav .hb-logo{opacity:1}
}

/* ============================================================
   HERO — image drift
   This stands in for the "slow moving video" in the brief. There is exactly
   one video clip in the whole project, and capheroasters.com — one of the
   cited references — serves no video at all, so the cinematic quality is
   carried by imagery instead.

   Applied to the <img>, NOT to .hb-card: the card carries the scrub's entry
   translateY, and two transforms on one element overwrite rather than
   compose. The img sits inside .hb-card__frame, which has overflow:hidden,
   so the scaled image stays clipped instead of bleeding past the card.
   ============================================================ */
@keyframes hb-drift{
  from{transform:scale(1)}
  to  {transform:scale(1.06)}
}
.hb-card img{animation:hb-drift 20s ease-in-out infinite alternate}

/* Negative delays start each card part-way through its cycle, so no two
   drift in unison and none of them sits waiting to begin. */
.hb-card:nth-child(2) img{animation-delay:-4s}
.hb-card:nth-child(3) img{animation-delay:-8s}
.hb-card:nth-child(4) img{animation-delay:-12s}
.hb-card:nth-child(5) img{animation-delay:-16s}

/* RETIRED — owner decision, 2026-08-12: the drift runs for everyone.
   Restore by removing `.retired`. See docs/go-live-checklist.md. */
@media (prefers-reduced-motion: reduce){
  .retired .hb-card img{animation:none}
}
/* Off below the scrub breakpoint too — continuous animation is a battery
   and data cost on phones for an effect that reads as ambient at best. */
@media (max-width:1100px){
  .hb-card img{animation:none}
}

/* Tickers double as a manual "next" control — hint that with an
   underline + arrow that appears on hover/focus. */
.is-clickable{cursor:pointer;display:inline-flex;align-items:center;gap:6px;
  border-bottom:1px solid transparent;transition:border-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
.is-clickable::after{content:"→";opacity:0;transform:translateX(-4px);
  transition:opacity var(--dur-fast) var(--ease),transform var(--dur-fast) var(--ease)}
.is-clickable:hover,.is-clickable:focus-visible{border-color:currentColor}
.is-clickable:hover::after,.is-clickable:focus-visible::after{opacity:1;transform:none}

/* ============================================================
   SECTIONS
   ============================================================ */
.hb-sec{padding-top:clamp(72px,11vw,168px);padding-bottom:clamp(72px,11vw,168px)}
.hb-sec--tight{padding-top:clamp(48px,7vw,110px);padding-bottom:clamp(48px,7vw,110px)}
.hb-split{display:grid;grid-template-columns:1fr 1fr;gap:clamp(28px,6vw,90px);align-items:center}
.hb-labelrow{display:flex;align-items:center;gap:14px;margin-bottom:22px}
/* Sage as the secondary accent: decorative rules only, not structural
   hairlines — keeping it off the item/table dividers stops it reading as
   a large fill. */
.hb-labelrow .ln{flex:1;height:1px;background:var(--hb-sage)}
.hb-fig{aspect-ratio:3/4}
.hb-fig--wide{aspect-ratio:16/10}
.hb-fig img,.hb-fig{overflow:hidden}
.hb-fig img{width:100%;height:100%;object-fit:cover;display:block}

/* A figure that takes its height from the column beside it, instead of a 3:4
   portrait setting the height of the whole section. The image is taken OUT OF
   FLOW so it contributes no height of its own - without that the row still
   sizes to the photo and stretching the figure changes nothing. object-fit
   then crops it to whatever height the text came out at.
   Two-column layouts only: below 860px .hb-split is one column, there is no
   neighbour to match, and the figure keeps its own ratio. */
.hb-fig--fill{position:relative}
@media(min-width:861px){
  .hb-split--match{align-items:stretch}
  /* Floor for very short copy, so the crop cannot become a letterbox strip. */
  .hb-split--match .hb-fig--fill{aspect-ratio:auto;min-height:320px}
  .hb-split--match .hb-fig--fill img{position:absolute;inset:0}
}
@media(max-width:860px){
  /* Stacked, so there is nothing to match - but a 3:4 portrait at full width
     is close to a whole screen of photograph before a single word of the
     offer. Same landscape crop as the desktop layout gets. */
  .hb-split--match .hb-fig--fill{aspect-ratio:4/3}
}
.hb-figcap{position:absolute;left:14px;bottom:12px;z-index:2;display:inline-flex;align-items:center;gap:10px;
  padding:7px 12px;background:rgba(25,25,25,.68);backdrop-filter:blur(2px);
  font-family:var(--font-sans);font-size:var(--fs-2xs);letter-spacing:var(--tracking-wide);
  text-transform:uppercase;color:var(--nib-white)}

/* ============================================================
   DARK BREAK PANEL  (statement)
   ============================================================ */
.hb-dark{background:var(--canvas-invert);color:var(--ink-invert);
  padding:clamp(90px,15vw,220px) var(--gutter)}
.hb-dark .stmt{font-family:var(--font-display);font-weight:var(--fw-light);
  font-size:var(--fs-display-md);line-height:var(--lh-tight);letter-spacing:var(--tracking-tight);
  text-transform:uppercase;max-width:16ch;text-wrap:balance}
.hb-dark .stmt .nib-italic{text-transform:none;color:var(--accent)}
.hb-dark .sub{font-family:var(--font-sans);font-style:italic;color:rgba(255,255,255,.6);
  margin-top:26px;font-size:var(--fs-md)}

/* Narrow phones: --fs-display-md is clamp(3.5rem, 8vw + 1rem, 6.1rem), so its
   FLOOR is 56px and it stops scaling below ~500px wide. "HANDCRAFTED." sets
   427px at 56px against a 320px content box on a 360px phone - 107px of the
   word is thrown away, silently, because body is overflow-x:clip. The clamp
   lives in the vendored base and must not be edited, so override the size
   here instead. 11vw keeps the word inside the gutters all the way down and
   hands back to the 56px original once there is room for it (~509px). */
@media (max-width:560px){
  .hb-dark .stmt{font-size:clamp(2.25rem,11vw,3.5rem)}
}

/* ============================================================
   NUMBERED INDEX (menu as contents)
   ============================================================ */
.hb-ixhead{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:12px;
  margin-bottom:10px}
.hb-ix{border-top:1px solid var(--line)}
.hb-ixrow{display:grid;grid-template-columns:70px 1fr auto;align-items:center;gap:clamp(14px,3vw,40px);
  padding:clamp(20px,2.6vw,34px) 4px;border-bottom:1px solid var(--line);position:relative;
  color:var(--ink);transition:padding-left var(--dur) var(--ease)}
.hb-ixrow:hover{padding-left:24px}
.hb-ixrow .n{font-family:var(--font-sans);font-size:var(--fs-sm);color:var(--ink-muted);
  letter-spacing:.04em}
/* Sentence case, not caps: these are a scannable list of categories, not
   eight headlines. Caps at this size competed with the section heading and
   is measurably slower to read. Kept in Fraunces so the section still reads
   as part of the editorial system rather than an app menu. Tracking eased
   off --tracking-tight, which is tuned for uppercase. */
.hb-ixrow .nm{font-family:var(--font-display);font-weight:var(--fw-light);
  font-size:clamp(28px,4.2vw,56px);line-height:1.05;letter-spacing:-0.015em;
  transition:color var(--dur-fast) var(--ease)}
.hb-ixrow:hover .nm{color:var(--accent)}
.hb-ixrow .mt{font-family:var(--font-sans);font-size:var(--fs-2xs);letter-spacing:var(--tracking-wide);
  text-transform:uppercase;color:var(--ink-muted);text-align:right;max-width:22ch}
.hb-ixrow .go{position:absolute;right:4px;opacity:0;transform:translateX(-8px);color:var(--accent);
  transition:opacity var(--dur-fast) var(--ease),transform var(--dur-fast) var(--ease)}
.hb-ixrow:hover .go{opacity:1;transform:none}

/* ============================================================
   VALUES (numbered, overlapping-ish trio)
   ============================================================ */
.hb-trio{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(20px,3vw,44px)}

/* RETIRED — pending owner review (2026-08-12)
   "On the board this month" was briefly its own section under the hero. The
   owner asked for the specials to be part of the hero lineup with a badge
   instead, so this is unused. Prefixed so it cannot match; kept rather than
   deleted per the no-delete standing instruction. */
.retired .hb-specials .hb-trio{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
.retired .hb-specials .hb-val h3{margin:0 0 6px}
.retired .hb-specials-price{color:var(--ink);white-space:nowrap}
.hb-val .vfig{aspect-ratio:4/5;overflow:hidden}
.hb-val .vfig img{width:100%;height:100%;object-fit:cover;display:block}
.hb-val:nth-child(2){transform:translateY(clamp(20px,4vw,56px))}
.hb-val .vn{font-family:var(--font-sans);font-size:var(--fs-sm);color:var(--ink-muted);
  margin:18px 0 8px;letter-spacing:.04em}
.hb-val h3{font-family:var(--font-display);text-transform:uppercase;font-weight:var(--fw-light);
  font-size:var(--fs-xl);line-height:1;letter-spacing:var(--tracking-tight);margin:0 0 10px}
.hb-val p{font-family:var(--font-sans);color:var(--nib-ink-70);font-size:var(--fs-body);
  line-height:var(--lh-body);max-width:38ch}

/* ============================================================
   MARQUEE
   ============================================================ */
.hb-marq{overflow:hidden;border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  padding:18px 0;white-space:nowrap;background:var(--canvas)}
.hb-marq-in{display:inline-flex;align-items:center;gap:34px;animation:hbslide 34s linear infinite;will-change:transform}
.hb-marq:hover .hb-marq-in{animation-play-state:paused}
.hb-mq{font-family:var(--font-serif);font-style:italic;font-size:clamp(20px,2.4vw,30px);color:var(--ink)}
/* Marquee separator. This was the character U+2733 EIGHT SPOKED ASTERISK,
   coloured sage. It looked right on desktop and wrong on phones, because
   U+2733 carries an emoji presentation: Android renders it from the colour
   emoji font, which ignores `color` entirely and paints a saturated green
   glyph. The sage was never visible on mobile at all.

   A variation selector (U+FE0E) would force text presentation, but support
   is patchy on exactly the devices that get it wrong. Drawing the shape
   ourselves removes the font from the question — it cannot become an emoji,
   and it honours the palette on every device. Rotated square rather than a
   star: the asterisk read as decorative in a way that fought the type. */
.retired .hb-mq-star{color:var(--hb-sage);font-size:14px}
.hb-mq-sep{width:6px;height:6px;flex:0 0 auto;align-self:center;
  background:var(--hb-sage);transform:rotate(45deg)}
@keyframes hbslide{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* The marquee keeps scrolling under prefers-reduced-motion, at the owner's
   explicit request — stopping it was why the banner went static on their
   machine.

   This IS a deliberate accessibility trade, not an oversight. Mitigations
   that stay in place: the band is aria-hidden="true" so it is invisible to
   screen readers, it carries no information that is not elsewhere on the
   page, and .hb-marq:hover pauses it. The gap is keyboard and touch users
   who have asked for reduced motion and cannot hover to pause.

   RETIRED — pending owner review (2026-08-12): the guard that stopped it.
   Restore by removing the .retired prefix if the trade is reconsidered. */
@media (prefers-reduced-motion: reduce){
  .retired .hb-marq-in{animation:none}
}

/* ============================================================
   REVIEWS (cream, off-centre, single cycling quote)
   ============================================================ */
/* Moved up to follow the menu index directly, and moved off the dark panel:
   two full-dark sections back to back reads heavy, and the brief asks for
   most of the page to stay neutral. Still deliberately restrained — quiet
   social proof between the louder sections, not a display-type statement.
   The star uses roast (6.3:1 on cream), never gold: gold is 1.4:1 on cream
   and cannot carry a mark, and gold is reserved for interactive elements
   regardless. */
/* Content sits inside .nib-container like every other section, so the left
   edge lines up with the rest of the page. Padding the section with
   --hb-axis instead put the reviews on a different vertical axis from
   everything above and below it, which read as inconsistent.

   The band tint is what finally gives sage a job. It marks the reviews as a
   distinct beat between the menu index and the dark panel without breaking
   the "mostly neutral" rule — it is a wash, not a colour block. Ink on it
   measures 13.9:1. */
.hb-reviews{background:var(--hb-band)}
.hb-reviews-in{max-width:620px}
.hb-rating{display:flex;align-items:baseline;gap:8px;margin:0 0 24px;
  font-family:var(--font-sans);font-size:var(--fs-body);color:var(--ink)}
.hb-rating b{font-family:var(--font-display);font-weight:var(--fw-light);
  font-size:var(--fs-xl);line-height:1;letter-spacing:var(--tracking-tight)}
.hb-rating .star{color:var(--hb-roast);font-size:var(--fs-md)}

/* Reuses the hero-stack's crossfade-cycle pattern instead of a scrolling
   marquee — full sentences are hard to read mid-scroll, so this holds
   still and changes on a timer instead. */
.hb-quote{max-width:46ch;margin:24px 0 0}
.hb-quote-text{font-family:var(--font-sans);font-style:italic;font-size:clamp(18px,2vw,24px);
  line-height:1.5;color:var(--ink);margin:0;transition:opacity .3s var(--ease)}
.hb-quote-who{display:block;margin-top:12px;font-family:var(--font-sans);font-style:normal;
  font-size:var(--fs-2xs);letter-spacing:var(--tracking-wide);text-transform:uppercase;
  color:var(--nib-ink-70);transition:opacity .3s var(--ease)}
.hb-quote-n{font-family:var(--font-sans);font-size:var(--fs-2xs);
  letter-spacing:.06em;color:var(--nib-ink-70)}
.hb-quote-nav{display:flex;align-items:center;justify-content:flex-start;gap:16px;margin-top:20px}
.hb-quote-arrow{width:28px;height:28px;flex:none;border:1px solid var(--line);border-radius:var(--radius-pill);
  background:none;color:var(--ink);font-family:var(--font-sans);font-size:13px;line-height:1;cursor:pointer;
  transition:border-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}
.hb-quote-arrow:hover,.hb-quote-arrow:focus-visible{border-color:var(--hb-roast);color:var(--hb-roast)}
.hb-reviews .hb-tlink{margin-top:24px;font-size:var(--fs-body-sm)}

/* RETIRED — pending owner review (2026-08-11)
   .hb-tlink--invert styled the "Read them on Google" link against the dark
   reviews panel. Reviews are cream now and no live page uses this modifier
   (checked across all five). Selectors prefixed so they cannot match; kept
   rather than deleted per the owner's no-delete standing instruction. */
.retired .hb-tlink--invert{color:var(--nib-white);border-bottom-color:rgba(255,255,255,.35)}
.retired .hb-tlink--invert .arw{color:rgba(255,255,255,.6)}
.retired .hb-tlink--invert:hover{color:var(--accent);border-color:var(--accent)}
.retired .hb-tlink--invert:hover .arw{color:var(--accent)}

/* ============================================================
   FOLLOW (social CTA band)
   ============================================================ */
.hb-follow{display:flex;flex-direction:column;align-items:flex-start;gap:20px;
  max-width:640px;margin-inline:0}
.hb-follow .nib-heading{font-size:var(--fs-xl)}
.hb-follow .hb-actions{justify-content:flex-start}

/* ============================================================
   CLOSER
   ============================================================ */
.hb-closer{padding-top:clamp(80px,12vw,180px);padding-bottom:clamp(60px,9vw,140px)}
.hb-closer .big{font-family:var(--font-display);font-weight:var(--fw-light);
  font-size:var(--fs-display-lg);line-height:var(--lh-tight);letter-spacing:var(--tracking-tight);
  text-transform:uppercase;margin:0;text-wrap:balance}
.hb-closer .big .nib-italic{text-transform:none;color:var(--accent)}
.hb-cgrid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(24px,5vw,70px);margin-top:clamp(30px,5vw,60px)}
.hb-cline{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;
  border-top:1px solid var(--line);padding:15px 0}
.hb-cline .k{font-family:var(--font-sans);font-size:var(--fs-2xs);letter-spacing:var(--tracking-wide);
  text-transform:uppercase;color:var(--ink-muted)}
.hb-cline .v{font-family:var(--font-sans);font-size:var(--fs-body);color:var(--ink)}

/* ============================================================
   FOOTER
   ============================================================ */
.hb-foot{background:var(--canvas-invert);color:var(--ink-invert);padding:clamp(56px,8vw,110px) var(--gutter) 40px;
  container-type:inline-size}
/* "HUCKLEBERRY" must fit on one line at every width. Its rendered width is
   ~7.2x the font-size in this face, so sizing off the footer's own content
   box (cqw) keeps it in bounds instead of a fixed px floor, which overflowed
   by ~68px on a 375px screen and got clipped by body{overflow-x:hidden}.
   The vw line is the fallback for browsers without container queries. */
.hb-foot-word{font-family:var(--font-display);text-transform:uppercase;font-weight:var(--fw-light);
  font-size:clamp(28px,12vw,220px);
  font-size:min(220px,13.5cqw);
  line-height:.82;letter-spacing:var(--tracking-tight);color:var(--nib-white);white-space:nowrap}
/* The footer wordmark carries (TM), not (R). (R) is reserved for REGISTERED
   marks and the name is not registered; (TM) asserts an unregistered mark
   and needs no filing. It briefly carried (R) until 2026-08-27. If the mark
   is ever registered, swapping the character is the only change needed. */
.hb-foot-word .reg{font-size:.28em;vertical-align:super;color:rgba(255,255,255,.45)}
.hb-foot-top{display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:40px;padding:clamp(36px,6vw,80px) 0}

/* ============================================================
   CONSENT BANNER
   Injected by assets/analytics.js, so it exists on every page
   without the markup being duplicated across five files.
   ============================================================ */
.hb-consent{position:fixed;left:0;right:0;bottom:0;z-index:60;
  display:flex;align-items:center;justify-content:space-between;
  gap:20px;flex-wrap:wrap;padding:16px var(--gutter);
  background:var(--ink);color:var(--canvas);
  border-top:1px solid rgba(255,255,255,.14)}
.hb-consent__text{margin:0;max-width:68ch;
  font-family:var(--font-sans);font-size:var(--fs-body-sm);line-height:1.5;
  color:rgba(255,255,255,.88)}
.hb-consent__actions{display:flex;gap:10px;flex:0 0 auto}
.hb-consent .nib-btn{white-space:nowrap}
/* The outlined button sits on ink here, not cream, so it needs its own
   colours - the base .nib-btn assumes a light background. */
.hb-consent .nib-btn:not(.nib-btn--solid){
  border-color:rgba(255,255,255,.45);color:var(--canvas);background:transparent}
.hb-consent .nib-btn:not(.nib-btn--solid):hover,
.hb-consent .nib-btn:not(.nib-btn--solid):focus-visible{
  background:var(--canvas);color:var(--ink);border-color:var(--canvas)}

/* Phones: stack, and let the buttons share the width so both are an easy
   thumb target rather than two small pills crowded on one line. */
@media (max-width:700px){
  .hb-consent{flex-direction:column;align-items:stretch;gap:10px;padding:12px var(--gutter)}
  .hb-consent__actions{width:100%}
  .hb-consent__actions .nib-btn{flex:1 1 0;justify-content:center;text-align:center}
  /* Every line here is a line the hero has to give up, and on a 640px-tall
     phone the banner was taking 28% of the viewport. The reassurance is the
     least load-bearing sentence, so it goes first. */
  .hb-consent__more{display:none}
  .hb-consent__text{font-size:var(--fs-xs);line-height:1.45}
}

.hb-consent-reopen{background:none;border:0;padding:0;cursor:pointer;
  font-family:inherit;font-size:inherit;color:inherit;opacity:.75;
  text-decoration:underline;text-underline-offset:3px}
.hb-consent-reopen:hover,.hb-consent-reopen:focus-visible{opacity:1}

/* Reinstated 2026-09-07, with the missing half of it this time.

   While the banner is open it must not sit on top of anything clickable. The
   hero is a pinned, viewport-height composition, so page-level padding cannot
   move its contents - the room has to come out of the pin itself.
   --hb-consent-h is measured and published by analytics.js after the banner
   renders, because its height depends on how the copy wraps at that width.

   This rule was removed on 2026-08-29 because it broke the logo animation:
   hb-hero.js measures the wordmark FLIP at init, BEFORE the banner is
   injected, so padding the pin afterwards moved the wordmark while
   --dx/--dy/--s still described its old position, and it travelled to the
   wrong place. That was a fault in the measurement, not in this rule, and
   hb-hero.js now re-measures whenever .hb-consent-open flips - so the layout
   is no longer being moved underneath a stale measurement.

   That coupling is real and invisible from here: if this rule changes, the
   observer in hb-hero.js is what keeps the animation honest. Without it the
   wordmark drifts, and it looks intermittent because it only misbehaves
   while the banner happens to be showing.

   Verified 2026-09-07 at 360x640, 375x667, 390x844 and 412x915: the hero
   Order button and the "See the menu" link both clear the banner, and the
   wordmark still lands on the header logo at 0,0 with the banner open, after
   dismissing it, and after a mid-page refresh.

   Padding-bottom alone is not enough, and it is worth knowing why. The pin is
   a flex column with justify-content:safe center, and on a tall phone its
   contents do not fit: 597px of composition in 567px of content box. "safe"
   then aligns to the start, so the content hangs off the bottom and growing
   padding-bottom only shrinks the box it is already overflowing - measured,
   it moved the Order button by exactly nothing. In the overflow case the
   only thing that moves the bottom of the hero is the padding at the TOP, so
   both are set here. Padding-bottom still earns its place on shorter phones,
   where the composition does fit and centring shifts it up by half. */
.hb-consent-open .hb-hero--scrub .hb-hero__pin{
  padding-top:clamp(56px,8svh,80px);
  padding-bottom:calc(var(--hb-consent-h, 0px) + 64px)}

/* And the padding can only do so much before it is eating the composition
   from the wrong end. The short-phone rules below already treat the card as
   the thing to give up when the pin runs out of room; an open banner is the
   same problem arriving on a phone that is not short, so it gets the same
   answer. Width only, deliberately - a 111px banner makes every phone a short
   phone, and keying this to a height threshold is what left the 761-890px
   band unhandled in the first place. */
@media (max-width:860px){
  .hb-consent-open .hb-hero--scrub .hb-hero__line{--hb-card-h:clamp(130px,20svh,200px)}
}
/* Below 700px the card stops taking its height from --hb-card-h at all - it
   goes full-bleed and is capped by .hb-card__frame's max-height instead, so
   the rule above is inert here and the cap is the only lever. Worth stating
   plainly: setting --hb-card-h on a phone looks like it should work and does
   nothing. */
@media (max-width:700px){
  .hb-consent-open .hb-hero--scrub .hb-card__frame{max-height:29svh}
}

/* Everything else just needs clearance at the end of the document, so the
   footer and closer CTAs are not trapped underneath it. */
.hb-consent-open body{padding-bottom:var(--hb-consent-h, 0px)}

.hb-foot-logo{height:32px;margin-bottom:20px}
.hb-foot h4{font-family:var(--font-sans);font-size:var(--fs-2xs);letter-spacing:var(--tracking-xwide);
  text-transform:uppercase;color:rgba(255,255,255,.5);margin:0 0 14px}
.hb-foot a,.hb-foot p{font-family:var(--font-sans);font-size:var(--fs-body-sm);color:rgba(255,255,255,.85);
  display:block;padding:6px 0;margin:0;transition:color var(--dur-fast)}
/* Gold on the dark footer, not honey: honey is 4.16:1 there. These are
   links, so gold is the sanctioned token. */
.hb-foot a:hover,.hb-foot a:focus-visible{color:var(--hb-gold)}
.hb-foot-bottom{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;
  border-top:1px solid rgba(255,255,255,.14);padding-top:24px;
  font-family:var(--font-sans);font-size:var(--fs-2xs);letter-spacing:var(--tracking-wide);
  text-transform:uppercase;color:rgba(255,255,255,.5)}

/* ============================================================
   INTERIOR PAGE HEAD (Menu / About)
   ============================================================ */
.hb-pagehead{padding-top:clamp(130px,20vh,200px);padding-bottom:clamp(40px,6vw,80px)}
.hb-pagehead-top{display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  flex-wrap:wrap;padding-bottom:clamp(20px,4vw,44px)}
.hb-pagehead h1{font-family:var(--font-display);font-weight:var(--fw-light);
  font-size:var(--fs-display-md);line-height:var(--lh-tight);letter-spacing:var(--tracking-tight);
  text-transform:uppercase;margin:0;text-wrap:balance}
.hb-pagehead h1 .nib-italic{text-transform:none}
.hb-pagehead .nib-body{margin-top:clamp(20px,3vw,34px);max-width:56ch}

/* ============================================================
   IMAGE DUO (two-up figure pair)
   ============================================================ */
.hb-duo{display:grid;grid-template-columns:1fr 1fr;gap:clamp(14px,2.4vw,28px);
  margin-bottom:clamp(28px,4vw,54px)}
.hb-duo .hb-fig{aspect-ratio:3/4}

/* ============================================================
   MENU CHAPTERS
   ============================================================ */
.hb-chap{padding-top:clamp(56px,8vw,120px)}
.hb-chap:first-of-type{padding-top:0}
.hb-chap-head{display:flex;gap:clamp(16px,3vw,32px);align-items:baseline;
  border-top:1px solid var(--line);padding-top:clamp(20px,3vw,36px);margin-bottom:clamp(28px,4vw,54px)}
.hb-chap-n{font-family:var(--font-sans);font-size:var(--fs-sm);color:var(--ink-muted);
  letter-spacing:.04em;flex:none}
.hb-chap-head h2{font-family:var(--font-display);text-transform:uppercase;font-weight:var(--fw-light);
  font-size:clamp(32px,4.6vw,60px);line-height:1;letter-spacing:var(--tracking-tight);margin:0 0 10px}
.hb-chap-head .nib-body{margin:0}
/* Roast, not honey: this tag is real information at 10.5px, and honey on
   cream measures 3.68:1 — under the 4.5:1 AA floor for text this size.
   Roast measures 6.3:1 and stays in-palette. (The brief's own note:
   "reserve honey text for large/accent use, not body copy.") */
.hb-flag{display:inline-flex;align-items:center;gap:8px;margin-top:12px;
  font-family:var(--font-sans);font-size:var(--fs-2xs);letter-spacing:var(--tracking-wide);
  text-transform:uppercase;color:var(--accent-strong)}

/* ============================================================
   ITEM LIST (editorial menu rows, with thumbnail)
   ============================================================ */
.hb-items{border-top:1px solid var(--line)}
.hb-item{display:grid;grid-template-columns:56px 1fr;gap:clamp(14px,2.6vw,24px);
  align-items:center;padding:clamp(14px,2vw,22px) 4px;border-bottom:1px solid var(--line)}
.hb-item-fig{width:56px;height:56px;overflow:hidden;background:var(--canvas-alt);flex:none}
.hb-item-fig img{width:100%;height:100%;object-fit:cover;display:block}
.hb-item-top{display:flex;justify-content:space-between;align-items:baseline;gap:16px}
.hb-item-name{font-family:var(--font-serif);font-size:var(--fs-md);color:var(--ink)}
/* 16px, not 14. This is reading copy, and the menu is the page with the most
   of it — it was the last place the owner's "body text 16–18px" brief had
   not been applied. Measure widened with the type so the line length stays
   comfortable rather than getting shorter. */
.hb-item-desc{font-family:var(--font-sans);font-size:var(--fs-body-sm);color:var(--nib-ink-70);
  line-height:var(--lh-body);margin-top:4px;max-width:56ch}
.hb-item-price{font-family:var(--font-sans);font-size:var(--fs-xs);letter-spacing:var(--tracking-wide);
  text-transform:uppercase;color:var(--ink-muted);white-space:nowrap}

/* Compact price list — the full long-tail lineup (standard café drinks,
   everyday pastries) where a thumbnail per row isn't realistic. Reuses
   .nib-label for its sub-heading. */
.hb-minilist{display:grid;grid-template-columns:1fr 1fr;gap:0 clamp(20px,4vw,48px);margin-top:16px}
.hb-minirow{display:flex;justify-content:space-between;align-items:baseline;gap:12px;
  padding:9px 0;border-bottom:1px solid var(--line)}
/* The item NAME in the compact price lists — content, not a label, so it
   takes the prose size. The price beside it (.hb-minirow .v) stays small. */
.hb-minirow .k{font-family:var(--font-sans);font-size:var(--fs-body-sm);color:var(--ink)}
.hb-minirow .v{font-family:var(--font-sans);font-size:var(--fs-2xs);letter-spacing:var(--tracking-wide);
  text-transform:uppercase;color:var(--ink-muted);white-space:nowrap}
.hb-subhead{margin:clamp(32px,4vw,48px) 0 4px;display:block}
@media(max-width:860px){.hb-minilist{grid-template-columns:1fr}}

/* ============================================================
   ORDER — SQUARE IFRAME WRAP
   ============================================================ */
.hb-frame-wrap{padding-top:clamp(130px,20vh,200px);padding-bottom:clamp(60px,9vw,120px)}
.hb-frame-head{margin-bottom:clamp(24px,4vw,48px)}
.hb-frame{width:100%;height:80vh;min-height:640px;border:1px solid var(--line);background:var(--canvas-alt)}
.hb-frame-fallback{margin-top:18px;display:flex;justify-content:flex-end}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media(max-width:860px){
  .hb-hero-foot,.hb-split,.hb-cgrid{grid-template-columns:1fr;gap:26px}
  .hb-trio{grid-template-columns:1fr;gap:44px}
  .hb-val:nth-child(2){transform:none}
  .hb-foot-top{grid-template-columns:1fr;gap:28px}
  .hb-stack{display:block}
  .hb-stack .small{width:66%;margin:-16% 0 0 auto;transform:none;display:block}
  .hb-stack .cap{margin:12px 0 0}
  .hb-ixrow{grid-template-columns:48px 1fr;gap:16px}
  .hb-ixrow .mt{display:none}
  .hb-nav-right .hb-nav-link{display:none}
  .hb-nav-icon{display:inline-flex}
  .hb-nav-toggle{display:inline-block}
  /* Tighten the header so logo + status pill + CTA + toggle all fit. */
  .hb-nav{gap:10px}
  .hb-logo{height:34px}
  .hb-nav.scrolled .hb-logo{height:30px}
  .hb-open{margin-left:9px;padding:4px 8px;gap:5px}
  .hb-nav .nib-btn{padding:10px 15px;font-size:var(--fs-xs)}
  .hb-duo{grid-template-columns:1fr}
  .hb-item{grid-template-columns:44px 1fr;gap:14px}
  .hb-item-fig{width:44px;height:44px}
  .hb-frame{height:75vh;min-height:520px}

  /* "What we hold to" — stacking three full-width 4:5 photos made this
     section roughly three screens tall on a phone. Side-by-side instead:
     a square thumbnail with the text beside it, so all three fit in about
     one screen and read as a list rather than three separate posters. */
  .hb-trio{gap:28px}
  /* minmax(0,1fr), NOT 1fr. A grid track sized 1fr keeps an implicit
     min-width:auto, so a long unbreakable word ("NEIGHBOURHOOD") widens the
     track past the viewport instead of wrapping — which is why the headings
     and body copy were running off the right edge. */
  .hb-val{display:grid;grid-template-columns:88px minmax(0,1fr);
    column-gap:16px;row-gap:2px;align-items:start}
  .hb-val .vfig{grid-column:1;grid-row:1 / span 3;aspect-ratio:1}
  .hb-val .vn,.hb-val h3,.hb-val p{grid-column:2;min-width:0}
  /* --fs-xl (33.75px) is a desktop display size; beside an 88px thumbnail
     it cannot fit. Fluid, and tracking back to normal so it does not push. */
  .hb-val h3{margin:0;font-size:clamp(19px,5.2vw,26px);letter-spacing:0;
    overflow-wrap:break-word}
  .hb-val p{max-width:none;margin-top:4px;font-size:var(--fs-body-sm);
    overflow-wrap:break-word}

  /* Address / hours rows — space-between pushed the value hard right and
     wrapped it raggedly under its own label. Label above value, both left
     aligned, is legible at any width. */
  .hb-cline{flex-direction:column;align-items:flex-start;gap:4px;flex-wrap:nowrap}
  .hb-cline .v{text-align:left}

  /* The closer heading is display-scale; keep it from crowding the edge. */
  .hb-closer .big{font-size:clamp(44px,13vw,80px)}
  .hb-cgrid{margin-top:32px}
}
/* Header space, phones. The directions icon adds ~50px to a row that was
   already tight, and the owner chose to keep the words on "Order online"
   rather than reduce the primary CTA to an unlabelled icon — unlabelled
   CTAs get tapped less, and ordering is what the site exists to drive.
   So the room comes from the logo and the pill instead. */
@media(max-width:700px){
  .hb-logo{height:28px}
  .hb-nav.scrolled .hb-logo{height:26px}
  /* 44px stays — see the touch-target note on .hb-nav-icon. */
  .hb-nav{gap:8px}
  .hb-nav-icon svg{width:17px;height:17px}
  .hb-open{margin-left:6px;padding:3px 7px;font-size:var(--fs-2xs)}
  .hb-nav .nib-btn{padding:9px 13px}
}

/* Below ~480px even that is not enough for logo + pill + directions + CTA +
   toggle. The pill goes first: it is the one item that is a nicety rather
   than an action, and the hours still appear on Home and on Contact.
   (Was 359px; the directions icon moved the threshold up.) */
@media(max-width:480px){
  .hb-open{display:none}
}
