@property --rot{syntax:"<angle>";inherits:false;initial-value:0deg}
@property --pat-rot{syntax:"<angle>";inherits:false;initial-value:0deg}
/* ==========================================================================
   Fenian's Pub — v2.1 "Snug Collage"
   Right angles, imperfect shards, lead lines, dirt, frosted stained glass.
   Colour sampled from the window. Type from Typekit kit luu5kja only.
   ========================================================================== */

/* ---------- metric-matched fallbacks (22 Sep) ----------------------------
   The kit is font-display:auto (the kit project sets that, not this sheet),
   so text is held for up to 3 seconds and then paints in whatever is next in
   the stack. Measured on the phone profile the kit lands at 9.5s, so there is
   a long stretch in Impact and Georgia, and the moment the real face arrives
   every line re-wraps: that swap is the WHOLE of this site's layout shift
   (About 0.066 at 4.15s, the hero box 461->458px and the section under it
   moving 51px; Menu 0.043 on the tab panel; pub-life 0.027 on the hero plate).

   Each rule below is the same local font with size-adjust set so its average
   advance matches the kit face it stands in for, and ascent/descent overridden
   to that face's. Measured in the browser against the live kit
   (docs/tools/fontmetrics.py writes the numbers; re-run it if Opie
   republishes the kit). Nothing here touches the settled page: once the kit
   face is there it wins, and these only ever describe the wait.

   local() with no url(): if the machine has no Impact or Georgia the rule
   simply fails to match and the stack falls through to the plain name behind
   it, which is exactly what happens today. Nothing is downloaded. */
@font-face{font-family:"bourbon-fb";src:local("Impact");size-adjust:77.2%;ascent-override:129%;descent-override:15.8%;line-gap-override:0%}
@font-face{font-family:"bourbon-lines-fb";src:local("Impact");size-adjust:77.2%;ascent-override:129.1%;descent-override:15.5%;line-gap-override:0%}
@font-face{font-family:"broadsheet-fb";src:local("Georgia");size-adjust:86.3%;ascent-override:124.2%;descent-override:37%;line-gap-override:0%}
@font-face{font-family:"duality-steel-fb";src:local("Impact");size-adjust:97.5%;ascent-override:108.1%;descent-override:24.5%;line-gap-override:0%}
@font-face{font-family:"gastromond-fb";src:local("Georgia");size-adjust:117.6%;ascent-override:97.1%;descent-override:20.6%;line-gap-override:0%}

:root{
  /* ---- the palette, and only the palette ----------------------------
     Adobe Color "FENIAN'S UPDATED", 20 September. Source of truth:
     C:\fenians\LOGOS\AdobeColor FENIAN'S UPDATED.ase (six swatches).
     Every other colour on this sheet is one of these six, an alpha of
     one of them. Nothing else goes in. */
  --c-green:#058646; --c-cream:#f2e2d3; --c-teal:#226f7c;
  --c-orange:#b04900; --c-red:#740001; --c-black:#1f1d1d;

  /* Role names the stylesheet already speaks, aliased onto the six. The
     old three-step ramps (gold/bright/deep, green/glass/deep) collapse to
     one swatch each; the names stay so nothing downstream has to move. */
  --cream:var(--c-cream); --cream-2:var(--c-cream); --paper:var(--c-cream);
  --gold:var(--c-orange); --gold-bright:var(--c-orange); --gold-deep:var(--c-orange);
  --green:var(--c-green); --green-glass:var(--c-green); --green-deep:var(--c-green);
  --ruby:var(--c-red); --ruby-bright:var(--c-red);
  --lead:var(--c-black); --came:var(--c-teal); --oak:var(--c-teal);   /* both unused */
  --night:var(--c-black); --ink:var(--c-black);
  /* The box surface. Opie, 20 September: no box on this site is black or
     grey — the black is the page behind them and nothing else. Teal is the
     swatch with no other job, and it is the one cool value in a warm set. */
  --stock:var(--c-teal);
  --smoke:rgba(242,226,211,.66); --smoke-2:rgba(242,226,211,.4);

  --f-wood:"bourbon","bourbon-fb",Impact,sans-serif;          /* set oblique on .wood */
  --f-wood-lines:"bourbon-lines","bourbon-lines-fb",Impact,sans-serif;
  --f-tape:"broadsheet","broadsheet-fb",Georgia,serif;        /* tape captions + the nav */
  --f-sub:"duality-steel","duality-steel-fb",Impact,sans-serif;  /* sub- and pre-headings */
  --track-wood:.045em;                              /* Bourbon sets tight; open it up */
  --f-fat:"gastromond","gastromond-fb",Georgia,serif;
  --f-did:"essonnes-headline",Georgia,serif;
  --f-body:"mrs-eaves",Georgia,serif;
  --f-sc:"mrs-eaves-roman-small-caps","mrs-eaves",Georgia,serif;
  --f-type:"de-walpergens-pica","Courier New",monospace;
  --f-num:"ratiomodern","Arial Narrow",sans-serif;
  --f-item:"broadsheet","broadsheet-fb",Georgia,serif;        /* menu item names (Opie, 21 Sep) */

  --max:1360px; --sh-w:6px; --rot-max:1.45; --pat-w:160px; --pat-rot-max:15; --pat-opacity:.3; --pat-src:url("../img/tex/seamless-01-dark.svg"); --gut:clamp(18px,3vw,44px); --nav-h:74px;
  --ease:cubic-bezier(.22,.8,.26,1); --snap:cubic-bezier(.2,1.4,.4,1); --out:cubic-bezier(.16,1,.3,1);
}

*,*::before,*::after{box-sizing:border-box}
/* Shift+Tab scrolls the stop it lands on to the top of the scrollport, which is
   under the fixed .nav (height --nav-h). Measured 23 Sep: pub-life's "Back to
   About Fenian's" and the Events hero pause button landed completely behind the
   bar, focus ring and all, on four pages at 1440 and three at 390 (WCAG 2.4.11).
   scroll-padding shrinks the scrollport for scroll-into-view only — it moves no
   box and changes no page height. It also lands the skip link's #main and every
   "Book a table" #book below the bar instead of under it. */
html{-webkit-text-size-adjust:100%;scroll-padding-top:calc(var(--nav-h) + 16px)}
body{margin:0;background:var(--night);color:var(--cream);font:400 19px/1.6 var(--f-body);overflow-x:hidden;position:relative}
/* body::before was a fixed full-viewport dirt-03 layer blended with SCREEN. The
   Dirt-01 art is pure black with an alpha mask, and screen(backdrop, black) is the
   backdrop, so it painted nothing on any page and the compositor carried it on
   every scroll. Cut 22 Sep, pixel-diffed at five scroll positions on four pages:
   0 changed pixels. Verbatim in docs/REMOVED-2026-09-22.md. */
body::after{content:"";position:fixed;inset:0;z-index:0;pointer-events:none;background:radial-gradient(120% 90% at 50% 40%,transparent 50%,rgba(0,0,0,.55))}
body > *,body > main > *{position:relative;z-index:1}
/* <main> wraps each page's body (build.py page(), verify 21 Sep: the skip link's
   target and the page's main landmark). It must not become a stacking context,
   or the Store's drawer (z 95) would sit inside it, under the nav (z 90); static,
   its children stack against the nav, overlay and drawer exactly as they did as
   body's own children. No ring round the whole page when the skip link lands. */
body > main{position:static;z-index:auto}
main:focus{outline:none}
/* #book (the footer) carries tabindex="-1" since 22 Sep so a click on any
   "Book a table" hands the keyboard its place; measured before, focus fell
   back to <body> and the next Tab restarted at the top of the page. Same
   treatment as <main> above: a destination, not a control, so no ring. */
.footer:focus{outline:none}
/* The skip link: first stop on every page, out of sight until a keyboard lands
   on it; then it sits over the nav's top-left corner. */
.skip{position:fixed;left:var(--gut);top:10px;z-index:120;translate:0 calc(-100% - 24px);
  font-family:var(--f-tape);text-transform:uppercase;letter-spacing:.1em;font-size:14px;line-height:1;
  background:var(--c-cream);color:var(--c-black);padding:12px 18px;border:2px solid var(--c-black);
  box-shadow:var(--sh-w) var(--sh-w) 0 var(--c-orange)}
.skip:focus{translate:none}
.skip:focus-visible{outline-color:var(--c-cream)}   /* it sits over the hero: the cream ring, as the nav's */
img{max-width:100%;height:auto;display:block}
a{color:inherit;text-decoration:none}
ul{margin:0;padding:0;list-style:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
:focus-visible{outline:3px solid var(--gold-bright);outline-offset:3px}
/* WCAG 1.4.11 (verify, 21 Sep): the orange ring measured 1.05-2.4:1 wherever it
   is drawn over a coloured box (teal, green, red, and the boxes' own orange
   shadows) and 2.6-3:1 over the hero footage. There it is cream, as the pinned
   quote's toggle already was; it stays orange on the page black (3.0:1) and on
   the paper and cream stock (4.8:1). */
.shard :focus-visible,.shard:focus-visible,.glass :focus-visible,.drawer :focus-visible,
.nav :focus-visible,[data-hero-video] :focus-visible,.overlay :focus-visible{outline-color:var(--c-cream)}
.shard.cream :focus-visible,.paper :focus-visible{outline-color:var(--gold-bright)}
::selection{background:var(--ruby);color:var(--cream)}
p{margin:0 0 1.1em}

/* ---------- type ---------- */
/* Bourbon Oblique for the face, Bourbon Oblique Lines for the drop — the two
   layers of the same family, so they set and wrap identically and the drop is
   just the same string behind the face. Opie, 20 September.

   The drop is FLAT --c-orange. No opacity, no blend, no mix, no second
   text-shadow. If it does not measure #b04900 on screen, something has been
   added that should not be there.

   Offset: --drop-o. The page heroes keep the full --sh-w; every other
   headline runs at a third of it, which is what Opie asked for after the
   6px drop started crowding the eyebrows and body copy under it. */
.wood{--face:var(--cream);--drop-o:var(--sh-w);
  font-family:var(--f-wood);letter-spacing:var(--track-wood);font-style:italic;font-weight:400;text-transform:uppercase;line-height:.88;letter-spacing:var(--track-wood);position:relative;display:inline-block;color:var(--face);margin:0}
.wood::before{content:attr(data-text);position:absolute;left:var(--drop-o);top:var(--drop-o);width:100%;z-index:-1;
  font-family:var(--f-wood-lines);font-style:italic;color:var(--c-orange);opacity:1}
.wood.ink{--face:var(--c-black)}
/* everything that is not a page hero */
.h-md,.h-sm,.card .wood,.break .wood,.sec-head .wood{--drop-o:2px}
.type{font-family:var(--f-type);text-transform:uppercase;letter-spacing:.08em;font-size:14px;line-height:1.3}
h1,h2,h3,h4{margin:0}
.h-md{font-size:clamp(32px,4.4vw,64px)}
.h-sm{font-size:clamp(24px,2.6vw,36px)}
.kick{font-family:var(--f-sub);font-size:clamp(22px,2.6vw,34px);letter-spacing:.03em;color:var(--cream);display:block;margin-bottom:6px}
.lede{font-size:clamp(19px,1.6vw,23px);color:var(--smoke);max-width:56ch;line-height:1.5}
.wrap{width:100%;max-width:var(--max);margin:0 auto;padding:0 var(--gut)}
.sec{padding:clamp(70px,9vw,140px) 0;position:relative;overflow-x:clip}
/* overflow-x:clip on every section (integration, 21 Sep): whatever hangs past a
   box (the Blog photo's EST stamp, right:-30px; a tape at left:-20px) stops at the
   viewport edge instead of widening a phone's layout (the Blog laid out 398-405px
   wide at 390). fx-boxes already added the same clip at runtime (.fx-bx-clipx) to
   any section holding a box group; this makes it true with no JS, reduced motion and
   ?static too. clip, not hidden: no scroll container, so position:sticky inside (the
   calendar detail, the post glossary) still works, and vertical overhangs stay. */
/* The section head owns its seeded tilt (it is in ROT_SEL). It used to reach it
   only through .rv, whose resting transform is translateY(0) rotate(--rot);
   the motion pass took .sec-head off .rv (fx-type reveals the title instead of
   the block rising), so the rest state is stated here. Same angle as before. */
.sec-head{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;flex-wrap:wrap;margin-bottom:clamp(30px,4vw,60px);transform:rotate(var(--rot,0deg))}
.mark{position:relative;white-space:nowrap}
.mark::after{content:"";position:absolute;left:-2%;right:-3%;bottom:.04em;height:.28em;background:var(--gold);z-index:-1;transform:rotate(var(--rot,0deg));opacity:.9}
.mark.green::after{background:var(--green-glass)}

/* ---------- collage bits ---------- */
.tape{display:inline-block;font-family:var(--f-tape);text-transform:uppercase;letter-spacing:.06em;font-size:13px;line-height:1;background:rgba(242,226,211,.88);color:var(--ink);padding:9px 16px;transform:rotate(var(--rot,0deg));box-shadow:0 2px 6px rgba(0,0,0,.35);position:relative;z-index:2}
.tape::before,.tape::after{content:"";position:absolute;top:0;bottom:0;width:8px;background:repeating-linear-gradient(45deg,transparent 0 3px,rgba(0,0,0,.08) 3px 5px)}
.tape::before{left:0}.tape::after{right:0}
.tape.gold{background:var(--gold)}
.tape.ruby{background:var(--ruby);color:var(--cream)}
.tape.green{background:var(--green);color:var(--cream)}
.stamp{width:118px;height:118px;border-radius:50%;border:3px double var(--ruby-bright);color:var(--ruby-bright);display:grid;place-items:center;text-align:center;font-family:var(--f-type);text-transform:uppercase;font-size:11px;letter-spacing:.14em;line-height:1.1;transform:rotate(var(--rot,0deg));opacity:.92;position:relative;padding:12px;overflow:hidden}
/* .stamp::after was dirt-05 blended with SCREEN over black art: invisible. Cut
   22 Sep (docs/REMOVED-2026-09-22.md). dirt-05.png is now referenced by nothing. */
.stamp b{font-family:var(--f-wood);letter-spacing:var(--track-wood);font-size:30px;display:block;letter-spacing:.02em}
.sticker{display:inline-grid;place-items:center;text-align:center;padding:14px 18px;background:var(--gold-bright);color:var(--cream);font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-size:22px;line-height:.95;border:2px solid var(--lead);box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead);transform:rotate(var(--rot,0deg))}
.sticker.ruby{background:var(--ruby);color:var(--cream)}

/* ---------- shard panels ---------- */
.shard{position:relative;background:var(--stock);color:var(--cream);--smoke:var(--c-cream);border:2px solid var(--lead);box-shadow:var(--sx,var(--sh-w)) var(--sy,var(--sh-w)) 0 var(--sh,var(--gold));transform:rotate(var(--rot,0deg))}
/* No image here since 22 Sep: the base rule's dirt-07 was blended with SCREEN over
   black art, i.e. invisible on every .shard on the site. The rule stays because the
   two modifiers below DO paint (multiply) and inherit its box, position, size and
   repeat. Give any new modifier its own background-image. */
.shard::after{content:"";position:absolute;inset:0;pointer-events:none;opacity:.28;background-position:center;background-size:900px;background-repeat:repeat;mix-blend-mode:multiply}
/* :where() so this carries no specificity — a tape or sticker that sets its
   own z-index keeps it. At (0,1,1) this beat .tape{z-index:2} and every
   label overhanging a photo slid behind it. */
:where(.shard) > :where(:not(.pat)){position:relative;z-index:1}

/* seamless shamrock pattern — one layer per box, no image inside.
   .pat is injected by site.js (see PAT_SEL); it clips itself so the box
   can keep overflow:visible and its stickers can still peek out.

   The tile is the black copy of the shamrock, blended soft-light.
   Soft-light shifts lightness and leaves hue alone, so the motif comes out a
   shade of whatever the box already is — lighter green on green, lighter ruby
   on ruby — instead of a grey stamped on top.

   One flat file drawn with background-image. It was written as one file under
   a CSS mask first and that worked, but mask-image on an SVG is newer than
   this site needs to be, and where it is missing the layer vanishes completely
   with nothing to see. Knobs: --pat-w, --pat-opacity, --pat-src. */
.has-pat{position:relative}
.pat{position:absolute;inset:0;z-index:0;overflow:hidden;pointer-events:none;border-radius:inherit}
.pat::before{content:"";position:absolute;inset:-60%;
  background:var(--pat-src) 0 0/var(--pat-w) auto repeat;
  transform:rotate(var(--pat-rot,0deg));
  mix-blend-mode:soft-light;opacity:var(--pat-opacity)}
/* Direction. Every stock Opie set reads as a motif a shade DARKER than the
   field. So the dark copy is the only one used; there is no light copy any
   more. Strength varies by stock: see the two overrides under this rule. */
/* Strength. Soft-light bites far harder on a light field than a dark one — the
   same opacity that is a whisper on the green panel is mottling on the paper —
   so the pale stock runs at about a third. */
/* Dark saturated stock carries it heavier than anything else at the same
   number — the shaded motif goes almost black. Opie took 25% off the green and
   the red; the teal box stock is the same condition and is eased with them.
   This rule sits FIRST: it and the pale rule below have equal specificity, so
   the pale stock has to win on source order or a cream shard takes .225. */
.shard,.grn,.rby,.shard.green,.shard.ruby{--pat-opacity:.225}
.cream,.gold,.paper,.card{--pat-opacity:.11}
/* Lift the content off the pattern layer. Written with :where() so this
   carries ZERO specificity: a tape, sticker or stamp that sets its own
   z-index keeps it. Without that, this rule beat .tape{z-index:2} and every
   label that overhangs a photo slid behind it — the crew cards were the
   visible case, but it applied to every tape and sticker on the site. */
:where(.has-pat) > :where(:not(.pat)){position:relative;z-index:1}
.shard.ruby{--sh:var(--ruby)}
.shard.green{--sh:var(--green-glass)}
.shard.cream{background:var(--paper);color:var(--ink);--sh:var(--lead)}
.shard.cream::after{background-image:url("../img/tex/dirt-01.webp");mix-blend-mode:multiply;opacity:.35}
.shard.gold{background:var(--gold);color:var(--cream);--sh:var(--lead)}
/* dirt-07 spelled out here since 22 Sep: it used to be inherited from .shard::after,
   which no longer carries an image. This is the one place on the site the file shows. */
.shard.gold::after{background-image:url("../img/tex/dirt-07.webp");mix-blend-mode:multiply;opacity:.25}
.shard.grn{background:var(--green);--sh:var(--gold)}
.shard.rby{background:var(--ruby);--sh:var(--gold-bright)}

/* glass — frosted, tinted, stained */
.glass{position:relative;color:var(--cream);background:linear-gradient(135deg,rgba(176,73,0,.22),rgba(5,134,70,.24) 55%,rgba(116,0,1,.18)),rgba(34,111,124,.34);-webkit-backdrop-filter:blur(16px) saturate(1.3);backdrop-filter:blur(16px) saturate(1.3);border:1px solid rgba(242,226,211,.42);box-shadow:inset 0 1px 0 rgba(242,226,211,.35),inset 0 -1px 0 rgba(0,0,0,.35),var(--sh-w) var(--sh-w) 0 rgba(31,29,29,.7)}
/* -faint: the 1280px copy webimg.py writes. The mosaic is drawn here at 16%
   opacity through `overlay`, so the full 1920px plate was 8x the file for a
   difference no one can see (SSIM 0.98 at full strength, far above that at 16%). */
.glass::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.16;mix-blend-mode:overlay;background:url("../img/tex/glass-brown-faint.webp") center/900px}
/* .glass::after was dirt-09 blended with SCREEN over black art: invisible. Cut
   22 Sep (docs/REMOVED-2026-09-22.md). dirt-09 still shows on .prod .art, multiply. */
:where(.glass) > :where(:not(.pat)){position:relative;z-index:1}
.glass.green{background:linear-gradient(135deg,rgba(5,134,70,.28),rgba(5,134,70,.42)),rgba(34,111,124,.34)}
.glass.green::before{background-image:url("../img/tex/glass-green-faint.webp")}
.glass.amber{background:linear-gradient(135deg,rgba(176,73,0,.32),rgba(116,0,1,.22)),rgba(34,111,124,.34)}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;gap:12px;font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-size:22px;letter-spacing:.05em;line-height:1;color:var(--cream);background:var(--gold-bright);padding:16px 26px 14px;border:2px solid var(--lead);box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead);transition:transform .15s var(--snap),box-shadow .15s;position:relative}
.btn:hover{transform:translate(-2px,-2px);box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead)}
.btn:active{transform:translate(4px,4px);box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead)}
.btn--ghost{background:transparent;color:var(--cream);border-color:var(--cream);box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold)}
.btn--ghost:hover{box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold)}
.btn--sm{font-size:18px;padding:12px 18px 10px;box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead)}
.link{font-family:var(--f-type);text-transform:uppercase;letter-spacing:.14em;font-size:14px;color:var(--cream);border-bottom:2px solid var(--gold-bright);padding-bottom:2px}
.link:hover{color:var(--cream);border-color:var(--cream)}

/* ==========================================================================
   NAV
   ========================================================================== */
.nav{position:fixed;top:0;left:0;right:0;z-index:90;height:var(--nav-h);display:flex;align-items:center;transition:.35s}
.nav.is-solid{background:rgba(31,29,29,.88);border-bottom:2px solid var(--lead);box-shadow:0 var(--sh-w) 0 var(--gold)}
/* The open overlay is taller than a phone (and a 768 tablet), so it scrolls, and
   at the top of a page the nav is clear: the tiles slid under the logo and it
   printed over them. The nav wears its own scrolled bar while the menu is open
   (the same three values; fx.css html.hv-nav repeats them too). */
.menu-open .nav{background:rgba(31,29,29,.88);border-bottom:2px solid var(--lead);box-shadow:0 var(--sh-w) 0 var(--gold)}
.nav .wrap{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:20px}
.nav-brand{display:flex;align-items:center;min-width:0}
.nav-brand img{height:42px;width:auto;max-width:100%;filter:drop-shadow(0 2px 5px rgba(0,0,0,.6))}
.nav-links{display:flex;gap:6px;justify-content:center}
.nav-links a{font-family:var(--f-tape);font-size:15px;letter-spacing:.1em;text-transform:uppercase;color:var(--cream);padding:9px 12px;border:2px solid transparent;transition:.15s}
.nav-links a:hover{border-color:var(--cream);transform:rotate(var(--rot,0deg))}
.nav-links a.is-current{background:var(--cream);color:var(--ink);border-color:var(--lead);box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold);transform:rotate(var(--rot,0deg))}
.nav-right{display:flex;justify-content:flex-end;align-items:center;gap:12px}
/* The properties are named one by one because `all` also tweens `visibility`,
   and the bag drawer's close button wears this same class. .drawer.is-open
   flips that button's inherited visibility; a discrete transition only flips at
   the half-way point, so the focus() call openDrawer() makes in the same task
   landed on a button still computing visibility:hidden and did nothing
   (measured 23 Sep: activeElement stayed on <body>). ?static hid it for a whole
   pass because html.static kills transitions. Only :hover animates here,
   transform and box-shadow, so naming the list moves nothing. */
.burger{width:46px;height:46px;border:2px solid var(--cream);display:grid;place-items:center;background:var(--c-orange);box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold);transition:transform .15s,box-shadow .15s,background-color .15s,border-color .15s,color .15s}
.burger:hover{transform:translate(-1px,-1px);box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold)}
.burger span{display:block;width:20px;height:3px;background:var(--cream);position:relative;transition:.3s}
.burger span::before,.burger span::after{content:"";position:absolute;left:0;width:20px;height:3px;background:var(--cream);transition:.3s}
.burger span::before{top:-7px}.burger span::after{top:7px}
.menu-open .burger span{background:transparent}
.menu-open .burger span::before{top:0;transform:rotate(45deg)}
.menu-open .burger span::after{top:0;transform:rotate(-45deg)}
.overlay{position:fixed;inset:0;z-index:80;background:rgba(31,29,29,.94);padding:calc(var(--nav-h) + 20px) var(--gut) 40px;opacity:0;visibility:hidden;transition:.35s;overflow:auto}
/* The texture is on .menu-open, not on .overlay, since 22 Sep. The CLOSED overlay
   sits in every page's markup, so the old rule downloaded a 1.12 MB stained-glass
   plate on every first load for a layer nobody sees until they tap the burger — a
   third of the whole landing page. Same picture, same 12%, fetched when the menu
   opens; it is the faint 1280px copy because 12% cannot tell the difference. */
.overlay::before{content:"";position:absolute;inset:0;background-position:center;background-size:cover;opacity:.12;pointer-events:none}
.menu-open .overlay::before{background-image:url("../img/tex/glass-brown-faint.webp")}
.menu-open .overlay{opacity:1;visibility:visible;transition:opacity .35s,visibility 0s}   /* visible at once on the way in, so site.js can put focus on the first tile; it still fades */
.overlay .wrap{display:grid;grid-template-columns:repeat(6,1fr);gap:22px;grid-auto-rows:minmax(140px,auto);position:relative}
.ov-tile{padding:24px;display:flex;flex-direction:column;justify-content:flex-end;opacity:0;transition:transform .45s var(--snap),opacity .4s}
.ov-tile{transform:translateY(30px) rotate(var(--rot,0deg))}
.menu-open .ov-tile{transform:translateY(0) rotate(var(--rot,0deg));opacity:1}
.menu-open .ov-tile:nth-child(2){transition-delay:.05s}.menu-open .ov-tile:nth-child(3){transition-delay:.1s}.menu-open .ov-tile:nth-child(4){transition-delay:.15s}
.menu-open .ov-tile:nth-child(5){transition-delay:.2s}.menu-open .ov-tile:nth-child(6){transition-delay:.25s}.menu-open .ov-tile:nth-child(7){transition-delay:.3s}
.ov-tile .n{font-family:var(--f-wood);letter-spacing:var(--track-wood);font-size:clamp(34px,4.6vw,66px);text-transform:uppercase;line-height:.9}
.ov-tile .s{font-family:var(--f-type);font-size:12px;letter-spacing:.1em;text-transform:uppercase;opacity:.8;margin-top:8px}
.ov-tile .idx{position:absolute;top:14px;left:18px;font-family:var(--f-type);font-size:12px;letter-spacing:.2em}
/* B12, 21 Sep (Opie's marked-up screenshot: "five full sized boxes and the two
   half sized"). Desktop, 6 columns: About 2x2, Menu / Events / Store 2 each, Blog
   and Home 1 each, then ONE Find us + Hours box across the whole grid. At <=1100px
   (tablet and phone) 2 columns: full tiles span both, the two half tiles one each,
   so Blog and Home stay side by side at every width. */
.ov-tile.big{grid-column:span 2;grid-row:span 2}.ov-tile.wide{grid-column:span 2}.ov-tile.half{grid-column:span 1}
/* a half tile is narrow enough on a phone for its line to wrap and push the
   name up into the idx label: keep the name clear of it */
.ov-tile.half{padding-top:40px}
.ov-tile.info{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;align-items:start;justify-content:stretch;font-size:15px;line-height:1.5}
/* Find us beside Hours, split by the same rule that sits under "Book a table"
   (.link: 2px solid --gold-bright). Vertical between the columns; on a phone the
   hours rows do not fit beside the address, so the two stack and the rule turns
   horizontal (RESPONSIVE, <=560px). */
.ov-tile.info .ov-col{min-width:0;padding-right:clamp(18px,3vw,40px)}
.ov-tile.info .ov-col + .ov-col{border-left:2px solid var(--gold-bright);padding-left:clamp(18px,3vw,40px);padding-right:0}
.ov-tile.info strong{font-family:var(--f-type);letter-spacing:.14em;text-transform:uppercase;font-size:12px;display:block;margin-bottom:6px;color:var(--cream)}

/* ==========================================================================
   HERO — the window you light
   ========================================================================== */
.hero{min-height:min(100svh,980px);position:relative;overflow:hidden;isolation:isolate;padding:calc(var(--nav-h) + 20px) 0 70px;display:grid;grid-template-columns:minmax(0,1fr);align-items:center}
.hero-mosaic{position:absolute;left:-10%;right:-10%;bottom:-20%;height:60%;z-index:-1;opacity:.35;background:url("../img/tex/glass-brown.webp") center/1200px;transform:rotate(var(--rot,0deg));mix-blend-mode:overlay;-webkit-mask:linear-gradient(transparent,#000 40%);mask:linear-gradient(transparent,#000 40%)}
.hero-grid{display:grid;grid-template-columns:1.05fr .95fr;gap:0;align-items:center;position:relative}
.stack{position:relative;width:min(640px,92%);aspect-ratio:1116/944;margin:0 auto;transform-style:preserve-3d;transform:perspective(1100px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));transition:transform .12s linear;will-change:transform;--mx:50%;--my:45%}
.stack.is-idle{transition:transform 1.2s var(--out)}
.stack > img,.stack > .layer{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;pointer-events:none}
.stack .dark{filter:brightness(.38) saturate(.55) contrast(1.1);transform:translateZ(0)}
.stack .lit{transform:translateZ(38px);-webkit-mask:radial-gradient(circle 190px at var(--mx) var(--my),#000 0,#000 45%,transparent 100%);mask:radial-gradient(circle 190px at var(--mx) var(--my),#000 0,#000 45%,transparent 100%);filter:drop-shadow(0 0 30px rgba(176,73,0,.55))}
.stack .sheen{transform:translateZ(60px);background:linear-gradient(115deg,transparent 30%,rgba(242,226,211,.28) 46%,rgba(242,226,211,.05) 50%,transparent 62%);background-size:220% 100%;background-position:calc(var(--mx) * 1.4) 0;-webkit-mask:url("../img/web/logo-mask-700.webp") center/contain no-repeat;mask:url("../img/web/logo-mask-700.webp") center/contain no-repeat;opacity:.8}
.stack .shadow{position:absolute;left:8%;right:8%;bottom:-8%;height:14%;background:radial-gradient(50% 50% at 50% 50%,rgba(0,0,0,.7),transparent 70%);transform:translateZ(-40px)}
.hero-copy{position:relative;z-index:2;margin-left:-8%}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.hero-copy .wood{font-size:clamp(64px,9.2vw,150px);transform:rotate(var(--rot,0deg));transform-origin:left bottom;display:block;width:max-content}
.hero-copy .line1{margin-left:6%}
.hero-copy .line2{margin-left:14%}
.hero-copy .tape{position:relative;display:block;width:max-content;max-width:100%;margin:26px auto 0;--rot:-1.45deg;font-size:16px;padding:12px 20px;text-align:center}
.hero-cta{display:flex;gap:18px;flex-wrap:wrap;margin-top:34px;margin-left:6%}
.btn{transform:rotate(var(--rot,0deg))}
.hero-cta .btn:hover{transform:translate(-2px,-2px) rotate(0)}
.hero-meta{position:absolute;bottom:26px;left:0;right:0;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;padding:0 var(--gut);font-family:var(--f-type);font-size:12px;letter-spacing:.2em;text-transform:uppercase;color:var(--smoke)}   /* was --smoke-2: 2.9:1 over the B10 hero footage */
.hero-meta span:nth-child(2){grid-column:2;text-align:center}

/* ==========================================================================
   BREAKS — mosaic band, frosted panel overlapping both neighbours
   ========================================================================== */
.break{position:relative;height:clamp(240px,40vh,420px);margin:-70px 0;z-index:3;pointer-events:none}
.break .band{position:absolute;left:-6%;right:-6%;top:0;bottom:0;background:url("../img/tex/glass-brown.webp") center/1400px;transform:rotate(var(--rot,0deg));border-top:3px solid var(--lead);border-bottom:3px solid var(--lead);box-shadow:0 0 0 var(--sh-w) var(--gold-deep),0 24px 60px rgba(0,0,0,.6);will-change:transform}
.break.green .band{background-image:url("../img/tex/glass-green.webp")}
/* Phones take the 1280px copy of both mosaics (22 Sep). The band draws its
   texture at a 1400px tile inside a box that is 437px wide at 390, so under a
   third of the tile is ever on screen and the full 1920px plate is pixels the
   device cannot show: -183 KB (brown) and -139 KB (green) on the connection
   that can least afford them. The Blog's .hero-mosaic goes with them — it is
   the same file, drawn at 35% through `overlay` behind a gradient mask, and on
   a phone it was that page's LCP element at 14.5s. Desktop is untouched.
   This must stay AFTER the two rules above and after .hero-mosaic. */
@media(max-width:860px){
  .break .band,.hero-mosaic{background-image:url("../img/tex/glass-brown-faint.webp")}
  .break.green .band{background-image:url("../img/tex/glass-green-faint.webp")}
}
/* B5: the band overhangs .break by 6% a side, which widened a 390px phone to
   413px of scrollable page even under body{overflow-x:hidden}. THIS is the
   only copy of the rule (22 Sep: fx.css stated it identically; site.css keeps
   it because it covers every page whether or not fx.css loads). clip, not
   hidden: no scroll container, and the shadows still hang past vertically. */
.break{overflow-x:clip}
.break .band::after{content:"";position:absolute;inset:0;background:url("../img/tex/dirt-11.webp") center/1200px repeat;mix-blend-mode:multiply;opacity:.4}
.break .brk-panel{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) rotate(var(--rot,0deg));width:min(760px,86%);padding:clamp(24px,4vw,48px);pointer-events:auto;text-align:center}
.break .brk-panel .wood{font-size:clamp(36px,5.4vw,80px)}
.break .brk-panel p{margin:10px 0 0;color:var(--cream);font-family:var(--f-did);font-style:italic;font-size:clamp(18px,2vw,26px)}
/* B5, 21 Sep: the tapes sit on the plaque's straight top and bottom runs, clear
   of the notched corners (the outer cut reaches .66 x --plq-b along the edge),
   and rest inside the 1.45deg ceiling. site.js never re-rolls them — their
   .break owns the seeded angle — so these ARE the resting angles (were -4 / 3). */
.break .brk-panel .tape{position:absolute;top:-16px;left:calc(var(--plq-b,64px) * .9);--rot:-1.4deg}
.break .brk-panel .tape.r{left:auto;right:calc(var(--plq-b,64px) * .9);top:auto;bottom:-16px;--rot:1.2deg}

/* ==========================================================================
   COLLAGE GRID (home) — imperfect boxes
   ========================================================================== */
.collage{display:grid;grid-template-columns:repeat(12,1fr);grid-auto-rows:minmax(120px,auto);gap:24px;position:relative}
.collage > *{transition:transform .35s var(--snap)}
/* The lift hovers (here, .ev and .prod) are for a pointer that hovers. On
   touch a tap leaves :hover stuck until the next tap elsewhere, so the box
   loses its seeded tilt and hangs 6px up. They were dead after the old
   rise-in reveal; the motion pass (21 Sep) brought them back. */
@media (hover:hover){.collage > *:hover{transform:rotate(0) translateY(-6px);z-index:4}}
.c-tile{padding:58px 26px 26px;display:flex;flex-direction:column;justify-content:flex-end;min-height:220px;overflow:hidden;isolation:isolate}
.c-tile img.bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;filter:saturate(.85) contrast(1.05)}
.c-tile .scrim{position:absolute;inset:0;z-index:0;background:linear-gradient(180deg,rgba(31,29,29,.05) 30%,rgba(31,29,29,.88))}
.c-tile .t{font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-size:clamp(28px,3vw,44px);line-height:.9}
.c-tile .d{font-size:16px;color:var(--smoke);margin-top:8px;max-width:34ch;line-height:1.45}
.c-tile .tape{position:absolute;top:14px;left:14px;z-index:2}
.c-tile .sticker{position:absolute;top:14px;right:14px;z-index:2;font-size:16px;padding:10px 12px}
.c-tile.cream .d{color:rgba(31,29,29,.72)}
.c-tile.gold .d{color:rgba(242,226,211,.82)}
.pour{margin:0;padding:0;font-family:var(--f-num);font-weight:500;text-transform:uppercase;letter-spacing:.06em}
.pour li{display:flex;justify-content:space-between;gap:12px;padding:8px 0;border-bottom:2px dotted rgba(242,226,211,.28);font-size:20px}
.pour li:last-child{border:0}
.pour b{font-weight:800;color:var(--cream)}
.pour small{font-family:var(--f-type);font-size:11px;letter-spacing:.14em;opacity:.7}
.hours{font-family:var(--f-wood);font-style:italic;font-weight:400;text-transform:uppercase;letter-spacing:var(--track-wood);font-size:17px;display:grid;grid-template-columns:auto 1fr;gap:3px 18px;margin:8px 0 0}
.hours dt{color:var(--ruby)}.hours dd{margin:0}
/* TWO SETS OF HOURS, side by side and labelled. A pub that prints one set
   sends somebody to the door at half nine for a plate the kitchen stopped
   serving at nine; this is the fix, and it is why the kitchen column comes
   first. Stacks under 600px, kitchen still first. */
.hours-card{max-width:780px;margin:0 auto 46px}
.hours-cols{display:grid;grid-template-columns:1fr 1fr;gap:10px clamp(20px,4vw,52px);margin-top:10px}
.hours-h{display:block;font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;
  font-size:15px;color:var(--c-orange)}
.hours-card .hours{margin-top:4px}
.hours-late{margin:18px 0 0;max-width:58ch;color:rgba(26,21,16,.78);font-size:15px;line-height:1.5}
.hours-late b{color:var(--ruby)}
@media(max-width:600px){.hours-cols{grid-template-columns:1fr;gap:14px}}
/* The kitchen line where there is room for a sentence and not a table:
   the footer and the burger overlay. */
.foot-kitchen,.hours-kit{margin:10px 0 0;font-family:var(--f-body);font-style:normal;
  text-transform:none;letter-spacing:0;font-size:14px;line-height:1.45;color:var(--smoke)}
.big-time{font-family:var(--f-wood);letter-spacing:var(--track-wood);font-size:clamp(44px,4.6vw,72px);text-transform:uppercase;line-height:.9}
.s3{grid-column:span 3}.s4{grid-column:span 4}.s5{grid-column:span 5}.s6{grid-column:span 6}.s7{grid-column:span 7}.s8{grid-column:span 8}.s12{grid-column:span 12}
.r2{grid-row:span 2}.r3{grid-row:span 3}

/* ==========================================================================
   MARQUEE
   ========================================================================== */
.marquee{overflow:hidden;background:var(--ruby);border-top:3px solid var(--lead);border-bottom:3px solid var(--lead);box-shadow:0 var(--sh-w) 0 var(--gold);padding:14px 0;transform:rotate(var(--rot,0deg)) scale(1.04);margin:70px 0 30px;position:relative;z-index:2}
.marquee.green{background:var(--green);transform:rotate(var(--rot,0deg)) scale(1.04);box-shadow:0 calc(var(--sh-w) * -1) 0 var(--gold)}
/* The band is scaled 1.04 so its tilted ends never show a gap, which puts it ~2% past
   the viewport each side; it sits between sections, so this wrapper (build.py, the Blog
   page) clips it at the viewport edge. overflow-x:clip makes no formatting context, so
   the band's margins and its vertical overhang are exactly as before. */
.marquee-clip{overflow-x:clip}
.marquee-track{display:flex;width:max-content;animation:marquee 38s linear infinite}
.marquee.green .marquee-track{animation-direction:reverse}
.marquee.is-paused .marquee-track{animation-play-state:paused}
@media (hover:hover){.marquee:hover .marquee-track{animation-play-state:paused}}   /* a tap leaves :hover stuck on touch, which held it paused */
/* The pause toggle (site.js, WCAG 2.2.2): a tap or click on the band pauses it;
   a keyboard reaches this button, which is out of sight until it has focus. */
.marquee-clip{position:relative}
.mq-toggle{position:absolute;left:var(--gut);top:50%;z-index:3;translate:0 -50%;opacity:0;pointer-events:none;
  font-family:var(--f-tape);text-transform:uppercase;letter-spacing:.1em;font-size:14px;line-height:1;
  background:var(--c-cream);color:var(--c-black);padding:12px 18px;border:2px solid var(--c-black);
  box-shadow:var(--sh-w) var(--sh-w) 0 var(--c-orange)}
.mq-toggle:focus-visible{opacity:1;pointer-events:auto;outline-color:var(--c-cream)}
.marquee span{font-family:var(--f-wood);letter-spacing:var(--track-wood);font-size:clamp(30px,3.6vw,52px);text-transform:uppercase;color:var(--cream);padding:0 22px;display:flex;align-items:center;gap:22px;white-space:nowrap;line-height:1}
/* Box accents. Every little square mark on the site is a shamrock now —
   shamrocks_three_leaf.svg, one flat contrasting colour per placement, no
   border and no box behind it. --mark-sz and --mark-rot are the two knobs.
   Opie, 20 September. The files are pre-coloured rather than masked; see the
   pattern note above for why nothing here leans on mask-image. */
.marquee span::after{content:"";--mark-src:url("../img/brand/shamrock-cream.svg");--mark-sz:32px;--mark-rot:-12deg;
  background:var(--mark-src) center/contain no-repeat;width:var(--mark-sz);height:var(--mark-sz);border:0;
  transform:rotate(var(--mark-rot));flex:none}
@keyframes marquee{to{transform:translateX(-50%)}}

/* ==========================================================================
   THE ROOM
   ========================================================================== */
.room{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(30px,5vw,80px);align-items:center}
.room-img{position:relative;--rot:-1.45deg;padding:0;overflow:visible}
.room-img img{width:100%;height:auto;display:block;filter:saturate(.9)}
.room-img .stamp{position:absolute;right:-30px;bottom:-30px;--rot:1.4deg}
.room-img .tape{position:absolute;left:-20px;top:22px;--rot:-1.4deg}
.room-copy{position:relative}
.room-copy p{color:var(--smoke)}
/* B2, 21 Sep: the Favrile footnote tape under the Chapter one copy. In flow, so it
   can never sit on the paragraphs; line-height opened up because it wraps on a phone. */
.room-copy .tape.fn{margin-top:14px;--rot:-1.2deg;max-width:100%;line-height:1.35}
.room-copy p:first-of-type::first-letter{font-family:var(--f-fat);font-size:4.4em;float:left;line-height:.78;margin:8px 12px 0 0;color:var(--cream)}

/* ==========================================================================
   RUNAROUND — the copy flows round the shape of a picture, not its box.
   About, Chapter one (build.py). Opie, 24 Sep, against Framer's Runaround
   Text Wrap component: "text dynamically wraps around the actual visible
   contours, not bounding boxes."

   It is `float` + `shape-outside:url()`: the browser reads the picture's
   ALPHA CHANNEL and makes the float area that silhouette. So the two
   cut-outs have to be transparent PNGs/WebPs, and they are
   (assets/img/cut/, see the README there). `shape-image-threshold` is how
   opaque a pixel must be to count as solid, which is what keeps a soft edge
   or a drop shadow from pushing the text away; `shape-margin` is the gutter
   the text keeps off the silhouette. Those two are Framer's "Alpha
   Threshold" and "Wrap Padding" under different names.

   TWO PLACES NAME EACH FILE: the <img src> in build.py and the
   shape-outside below. They must stay the same file or the text will wrap
   round one picture while a different one is drawn. There is no way round
   it: shape-outside takes one URL and cannot read the element's own src.

   SAME ORIGIN ONLY. A shape-outside image from another origin is ignored
   without a word and the float goes back to being a rectangle. These are
   ours, served from the same host, so it holds.

   NO PARALLAX ON A RUNAROUND FLOAT. A `translate` moves the picture and not
   the hole the text is keeping for it, so the two come apart. Fig. 2 lost
   its data-px when it moved in here.
   ========================================================================== */
.runaround{max-width:900px;margin:0 auto}
.runaround::after{content:"";display:block;clear:both}      /* the container holds its floats */
/* The footnote flows beside a float like anything else: the last paragraph
   is short and clearing here left a hole under the lamp. */
.runaround .tape.fn{display:inline-block;margin-top:18px}

.ra{
  --ra-w:34%;          /* width, as a share of the column */
  --ra-t:.45;          /* shape-image-threshold: how opaque a pixel must be to be solid */
  --ra-m:22px;         /* shape-margin: the gutter the text keeps off the silhouette */
  width:var(--ra-w);
  height:auto;
  /* MARGINS STAY ZERO. shape-outside:url() maps the picture into the MARGIN
     box, so a margin on one side stretches the silhouette away from the
     picture that is drawn. All the spacing is shape-margin, which inflates
     the shape itself and leaves the mapping alone. */
  margin:0;
  shape-margin:var(--ra-m);
  shape-image-threshold:var(--ra-t);
}
.ra-l{float:left}
.ra-r{float:right}
img.ra{display:block;height:auto}

/* ---- the bleed (Opie, 24 Sep: "slight out of frame") ----
   Each cut-out runs past the edge of the text column into the page gutter, so
   the tram's front end and the lamp's near side sit outside the measure. It is
   a negative margin on the OUTER side, and it has to be: `translate` or
   `position:relative` would move the picture and leave the hole the text is
   keeping for it behind. .sec is overflow-x:clip, so on a narrow desk screen
   the bleed is simply cropped at the viewport instead of widening the page.

   WHAT THAT COSTS, and why it is affordable here. shape-outside:url() maps the
   picture into the MARGIN box, so a negative margin squeezes the silhouette by
   that much. The squeeze is on the bleeding side, where the margin is, and the
   inner side (margin 0) stays pinned to the box edge — which is the side the
   text is actually running down. Measured at 1440: the contour tracks the tram
   within a couple of px of the drawn edge. Widen the bleed much past 10% and
   it will start to show; check it against the picture, not the numbers. */
.ra-trolley{--ra-w:38%;shape-outside:url("../img/cut/trolley-cut.webp");
  margin-right:-8%;margin-top:-18px}
.ra-tiffany{--ra-w:37%;shape-outside:url("../img/cut/tiffany-cut.webp");
  margin-left:-7%;margin-top:-26px}

/* ---- the tape on a cut-out ----
   The house tape, as every framed picture here wears one. It is absolutely
   positioned, so it is outside the float's box and outside the silhouette the
   text is following: the text does not know it is there.

   SO IT HAS TO SIT ON SOLID PICTURE. Hung off the top edge, the way the
   framed shards wear theirs, it would float in the middle of the hole the
   text has left — the top of both these cut-outs is transparent (sky over the
   tram, the gap either side of the lamp's crown). Each is placed at the
   height where its object is widest and hung one tape-thickness past that
   edge, which is inside the shape-margin gutter. Move a tape and check it
   against the silhouette, not against the box.

   The angle is site.js's: .tape is in ROT_SEL, so each one is re-rolled per
   load inside the 1.45 degree ceiling. The values here are the no-JS resting
   tilt, nothing more. */
.ra{position:relative}
.ra > img{width:100%;height:auto;display:block}
.ra .tape{position:absolute;z-index:2;white-space:nowrap}
.ra-trolley .tape{left:-14px;top:42%;--rot:-1.4deg}    /* across the tram's rear end, the inner side */
.ra-tiffany .tape{left:-12px;top:64%;--rot:1.3deg}     /* low on the drum, clear of the horse */

/* ---- LIVE: the copy re-wraps while you move a picture (assets/js/fx-wrap.js) ----
   Everything above is the floor: a float, a silhouette, and no way to move it.
   The engine adds the half of Framer's component Opie asked for by name, the
   one where you drag an object and the text keeps re-wrapping. It cannot be
   CSS: shape-outside only applies to a float, a float sits where the flow puts
   it, and CSS Exclusions never shipped anywhere.

   So under .ra-live the two cut-outs come out of flow and go to absolute,
   measured to exactly where the floats had them, and the text is instead
   broken against a LADDER of zero-content floats down each side, one rung
   every few px, each rung as wide as the objects intrude at that height. The
   browser still does the line breaking; it is just given something to break
   against. The engine writes nothing but rung widths.
   ========================================================================== */
.runaround{position:relative}
/* The copy's own formatting context, built by the engine. display:flow-root
   keeps the first paragraph's top margin from collapsing out through the rung
   ladder, which is what made the rungs an uneven grid and put every width in
   at the wrong height. */
.ra-copy{display:flow-root}
.ra-rung{display:block;width:0;pointer-events:none}
.ra-rung-left{float:left;clear:left}
.ra-rung-right{float:right;clear:right}
.ra-live .ra{position:absolute;z-index:3;float:none;margin:0;shape-outside:none;touch-action:none}
/* The drop cap stops floating and is pinned by the engine instead; see the
   long note in fx-wrap.js. A float cannot be placed above an earlier one, so
   left in the flow it would be shoved to the foot of the rung ladder and the
   paragraph would open on a missing letter. */
/* .ra-live and .room-copy are the SAME element, so a descendant combinator
   between them matches nothing. Written with a space first, this rule never
   applied: the drop cap went on floating, could not be placed above the rung
   ladder in front of it, and landed at the foot of the ladder as a stray
   capital in the middle of the section. */
.ra-live.room-copy p:first-of-type::first-letter,
.ra-live .ra-copy p:first-of-type::first-letter{float:none;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;color:inherit}
.ra-cap{position:absolute;z-index:2;pointer-events:none;
  font-family:var(--f-fat);font-size:4.4em;line-height:.78;color:var(--cream)}
/* No text selection anywhere in the block while something is being dragged. */
.ra-dragging{-webkit-user-select:none;user-select:none}
.ra-dragging .ra{will-change:left,top}

/* ?wrap=debug — Framer's Debug Mode: the rungs the text is breaking against,
   and the picture's own box under its silhouette. */
.ra-debug .ra-rung{background:rgba(176,73,0,.35);outline:1px solid rgba(242,226,211,.35)}
.ra-debug .ra{outline:1px dashed var(--c-cream)}

/* Narrow: a float in a column this size leaves a gutter of text nobody can
   read, so they stack and the silhouette stops mattering. */
@media (max-width:820px){
  .ra{float:none;width:min(78%,340px);margin:26px auto !important;shape-outside:none}
}

/* ==========================================================================
   MENU — printed cards on the paper wall
   ========================================================================== */
/* overflow-x:clip: each card's upper-right shamrock hangs 26px past it, which
   pushed a 390px phone's Menu page out to 407px. clip, not hidden: it makes no
   scroll container, and the marks still hang past the cards vertically. */
.paper{background:var(--paper);color:var(--ink);position:relative;overflow-x:clip}
.paper::before{content:"";position:absolute;inset:0;pointer-events:none;background:url("../img/tex/dirt-01.webp") center/1200px repeat;mix-blend-mode:multiply;opacity:.4}
.paper::after{content:"";position:absolute;inset:0;pointer-events:none;background:url("../img/tex/glass-brown-faint.webp") center/1600px;mix-blend-mode:multiply;opacity:.08}
.paper > *{position:relative;z-index:1}
.paper .kick{color:var(--ruby)}
.paper .link{color:var(--green-deep);border-color:var(--green-deep)}
.card{background:var(--c-cream);color:var(--ink);padding:34px 30px 30px;border:2px solid var(--lead);box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold);position:relative;transform:rotate(var(--rot,0deg))}
.card::before{content:"";position:absolute;inset:8px;border:1px solid var(--gold-deep);pointer-events:none}
.card::after{content:"";position:absolute;inset:0;pointer-events:none;background:url("../img/tex/dirt-03.webp") center/900px repeat;mix-blend-mode:multiply;opacity:.3}
.card > *{position:relative;z-index:1}
.card .corner{position:absolute;width:12px;height:12px;background:var(--ruby);border:2px solid var(--lead);z-index:2}
/* Opie, 20 September: on the menu cards only the upper-right mark turns into
   a shamrock. The other three stay as they are. */
.card .corner.tr{--mark-src:url("../img/brand/shamrock-red.svg");--mark-sz:38px;--mark-rot:16deg;
  width:var(--mark-sz);height:var(--mark-sz);background:var(--mark-src) center/contain no-repeat;border:0;
  transform:rotate(var(--mark-rot))}
.card .corner.tl{top:-8px;left:-8px}.card .corner.tr{top:-28px;right:-26px}.card .corner.bl{bottom:-8px;left:-8px}.card .corner.br{bottom:-8px;right:-8px}
.card .eyebrow{display:block;margin-bottom:6px;font-family:var(--f-sub);letter-spacing:.14em;text-transform:uppercase;font-size:12px;color:var(--green)}
.card .wood{display:block;font-size:clamp(30px,3vw,44px);color:var(--c-teal)}
/* A card note: one line the whole list shares, so five rows do not each
   repeat it (the dressings are all made here and all bottled). Sits under
   the heading, above the list, and reads as the list's own aside. */
.card-note{margin:2px 0 14px;max-width:46ch;color:rgba(26,21,16,.72);font-size:15px;line-height:1.45}
.card-note a{color:inherit;text-decoration:underline;text-underline-offset:3px}
.menu-list{margin:14px 0 0;padding:0}
.menu-list li{display:grid;grid-template-columns:1fr auto;gap:4px 16px;padding:12px 0;border-bottom:2px dotted rgba(31,29,29,.3);align-items:baseline}
.menu-list li:last-child{border:0}
.menu-list .n{font-family:var(--f-item);font-weight:400;text-transform:uppercase;font-size:20px;letter-spacing:.02em;color:var(--green-deep)}
.menu-list .p{font-family:var(--f-fat);font-size:24px;color:var(--ruby)}
.menu-list .d{grid-column:1/-1;font-size:15.5px;color:rgba(31,29,29,.7);line-height:1.45}
.menu-list .badge{display:inline-block;font-family:var(--f-type);font-size:10px;letter-spacing:.14em;text-transform:uppercase;background:var(--ruby);color:var(--cream);padding:3px 7px;margin-left:8px;vertical-align:middle;transform:rotate(var(--rot,0deg))}
.menu-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:36px 28px;align-items:start}
.menu-grid .card:nth-child(1){--rot:-1.45deg}.menu-grid .card:nth-child(2){--rot:1deg;margin-top:34px}.menu-grid .card:nth-child(3){--rot:-.6deg;margin-top:10px}
.menu-two{display:grid;grid-template-columns:1fr 1fr;gap:36px 32px;align-items:start}
.menu-two .card:nth-child(2){--rot:1.2deg;margin-top:40px}
.tabs{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:44px}
.tab{font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-size:22px;padding:12px 20px 10px;border:2px solid var(--lead);color:var(--ink);background:var(--cream);box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead);transition:.15s;transform:rotate(var(--rot,0deg))}
.tab:nth-child(1){--rot:-1.45deg}.tab:nth-child(2){--rot:1.45deg}.tab:nth-child(3){--rot:-1deg}.tab:nth-child(4){--rot:1.45deg}
.tab[aria-selected="true"]{background:var(--green);color:var(--cream);transform:translate(-2px,-2px) rotate(0);box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead)}
.tab:hover{transform:translate(-2px,-2px) rotate(0)}
.panel{display:none;animation:drop .5s var(--snap)}
.panel.is-active{display:block}
/* Per-panel heading, for the no-JS render only: head()'s noscript style opens
   all four panels and hides .tabs, and the panels are labelled by those tab
   buttons and nothing else. display:none here means no pixel moves for anyone
   with scripting on. */
.nojs-h{display:none;font-family:var(--f-sub);font-size:22px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink);margin:0 0 18px}
@keyframes drop{from{opacity:0;transform:translateY(-18px) rotate(var(--rot,0deg))}}

/* ==========================================================================
   EVENTS
   ========================================================================== */
.rail{display:flex;gap:26px;overflow-x:auto;scroll-snap-type:x mandatory;padding:20px var(--gut) 36px;margin:0 calc(var(--gut) * -1);scrollbar-width:thin;scrollbar-color:var(--gold) transparent}
.ev{flex:0 0 min(380px,84vw);scroll-snap-align:start;padding:24px;display:grid;grid-template-columns:auto 1fr;gap:18px;align-items:start}
.ev:nth-child(odd){--rot:-1.45deg}.ev:nth-child(even){--rot:1.45deg;margin-top:18px}
@media (hover:hover){.ev:hover{transform:rotate(0) translateY(-6px)}}
.day{width:78px;background:var(--cream);color:var(--green-deep);text-align:center;padding:10px 0 8px;border:2px solid var(--lead);box-shadow:var(--sh-w) var(--sh-w) 0 var(--ruby);font-family:var(--f-type);text-transform:uppercase;line-height:1;transform:rotate(var(--rot,0deg))}
.day b{display:block;font-family:var(--f-wood);letter-spacing:var(--track-wood);font-size:38px;margin:2px 0}
.day small{font-size:11px;letter-spacing:.2em}
.ev h3{font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-size:28px;line-height:.95;margin:0 0 7px}
.ev .when{font-family:var(--f-sub);letter-spacing:.1em;text-transform:uppercase;font-size:13px;color:var(--cream);display:block;margin-bottom:4px}
.ev p{color:var(--smoke);font-size:15.5px;margin:8px 0 12px;line-height:1.45}
/* minmax(0,1fr) and min-width/min-height:0 on the cells: an aspect-ratio box
   takes its content height (47px) as a minimum and hands it to its width, so
   the grid could not get narrower than 366px and cut the Sunday column and
   the next-month button off a 360 or 375 phone (the .sec clip hides the
   overflow). The ≤520px block shrinks the cell's own padding and figure. */
.cal{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:6px}
/* The week rows exist only to satisfy the WAI-ARIA grid pattern site.js builds
   (role=row around role=gridcell). display:contents takes the box out of layout
   entirely, so every cell is still a direct grid item of .cal and not one pixel
   moves. Never give .cal-row a background, border, padding or shadow:
   display:contents paints none of them. */
.cal-row{display:contents}
/* The week rows exist only to satisfy the WAI-ARIA grid pattern site.js builds
   (role=row around role=gridcell). display:contents takes the box out of layout
   entirely, so every cell is still a direct grid item of .cal and not one pixel
   moves. Never give .cal-row a background, border, padding or shadow:
   display:contents paints none of them. */
.cal-row{display:contents}
.cal .dow{font-family:var(--f-type);letter-spacing:.16em;text-transform:uppercase;font-size:11px;color:var(--cream);text-align:center;padding:6px 0}
.cal .cell{aspect-ratio:1;min-width:0;min-height:0;border:2px solid rgba(242,226,211,.16);padding:8px;position:relative;font-family:var(--f-num);font-weight:500;font-size:17px;color:var(--smoke-2);transition:.15s}
.cal .cell.has{border-color:var(--cream);color:var(--cream);cursor:pointer;background:rgba(242,226,211,.04);box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead)}
.cal .cell.has:hover,.cal .cell.is-sel{background:var(--green);border-color:var(--lead);transform:rotate(var(--rot,0deg));box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold)}
.cal .cell.today{outline:2px dashed var(--gold-bright);outline-offset:-4px}
/* .cal .cell.today is (0,3,0) and beats the house :focus-visible at (0,1,0), so
   without this the today cell drew the same ring focused and unfocused - i.e.
   no focus indicator at all (measured 23 Sep). Same ring its neighbours get;
   unfocused, the dashed marker is unchanged. */
.cal .cell.today:focus-visible{outline:3px solid var(--gold-bright);outline-offset:3px}
/* .cal .cell.today is (0,3,0) and beats the house :focus-visible at (0,1,0), so
   without this the today cell drew the same ring focused and unfocused - i.e.
   no focus indicator at all (measured 23 Sep). Same ring its neighbours get;
   unfocused, the dashed marker is unchanged. */
.cal .cell.today:focus-visible{outline:3px solid var(--gold-bright);outline-offset:3px}
.cal .cell.pad{opacity:.2;border-style:dashed}
.cal .cell .dot{position:absolute;bottom:8px;left:8px;right:8px;font-family:var(--f-type);font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--cream);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cal-wrap{display:grid;grid-template-columns:1.4fr 1fr;gap:44px;align-items:start}
.cal-wrap > *{min-width:0}   /* the column (and the detail panel under it on a phone) follows the calendar down */
.cal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}
.cal-nav{display:flex;gap:10px}
.cal-nav button{width:42px;height:42px;position:relative;border:2px solid var(--cream);display:grid;place-items:center;box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold);font-size:20px}
/* 42px drawn, 44px to a thumb (WCAG 2.5.5). The extra ring is an invisible
   pseudo-element, not padding: padding would grow the bordered square and
   the two arrows are part of the look. inset is measured from the PADDING box,
   which is the border box less its 2px borders, so -1px measured 42x42 and
   bought nothing; -3px is what reaches 1px outside the button. The 10px gap
   leaves 4px between the two expanded areas, so neither can steal the other's
   taps. Same trick would suit any other small control. */
.cal-nav button::after{content:"";position:absolute;inset:-3px}
.cal-nav button:hover{background:var(--gold);color:var(--cream)}
.ev-detail{padding:26px;position:sticky;top:calc(var(--nav-h) + 24px);--rot:1.45deg}
.ev-detail img{width:100%;aspect-ratio:16/10;object-fit:cover;border:2px solid var(--lead);margin-bottom:18px;filter:saturate(.9)}
.ev-detail .eyebrow{font-family:var(--f-sub);letter-spacing:.12em;text-transform:uppercase;font-size:12px;color:var(--cream);margin-bottom:6px;display:block}
.ev-detail .wood{font-size:clamp(30px,3vw,44px)}
/* Contract 7, 21 Sep: an event may carry a line-up (`bill`: slot, name,
   one_liner) and a "learn more" post (`url` + `link`, its label). site.js
   renders both into the detail panel, between the blurb and the Book button;
   an event without them looks exactly as it did. Cream on the box stock, as
   the blurb above them is. */
.ev-bill{margin:0 0 18px;padding:0;list-style:none;border-top:2px dotted rgba(242,226,211,.3)}
.ev-bill li{display:grid;grid-template-columns:auto minmax(0,1fr);gap:3px 14px;padding:10px 0;border-bottom:2px dotted rgba(242,226,211,.3)}
.ev-bill .ev-slot{grid-row:span 2;min-width:3.6em;padding-top:5px;font-family:var(--f-type);font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--c-cream)}
.ev-bill b{font-family:var(--f-wood);font-style:italic;font-weight:400;text-transform:uppercase;letter-spacing:var(--track-wood);font-size:20px;line-height:1.05;color:var(--c-cream)}
.ev-bill .ev-line{font-size:15px;line-height:1.4;color:var(--c-cream)}
.ev-more{display:table;margin:0 0 22px}
/* WCAG 2.5.8: "Details" draws 72.8x20.9 and .ev-more 290.6x32.6, both under the
   24px floor in height. Same invisible ::after as .cal-nav button; the padding
   box is 18.9px after the 2px border-bottom, so -13px takes it to 44.9. Both
   are the last thing in their box, so the pad falls on the paragraph above and
   into the margin below and nothing drawn moves. Scoped to .ev .link, so
   .sec-head a.link and .paper .link are untouched. */
.ev .link,.ev-more{position:relative}
.ev .link::after,.ev-more::after{content:"";position:absolute;inset:-13px -6px}
/* The calendar's no-JS floor (build.py cal_nojs). <noscript> content is never
   rendered when scripting is on, so none of this can move a pixel. */
.cal-nojs{list-style:none;margin:0;padding:0;color:var(--c-cream)}
.cal-nojs li{padding:10px 0;border-bottom:2px dotted rgba(242,226,211,.3);font-size:15px;line-height:1.45}
/* WCAG 2.5.8: "Details" draws 72.8x20.9 and .ev-more 290.6x32.6, both under the
   24px floor in height. Same invisible ::after as .cal-nav button; the padding
   box is 18.9px after the 2px border-bottom, so -13px takes it to 44.9. Both
   are the last thing in their box, so the pad falls on the paragraph above and
   into the margin below and nothing drawn moves. Scoped to .ev .link, so
   .sec-head a.link and .paper .link are untouched. */
.ev .link,.ev-more{position:relative}
.ev .link::after,.ev-more::after{content:"";position:absolute;inset:-13px -6px}
/* The calendar's no-JS floor (build.py cal_nojs). <noscript> content is never
   rendered when scripting is on, so none of this can move a pixel. */
.cal-nojs{list-style:none;margin:0;padding:0;color:var(--c-cream)}
.cal-nojs li{padding:10px 0;border-bottom:2px dotted rgba(242,226,211,.3);font-size:15px;line-height:1.45}
.feature{display:grid;grid-template-columns:1fr 1fr;align-items:stretch;gap:0}
.feature img{width:100%;height:100%;object-fit:cover;min-height:380px;border:2px solid var(--lead);box-shadow:calc(var(--sh-w) * -1) var(--sh-w) 0 var(--ruby);transform:rotate(var(--rot,0deg))}
.feature > div{padding:clamp(28px,4vw,56px)}

/* ==========================================================================
   LIGHTBOX — dormant (site.js builds it for any .gal a[data-lb]; nothing has one)
   ========================================================================== */
.lightbox{position:fixed;inset:0;z-index:100;background:rgba(31,29,29,.94);display:grid;place-items:center;opacity:0;visibility:hidden;transition:.3s}
.lightbox.is-open{opacity:1;visibility:visible}
.lightbox img{max-width:90vw;max-height:84vh;border:3px solid var(--lead);box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold)}
.lightbox button{position:absolute;top:50%;transform:translateY(-50%);width:54px;height:54px;border:2px solid var(--cream);font-size:26px;color:var(--cream);background:var(--c-orange);box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold)}
.lightbox .prev{left:20px}.lightbox .next{right:20px}.lightbox .close{top:24px;right:24px;transform:none}
.lightbox button:hover{background:var(--gold);color:var(--cream)}

/* ==========================================================================
   SOCIAL — hub and spoke
   ========================================================================== */
/* #social sat a full .sec padding below the feature block above it, which
   on a wide screen is ~250px of empty stock before "Hub & spoke". Opie
   flagged the gap, 20 September. Top padding only — the rest of the section
   rhythm is untouched. */
#social{padding-top:clamp(28px,3.4vw,54px)}
.hub{position:relative;min-height:780px;margin:40px 0 0}
.hub svg.lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:0;overflow:visible}
.hub svg.lines path{fill:none;stroke:var(--gold);stroke-width:3;stroke-dasharray:8 8;opacity:.55;transition:.3s}
.hub svg.lines path.hot{stroke:var(--gold-bright);opacity:1;stroke-width:4;stroke-dasharray:none}
.hub-core{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:min(300px,50%);z-index:2;text-align:center}
.hub-core img{width:100%;filter:drop-shadow(0 0 26px rgba(176,73,0,.6))}
.hub-core .tape{position:absolute;left:50%;bottom:-8px;transform:translateX(-50%) rotate(var(--rot,0deg))}
.spoke{position:absolute;width:min(300px,42%);padding:18px 20px;z-index:2;display:flex;flex-direction:column;gap:8px;transition:transform .3s var(--snap),box-shadow .3s}
.spoke:hover{transform:rotate(0) translateY(-6px);z-index:5}
.spoke .head{display:flex;align-items:center;gap:12px}
.spoke .glyph{width:46px;height:46px;border:2px solid var(--lead);display:grid;place-items:center;background:var(--cream);color:var(--ink);box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead);flex:none;transform:rotate(var(--rot,0deg))}
.spoke .glyph svg{width:24px;height:24px;fill:currentColor}
.spoke .name{font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-size:26px;line-height:.9}
.spoke .handle{font-family:var(--f-type);font-size:12px;letter-spacing:.1em;color:var(--cream)}
.spoke .post{font-size:15px;color:var(--smoke);line-height:1.45;margin:0;border-top:2px dotted rgba(242,226,211,.25);padding-top:10px}
.spoke .foot{display:flex;justify-content:space-between;align-items:center;font-family:var(--f-type);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--smoke-2)}
.spoke .foot b{font-family:var(--f-num);font-weight:800;font-size:18px;color:var(--cream);letter-spacing:0}
/* THE FOOT ROW, and what goes in it. Two children, spaced apart: the count slot on the
   left and the call on the right. The call sits in the same place whether or not there
   is a number beside it, so a channel reads finished either way.
   .spoke-count is prefixed because plain .count is the bag badge (.cart-btn .count
   above). It is empty until something feeds it — build.py's PUB["socials"] count field,
   or a script finding all five by their data-count key — and an empty span draws
   nothing, so wiring real numbers in later moves no other markup.
   A channel with no account yet (.spoke.is-soon) gets the same tile and the same foot,
   and loses only the link, so it reads as reserved rather than unfinished. The tape must
   not wrap: "OPENING / SOON" over two lines grew one tile to 211px against its
   neighbours' 190-196, measured 23 Sep. No hover lift either — the tile is not
   clickable, so it must not rise under the pointer as though it were. */
.spoke .foot .tape{white-space:nowrap}
.spoke .foot .spoke-count:empty{display:none}
.spoke.is-soon:hover{transform:rotate(var(--rot,0deg))}
.spoke[data-pos="1"]{left:0;top:2%;--rot:-1.45deg}
.spoke[data-pos="2"]{right:0;top:0;--rot:1.45deg}
.spoke[data-pos="3"]{left:2%;bottom:6%;--rot:1.45deg}
.spoke[data-pos="4"]{right:3%;bottom:2%;--rot:-1.45deg}
/* X is centred with auto margins, NOT translateX(-50%): its transform must stay the
   plain rotate every spoke has, so its hover is the same lift-and-straighten. The
   old transform centring leaked into the stacked layout (<=860px), where hovering X
   threw it half its own width to the left. */
.spoke[data-pos="5"]{left:0;right:0;margin-left:auto;margin-right:auto;bottom:0}

/* ==========================================================================
   FOOTER
   ========================================================================== */
.footer{padding:clamp(60px,8vw,110px) 0 28px;position:relative}
.footer::before{content:"";position:absolute;left:0;right:0;top:0;height:8px;background:repeating-linear-gradient(90deg,var(--ruby) 0 40px,var(--lead) 40px 48px,var(--green) 48px 88px,var(--lead) 88px 96px,var(--c-orange) 96px 136px,var(--lead) 136px 144px)}
.foot-grid{display:grid;grid-template-columns:1.2fr 1fr 1fr 1.3fr;gap:44px;padding-bottom:48px;border-bottom:2px dashed rgba(242,226,211,.2)}
.footer h2{font-family:var(--f-type);font-size:12px;letter-spacing:.24em;text-transform:uppercase;color:var(--cream);margin-bottom:16px}
.footer p,.footer li{color:var(--smoke);font-size:16px}
.footer li{padding:4px 0}
/* The footer's phone number and e-mail were 19px-tall targets on a 390 phone
   (108x19 and 192x19). The anchor now fills its own row instead: block, and
   4px of padding cancelled by 4px of negative margin, so the row height, the
   baseline and the dotted rules are all exactly where they were and the
   target is 27px tall and the full column wide. It stops at the row, so two
   rows never overlap. */
.footer li a{display:block;padding:4px 0;margin:-4px 0}
.footer li a:hover{color:var(--cream)}
.foot-logo{width:170px;margin-bottom:16px;filter:drop-shadow(0 0 16px rgba(176,73,0,.4));transform:rotate(var(--rot,0deg))}
/* Every hours listing is Bourbon Oblique with NO drop (Opie, 21 Sep) — the face alone,
   not .wood, so there is no ::before layer. Bourbon ships at 400 only: no faux bold. */
.hours-list,.hours-row{font-family:var(--f-wood);font-style:italic;font-weight:400;text-transform:uppercase;letter-spacing:var(--track-wood);font-size:16px}
.hours-list li{display:flex;justify-content:space-between;border-bottom:2px dotted rgba(242,226,211,.2)}
.map{position:relative;aspect-ratio:4/3;overflow:hidden;--rot:1.45deg;padding:0}
.map img{width:100%;height:100%;object-fit:cover;filter:saturate(.3) brightness(.55) contrast(1.2)}
.map .pin{position:absolute;left:50%;top:50%;width:22px;height:22px;background:var(--gold-bright);border:2px solid var(--lead);transform:translate(-50%,-50%) rotate(45deg)}
.map .tape{position:absolute;left:12px;bottom:12px;--rot:-1.4deg}
/* The real address is long enough to wrap: keep each phrase whole so the only break
   falls between the address and the note, never at the hyphen in "stand-in". */
.map .tape .nw{white-space:nowrap}
/* CONTACT PAGE (contact.html, 23 Sep). Opie: no published e-mail address — the two
   ways out are the telephone and this form, and the form does not submit until
   HANDOFFS["contact"]["url"] is set.
   The fields borrow the newsletter input's treatment below (cream field, hard gold
   offset) so the page needs no new look, only a column. Labels are real <label
   for=...> rather than placeholders: a placeholder disappears the moment someone
   types, which loses the question for anyone who is interrupted. */
.contact-grid{display:grid;grid-template-columns:1fr 340px;gap:28px;align-items:start}
.contact-main{padding:26px 28px 30px;transform:rotate(var(--rot,0deg))}
.contact-side{display:flex;flex-direction:column;gap:18px}
.c-block{padding:20px 22px;transform:rotate(var(--rot,0deg))}
.c-block p{margin:8px 0 0;font-size:15px;color:var(--smoke);line-height:1.45}
.c-block .btn,.c-block .tape{margin-top:14px}
/* The number is the loudest thing on the page, because it is the channel that works
   today while the form waits on its endpoint. */
.c-tel{display:block;font-family:var(--f-wood);font-size:30px;line-height:1;letter-spacing:var(--track-wood);color:var(--cream);text-decoration:none;margin-top:6px}
.c-tel:hover,.c-tel:focus-visible{color:var(--gold-bright)}
.f-row{display:flex;flex-direction:column;gap:6px;margin-top:16px}
.f-row label{font-family:var(--f-type);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--smoke)}
.f-row small{font-family:var(--f-type);font-size:11px;letter-spacing:.06em;color:var(--smoke)}
.f-row input,.f-row select,.f-row textarea{padding:12px 14px;border:2px solid var(--cream);background:var(--c-cream);color:var(--c-black);font:inherit;font-size:15px;box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold);width:100%;box-sizing:border-box}
.f-row textarea{resize:vertical;min-height:130px}
.f-send{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:22px}
/* nw: a tape that must stay on one line. "OPENING / SOON" over two lines once grew a
   hub tile 21px past its neighbours, 23 Sep. */
.tape.nw{white-space:nowrap}
@media (max-width:860px){
  .contact-grid{grid-template-columns:1fr}
  .contact-main{padding:20px 18px 24px}
  .c-tel{font-size:26px}
}
.newsletter{display:flex;gap:10px;margin-top:12px}
.newsletter input{flex:1;padding:12px 14px;border:2px solid var(--cream);background:var(--c-cream);color:var(--c-black);font:inherit;font-size:15px;box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold)}
.newsletter input::placeholder{color:rgba(31,29,29,.55)}
.small{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;padding-top:22px;font-family:var(--f-type);letter-spacing:.14em;text-transform:uppercase;font-size:11px;color:var(--smoke)}   /* was --smoke-2 (3.25:1 at 11px); --smoke is 6.48:1 on the page black */

/* ==========================================================================
   STORE
   ========================================================================== */
.products{display:grid;grid-template-columns:repeat(12,1fr);gap:30px;align-items:start}
.prod{overflow:visible;display:flex;flex-direction:column;position:relative}
.prod:nth-child(4n+1){--rot:-1.45deg}.prod:nth-child(4n+2){--rot:1.2deg;margin-top:28px}.prod:nth-child(4n+3){--rot:-.8deg;margin-top:10px}.prod:nth-child(4n){--rot:1.45deg}
@media (hover:hover){.prod:hover{transform:rotate(0) translateY(-6px);z-index:3}}
.prod .art{position:relative;aspect-ratio:1;display:grid;place-items:center;overflow:hidden;border-bottom:2px solid var(--lead)}
.prod .art img{width:64%;height:auto;transition:transform .5s var(--out);filter:drop-shadow(0 18px 26px rgba(0,0,0,.5))}
.prod:hover .art img{transform:scale(1.06) rotate(var(--rot,0deg))}
.prod .art::after{content:"";position:absolute;inset:0;background:url("../img/tex/dirt-09.webp") center/700px;mix-blend-mode:multiply;opacity:.35;pointer-events:none}
/* Five art grounds, four swatches — no teal here now that the card body
   behind them is teal, and no black. */
.prod .art.g{background:var(--c-green)}.prod .art.d{background:var(--c-orange)}.prod .art.c{background:var(--c-cream)}.prod .art.r{background:var(--c-red)}.prod .art.k{background:var(--c-green)}
.prod .art .lbl{position:absolute;left:14px;bottom:12px;font-family:var(--f-type);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:rgba(242,226,211,.75);z-index:1}
.prod .art.c .lbl{color:rgba(31,29,29,.6)}
.prod .body{padding:20px 22px 22px;display:flex;flex-direction:column;gap:8px;flex:1}
.prod h2{font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-size:28px;line-height:.9;margin:0}
.prod .desc{color:var(--smoke);font-size:15px;line-height:1.45;margin:0}
.prod .row{display:flex;justify-content:space-between;align-items:center;margin-top:auto;padding-top:10px}
.prod .price{font-family:var(--f-fat);font-size:30px;color:var(--cream)}
.prod .sticker{position:absolute;top:-14px;right:-12px;z-index:3;font-size:16px;padding:9px 12px}
.prod.s3{grid-column:span 3}.prod.s4{grid-column:span 4}.prod.s6{grid-column:span 6}
.sizes{display:flex;gap:6px}
.sizes button{width:36px;height:36px;border:2px solid var(--cream);font-family:var(--f-type);font-size:12px;transition:.15s}
.sizes button[aria-pressed="true"],.sizes button:hover{background:var(--cream);color:var(--ink);transform:rotate(var(--rot,0deg));box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold)}
.cart-btn{position:relative}
/* min-width, not width: at three digits the fixed 24px box printed the third
   digit outside its own 2px border. Border-box with a 2px border leaves a 20px
   content box, and "1" and "12" both measure 20px, so one and two digits stay at
   exactly the 24.246px they were - measured to three decimals, before and after.
   Do not add padding here: 4px of it widened the two-digit badge to 28.8px. */
.cart-btn .count{position:absolute;top:-10px;right:-10px;background:var(--ruby);color:var(--cream);min-width:24px;width:auto;height:24px;border:2px solid var(--lead);font-family:var(--f-num);font-size:13px;display:grid;place-items:center;transform:rotate(var(--rot,0deg))}
.cart-btn .count[hidden]{display:none}   /* site.js hides the badge while the bag is empty; display:grid above was beating the attribute */
/* visibility, 22 Sep: the closed drawer is off-screen by transform alone, so
   its ✕ and its "Checkout" button were still two tab stops on the Store,
   1,500px to the right of the page. visibility:hidden takes them out of the
   order and out of the a11y tree with no JS; the 0s transition DELAYED by the
   slide keeps it visible all the way out, and .is-open drops the delay so it
   is visible the instant it starts sliding in. Not one pixel moves. */
.drawer{position:fixed;top:0;right:0;bottom:0;width:min(440px,100%);z-index:95;background:var(--stock);--smoke:var(--c-cream);border-left:3px solid var(--lead);box-shadow:calc(var(--sh-w) * -1) 0 0 var(--gold);transform:translateX(110%);visibility:hidden;transition:transform .4s var(--out),visibility 0s .4s;padding:28px;display:flex;flex-direction:column;gap:18px}
.drawer.is-open{transform:none;visibility:visible;transition:transform .4s var(--out)}
.drawer-head{display:flex;justify-content:space-between;align-items:center}
.drawer-list{flex:1;overflow:auto;display:flex;flex-direction:column;gap:12px}
.drawer-list .row{display:grid;grid-template-columns:1fr auto;gap:10px;padding:12px;border:2px solid var(--cream);font-family:var(--f-num);text-transform:uppercase;letter-spacing:.04em;font-size:18px;box-shadow:var(--sh-w) var(--sh-w) 0 var(--gold)}
.drawer-list .row small{display:block;font-family:var(--f-type);color:var(--smoke-2);font-size:11px;letter-spacing:.14em}
.drawer-list .empty{color:var(--smoke-2);text-align:center;padding:40px 0;font-family:var(--f-type);font-size:13px;letter-spacing:.1em;text-transform:uppercase}
.drawer-total{display:flex;justify-content:space-between;font-family:var(--f-type);text-transform:uppercase;letter-spacing:.14em;border-top:2px dashed rgba(242,226,211,.3);padding-top:16px;align-items:center}
.drawer-total b{font-family:var(--f-fat);font-size:32px;letter-spacing:0;color:var(--cream)}
.scrim{position:fixed;inset:0;z-index:94;background:rgba(0,0,0,.55);opacity:0;visibility:hidden;transition:.3s}
.scrim.is-open{opacity:1;visibility:visible}
.toast{position:fixed;left:50%;bottom:28px;transform:translate(-50%,20px) rotate(var(--rot,0deg));z-index:96;background:var(--gold-bright);color:var(--ink);padding:12px 22px;border:2px solid var(--lead);box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead);font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-size:20px;opacity:0;transition:.3s var(--snap);pointer-events:none}
.toast.is-on{opacity:1;transform:translate(-50%,0) rotate(var(--rot,0deg));pointer-events:auto}

/* ==========================================================================
   ABOUT / page hero
   ========================================================================== */
.page-hero{padding:calc(var(--nav-h) + clamp(50px,9vw,130px)) 0 clamp(50px,7vw,100px);position:relative;overflow:hidden;isolation:isolate}
.page-hero .bg{position:absolute;inset:-6%;z-index:-1;will-change:transform}
.page-hero .bg img{width:100%;height:100%;object-fit:cover;opacity:.32;filter:saturate(.8)}
.page-hero .bg::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(31,29,29,.5),var(--night))}
.page-hero .wood{font-size:clamp(60px,10vw,150px);transform:rotate(var(--rot,0deg));transform-origin:left bottom}
.page-hero .tape{position:relative;top:-10px;margin-left:8px}
/* B10, 21 Sep — the page-hero video (contract 1; build.py HV_BG), on every
   inner page hero and the Blog's .hero. This is its RESTING state, and all
   there is with no JS: the poster (or the paused loop) at the old still's .32
   opacity and .8 saturation under the old gradient, so each hero sits exactly
   as dim and integrated as the image it replaced. The Blog's .hero keeps its
   own gradient (the faint orange bloom) but not its old .5: the footage is
   about 2.7x brighter than bar-wide.png, and .32 is what holds its headline
   over 4.5:1 (measured by the hero engine). fx.css section 5 (fx-hero) repeats
   these and adds the intro (full colour, no tint, until the glass clears).
   Everything here is :where(), zero specificity, so any fx.css fx-hero rule wins
   without a fight. The one exception switches the old ::after off: .hv-tint
   draws that gradient now, because it has to fade out for the intro. */
.page-hero .bg.hv::after{content:none}
:where([data-hero-video]) > :where(.bg.hv){position:absolute;inset:-6%;z-index:-1;overflow:hidden}
:where(.hero[data-hero-video]) > :where(.bg.hv){z-index:-2}   /* under the Blog's mosaic (-1), as .hero-bg was */
:where(.hv-vid){display:block;width:100%;height:100%;object-fit:cover;opacity:.32;filter:saturate(.8)}
:where(.hv-tint){position:absolute;inset:0;pointer-events:none;background:linear-gradient(180deg,rgba(31,29,29,.5),var(--night))}
:where(.hero[data-hero-video] .hv-vid){object-position:center 45%}
:where(.hero[data-hero-video] .hv-tint){background:radial-gradient(60% 60% at 50% 45%,rgba(176,73,0,.14),transparent 70%),linear-gradient(180deg,rgba(31,29,29,.6),rgba(31,29,29,.2) 40%,var(--night))}
/* The poster is a <picture> UNDER the video (build.py HV_BG, verify 21 Sep), not
   the poster attribute: a phone picks its own frame 0 by media query with no JS,
   and no longer fetches the desktop one first. It takes the video's box and dim;
   a video with no frame paints nothing, so it shows through until fx-hero sees
   the video hold a frame (.hv-frame, fx.css section 5) and steps it out. The
   video is positioned so it paints over it. */
.hv-poster{position:absolute;inset:0;display:block}
.hv-poster > img{display:block;width:100%;height:100%;max-width:none;object-fit:cover}
:where(.hv-poster > img){opacity:.32;filter:saturate(.8)}
:where(.hero[data-hero-video] .hv-poster > img){object-position:center 45%}
.bg.hv > .hv-vid{position:relative}
.timeline{display:grid;grid-template-columns:repeat(4,1fr);gap:26px;position:relative;padding-top:30px}
.timeline::before{content:"";position:absolute;left:0;right:0;top:44px;height:3px;background:repeating-linear-gradient(90deg,var(--gold) 0 14px,transparent 14px 22px)}
.tl-item{padding:36px 22px 22px;--rot:-1deg}
.tl-item:nth-child(even){--rot:1.4deg;margin-top:30px}
.tl-item::before{content:"";position:absolute;top:-16px;left:20px;z-index:2;
  --mark-src:url("../img/brand/shamrock-orange.svg");--mark-sz:28px;--mark-rot:-18deg;
  width:var(--mark-sz);height:var(--mark-sz);background:var(--mark-src) center/contain no-repeat;
  transform:rotate(var(--mark-rot))}
.tl-item b{font-family:var(--f-wood);letter-spacing:var(--track-wood);font-style:italic;font-size:38px;color:var(--cream);text-shadow:2px 2px 0 var(--c-orange);display:block;line-height:.9;margin-bottom:8px;text-transform:uppercase}
.tl-item p{color:var(--smoke);font-size:15.5px;margin:10px 0 0;line-height:1.45}
/* B16, 21 Sep: the year under each heading — upright wood face, tracked, no drop. */
.tl-item .yr{display:block;font-family:var(--f-wood);font-style:normal;font-weight:400;font-size:20px;letter-spacing:.16em;line-height:1;color:var(--cream);text-shadow:none;text-transform:uppercase;margin:0}
.crew{display:grid;grid-template-columns:repeat(3,1fr);gap:34px 26px}
.person{padding:0;overflow:visible}
.person:nth-child(3n+1){--rot:-1.45deg}.person:nth-child(3n+2){--rot:1.2deg;margin-top:26px}.person:nth-child(3n){--rot:-.7deg;margin-top:8px}
.person .ph{aspect-ratio:4/3;overflow:hidden;border-bottom:2px solid var(--lead);position:relative}
.person .ph img{width:100%;height:100%;object-fit:cover;filter:saturate(.75) contrast(1.08)}
.person .b{padding:18px 22px 22px}
.person h3{font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-size:30px;line-height:.9;margin:0}
.person .role{font-family:var(--f-sub);letter-spacing:.04em;color:var(--cream);font-size:20px}
.person p{color:var(--smoke);font-size:15px;margin:8px 0 0;line-height:1.45}
.person .tape{position:absolute;top:-12px;right:14px;--rot:1.4deg}   /* was 4deg; site.js never re-rolls a tape inside a .shard */
/* Opie, 20 September: the call-out is words on the page. No box, no
   texture, no stamp — it is out of PAT_SEL in site.js so it never gets a
   pattern layer, and the stamp is out of the markup. */
.quote{font-family:var(--f-sub);font-size:clamp(34px,5vw,72px);line-height:1.04;letter-spacing:.02em;max-width:22ch;margin:0 auto;text-align:center;position:relative;background:none}
.quote em{color:var(--cream);font-family:var(--f-sub);font-style:normal}   /* B6: Duality Steel only, the em too. The kit has no italic of it: never let the browser fake one */

/* ==========================================================================
   ABOUT, batch 2 — the horse band (B7), the rooms (B8), the team teaser (B9)
   Every class here is new and prefixed (horse- / nook- / tm-): .room,
   .room-img and .room-copy already mean the Chapter one block, and .tl and
   .panel have both broken this sheet by colliding before.
   ========================================================================== */

/* B7 — the frosted carousel-horse band, straight after the quote section. It
   sits between sections, outside .wrap, so it runs edge to edge at every width.
   Its natural height is 19vw (the source is 2048x389); under ~950px wide that
   is a sliver, so it holds a floor and crops sideways instead, around the horse
   panel (x 798-1162 of 2048 in the source; object-position 46% keeps that panel
   centred from a 390 phone up to 950px). Lead rules top and bottom, as on the
   .break band. No parallax, no shadow: a transition, not a panel. */
.horse-band{position:relative;height:max(180px,19vw);overflow:hidden;border-top:3px solid var(--lead);border-bottom:3px solid var(--lead)}
.horse-band img{width:100%;height:100%;max-width:none;object-fit:cover;object-position:46% 50%}

/* B8 — the rooms: kick "nooks and corners", heading "Public Home" with an
   exaggerated gap between the words and an ACCESS tape tacked into it, so it
   reads "Public ACCESS Home". The heading keeps aria-label "Public Access Home";
   the tape is aria-hidden.

   The gap is word-spacing, not a spacer: the heading stays one text node for
   the header reveal (fx-type splits it and keeps real spaces), and its drop
   layer inherits the spacing, so the drop stays in register.

   The tape rides .nooks-gap, an invisible copy of the heading in the same
   face, size, tracking and spacing, laid in the same grid cell. .nooks-sp
   wraps exactly the space between PUBLIC and HOME, so the tape centres in the
   gap at every size and whatever the fonts are doing. --nk-gap is the knob:
   the tape's width (ACCESS in Broadsheet at max(13px,.3em) of the heading,
   .85em padding a side = 5.55 tape-ems, measured) less a Bourbon space
   (.24em) less 12px, so the tape overlaps each word by 6px at every size.
   It resolves per element, against the heading's own font size. */
.nooks-hd{display:grid;justify-items:start;--nk-gap:calc(max(13px,.3em) * 5.55 - 12px - .24em)}
.nooks-hd > .nooks-h,.nooks-hd > .nooks-gap{grid-row:2;grid-column:1}
.nooks-h{word-spacing:var(--nk-gap);white-space:nowrap}
.nooks-gap{font-family:var(--f-wood);font-style:italic;font-weight:400;text-transform:uppercase;letter-spacing:var(--track-wood);
  line-height:.88;font-size:clamp(32px,4.4vw,64px);word-spacing:var(--nk-gap);white-space:nowrap;visibility:hidden;pointer-events:none}
.nooks-sp{position:relative}
.nooks-gap .tape{visibility:visible;position:absolute;left:50%;top:50%;translate:-50% -50%;--rot:-1.4deg;
  font-size:max(13px,.3em);padding:.5em .85em;font-style:normal;word-spacing:normal;white-space:nowrap}   /* upright like every tape: the copy it rides is italic */
/* With the header reveal armed (fx-type), the tape waits for the heading: it is
   hidden while the heading is armed and tacks on after the face has typed.
   No JS, reduced motion or ?static: html.fx-type is never set, the tape is
   simply there. */
html.fx-type .nooks-hd .nooks-gap .tape{transition:opacity .3s ease-out 1.25s}
html.fx-type .nooks-hd .fxt-armed ~ .nooks-gap .tape{opacity:0;transition:none}
/* ...and on the way back (C2) it goes FIRST, the mirror of arriving last: fx-type
   puts .fxt-rev on the heading while it un-types, and the tape fades as that
   begins. Without this it stood alone over the emptying gap, then blinked off
   in one frame when the heading re-armed. */
html.fx-type .nooks-hd .fxt-rev ~ .nooks-gap .tape{opacity:0;transition:opacity .3s ease-out}

/* Eight tiles, room-card style: square photo on a mat, house tape on the photo,
   title in the wood face, a short line. The squares are at least a third
   smaller than the crew photos were (407px wide at 1440; these are ~264px):
   4 across on a desktop, 2 on a tablet or phone. The tiles are a box group for
   the exploding bento (.nooks > .nook). */
.nooks{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:36px 30px;align-items:start}
.nook{padding:14px 14px 20px;overflow:visible}
.nook:nth-child(4n+1){--rot:-1.2deg}.nook:nth-child(4n+2){--rot:1deg;margin-top:22px}
.nook:nth-child(4n+3){--rot:-.6deg;margin-top:8px}.nook:nth-child(4n){--rot:1.3deg;margin-top:28px}
.nook .ph{position:relative;aspect-ratio:1;border:2px solid var(--lead)}
.nook .ph img{width:100%;height:100%;object-fit:cover;filter:saturate(.85) contrast(1.05)}
/* The tape hangs off the photo's lower edge, alternating sides. site.js never
   re-rolls a tape inside a .shard, so these are the resting angles (<= 1.45). */
.nook .tape{position:absolute;left:-12px;bottom:14px;max-width:calc(100% + 4px);white-space:normal;line-height:1.2;text-align:left;--rot:-1.3deg}
.nook:nth-child(even) .tape{left:auto;right:-12px;--rot:1.2deg}
.nook h3{font-family:var(--f-wood);letter-spacing:var(--track-wood);text-transform:uppercase;font-weight:400;font-size:clamp(22px,1.9vw,28px);line-height:.95;margin:16px 0 0}
.nook p{color:var(--smoke);font-size:15px;line-height:1.45;margin:8px 0 0}

/* B9 — "Meet the team" teaser under the rooms: one teal box, copy left, the
   button right; it wraps to a stack on a phone. */
.tm-teaser{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:24px 48px;padding:clamp(28px,4vw,52px)}
.tm-teaser .lede{margin:14px 0 0;max-width:48ch}

@media(max-width:860px){
  .nooks{grid-template-columns:repeat(2,minmax(0,1fr));gap:30px 22px}
  .nooks .nook{margin-top:0}
}
@media(max-width:520px){
  .nooks{gap:24px 14px}
  .nook{padding:9px 9px 14px}
  .nook h3{font-size:20px;margin-top:12px}
  .nook p{font-size:14px}
  .nook .tape{font-size:11px;padding:7px 10px;left:-8px;bottom:10px}
  .nook:nth-child(even) .tape{right:-8px}
}

/* ==========================================================================
   POSTS — the "learn more" / blog-post template (B14, 21 Sep)
   build.py POSTS + post_page(): trad-session.html, pub-life.html and every
   post after them. Every new class is prefixed post- / ph- / pf- / pv- / pc-:
   .tl and .panel have both broken this sheet by colliding.

   The page: a hero, then one grid holding the facts box, the article column
   and the glossary, in that DOM order. That is the phone order (facts under
   the hero, glossary after the body); from 1000px the two boxes stand in a
   sidebar and the glossary sticks.
   ========================================================================== */

/* The hero is a .page-hero, so fx-type plays its title as a page title (from
   fonts-ready, with the hover stretch) and the page-hero rules give it the
   nav clearance, the parallax layer and the tape after the title.
   bleed (default): the picture at full strength, full width, under a scrim
   that darkens toward the copy: down the page and in from the left, where
   the copy sits. plate: the art framed beside the title at its own size. */
.post-hero{display:flex;flex-direction:column;justify-content:flex-end;min-height:72vh;min-height:72svh;padding:calc(var(--nav-h) + 48px) 0 clamp(34px,5vw,64px)}
.post-hero .bg img{opacity:1;filter:saturate(.92)}
.post-hero .bg::after{background:
  linear-gradient(90deg,rgba(31,29,29,.74) 0,rgba(31,29,29,.36) 46%,rgba(31,29,29,0) 76%),
  linear-gradient(180deg,rgba(31,29,29,.5) 0,rgba(31,29,29,0) 22%,rgba(31,29,29,.24) 42%,rgba(31,29,29,.88) 72%,var(--night))}
.post-hero .wood{font-size:clamp(50px,7.2vw,112px)}
.post-hero .lede{margin:18px 0 0;max-width:52ch;color:var(--c-cream)}
.post-hero .post-meta{display:flex;flex-wrap:wrap;max-width:none;margin:18px 0 0;font-family:var(--f-type);font-size:13px;line-height:1.5;letter-spacing:.12em;text-transform:uppercase;color:var(--c-cream)}
.post-meta span + span::before{content:"·";margin:0 .8em}
.post-hero.is-bare,.post-hero.is-plate{min-height:0;justify-content:flex-start}
.post-hero.is-plate{padding-bottom:clamp(20px,3vw,44px)}
.post-hero.is-plate .wood{font-size:clamp(46px,5.8vw,92px)}
.ph-grid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);gap:clamp(30px,5vw,80px);align-items:center}
/* The plate never draws past its art's own width (the toast is 894px): at
   gauge, framed like a photo shard, not stretched soft across the hero. */
.ph-plate{justify-self:end;width:100%;max-width:min(560px,var(--ph-w,560px));margin:0;padding:0;--rot:1.2deg}
.ph-plate img{width:100%;height:auto}

/* The body. */
.post-body{padding:clamp(44px,5vw,76px) 0 clamp(70px,8vw,120px)}
.post-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(36px,4vw,52px);align-items:start}
/* The glossary is emitted before the article (build.py POST_TPL) so the keyboard
   reaches it with the sidebar it belongs to instead of after the article's closing
   calls to action. One column auto-places by source order, so order puts it back
   last here; the min-width:1000px block below places all three explicitly, where
   order changes no position. */
.post-grid > .post-gloss{order:3}
/* The glossary is emitted before the article (build.py POST_TPL) so the keyboard
   reaches it with the sidebar it belongs to instead of after the article's closing
   calls to action. One column auto-places by source order, so order puts it back
   last here; the min-width:1000px block below places all three explicitly, where
   order changes no position. */
.post-grid > .post-gloss{order:3}
/* The article column: ~68 characters a line at 19px in the body face. */
.post-main{min-width:0;max-width:640px;font-size:19px;line-height:1.66;color:rgba(242,226,211,.88)}
.post-main p{margin:0 0 1.05em}
.post-sec > .wood{display:block;margin:1.9em 0 .7em}
.post-sec:first-child > .wood{margin-top:0}
/* the drop cap: the house display face, as on About's Chapter one */
.post-main .dropcap::first-letter{float:left;margin:.07em .1em 0 0;font-family:var(--f-fat);font-size:4.3em;line-height:.8;color:var(--c-cream)}
.post-sec p a{color:var(--c-cream);border-bottom:2px solid var(--c-orange)}
.post-sec p a:hover{border-bottom-color:var(--c-cream)}

/* Pull quote: Duality Steel, big, one palette rule. */
.post-pull{margin:clamp(36px,4.4vw,60px) 0;padding:4px 0 4px clamp(18px,2.4vw,30px);border-left:var(--sh-w) solid var(--c-orange)}
.post-pull p{margin:0;font-family:var(--f-sub);font-weight:400;font-style:normal;font-size:clamp(30px,3.6vw,48px);line-height:1.05;letter-spacing:.02em;color:var(--c-cream)}

/* Figures: column width, a .shard (hard shadow, seeded tilt; these are the
   no-JS rests, inside the ceiling), the tape caption pinned on a lower
   corner, alternating sides with the shadow colour. */
.post-fig{margin:clamp(40px,5vw,64px) 0;padding:0;overflow:visible;--rot:-1.1deg}
.post-fig.green{--rot:1.2deg}
.post-fig img{width:100%;height:auto}
.post-fig .tape{position:absolute;left:-12px;bottom:-15px;max-width:calc(100% - 24px);white-space:normal;line-height:1.25;--rot:-1.3deg}
.post-fig.green .tape{left:auto;right:-12px;--rot:1.2deg}
.pf-slot{display:grid;place-items:center;aspect-ratio:3/2;padding:20px;background:var(--c-red);outline:2px dashed rgba(242,226,211,.55);outline-offset:-14px;text-align:center;
  font-family:var(--f-type);font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--c-cream)}

/* The video slot, 16:9 at every width: a file, the YouTube facade, or the
   labelled stand-in (build.py post_video() decides at build time). */
.post-video{margin:clamp(44px,5.4vw,72px) 0;padding:0}
.pv-head{margin:0 0 18px}
.pv-head .kick{margin-bottom:4px}
.pv-frame{position:relative;aspect-ratio:16/9;overflow:hidden;background:var(--c-black);border:2px solid var(--c-black);box-shadow:var(--sh-w) var(--sh-w) 0 var(--c-orange)}
.pv-frame video,.pv-frame iframe{display:block;width:100%;height:100%;border:0}
.pv-slot{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:clamp(20px,4vw,44px);text-align:center;
  background:var(--c-red);outline:2px dashed rgba(242,226,211,.5);outline-offset:-14px}
.pv-label{max-width:16em;font-family:var(--f-wood);font-style:italic;font-weight:400;text-transform:uppercase;letter-spacing:var(--track-wood);font-size:clamp(24px,3vw,40px);line-height:.98;color:var(--c-cream)}
.pv-drop{max-width:58ch;font-family:var(--f-type);font-size:12px;line-height:1.45;letter-spacing:.12em;text-transform:uppercase;color:var(--c-cream)}
/* The facade: the post's own hero as the poster, and the house play button.
   No glow: hover lifts it 2px like every button; focus rings the button. */
/* the file path wraps as a whole, never at the hyphen in a slug, unless it is
   wider than the block itself (a 320px phone) */
.pv-path{display:inline-block;max-width:100%}
.pv-yt{position:absolute;inset:0;display:block}
.pv-yt img{width:100%;height:100%;object-fit:cover}
.pv-play{position:absolute;left:50%;top:50%;width:84px;height:84px;margin:-42px 0 0 -42px;background:var(--c-orange);border:2px solid var(--c-black);box-shadow:var(--sh-w) var(--sh-w) 0 var(--c-black);transition:translate .15s var(--snap)}
.pv-play::before{content:"";position:absolute;left:30px;top:23px;border-style:solid;border-width:17px 0 17px 27px;border-color:transparent transparent transparent var(--c-cream)}
@media (hover:hover){.pv-yt:hover .pv-play{translate:-2px -2px}}
.pv-yt:focus-visible{outline:none}
.pv-yt:focus-visible .pv-play{outline:3px solid var(--c-cream);outline-offset:3px}
.post-video figcaption{max-width:56ch;margin-top:16px;font-size:16px;line-height:1.5;color:rgba(242,226,211,.8)}

/* The sidebar boxes. Facts: the ruby stock (cream on red, 9:1). Glossary:
   the paper stock with black type, for the longest reading in the box. The
   tape is each box's heading. */
.post-box{padding:34px 24px 24px;overflow:visible}
.post-box > .tape{position:absolute;top:-15px;left:18px;margin:0;font-weight:400;--rot:-1.2deg}
.post-box dl{margin:0}
.post-facts dl{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:18px}
.post-facts dt,.post-facts dd{margin:0;padding:10px 0;border-bottom:2px dotted rgba(242,226,211,.3)}
.post-facts dt:last-of-type,.post-facts dd:last-of-type{border-bottom:0}
.post-facts dt{padding-top:13px;font-family:var(--f-type);font-size:12px;line-height:1.35;letter-spacing:.14em;text-transform:uppercase;color:var(--c-cream)}
.post-facts dd{font-family:var(--f-wood);font-style:italic;font-weight:400;text-transform:uppercase;letter-spacing:var(--track-wood);font-size:20px;line-height:1.1;color:var(--c-cream)}
.post-gloss dt{margin-top:18px;font-family:var(--f-wood);font-style:italic;font-weight:400;text-transform:uppercase;letter-spacing:var(--track-wood);font-size:22px;line-height:1;color:var(--c-red)}
.post-gloss dt:first-child{margin-top:0}
.post-gloss .say{display:block;margin-top:5px;font-family:var(--f-type);font-style:normal;font-size:12.5px;line-height:1.35;letter-spacing:.06em;text-transform:none;color:var(--c-black)}
.post-gloss dd{margin:6px 0 0;font-size:15.5px;line-height:1.5;color:var(--c-black)}
/* The paper stock's dirt (.shard.cream::after, .35) put its darkest specks under
   the red terms at 4.3:1. This box is the longest small reading on the page, so
   its dirt runs lighter; the paper still reads as paper. */
.post-gloss.shard.cream::after{opacity:.2}

/* A guide's contents list (pub-life): the rule names only, numbered, in the
   wood face with no drop, as the hours listings are. */
.post-contents{margin:clamp(30px,4vw,48px) 0 0}
.pc-label{margin:0 0 6px;font-family:var(--f-type);font-size:12px;font-weight:400;letter-spacing:.2em;text-transform:uppercase;color:var(--c-cream)}
.post-contents ol{margin:0;padding:0;list-style:none;counter-reset:pc}
.post-contents li{counter-increment:pc;display:flex;align-items:baseline;gap:18px;padding:13px 0;border-bottom:2px dotted rgba(242,226,211,.3);
  font-family:var(--f-wood);font-style:italic;font-weight:400;text-transform:uppercase;letter-spacing:var(--track-wood);font-size:clamp(24px,2.6vw,34px);line-height:1;color:var(--c-cream)}
.post-contents li::before{content:counter(pc,decimal-leading-zero);flex:none;font-family:var(--f-type);font-style:normal;font-size:13px;letter-spacing:.16em;color:var(--c-cream)}

/* The end: the call to action and the way back. */
.post-end{margin-top:clamp(50px,6vw,84px);padding-top:clamp(28px,3vw,40px);border-top:2px dashed rgba(242,226,211,.3)}
.post-cta-line{max-width:30ch;margin:0 0 24px;font-family:var(--f-sub);font-size:clamp(24px,2.4vw,32px);line-height:1.12;letter-spacing:.02em;color:var(--c-cream)}
.post-back{display:table;margin-top:36px}
.post-end > .post-back:first-child{margin-top:0}   /* a post with no call to action: the way back sits right under the rule */

/* Desktop: the boxes become a sidebar. The glossary is the reference a
   reader keeps glancing back to, so it sticks under the nav for the length
   of the article; it is taller than most screens, so its list scrolls inside
   the box (the tape stays put on top). The facts box sits above it and
   scrolls away with the opening section. */
@media (min-width:1000px){
  .post-grid{grid-template-columns:minmax(0,640px) minmax(280px,340px);grid-template-rows:auto 1fr;justify-content:space-between;column-gap:clamp(40px,5vw,90px);row-gap:48px}
  .post-main{grid-column:1;grid-row:1 / span 2}
  .post-facts{grid-column:2;grid-row:1}
  .post-gloss{grid-column:2;grid-row:2;position:sticky;top:calc(var(--nav-h) + 34px);display:flex;flex-direction:column;
    max-height:calc(100vh - var(--nav-h) - 64px);max-height:calc(100svh - var(--nav-h) - 64px)}
  .post-gloss dl{min-height:0;overflow-y:auto;overscroll-behavior:contain;margin-right:-12px;padding-right:12px;scrollbar-width:thin;scrollbar-color:var(--c-red) transparent}
  .post-solo .post-grid{grid-template-columns:minmax(0,640px)}
}
@media (max-width:860px){
  /* One column of copy is taller than the picture's 58svh here, so the copy
     would cover all of it, the lamp and the window included (the kick measured
     2.6:1 there). Instead the picture gets a clear band under the nav, and the
     copy starts where the scrim has gone dark. The scrim's stops are tied to
     that band, not to the hero's height, which the copy decides. */
  .post-hero{min-height:58vh;min-height:58svh;padding-top:calc(var(--nav-h) + var(--ph-band))}
  .post-hero{--ph-band:clamp(90px,24svh,230px)}
  .post-hero .bg::after{background:linear-gradient(180deg,rgba(31,29,29,.5) 0,rgba(31,29,29,.06) calc(var(--nav-h) + 20px),rgba(31,29,29,.14) calc(var(--nav-h) + var(--ph-band) * .45),rgba(31,29,29,.86) calc(var(--nav-h) + var(--ph-band) + 30px),rgba(31,29,29,.93) calc(var(--nav-h) + var(--ph-band) + 160px),var(--night))}
  .post-hero.is-plate,.post-hero.is-bare{padding-top:calc(var(--nav-h) + 34px)}
  /* and the picture layer stops where the scrim has gone to black, so the 16:9
     frame is sized to the band, not to the whole tall hero: the players' faces
     land in the clear band instead of under the copy */
  .post-hero .bg{top:0;bottom:auto;height:calc(var(--nav-h) + var(--ph-band) + 190px)}
  .ph-grid{grid-template-columns:minmax(0,1fr)}
  .ph-plate{justify-self:start;max-width:min(100%,520px)}
  /* the hero tape drops under the title here: clear of its drop, not lifted into it */
  .post-hero .tape{top:0;margin:14px 0 0}
}
@media (max-width:520px){
  /* one meta item a line: a separator must never start a line */
  .post-hero .post-meta{flex-direction:column;gap:2px}
  .post-meta span + span::before{content:none}
  .post-main{font-size:18px}
  .post-box{padding:30px 18px 20px}
  .pv-play{width:64px;height:64px;margin:-32px 0 0 -32px}
  .pv-play::before{left:22px;top:17px;border-width:13px 0 13px 21px}
}

/* ==========================================================================
   MOTION — reveals, parallax, reduced
   ========================================================================== */
.rv{opacity:0;transform:translateY(40px) rotate(var(--rot,0deg));transition:opacity .7s var(--out),transform .8s var(--snap)}
.rv.is-in{opacity:1;transform:translateY(0) rotate(var(--rot,0deg))}
[data-px]{will-change:translate;translate:0 var(--py,0px)}
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none!important;transition-duration:.01ms!important}
  .rv{opacity:1;transform:rotate(var(--rot,0deg))}
  .stack{transform:none!important}
  .stack .lit{-webkit-mask:none;mask:none}
}
html.static *,html.static *::before,html.static *::after{animation:none!important;transition:none!important}
html.static .rv{opacity:1!important;transform:rotate(var(--rot,0deg))!important}
html.static [data-px]{translate:none!important}
html.static .stack{transform:perspective(1100px) rotateX(4deg) rotateY(-7deg)!important}
html.static .stack .lit{-webkit-mask:radial-gradient(circle 240px at 52% 42%,#000 0,#000 45%,transparent 100%);mask:radial-gradient(circle 240px at 52% 42%,#000 0,#000 45%,transparent 100%)}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media(max-width:1100px){
  .collage{grid-template-columns:repeat(6,1fr)}
  .s7,.s8,.s12{grid-column:span 6}.s5{grid-column:span 3}.s4{grid-column:span 3}
  .products{grid-template-columns:repeat(6,1fr)}.prod.s3{grid-column:span 3}.prod.s4,.prod.s6{grid-column:span 6}
  .foot-grid{grid-template-columns:1fr 1fr}.timeline,.crew{grid-template-columns:repeat(2,1fr)}
  .hub{min-height:820px}.spoke{width:min(280px,44%)}
  .overlay .wrap{grid-template-columns:repeat(2,1fr)}.ov-tile.big,.ov-tile.wide,.ov-tile.info{grid-column:span 2;grid-row:auto}.ov-tile.half{grid-column:span 1}
}
@media(max-width:860px){
  .nav-brand img{height:34px}
  .nav-links{display:none}.nav .wrap{grid-template-columns:auto 1fr}.nav-right .btn{display:none}
  .hero-grid{grid-template-columns:1fr;gap:10px}.hero-copy{margin:0;text-align:center}.hero-copy .wood{transform:rotate(var(--rot,0deg));transform-origin:center;margin-inline:auto}.hero-copy .line1,.hero-copy .line2{margin-left:auto;margin-right:auto}.hero-copy .tape{margin-top:18px}.hero-cta{justify-content:center;margin-left:0}.hero-meta{display:none}
  .stack{width:min(520px,90%)}
  .room,.cal-wrap,.feature,.menu-two{grid-template-columns:1fr}.menu-grid{grid-template-columns:1fr}
  /* The photo runs the full column here, so its overhangs (-30px stamp, -20px
     tape) reached past an 18-23px gutter and the section clip sliced them (the
     Blog's EST stamp lost "BACK WHEN"'s end). They still hang off the photo. */
  .room-img .stamp{right:-12px}.room-img .tape{left:-12px}
  .menu-grid .card,.menu-two .card{margin-top:0}
  .collage{grid-template-columns:repeat(2,1fr)}.s3,.s4,.s5,.s6,.s7,.s8,.s12{grid-column:span 2}.r3{grid-row:span 2}
  .ev-detail{position:static}
  .products{grid-template-columns:1fr 1fr}.prod.s3,.prod.s4,.prod.s6{grid-column:span 1}
  .break{height:220px;margin:-40px 0}.break .brk-panel{width:92%}
  .hub{min-height:0;display:flex;flex-direction:column;gap:22px;padding-left:26px;border-left:3px dashed var(--gold)}
  /* relative, not static: the tape hangs off the logo with position:absolute, and a
     static core handed it to .hub, which dropped it behind the last card. */
  .hub svg.lines{display:none}.hub-core{position:relative;left:auto;top:auto;transform:none;width:60%;margin:0 auto 10px}
  .spoke{position:static;width:100%;transform:rotate(var(--rot,0deg))}
}
@media(max-width:520px){
  :root{--gut:18px}
  .collage,.crew,.timeline,.products{grid-template-columns:1fr}
  .s3,.s4,.s5,.s6,.s7,.s8,.s12,.prod.s3,.prod.s4,.prod.s6{grid-column:span 1}.r2,.r3{grid-row:span 1}
  .foot-grid{grid-template-columns:1fr}.cal .cell .dot{display:none}
  .cal .cell{padding:4px 5px;font-size:15px}
  /* A day draws 35.4px at 320 and 41.1px at 360, under the 44px of WCAG 2.5.5.
     The same invisible ::after as .cal-nav button, taken to half the 6px
     gutter so two days can never overlap: measured 41.4x41.4 at 320 and
     47.1x47.1 at 360, with the grid and page heights byte-identical. inset is
     measured from the PADDING box, i.e. the border box less its 2px borders,
     so -5px buys 3px a side. A true 44px at 320 needs the cell itself to grow
     (aspect-ratio:auto;min-height:44px), which adds 43px to that page - a look
     change and Opie's call. */
  .cal .cell.has::after{content:"";position:absolute;inset:-5px}
  /* A day draws 35.4px at 320 and 41.1px at 360, under the 44px of WCAG 2.5.5.
     The same invisible ::after as .cal-nav button, taken to half the 6px
     gutter so two days can never overlap: measured 41.4x41.4 at 320 and
     47.1x47.1 at 360, with the grid and page heights byte-identical. inset is
     measured from the PADDING box, i.e. the border box less its 2px borders,
     so -5px buys 3px a side. A true 44px at 320 needs the cell itself to grow
     (aspect-ratio:auto;min-height:44px), which adds 43px to that page - a look
     change and Opie's call. */
  .cal .cell.has::after{content:"";position:absolute;inset:-5px}
  .btn{font-size:19px;padding:13px 18px 11px}
  .menu-grid .card,.prod,.person,.tl-item{margin-top:0!important}
  .hero-copy .wood{font-size:clamp(48px,14vw,80px)}
}
/* B12: the Find us + Hours box stacks where two columns will not fit; the rule
   turns horizontal, same weight and colour. */
@media(max-width:560px){
  .ov-tile.info{grid-template-columns:1fr}
  .ov-tile.info .ov-col{padding-right:0}
  .ov-tile.info .ov-col + .ov-col{border-left:0;padding-left:0;border-top:2px solid var(--gold-bright);margin-top:16px;padding-top:14px}
}

/* ==========================================================================
   LANDING HERO — the liquid blob mask-reveal            (index.html only)
   Added 20 September 2026; rebuilt on Opie's layered exports the same day.
   Every class here is prefixed lq-: .tl and .panel have both silently broken
   this sheet by colliding, so nothing new goes in unprefixed. The engine is
   assets/js/liquid-reveal.js; playback is started in site.js. Markup contract
   is documented at the top of build.py's HOME block — change neither side
   alone.

   The section is full-bleed from the very top of the viewport and exactly one
   window tall. The fixed nav floats over it; the page does not scroll.

   Layers, bottom to top:
     .lq-reveal     the reveal backdrop video, and the reveal type image.
     .lq-type-pic   Present and loading/decoding, never painted by the browser
                    — the GL engine uploads both as textures. Held at
                    opacity:0, NOT display:none and NOT visibility:hidden,
                    either of which lets a browser stop the video decoder and
                    hand the engine a frozen texture.
     .lq-base       the wall. Always visible, pure DOM, its own blend group:
       .lq-wall       the still, object-fit:cover. Opaque.
       .lq-fx         the light + dust plate, object-fit:cover, blended over
                      the wall with mix-blend-mode:var(--lq-mix, screen).
     .lq-canvas     the engine's output: backdrop + type carrying the blob's
                    alpha. Above the base, pointer-events:none.
     .lq-copy       the overlay: the sr-only h1, nothing visible.

   Review hooks (?mix, ?stack — documented in build.py) arrive from the engine
   as --lq-mix inline and data-lq-mix / data-lq-stack on the section. Their
   rules are at the foot of this block.

   Fallback: the engine adds .lq-off to the section when it cannot run — no
   WebGL, programs refused, a video it cannot sample. With no JS at all the
   canvas is simply an unpainted transparent box. Both land on the same
   deliberate thing: the wall with its light moving over it.

   Palette: nothing in this block takes a colour. The section has no
   background of its own; body's --c-black shows through, and body is the only
   element that takes --c-black.
   ========================================================================== */
.hero-lq{position:relative;isolation:isolate;overflow:hidden;width:100%;
  height:100vh;                            /* fallback where svh is unknown */
  height:100svh;                           /* one window tall, from y = 0 */
  touch-action:pinch-zoom}
/* 100svh and no margin: the hero starts under the fixed nav, not below it.
   svh rather than dvh because the page never scrolls, so a phone's toolbars
   never retract and the two are the same height — but svh never changes under
   the engine, where every dvh change would reallocate the fluid grid and pop
   the blob.
   touch-action:pinch-zoom is what makes drag-to-reveal work on a phone. With
   the default (auto) the browser claims the drag as a pan: the engine gets one
   pointermove, then pointercancel, and nothing more. pinch-zoom hands the drag
   to the page and keeps two-finger zoom, which the page must not take away.
   The page does not scroll, so claiming the pan costs nothing. */

/* The base: one closed blend group. isolation:isolate is LOAD-BEARING. It is
   what guarantees the FX plate's blend mode reaches the wall and stops there;
   let the blend escape and a screened plate brightens whatever else sits
   behind it — body's dirt texture and the vignette at the page level, or any
   layer later added to the section under the base. Today two other things
   would also stop it (the z-index on this positioned box, and .hero-lq's own
   isolation at the section edge), so the rule is belt and braces — keep it
   anyway: it states the intent, and it is the one that survives someone
   tidying a z-index to auto or moving the base out of the section. */
.lq-base{position:absolute;inset:0;z-index:1;isolation:isolate}
.lq-wall,.lq-fx{position:absolute;inset:0;width:100%;height:100%}
.lq-wall{display:block;z-index:1}
.lq-wall img{width:100%;height:100%;max-width:none;object-fit:cover;object-position:center center}
.lq-fx{display:block;z-index:2;object-fit:cover;object-position:center center;mix-blend-mode:var(--lq-mix,screen)}

/* Texture sources: present, loading, never painted. */
.lq-reveal{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;opacity:0;pointer-events:none}
.lq-type-pic,.lq-type{position:absolute;inset:0;z-index:0;opacity:0;pointer-events:none}
.lq-type{width:100%;height:100%;max-width:none}

.lq-canvas{position:absolute;inset:0;width:100%;height:100%;display:block;z-index:2;pointer-events:none}
.lq-copy{position:absolute;inset:0;z-index:3;pointer-events:none}

/* engine could not run → the wall and its light, full bleed */
.hero-lq.lq-off .lq-canvas,.hero-lq.lq-off .lq-reveal,.hero-lq.lq-off .lq-type-pic{display:none}

/* Settle to a still. site.js holds the FX plate on one frame over the wall,
   and the liquid has nothing to react to with no cursor, so the canvas comes
   out. visibility, not display, so an engine that initialises anyway still
   measures a correctly sized box instead of a 0×0 one.
   ?static deliberately does NOT hide the canvas: that is the screenshot route
   for reviewing the blob itself, and html.static already stops every
   transition and animation on the page. */
@media(prefers-reduced-motion:reduce){
  .hero-lq .lq-canvas{visibility:hidden}
}

/* Review hooks — set by the engine at mount, see build.py.
   ?stack=wall is Opie's "vice versa": FX underneath, the wall on top carrying
   the blend. For screen (and lighten, and multiply) that is the same picture:
   those modes are commutative and both layers are opaque. Only overlay,
   soft-light, hard-light, color-dodge and normal change with the order.
   ?mix=off hides the plate (the engine pauses it too): the wall alone. */
.hero-lq[data-lq-stack="wall"] .lq-fx{z-index:1;mix-blend-mode:normal}
.hero-lq[data-lq-stack="wall"] .lq-wall{z-index:2;mix-blend-mode:var(--lq-mix,screen)}
.hero-lq[data-lq-mix="off"] .lq-fx{display:none}
.hero-lq[data-lq-mix="off"] .lq-wall{mix-blend-mode:normal}

/* ==========================================================================
   DIVIDER PANELS — the green plaque (B5, Opie 21 Sep)
   The .brk-panel over every .break band (about, menu, events, store, and the
   Blog page's two) is the green enamel plaque. Source: C:/fenians/web
   elements/icons_buttons/button-plaque-wide-green-fullres.png (998x444, alpha
   kept: the notched corners read better cut out). It replaces the liquid
   glass, which is archived in docs/REMOVED-2026-09-21.md.

   Two layers on the panel's ::before, so the plaque holds its shape at any
   size and never shows a seam:
   - the FRAME, plaque-frame.webp: the plaque with its green field cut out
     (derived from that source PNG by a flood fill of the field). A 9-slice
     border-image, slice 164 source px on every side. The outer notch ends
     ~117px in and the inner field's notch ~150px in, so every curve sits in
     the four fixed corners; only the straight runs stretch, and they are
     uniform along their length. Nothing curved is ever stretched.
   - the FIELD, plaque-field.webp: a clean 698x300 cut of the plaque's own
     green (x 150-848, y 72-372), stretched once as a background under the
     frame, inset 68 source px so its edge hides under the frame's inner
     line. Filling the 9-slice centre instead (border-image `fill`) showed
     hairline seams and texture breaks at every slice line on a phone.
   --plq-b is how big those 164px draw: the frame's scale, one number, so the
   frame stays crisp and in proportion at any panel width.
   --plq-dim darkens the FIELD only, with --c-black: cream on the plaque's
   lightest streaks is 2.6:1 as supplied; at .34 every field pixel holds
   4.5:1 or better under the cream title and sub (median about 7.4:1).
   The house hard shadow follows the notched outline (filter: drop-shadow),
   on ::before so it never lands on the type; the title keeps its one flat
   orange drop and nothing else.
   The text stays inside the inner field. In source px the field starts 72px
   in and its corner cut reaches back to x 148 on the top run, so the padding
   holds the text box 92px down and 156px in (x .56 and x .951 of --plq-b):
   below the frame, and clear of the corner cut even for a line that runs the
   full width. Measured at 1440, 1024 and 390: every line box of the title
   and the sub lies inside the field.
   ========================================================================== */
.break .brk-panel.plaque{
  --plq-b:clamp(52px,7vw,100px);      /* the drawn size of the 164px slice */
  --plq-dim:.34;                      /* --c-black over the field, for the 4.5:1 sub */
  --plq-in:calc(100% - var(--plq-b) * .83);   /* the field: 68 source px in from each edge */
  isolation:isolate;                  /* the plaque's z-index:-1 stays inside the panel */
  padding:calc(var(--plq-b) * .56) calc(var(--plq-b) * .951)}
.break .brk-panel.plaque::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  border:0 solid transparent;
  border-image:url("../img/ui/plaque-frame.webp") 164 / var(--plq-b) stretch;
  background:
    linear-gradient(rgba(31,29,29,var(--plq-dim)),rgba(31,29,29,var(--plq-dim))) center / var(--plq-in) var(--plq-in) no-repeat,
    url("../img/ui/plaque-field.webp") center / var(--plq-in) var(--plq-in) no-repeat;
  filter:drop-shadow(var(--sh-w) var(--sh-w) 0 var(--c-black))}
/* A 1x screen at phone width draws the frame at about a third of its pixels:
   the 600px copy is plenty there. Same cut, scaled (164 x 600/998 = 99).
   Every 2x/3x phone keeps the full file. */
@media (max-width:860px) and (max-resolution:1.49dppx){
  .break .brk-panel.plaque::before{border-image-source:url("../img/ui/plaque-frame-600.webp");border-image-slice:99}
}
