/* ============================================================================
   Features — the screenshot page.

   Same cabinet as the rest of the site (skeuo.css): the page sits on the game's
   grass, and every sentence sits on a brass-rimmed espresso panel, because loose
   type on the grass tile measures ~1.04:1 (the footer taught that once already).

   The COPY here is the owner's, verbatim from his features document, including
   his en-dashes, his single quotes and his spelling. The only words on this page
   that are not his are the alt attributes, which are descriptions of the
   pictures for people who cannot see them, not page copy.

   THE HEADERS ARE SILVER AND GOLD, and they are ONE metal rather than two
   colours of text: the leaf is a chrome horizon — sky-white down to the ground
   line just above the middle, then the cabinet's own brass reflected back up
   from below. give.css:1498 already lights the donate wordmark this way ("a warm
   gold reflection below it, which is also how the letters keep a thread of the
   cabinet's brass"); this is that construction at heading size, re-lit for the
   espresso panel instead of cream paper.
   ============================================================================ */

/* ONE column for the whole page: every panel and every picture share two edges.
   --fcol is wider than the about page's --col because the subject here is the
   screenshots, not a reading measure; the prose gets its own narrower measure on
   .feat__body, where a ch is the right size. It is also a CEILING, not a width —
   a tile is capped at min(--natw, …), so a 729px screenshot is never blown up
   past its own pixels however wide the column gets. */
.feat{ --fcol:1060px; max-width:1200px; margin:0 auto; padding:0 20px 30px; }

/* ── The metal ────────────────────────────────────────────────────────────────
   Silver above the horizon, gold below it. The stops are a reflection, not a
   ramp: white where the sky lands on the top bevel, a HARD dark band at the
   ground line at 44-47% (the line the eye reads as polish), then the warm floor
   thrown back up the lower half and a dark lip at the foot.

   ⚠ text-shadow:none is LOAD-BEARING. skeuo.css:201 puts a text-shadow on every
   h1/h2/h3, it inherits into child spans, and a text-shadow is painted ON TOP OF
   a background-clip:text fill — the whole line renders flat dark brown. The
   depth has to come from filter:drop-shadow(), which runs on the finished
   raster and therefore lands behind the metal.

   ⚠ The extrusion is NOT pitch.css's. Those browns (#3A2415 / #33200E) were lit
   for cream playbill paper; on a #2F2114 panel they disappear and the letter
   loses its struck edge. This one is a near-black extrusion for the depth plus
   one faint brass halo so the metal separates from the espresso behind it.

   ⛔⛔ AND THE HORIZON IS KEYED TO THE LINE, NOT TO THE ELEMENT. A
   background-clip:text fill paints across the element's whole content box, so a
   single 0-100% gradient puts the horizon at one height for the WHOLE heading —
   which lands in a different place on every letter, because an ascender starts
   near the box top and an x-height letter starts a third of the way down. The
   first cut of this rule rendered "Build the amusement park" with silver B/l/d/k
   and gold u/i/a/m/s: two colours of text, which is the opposite of one lit
   object, and it is invisible on a short single word like the H1.

   background-size:100% <line-height> with the default repeat gives EVERY LINE its
   own full sweep, so a heading that wraps to three lines is three struck lines
   rather than one gradient smeared down a paragraph. The stops below are then
   percentages OF A LINE BOX, and Trebuchet's own metrics say where the letters
   are inside it: cap top 19%, x-height top 36%, baseline 81%. The horizon sits at
   57% — just below the middle of the x-height band — so a lowercase letter is cut
   roughly in half and an ascender carries more sky, which is what a taller face
   catching the same light actually does. */
.feat .mg{
  --mg-lh:1.14;                   /* the line-height AND the gradient's period */
  line-height:var(--mg-lh);
  background-image:linear-gradient(180deg,
    #FFFFFF   0%,   /* above the caps */
    #FFFFFF  17%,   /* the sky on the cap bevel */
    #EEF4F9  27%,
    #CDD8E1  42%,
    #98A5B1  53%,
    #5E6A75  57%,   /* THE HORIZON — the dark band of the ground line */
    #FFF4D4  61%,   /* the floor, thrown straight back up */
    #F7D488  69%,
    #E2B65B  78%,
    #C08F29  85%,   /* the baseline is at 81% — the lip starts under it */
    #7E5A12 100%);  /* --brass-dark, in the descenders */
  background-size:100% calc(var(--mg-lh) * 1em);
  background-repeat:repeat;
  -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent;
  color:transparent;              /* the fallback if text-fill-color is missing */
  text-shadow:none;               /* see above — this one is load-bearing */
  filter:
    drop-shadow(0 0 .012em rgba(0,0,0,.92))        /* the edge, on any ground */
    drop-shadow(.020em .020em 0 #150B03)
    drop-shadow(.020em .020em 0 #150B03)
    drop-shadow(.018em .018em 0 #241505)
    drop-shadow(0 .05em .10em rgba(0,0,0,.72))     /* the sheet under it */
    drop-shadow(0 0 14px rgba(245,194,90,.22));    /* the panel's own brass, faint */
}
/* A browser without background-clip:text gets the game's flat gold title colour
   rather than a transparent, i.e. invisible, heading. */
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  .feat .mg{
    background-image:none;
    -webkit-text-fill-color:var(--title-gold); color:var(--title-gold);
    text-shadow:0 2px 0 var(--emboss-dark), 0 3px 6px rgba(0,0,0,.5);
    filter:none;
  }
}

/* ── Hero ─────────────────────────────────────────────────────────────────── */
/* WOFF2 first, TTF kept behind it, both built from one source by
   tools/make_woff2.py — the convention skeuo.css and pitch.css already state.
   Grand Hotel is the page's one display face: a retro script in the family the
   owner pointed at, cut finer than the obvious one, which is what "maybe not
   the exact font, somewhat pretentious" asks for. */
@font-face{
  font-family:"GrandHotel";
  src:url("../assets/fonts/GrandHotel.woff2") format("woff2"),
      url("../assets/fonts/GrandHotel.ttf") format("truetype");
  font-weight:400; font-style:normal; font-display:swap;
}

/* --hook-fs is declared ONCE and read twice: by the wordmark's font-size and by
   the negative margin that pulls the ornament up under it. They are one quantity
   — see the note on .feat__hero .feat__rule — and holding it in two places is how
   they drift apart at the clamp's ends. */
.feat__hero{ --hook-fs:clamp(84px,19vw,280px); text-align:center; padding:44px 0 22px; }
.feat__hook{
  --mg-lh:1.18;                   /* a script needs room: the descenders are long */
  font-family:"GrandHotel","Segoe Script",cursive;
  margin:0; font-size:var(--hook-fs); font-weight:400;
  letter-spacing:.006em; text-wrap:balance;
}
/* ⚠ NO letter-spacing beyond a hair. Grand Hotel is a CONNECTED script — its
   exit strokes are drawn to meet the next letter's entry — so spacing the word
   out to fill more width breaks every join and the wordmark falls apart into
   loose letters. Length here comes from SIZE, which is why the clamp is large. */

/* ── The wordmark's metal ─────────────────────────────────────────────────────
   ⛔ THE COLOUR DOES NOT MOVE. An earlier cut travelled the whole gold-silver-gold
   leaf across the letters, and the owner's word for it was a WIPE: when the base
   ramp itself slides, the wordmark's colour is different every second and the eye
   reads a changing thing rather than a struck thing. The metal is STILL now, and
   what moves is a separate specular band (.feat__sheen) that passes over it
   occasionally, plus the glints. A highlight travelling over fixed metal is a
   shimmer; the metal travelling under a fixed eye is a wipe.

   ⭐ THE STOPS ARE THIS FACE'S OWN METRICS, not the section heads'. Grand Hotel's
   ink sits differently in the line box from Trebuchet's, measured off the TTF at
   --mg-lh 1.18:  ink top 12.8% · x-height top 38.3% · baseline 72.6% ·
   deepest descender 78.3%.  So the horizon goes at 56% — just under the middle of
   the x-height band — and the dark lip lands below the baseline where the
   descenders are, instead of at 100% of a box the letters never reach.

   ⚠ drop-shadow, NEVER text-shadow — give.css:930 records the same rule for the
   donate wordmark. A text-shadow is painted ON TOP of a background-clip:text
   fill and buries the metal; drop-shadow runs on the finished raster, so the
   glow lands behind the letters where it belongs.

   ⛔⛔ AND NOTHING OVER THIS WORDMARK MAY SHARE ITS FILTERED BOX. `filter` applies
   to an element's WHOLE rendered subtree, so while the stars were children of the
   heading they were each given the heading's three near-black extrusion shadows —
   and a star is mostly transparent, so what reached the screen was the shadows:
   four dark smudges over the word, exactly upside-down from the intent. The metal
   is on an inner .feat__ink span; the stars and the sheen are its SIBLINGS inside
   .feat__spark, which carries no filter and only exists to be the positioning box.
   ⚠ Anything else added over this wordmark goes beside .feat__ink, never inside. */
.feat__hook .feat__ink{
  /* ⚠ RE-DECLARED, NOT INHERITED. `.feat .mg` sets --mg-lh:1.14 on the element
     itself, so the 1.18 on .feat__hook never reaches this span — which left the
     metal's stops applied to a line box 3.4% shorter than the one they were
     measured against, and left the sheen (which DOES inherit 1.18) sitting a few
     pixels lower than the ink it is supposed to lie exactly on top of. A custom
     property set on the element beats one inherited from its parent. */
  --mg-lh:1.18;
  display:inline-block;
  background-image:linear-gradient(180deg,
    #FFFFFF   0%,
    #FFFFFF  14%,   /* the sky on the top of the letter */
    #EFF5FA  26%,
    #CFDAE3  40%,
    #98A5B1  51%,
    #5E6A75  56%,   /* THE HORIZON — just under the middle of the x-height band */
    #FFF4D4  61%,   /* the floor, thrown straight back up */
    #F7D488  67%,
    #E2B65B  72%,   /* the baseline is at 72.6% */
    #B8862A  77%,
    #7E5A12 100%);  /* --brass-dark, down in the descenders */
  background-size:100% calc(var(--mg-lh) * 1em);
  background-repeat:repeat;
  filter:
    drop-shadow(0 0 .012em rgba(0,0,0,.92))
    drop-shadow(.014em .014em 0 #150B03)
    drop-shadow(.014em .014em 0 #150B03)
    drop-shadow(0 .04em .08em rgba(0,0,0,.72))
    drop-shadow(0 0 9px  rgba(255,246,214,.5))     /* the near glow */
    drop-shadow(0 0 30px rgba(245,194,90,.38))     /* the far one */
    drop-shadow(0 0 56px rgba(226,232,240,.18));   /* and the silver, faintly */
}

/* ── The occasional shimmer ───────────────────────────────────────────────────
   A SECOND copy of the word, laid exactly over the first, carrying nothing but a
   narrow white band on a transparent ground. For most of its cycle the band is
   parked off the right-hand edge and the layer paints nothing at all; once every
   13 seconds it crosses in about two. That is why it reads as a glint rather than
   as a wipe — the gold underneath never changes, a highlight simply passes over it.

   ⚠ It carries NO filter: a specular highlight does not cast a shadow, and giving
   it the ink's extrusion would print a second black word over the first. */
@keyframes feat-sheen{
  0%, 86% { background-position:132% 50%; }
  100%    { background-position:-32% 50%; }
}
.feat__sheen{
  position:absolute; left:0; top:0; pointer-events:none;
  white-space:nowrap; line-height:var(--mg-lh);
  background-image:linear-gradient(104deg,
    rgba(255,255,255,0) 40%,
    rgba(255,255,255,.30) 47%,
    rgba(255,255,255,.92) 50%,
    rgba(255,255,255,.30) 53%,
    rgba(255,255,255,0) 60%);
  background-size:230% 100%; background-repeat:no-repeat;
  background-position:132% 50%;
  -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent; color:transparent;
  text-shadow:none;
  animation:feat-sheen 13s linear infinite;
}

/* The glints: four-point stars that wink over the wordmark on their own beats.
   Drawn as two crossed tapers via conic-gradient rather than set as a character
   — pay.css:133 records two prior tofu bugs from ornaments typed as glyphs, and
   a star that renders as an empty box is worse than no star.
   ⚠ Each carries its OWN duration as well as its own delay: on one shared period
   they drift into step and start blinking together, which reads as a fault. */
.feat__spark{ position:relative; display:inline-block; }
.feat__spark i{
  position:absolute; display:block; pointer-events:none;
  width:var(--sz); height:var(--sz); margin:calc(var(--sz) / -2) 0 0 calc(var(--sz) / -2);
  left:var(--x); top:var(--y);
  background:
    radial-gradient(closest-side, #FFFFFF, rgba(255,248,220,.85) 26%, rgba(255,226,150,.30) 52%, transparent 74%),
    conic-gradient(from 0deg,
      rgba(255,255,255,.95) 0deg,  transparent 12deg, transparent 78deg,
      rgba(255,255,255,.95) 90deg, transparent 102deg, transparent 168deg,
      rgba(255,255,255,.95) 180deg, transparent 192deg, transparent 258deg,
      rgba(255,255,255,.95) 270deg, transparent 282deg, transparent 348deg);
  -webkit-mask-image:radial-gradient(closest-side, #000 0%, #000 30%, transparent 76%);
          mask-image:radial-gradient(closest-side, #000 0%, #000 30%, transparent 76%);
  opacity:0; transform:scale(.4) rotate(0deg);
  animation:feat-twinkle var(--dur,4.4s) ease-in-out infinite;
  animation-delay:var(--d);
}
@keyframes feat-twinkle{
  0%,72%,100%{ opacity:0; transform:scale(.35) rotate(0deg); }
  80%        { opacity:1; transform:scale(1)   rotate(28deg); }
  88%        { opacity:.55; transform:scale(.7) rotate(44deg); }
}

/* ⚠ The whole show stops for anyone who asked it to. The metal is static, so
   stopping the sheen and the stars leaves the wordmark exactly as designed —
   there is no half-swept gradient to park, which is the other reason the still
   base is the better construction. */
@media (prefers-reduced-motion:reduce){
  .feat__sheen{ animation:none; background-position:132% 50%; }
  .feat__spark i{ animation:none; opacity:0; }
}
/* ⚠ THE HOOK IS THE ONE THING ON THIS PAGE SET LOOSE ON THE GRASS, and it is
   under the 3.0 AA bar. tools/contrast_probe.py, 1440px:

       /features  .feat__ink    p05 1.10   median 1.84     (the script, as shipped)
       /features  .feat__ink    p05 1.04   median 1.88     (the same, when it wiped)
       /features  .feat__hook   p05 1.10   median 1.83     (the earlier grotesque)
       /about     .about__hook  p05 1.52   median 1.83     (shipped)
       /terms     .pay__hook    p05 1.52   median 1.78     (shipped)

   Re-measured at every step — grotesque 1.10, script-with-a-wipe 1.04, script on
   still metal at twice the size 1.10. The number does not move: it is set by the
   fact that a metal leaf must cross the grass's own mid luminance somewhere, not
   by the face, the size or the animation. The glimmer's glow cannot help the number and is not
   meant to — a probe that renders the background WITHOUT the text is blind to a
   halo by construction.

   That is the site's existing convention for a hero, not a new hole: about.css:24
   and pay.css:49 both set the hook loose there on purpose, and the chip rule the
   footer taught is about SMALL loose sentences, which this page has none of — its
   every paragraph is on a plate at p05 11.84.

   It is also not fixable while the headline is metal. Grass is #73B353, a MID
   luminance, and a struck-metal leaf necessarily runs from white through mid to
   dark, so some band of every letter must sit near the ground's own value. A
   1.5px near-black -webkit-text-stroke was tried here and MEASURED: it moved p95
   2.61 -> 3.67 and left p05 at 1.10 and the worst ink pixel at 1.00, because the
   probe scores the GLYPH CORE and a stroke is rim. It was removed rather than
   kept with a comment claiming a win it did not deliver. Raising this number
   means putting the hook on a dark chip, which is a change to how every page on
   the site opens — the owner's call, not a silent one. */

/* ── The divider: a drawn steel rail with two brass collars ────────────────────
   A SHAPE, never a character — pay.css:133 records two prior tofu bugs from
   ornaments typed as glyphs. The rail is one lit face between two dark arrises,
   which is what a length of round bar looks like; the collars are the cabinet's
   brass bevel stack, so the two metals of the headings appear again as hardware. */
.feat__rule{
  width:min(100%,var(--rule-w,420px)); aspect-ratio:600 / 112;
  margin:0 auto;
  background:url("../assets/logo/baroque-rule.svg?v=5949ea71") center/contain no-repeat;
  /* The relief is IN the file — a vertical gold ramp with a near-black rim on
     every contour. Only the shadow it casts belongs here, because that depends
     on the ground it is lying on and the file does not know which page it is. */
  filter:drop-shadow(0 2px 3px rgba(0,0,0,.55));
}
/* THE HEADER'S RULE RUNS THE WHOLE SECTION. It is the wide cut of the ornament —
   a longer run with five scroll units a side, not the compact motif stretched,
   which at this length would be two enormous curls with a bare stem between them.
   100% of .feat's content box, so it is wider than the 1060px panels below it and
   reads as the header's own bottom edge rather than as another divider. */
.feat__hero .feat__rule{
  /* ⚠ PULLED UP BY THE EMPTY PART OF THE LINE BOX. Grand Hotel's deepest
     descender reaches 78.3% of a 1.18em line box, so 21.7% of the wordmark's
     height — 70px at the top of the clamp — is guaranteed blank space under the
     letters. Left alone the ornament sits that far adrift and the header reads as
     two unrelated objects. -0.15em of the HOOK's size closes it and stays closed
     at every clamp value, which a fixed pixel margin cannot do. */
  margin-top:calc(var(--hook-fs) * -0.15); --rule-w:100%;
  aspect-ratio:1240 / 104;
  background-image:url("../assets/logo/baroque-rule-wide.svg?v=c596cdc8");
}
/* The closing rule is the same long cut, so the page opens and shuts on one shape. */
.feat__end{
  --rule-w:min(100%, 900px); aspect-ratio:1240 / 104; margin:38px auto 10px;
  background-image:url("../assets/logo/baroque-rule-wide.svg?v=c596cdc8");
}

/* ── A section: the panel of words, then that section's pictures ──────────── */
.feat__sec{ margin:44px auto 0; }
.feat__sec:first-of-type{ margin-top:34px; }

.feat__plate{
  max-width:var(--fcol); margin:0 auto;
  background:linear-gradient(180deg,#2F2114 0%,#1B1108 100%);
  border:1px solid var(--brass-mid); border-radius:12px;
  box-shadow:0 0 4px 1px rgba(0,0,0,.5) inset, var(--shadow-deep);
  padding:34px 46px 36px;
}
/* The panel is as wide as the pictures under it — one spine down the page — but
   PROSE at 968px runs past 100 characters a line, so the HEADING gets the panel
   and the SENTENCE keeps its own measure, centred under it.

   Both halves of that were wrong once. Letting the paragraph start at the panel's
   left edge and stop at 70ch left 270px of dead panel down the right-hand side
   and read as a layout bug; capping the HEADING at the same measure then wrapped
   every one of them onto two lines inside a wide, half-empty box. A heading is a
   display object and wants the full width; a sentence is not and does not. */
.feat__col{ margin:0 auto; }

.feat__title{
  margin:0; font-size:clamp(27px,3.9vw,44px);
  text-align:center; text-wrap:balance;
}
/* The heading's own flourish, tucked under the words. Smaller than the page
   dividers so it reads as an ornament for THAT heading rather than as a break
   between sections — same carving, two sizes, which is how a real printer's
   fleuron set works. */
.feat__title + .feat__rule{ --rule-w:340px; margin:6px auto 14px; }

/* --fread is declared IN ch on this element rather than on .feat, because a ch
   is the width of a "0" at the element's OWN font-size: 70ch inherited from a
   16px wrapper is a 20% shorter line than 70ch at 18.5px, which is a measure
   nobody chose. */
.feat__body{
  --fread:70ch;
  max-width:var(--fread); margin:0 auto;
  font-size:18.5px; line-height:1.78; color:var(--label);
  text-shadow:0 1px 0 rgba(0,0,0,.45);
}

/* ── Figures ──────────────────────────────────────────────────────────────────
   A verbatim port of css/about.css:69-95. It is a copy on purpose: about.css is
   the story page's sheet and this page does not load it, and hoisting .fig into
   skeuo.css would restyle /about — a fan-out nobody asked for. If the frame ever
   changes, both sheets change together.

   --natw is the picture's OWN pixel width, written on each figure, so no
   screenshot is ever blown up past its real resolution: the same "never upscale
   a sprite" rule the game itself renders by. */
.fig{ margin:0; }
.fig__frame{
  display:block; border-radius:8px; overflow:hidden;
  box-shadow:0 0 0 1px var(--wood-line), 0 0 0 2px var(--brass-mid), var(--shadow-drop);
}
.fig img{ width:100%; height:auto; display:block; }

/* ── The strip: a section's screenshots as one justified row ──────────────────
   Shown full width, thirteen screenshots made an 11 000px page. They are tiles
   now and they expand — so the row's job is to be SHORT and to leave every tile
   at a sane size, without cropping anything (a cropped screenshot is a screenshot
   of something else).

   The row is a classic justified gallery and the arithmetic is worth stating,
   because it is what makes every tile in a row exactly the same height without
   anyone choosing a height:

     flex-basis = --ar * --rowh   … the tile's natural width AT the target height
     flex-grow  = --ar            … so leftover width is shared in proportion

   Flexbox distributes shrinkage in proportion to basis, so when a row overflows,
   every tile scales by the SAME factor and the heights stay equal; when it
   underflows, grow ∝ aspect does the same thing upward. One row per section,
   filled edge to edge, no chosen numbers.

   --ar is the picture's own width/height, written on each figure by the build.
   The max-width is the same never-upscale cap the full-width figures had, expressed
   as a height so a lone tile in a one-picture section cannot inflate to 1060px
   wide and undo the whole point of this change.

   ⭐ --rowh IS A PACKING KNOB, NOT A HEIGHT. Because the basis is `--ar * --rowh`,
   it decides how many tiles FIT on a line before the row wraps — and the height
   that comes out is then whatever justifying that line produces. 190px is the
   value at which a section's three widest screenshots still share one line in
   the 1060px column (their bases total 1011 + 32 of gap) and wrap to 2 + 1 as
   soon as the column is narrower than that. So ONE number carries every width:
   3-up on a desktop, 2-up on a tablet, 1-up on a phone, and every line justified.

   The first cut had `flex-wrap:nowrap` plus a separate phone rule, and both
   halves were wrong where nothing had been measured: at 768px — inside the
   761-1000px band this site has been bitten in before — three tiles squeezed
   into one line came out **131px tall**, and the phone rule's `flex:1 1 50%`
   gave equal WIDTHS to pictures of different shapes, so a wrapped row read
   **168x107 beside 168x87**. Equal heights are a property of basis ∝ aspect;
   any percentage basis throws them away. */
.feat__strip{
  --rowh:190px; --maxh:340px;
  display:flex; flex-wrap:wrap; gap:16px;
  justify-content:center; align-items:flex-start;
  width:min(100%,var(--fcol)); margin:26px auto 0;
}
.feat__strip .fig{
  flex:var(--ar) 1 calc(var(--ar) * var(--rowh));
  max-width:min(var(--natw), calc(var(--ar) * var(--maxh)));
  min-width:0;                    /* or a flex item refuses to shrink past its content */
}
/* aspect-ratio on the FRAME, so the box is the right shape before the bytes
   arrive and the row does not reflow as the tiles load in. */
.feat__strip .fig__frame{ aspect-ratio:var(--ar); }
.feat__strip .fig img{ width:100%; height:100%; object-fit:cover; }

/* ── The tile as a control ────────────────────────────────────────────────────
   An <a> around the frame, so it is focusable, announced, and still works with
   this page's javascript blocked — its href is the screenshot itself. */
.fig__open{
  display:block; position:relative; border-radius:8px;
  text-decoration:none; outline-offset:3px;
}
.fig__open .fig__frame{
  transition:box-shadow .16s ease, transform .16s ease;
}
.fig__open:hover .fig__frame,
.fig__open:focus-visible .fig__frame{
  box-shadow:0 0 0 1px var(--wood-line), 0 0 0 2px var(--brass-highlight),
             0 0 14px rgba(255,208,106,.42), var(--shadow-deep);
  transform:translateY(-2px);
}
/* The affordance is a DRAWN magnifier — a ring and a handle — never a glyph.
   pay.css:133 records two prior tofu bugs from ornaments typed as characters,
   and this one would be a control that silently becomes an empty box. */
.fig__zoom{
  position:absolute; right:9px; bottom:9px; width:27px; height:27px;
  border-radius:50%;
  background:radial-gradient(circle at 36% 30%,
    #F9DE97 0%, #EFC873 22%, #D2A03F 52%, #A9781F 78%, #6E4E0F 100%);
  box-shadow:0 0 0 1px var(--wood-line), 0 2px 4px rgba(0,0,0,.6),
             inset 0 1px 0 rgba(255,255,255,.5);
  opacity:.88; transition:opacity .16s ease, transform .16s ease;
}
.fig__zoom::before{           /* the lens */
  content:""; position:absolute; left:7px; top:6px; width:10px; height:10px;
  border-radius:50%; border:2px solid #2A1806;
}
.fig__zoom::after{            /* the handle */
  content:""; position:absolute; left:16px; top:16px; width:7px; height:2px;
  border-radius:1px; background:#2A1806; transform:rotate(45deg);
  transform-origin:left center;
}
.fig__open:hover .fig__zoom,
.fig__open:focus-visible .fig__zoom{ opacity:1; transform:scale(1.08); }

/* ── The lightbox ─────────────────────────────────────────────────────────────
   A real <dialog>, so the focus trap, the inert background and Escape come from
   the browser rather than from hand-written key handling. */
.lb{
  border:0; padding:0; background:transparent;
  max-width:100vw; max-height:100vh; width:100%; height:100%;
  overflow:visible;
}
.lb::backdrop{ background:rgba(10,5,1,.86); }
.lb__in{
  height:100%; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:14px; padding:18px;
  box-sizing:border-box;
}
.lb__stage{ display:flex; align-items:center; justify-content:center; min-height:0; }
/* Never upscale here either: the picture stops at its own pixels. */
.lb__stage img{
  display:block; width:auto; height:auto;
  max-width:min(94vw, 100%); max-height:82vh;
  border-radius:8px;
  box-shadow:0 0 0 1px var(--wood-line), 0 0 0 2px var(--brass-mid),
             0 18px 46px rgba(0,0,0,.7);
}
.lb__bar{
  display:flex; align-items:center; gap:14px;
  padding:7px 10px 7px 14px; border-radius:22px;
  background:rgba(20,10,3,.82);
  box-shadow:0 0 0 1px rgba(126,90,18,.55), var(--shadow-drop);
}
.lb__count{ font-size:13px; color:var(--label-dim); min-width:56px; text-align:center; }
.lb__btn{
  position:relative; width:34px; height:34px; padding:0; cursor:pointer;
  border:1px solid var(--brass-mid); border-radius:50%;
  background:linear-gradient(180deg,#3a2410 0%,#25160a 100%);
  box-shadow:0 2px 4px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,233,168,.20);
}
.lb__btn:hover{ border-color:var(--brass-highlight); }
.lb__btn:focus-visible{ outline:2px solid var(--brass-highlight); outline-offset:2px; }
/* Chevrons and the close cross are drawn from rotated borders — shapes, again,
   not "<" and "x" set in a face that may not carry them. */
.lb__btn::before{
  content:""; position:absolute; left:50%; top:50%; width:9px; height:9px;
  border:2px solid var(--brass-light); border-right:0; border-bottom:0;
}
.lb__nav--prev::before{ transform:translate(-35%,-50%) rotate(-45deg); }
.lb__nav--next::before{ transform:translate(-65%,-50%) rotate(135deg); }
.lb__close::before,
.lb__close::after{
  content:""; position:absolute; left:50%; top:50%;
  width:14px; height:2px; border:0; border-radius:1px; background:var(--brass-light);
}
.lb__close::before{ transform:translate(-50%,-50%) rotate(45deg); }
.lb__close::after { transform:translate(-50%,-50%) rotate(-45deg); }

@media (prefers-reduced-motion:reduce){
  .fig__open .fig__frame,
  .fig__zoom{ transition:none; }
  .fig__open:hover .fig__frame,
  .fig__open:focus-visible .fig__frame{ transform:none; }
  .fig__open:hover .fig__zoom,
  .fig__open:focus-visible .fig__zoom{ transform:none; }
}

/* ── The closing section ──────────────────────────────────────────────────── */


/* ── Tablet ───────────────────────────────────────────────────────────────── */
@media (max-width:1000px){
  .feat__plate{ padding:30px 34px 32px; }
  /* --rowh is unchanged: it is the packing knob and the packing is already right
     here (2-up). Only the cap moves, so a tile that wraps onto a line of its own
     does not tower over the justified line above it. */
  .feat__strip{ --maxh:300px; }
}

/* ── Phone ────────────────────────────────────────────────────────────────────
   A three-tile row inside a 347px column puts each tile at ~100px tall, which is
   not a picture of anything. Below this width the row WRAPS to two per line and
   the tiles get their own, larger, height — the page is short here anyway,
   because a full-width tile on a phone is only ~200px tall to begin with. */
@media (max-width:760px){
  .feat{ padding:0 14px 20px; }
  .feat__hero{ padding:36px 0 24px; }
  /* ⚠ NO font-size override here. One clamp governs the wordmark at every width:
     a second rule in this block held it to 76px, so on a 600px screen the word
     was 208px under a 330px ornament while the desktop pair reads 750 under 1160.
     Two rules for one quantity is how the proportion comes apart in the middle. */
  .feat__sec{ margin-top:32px; }
  .feat__plate{ padding:24px 20px 26px; border-radius:10px; }
  .feat__title{ font-size:clamp(24px,6.4vw,32px); }
  .feat__body{ font-size:17px; line-height:1.72; }
  /* the flourish keeps its full detail at any size but stops earning its space
     below about 230px, so it is pulled in rather than shrunk further */
  .feat__title + .feat__rule{ --rule-w:250px; margin:4px auto 12px; }
  /* ⚠ THE LONG CUT GOES BACK TO THE COMPACT ONE ON A PHONE. Its five-unit run
     drawn 347px wide is at 0.28 of its design scale — the scrolls become hairlines
     and the leafwork turns to mush. Below this width the denser motif carries the
     same idea at a size it can actually be seen at. */
  .feat__hero .feat__rule,
  .feat__end{
    --rule-w:min(100%, 430px); aspect-ratio:600 / 112;
    background-image:url("../assets/logo/baroque-rule.svg?v=5949ea71");
  }

  /* No flex override here on purpose — see the note on .feat__strip. At this
     width the basis already exceeds the column, so every tile takes its own
     line and justifies to the full width; a `flex:1 1 50%` rule would only
     break the equal-height property that the aspect-proportional basis gives. */
  /* Below the wrap point every tile takes a line of its own, so the cap is what
     keeps the page short: without it a 1.12-aspect screenshot at full column
     width is 310px tall on a 375px phone and the page grows past the tall
     layout this change replaced. */
  .feat__strip{ gap:12px; margin-top:18px; --maxh:300px; }
  .fig__zoom{ width:24px; height:24px; right:7px; bottom:7px; }
  .fig__zoom::before{ left:6px; top:5px; width:9px; height:9px; }
  .fig__zoom::after { left:14px; top:14px; width:6px; }
  .lb__in{ padding:10px; }
  .lb__stage img{ max-height:74vh; }
}
