/* ============================================================================
   MEDIA ROOM  ·  service.tnaado.ca /media  ·  THE CONTINUOUS RUN
   ----------------------------------------------------------------------------
   THE BODY OF THE PAGE ONLY. The hero is not here — it is THE MOVEMENT, in
   css/media-hero-movement.css, and it owns everything above the first line of
   body copy.

   WHY THIS FILE WAS REWRITTEN (2026-08-12). Ethan, on the previous build:
   "this is not what i want, this site is like 3/10 effort. i need the site to
   flow, not set sections, one long continuous page with graphics mixing
   between sections."

   He was describing something measurable, not a mood. The page he was looking
   at was seven sibling <section> elements, each one a self-contained unit with
   its own .inner max-width, its own numbered eyebrow (S.01 … S.05), its own
   h2, its own lede, and then a grid of bordered boxes inside it — fifteen of
   those boxes across the page. Each section carried clamp(6rem,11vw,9rem) of
   padding top AND bottom, so consecutive ideas were separated by up to 18rem
   of empty ground, and four .ember dividers were drawn into that gap to mark
   the seam even harder. Three photographs existed in the whole body, all three
   at the same width, all three inside an identical bordered frame. That is the
   definition of "set sections": the reader is stopped, re-introduced, and
   handed a new box, five times.

   WHAT REPLACED IT. One grid for the entire body. Every paragraph, heading,
   list, note and photograph is a direct child of a single .mflow element and
   differs only in HOW WIDE IT SITS — reading column, popout, feature, full
   bleed, or a half-bleed that starts at the page edge and finishes inside the
   text. Nothing is boxed, nothing is re-introduced, and the transition from
   one idea to the next is a photograph rather than a gap. There is one
   background colour for the whole run.

   THE GRID is the named-line breakout pattern (Rachel Andrew's original
   full-bleed grid, as popularised by Ryan Mulligan and Viget). It is the right
   tool precisely because it makes "the same column, wider" the cheapest thing
   to express — which is what an editorial spread is. Zero browser-support
   concerns; named grid lines shipped with the original Grid spec.

   UNITS AND COLOUR SPACE, stated rather than inherited. All measurements are
   CSS px / rem / ch, all colours are sRGB and come from the locked palette in
   site.css (--ink #0a0a0a, --red #c8102e, --red-text #e8455a, --on-black
   #f5f1e8, --bone #faf8f3). Nothing new is invented here.

   COST. No canvas, no WebGL, no shader compiles, no scroll listeners, no
   per-frame filter. The whole composition is grid placement plus static
   background-image gradients. The only motion is the existing [data-reveal]
   opacity/transform transition from site.css, which is compositor-side, and
   the one sticky element (.mflow__folio) which the compositor handles without
   a main-thread scroll handler. Photographs carry width/height attributes
   written by scripts/sync-gallery.mjs, so the run does not reflow as it loads.
   ========================================================================== */


/* ═══════════════════════════════════════════════════════════════════════════
   1 · THE GRID
   Five widths, named. A child with no width class sits in the reading column.
   ═════════════════════════════════════════════════════════════════════════ */

.mflow {
  /* The colour the page itself is, captured here so the photographic surfaces
     below can reset --ink without the edge-feathers following them. See §4. */
  --m-ground: var(--ink);
  --mgap: clamp(1.1rem, 5vw, 3rem);
  --m-full: minmax(var(--mgap), 1fr);
  --m-feature: minmax(0, 10rem);
  --m-popout: minmax(0, 4.5rem);
  --m-content: min(63ch, 100% - var(--mgap) * 2);

  display: grid;
  grid-template-columns:
    [full-start] var(--m-full)
    [feature-start] var(--m-feature)
    [popout-start] var(--m-popout)
    [content-start] var(--m-content) [content-end]
    var(--m-popout) [popout-end]
    var(--m-feature) [feature-end]
    var(--m-full) [full-end];
  row-gap: clamp(1.1rem, 2vw, 1.6rem);

  position: relative;
  background: var(--ink);
  padding-block: clamp(3.5rem, 7vw, 6rem) clamp(4rem, 8vw, 7rem);
  /* The run begins the instant the hero's black ends. No seam, by design. */
  overflow-x: clip;
}

.mflow > *          { grid-column: content; }
.mflow > .u-popout  { grid-column: popout; }
.mflow > .u-feature { grid-column: feature; }
.mflow > .u-full    { grid-column: full; }

/* The two half-bleeds. These are the "graphics mixing between sections" move:
   a photograph that starts at the page edge and ends INSIDE the reading
   column, so the picture and the paragraph occupy the same horizontal band
   rather than taking turns. */
.mflow > .u-bleed-left  { grid-column: full-start / content-end; }
.mflow > .u-bleed-right { grid-column: content-start / full-end; }

/* Vertical accents. Used sparingly and by hand — the point is that the spacing
   between ideas VARIES, which is what makes a run read as continuous rather
   than as a stack of equal slabs. */
.u-lead-lg { margin-block-start: clamp(3.5rem, 7vw, 6.5rem); }
.u-lead-md { margin-block-start: clamp(2.2rem, 4.5vw, 4rem); }
.u-lead-sm { margin-block-start: clamp(1.2rem, 2.5vw, 2rem); }
/* Negative leads pull a block up over the photograph above it. */
.u-overlap { margin-block-start: clamp(-7rem, -6vw, -2.5rem); position: relative; z-index: 2; }


/* ═══════════════════════════════════════════════════════════════════════════
   2 · TYPE IN THE RUN
   No section banners. A heading is just a bigger line in the same column.
   ═════════════════════════════════════════════════════════════════════════ */

/* The opening statement — the only display-scale type in the body. */
.m-standfirst {
  font-family: var(--font-display);
  font-weight: 300;
  /* T-1041: was clamp(1.9rem, 4.4vw, 3.35rem) */
  font-size: clamp(1.65rem, 3.6vw, 2.8rem);
  line-height: 1.1;
  letter-spacing: -.025em;
  color: var(--on-black);
  margin: 0;
  text-wrap: balance;
}
.m-standfirst em { font-style: italic; color: var(--red); }

/* Body copy. One measure for the whole page, so the eye never re-calibrates. */
.m-p {
  font-size: clamp(1rem, 1.35vw, 1.12rem);
  line-height: 1.74;
  color: var(--on-black-mute);
  margin: 0;
  max-width: 63ch;
}
.m-p strong { color: var(--on-black); font-weight: 500; }
.m-p em { color: var(--on-black); font-style: italic; }

/* The lede that follows the standfirst — larger, lighter, still the same column. */
.m-p--lede {
  /* was clamp(1.05rem, 1.65vw, 1.28rem) */
  font-size: clamp(.98rem, 1.3vw, 1.15rem);
  line-height: 1.62;
  color: rgba(245,241,232,.74);
}

/* A running head. Replaces the S.0x eyebrow banner: it does not sit on its own
   line above a slab, it hangs in the margin beside the paragraph it opens. */
.m-turn {
  font-family: var(--font-display);
  font-weight: 400;
  /* T-1041: was clamp(1.45rem, 2.7vw, 2.1rem) */
  font-size: clamp(1.28rem, 2.3vw, 1.8rem);
  line-height: 1.16;
  letter-spacing: -.02em;
  color: var(--on-black);
  margin: 0;
  text-wrap: balance;
}
.m-turn em { font-style: italic; color: var(--red); }

/* The numeral that used to be a card's kicker. Now a marginal mark. */
.m-mark {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--red-text);
  margin: 0 0 .55rem;
  display: flex;
  align-items: baseline;
  gap: .6rem;
}
.m-mark::before {
  content: '';
  width: 22px; height: 1px;
  background: var(--red);
  flex: none;
  transform: translateY(-.25em);
}

/* Tag lists were three bordered pills per card. They are now a run-in line —
   same information, no box, and it reads as part of the sentence flow. */
.m-run-in {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: .35rem 1.1rem;
  margin: 0;
  padding: 0;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--on-black-faint);
}
.m-run-in li { position: relative; }
.m-run-in li + li::before {
  content: '·';
  position: absolute;
  left: -.68rem;
  color: rgba(245,241,232,.3);
}

/* Inline "keep reading elsewhere" link. A line with a rule, not a button. */
.m-go {
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-black);
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding-block-end: .3rem;
  border-bottom: 1px solid rgba(200,16,46,.45);
  transition: border-color 200ms var(--ease-out-expo), color 200ms var(--ease-out-expo);
}
.m-go span:last-child { transition: transform 200ms var(--ease-out-expo); }
.m-go:hover, .m-go:focus-visible { color: var(--red-text); border-bottom-color: var(--red); outline: none; }
.m-go:hover span:last-child, .m-go:focus-visible span:last-child { transform: translateX(4px); }
.m-go:active { transform: translateY(1px); transition-duration: 100ms; }


/* ═══════════════════════════════════════════════════════════════════════════
   3 · THE SPREAD
   An asymmetric two-up row. Every instance sets its own --split and --shift,
   so no two rows share proportions and the page never resolves into a grid of
   equal cards. This is the single primitive that carries most of the page.
   ═════════════════════════════════════════════════════════════════════════ */

.mspread {
  display: grid;
  grid-template-columns: var(--split, 1.15fr .85fr);
  gap: var(--sgap, clamp(1.6rem, 3.5vw, 3.2rem));
  /* Per-instance, because it is a composition decision and not a default. A
     tall portrait beside three lines of copy leaves the copy top-aligned with
     ~330px of dead ground under it (measured at 1440); centring that copy
     against the frame turns the same gap into deliberate white space above and
     below. Rows whose two columns are close in height stay at `start`. */
  align-items: var(--align, start);
}
.mspread__text { display: flex; flex-direction: column; gap: clamp(.9rem, 1.6vw, 1.3rem); }
.mspread__media { margin-block-start: var(--shift, 0); }
/* Reversed: media reads first in the DOM would break source order for screen
   readers, so the media stays second in the DOM and moves with `order`. */
.mspread--flip .mspread__text  { order: 2; }
.mspread--flip .mspread__media { order: 1; }


/* ═══════════════════════════════════════════════════════════════════════════
   3B · THE CARVE  ·  the curved boundary between copy and photograph
   ---------------------------------------------------------------------------
   FROM ETHAN'S OWN SKETCH (whiteboard, 2026-08-12). He drew the page as three
   bands, and the top band is the one that matters: the boundary between the
   text and the photograph under it is NOT a horizontal rule. It is a single
   smooth curve that leaves the LEFT page edge near the top, sweeps down and to
   the right, and flattens out into a horizontal line across the rest of the
   width. The photograph lives BELOW that curve, so it rides high up the left
   side of the page and the copy sits in the pocket the curve opens on the
   right. His words for it: "carve out the image in the layer so it's seen in a
   super flowy framing."

   WHY THE PANEL IS CLIPPED AND NOT THE PHOTOGRAPH. The obvious build is
   clip-path on the <img>. It is the wrong one: if the clip fails to resolve
   for any reason the image becomes a full rectangle and the copy — which is
   positioned over the pocket — lands on top of a photograph with no scrim and
   is unreadable. So the PHOTOGRAPH is a plain untouched rectangle and a panel
   painted in the page's own ground colour is laid over it and clipped instead.
   Failure mode is then "the picture is hidden", never "the words cannot be
   read", and the copy is always on flat ground so contrast is guaranteed in
   both themes without a scrim or a text-shadow.

   WHY objectBoundingBox. clip-path: path() takes absolute px and would need a
   different path per breakpoint and per content height. An SVG <clipPath> with
   clipPathUnits="objectBoundingBox" is expressed in 0..1 of the element's own
   box, so one path scales to every viewport. The curve does shear with the
   box's aspect ratio, which is why .mcarve pins its own aspect-ratio rather
   than letting content set the height, and why the narrow breakpoint swaps to
   a second, shallower path (#carve-sweep-narrow) instead of stretching the
   wide one into a cliff on a phone. Both paths are defined inline at the top
   of the run in media.html.

   COST. One static clip on one element. No animation touches it, so it is a
   paint-time cost on layout only and nothing per frame — measured identical
   frame timings with and without. There is no canvas, no filter, no blur.
   ═════════════════════════════════════════════════════════════════════════ */

.mcarve {
  position: relative;
  /* The height is DECLARED, not derived — see objectBoundingBox above: the
     curve's shape is a function of the box's ratio, so the box has to own its
     height or the sweep changes shape every time the copy rewraps.

     A CLAMPED HEIGHT, not aspect-ratio. aspect-ratio was the first build and
     it is unbounded at both ends: a swept sweep of breakpoints measured the
     band at 1949px tall at 1000px wide on the phone ratio, and 1173px at
     2560px wide on the desktop one. A band taller than the viewport is not a
     band. clamp() keeps the middle term (the ratio the sweep was drawn for)
     and puts a floor and a ceiling on it, so no viewport can push the height
     somewhere the curve was never designed for.

     The 46vw middle term is the 1440/660 proportion the composition was tuned
     at. The 860 ceiling is where the sweep stops reading as a sweep. */
  height: var(--carve-h, min(clamp(360px, 40vw, 580px), 60svh));
  isolation: isolate;
  margin: 0;
}
/* The raised variant, for a carve using one of the SHALLOW sweep paths — a
   shallower panel is a shorter copy pocket, so the band buys the height back.
   T-1095 moved this out of an inline style on media.html so the narrow-
   viewport --carve-h further down this file can still win on a phone; an
   inline custom property beats every stylesheet rule, media query or not. */
.mcarve--tall { --carve-h: min(clamp(420px, 44vw, 660px), 66svh); }
.mcarve__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Framed from the sketch: the photograph is read at the LEFT of the band,
     where the curve lifts it almost to the top of the page. */
  object-position: var(--carve-pos, 50% 42%);
  display: block;
  background: #111;
  z-index: 0;
}
/* -1px, not 0: the clip edge antialiases against whatever is behind it, and at
   inset:0 that left a 1px seam of photograph along the panel's straight top and
   side edges. Over-sizing the panel by a pixel puts those straight edges just
   outside the band, where they land on page ground of the identical colour. */
.mcarve__panel {
  position: absolute;
  inset: -1px;
  z-index: 1;
  pointer-events: none;
  /* A hairline that TRACES THE CURVE, so the sweep is legible whatever
     photograph is behind it. Needed because the panel is the page ground: in
     light mode cream against a photo is obvious, but in dark mode #0a0a0a
     against the dark half of a dark frame very nearly disappeared — caught in
     the 390px dark screenshot, where the carve is the whole gesture and it was
     barely readable.

     It has to be a filter on the PARENT of the clipped box, not on the clipped
     box itself. Filter Effects orders filter BEFORE clip-path on the same
     element, so a drop-shadow declared alongside the clip would shadow the
     unclipped RECTANGLE and then be clipped away — a no-op. Moving the clip
     onto ::after and the filter onto the parent applies the shadow to the
     already-clipped result, which is what follows the curve. Costs one static
     offscreen buffer at paint; nothing per frame. */
  filter: drop-shadow(0 1.5px 0 rgba(245,241,232,.16));
}
.mcarve__panel::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--m-ground, #0a0a0a);
  clip-path: url(#carve-sweep);
}
.mcarve__copy {
  position: absolute;
  z-index: 2;
  top: var(--carve-copy-top, clamp(.4rem, 2.2vw, 1.6rem));
  inset-inline-end: clamp(1.1rem, 5vw, 3rem);
  /* Sits in the pocket the curve opens on the right. 56ch keeps the same
     measure as the reading column so the eye does not re-calibrate. */
  width: min(56ch, 62%);
  display: grid;
  gap: clamp(.7rem, 1.5vw, 1.15rem);
  justify-items: start;
}
/* The caption belongs to the photograph, so it hangs at the bottom-left where
   the picture is deepest. Same folio-clearing indent as every other full-bleed
   caption in the run (§4), and a shadow because this one is ON the image. */
.mcarve__cap {
  position: absolute;
  z-index: 2;
  left: 0;
  bottom: clamp(.7rem, 1.8vw, 1.3rem);
  padding-inline-start: clamp(1.1rem, 5vw, 3rem);
  margin: 0;
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: rgba(245,241,232,.72);
  text-shadow: 0 1px 10px rgba(10,10,10,.9), 0 1px 3px rgba(10,10,10,.85);
  max-width: 44ch;
}
@media (min-width: 1180px) {
  .mcarve__cap { padding-inline-start: clamp(3.6rem, 6vw, 5.5rem); }
}


/* ── THE CURL ──────────────────────────────────────────────────────────────
   The sketch's third band: a photograph running off one page edge whose INNER
   edge is a big rounded corner, with the copy squeezed against it. Same shape
   family as the carve, one thousandth of the machinery — a border-radius is
   free, composites nothing and needs no clip source. Applied to the two
   half-bleeds so the edge that finishes inside the text is curved and the edge
   that runs off the page stays square, which is what makes the picture read as
   continuing past the viewport rather than as a rounded card floating on it. */
/* T-1124, 2026-08-14 — THE CURL WAS SIZED OFF THE VIEWPORT AND LANDED ON A
   SHORT BOX. 7vw capped at 7.5rem measured 100.8px at 1440 on the desk spread,
   whose picture is 339px tall — a radius just under a THIRD of the box's short
   side. At that ratio the two curled corners meet in the middle of the edge and
   the picture stops reading as a photograph running off the page and starts
   reading as a lozenge. Ethan's word for it was "a blob", and it also broke the
   plate badge (see §11): the badge is pinned to the top-right corner, which the
   radius had carved away entirely, so the "6" floated on bare page ground next
   to the picture instead of sitting on it.
   The radius now scales far more slowly, so it stays a deliberate soft inner
   edge at every width instead of a shape: 36px at 1440 (11% of the same box's
   height), 20px at 390. The sketch's intent survives — the edge that finishes
   inside the copy is curved, the edge leaving the viewport stays square. */
   DECLARED ON THE CONTAINER, NOT THE <img>. The badge is a SIBLING of the
   image, so a --curl set on the image itself is invisible to it and the
   badge's inset below would resolve to 0. It is set on .mfig / .mspread__media
   and inherits down to both. */
.mfig--curl,
.mspread--curl .mspread__media { --curl: clamp(1.25rem, 2.4vw, 2.25rem); }

/* Which corners get the radius is decided by WHICH PAGE EDGE the picture runs
   off, never by the element itself — the square edge is the one leaving the
   viewport. Left-bleeding pictures curl their right corners, right-bleeding
   pictures curl their left corners. Get this backwards and the picture reads
   as a rounded card that happens to be clipped, which is the opposite of the
   intent. .mspread--flip puts its media on the LEFT, so it is the left-bleed
   case; an unflipped .mspread--curl puts its media on the RIGHT. */
.u-bleed-left.mfig--curl img,
.mspread--flip.mspread--curl .mspread__media img {
  border-start-end-radius: var(--curl);
  border-end-end-radius: var(--curl);
}
.u-bleed-right.mfig--curl img,
.mspread--curl:not(.mspread--flip) .mspread__media img {
  border-start-start-radius: var(--curl);
  border-end-start-radius: var(--curl);
}


/* ═══════════════════════════════════════════════════════════════════════════
   4 · PHOTOGRAPHS IN THE RUN
   No frames, no borders, no cards. A caption hangs under the picture in mono
   at the smallest size on the page — the way a plate is captioned in print.
   ═════════════════════════════════════════════════════════════════════════ */

.mfig { margin: 0; position: relative; }
.mfig img {
  width: 100%;
  height: auto;
  display: block;
  /* Photographs stay photographic in both themes. A cream page behind a
     production still would fight it; the picture is the surface here. */
  background: #111;
}
.mfig--crop img { aspect-ratio: var(--ratio, 16 / 9); object-fit: cover; }

/* T-1118, 2026-08-14 — A PORTRAIT PLATE MUST NOT BECOME THE WHOLE SCREEN.
   .mspread is two columns until it collapses to one, at which point the media
   half is the full page width and a portrait renders at its intrinsic ratio.
   operator-body-rig-gimbal.jpg is 1050x1400, so at the 768 breakpoint it
   measured 768x1024 — exactly 100vh, a screen of photograph with no page
   around it. That is the thing Ethan has objected to three times, and it is
   the same failure the .u-band note above was written for; these in-spread
   plates simply never got a band.
   640px lands it at 62vh at 768, which is what the SAME picture already
   measures at 390 (520px/62vh) — so the fix makes the breakpoints agree
   rather than inventing a new size. Measured on /media: the other two spread
   plates sit at 42vh and 36vh and are untouched, and this one is 59vh at
   1440 and 62vh at 390, both already under the cap. */
.mspread__media .mfig img {
  max-height: min(70svh, 640px);
  object-fit: cover;
  object-position: var(--band-pos, 50% 40%);
}

/* A PLATE THAT OWNS ITS HEIGHT. T-1095 — see the --band-* note in site.css.
   Without this a full-bleed .mfig is width:100%/height:auto, so a 4:3 still
   at 1440px wide is 1080px tall and the reader gets a screen of photograph
   with no page around it. --band-h picks one of the three shared sizes;
   --band-pos is the crop anchor and is set per picture from the frame, never
   left to default on a photograph with a face in it. */
.mfig.u-band img {
  height: var(--band-h, var(--band-md));
  object-fit: cover;
  object-position: var(--band-pos, 50% 50%);
}
.mfig figcaption {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--on-black-faint);
  margin-block-start: .7rem;
  max-width: 46ch;
}
/* A caption under a picture that starts at the page edge must NOT also start at
   the page edge — it is type, and type wants a margin. Above 1180px the indent
   also has to clear the folio, which is fixed in that same left margin. */
.mflow > .u-full > figcaption,
.mflow > .u-bleed-left > figcaption { padding-inline-start: clamp(1.1rem, 5vw, 3rem); }
@media (min-width: 1180px) {
  .mflow > .u-full > figcaption,
  .mflow > .u-bleed-left > figcaption { padding-inline-start: clamp(3.6rem, 6vw, 5.5rem); }
}

/* T-1124 — THE SAME INDENT, FOR A CAPTION ONE LEVEL DEEPER. The two rules
   above only match a figcaption that is a DIRECT child of the bleeding
   element. In a .mspread the bleeding element is the spread itself and the
   caption is two levels down (.mspread__media > .mfig > figcaption), so it
   matched nothing and the desk spread's caption rendered at x=0 — mono type
   jammed flush against the left edge of the viewport with no margin at all,
   which is why it read as running off the page rather than as a caption.
   Measured before: x=0, padding-inline-start 0px. Same tokens as above so
   the two kinds of caption sit on one line, not two near-misses. */
.mspread.u-bleed-left .mspread__media figcaption { padding-inline-start: clamp(1.1rem, 5vw, 3rem); }
@media (min-width: 1180px) {
  .mspread.u-bleed-left .mspread__media figcaption { padding-inline-start: clamp(3.6rem, 6vw, 5.5rem); }
}
/* The right-bleeding case runs off the OTHER edge, so its caption needs the
   trailing margin instead — it already starts inside the grid. */
.mspread.u-bleed-right .mspread__media figcaption { padding-inline-end: clamp(1.1rem, 5vw, 3rem); }

/* A full-bleed plate the next idea is written ON TOP OF, rather than after.
   The literal reading of "graphics mixing between sections". */
.mplate { position: relative; margin: 0; }
.mplate img {
  width: 100%;
  height: clamp(360px, 62vh, 720px);
  object-fit: cover;
  display: block;
}
/* The scrim is weighted to the BOTTOM, where the copy sits. Its top stop only
   has to take the edge off the feather join; measured at .72 it was burying a
   photograph that is already a night exposure, so the picture read as a black
   band with type on it. .42 keeps the join soft and lets the frame be seen. */
.mplate__scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(10,10,10,.42) 0%, rgba(10,10,10,.16) 30%, rgba(10,10,10,.44) 64%, rgba(10,10,10,.93) 100%);
  pointer-events: none;
}
/* `bottom` clears the bottom edge-feather (clamp(44px,7vw,96px)) rather than
   sitting inside it. In dark mode the feather is black and overlapping it cost
   nothing; in light mode it is CREAM, and a cream wash under light type made
   the whole block unreadable — caught in a light-theme screenshot, not by
   reading the CSS. z-index 2 puts the copy above the feather either way. */
.mplate__copy {
  position: absolute;
  inset-inline: 0;
  bottom: clamp(4.6rem, 9vw, 7.5rem);
  padding-inline: clamp(1.1rem, 5vw, 3rem);
  max-width: 66ch;
  display: grid;
  gap: clamp(.55rem, 1.2vw, .9rem);
  z-index: 2;
}
@media (min-width: 1180px) { .mplate__copy { padding-inline-start: clamp(3.6rem, 6vw, 5.5rem); } }
.mplate .m-turn,
.mplate .m-p {
  text-shadow: 0 2px 26px rgba(10,10,10,.8), 0 1px 6px rgba(10,10,10,.85);
}
.mplate .m-p { color: rgba(245,241,232,.82); }

/* Feathered top/bottom edges on the full-bleed plates so a photograph dissolves
   into the page ground instead of butting against it with a hard horizontal
   line — a hard edge is a section boundary, which is the thing being removed.

   It fades --m-ground, NOT --ink. The two photographic surfaces (.mplate,
   .mroll) reset --ink to #0a0a0a inside themselves so they stay dark in light
   mode; if the feather read --ink it would fade BLACK into a cream page and
   draw the exact hard band it exists to dissolve. --m-ground is declared once
   on .mflow, computes there, and inherits down past those resets — so it is
   always the colour of the page the plate is sitting on.

   THE DEPTH IS THEME-DEPENDENT, and that is not a preference — see §11. A
   feather only works when the two tones it joins are close. Dark mode joins
   #0a0a0a to a dark photograph, so 96px of ramp is a vignette. Light mode
   joins cream to the same photograph, and 96px of ramp is a sweep through
   every mid-grey there is. §11 shortens it there rather than removing it, so
   the join is still soft and is no longer a band. */
.mfeather::before,
.mfeather::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  height: var(--m-feather-h, clamp(44px, 7vw, 96px));
  pointer-events: none;
  z-index: 1;
}
.mfeather::before { top: 0;    background: linear-gradient(to bottom, var(--m-ground, #0a0a0a), transparent); }
.mfeather::after  { bottom: 0; background: linear-gradient(to top,    var(--m-ground, #0a0a0a), transparent); }


/* ═══════════════════════════════════════════════════════════════════════════
   5 · THE FOLIO
   Replaces the five S.0x banners. A magazine puts the section name in the
   margin of the spread, where it orients you without interrupting you. This is
   that: one fixed marginal, desktop only, updated by js/media-flow.js.
   ═════════════════════════════════════════════════════════════════════════ */

.mflow__folio {
  position: fixed;
  left: clamp(.9rem, 2.2vw, 1.8rem);
  top: 50%;
  transform: translateY(-50%) rotate(180deg);
  writing-mode: vertical-rl;
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--on-black-faint);
  z-index: 40;
  pointer-events: none;
  display: none;
  align-items: center;
  gap: .9rem;
  opacity: 0;
  transition: opacity 320ms var(--ease-out-expo);
}
.mflow__folio.is-on { opacity: 1; }
.mflow__folio::after {
  content: '';
  width: 1px;
  height: 42px;
  background: linear-gradient(to bottom, var(--red), transparent);
}
.mflow__folio-label { transition: opacity 200ms var(--ease-out-expo); }
@media (min-width: 1180px) { .mflow__folio { display: flex; } }
@media (prefers-reduced-motion: reduce) {
  .mflow__folio, .mflow__folio-label { transition: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   6 · THE LEDGER  ·  three concentrations, grouped
   ---------------------------------------------------------------------------
   REORGANISED 2026-08-12 (Ethan: "organize the middle section a little more").

   The previous build was thirteen sibling rows sharing one hairline rhythm.
   That is a flat list, not a ledger: the two rows that were group HEADINGS
   were drawn at the same weight as the eleven line items beneath them, so
   nothing in the composition told the eye which items belonged to which
   concentration. Ten of the thirteen rows also carried an empty left cell, so
   a 13rem column ran the height of the block holding two words.

   HIERARCHY IS CARRIED BY TYPE AND BY THE RED NUMERAL, NOT BY A BOX. That is
   the constraint the whole page is built under — Ethan removed the boxed
   sections in the previous round and they do not come back to solve this. So
   the group is expressed three ways, all of them free: a display-scale roman
   numeral in --red hanging in the left margin (the same I/II/III the run uses
   above, so the ledger reads as the page's own summary rather than a new
   list); a display-scale name against .88rem items, a 1.7x jump; and a
   hairline that now separates GROUPS rather than every line, which takes the
   rule count from 13 to 3 so the eye counts three things instead of thirteen.

   The items sit in an auto-fit two-up so six deliverables occupy three lines
   instead of six, which is what closes the block up enough to read as one
   object per concentration.

   COST. Grid placement and type. No new colour, no border, no shadow, no
   filter, no animation — the only paint beyond text is a 5x1px rule per item.
   ═════════════════════════════════════════════════════════════════════════ */

.mledger { display: grid; gap: 0; margin: 0; }

.mledger__group {
  display: grid;
  grid-template-columns: minmax(0, 8.5rem) minmax(0, 1fr);
  gap: clamp(.9rem, 2.5vw, 2.6rem);
  padding-block: clamp(1.5rem, 3vw, 2.5rem);
  border-top: 1px solid rgba(245,241,232,.14);
}
.mledger__group:last-child { border-bottom: 1px solid rgba(245,241,232,.14); }

/* The mark hangs in the left margin: numeral first, then the one-word
   concentration under it. align-content:start so it stays pinned to the top of
   a group whose body is four lines or ten. */
.mledger__mark { display: grid; gap: .4rem; align-content: start; }
.mledger__no {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 300;
  font-size: clamp(1.7rem, 3vw, 2.4rem);
  line-height: .86;
  letter-spacing: -.02em;
  color: var(--red);
  margin: 0;
}
.mledger__cat {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--on-black-faint);
  margin: 0;
}

.mledger__body { display: grid; gap: clamp(.6rem, 1.4vw, 1rem); }
.mledger__name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.15rem, 2vw, 1.5rem);
  line-height: 1.12;
  letter-spacing: -.015em;
  color: var(--on-black);
  margin: 0;
}
.mledger__note {
  font-size: .9rem;
  line-height: 1.6;
  color: var(--on-black-mute);
  margin: 0;
  max-width: 54ch;
}

/* Two-up where there is room, one-up where there is not. minmax on the track
   rather than a media query, so the fold happens when the ITEMS need it and
   not at a width guessed in advance.

   19rem, not 14: at feature width the body column measures ~890px, and 14rem
   let auto-fit place THREE tracks. Three columns pushed the last item ~600px
   from the note it belongs to and left a ragged hole on the two groups that
   do not have a multiple of three (II has five items, III has four). The
   floor has to exceed a third of the widest body column to hold two —
   890/3 = 297px — so 19rem (304px) is the smallest value that does. */
.mledger__ships {
  list-style: none;
  margin: .15rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: .42rem clamp(1rem, 2.5vw, 2.2rem);
}
.mledger__ships li {
  position: relative;
  font-size: .88rem;
  line-height: 1.5;
  color: var(--on-black);
  padding-inline-start: 1rem;
}
/* The same 1px red rule .m-mark uses, at item scale. Not a bullet glyph and
   not a pill — the page has one accent mark and this is it. */
.mledger__ships li::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  top: .66em;
  width: 6px;
  height: 1px;
  background: var(--red);
}

@media (max-width: 620px) {
  /* One column: the numeral and the concentration go side by side on one line
     rather than stacking, so the mark costs one line instead of two. */
  .mledger__group { grid-template-columns: 1fr; gap: .7rem; }
  .mledger__mark { grid-auto-flow: column; justify-content: start; align-items: baseline; gap: .7rem; }
  .mledger__no { font-size: 1.5rem; line-height: 1; }
  /* T-1121 · phone density. Two groups of eight items measured 860px on a
     390 viewport, which is the whole screen given to one list. The items are
     short lines, so the height was rhythm rather than content: the gap and
     the group padding carry it, not the type. Tightened here only — the
     desktop rhythm is unchanged. */
  .mledger__group { padding-block: 1.15rem; }
  .mledger__ships { gap: .26rem clamp(1rem, 2.5vw, 2.2rem); }
  .mledger__ships li { line-height: 1.42; }
  .mledger__note { margin-block-end: .35rem; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   7 · MARGINAL NOTES
   The three "desks" were three boxes in a row. A sidenote is what they
   actually are — an annotation on the paragraph beside them — so they now sit
   in the margin at feature width and read as marginalia, not as a card deck.
   ═════════════════════════════════════════════════════════════════════════ */

.mnotes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: clamp(1.2rem, 3vw, 2.6rem);
}
.mnote { border-top: 1px solid rgba(200,16,46,.35); padding-block-start: .8rem; }
.mnote__label {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--red-text);
  margin: 0 0 .5rem;
}
.mnote__value { font-size: .85rem; line-height: 1.65; color: var(--on-black-mute); margin: 0; }
.mnote__value b { color: var(--on-black); font-weight: 500; }


/* ═══════════════════════════════════════════════════════════════════════════
   8 · THE ROLL  ·  the folder-driven gallery
   Markup between the SVC-GALLERY markers in media.html is GENERATED by
   scripts/sync-gallery.mjs from assets/gallery/media/. Do not hand-edit it.

   Twelve columns, full bleed, grid-auto-flow: dense — so two figures whose
   column ranges do not collide pack into the same row on their own, and the
   run interlocks. Each figure carries its slot's --col/--span/--ratio/--nudge
   as inline custom properties, written by the script from the photograph's
   REAL pixel dimensions. Adding files to the folder extends the run; it does
   not require a layout change, which is the entire point.
   ═════════════════════════════════════════════════════════════════════════ */

/* The band the roll lives in. Photographic, so it keeps an ink ground in both
   themes, and it feathers into the page above and below rather than starting
   with a hard horizontal edge. */
.mroll {
  position: relative;
  background: var(--ink);
  padding-block: clamp(3rem, 6vw, 5.5rem);
}

.roll {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(.7rem, 1.6vw, 1.4rem);
  grid-auto-flow: row dense;
  align-items: start;
  padding-inline: clamp(.7rem, 2.4vw, 1.6rem);
}
.roll__fig {
  margin: 0;
  grid-column: var(--col) / span var(--span);
  margin-block-start: var(--nudge, 0);
}
.roll__fig img {
  width: 100%;
  height: auto;
  aspect-ratio: var(--ratio, 3 / 2);
  object-fit: cover;
  display: block;
  background: #111;
}
.roll__cap {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--on-black-faint);
  margin-block-start: .6rem;
  max-width: 40ch;
}

/* The four story beats ride the roll instead of occupying a section of their
   own. On desktop they sit in a sticky column the photographs scroll past; on
   narrow screens the column un-sticks and simply runs above them, because a
   sticky rail in a 380px viewport is a worse experience than no rail. */
/* Head and rail share one left edge — the band's own spine. Centring the head
   over a left-aligned rail is what made this read as two unrelated blocks in
   the first pass. Above 1180px both indent far enough to clear the folio. */
.mroll__head,
.mroll-wrap { padding-inline: clamp(1rem, 3vw, 2.2rem); }
@media (min-width: 1180px) {
  .mroll__head,
  .mroll-wrap { padding-inline-start: clamp(3.6rem, 6vw, 5.5rem); }
}
.mroll__head {
  max-width: 74ch;
  display: grid;
  gap: clamp(.7rem, 1.4vw, 1.1rem);
  margin-block-end: clamp(2rem, 4vw, 3.4rem);
}
.mroll-wrap {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: clamp(1.4rem, 3.5vw, 3rem);
  align-items: start;
}
.mbeats { position: sticky; top: calc(var(--header-h) + 2.5rem); display: grid; gap: 1.4rem; }
.mbeat { opacity: .34; transition: opacity 260ms var(--ease-out-expo); }
.mbeat.is-live { opacity: 1; }
.mbeat__num {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--red-text);
  margin: 0 0 .3rem;
}
.mbeat__title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: 1.06rem;
  line-height: 1.2;
  color: var(--on-black);
  margin: 0 0 .35rem;
}
.mbeat__copy { font-size: .82rem; line-height: 1.6; color: var(--on-black-mute); margin: 0; }

@media (max-width: 1023px) {
  .mroll-wrap { grid-template-columns: 1fr; }
  .mbeats { position: static; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
  .mbeat { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .mbeat { opacity: 1; transition: none; } }


/* ═══════════════════════════════════════════════════════════════════════════
   9 · WHERE IT LANDS
   Was three bordered cards. Now three entries in one continuous list, split by
   a hairline, with the name doing the work the border used to do.
   ═════════════════════════════════════════════════════════════════════════ */

.mlands { display: grid; }
.mlands__item {
  display: grid;
  grid-template-columns: minmax(0, 16rem) minmax(0, 1fr);
  gap: clamp(.8rem, 3vw, 2.6rem);
  align-items: start;
  padding-block: clamp(1.4rem, 3vw, 2.2rem);
  border-top: 1px solid rgba(245,241,232,.11);
  transition: background 260ms var(--ease-out-expo);
}
.mlands__item:last-child { border-bottom: 1px solid rgba(245,241,232,.11); }
.mlands__item:hover, .mlands__item:focus-visible {
  background: linear-gradient(to right, rgba(200,16,46,.08), transparent 62%);
  outline: none;
}
.mlands__name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.35rem, 2.4vw, 1.85rem);
  line-height: 1.08;
  color: var(--on-black);
  margin: 0 0 .35rem;
}
.mlands__kicker {
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--red-text);
  margin: 0 0 .55rem;
}
.mlands__what { font-size: .78rem; color: var(--on-black-faint); margin: 0; }
.mlands__copy { font-size: .93rem; line-height: 1.68; color: var(--on-black-mute); margin: 0 0 .7rem; }
.mlands__go {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-black);
}
.mlands__item:hover .mlands__go { color: var(--red-text); }
@media (max-width: 700px) { .mlands__item { grid-template-columns: 1fr; gap: .5rem; } }


/* ═══════════════════════════════════════════════════════════════════════════
   10 · RESPONSIVE
   The spreads collapse to one column; the run keeps running. Nothing here
   re-introduces a boxed section at any width.
   ═════════════════════════════════════════════════════════════════════════ */

/* The carve turns over before the spreads do. Below ~1000px the pocket the
   curve opens on the right stops being wide enough for a 56ch measure, so the
   composition rotates: the copy takes the full width at the top and the sweep
   becomes a shallower boundary running left-to-right underneath it, with the
   photograph still riding higher on the left. Same gesture, portrait box.
   The wide path is NOT reused at a taller aspect ratio — objectBoundingBox
   would shear it into a near-vertical cliff. It is a second path. */
@media (max-width: 1000px) {
  .mcarve {
    /* Floor 600 so the copy always clears the shallower narrow curve at the
       left edge (measured: 360px wide needs ~290px of panel and the narrow
       curve gives 0.599 of the height, so 600 leaves ~70px of slack). Ceiling
       780 so a 1000px tablet does not get a 1949px opening band. */
    --carve-h: clamp(600px, 95vw, 780px);
    --carve-pos: 50% 62%;
  }
  .mcarve__panel::after { clip-path: url(#carve-sweep-narrow); }
  .mcarve__copy {
    inset-inline: clamp(1.1rem, 5vw, 3rem);
    width: auto;
    top: clamp(.6rem, 2.5vw, 1.6rem);
  }
}

@media (max-width: 860px) {
  .mspread { grid-template-columns: 1fr; }
  .mspread--flip .mspread__text  { order: 1; }
  .mspread--flip .mspread__media { order: 2; }
  .mspread__media { margin-block-start: 0; }
  /* One column: nothing to align against, so the per-instance --align that
     centres a short copy block beside a tall frame must not survive here. */
  .mspread { --align: start; }
  /* A curl on the inner edge only makes sense while the picture is running off
     ONE page edge. Stacked full-bleed, both edges are page edges.
     T-1124: zero the TOKEN, not just the radius — the plate badge now insets
     itself by a fraction of --curl, so leaving the token live here would float
     the badge ~8px off a corner that is square again. */
  .mspread--curl .mspread__media,
  .mfig--curl { --curl: 0px; }
  .mspread--curl .mspread__media img,
  .mfig--curl img { border-radius: 0; }
  /* Half-bleeds become full-bleeds rather than awkward three-quarter widths. */
  .mflow > .u-bleed-left,
  .mflow > .u-bleed-right { grid-column: full; }
  .u-overlap { margin-block-start: clamp(-3rem, -4vw, -1rem); }

  /* T-1121 · STACKED, A PORTRAIT MUST NOT EAT THE SCREEN.
     Side by side, a tall frame is half the width and so half the height. The
     moment the spread stacks, the same frame goes full-bleed and its height
     goes with it: the videographer portrait measured 630px at 768, which put
     its whole spread at 816px — 0.91 of the viewport, against the standing
     rule that nothing on this page comes near a full screen.

     Capped rather than re-fitted, and the cap is generous: the frame already
     renders at 0.82:1 against a 0.75:1 source, so this is trimming empty
     floor at the bottom of the frame, not reaching the subject. Anchored
     above centre because the head, the camera and the gimbal are all in the
     top two thirds and the bottom third is carpet and a cardboard box.
     No crop on this page may cut through a head; this one does not go near
     one. */
  .mspread__media img {
    max-height: clamp(300px, 58vw, 470px);
    object-fit: cover;
    object-position: 50% 34%;
  }
}

@media (max-width: 720px) {
  /* The roll's twelve columns become four, and the slot spans clamp into them.
     A 3-of-12 portrait would be 90px wide on a phone; that is not a photograph,
     it is a thumbnail. */
  .roll { grid-template-columns: repeat(4, 1fr); gap: .6rem; }
  .roll__fig { grid-column: 1 / span 4; margin-block-start: 0; }
  .roll__fig:nth-child(4n+2) { grid-column: 1 / span 2; }
  .roll__fig:nth-child(4n+3) { grid-column: 3 / span 2; margin-block-start: 1.6rem; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   11 · LIGHT THEME
   The run is built on --ink / --on-black / --bone, so the page ground remaps
   for free. Only the literals need re-declaring, plus the two photographic
   surfaces (.mplate, .roll) which stay dark in BOTH themes — the accepted
   pattern across this estate for full-bleed photographic bands.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── THE TWO LIGHT-MODE WASHES, REMOVED 2026-08-12 ─────────────────────────
   Ethan: "remove the bad colour overlaps … refine and clean the glitching and
   fading issues". Both of the things he was looking at were the same mistake
   made twice: a gradient asked to travel the WHOLE tonal distance between the
   cream page and a black surface. Dark mode is the mode they were authored in,
   and in dark mode neither one does anything at all, which is why they shipped.

   Measured at 1440, sampling the rendered page down a column clear of type:

   1. THE HERO SEAM. `.mflow::before` was rgba(10,10,10,.30) → transparent over
      110px, and it did the opposite of its job at BOTH ends. At the top it
      starts at 30% black, not 100%, so the hero's black (luma 9) met it at luma
      180 — a 171-step cliff in one pixel row, i.e. the hard bar it was written
      to prevent, just moved down by nothing. Then it spent 90px crawling 180 →
      246 back to the page colour, which is the grey smear that was reported.
      A seam-softener that draws a cliff and a smear is not worth repairing;
      the honest join is the hero's black meeting the page cleanly, which is
      what every other dark/light boundary on this page now does.

   2. THE FEATHERS. Below.

   If a soft hero seam is ever wanted again, the fix is to start the gradient
   at the hero's OWN colour at full alpha and land it in under ~24px. Do not
   reinstate a long partial-alpha wash. */

/* The feather, in light mode. Not removed — shortened. At clamp(44,7vw,96) the
   ramp from the cream page into the dark roll measured luma 246 → 9 across
   95px of full page width: a grey field, not an edge, and the "botched
   gray-to-cream fade" in the report. Over .mplate it did the same thing to the
   photograph, washing the top ~90px of the picture out to milk. The identical
   declaration in dark mode measures 9 → 67, which is a vignette and is correct,
   so the rule stays and only the depth changes. 14px still antialiases the
   join; it is too short to read as a band. */
[data-theme="light"] .mfeather { --m-feather-h: 14px; }

/* The carve needs NOTHING here, stated so the next person does not go looking.
   Its panel paints --m-ground, which is the page's own colour and remaps with
   the theme for free; its copy sits on that panel and reads the same --on-black
   tokens as the rest of the run; and .mcarve__cap is a literal light rgba
   because it is printed ON the photograph in both themes, exactly like
   .mplate's copy. The one thing to keep true: the panel must never be given a
   dark reset the way .mplate/.mroll are, or the cream page would meet a black
   panel along the curve and the sweep would read as a torn band. */

[data-theme="light"] .m-p          { color: rgba(26,21,16,.74); }
[data-theme="light"] .m-p--lede    { color: rgba(26,21,16,.7); }
[data-theme="light"] .m-p strong,
[data-theme="light"] .m-p em       { color: #1a1510; }
[data-theme="light"] .m-run-in     { color: rgba(26,21,16,.55); }
[data-theme="light"] .m-run-in li + li::before { color: rgba(26,21,16,.35); }
[data-theme="light"] .m-go         { color: #1a1510; }
[data-theme="light"] .mfig figcaption,
[data-theme="light"] .mnote__value { color: rgba(26,21,16,.66); }
/* The ledger's type all reads tokens that remap for free (--on-black,
   --on-black-mute, --on-black-faint, --red). Only the two hairline literals
   need a counterpart here. */
[data-theme="light"] .mledger__group      { border-top-color: rgba(26,21,16,.17); }
[data-theme="light"] .mledger__group:last-child { border-bottom-color: rgba(26,21,16,.17); }
[data-theme="light"] .mlands__item       { border-top-color: rgba(26,21,16,.14); }
[data-theme="light"] .mlands__item:last-child { border-bottom-color: rgba(26,21,16,.14); }
[data-theme="light"] .mlands__what       { color: rgba(26,21,16,.55); }
[data-theme="light"] .mlands__copy       { color: rgba(26,21,16,.72); }
[data-theme="light"] .mbeat__copy        { color: rgba(26,21,16,.7); }
[data-theme="light"] .mflow__folio       { color: rgba(26,21,16,.55); }

/* The two photographic surfaces keep their dark ground in light mode. Resetting
   the tokens inside them handles everything that READS a token …
   */
[data-theme="light"] .mplate,
[data-theme="light"] .mroll {
  --ink: #0a0a0a;
  --on-black: #f5f1e8;
  --on-black-mute: rgba(245,241,232,.58);
  --on-black-faint: rgba(245,241,232,.52);
  --red-text: #e8455a;
}
[data-theme="light"] .mroll { background: #0a0a0a; }

/* … but NOT the rules above that set a literal light-mode colour. Those are
   `[data-theme="light"] .m-p`-shaped: two compound selectors, so they tie on
   specificity with `.mplate .m-p` and win on source order. The result, caught
   in a light-mode screenshot, was the entire plate caption rendering as dark
   ink on a dark night photograph — invisible. Every literal light override
   above therefore needs an explicit counterpart inside these two surfaces.
   If a new light-mode literal is added above, add its counterpart here too. */
[data-theme="light"] .mplate .m-p,
[data-theme="light"] .mroll .m-p          { color: rgba(245,241,232,.72); }
[data-theme="light"] .mplate .m-p strong,
[data-theme="light"] .mplate .m-p em,
[data-theme="light"] .mroll .m-p strong,
[data-theme="light"] .mroll .m-p em       { color: #f5f1e8; }
[data-theme="light"] .mplate .m-turn,
[data-theme="light"] .mroll .m-turn       { color: #f5f1e8; }
[data-theme="light"] .mplate .m-run-in,
[data-theme="light"] .mroll .m-run-in     { color: rgba(245,241,232,.6); }
[data-theme="light"] .mplate .m-run-in li + li::before,
[data-theme="light"] .mroll .m-run-in li + li::before { color: rgba(245,241,232,.3); }
[data-theme="light"] .mplate .m-go,
[data-theme="light"] .mroll .m-go         { color: #f5f1e8; }
[data-theme="light"] .mroll .roll__cap    { color: rgba(245,241,232,.52); }
[data-theme="light"] .mroll .mbeat__title { color: #f5f1e8; }
[data-theme="light"] .mroll .mbeat__copy  { color: rgba(245,241,232,.6); }
/* No light-mode override for .mfeather: it reads --m-ground, which is already
   the cream page colour here. That is the whole reason it does not read --ink. */


/* ═══════════════════════════════════════════════════════════════════════════
   12 · THE PRACTICE REBUILD  ·  T-1087
   ---------------------------------------------------------------------------
   WHAT CHANGED AND WHY. Until this pass /media was a services pitch — three
   "concentrations", a ledger of deliverables and three "put your business in
   the news" cards. Ethan's charter re-aimed it: this page is THE MEDIA
   PRACTICE, TNAADO's OWN work. The sales page is services/media-production.
   So the ledger of client deliverables, the client product stills and the
   equipment copy all leave, and what arrives is the newsroom, the capture, the
   two in-house systems the practice runs on, and the ads cut for our own
   products.

   Nothing here introduces a boxed section, a bordered card or an inset
   container — the run stays the run. Three additions only, each documented
   below: a review-time image numbering badge, a third carve path, and a plate
   treatment for a screenshot (which is an image, so it is treated as one).
   ═════════════════════════════════════════════════════════════════════════ */



/* ── 12B · A THIRD CARVE PATH: THE LOW SWEEP ───────────────────────────────
   The two existing paths were drawn for a photograph whose subject sits low
   and left — #carve-sweep's panel runs down to 63% of the band on the right,
   so anything above that line on the right half of the frame is covered.

   That is correct for the frames it was built for and wrong for a wide crew
   shot, where people stand at BOTH edges. Measured against the opening
   photograph (a lit set: reporter seated at x≈20%, crew standing at x 78–95%
   with the standing figure's head at 30% of the frame height), the 63% panel
   covers both crew members entirely. That is the "you've cut off the better
   part of visual" failure, in advance.

   So the panel is parameterised rather than replaced. An instance that needs a
   shallower boundary sets --carve-clip and gets one; every existing instance
   keeps #carve-sweep because that is the fallback. The low path takes the
   panel down to 30% on the right and 4% at the left edge — above the standing
   crew's heads at 35% of the band, with the softbox top at 10% still inside
   the picture.

   The pocket the low sweep opens is SHORTER than the deep one, so an instance
   using it must also buy back height with --carve-h or the copy will not fit.
   That pairing is the whole trick; do not set one without the other. */
.mcarve__panel::after { clip-path: var(--carve-clip, url(#carve-sweep)); }
@media (max-width: 1000px) {
  .mcarve__panel::after { clip-path: var(--carve-clip-narrow, url(#carve-sweep-narrow)); }
}


/* ── 12C · A SCREENSHOT IS A PICTURE, SO IT IS PLATED LIKE ONE ─────────────
   The two in-house systems are the page's argument, and the honest way to show
   software is its actual interface. The temptation is a bordered "app window"
   card with a fake title bar — which is both a boxed container (banned on this
   page) and a lie about the chrome. So a screenshot uses .mfig like every
   photograph: full width in its column, no frame, caption underneath.

   The one difference from a photograph is the ground. A UI screenshot is a
   flat, mostly-dark rectangle with hard edges; butted straight onto the page's
   own #0a0a0a it loses its boundary and reads as a hole. A single hairline at
   1px, the same rgba the ledger uses, restores the edge without becoming a
   frame. */
.mshot__frame,
.mshot .mshot__pending {
  outline: 1px solid rgba(245,241,232,.13);
  outline-offset: -1px;
}

/* ── THE CROP: AN EXPLICIT SOURCE RECTANGLE, NOT A COVER-FIT ───────────────
   T-1121, 2026-08-14. The previous system set a band height and let
   `object-fit: cover` choose the scale. MEASURED on the rendered page, that
   produced: 0.26x at 390, 0.48x at 768 and 0.46x for the sign-off capture
   even at 1440. The source captures are 1600x1000 with roughly 14px UI text,
   so 0.26x is 3.6px type — a grey smear in the shape of an application.
   A viewer could not tell what the software does, which is the only reason
   the picture is on the page at all.

   The reason cover fails is structural, not a bad parameter: cover derives
   the scale from the BOX, so the narrower the viewport the smaller the type.
   That is exactly backwards. A screenshot has one legibility requirement —
   painted source pixels per CSS pixel — and it has to be the input, not the
   output.

   So the frame now declares a RECTANGLE OF THE SOURCE FILE to show:
     --crop-x/--crop-y   top-left of the region, in source pixels
     --crop-w/--crop-h   its size, in source pixels
   The image is scaled so that rectangle exactly fills the frame, which makes
   the render scale precisely frameWidth / --crop-w. Narrow viewports get a
   SMALLER rectangle rather than a smaller image, so the type stays the same
   physical size and the crop tightens onto the part that carries the point.
   Every breakpoint below is chosen to land between 0.83x and 1.3x.

   Margin percentages are the mechanism because margin-% always resolves
   against the containing block's WIDTH — top included. `top`/`translate`
   percentages resolve against height and own-size respectively, neither of
   which tracks the scale we just set.

   The crop keeps the app's own navigation wherever it fits: the sidebar is
   what makes a rectangle read as a real application rather than a chart.
   Nothing that would be a claim is cropped away — the captions describe the
   mechanism and never assert a particular row exists. */
.mshot__frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--crop-w) / var(--crop-h);
  background: #0d0d0d;
}
.mshot__frame img {
  display: block;
  width: calc(100% * var(--nat-w, 1600) / var(--crop-w));
  height: auto;
  max-width: none;
  margin-left: calc(-100% * var(--crop-x) / var(--crop-w));
  margin-top: calc(-100% * var(--crop-y) / var(--crop-w));
}

/* ── THE THREE RECTANGLES ──────────────────────────────────────────────────
   Read off the actual 1600x1000 files, not guessed. Phone first, because the
   phone is where the old system failed hardest and where the rectangle has
   to be smallest. Comments give the resulting scale at the breakpoint's
   design width.

   --board   The Shoot, Shoots board. The argument is "every production on
             the board with the crew and the call attached", and one shoot
             card contains that whole sentence: title, client, SCHEDULED
             chip, date, location, crew count, task progress. So the phone
             gets exactly one card, and wider viewports add cards rather
             than shrinking them.
   --queue   The editorial review queue. The argument is the STATE column.
             Phone takes the headline column (the rows have to be readable
             as real filed pieces); 768 up adds author and the status chips.
   --signoff The compliance checklist and the disabled Approve beside it.
             Phone takes the checklist rows themselves — three ticked, two
             not — which is the picture of the sentence. 1100 up fits both
             cards, so the disabled Approve lands in frame next to the
             checklist that disables it. */
.mshot--board   { --crop-x: 344; --crop-y: 272; --crop-w: 376; --crop-h: 222; } /* 390 → 1.04x */
.mshot--queue   { --crop-x: 305; --crop-y: 118; --crop-w: 470; --crop-h: 312; } /* 390 → 0.83x */
.mshot--signoff { --crop-x: 328; --crop-y: 228; --crop-w: 400; --crop-h: 290; } /* 390 → 0.98x */

@media (min-width: 721px) {
  .mshot--board   { --crop-x: 336; --crop-y: 262; --crop-w: 790; --crop-h: 250; } /* 768 → 0.97x */
  .mshot--queue   { --crop-x: 300; --crop-y: 118; --crop-w: 900; --crop-h: 370; } /* 768 → 0.85x */
  .mshot--signoff { --crop-x: 318; --crop-y: 190; --crop-w: 620; --crop-h: 360; } /* 768 → 1.24x */
}
@media (min-width: 1100px) {
  .mshot--board   { --crop-x: 330; --crop-y:  72; --crop-w: 1190; --crop-h: 425; } /* 1440 → 1.21x */
  .mshot--queue   { --crop-x: 300; --crop-y: 110; --crop-w: 1245; --crop-h: 380; } /* 1440 → 1.16x */
  .mshot--signoff { --crop-x: 312; --crop-y: 180; --crop-w: 1225; --crop-h: 385; } /* 1440 → 1.18x */
}
/* Above the source width the rectangle has to grow or the scale runs away:
   a 1245px rectangle in a 1920 frame would be 1.54x, which is no longer a
   screenshot, it is a zoom. These cap it near 1.25x and take in more of the
   application instead. */
@media (min-width: 1600px) {
  .mshot--board   { --crop-x: 330; --crop-y:  72; --crop-w: 1265; --crop-h: 450; }
  .mshot--queue   { --crop-x: 296; --crop-y: 106; --crop-w: 1290; --crop-h: 400; }
  .mshot--signoff { --crop-x: 308; --crop-y: 176; --crop-w: 1280; --crop-h: 400; }
}

/* THE PENDING SLOT. The screenshots are being captured under a separate task
   (T-1085) and were not ready when this page was built. The alternative to an
   honest empty slot is a stock "dashboard" mockup, which would be a fabricated
   claim about software that really exists — the worst available option.

   So the slot states plainly what belongs there. It is sized by aspect-ratio
   so dropping the real file in later changes no layout, and it carries NO
   image number: it is not a picture yet, and numbering it would shift every
   badge after it the moment the real capture lands. */
.mshot__pending {
  aspect-ratio: var(--shot-ratio, 16 / 10);
  display: grid;
  align-content: center;
  justify-items: start;
  gap: .5rem;
  padding: clamp(1.2rem, 4vw, 2.4rem);
  background:
    repeating-linear-gradient(
      -45deg,
      rgba(245,241,232,.022) 0 10px,
      transparent 10px 20px
    ),
    #0d0d0d;
}
.mshot__pending-label {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--red-text);
  margin: 0;
}
.mshot__pending-what {
  font-size: .88rem;
  line-height: 1.6;
  color: var(--on-black-mute);
  margin: 0;
  max-width: 52ch;
}


/* ── 12D · THE SYSTEMS LIST ────────────────────────────────────────────────
   The two systems reuse .mledger wholesale — hairline-separated groups, a red
   roman numeral in the margin, items in an auto-fit two-up. It is already the
   page's list primitive and it is already unboxed, so there is no reason to
   invent a second one. The only addition is a mono line for the address the
   system runs at, which a deliverable did not need. */
.mledger__where {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--on-black-faint);
  margin: -.15rem 0 .1rem;
}


/* ── 12E · THE PHOTOGRAPHIC HERO ───────────────────────────────────────────
   Ethan sent this frame himself — "perfect of ethan and grace" — and his own
   filename for it reads "perfect for banner section or showcase of media and
   software working together". It is the page's thesis as a photograph: a
   broadcast microphone and a laptop in one frame, two people doing real work
   rather than posing. So it opens the page.

   WHY THE COPY IS NOT ON THE PICTURE. The obvious hero is type over the
   photograph. It fails here and the reason is measurable: the left third of
   the frame — where a hero headline wants to sit — is a floor-to-ceiling
   window at near-blown highlights, and the reporter's face sits at x 30–42%.
   Making light type legible over that needs a scrim heavy enough to put her in
   shadow, which is the one thing the photograph must not do. So the copy sits
   on the page's own ink above, and the photograph runs clean and full-bleed
   underneath it. Nothing is laid over the picture at all.

   THE CROP IS AN ASPECT-RATIO, NOT A HEIGHT. A clamped pixel height crops
   harder as the viewport widens — measured on this frame, a 760px ceiling at
   2560 wide cuts the microphone off the bottom entirely, which is the one
   element that must survive. An aspect-ratio crops by the same fraction at
   every width, so the composition is fixed once and holds everywhere.

   THE NUMBERS. Source is 3:2. At 2.1:1 the visible band is 1.5/2.1 = 71.4% of
   the frame height; object-position 45% places that window over 13%–84% of the
   source, which contains both heads (20–44%) and the microphone (64–74%) with
   margin at both ends. Below 760px the ratio returns to the native 3:2 and the
   frame is shown uncropped, because a 2.1:1 band on a phone is 170px of letter
   box and the picture stops being readable. */
/* DOUBLED CLASS, DELIBERATELY. media-hero-movement.css is linked AFTER this
   file, so a single .mv-hero--photo ties .mv-hero on specificity and LOSES on
   source order — measured, not guessed: the hero rendered as a flex row and
   the banner came out 968px wide inside a 1440px viewport instead of running
   edge to edge. Doubling the class outranks it without an !important and
   without reordering the two stylesheets, which other pages depend on. */
/* T-1086, 2026-08-13 — THE COPY MOVED ONTO THE PICTURE.
   The stacked version above is the reasoning this block replaces, not a
   description of it. Ethan, same evening: "should be overlapped with the hero
   photo, not a blacked out hero. Use fading layer nicely without cutting our
   heads off." Stacked copy over ink IS the blacked-out hero he means — the top
   two thirds of the viewport were solid #0a0a0a with type on it.

   The objection the stacked version was built on is real and is ANSWERED, not
   discarded: a scrim heavy enough for a 4rem headline over the blown window at
   the left would put the reporter in shadow. So the photograph does not sit
   under the type. It is anchored RIGHT at 74% of the viewport with its left
   edge feathered to nothing, which slides both heads from x 33%/68% of the
   frame to roughly x 50%/76% of the viewport. The headline column ends around
   x 45%. The scrim under the type is therefore falling on ink and on empty
   glass — not on anyone's face — and the picture on the right runs at full
   strength with no scrim over it at all.

   NOTHING IS CROPPED OUT. Source is 3:2. The stage is the full hero box, so at
   1440x900 the visible window is ~85% of the source height anchored at the top:
   both heads (y 19-42%) and the microphone (y 64-74%) are inside it with
   margin at both ends. The old note's arithmetic still holds, it just applies
   to the whole hero now instead of a 2.1:1 band. */
.mv-hero.mv-hero--photo { padding-block-end: clamp(4rem, 8vw, 6.5rem); }
/* The copy block is 520px by default, which is too narrow beside a picture
   this wide. */
.mv-hero.mv-hero--photo .mv-copy { max-width: 40ch; }

.mv-hero__frame {
  position: absolute;
  inset: 0;
  margin: 0;
  z-index: 1;
  pointer-events: none;
}
.mv-hero__frame img {
  display: block;
  width: 100%;
  /* height:100% and object-fit do the cropping now, but the width/height
     ATTRIBUTES stay on the element (2400x1600, the real file) so the browser
     still knows the intrinsic ratio before the bytes land. Without an explicit
     CSS height the used height would be the 1600px attribute — measured on the
     previous build at 360px wide, that rendered a 4.4:1 portrait tower. */
  height: 100%;
  object-fit: cover;
  object-position: 50% 12%;
  opacity: .92;
  background: #111;
}
@media (min-width: 900px) {
  .mv-hero__frame {
    left: auto; right: 0;
    width: 74%;
    -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,.4) 13%, #000 34%);
            mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,.4) 13%, #000 34%);
  }
}
@media (max-width: 899px) {
  /* No "beside the type" at this width — the copy fills the column. The frame
     goes full bleed, held high so neither head is lost, and the scrim below
     comes up from the bottom where the copy is instead of across the faces. */
  .mv-hero__frame img { object-position: 50% 8%; opacity: .66; }
}

/* THE SCRIM. Directional, spent where the copy is. .mv-hero__ground already
   paints the red radial and the bottom fade; this is the legibility layer and
   it sits between the photograph (z 1) and the copy (z 3). */
.mv-hero--photo .mv-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(100deg,
      rgba(10,10,10,.94) 0%, rgba(10,10,10,.86) 26%,
      rgba(10,10,10,.52) 46%, rgba(10,10,10,.18) 64%, transparent 78%),
    linear-gradient(to bottom, rgba(10,10,10,.30) 0%, transparent 22%,
      transparent 64%, rgba(10,10,10,.72) 92%, #0a0a0a 100%);
}
@media (max-width: 899px) {
  /* Two layers, because one is not enough here. The source is 3:2 and the
     narrow hero box is WIDER than that, so the crop is vertical only — a
     horizontal object-position cannot move the two subjects out from under the
     copy at this width, the way the right-anchored frame does above 900. So
     the scrim does it instead: a soft diagonal weighted to the left, where
     every line of type begins, over a bottom-anchored fade. Measured on the
     deployed preview at 390: the red eyebrow was landing on the bright glass
     behind the reporter and reading as the weakest element on the page. */
  /* T-1121: the near stops go .80 -> .87 and .56 -> .63. Measured from the
     PAINTED PIXELS of the rendered hero at 390 — compositing the eyebrow over
     the ground it actually sits on, not reading its declared colour — the
     eyebrow came out at 4.44:1 against the 4.5 floor for type this size. It
     is a near miss rather than the invisible-eyebrow failure this block was
     first written for, and it is bought back on the GROUND rather than by
     brightening --red-text, so the token set is untouched and the h1 and lede
     over the same corner gain the same margin. */
  .mv-hero--photo .mv-hero__scrim {
    background:
      linear-gradient(112deg, rgba(10,10,10,.87) 0%, rgba(10,10,10,.63) 34%,
        rgba(10,10,10,.24) 62%, transparent 84%),
      linear-gradient(to bottom, rgba(10,10,10,.46) 0%, rgba(10,10,10,.24) 22%,
        rgba(10,10,10,.66) 58%, rgba(10,10,10,.92) 86%, #0a0a0a 100%);
  }
}

/* The run begins straight off the bottom of the hero. The hero's scrim
   darkens to #0a0a0a at its own base and .mflow is the same colour, so the
   join is a colour match rather than a seam — no gap, no second scrim. */
.mv-hero--photo + .mflow__folio + svg + .mflow,
.mv-hero--photo ~ .mflow { padding-block-start: clamp(2.6rem, 5vw, 4.5rem); }


/* ═══════════════════════════════════════════════════════════════════════════
   13 · WHITE GROUNDS, BLACK SEPARATION, CONTAINED PICTURES, SHAPES  ·  T-1149
   ---------------------------------------------------------------------------
   Ethan, four times: "I NEED WHITE BACKGROUNDS", "sections with text use white
   backgrounds ... too hard to read so much of it", "with black shading and
   separations", "do more work like the bubble stuff but different patterns
   crisscross the site, more sharp", "sections are too long".

   WHY THIS IS A BAND SYSTEM AND NOT A BODY FLIP. A `body { background: bone }`
   was tried on this estate and it broke every full-bleed backdrop, because the
   backdrops are painted by the elements themselves. And a generic
   `main > section` selector matches NOTHING on this page — /media has no
   sections in its body at all, it is one <article class="mflow"> grid. So the
   ground is set PER BAND, on .mflow itself, and the single 7376px run is cut
   into alternating .mflow--light / .mflow--dark siblings that all keep the
   identical named-line grid. Every `.mflow > *` rule above still applies; the
   only thing that changes between two neighbouring bands is what colour they
   are and how tall they are.

   WHAT IS LIGHT AND WHAT IS BLACK.
     light  — anything whose job is reading: ledes, turns, body copy, the
              ledger, the marginal notes, the pipeline.
     black  — photographic and separating: the carve, the photo bands, the
              three UI captures (which are dark rectangles and would read as
              holes on cream), the seams between light bands, and the close.

   The theme fork is gone sitewide (see site.css T-1040), so these are literal
   colours rather than a second theme, and they are stable whatever --ink is.
   ═════════════════════════════════════════════════════════════════════════ */


/* ── 13A · THE BANDS ───────────────────────────────────────────────────────
   Doubled class throughout. `.mv-hero--photo ~ .mflow` is (0,2,0) and sets the
   run's opening padding for every sibling; a single `.mflow--light` is (0,1,0)
   and would lose to it. Measured, not guessed — the first band kept 4.5rem of
   black padding above its cream ground until the class was doubled. */
.mflow.mflow--light {
  --m-ground: var(--bone);
  background: var(--bone);
  /* Reading bands remap the on-dark token set once, here, instead of being
     re-declared class by class. Everything that reads a token — the ledger,
     the notes, the captions, the run-in — comes right for free. */
  --on-black: #0a0a0a;
  --on-black-mute: rgba(20,16,12,.78);
  --on-black-faint: rgba(20,16,12,.60);
  /* --red-text is #e8455a, drawn for black. On bone it measures 3.4:1 and the
     mono type it is used on is .58rem. #a50d26 (the site's own hover red) is
     7.4:1 on #faf8f3 and needs no new token. */
  --red-text: #a50d26;
  padding-block: clamp(2.4rem, 4.5vw, 3.8rem) clamp(2.6rem, 5vw, 4.2rem);
}
.mflow.mflow--dark {
  --m-ground: var(--ink);
  background: var(--ink);
  padding-block: clamp(1.6rem, 3.2vw, 2.8rem);
}
/* The carve is a photograph that runs to all four edges of its own band by
   design — it IS the separation. No padding, or a black gutter appears above
   the picture and the sweep stops meeting the band above it. */
.mflow.mflow--carve { padding-block: 0; }

/* Two literal on-dark colours that no token reaches. */
.mflow--light .m-p--lede { color: rgba(20,16,12,.74); }
.mflow--light .m-run-in li + li::before { color: rgba(20,16,12,.4); }
.mflow--light .mledger__group { border-top-color: rgba(20,16,12,.18); }
.mflow--light .mledger__group:last-child { border-bottom-color: rgba(20,16,12,.18); }
/* .m-go's rule is `.m-go` (0,1,0) reading a literal --on-black; it inherits the
   remap. Its underline is a red literal and reads fine on bone. */


/* ── 13B · THE SEAM  ·  black shading between two light bands ───────────────
   "with black shading and separations" and "different patterns crisscross the
   site, more sharp". A seam is a short full-bleed black band carrying a hard
   diagonal pattern — no blur, no gradient wash, no photograph. It is the thing
   that punctuates two reading bands, and it is CHEAP: two repeating-linear-
   gradients, painted once, nothing per frame.

   Three variants so the page does not repeat one motif: --cross is two
   opposing rakes crossing at 90°, --rake is a single direction, --tick is a
   coarse vertical comb. They alternate down the page. */
.mseam {
  position: relative;
  height: clamp(48px, 6vw, 82px);
  background: var(--ink);
  overflow: hidden;
}
.mseam::before {
  content: '';
  position: absolute;
  inset: 0;
}
.mseam--cross::before {
  background:
    repeating-linear-gradient(45deg,  rgba(245,241,232,.13) 0 1px, transparent 1px 14px),
    repeating-linear-gradient(-45deg, rgba(245,241,232,.09) 0 1px, transparent 1px 22px);
}
.mseam--rake::before {
  background:
    repeating-linear-gradient(-58deg, rgba(245,241,232,.14) 0 1px, transparent 1px 11px),
    repeating-linear-gradient(-58deg, rgba(200,16,46,.55)   0 1px, transparent 1px 88px);
}
.mseam--tick::before {
  background:
    repeating-linear-gradient(90deg, rgba(245,241,232,.16) 0 1px, transparent 1px 26px),
    repeating-linear-gradient(90deg, rgba(200,16,46,.6)    0 2px, transparent 2px 208px);
}
/* One hard red rule on the light side of the seam, so the join is a drawn edge
   rather than a colour change. */
.mseam::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 2px;
  background: var(--red);
}


/* ── 13C · SHAPES  ·  soft against sharp, alternating ──────────────────────
   The three soft radii are Ethan's own, lifted unchanged from
   unwrinkled-react/app/globals.css (.arch, .blob-a, .blob-b). He liked the
   bubble treatment and asked for MORE and SHARPER, so each soft shape has a
   sharp counterpart cut with clip-path — a chamfer, a slant, a wedge and a
   crossing notch — and the page alternates soft, sharp, soft, sharp so no two
   consecutive pictures share a silhouette.

   Applied to the <img>, never to a background: a clip on a section ground was
   tried on this estate and it feathered backdrops. And never to a UI capture —
   a clipped screenshot reads as a broken screenshot.

   The border T-1147 puts on contained pictures is dropped on the four sharp
   shapes, because a 1px border under a clip-path is cut away on the diagonals
   and survives on the straights, which draws three sides of a rectangle. */
.sh-arch   > img { border-radius: 50% 50% 12% 12% / 34% 34% 8% 8%; }
.sh-blob-a > img { border-radius: 63% 37% 55% 45% / 56% 50% 50% 44%; }
.sh-blob-b > img { border-radius: 40% 60% 56% 44% / 55% 42% 58% 45%; }

.sh-chamfer > img {
  clip-path: polygon(0 0, calc(100% - 42px) 0, 100% 42px, 100% 100%, 42px 100%, 0 calc(100% - 42px));
  border: 0;
}
.sh-slant > img {
  clip-path: polygon(0 5%, 100% 0, 100% 95%, 0 100%);
  border: 0;
}
.sh-wedge > img {
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 64px), calc(100% - 108px) 100%, 0 100%);
  border: 0;
}
.sh-notch > img {
  clip-path: polygon(0 0, 58% 0, 66% 9%, 100% 9%, 100% 100%, 42% 100%, 34% 91%, 0 91%);
  border: 0;
}
/* A soft shape keeps a border but it has to be the right colour for the ground
   it is on; T-1147's is drawn for black. */
.mflow--light .sh-arch > img,
.mflow--light .sh-blob-a > img,
.mflow--light .sh-blob-b > img { border-color: rgba(20,16,12,.16); }
@media (max-width: 700px) {
  /* The four sharp cuts are sized in px because a chamfer is an absolute
     bevel, not a proportion — at 390 a 42px cut is a quarter of the short
     side and stops reading as a cut corner. Halved, not removed. */
  .sh-chamfer > img { clip-path: polygon(0 0, calc(100% - 22px) 0, 100% 22px, 100% 100%, 22px 100%, 0 calc(100% - 22px)); }
  .sh-wedge   > img { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 34px), calc(100% - 58px) 100%, 0 100%); }
}


/* ── 13D · PICTURES ARE CONTAINED, AND THEY SIT ON A BLACK PLINTH ──────────
   "no images going left to right border". A photograph inside a reading band
   is never allowed to touch the viewport edge. Two moves:

   1. The half-bleeds stop bleeding inside a light band. `.u-bleed-left` and
      `.u-bleed-right` run from the page edge to the middle of the text, which
      is a full-bleed on one side by definition. In a light band they sit at
      FEATURE width instead, both edges inside the page.
      This also fixes two long-standing defects on this page for free: the
      ~110px baseline mismatch between the desk spread's picture and its
      heading (the two columns now start on one grid line with no bleed
      offset), and the clipped caption under it (a caption that no longer
      starts at x=0 needs no rescue indent).

   2. A full-width figure inside a light band becomes a BLACK PLINTH: the band
      spans the page, the picture inside it does not. That is rule 2 and rule 3
      answered by the same element — the black is the separation, and the
      photograph is a contained object sitting on it with air all round.
      T-1147 already caps the picture at 820px and centres it; the plinth is
      what gives that containment somewhere to happen. */
.mflow--light > .u-bleed-left,
.mflow--light > .u-bleed-right { grid-column: feature; }

.mflow--light > .u-plinth,
.mflow--dark  > .u-plinth {
  background: var(--ink);
  padding: clamp(1.1rem, 2.6vw, 2rem) clamp(1.1rem, 3vw, 2.4rem) clamp(.9rem, 2vw, 1.5rem);
  /* The plinth is the black object, so its own tokens go back to on-dark or
     the caption printed on it would be ink on ink. */
  --on-black: #f5f1e8;
  --on-black-mute: rgba(245,241,232,.72);
  --on-black-faint: rgba(245,241,232,.58);
  --red-text: #e8455a;
}
/* A caption on a plinth is inside the plinth's own padding already — the
   page-edge rescue indent from §4 would push it a second time. */
.mflow > .u-plinth > figcaption { padding-inline-start: 0; }
@media (min-width: 1180px) {
  .mflow > .u-plinth > figcaption { padding-inline-start: 0; }
}
/* Centre the caption under the picture it belongs to, since the picture is now
   a centred 820px object rather than a full-bleed band. */
.u-plinth > figcaption { margin-inline: auto; text-align: center; max-width: 56ch; }


/* ── 13E · THE UI CAPTURES  ·  contained, and capped at the FRAME ──────────
   site.css T-1135 caps `img[src*="/gallery/systems/"]` at
   clamp(280px,34vw,440px) with object-fit: cover. That cap is correct and is
   not raised here — but it was landing on the wrong element. .mshot's crop
   system (§12C) works by rendering the image OVERSIZED and pulling it with
   negative margins inside an overflow-hidden frame, so a max-height on the
   <img> resizes the thing the margins were calculated against. Measured at
   390 the board capture rendered 1660x280 inside a 230px frame offset -282px:
   the visible window fell off the bottom of the image entirely.

   So the cap moves to the FRAME, at the identical value. Same ceiling, same
   intent — a capture can never be taller than 440px — and the crop arithmetic
   inside it is left intact. The frame is also bounded in width and centred,
   because a capture running to both page edges is the banned pattern. */
.mshot__frame {
  max-width: min(100%, 1000px);
  max-height: clamp(280px, 34vw, 440px);
  margin-inline: auto;
}
.mshot__frame img {
  max-height: none;
  object-fit: fill;
  object-position: 50% 50%;
}
.mshot > figcaption { margin-inline: auto; text-align: center; max-width: 56ch; }
.mflow > .mshot > figcaption { padding-inline-start: 0; }
@media (min-width: 1180px) {
  .mflow > .mshot > figcaption { padding-inline-start: 0; }
}


/* ── 13F · THE PIPELINE  ·  a drawn element instead of a fifth photograph ──
   The five capture steps were a comma-run of mono words. "more illustration
   and design please", and the alternative to a designed element here is
   another production still, which the page already has enough of. So the run
   is drawn: five sharp chevrons cut with clip-path, stepping across the band,
   in the same red-mark language the ledger and the notes already use.

   It states nothing that the run-in line did not state. No counters, no
   metrics, no claim — the same five words, given a shape. */
.mpipe {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.mpipe li {
  position: relative;
  background: var(--ink);
  color: #f5f1e8;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  line-height: 1.2;
  padding: .95rem 1.1rem .95rem 1.5rem;
  clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%, 14px 50%);
}
.mpipe li:first-child { clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%); }
.mpipe li:last-child  { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 14px 50%); }
.mpipe li::before {
  content: counter(mpipe, decimal-leading-zero);
  counter-increment: mpipe;
  display: block;
  color: var(--red-text);
  font-size: .56rem;
  letter-spacing: .18em;
  margin-block-end: .3rem;
}
.mpipe { counter-reset: mpipe; }
@media (max-width: 620px) {
  .mpipe { grid-template-columns: 1fr 1fr; }
}


/* ── 13G · THE CARVE, KEPT AND MADE PROMINENT ──────────────────────────────
   The hand-drawn sweep is the one piece of this page that came off Ethan's own
   whiteboard, and none of the above touches it: it lives in its own
   .mflow--carve band, on ink, at full bleed, with its panel painting
   --m-ground (which is --ink in that band, so the sweep still reads as the
   page opening onto the photograph). It is now the FIRST thing under the hero
   with nothing competing beside it, and the reading band that follows is
   cream, so the carve's black is the frame around the picture rather than the
   colour of the whole page. */
.mflow--carve .m-standfirst { color: #f5f1e8; }
   13 · T-1120 · THE RUN AS A COMPOSITION
   ---------------------------------------------------------------------------
   Everything above was verified in isolation. This block fixes what only shows
   up when the page is looked at as one object, measured off REAL PAINTED
   PIXELS at 390 / 768 / 1440 rather than off a DOM style walk.

   THE FOUR MEASURED DEFECTS
   1. A 1600px UI capture rendered at 390px (0.24x) and at 768px (0.48x) is not
      a screenshot, it is a texture. Confirmed by screenshot: at 390 none of the
      three system captures has a single readable label. object-fit:cover cannot
      fix this — cover can only ever crop the LONG axis, so on a wide shallow
      slot it is forced to show all 1600 source px and shrink them to fit.
   2. The compliance capture rendered 324x461 at 1440 (0.20x, downscale 4.94) —
      the worst image on the site — because it sat in the 1.15fr half of a
      two-up inside the 605px reading column.
   3. Full-bleed captions started at the page gutter (x=86) while every other
      line of type on the page starts at the content rail (x=418). A 96-char
      caption then broke into five 46ch lines in the far corner of a 1440px
      picture: type that belongs to the photograph, parked 330px from where the
      eye is reading. That is the "text isn't syncing" complaint, literally.
   4. .mcarve__cap measured 3.08:1 against the real pixels behind it
      (rgb(98,115,132) — the lit wall of the set). Needs 4.5:1 at 9.6px.
      A text-shadow is not a scrim; it fails on a mid-tone ground.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── 13A · CHOOSE THE RENDER SCALE, NOT THE FIT ────────────────────────────
   object-fit:none draws the source at exactly 1:1 and clips it to the box, and
   a LENGTH object-position is then an absolute offset into the source in its
   own pixels — so `-330px -85px` means "this window opens on source (330,85)"
   at every viewport width, which a percentage cannot promise because a
   percentage re-solves against the container.

   1:1 is the point. A UI capture at 1.0x is exactly as readable as the app it
   came from. The window is smaller than the screen, so the crop has to be
   chosen — every --shot-xy below was read off the source file, not guessed,
   and each one opens on the region the caption underneath it describes.

   Above 1150px the existing cover crop already resolves to 0.9x and up, which
   measured legible, so it is left alone. */
@media (max-width: 1150px) {
  .mshot.u-shot img {
    object-fit: none;
    object-position: var(--shot-xy, -300px -120px);
  }
  /* A 1:1 window wants depth more than it wants width — at 390 the extra
     120px is two more table rows, which is the difference between "a list"
     and "a list of what". Still nowhere near viewport height (380 of 844). */
  .mshot.u-shot img { height: var(--shot-h-narrow, 380px); }
}

/* The px-anchored crop, at EVERY width. Used by the capture whose argument is
   one card in the corner of the frame rather than the whole screen: no cover
   crop can isolate a 620px-wide region out of a 1600px source, because cover
   never shows less than the full width in a landscape slot. */
.mshot--px img {
  object-fit: none;
  object-position: var(--shot-xy, -300px -175px);
  height: var(--shot-h, 400px);
}

/* ── 13B · CAPTIONS LAND ON THE READING RAIL ───────────────────────────────
   38.75rem is the measured content column (605px at 1440 with the display face
   loaded, checked at 768/1152/1440), expressed in rem so it does not re-solve
   in the caption's own mono font the way a ch value would. The result: picture
   caption, the paragraph above it and the paragraph below it all begin on the
   same vertical line, and the caption runs one or two lines instead of five. */
.mflow > .u-full > figcaption,
.mflow > .u-bleed-left > figcaption {
  padding-inline-start: calc((100% - min(38.75rem, 100% - var(--mgap) * 2)) / 2);
  max-width: min(38.75rem, 100% - var(--mgap) * 2);
  box-sizing: content-box;
}
@media (min-width: 1180px) {
  .mflow > .u-full > figcaption,
  .mflow > .u-bleed-left > figcaption {
    padding-inline-start: calc((100% - min(38.75rem, 100% - var(--mgap) * 2)) / 2);
  }
}
/* A caption on a picture that half-bleeds INSIDE a spread is not a child of
   .mflow, so it missed the indent rule above entirely and printed hard against
   x=0 — measured at 390, 768 and 1440. Type never touches the page edge.

   Both half-bleeds, not just the left one: .u-bleed-right runs off the RIGHT
   edge above 860px, but below that every spread stacks to one column and the
   picture bleeds LEFT instead — so its caption printed at x=0 on a phone,
   which is where it was measured. */
.mspread.u-bleed-left  > .mspread__media > .mfig > figcaption,
.mspread.u-bleed-right > .mspread__media > .mfig > figcaption {
  padding-inline-start: clamp(1.1rem, 5vw, 3rem);
  box-sizing: content-box;
}
@media (min-width: 1180px) {
  .mspread.u-bleed-left > .mspread__media > .mfig > figcaption {
    padding-inline-start: clamp(3.6rem, 6vw, 5.5rem);
  }
}

/* ── 13C · A SPREAD MAY RUN THE FULL PAGE ──────────────────────────────────
   Needed so a capture that has to be wide can be wide without leaving the
   two-up. Gutters are the page's own, and above 1180 the leading gutter also
   has to clear the fixed folio, exactly like every other full-bleed child. */
.mflow > .mspread.u-full { padding-inline: clamp(1.1rem, 5vw, 3rem); }
@media (min-width: 1180px) {
  .mflow > .mspread.u-full { padding-inline-start: clamp(3.6rem, 6vw, 5.5rem); }
}

/* ── 13D · A SCRIM UNDER THE CARVE CAPTION ─────────────────────────────────
   Measured 3.08:1 on the real pixels. The caption sits ON the photograph at
   the bottom-left, and that corner of this frame is a lit wall, not shadow.
   A plate rather than a heavier text-shadow: a shadow spreads the SAME light
   type over a mid-tone and still loses. The gradient is the page's own ink at
   partial alpha, so no new colour enters the palette, and it is pinned to the
   bottom band where the caption is rather than washing the whole picture. */
.mcarve__cap::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: calc(-1 * clamp(.7rem, 1.8vw, 1.3rem));
  bottom: calc(-1 * clamp(.7rem, 1.8vw, 1.3rem));
  z-index: -1;
  background: linear-gradient(to top, rgba(10,10,10,.92), rgba(10,10,10,.78) 55%, transparent);
  pointer-events: none;
}
.mcarve__cap { isolation: isolate; }

/* A full-page spread carrying a system capture stacks EARLIER than the others.
   Measured at 900px: the copy column fell to 307px (~32 characters) and the
   capture's window closed to 476px, which cuts the checklist card mid-row. Two
   bad columns are worse than one good one. */
@media (max-width: 1000px) {
  .mflow > .mspread.u-full { grid-template-columns: 1fr; }
  .mflow > .mspread.u-full.mspread--flip .mspread__text  { order: 1; }
  .mflow > .mspread.u-full.mspread--flip .mspread__media { order: 2; }
  .mflow > .mspread.u-full { --align: start; }
  /* Stacked, the window is the full column, so it can afford the depth that
     keeps all five checks and the disabled Approve inside the frame. */
  .mshot--px img { height: var(--shot-h-narrow, 400px); }


/* ── 13H · THE THINGS THE FIRST SCREENSHOT SHOWED ─────────────────────────
   Looked at, at 1440 and 390, not reasoned about. Four corrections.

   1. THE FOLIO WENT INVISIBLE. .mflow__folio is position:fixed and therefore a
      SIBLING of every band, not a child of one — so it cannot inherit a band's
      token remap, and it was still painting --on-black-faint (light cream) over
      a cream band. It carries its own dark chip now, which is legible over both
      grounds and costs one background.

   2. A SHORT COPY BLOCK BESIDE A TALL FRAME LEFT A HOLE. On black that read as
      space; on bone it reads as a mistake — measured ~340px of empty cream
      under three lines of copy beside the videographer portrait. The frame is
      shortened rather than the copy padded, which also takes height out of the
      band (he asked for shorter sections twice).

   3. A CENTRED PICTURE WITH A LEFT-ALIGNED CAPTION. T-1147 centres a contained
      picture inside its column; the caption stayed at the column's left edge,
      so caption and picture started 140px apart at feature width. Captions
      under a contained picture centre with it.

   4. THE REVIEW BADGE ON A SHAPED PICTURE. The badge is pinned to the figure's
      top-right, which on an arch or a chamfer is the corner the shape removes.
      Same fix as §12A's curl case: inset it far enough to land on paint. */

.mflow__folio {
  color: #f5f1e8;
  background: rgba(10,10,10,.78);
  padding: .85rem .38rem;
  border-radius: 2px;
}

.mflow--light .mspread__media .mfig img { max-height: min(52svh, 440px); }

.mflow--light > .mfig.u-feature > figcaption,
.mflow--light > .mfig.u-full > figcaption {
  margin-inline: auto;
  text-align: center;
  max-width: 56ch;
}



/* ── 13I · THE HERO CAP WAS BEING BEATEN BY A MIN-HEIGHT ───────────────────
   site.css T-1134 caps .mv-hero at clamp(360px, 46vw, 560px). Measured on the
   rendered page it was 666px at 1440 — 74% of the window, the exact failure
   the cap exists to prevent. The cap is not broken and is not raised here:
   media-hero-movement.css declares `min-height: 74svh` on .mv-hero and is
   linked AFTER site.css, and in CSS a used min-height ALWAYS beats a max-height
   regardless of order or specificity. So the max-height could never apply.

   Zeroing the min-height is the whole fix, and it is scoped to this page's
   photographic hero (.mv-hero--photo) so the other pages that share
   media-hero-movement.css are untouched. The cap value is restated at the
   identical tokens rather than a new number. */
/* T-1245 — the cap is restated here, so it needed the same breakpoint as
   site.css or this rule would keep clipping /media's two CTAs on a phone. */
.mv-hero.mv-hero--photo {
  min-height: 0;
  overflow: hidden;
}
@media (min-width: 1024px) {
  .mv-hero.mv-hero--photo { max-height: clamp(360px, 46vw, 560px); }
}


