/* ==========================================================================
   fx.css — the motion pass, 21 Sep 2026 (docs/MOTION-SPEC-2026-09-21.md), with
   batch 2 the same day (docs/BATCH2-SPEC-2026-09-21.md). One stylesheet for every
   fx engine,
   linked after site.css on the inner pages only (build.py FX_HEAD: about,
   team, menu, events, store, blog and every post page). The landing page
   never loads it.

   Six sections, one per engine:
     1  fx-type      the header reveal          assets/js/fx-type.js
     2  fx-boxes     the exploding bento        assets/js/fx-boxes.js
     3  fx-dividers  the stained-glass break    assets/js/fx-dividers.js
     4  fx-wipe      the page wipe              assets/js/fx-wipe.js    (no rules here: see the section)
     5  fx-hero      the page-hero video intro  assets/js/fx-hero.js
     6  fx-quote     the pinned quote (About)   assets/js/fx-quote.js
   Sections 4-6 were separate files (fx-wipe.css, fx-hero.css, fx-quote.css)
   until the integrator merged them on 21 Sep; their text is unchanged apart
   from each header's "where am I linked" line. The scripts load in their own
   order (build.py FX_ENGINES: wipe, hero, quote, boxes, dividers, type). No
   rule in one section depends on another section's position, except that
   5 and 6 must come after site.css, which every section does.

   Every hidden or moving state below is scoped under its engine's class on
   <html> (fx-type / fx-boxes / fx-dividers / fx-quote, and .hv-intro, which
   fx-hero adds only while an intro runs), and an engine adds that class only
   when it will animate. No JS, reduced motion or ?static: nothing here hides
   anything. What does apply in every mode hides nothing: the heading's
   accessible-name fix (1), .break{overflow-x:clip} (3), the dim resting look
   of the hero video (5) and the pinned quote's resting layout (6).
   Hidden states and final states are both plain CSS: the engines' animations
   only paint the journey between them, forwards or (C2) backwards.
   ========================================================================== */


/* ##########################################################################
   1 of 6 — fx-type: the header reveal   (assets/js/fx-type.js)
   ########################################################################## */

/* ==========================================================================
   fx-type — the header reveal (assets/js/fx-type.js). MOTION-SPEC 1, 2, 3, 6, 8.
   The split structure, the drop layer that replaces the stock ::before on
   split headers, and the pre-reveal hidden states. Every hidden state is
   scoped under html.fx-type, which the script adds only when motion is
   allowed — with no JS, reduced motion or ?static nothing here hides anything.
   Timings, distances and the stretch values live in the script's CONFIG.
   ========================================================================== */

/* Accessible name, in every mode. The stock drop repeats the headline in a
   ::before and Chrome reads generated content into the heading's name
   ("THE MENU THE MENU" in the AX tree). The alt text after the slash gives
   the pseudo an empty name. A browser that does not know the syntax drops
   the declaration and keeps site.css's, which paints identically. */
.wood::before{content:attr(data-text) / ""}

/* The split header. The face stays in flow exactly where the text was; the
   drop is the old ::before rebuilt as real spans over the same box at
   --drop-o, flat --c-orange in the Lines face, so the two wrap alike. */
.wood.fx-split{isolation:isolate}              /* the z-index:-1 drop paints inside the heading, never behind a box */
.wood.fx-split::before{content:none}           /* split headers only: the spans ARE the drop now */
.fxt-w{display:inline-block;white-space:nowrap} /* words: the drop's move by transform; the face mirrors them */
.fxt-drop{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);pointer-events:none}
.fxt-drop,.fxt-sr{-webkit-user-select:none;user-select:none}  /* copy-paste takes the face once, as it did with the ::before */
.fxt-hs .fxt-l{display:inline-block;transform-origin:50% 100%}  /* hover titles: a letter needs a box to scale; it grows upward */
.fxt-caret{position:absolute;left:0;top:0;background:currentColor;opacity:0;pointer-events:none}  /* zero layout; JS sizes and moves it */

/* Pre-reveal. Before the fonts gate splits a header, keep its box and hide
   its ink; once split, the face letters, the drop words and the header's
   kick / lede / hero tape wait at opacity 0 for their turn. */
html.fx-type .wood.fxt-armed:not(.fx-split),
html.fx-type .wood.fxt-armed:not(.fx-split)::before{color:transparent}
html.fx-type .fxt-armed .fxt-face .fxt-l,
html.fx-type .fxt-armed .fxt-drop .fxt-w,
html.fx-type .fxt-pre{opacity:0}

/* Paper never scrolls: print every header whole — even one caught
   mid-reveal (!important outranks a running animation). */
@media print{
  html.fx-type .fxt-l,
  html.fx-type .fxt-w,
  html.fx-type .fxt-f{opacity:1!important}
  .fxt-caret{display:none}
}


/* ##########################################################################
   2 of 6 — fx-boxes: the exploding bento   (assets/js/fx-boxes.js)
   ########################################################################## */

/* ==========================================================================
   fx-boxes — the exploding bento. Motion pass, 21 Sep 2026.
   Pairs with assets/js/fx-boxes.js.

   Every rule below is scoped under html.fx-boxes, which the script adds only
   when it is going to animate. With no JS, with reduced motion and with
   ?static none of it applies and nothing is hidden. The entrance itself is
   Web Animations on the individual translate / rotate / scale properties and
   fills backwards only (over the stagger delay), never forwards: the resting
   state of every box is plain site.css, so its seeded tilt and its hover are
   exactly what they were.
   ========================================================================== */

/* The travel of a parallax box. data-px owns `translate` on .room-img and
   .feature img (site.js writes --py), so the entrance cannot tween translate
   there without breaking the parallax; it tweens these two instead and the
   rule below adds them in. Registered so they interpolate. If fx-dividers
   registers the same names, the descriptors must be identical. */
@property --fx-x{syntax:"<length>";inherits:false;initial-value:0px}
@property --fx-y{syntax:"<length>";inherits:false;initial-value:0px}
html.fx-boxes .fx-bx[data-px]{translate:var(--fx-x,0px) calc(var(--py,0px) + var(--fx-y,0px))}

/* Waiting to enter: invisible and not clickable, but still in the layout
   (so it measures at rest) and still in the accessibility tree (so a screen
   reader loses nothing). Focus that lands on one brings it in at once. */
html.fx-boxes .fx-bx-hide{opacity:0;pointer-events:none}

/* Boxes enter from past the column edges, up to 190px. The section holding a
   group clips sideways so the page never widens — on a phone a wider page
   zooms the whole layout out. clip, not hidden: hidden makes a scroll
   container, and a scroll-container ancestor kills position:sticky (the
   events .ev-detail). The section spans the viewport, so the clip line IS the
   viewport edge and nothing that rests on the page is cut. Vertical overflow
   is untouched: stickers, tapes and shamrocks still hang past their boxes. */
html.fx-boxes .fx-bx-clipx{overflow-x:clip}

/* Hub & spoke. The spokes burst out of the pub, so they fly BEHIND the core
   (it is z-index 2) and come to the front again on landing. The dashed lines
   would point at empty space until then: they wait for the hub's own
   fx:landed, when site.js redraws them from the rest positions. Leaving, the
   hub loses .fx-bx-landed and the lines fade out first; the spokes wait the
   same .6s (fx-boxes CONFIG.hubLinesMs) before they fly home. */
html.fx-boxes .hub .spoke.fx-bx-flying{z-index:1}
html.fx-boxes .hub.fx-bx-g svg.lines{opacity:0;transition:opacity .6s cubic-bezier(.4,0,.2,1)}
html.fx-boxes .hub.fx-bx-g.fx-bx-landed svg.lines{opacity:1}

/* Paper never animates: a box that has not come in yet, or is caught
   mid-flight, still prints whole. */
@media print{html.fx-boxes .fx-bx{opacity:1!important}}


/* ##########################################################################
   3 of 6 — fx-dividers: the band slides in, the plaque assembles   (assets/js/fx-dividers.js)
   ########################################################################## */

/* ==========================================================================
   fx-dividers — batch 2, C1 (replaces the centre clip-path wipe). Driven by
   assets/js/fx-dividers.js. Every rule that moves or hides anything is scoped
   under html.fx-dividers, which the engine adds only when it will animate:
   with no JS, reduced motion or ?static the break is exactly site.css's.
   ========================================================================== */

/* The panel's entrance scale, as a number the engine can animate. Registered
   so it interpolates (an unregistered custom property would flip at 50%).
   inherits:false keeps it off the title and tapes inside the panel. --fx-x /
   --fx-y are registered in section 2 (same descriptors; one registration
   serves both engines). */
@property --fxd-s{syntax:"<number>";inherits:false;initial-value:1}

/* The band's slide. data-px owns `translate` (site.js writes --py), so the
   slide rides --fx-x and is added in. At rest --fx-x is 0px and this equals
   site.css's own translate:0 var(--py). Out, the engine writes an inline
   --fx-x that puts the whole band past the viewport edge, where .break's
   overflow-x:clip (below) hides it. */
html.fx-dividers .break .band{translate:var(--fx-x,0px) calc(var(--py,0px) + var(--fx-y,0px))}

/* The plaque's travel and entrance scale. Its `transform` is its centring,
   translate(-50%,-50%) rotate(--rot), and the individual `scale` applies
   ahead of that, so on its own .94 would pivot on the panel's visual
   bottom-right corner and drift it. The (s-1)x50% term cancels that exactly,
   so it grows about its visual centre; --fx-x / --fx-y are the exploded
   offset; --py is the parallax, carried through untouched. At rest
   (--fxd-s:1, --fx-x/y:0) both lines equal site.css's own values. */
html.fx-dividers .break .brk-panel{
  scale:var(--fxd-s);
  translate:calc((var(--fxd-s) - 1) * 50% + var(--fx-x,0px)) calc(var(--py,0px) + (var(--fxd-s) - 1) * 50% + var(--fx-y,0px))}

/* The plaque waits for the band, and goes before it. Opacity, never display
   or visibility, so the text still lays out and its fonts still load; its
   parts (sub-line, tapes) are inside it and go with it. Not clickable while
   it is not there (the hover-stretch title would catch the pointer). */
html.fx-dividers .break.fxd-pre .brk-panel{opacity:0;pointer-events:none}

/* Paper never scrolls: print every break in, its band home, its plaque on.
   The band's --fx-x is written inline, hence !important. */
@media print{
  html.fx-dividers .break .band{--fx-x:0px!important}
  html.fx-dividers .break .brk-panel,
  html.fx-dividers .break .brk-panel > *{opacity:1!important}
}

/* .break{overflow-x:clip} is stated once, in site.css (22 Sep). It has to
   hold on a page that fails to fetch this sheet too, and it was written here
   identically; two copies of one value is one to keep in step by hand. */


/* ##########################################################################
   4 of 6 — fx-wipe: the page wipe   (assets/js/fx-wipe.js)
   ########################################################################## */

/* ==========================================================================
   No rules here, on purpose. The wipe's CSS all lives inline in every
   page's <head>, as WIPE_HEAD (build.py, generated by wipe_head.py beside
   it), for two reasons:
   - the cover (html::before, its four panels and their blurred first-paint
     copies, its positions and transitions) must be on screen at the FIRST
     paint of a page reached by a wipe, before any stylesheet has loaded;
   - the landing page loads no fx.css, but it wipes too, and the motion
     blur's zero-size <svg> needs its rule there (without it the svg sits in
     a line box at the foot of <body> and the no-scroll landing page scrolls).
   So the three rules that were assets/css/fx-wipe.css (the svg's box, the
   blur filter hook-up, forced colours) moved into WIPE_HEAD's <style> on
   21 Sep and fx-wipe.css was deleted. See wipe_head.py _css().
   ========================================================================== */


/* ##########################################################################
   5 of 6 — fx-hero: the page-hero video intro   (assets/js/fx-hero.js)
   ########################################################################## */

/* ==========================================================================
   fx-hero — the page-hero video and its intro (C3). Pairs with
   assets/js/fx-hero.js. Merged here as section 5 by the integrator (21 Sep);
   it was assets/css/fx-hero.css, linked after site.css. Every page with a
   [data-hero-video] hero loads fx.css.

   THE DEFAULT IS THE DIM STATE. With no JS (the video has no autoplay
   attribute, so it never plays), with reduced motion, ?static, Save-Data, and
   after every intro, the hero looks the way the old dim background image did:
   the video (or its poster, frame 0) at the old image's .32 opacity and
   .8 saturation under the old .5 -> night gradient. Those three numbers are
   site.css's old .page-hero .bg treatment, verbatim, so the heroes sit as
   dim and integrated as before (measured: see the engine's return notes).

   The intro state (.hv-intro, added by the engine before first paint) is the
   video at full colour with no overlay. The engine takes .hv-intro off when
   the glass in the footage clears; .hv-ramp carries the ~1.2s ease back to
   the dim state. Nothing here hides text, and nothing here is scoped to JS
   except the intro, so a page whose script never runs is always the dim hero.
   ========================================================================== */

/* The layer. .page-hero .bg already positions it (site.css). The Blog's .hero
   has no .bg rule of its own, so the box is set here for any hero that
   carries the video; the values are the ones .page-hero .bg uses. */
[data-hero-video] > .bg.hv{position:absolute;inset:-6%;z-index:-1;overflow:hidden}
/* The Blog hero keeps its mosaic (z -1) ABOVE the picture, as .hero-bg was (-2). */
.hero[data-hero-video] > .bg.hv{z-index:-2}
/* site.css draws the old gradient as .page-hero .bg::after. .hv-tint replaces
   it (it has to fade out for the intro, and a pseudo-element cannot be
   targeted by the engine's state classes as cleanly), so the old one is off
   wherever the video is. (0,3,1) beats site.css's (0,2,1). */
[data-hero-video] .bg.hv::after{content:none}

.hv-vid,
.hv-poster > img{display:block;width:100%;height:100%;object-fit:cover;
  opacity:var(--hv-dim,.32);filter:saturate(var(--hv-sat,.8))}
/* The poster <picture> under the video (build.py HV_BG) steps out once the
   video holds a frame of its own (fx-hero puts .hv-frame on the hero): at the
   dim .32 the two would otherwise stack and read brighter. */
.hv-frame .hv-poster{visibility:hidden}
.hv-tint{position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(31,29,29,.5),var(--night))}

/* Per hero: each keeps the gradient its old image had in site.css.
   Page heroes: .page-hero .bg img{opacity:.32;filter:saturate(.8)} + the
   ::after .5 -> night, verbatim.
   The Blog's .hero: its own ::after (a faint orange bloom, .6 -> .2 -> night)
   and its object-position, but NOT its old .5 opacity: the interior footage
   is about 2.7x brighter than bar-wide.png (mean luminance .110 vs .040), and
   at .5 the worst frame put the headline at 3.5:1. At .32 — the page heroes'
   number, so the site has one dim level — the worst frame of the whole loop
   measures 5.3:1 on desktop and 4.8:1 on a phone (the old image: 3.7:1). */
.hero[data-hero-video] .hv-vid,
.hero[data-hero-video] .hv-poster > img{object-position:center 45%}
.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 intro: full colour, full strength, no overlay. The Blog's mosaic is an
   overlay too, so it steps out with the tint and comes back with it. */
.hv-intro .hv-vid,
.hv-intro .bg .hv-poster > img{opacity:1;filter:saturate(1)}   /* (0,3,1): above site.css .page-hero .bg img */
.hv-intro .hv-tint,
.hv-intro .hero-mosaic{opacity:0}

/* The header waits for the glass (C3). On the page heroes fx-type holds the
   kick, title, tape and lede itself (it waits for the hero's fx:landed). Two
   cases it does not cover, so the copy never sits on the full-colour footage:
   - the Blog hero's tape and buttons (fx-type only holds its .wood titles);
   - any hero on a page where fx-type is not running (the Blog today, or a
     page whose fx-type.js failed to load): the whole copy block waits.
   - the Blog hero's meta strip (the address, "Move the light"), which sits
     outside .hero-copy and read 1.6-2.6:1 on the footage (verify, 21 Sep);
   - the Store's Bag button, which sits beside the heading's block, not after
     it, so fx-type does not hold it (1.5:1 on the glass). It is also not
     clickable while unseen: a tap there lands the intro and nothing else.
   All three come in with the tint ramp below.
   .hv-intro exists only while the engine is running an intro, so none of
   this can apply with no JS, reduced motion or ?static. */
.hero.hv-intro .hero-copy > :not(.wood),
.hero.hv-intro .hero-meta,
.page-hero.hv-intro .cart-btn,
html:not(.fx-type) .hero.hv-intro .hero-copy,
html:not(.fx-type) .page-hero.hv-intro > .wrap{opacity:0}
.page-hero.hv-intro .cart-btn{pointer-events:none}

/* The ramp back to dim. The engine sets --hv-ramp (1200ms when the glass
   clears on its own, shorter on a skip) and adds .hv-ramp in the same frame
   it removes .hv-intro, so the transition runs from the intro state. Only
   opacity and filter move: both composite on the GPU over a playing video. */
.hv-ramp .hv-vid,
.hv-ramp .bg .hv-poster > img,
.hv-ramp .hv-tint,
.hv-ramp .hero-mosaic{transition:opacity var(--hv-ramp,1200ms) ease,filter var(--hv-ramp,1200ms) ease}
.hv-ramp .hero-copy,
.hv-ramp .hero-copy > :not(.wood),
.hv-ramp .hero-meta,
.page-hero.hv-ramp .cart-btn,
.page-hero.hv-ramp > .wrap{transition:opacity var(--hv-ramp,1200ms) ease}

/* The nav over the full-colour intro (fx-hero.js CONFIG.solidNav). The cream
   links measured 1.5:1 on the bright glass; while html.hv-nav is on, the nav
   wears the bar it already wears once the page scrolls. The same three values
   as site.css .nav.is-solid: keep them in step. The nav's own .35s transition
   brings the bar in and takes it away with the tint. */
html.hv-nav .nav{background:rgba(31,29,29,.88);border-bottom:2px solid var(--lead);box-shadow:0 var(--sh-w) 0 var(--gold)}

/* ---- the pause / play toggle (WCAG 2.2.2) --------------------------------
   Injected by the engine (no JS = no playing video = no button). Bottom
   right of the hero, clear of the left-set heading and lede. The house small
   square (the calendar's prev/next is the model): cream 2px rule, cream
   glyph, a hard --sh-w drop, on the box stock (teal). It rests at .85 to stay
   quiet; the fill is solid because a see-through one let the bright intro
   glass under the glyph (measured 2.6:1; solid at .85 keeps it over 3:1 on
   the brightest frame). No hover glow: hover and focus only lift it to 1. */
.hv-btn{position:absolute;z-index:3;right:var(--gut);bottom:clamp(12px,2.2vw,26px);
  width:40px;height:40px;display:grid;place-items:center;padding:0;
  border:2px solid var(--cream);background:var(--stock);color:var(--cream);
  box-shadow:var(--sh-w) var(--sh-w) 0 var(--lead);
  rotate:var(--hv-rot,0deg);opacity:.85;transition:opacity .15s}
.hv-btn:hover,.hv-btn:focus-visible{opacity:1}
.hv-ico{display:block;width:12px;height:14px;
  background:linear-gradient(90deg,currentColor 0 34%,transparent 34% 66%,currentColor 66%)}
.hv-btn[data-state="paused"] .hv-ico{width:13px;margin-left:3px;background:currentColor;
  -webkit-clip-path:polygon(0 0,100% 50%,0 100%);clip-path:polygon(0 0,100% 50%,0 100%)}
@media (pointer:coarse){.hv-btn{width:44px;height:44px}}   /* a thumb-sized target */
@media print{.hv-btn{display:none}}


/* ##########################################################################
   6 of 6 - fx-quote: the quote band on About   (assets/js/fx-quote.js)
   ########################################################################## */

/* ==========================================================================
   fx-quote - the About quote band (SPEC B6; contract 2). Script:
   assets/js/fx-quote.js. Merged here as section 6 by the integrator (21 Sep);
   it was assets/css/fx-quote.css. Only About carries a .quote-band, so
   everything below matches nothing on the other pages.

   REBUILT 24 SEPTEMBER. Opie, against inline-media-text.framer.website: the
   pictures belong INSIDE the sentence. So the four framed pieces that used to
   sit in bands above and below the quote, and the scroll pin that slid them
   in, are gone; the pictures are now .im chips in the line itself and the
   section is one plain band. The old stage is in docs/REMOVED-2026-09-24.md.

   WHAT IS UNTOUCHED, because Opie asked for it to be: the quote's face, its
   size, its measure and its narrow brand-colour drop. Those live in site.css
   (.quote). Nothing here writes a font, a colour or a shadow on the type.

   THE CHIP. A square, about one line tall, sized in `em` of the quote, so it
   scales with the type at every breakpoint instead of needing its own ladder.
   It is framed like every other box on this site: 2px lead border, the
   --sh-w hard shadow in a brand colour, and a seeded tilt inside the 1.45 deg
   ceiling (site.js ROT_SEL carries .im). Three are stills. The last is the
   train lamp, and it is the ONLY thing in the section that moves.

   STATES. Nothing is hidden and nothing is pinned, so no JS, reduced motion
   and ?static all render exactly what a full browser renders, with both
   videos resting on their posters.
   ========================================================================== */

.quote-band{
  /* ---- knobs ---- */
  --qb-tint-flat:.42;          /* --c-black over the whole loop */
  --qb-tint-core:.54;          /* and this much more behind the quote (radial) */
  --qb-tint-shape:60% 48%;     /* the radial's radii, as % of the band */
  --qb-tint-solid:38%;         /* full strength out to this % of the radii */
  --qb-pad:clamp(72px,12svh,140px);

  position:relative;
  display:grid;
  place-items:center;
  min-height:100vh;
  min-height:100svh;
  padding:var(--qb-pad) var(--gut);
  overflow:clip;               /* clip, never hidden: hidden makes a scroll container */
  isolation:isolate;
}
.pq-bg{position:absolute;inset:0;z-index:0;display:block;width:100%;height:100%;object-fit:cover}
.pq-tint{position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    radial-gradient(ellipse var(--qb-tint-shape) at 50% 52%,rgba(31,29,29,var(--qb-tint-core)) 0,rgba(31,29,29,var(--qb-tint-core)) var(--qb-tint-solid),rgba(31,29,29,0) 100%),
    rgba(31,29,29,var(--qb-tint-flat))}

/* the quote: on top, centred, its own measure. Cream is the only swatch that
   clears 4.5:1 over the tinted loop (measured, see the engine notes); the
   face and the em's face are site.css's (Duality Steel, content-C). */
.quote-band .pq-copy{position:relative;z-index:3;width:auto;max-width:none;margin:0;padding:0}
.quote-band .quote{color:var(--c-cream);margin:0 auto}
.quote-band .quote em{color:var(--c-cream)}
/* The chips are wider than a word, so the 22ch measure site.css gives the
   quote would break the line in the wrong places. This is the only thing in
   this file that touches the type block, and it moves no type. */
.quote-band .quote{max-width:min(20ch,100%)}

/* ---- the inline chips ---- */
.im{
  --im-h:1.2em;                /* height, in the quote's own type size */
  --im-ar:1;                   /* square: the reference's 50x50 and 60x60 chips */
  --im-sh:var(--c-orange);     /* hard-shadow colour, set per chip in build.py */
  display:inline-block;
  vertical-align:middle;
  position:relative;
  width:calc(var(--im-h) * var(--im-ar));
  height:var(--im-h);
  margin:0 .16em;
  /* Sit on the x-height rather than the line box: vertical-align:middle puts a
     square chip slightly low against a cap-heavy face like this one. */
  translate:0 -.08em;
  border:2px solid var(--c-black);
  box-shadow:var(--sh-w) var(--sh-w) 0 var(--im-sh);
  background:var(--c-black);
  rotate:var(--rot,0deg);
  overflow:hidden;
}
.im > img,.im > video{display:block;width:100%;height:100%;object-fit:cover}

/* ---- the video toggle (WCAG 2.2.2): bottom-left corner of the band ---- */
.pq-toggle{position:absolute;z-index:4;left:var(--gut);bottom:18px;width:40px;height:40px;padding:0;
  display:grid;place-items:center;
  background:var(--c-cream);color:var(--c-black);border:2px solid var(--c-black);
  box-shadow:var(--sh-w) var(--sh-w) 0 var(--c-orange);cursor:pointer}
/* The house focus ring is orange, which all but vanishes against this
   button's own orange shadow over the amber loop. Cream reads (measured). */
.pq-toggle:focus-visible{outline-color:var(--c-cream)}
@media (pointer:coarse){.pq-toggle{width:44px;height:44px}}   /* thumb-sized, as the hero's .hv-btn */
.pq-ico{position:relative;display:block;width:14px;height:16px}
.pq-ico::before,.pq-ico::after{content:"";position:absolute;top:0;bottom:0;width:5px;background:currentColor}
.pq-ico::before{left:0}.pq-ico::after{right:0}
/* paused: a play triangle */
.pq-toggle.is-paused .pq-ico::before{left:2px;width:0;height:0;background:none;border-style:solid;border-width:8px 0 8px 13px;border-color:transparent transparent transparent currentColor}
.pq-toggle.is-paused .pq-ico::after{display:none}

/* Narrow / portrait. The chip stays tied to the type, so it shrinks with it;
   only the band's padding and the tint's shape need saying. */
@media (max-aspect-ratio:4/5),(max-width:700px){
  .quote-band{--qb-pad:clamp(64px,10svh,104px);--qb-tint-shape:92% 34%;--qb-tint-solid:56%}
  .quote-band .quote{max-width:min(16ch,100%)}
  .im{--im-h:1.1em;margin:0 .12em}
}

@media print{
  .quote-band{min-height:0;padding:24px 0}
  .pq-bg,.pq-tint,.pq-toggle{display:none}
  .im{rotate:var(--rot,0deg)}
}
