/* /founder/ — the scan.
 *
 * Her idea, in her words: a black silhouette with wings open and
 * electricity, a button that says scan, and "a card almost like a passport
 * with my face name and so on" appearing on the right.
 *
 * ── WHY A RECTANGLE IS ALLOWED HERE ──────────────────────────────────
 * Five versions of the nav died because they were a framed rectangle
 * floating in the middle of a page -- "now it looks like a postcard" -- and
 * the frame was the problem, not the paint on it. A passport is different
 * in exactly one way that turns out to be the whole thing: it is a
 * rectangle BECAUSE passports are rectangles. The shape is load-bearing.
 * That is the same permission the bezel earned by being the edge of a
 * display rather than a box drawn around text.
 *
 * So the document gets to be a document -- and in return it has to behave
 * like one. It is laid out as a real data page (photo window, fields,
 * machine-readable zone), the strip at its foot is a genuine ICAO 9303 MRZ
 * with real check digits rather than decorative glyphs, and the scan that
 * reveals it is the ultraviolet pass that border control actually performs:
 * passports carry inks and holograms that are invisible in ordinary light
 * and appear only under UV. Nothing on this page performs an idea it is
 * not also doing.
 *
 * ✗ Not here, all of them killed on the nav and not smuggled back in under
 * a new name: a glowing gradient border, corner brackets, fake coordinates,
 * a unit ID, a status line, a title bar, a grotesk, a violet fill, and a
 * sweep that crawls downward.
 */

/* ⚠️ NEWSREADER WAS NOT LOADED ON THIS PAGE, and her one line of prose was
   silently falling back to the mono -- which is the exact distinction the
   line exists to make. On this site the serif means A PERSON SPEAKING (her
   legend under the eyepiece, her manifesto); the card's values are Rajdhani
   precisely so that her voice has somewhere to land. Set in the mono it
   read as a tenth field.
   A missing @font-face does not error. It just quietly uses something
   else, and the something else was the one face that undoes the point. */
@font-face {
  font-family: 'Newsreader';
  src: url('../assets/newsreader.woff2') format('woff2');
  font-weight: 200 800; font-display: swap;
}
@font-face {
  font-family: 'DotGothic16';
  src: url('../assets/dotgothic16.woff2') format('woff2');
  font-weight: 400; font-display: swap;
}
@font-face {
  font-family: 'Rajdhani';
  src: url('../assets/rajdhani-700.woff2') format('woff2');
  font-weight: 700; font-display: swap;
}

:root {
  /* ★ ALL RED, her call: "red is my color", no purples anywhere on this
     page. And specifically NOT the site's --blood #ff2d55, which carries
     85 of blue and reads pink against black -- her note was "bright red,
     not pink red", so every red here is near-zero blue.
     The page keeps a hierarchy inside the one hue rather than reaching
     for a second colour: white-hot for the thing being read, bright red
     for structure, dark red for everything that is only furniture. */
  --red:      #ff1a08;   /* bright, zero-ish blue */
  --red-hot:  #ffd9d2;   /* the white-hot end */
  --red-deep: #8c1206;   /* structure that should recede */
  --dim:      #5c2a24;
  --paper:    #ffe9e4;   /* what the document prints in */
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --disp: 'Rajdhani', 'Arial Narrow', ui-sans-serif, system-ui, sans-serif;
  --cut:  'DotGothic16', 'Courier New', ui-monospace, monospace;
  --serif: 'Newsreader', Georgia, serif;
}

* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; background: #000; }

body {
  opacity: 0;
  transition: opacity 460ms ease;
  color: var(--dim);
  font-family: var(--mono);
  font-size: clamp(0.78rem, 1.02vw, 0.92rem);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}
body.is-in { opacity: 1; }
@media (prefers-reduced-motion: reduce) { body { opacity: 1; transition: none; } }

/* ── The hand-off ─────────────────────────────────────────────────────
   ★ Her ask: "make a red black nice gradient between the image and my
   passport thing". The photograph's right edge is a dark red; letting it
   run straight into pure black leaves a dead strip, and a dead strip is
   what makes a composite read as a cut-out pasted onto a panel. This is
   the colour that belongs between them.

   ⚠️⚠️ AND IT IS DITHERED, which is the part that actually decides whether
   this looks expensive. A near-black gradient is the worst case for
   BANDING: across several hundred pixels it may only cross a dozen
   8-bit steps, so the browser paints a dozen flat strips, and the eye is
   sharpest at precisely that -- in precisely these darks. More stops
   shrinks the steps; noise finishes the job, because dithering turns the
   hard boundary between two bands into a stochastic mix the eye integrates
   as a smooth ramp.
   ⚠️ The noise is at 0.035. That is not a texture, it is UNCERTAINTY --
   above about 0.06 you start seeing grain instead of not seeing bands.

   ⚠️ Behind the document (z-index 0 against the card's 2), so it joins the
   picture to the card without touching the card. */
.fd__bridge {
  position: fixed;
  top: 0; bottom: 0;
  left: 26%; width: 48%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1100ms ease 200ms;
  background:
    linear-gradient(90deg,
      rgba(150,16,6,0.00) 0%,
      rgba(150,16,6,0.10) 12%,
      rgba(132,14,6,0.17) 24%,
      rgba(108,12,5,0.20) 36%,
      rgba( 82,10,4,0.19) 48%,
      rgba( 56, 8,3,0.15) 62%,
      rgba( 32, 5,2,0.10) 76%,
      rgba( 14, 2,1,0.05) 88%,
      rgba(  0, 0,0,0.00) 100%);
}
/* it arrives with the document -- the bridge exists to join two things,
   and until the scan lands there is only one of them */
body.is-scanned .fd__bridge { opacity: 1; }
.fd__bridge::after {
  content: '';
  position: absolute; inset: 0;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: reduce) { .fd__bridge { transition: none; } }
@media (max-width: 720px) { .fd__bridge { display: none; } }

/* ── Layout ───────────────────────────────────────────────────────────
   Subject left, document right, and the beam travels between them. The
   direction is the argument for the arrangement: she is on the left
   because that is where the scan starts. */
/* ★ The figure is sized by WHAT IS LEFT, not by a magic viewport number.
   Her ask was simply "bigger", and the honest ceiling is however much
   height the page actually has spare once the controls are accounted
   for -- so the stage takes a `1fr` row, the controls take `auto`, and
   she fills the row. That makes her as large as the page can hold at
   every viewport instead of a number that fits one window and overflows
   another (the first version was sized by WIDTH on a 2:3 box, which made
   it 87svh tall and pushed the scan button off the bottom of the
   screen). Padding came down at the same time to give the row more to
   hand her. */
/* ⚠️ THE 46px PADDING FLOOR IS GONE WITH THE BEZEL. It existed for one
   reason -- "scan is overflowed again" was never the page scrolling, it
   was the button sitting UNDER the far bezel's 27px run -- and there is
   no frame to clear any more. The figure gets that space back.
   ⚠️ No left padding on the stage side: the photograph runs to the edge
   of the screen, because a margin around a place turns it back into a
   picture of a place. */
.fd {
  position: relative;
  z-index: 1;
  min-height: 100svh;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  gap: clamp(10px, 2svh, 22px);
  /* ⚠️ NO PADDING ON .fd AT ALL -- the children carry their own. This is
     what lets the photograph be a full-height layer: an absolutely
     positioned child resolves against the PADDING box, so any padding here
     would inset the picture from the top and bottom of the screen and the
     "compressed" feeling would survive every other fix. */
  padding: 0;
  /* ⚠️ The figure's 3% bleed is deliberate but it still counts as
     scrollable overflow (measured: 9px of page). `clip` rather than
     `hidden` so it does not create a scroll container, with a margin so
     the document's own glow is not shaved off at the edges. */
  overflow: clip;
  /* ⚠️ NO clip-margin. 48px was allowing the beam -- which is deliberately
     972px tall against a 900px figure so its ends never show -- to go on
     counting as 9px of scrollable page. Nothing inside needs to paint
     outside .fd: the document's glow is 22px and the document sits 58px
     clear of every edge. */
  overflow-clip-margin: 0;
}
.fd__stage {
  display: grid;
  /* ⚠️ 0.78fr -> 0.62fr, and it is the CARD that needed it rather than a
     taste about balance. At 0.78 the document only reached landscape at
     1440 and sat at a square 1.12 on her own 1260 window -- which is the
     worst of the three proportions, since a square reads as neither a
     document nor a card. The split is what decides the card's shape, so
     the split is where the shape has to be fixed. The figure loses about
     30px of width for it. */
  /* ⚠️ The figure column is sized by the PLATE now, not by a fraction.
     The flying plate is 0.80 portrait, so at full row height it is
     0.8 x that wide -- about 0.8 of a laptop's height, i.e. roughly the
     left two-thirds of the screen -- and the document takes what is left.
     Expressing it as `auto` lets the picture decide and keeps the two
     from fighting over a ratio neither of them chose. */
  /* ★ ONE COLUMN. The figure has left the grid entirely (see .subject) so
     this row now holds only the document. Her note: "it needs to sort of
     take up more of the screen. it feels compressed."
     The compression was structural -- as a grid item the figure was
     height-bound by the row, and the row was whatever was left after the
     controls, so she could never be taller than about 85% of the screen
     however the columns were split. Out of the flow she is bound by the
     VIEWPORT instead. */
  grid-template-columns: 1fr;
  align-items: center;
  align-self: stretch;
  min-height: 0;
  padding: clamp(16px, 3svh, 34px) clamp(26px, 4vw, 60px) 0 0;
}
/* ⚠️ The cap is for the SIDE-BY-SIDE layout only. Applied unconditionally
   it also hit the stacked phone layout, where 44vw is 172px -- the card
   became a strip and pushed 48px of horizontal overflow. */
@media (min-width: 721px) {
  .fd__stage > .doc { justify-self: end; max-width: min(44vw, 620px); }
}

/* ── The subject ──────────────────────────────────────────────────────
   Two copies of one plate, stacked. The dark one is her standing with
   the lightning dead; the live one is clipped to however far the beam
   has travelled, so the scan charges her rather than sliding a line over
   a picture.
   ⚠️ clip-path on a static image is a compositor operation. The plate is
   never repainted and no filter is recomputed per frame -- which is the
   only reason a full-height image can carry an effect on this site at
   all (a per-frame filter measured ~40% of the budget on the hero). */
/* ⚠️ SIZED BY HEIGHT, NOT WIDTH. Setting `width: 58svh` on a 2:3 box makes
   it 87svh TALL, which overflowed the viewport and pushed the scan button
   off the bottom of the page. The constraint that matters for a standing
   figure is how tall it is allowed to be; the width follows from it. */
.subject {
  position: absolute;
  --p: 0;
  /* ⚠️⚠️ SIZED BY WIDTH, AND THE OLD HEIGHT RULES HAD TO GO WITH IT. "bring
     the image more towards the right so it takes up more screen... make her
     take up more of the screen."
     Height-driven, a 4:5 plate can only get wider by getting taller, and it
     was already bleeding past the viewport -- so the ceiling was the
     screen's height rather than anything I had chosen. Driving it from
     width makes "how much screen does she take" a number I can set.
     ⚠️ The first attempt at this did nothing, because `height: 100%;
     width: auto` from the previous version was still sitting FURTHER DOWN
     the same rule and quietly won. Measured 720x900 when 950 was asked
     for. Adding a declaration is not the same as changing one.
     ⚠️ 66vw WAS about the limit, and 46vw is what it is now -- her call:
     "zoom out on the angel maybe 30% because she reads too close to the
     screen". She is describing CAMERA DISTANCE, and the cause was that at
     66vw the plate stood ~1.25x the viewport height, so .fd's `overflow:
     clip` cut ~10% off the top and bottom. A figure whose head and feet
     are both outside the frame reads as pressed up against the glass,
     because that is exactly what a close-up is.
     At 0.7x she comes to ~0.87x the viewport height and the whole plate
     is on screen for the first time -- the top feathers, the full fall of
     the wing, sea below her. Nothing is cropped by the viewport any more,
     which is the actual change; the size is just how it was achieved.
     ⚠️ ALL THREE TERMS ARE SCALED, not only the first. The other two are
     the ceilings that bind on wide and mid windows, and leaving either at
     its old value would have meant no change at all above ~1545px or
     below ~1150px -- the fix would have worked on exactly the window I
     happened to screenshot. */
  left: 0; top: 50%;
  translate: 0 -50%;
  width: min(46vw, calc((100vw - 380px) * 0.7), 714px);
  height: auto;
  aspect-ratio: 4 / 5;          /* the plate's own 0.80 */
  z-index: 0;
  /* ★ FEATHERED INTO THE BLACK on the side the document is on, so the
     photograph does not end on a hard vertical line.
     ⚠️⚠️ AN EASED, MANY-STOP RAMP -- NOT A TWO-STOP FADE. A near-black
     gradient is the worst case for BANDING: crossing only a dozen 8-bit
     steps over several hundred pixels paints a dozen flat strips, and the
     eye is sharpest at exactly that, in exactly these darks. More stops
     means smaller steps.
     ⚠️ It fades to TRANSPARENT, never to a colour -- a fade to black only
     looks right over black and leaves a visible box over anything else. */
  -webkit-mask-image:
    linear-gradient(90deg, #000 0 46%,
      rgba(0,0,0,0.97) 56%, rgba(0,0,0,0.88) 64%, rgba(0,0,0,0.72) 71%,
      rgba(0,0,0,0.52) 78%, rgba(0,0,0,0.32) 85%, rgba(0,0,0,0.16) 91%,
      rgba(0,0,0,0.06) 96%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 6%, #000 94%, transparent 100%);
          mask-image:
    linear-gradient(90deg, #000 0 46%,
      rgba(0,0,0,0.97) 56%, rgba(0,0,0,0.88) 64%, rgba(0,0,0,0.72) 71%,
      rgba(0,0,0,0.52) 78%, rgba(0,0,0,0.32) 85%, rgba(0,0,0,0.16) 91%,
      rgba(0,0,0,0.06) 96%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 6%, #000 94%, transparent 100%);
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}
.subject__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* Dead: the arcs crushed almost to nothing, so there is somewhere for
   the charge to come FROM. Not hidden -- she is visible standing there,
   you simply cannot see any power in her. */
/* Before the scan: the night is there but nothing in her is lit.
   ⚠️ NOT grayscale. On the silhouette, saturate(0) was free because the
   plate was neutral white arcs anyway. This is a colour photograph of a
   real coastline, and draining it to grey reads as a broken image rather
   than as an unlit one. Dim and cooled, not colourless. */
/* ⚠️ 0.42 -> 0.74, AND SHE WAS EFFECTIVELY INVISIBLE AT 0.42. Her note:
   "the angel should be visible there and then you press scan. not nothing
   or idk??" -- measured, the figure half of the frame sat at mean luma
   4.32 before the scan against 25.15 after it. 17% of the lit state on a
   plate that is a NIGHT SCENE to begin with is not dim, it is black.
   The number was inherited from the silhouette, where 0.26 was right
   because that plate was white arcs on pure black and crushing them still
   left a readable shape. This plate has no white in it to crush.
   0.74 put her at 45% of the lit state; "tone down the angel just a little
   so you can see her but not as much as now" took it to 0.62, which is
   34%. Still unmistakably present, with more left for the scan to give
   back. Above about 0.86 the scan stops being worth watching at all. */
/* ✗ THE DIM FILTER IS GONE. A filter dims the whole photograph, which is
   what put the coast and the moon down in order to put her down. Her light
   is its own layer now -- see the markup. */
.subject { isolation: isolate; }   /* keep the additive blend off the page */
.subject__dim, .subject__live {
  position: absolute; inset: 0;
  /* ★ plus-lighter is TRUE ADDITION: dst + src. It is the operator that
     makes this work at all, because adding black is a no-op and so the
     light layer needs no alpha, no mask and no edge.
     ⚠️ `screen` as the fallback -- screen(a,b) = a+b-ab, and on a plate
     this dark the ab term is negligible, so it is additive to within a
     rounding error where plus-lighter is unsupported. */
  mix-blend-mode: screen;
}
@supports (mix-blend-mode: plus-lighter) {
  .subject__dim, .subject__live { mix-blend-mode: plus-lighter; }
}
.subject__dim picture, .subject__live picture { display: contents; }
/* ⚠️ 0.2 + 0.8 = 1.0 exactly where the beam has passed, so the scanned
   state is the untouched photograph rather than an approximation of it. */
.subject__dim { opacity: 0.2; }

/* ★ Live, and VIOLET rather than white. The arcs were generated pure
   neutral white (measured mean RGB 234/234/235, channel spread 2.5)
   precisely so they could be driven to any colour cleanly -- a duotone
   chain is enough, with nothing in the source to fight. Violet is the
   colour of the deck she was chosen from on /inside/, which is what
   makes the red at the end of the scan mean something. */
/* ★ COLOUR BY MULTIPLY, NOT BY FILTER. The arcs were generated pure
   neutral white (measured 234/234/235, channel spread 2.5) precisely so
   they could be driven to any colour -- and multiply is the operation
   that actually does it: white x C is exactly C, black x anything stays
   black. The whole image is its own mask.
   ✗ A grayscale/sepia/hue-rotate duotone was tried first and CANNOT
   REACH RED. Searched the full space of hue x saturate x brightness
   against a blood-red target; the nearest the chain gets is
   (255,154,56), which reads as fire. sepia() produces a warm mid-tone
   and hue-rotate is a linear matrix approximation that cannot push green
   down without taking red with it. Worth remembering before reaching
   for a duotone to recolour anything toward red again.
   ⚠️ And no var() inside a calc() inside a filter -- that silently drops
   the entire filter declaration here, with no error. Filters on this
   page are literal values only. */
/* ⚠️⚠️ THE TINT IS SIZED BY THE PICTURE, NOT BY THE BOX, and this was a
   real bug on her screen rather than a tidiness point. The tint fills its
   element and multiplies; the image inside is object-fit:contain. The
   moment the box stops matching the image's 2:3 -- which happened at
   840x700, her own window -- `contain` letterboxes the picture and the
   tint goes on painting into the empty bands. Result: two solid red
   rectangles above and below her. It was invisible at 1440x900 purely
   because the box happened to match the aspect exactly there.
   The grid is the fix: one cell, the image sizes it, the tint fills the
   cell. There is no longer any such thing as "outside the picture but
   inside the layer". */
/* ── Lit, and clipped to the beam ────────────────────────────────────
   ⚠️ MUCH SIMPLER THAN IT WAS, because the plate changed species. The
   silhouette needed a grid, a <picture> grid item and a multiply tint to
   get colour into neutral-white arcs. This plate is already a colour
   photograph, so "lit" is just the image at full exposure and "dead" is
   the same image dimmed -- no tint, no isolation, no blend. Every one of
   those rules carried a hard-won comment and every one of them is now
   solving a problem that does not exist on this asset. */
.subject__live {
  --live: 0.8;
  opacity: var(--live);
  clip-path: inset(0 calc((1 - var(--p)) * 100%) 0 0);
}
.subject__live picture { display: contents; }
.subject__live .subject__img { filter: saturate(1.06) contrast(1.02); }

/* ✗ THE PROBE IS GONE. "remove the effect of each thing on the passport
   points to something on the winged me. not good."
   It was built as the answer to "i like it but i dont" -- the page's only
   mechanism fired once in the first four seconds and then it was a poster,
   and this made the document interrogable. She has now seen it and does
   not want it, which settles it: the lookup grammar works on /inside/
   because a daemon list IS an index into something; a passport is not an
   index into a photograph, and pointing one at the other was a connection
   I asserted rather than one the page already had.
   The field-reading styles below are kept -- one line lit and the rest
   receding is still how this site behaves -- they simply no longer drive
   anything on the figure. */

/* The beam itself. A hard white-hot edge with a short leading glow -- an
   instrument, not a gradient wash. */
.beam {
  position: absolute; top: -4%; bottom: -4%;
  left: calc(var(--p, 0) * 100%);
  width: 2px; margin-left: -1px;
  background: #fff;
  box-shadow: 0 0 6px rgba(255,120,100,0.95), 0 0 20px var(--red),
              0 0 56px rgba(255,26,8,0.6);
  opacity: 0;
}
.fd.is-scanning .beam { opacity: 1; }

/* ── The document ─────────────────────────────────────────────────────
   Dark, matte, with one lit top edge and a shadow under it -- the same
   material language as the daemon slabs on /inside/, so the two pages
   are made of the same stuff. No glow around it. The edge is a hairline
   because a document has a physical edge, not because a panel needs a
   frame. */
.doc {
  position: relative;
  z-index: 2;
  /* A data page has a size. Left to fill a 1fr column it sprawled to
     680px of mostly-empty rules, which reads as an empty table rather
     than as a document. */
  /* ⚠️ NARROWER ON PURPOSE. Her note: "make it taller not thicker, so
     more like portrait". At 648 it was a landscape slab; the fields wrap
     at this width and the card grows downward, which is the proportion a
     document actually has. */
  justify-self: end;
  align-self: center;
  /* ★★ LANDSCAPE, AND THE PORTRAIT BEFORE IT WAS A SYMPTOM RATHER THAN A
     DECISION. Measured across widths at 1440x900:

        470 ->  470x634  (0.74)  THREE values wrap to three lines, 16 lines
        520 ->  520x532  (0.98)  nothing wraps to three, 12 lines
        600 ->  600x532  (1.13)
        680 ->  680x457  (1.49)  9 lines

     The card was tall BECAUSE IT WAS TOO NARROW. "Calling death an
     engineering problem", "Neurons / Silicon / Code / Capital" and
     "Aiding and abetting dangerous ideas" each broke over three lines,
     and those four extra lines were the entire portrait proportion. It
     was not a shape, it was a wrap.
     ⚠️ Her earlier note was "make it taller not thicker, so more like
     portrait" -- this reverses it, deliberately, because the thing that
     made 648 wrong was that it was a landscape SLAB with nothing in it,
     not that landscape is wrong. 680x457 is 1.49 against the ISO 7810
     ID-1 ratio of 1.586: close enough that the shape is doing the same
     work the passport rectangle was always doing. It is a card because
     identity cards are that shape. And every value now reads on one
     line, which is the "i understand those, but the average wont" test. */
  width: min(100%, 680px);
  /* ⚠️ 16px -> 11px on the floor. The card's own inner padding was 4.10% of
     a 390 phone against 1.81% of a 1440 laptop -- a 2.3x divergence in the
     one measurement her note was actually about, "the spacing between
     borders at a similar ratio". Only the FLOOR moves, so the laptop card
     is untouched at 26px; the phone drops to 11px (2.82%), which closes the
     gap to 1.0 point and hands 10px straight to the value column, where
     every line that stops wrapping is worth 22px of card height. */
  padding: clamp(11px, 2.2vw, 26px);
  /* ★ OPAQUE. The rain was falling THROUGH it -- you could read binary
     across her face and behind every field -- and that cost the card the
     one thing that justified it being a rectangle: a passport is a solid
     object you could pick up. Translucent, it reads as a HUD panel
     floating on an effect, which is the species of thing she has thrown
     out of every other page. The digits belong behind it, not in it.
     A near-black base under the same top-lit gradient, so it is still a
     surface with a direction, just one you cannot see through. */
  /* ★★ IT HAS TO BE MADE OF THE SAME LIGHT AS THE PHOTOGRAPH. Her note:
     "can we make this match the sea image more somehow... it looks too
     sanitized and doesnt suit the rest of the site."
     Measured off a real frame, which made the cause obvious:

       scene, sea        RGB  31.6 /  0.3 /  0.2     (R-B spread +31)
       scene, night sky  RGB   9.8 /  0.0 /  0.0     (R-B spread +10)
       scene, the moon   RGB 109.6 /  7.7 /  3.3     (R-B spread +106)
       THE CARD          RGB  30.3 / 23.6 / 21.7     (R-B spread  +8)

     THE SCENE HAS NO NEUTRAL DARKS IN IT AT ALL -- green and blue are at
     zero everywhere, because the whole plate was generated under her
     red-only style bible. The card was grey. It was the one object on
     screen made of a colour that world does not contain, which is exactly
     why it read as a UI component dropped onto a photograph rather than
     as a thing lying in the same night.
     So: the base is red-black, every highlight is red rather than white,
     and the sheen runs from the LEFT because that is where the moon is in
     this composition. A document takes the light of the room it is in. */
  background:
    /* the moon, off to the left of the card */
    linear-gradient(97deg,
      rgba(255,74,38,0.13) 0%, rgba(255,40,16,0.05) 24%, rgba(0,0,0,0) 58%),
    /* top-lit, but in the scene's hue -- no white anywhere */
    linear-gradient(180deg,
      rgba(255,96,64,0.085) 0%, rgba(255,40,18,0.022) 34%,
      rgba(0,0,0,0.14) 64%, rgba(0,0,0,0.44) 100%),
    linear-gradient(#140406, #0a0202);
  /* ★ A REAL EDGE, her ask. A document has one -- this is the card's
     own boundary rather than a frame drawn round content, which is the
     same distinction that lets the bezel exist.
     ⚠️ It is CYAN and it arrives with the scan: the border is the colour
     of the beam, so the card reads as something the instrument produced
     rather than as a panel that was always sitting there. A border that
     is simply present is decoration; one that appears at the moment the
     document is issued is part of the event. */
  border: 1px solid rgba(255,26,8,0.14);
  box-shadow: inset 0 1px 0 rgba(255,150,120,0.17),
              inset 1px 0 0 rgba(255,110,80,0.13),   /* the lit edge */
              inset 0 -1px 0 rgba(0,0,0,0.92);
  transition: opacity 420ms ease,
              border-color 620ms ease 180ms, box-shadow 620ms ease 180ms;
  /* ⚠️ NO TRANSFORM. Her call: "make the card appear and stay in the same
     place. no weird movement." It used to rise 6px as it faded in, which
     is the standard "something arriving" gesture and is wrong HERE -- the
     card is a document being issued into a slot, not a notification
     sliding in. Six pixels is also exactly the amount that reads as a
     wobble rather than as travel: too small to be a move, too large to be
     still. It fades, in place. */
  opacity: 0;
  transition: opacity 420ms ease;
  /* ⚠️ "fix the slight card movement." I could not reproduce it in this
     browser -- the card's document position is identical at eight scroll
     offsets and the portrait renders byte-identically at three -- so this
     treats the mechanism that headless cannot show: the card carries a
     grain `::before` at `mix-blend-mode: overlay`, and an UN-ISOLATED
     blend composites against whatever is painted behind it. On a real
     phone that backdrop changes as the page scrolls, so the card is
     re-composited every frame and can shimmer or shift by a subpixel.
     `isolation: isolate` resolves the blend inside the card, against its
     own background, which is the only thing it was ever meant to blend
     with; `translateZ(0)` gives it a stable layer of its own so scrolling
     cannot repaint it at all.
     ⚠️ Phones only. On desktop `.fd__bridge` is `position: fixed`, and a
     transform on an ancestor would make the card a containing block for
     it. There is no bridge under 720px, so there is nothing to break. */
  isolation: isolate;
}
@media (max-width: 720px) {
  .doc { transform: translateZ(0); backface-visibility: hidden; }
}
/* ★ GRAIN. A flat fill is the other half of "sanitized": every real
   surface in this frame -- sea, haze, sky -- is noisy, and the card was
   the only clean one. feTurbulence as a data URI so it costs no asset and
   rasterises once; `overlay` so it modulates the surface rather than
   greying it, which a plain translucent noise layer would do.
   ⚠️ pointer-events none and inset 1px, or it sits over the border and
   eats clicks on the fields underneath. */
.doc::before {
  content: '';
  position: absolute; inset: 1px;
  pointer-events: none;
  z-index: 0;
  opacity: 0.5;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.42'/%3E%3C/svg%3E");
}
.doc > * { position: relative; z-index: 1; }
.doc.is-live { opacity: 1; }
.fd.is-done .doc {
  border-color: rgba(255,26,8,0.5);
  box-shadow: inset 0 1px 0 rgba(255,226,220,0.2),
              inset 0 -1px 0 rgba(0,0,0,0.9),
              0 0 22px rgba(255,26,8,0.10);
}

.doc__head {
  display: flex; align-items: baseline; gap: 1ch;
  margin: 0 0 1.5em;
}
.doc__auth {
  font-family: var(--disp); font-weight: 700;
  font-size: 1.22em; letter-spacing: 0.1em; text-transform: uppercase;
  color: #ffcfc6;
}
.doc__issue {
  margin-left: auto;
  font-size: 0.82em; letter-spacing: 0.16em; text-transform: uppercase;
  color: #b4604f;
}

.doc__body {
  display: grid;
  /* ★ Bigger than a passport photo would strictly be, because the
     filaments on her neck are the one thing on this card that has to
     read -- and at 148px they were two pixels wide and no amount of
     saturation could survive that. Pixels, not colour, was the
     binding constraint. It also fills space the document was
     leaving empty. */
  grid-template-columns: clamp(104px, 12vw, 148px) 1fr;
  gap: clamp(14px, 2vw, 26px);
  align-items: start;
}


/* The photo window. A passport photo sits in a recess, so this is inset
   rather than raised -- dark lip above, light lip below, the opposite of
   the document's own edge. */
.doc__photo {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: #000;
  box-shadow: inset 0 1px 0 rgba(0,0,0,0.95), inset 0 -1px 0 rgba(255,226,220,0.10);
}
/* ⚠️ object-position is MEASURED, not centred. Her lit pixels sit at
   x-frac 0.531 / y-frac 0.458 of the plate, not at 0.5/0.5, and this
   window crops hard once it is stretched. Checked: cropping the 0.80
   source all the way to 0.50 loses 0.1% of her lit pixels, so the tall
   window is safe -- everything either side of her is dark ground. */
.doc__photo img {
  width: 100%; height: 100%; display: block;
  object-fit: cover; object-position: 53% 46%;
}

/* ★ The portrait's filaments, as their own layer so they can take the
   same colour as the silhouette. SCREEN onto the photo, because the arcs
   are light being added to a face rather than a filter over it -- and
   black screens to nothing, so everything that is not an arc disappears.
   ⚠️ mix-blend-mode creates a stacking context, which is what keeps the
   multiply inside from reaching the photograph underneath: the tint
   colours the arcs, the arcs light the face. Doing it the other way round
   -- multiplying the photo directly, the way the silhouette works --
   turns her skin red, which is the whole reason this needed two plates
   instead of one. */
.doc__arcs {
  position: absolute; inset: 0;
  mix-blend-mode: screen;
  pointer-events: none;
}
/* ⚠️ Same object-position as the photograph or the filaments slide off
   her neck the moment the window stops being 4:5. .doc__arcs is inset:0
   over the same box, so the two crops must be declared identically --
   they are not inherited. */
.doc__arcs img {
  width: 100%; height: 100%; display: block;
  object-fit: cover; object-position: 53% 46%;
}
.doc__arcs-tint {
  position: absolute; inset: 0;
  background: var(--tint, #ffffff);
  mix-blend-mode: multiply;
}
.fd.is-done .doc__arcs-tint {
  background: #ff0a1e;
  transition: background-color 900ms ease 220ms;
}

/* ★ ONCE THE CARD IS LANDSCAPE, THE PHOTO RUNS ITS FULL HEIGHT. At 4:5
   inside a landscape card the photo stopped two-thirds of the way down
   and left a dead rectangle under it -- the "empty table" look that
   killed the first version of this document. A real ID card's photo runs
   the height of the card; here the fields are what set that height, so
   stretching the window to them closes the gap and makes the photograph
   the second-largest thing on the card instead of a stamp in a corner.

   ⚠️⚠️ THIS BLOCK MUST STAY BELOW `.doc__photo`. It is the same (0,1,0)
   specificity as the base rule, so source order is the only thing
   deciding it -- placed above, `aspect-ratio: auto` silently lost and
   the photo kept its 4:5, which with a stretched height of 400px
   resolved its WIDTH to 320 and overflowed a 202px column by 118px. A
   media query does not add specificity.

   ⚠️ Gated on width, not applied everywhere. Stacked on a phone the card
   is full-bleed and the fields are very tall, so stretching would hand
   her a 120x600 slot -- a letterbox of one eye. Below this width the 4:5
   window is right and stays. */
@media (min-width: 1100px) {
  .doc__body { align-items: stretch; grid-template-columns: clamp(150px, 14vw, 205px) 1fr; }
  .doc__photo { aspect-ratio: auto; min-height: 0; }
}

.doc__fields { margin: 0; display: grid; gap: 0.72em; }
.f { display: grid; grid-template-columns: minmax(96px, 11.5ch) 1fr; gap: 1.2ch;
     align-items: baseline;
     opacity: 0; transform: translate3d(-5px, 0, 0);
     transition: opacity 260ms ease, transform 260ms ease; }
.f.is-in { opacity: 1; transform: none; }

/* ★ BRIGHTER, her note: "the page reads as dark still". The labels were
   at rgba(74,58,92,.95) -- luma 62, which is barely above the void they
   sit on. Labels now read, values are near-white, and the card's own
   surface came up with them so the text is not floating on nothing. */
dt {
  font-family: var(--disp); font-weight: 700;
  font-size: 0.92em; letter-spacing: 0.13em; text-transform: uppercase;
  color: #c9705f;
}
/* ⚠️ THE VALUES ARE NOT SET IN THE BITMAP FACE ANY MORE. "i dont like the
   font for the info. the titles are ok." The titles are Rajdhani and she
   kept them; the values were DotGothic16, whose whole character is being a
   pixel font -- which reads as retro game rather than as a document, and
   its loose tracking pushed half the fields onto a second line.
   Rajdhani for both now, split by CASE and colour rather than by family,
   which is exactly what a real ID card does: one typeface, labels small
   and tracked-out, values larger and set normally. It is also tighter --
   "Neurons / Silicon / Code / Capital" fits on one line again.
   ⚠️ Newsreader was the other candidate and was rejected on purpose: it is
   this site's voice for A PERSON SPEAKING (the manifesto, her legend under
   the eyepiece), and spending it on machine-issued data would blunt the
   one distinction that is doing real work. */
dd {
  margin: 0;
  font-family: var(--disp); font-weight: 700;
  font-size: 1.12em; letter-spacing: 0.012em;
  color: var(--paper);
  min-height: 1.5em;
}
/* ⚠️ An EMPTY field is ruled, not blank. Her instruction was to put the
   titles in and leave the values until she is in the mood to write them,
   and an unfilled rule is what an unissued document actually looks like
   -- it reads as deliberate rather than as something that failed to
   load. Delete this rule the day the last field is filled. */
dd:empty::after {
  content: '';
  display: block;
  height: 1px; width: 100%;
  margin-top: 0.75em;
  background: repeating-linear-gradient(90deg,
    rgba(120,100,145,0.5) 0 7px, transparent 7px 13px);
}

/* ── Her line ─────────────────────────────────────────────────────────
   ★ The only thing on this page she says in her own voice. Set in the
   serif because on this site that face means A PERSON SPEAKING; the card's
   values are Rajdhani precisely so this has somewhere to land. In the
   card's own face it would read as a tenth field. */
.fd__said {
  grid-column: 1 / -1;
  justify-self: end;
  max-width: min(44vw, 52ch);
  margin: 0;
  padding: 0 clamp(26px, 4vw, 60px) 0 0;
  text-align: right;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1rem, 1.35vw, 1.22rem);
  line-height: 1.5;
  color: #c7958b;
  position: relative; z-index: 2;
  opacity: 0;
  transition: opacity 900ms ease 420ms;
}
/* It arrives with the document: she speaks once the file is open. */
body.is-scanned .fd__said { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .fd__said { transition: none; } }

/* ── The control ──────────────────────────────────────────────────────*/
.fd__ctl {
  position: relative; z-index: 2;
  display: flex; align-items: center; flex-wrap: wrap;
  /* ⚠️ The row gap is SET, not inherited from the shorthand, and it is set
     for the hit areas below. `gap: 1.4ch` gave 10.5px in both axes -- the
     horizontal figure was chosen, the vertical one was a side effect
     nobody picked. Once each control carries a 46px tap area, a 10.5px
     gutter between two stacked 21px links means their areas overlap and
     the lower one takes a bite out of the upper: measured, it dropped
     `scan` -- the control the whole page exists for -- from 46 to 40 on a
     landscape phone. 24px is the smallest gutter at which every adjacent
     pair sat 46px centre-to-centre back when this row held four controls.
     ⚠️ KEPT AT 24 THOUGH ONLY TWO REMAIN. It costs nothing (two controls
     share a row at every viewport this site is tested at, and row-gap does
     nothing until a row wraps) and it is the one number here that was
     measured against the worst case this row has ever had. Anything added
     back to this row inherits a gutter that already works.
     ⚠️ No media query, on purpose: row-gap does nothing until the row
     actually wraps, so this applies at exactly the widths that need it and
     is inert on the one-row layout above 860px. */
  column-gap: 1.4ch; row-gap: 24px;
  margin: 1.4em 0 0;
  /* ⚠️ Its own left padding, because .fd's is now 0 on that side so the
     photograph can run to the edge of the screen. Without this the button
     sits flush against the viewport and reads as clipped. The picture
     wants no margin; the controls still do. */
  padding: 0 clamp(26px, 4vw, 60px) clamp(16px, 3svh, 30px);
}
.scan {
  font: inherit;
  font-family: var(--disp); font-weight: 700;
  font-size: 1.06em; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--red);
  background: none;
  border: 1px solid rgba(255,26,8,0.45);
  padding: 0.5em 1.8em;
  cursor: pointer;
  transition: color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
.scan:hover, .scan:focus-visible {
  color: #fff; border-color: var(--red); outline: none;
  box-shadow: inset 0 0 18px rgba(255,26,8,0.16), 0 0 14px rgba(255,26,8,0.35);
}
.fd__back {
  margin-left: auto;
  color: var(--dim); text-decoration: none;
  letter-spacing: 0.16em; text-transform: uppercase;
  border-bottom: 1px solid rgba(74,58,92,0.8); padding-bottom: 2px;
}
.fd__back:hover, .fd__back:focus-visible {
  color: #fff; border-color: var(--red);
  text-shadow: 0 0 5px #fff, 0 0 14px var(--red); outline: none;
}

/* ── Hit areas ──────────────────────────────────────────────────────────
 * Hit-tested, `scan` is 35px tall and `back` is 24. The floor for a
 * finger is 44 (Apple's HIG; WCAG 2.5.5 lands in the same place), and
 * these two are the entire interactive surface of the page -- one runs
 * the scan, the other is the way out.
 *
 * ⚠️ TWO CROSS-SITE LINKS LIVED IN THIS ROW AND BOTH ARE GONE -- her call.
 * Worth keeping the lesson they taught on the way out: they were added
 * after this block was written, by the SEO pass, into a row whose other
 * members had already been fixed, so the page silently went back to having
 * untappable controls on it without anything regressing. 21-24px tall,
 * hit-tested 22. A hit-area pass is a SNAPSHOT of the controls that
 * existed on the day; the next control added to this row will not inherit
 * it, so add it to the selector below when you add it.
 *
 * ⚠️ NOT min-height, and not padding. `scan` has a drawn border, so any
 * height added to it is visible, and the short-window tiers above already
 * tune its padding for a reason; `back` carries a border-bottom
 * underline, so padding-bottom would push the rule off the word. Both
 * would also change what a laptop sees, and a 1280x760 window is one of
 * these tiers.
 *
 * ✓ A transparent, absolutely-positioned ::after on each: it grows the
 * area a finger can land on and paints nothing, so no pixel moves at any
 * viewport. 46 rather than 44 because the area is measured outward from
 * the centre in 2px steps and a control exactly on the floor is one
 * rounding error from being under it.
 * ⚠️ Deliberately not behind `@media (pointer: coarse)`: this project's
 * headless browser reports `pointer: none` at every viewport so such a
 * gate cannot be verified here, a touchscreen laptop reports
 * `pointer: fine` while being driven by a finger, and a comfortable hit
 * area costs a mouse nothing. */
.scan, .fd__back { position: relative; }
.scan::after, .fd__back::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 46px;
  translate: 0 -50%;
}
/* ⚠️ These tile only because `.fd__ctl` sets a 24px row-gap -- see the note
   there. At the 10.5px the `gap` shorthand used to give, every one of these
   areas overlapped its neighbour's and the taller controls lost the fight
   to whatever happened to sit below them in the DOM. The two numbers are
   one decision: tighten that gap and re-measure every control in this
   row. */

/* ⚠️ SHORT WINDOWS, which is where this broke. Measured at 840x700 --
   her own laptop size -- the page overflowed by 213px and the scan button
   sat at y=889, off the bottom of the screen. Two causes compounding:
   the stage stacked to one column below 860px, and a nine-field card is
   far taller than the five-field one this layout was built for.
   The stack is now held off until 720px, and everything tightens on a
   short viewport rather than the page growing past it. Height is the
   constraint that actually binds here, so it is the one being measured. */
@media (max-height: 820px) {
  .fd { padding: clamp(14px, 2svh, 24px) 0; gap: 12px; }
  .doc, .fd__ctl { margin-left: clamp(16px, 5vw, 32px); margin-right: clamp(16px, 5vw, 32px); }
  .doc { padding: clamp(12px, 1.6vw, 18px); }
  .doc__head { margin-bottom: 1em; }
  .doc__fields { gap: 0.42em; }
  .doc__body { gap: clamp(10px, 1.4vw, 18px); }
  .fd__ctl { margin-top: 1.2em; }
  .scan { padding: 0.4em 1.4em; }
}
/* ⚠️ ON A SHORT WINDOW THE PHOTO GOES BACK BESIDE THE FIELDS, and that is
   a decision rather than an accident -- it was silently overriding the
   stacked layout until this comment existed. Stacking is the right
   proportion and the wrong fit: at 840x620 a stacked card measures ~619
   tall, and with the controls and the frame's clearance the page needs
   767px it does not have. Beside, it fits at 372x429, which is still
   portrait. Proportion loses to being on the screen. */
@media (max-height: 760px) {
  .doc__fields { gap: 0.3em; }
  .doc__body { grid-template-columns: clamp(96px, 12vw, 150px) 1fr; }
  .f { font-size: 0.94em; }
}

/* ⚠️⚠️ AND THEN THE LANDSCAPE PHONE, WHICH IS A WHOLE TIER SHORTER AGAIN.
   Measured at 844x390:

      .fd padding           28      (14 top + 14 bottom)
      the stage row        397.5    (6 of padding + a 381.5px card)
      gap                   12
      the controls          66
      total                503.5    against a 390px viewport

   114px of scroll on a page that says `min-height: 100svh`, and because
   the card is `align-self: center` inside a row taller than the screen,
   the first thing lost is its HEAD -- the issuing authority, the
   photograph and NAME, i.e. the top of a document, which is the one part
   of a passport that is not optional. Caught on screen with the controls
   scrolled into view: the card began above y=0.

   The card is where the height is, and the reason it is 381.5 tall is
   that the value column is only 124px wide, so three of the nine values
   ("Calling death an engineering problem", "Neurons / Silicon / Code /
   Capital", "Aiding and abetting dangerous ideas") each break over two
   lines. The fix is therefore WIDTH, not shrinking type: every pixel
   taken off the photo column, off the two gaps and off the label column
   goes straight into the value column, and each line that stops wrapping
   is worth 22px of height on its own. Type comes down only a little, and
   only as the last term.

   ⚠️ `align-self: start` is the safety net, not the fix. If a longer
   value ever reopens the overflow, the card is then clipped at the
   BOTTOM, where the page can be scrolled to reach it, instead of having
   its head cut off above a viewport nobody can scroll upward into. */
@media (max-height: 560px) and (min-width: 721px) {
  .fd { padding: 8px 0; gap: 8px; }
  .fd__stage { padding-top: 6px; }
  .fd__ctl { margin-top: 0.9em; padding-bottom: 10px; }

  /* ★ AND THE CARD GETS WIDER, which is the lever that actually moves
     this. 44vw is 371px at 844 and leaves the values 124px; at 50vw they
     get 175 and two more of them stop breaking over two lines, which is
     42px of height for 51px of width that was doing nothing. The figure
     is `min(66vw, 100vw-380px, 1020px)` = 464px here, so the card's left
     edge lands 76px inside it -- but that part of the plate is already
     past the 46% point of its own feather mask (alpha ~0.2 there) and
     the card paints over it with its own surface at z-index 2. The
     photograph loses a sliver of its faintest tail; the document becomes
     legible. */
  .fd__stage > .doc { max-width: min(50vw, 620px); }
  .doc { padding: 10px; align-self: start; }
  .doc__head { margin-bottom: 0.7em; }
  /* the photo is here so the filaments on her neck read; 86px still
     carries them, and 15px of it is three fewer wrapped lines */
  .doc__body { grid-template-columns: clamp(86px, 10vw, 120px) 1fr; gap: 12px; }
  .doc__fields { gap: 0.24em; }
  .f { font-size: 0.88em; grid-template-columns: minmax(72px, 10ch) 1fr; gap: 0.9ch; }
}

@media (max-width: 720px) {
  /* The beam still crosses her, but the document lands underneath rather
     than beside -- below this width a two-column split makes both the
     figure and the data page too small to read. */
  /* full-bleed on a phone: no side padding, so the photograph reaches
     both edges the way it reaches the left edge on a laptop */
  .fd__stage { grid-template-columns: 1fr; gap: 18px; padding: 0; }
  /* ★ FULL WIDTH ON A PHONE. The old rule (58% of the column) was sized
     for the standing silhouette, which was a figure on black and looked
     fine small. This plate is a PLACE -- a coastline, a moon, her in the
     air over it -- and at 211px none of that is legible. There is no card
     beside it here, so it takes the whole width. */
  .subject {
    /* ⚠️ BACK INTO THE FLOW. The desktop rule takes her out of it so she
       can be viewport-height; stacked, that would float her over the card
       instead of sitting above it. */
    /* ⚠️ `translate` HAS TO BE RESET TOO. The desktop rule centres her with
       top:50% + translate:-50%, and this block only put position back to
       static -- a static element still honours `translate`, so she was
       shifted up by half her own height and sat at y -244..244 over the
       card. Resetting position is not resetting placement. */
    position: static; top: auto; translate: none;
    height: auto; width: 100%; max-width: none;
    aspect-ratio: 4 / 5;
    justify-self: stretch;
    /* and the feather goes the other way: nothing sits to its right, so
       the fade belongs on the BOTTOM edge, into the card below it. */
    -webkit-mask-image: linear-gradient(180deg, #000 0 78%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 0 78%, transparent 100%);
    -webkit-mask-composite: source-over;
            mask-composite: add;
  }
  /* ⚠️⚠️ THE PASSPORT WAS HANGING OFF THE LEFT EDGE OF THE SCREEN, and
     `width: 100%` was the cause rather than the fix. The short-window tier
     above (`max-height: 820px`) gives `.doc` a `clamp(16px, 5vw, 32px)`
     margin on each side -- it was written for a short LAPTOP -- and a
     360x800 phone matches it on height. Margins sit OUTSIDE width, so
     100% + 36px of margin is a 396px card in a 360px column, and
     `justify-self: end` puts every pixel of that overflow on the LEFT.
     Measured at 360x800: the card began at x=-18 and the portrait at
     x=-5, both cut, and `.fd`'s `overflow: clip` hid the evidence.
     ⚠️ AND 390x844 WAS WRONG THE OTHER WAY. At 844 tall the height tier
     does not match, so there were no margins at all and the card sat
     flush against both screen edges -- a document with no gutter, its
     border touching the glass. One viewport clipped it, the next had it
     bleeding; neither was a passport sitting on a page.
     ✓ `width: auto` + `justify-self: stretch` lets the card fill the
     column MINUS its margins, and the gutter is declared here so it no
     longer depends on whether a phone happens to be under 820px tall.
     ★ The general trap, which has now cost this project twice: a
     HEIGHT-ONLY media query cannot tell a short laptop from a tall phone.
     The vision page hit the same thing from the other side, where a
     width-only query could not tell a narrow phone from a landscape one. */
  .doc {
    width: auto;
    justify-self: stretch;
    margin-left: clamp(14px, 4vw, 20px);
    margin-right: clamp(14px, 4vw, 20px);
  }
  /* the controls share the card's gutter, so the two line up */
  .fd__ctl {
    margin-left: clamp(14px, 4vw, 20px);
    margin-right: clamp(14px, 4vw, 20px);
    padding-left: 0; padding-right: 0;
  }
  /* ⚠️ HER OWN SENTENCE WAS 158px WIDE. `max-width: min(44vw, 52ch)` plus
     `text-align: right` is the desktop rule, where it hugs the card in the
     empty half of a two-column page. On a phone 44vw is 158px, so the one
     line on this page that is her SPEAKING broke over five ragged
     right-aligned lines in a column narrower than the card above it.
     Full width and centred here, which is how her serif prose is set on
     /our-vision/ -- under a full-width card it reads as her caption to the
     document, which is exactly what it is. */
  .fd__said {
    justify-self: stretch;
    max-width: none;
    text-align: center;
    padding: 0 clamp(14px, 4vw, 20px);
  }
  .doc__body { grid-template-columns: clamp(84px, 24vw, 120px) 1fr; }
  /* ⚠️ THE LABEL COLUMN WAS EATING THE VALUES. `minmax(96px, 11.5ch)` is
     the desktop split, and 96px of a 211px field area leaves 106 for the
     value -- so "Michelle Krystallis" broke across two lines in the NAME
     field, the first row of a document, which is where a passport looks
     most like a mistake. The labels are short words ("Name", "Age",
     "Status"); they never needed 96px, they inherited it from a card
     three times this wide.
     Measured at 360x800, 96 -> 74: NAME goes to one line, KNOWN FOR and
     WANTED FOR each drop from three lines to two, and the card loses 3 of
     its 15 lines of text -- so this buys height as well as dignity.
     ✓ The PHOTO is untouched. Taking the photo column down to 20vw as well
     was measured and gained nothing further, and her face is the second
     largest thing on the card by design. */
  .f { grid-template-columns: minmax(74px, 9.5ch) 1fr; gap: 0.9ch; }

  /* ★★ SCAN AT THE TOP -- she asked for it and she kept it: "i like the
     scan at the top". Measured reason it was needed: the button sat at
     y=1058 on an 844px phone, 214px below the fold, so you landed on this
     page with no control on it anywhere.
     ⚠️ FLEX, NOT THE INHERITED GRID. `.fd` is `grid-template-rows:
     minmax(0, 1fr) auto`, so `order: -1` drops the control row into the
     STRETCHY first row and opens a 219px gap between the button and the
     photograph. A column flex container sizes children to content. */
  .fd { display: flex; flex-direction: column; justify-content: flex-start; }
  .fd__ctl { order: -1; margin-top: 0; padding-top: clamp(10px, 2svh, 18px); }
  body.is-scanned .fd__ctl { order: 1; margin-top: 1.2em; padding-top: 0; }

  /* ✗✗ REVERTED, AND IT SHIPPED IN THE SAME COMMIT AS THE LINE ABOVE WHICH
     IS WHY IT LOOKED LIKE THE BUTTON WAS AT FAULT: `.doc { display: none }`
     until `.is-live`. It removed ~570px of reserved space, so pressing SCAN
     grew the page by that much IN ONE FRAME and the entire layout jumped
     under her thumb. "now it does this weird zoom" -- that is precisely
     what a reflow that size looks like, and it is the card appearing, not
     the button moving.
     ★ The empty space it was trying to reclaim is BELOW THE FOLD and
     nobody scrolls into it before pressing scan. A layout that is honest
     and still beats one that is compact and lurches. Tighten the stack
     instead -- see the spacing tier below. */

  /* ⚠️ THE BOTTOM FEATHER WAS EATING HER LEGS. 78% -> transparent removed
     the lower fifth of the plate, and since the plate's top third is night
     sky, what was left on screen was a band containing her head and one
     wing -- which is why a 4:5 image in a 4:5 box still read as a hard
     crop. 88% keeps the fade into the card and gives her body back. */
  .subject {
    -webkit-mask-image: linear-gradient(180deg, #000 0 88%, transparent 100%);
            mask-image: linear-gradient(180deg, #000 0 88%, transparent 100%);
  }
  /* ⚠️ AND THE GLOW GOES. Its whole job is the empty half of a two-column
     page; stacked, the document sits directly under her and there is no
     void to fill. Worse, its geometry is expressed as percentages of a
     fixed layer positioned for the side-by-side layout, so here the floor
     band and the contact spill landed ON the card -- a visible orange
     smear through the fields. An effect tuned to one layout has to be
     switched off in the other, not merely rescaled. */
  .fd__glow { display: none; }
}
