/* ============================================================
   Traidy Naidoo ministry site: layout + colour preview
   Type: Atkinson Hyperlegible (brand kit, one face doing every job)
   Palette: brand kit. Indigo #282562 anchors, five carry the colour.
   Three colour directions, switchable. Same layout in each.
   ============================================================ */

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0}
img{max-width:100%;display:block}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

:root{
  /* GEIST EVERYWHERE, 2026-08-19. Rokas: "the whole web should be in the geist
     font." It started as the hero name only (chosen off a four-way render); he
     then took it site-wide, so --f is Geist and every face on the site follows it
     through --f-display and --f-name below. Atkinson Hyperlegible was the previous
     single face; it is dropped from the font load entirely. One note kept for the
     record: Geist ships no italic, so the few italic runs (the section counts)
     render as a synthesised oblique, which is acceptable at that size. */
  --f: "Geist", system-ui, sans-serif;
  --f-display: var(--f);          /* swapped by the FACE toggle */
  --f-name: var(--f-display);     /* the name is the same Geist as the rest now */
  --display-case: uppercase;      /* the reference look; CASE toggle reverts it */
  --display-weight: 700;
  --display-tracking: -.035em;
  --pad: clamp(1.25rem, 5vw, 5.5rem);
  --maxw: 1400px;
  /* section coding, shared across all three directions.
     Used as marks, rules and dots only, never as small text. */
  --a-story:    #FFA100;  /* amber  */
  --a-music:    #B23C7A;  /* plum   */
  --a-messages: #3D63C4;  /* blue   */
  --a-speak:    #C7563B;  /* rust   */
  --a-rose:     #F499BC;  /* rose, interaction */
}

/* ---- Direction 1: Spiritual Precipitation (cool dark) ---- */
[data-theme="precipitation"]{
  /* BLACK WITH THE ZONE PAIR, 2026-08-15, and it has to move WITH it. `--ground`
     is not just the fallback background: it is the colour every scrim fades TO.
     The hero and the poster both dissolve their photograph into the page with
     `linear-gradient(... var(--ground) ...)` stops, so leaving this at the old
     #0B0A14 while the body went black would have hazed a navy edge around each
     photograph against a black page, which is precisely the seam he asked to
     remove. --ground-2 and --ground-3 stay lifted on purpose: they are component
     surfaces (the skip link, the media wells behind an image that has not
     decoded), and those need to be visible AGAINST the ground, not equal to it. */
  --ground:#000000; --ground-2:#15132A; --ground-3:#1E1B38;
  --ink:#F7F1E6; --ink-dim:var(--ink); --ink-faint:var(--ink); /* 2026-09-17 Rokas: no dimmed body text, all bright */
  --line:rgba(247,241,230,.14); --line-strong:rgba(247,241,230,.28);
  --shadow: 0 24px 70px -20px rgba(0,0,0,.85);
  /* THE ISLAND'S OWN FOUR, and they are per direction rather than derived from
     `--ground` because the pill floats over PHOTOGRAPHS as often as over the
     page. `--ground` at 78% would be black over a black jacket and invisible;
     these are lifted off the ground on all three directions so the pill always
     reads as an object in front of the page rather than a hole in it. The key
     is the inverse pair, which is the only high-contrast control on the site and
     is why the play button never needs a border. */
  --isl-bg:rgba(22,20,36,.80); --isl-line:rgba(247,241,230,.20);
  --isl-key:#F7F1E6; --isl-keyink:#0B0A14;
}
/* ---- Direction 2: Banqueting Table (warm dark) ---- */
[data-theme="banquet"]{
  --ground:#2B140F; --ground-2:#3A1C15; --ground-3:#4A251B;
  --ink:#FBEEDF; --ink-dim:var(--ink); --ink-faint:var(--ink); /* 2026-09-17 Rokas: no dimmed body text, all bright */
  --line:rgba(251,238,223,.16); --line-strong:rgba(251,238,223,.32);
  --shadow: 0 24px 70px -20px rgba(20,7,4,.8);
  --isl-bg:rgba(62,30,22,.82); --isl-line:rgba(251,238,223,.22);
  --isl-key:#FBEEDF; --isl-keyink:#2B140F;
}
/* ---- Direction 3: Covenant (ivory) ---- */
[data-theme="covenant"]{
  --ground:#F7F1E6; --ground-2:#FFFDF8; --ground-3:#EFE6D5;
  --ink:#282562; --ink-dim:rgba(40,37,98,.76); --ink-faint:rgba(40,37,98,.74);
  --line:rgba(40,37,98,.16); --line-strong:rgba(40,37,98,.34);
  --shadow: 0 24px 60px -24px rgba(40,37,98,.28);
  /* The pale direction inverts the pair rather than lightening it. Ivory on
     ivory would be the pill disappearing into the page, which is the same
     failure the dark directions avoid from the other side. */
  --isl-bg:rgba(255,253,248,.88); --isl-line:rgba(40,37,98,.22);
  --isl-key:#282562; --isl-keyink:#F7F1E6;
}

/* ---- THE TWO ZONES ---------------------------------------------------------
   Rokas, 2026-08-14: "take one of the brand colours of the logo, and music is
   one colour, uniformity across the music section, and then once you scroll down
   into the ministry section it fades into a different colour, just to signify a
   different side of him... but how all these different callings come together
   into one person. We could integrate that theme into the background."

   THE TWO COLOURS ARE HIS MARK'S OWN, WHICH IS WHAT MAKES THIS AN ARGUMENT
   RATHER THAN A GRADIENT. Sampling `img/traidy-lion-primary.png` returns two
   poles either side of a navy base: a royal blue (#3060A8, #304890) and a rust
   (#A84830, #C04830) with gold and pink between them. The music half takes the
   blue side, the ministry half takes the rust side, and BOTH ARE IN ONE MARK —
   which is the brief's thesis (§2, many callings, one person) said in colour
   instead of in a sentence. The rust is also, by accident and usefully, the
   colour of the jacket he preaches in.

   THE SHIFT IS TINY ON PURPOSE. These are casts on a near-black (or on ivory),
   not two different backgrounds: measured, every pair sits within 1.5% of the
   other's relative luminance, so nothing the contrast suite has already passed
   can be broken by scrolling. If the difference reads as "the page changed
   colour" rather than "the light changed", it has gone too far.

   IT WORKS ON ALL THREE DIRECTIONS because it is expressed as a pair per
   direction rather than as two fixed colours. The direction decision stays open. */
/* PRECIPITATION'S PAIR IS BLACK TO BLACK, 2026-08-15. Rokas: "the background
   should be all black so it's a nice continuous background."

   THE PAIR WAS #090B18 AND #160C0B, a cool near-black and a warm near-black. The
   note above says the shift should read as "the light changed" and not as "the
   page changed colour", and on his screen it was reading as the second thing:
   the cool cast met the warm cast at the hinge and the two halves looked like two
   backgrounds with a seam, which is the opposite of what the crossfade was for.

   SETTING BOTH ENDS TO THE SAME VALUE RETIRES THE EFFECT WITHOUT REMOVING THE
   MACHINERY. js/zones.js still writes --zone-t and the mix still runs; it simply
   resolves black to black at every scroll position, so the hinge is flat. That is
   deliberate rather than lazy: the pair is per direction and the direction
   decision is still open, so banquet and covenant keep their shift and this stays
   one line to reverse. If precipitation is ever confirmed as the only direction,
   THEN zones.js and the mix can go.

   Pure #000 and not a lifted near-black: he asked for black, the ink is ivory at
   full contrast against it, and the photographs on this page are dark enough at
   their edges that a true black is what makes them dissolve into the page rather
   than sit on a slightly different rectangle. */
[data-theme="precipitation"]{ --g-music:#000000; --g-ministry:#000000 }
[data-theme="banquet"]     { --g-music:#21151F; --g-ministry:#2B140F }
[data-theme="covenant"]    { --g-music:#EFF2F8; --g-ministry:#F9F0E4 }

/* `--zone-t` is 0 in the music half and 1 in the ministry half. On the HOME page
   js/zones.js drives it per frame across the hinge; on every inner page it is
   set once from `data-zone`, because those pages belong to one half or the
   other and have no hinge to cross. */
[data-zone="ministry"]{ --zone-t:1 }

/* WHITE, NOT GREY, ON THE MUSIC PAGE, 2026-09-10. Rokas: "make sure all the
   writing on the page is white-coloured and not grey." The page's body copy
   (ledes, the album passage, FAQ answers) runs at --ink-dim (70% ivory) and a few
   micro-labels at --ink-faint (56%); on this page both collapse to full --ink, so
   every run of text reads at one bright ivory. Scoped to data-page="music" only;
   every other page keeps its dim/faint reading hierarchy. The scrub statement is
   unaffected (it dims via --stmt-dim, a separate token). */
[data-page="music"]{ --ink-dim:var(--ink); --ink-faint:var(--ink) }

body{
  font-family:var(--f); color:var(--ink);
  /* faux-BOLD OFF, 2026-09-10. Real weights 400-700 are all loaded, so nothing
     meant to be bold relies on synthesis; disabling weight synthesis stops the
     browser drawing a heavier fallback for a beat while the face loads, which is
     the brief "bold then normal" flash Rokas saw on the music lede (a Safari
     font-load quirk Chromium hides). `style` keeps faux-italic for the <em>. */
  font-synthesis:style;
  /* THE MIX IS THE BACKGROUND, and `--ground` is the fallback rather than the
     value. A browser without color-mix gets the direction's flat ground and
     loses nothing but the shift; the WebKit support check for color-mix is
     already in verify/webkit-pass.js, which is why this is safe to rely on.

     NO TRANSITION ON THIS. Custom properties do not animate without @property,
     and that is exactly what is wanted: the transition below is for the DIRECTION
     switch (a discrete change), while the zone is driven per frame from scroll
     position and must land on the frame it is given. A .5s ease on top of a
     per-frame update is what makes a scroll-linked colour feel like lag. */
  background: var(--ground);
  background: color-mix(in oklab,
    var(--g-ministry) calc(var(--zone-t, 0) * 100%),
    var(--g-music));
  font-size:clamp(1rem,.96rem + .2vw,1.0625rem); line-height:1.6;
  transition: color .5s ease;
  /* A stacking context, so `body::before` (the aura, z-index -1) paints ABOVE this
     ground and BEHIND the content rather than being hidden under the opaque
     ground. isolation makes the context without touching layout or the fixed
     header/island's containing block. */
  isolation:isolate;
}

/* ---- THE AURA — a warm sunset wash behind everything -----------------------
   Added 2026-08-17. Rokas: "a subtle subtle gradient behind everything that would
   move with the scroll ... a nice sunsetty kind of colour that would appear and
   reappear around the background when you scroll."

   A FIXED ::before, NOT `background-attachment: fixed`, because iOS Safari ignores
   the latter and a real fixed layer is the only one that parallaxes there.
   `pointer-events:none` so it never eats a click; z-index -1 so it is under all
   content and over the ground.

   TWO SOFT LIGHTS THAT CROSS. `--gy` is driven by js/glow.js as a slow cosine of
   the scroll, so the two glows slide in opposite directions and warm light wells
   up in one corner as it recedes from the other. With no JS or reduced motion,
   `--gy` holds at .5 and the wash is simply static.

   DARK DIRECTIONS ONLY. The glow image is scoped to precipitation and banquet; on
   the light covenant ground a warm wash reads as a stain, so it gets the bare
   layer.

   SOFTER AND WIDER, 2026-08-17 (Rokas, on the first pass). The alphas came down to
   the .07 range and the glows grew past a viewport across, so the light is a broad
   diffuse wash rather than two readable shapes. On a near-black ground this is a
   whisper, which is the "subtle subtle" the brief asked for twice. */
body::before{
  content:""; position:fixed; inset:0; z-index:-1; pointer-events:none;
}
[data-theme="precipitation"] body::before,
[data-theme="banquet"] body::before{
  background-image:
    radial-gradient(closest-side, rgba(255,168,84,.075), rgba(255,168,84,0) 74%),
    radial-gradient(closest-side, rgba(210,86,96,.06), rgba(210,86,96,0) 74%);
  background-repeat:no-repeat, no-repeat;
  background-size: 125vmax 125vmax, 104vmax 104vmax;
  background-position:
    14% calc(var(--gy, .5) * 100%),
    86% calc((1 - var(--gy, .5)) * 100%);
}
.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad)}

/* type scale */
.display{font-size:clamp(2.75rem,1.4rem + 6.4vw,7rem);line-height:.92;letter-spacing:-.035em;margin:0}
h2{font-size:clamp(1.75rem,1.2rem + 2vw,3rem);line-height:1.05;letter-spacing:-.025em;margin:0}
h3{font-size:clamp(1.125rem,1rem + .5vw,1.4rem);line-height:1.2;letter-spacing:-.012em;margin:0}
.lede{font-size:clamp(1.0625rem,1rem + .5vw,1.375rem);line-height:1.5;color:var(--ink-dim);max-width:46ch}
.kicker{
  font-size:.75rem;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-faint);
  display:flex;align-items:center;gap:.6rem;margin:0 0 1.25rem
}
.kicker::before{content:"";width:1.75rem;height:2px;background:var(--dot,var(--line-strong));flex:none}

a{color:inherit;text-decoration:none}
:focus-visible{outline:2.5px solid var(--a-rose);outline-offset:3px;border-radius:2px}
/* PIECE 1 SAFETY PASS, 2026-08-13. --a-rose measures 1.5:1 on the covenant
   ground and it is currently the WHOLE focus indicator, so a keyboard visitor on
   the ivory direction gets an invisible ring. Per-theme --focus token, exactly as
   ARCHITECTURE §3.0 specifies. Scoped to main/footer ON PURPOSE: the shared rule
   directly above governs the signed-off header, and this pass does not get to
   rewrite it. The header therefore keeps --a-rose and is flagged, not fixed.
   The var() fallback is there so a document with no data-theme still gets a
   visible ring rather than an invalid outline colour, which resolves to none. */
[data-theme="precipitation"]{ --focus:#F7F1E6 }
[data-theme="banquet"]{ --focus:#FBEEDF }
[data-theme="covenant"]{ --focus:#282562 }
main :focus-visible, footer :focus-visible{ outline:2.5px solid var(--focus,var(--ink)); outline-offset:3px }

/* ---------- header ----------
   REBUILT 2026-08-13 (Rokas), off the two references Traidy himself shared:
   jasonbergh.com and markclennon.com. Both run PLAIN TEXT nav, wide-tracked
   uppercase, grouped, and between them they carry not one capsule, fill,
   border or corner radius. vanmorrison.com agrees and supplies the third
   fact, measured: its wordmark centre sits at x=720.5 in a 1440 viewport,
   i.e. genuinely centred, not approximately.
   So: links left, NAME CENTRE, links right, nothing boxed.

   WHAT THIS BREAKS, AND IT IS NOT OPTIONAL TO FIX. The three frosted capsules
   it replaces were NOT decoration. They were the fix for a measured failure:
   the mark strap read 3.69:1 over the footage with no backing. Deleting them
   brings that straight back, so the film's wash now carries a TOP BAND (see
   the name-mode .hero-film::after) and the header gets measured on composited
   pixels rather than reasoned about.
   Full take/leave note: reference-capture.md */
.top{
  position:fixed;top:0;left:0;right:0;z-index:60;
  padding:clamp(1.1rem,2.1vh,1.7rem) clamp(1.25rem,3vw,2.25rem);
  pointer-events:none;
  background:transparent
}
/* ---- THE BAR FADES WITH THE SCROLL, 2026-08-17 ---------------------------
   Rokas: the bar "should come in smoothly and not just appear out of nowhere.
   fade in nicely as you're scrolling down." It used to flip: js/hdr.js set
   `data-scrolled` past 24px and `.top` transitioned transparent to black over
   260ms, so it arrived all at once the instant the page was nudged. Now the bar is
   this ::before and its OPACITY is the scroll position: js/hdr.js writes `--bar-o`
   from 0 at the top to 1 by ~150px, so the black wells up under the nav as you go.

   ON ::before, NOT `.top`'s BACKGROUND, so the value is an opacity on the ground
   colour rather than an alpha baked into one colour. That keeps it theme-safe (it
   is var(--ground), whatever the ground is) and keeps the fade on the compositor.
   z-index:-1 sits it inside `.top`'s own stacking context, behind the links and the
   lion, so only the bar fades and the nav never does. */
.top::before{
  content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:var(--ground); opacity:var(--bar-o,0)
}
/* ---- THE SCROLLED BAR: WHY IT IS SOLID `--ground` -------------------------
   Rokas, 2026-08-16: "make like a black bar behind the nav so that it's easy to
   see when you're scrolling." The bar itself is drawn by `.top::before` above and
   its opacity fades with the scroll (2026-08-17, see that block). This note keeps
   the one decision that is still true regardless of how it fades in:

   `--ground` AND NOT A TRANSLUCENT WASH at full strength. A semi-transparent black
   would let the photograph under it show through as a grey haze, which is the "dark
   band across a continuous lit studio" the header script's own note warns about,
   and worse, a haze reads as a mistake where a solid bar reads as a bar. The fade
   passes through that haze on its way in, but it does not rest there: by ~150px the
   bar is the page colour at full opacity rather than a new one.

   NO BOTTOM HAIRLINE. Every band below meets the bar on the same black, so a rule
   there would be the only line on the site drawn against nothing.

   THE INTRO NEEDS NO SEPARATE GUARD ANY MORE. It used to, because the bar was the
   header's own background; the whole `.top` is held at opacity:0 during the intro
   (see the intro block) and fades in as one thing, so the bar rides in with the nav
   and a deep link that lands scrolled mid-intro no longer paints a strip early. */
.top-in{
  display:grid;align-items:center;grid-template-columns:1fr auto 1fr;
  gap:clamp(1rem,3vw,2.5rem);max-width:var(--maxw);margin-inline:auto
}
.top-in > *{pointer-events:auto}
/* THE CENTRE SLOT holds the lion (Rokas, 2026-08-13). It used to be an invisible
   wordmark reserving a landing box for the scroll dock; the dock is gone and the
   mark is what sits here instead. This is also what settles the open question
   from earlier the same day about whether the mark had a home: it does, and it
   is the middle of the nav rather than the footer. */
.top-slot{display:block;justify-self:center;line-height:0}
.top-slot img{height:2.75rem;width:auto;display:block}
.nav{display:flex;align-items:center;gap:clamp(.9rem,2.1vw,1.9rem)}
/* .nav-r went with the second nav on 2026-08-14: the right slot is .social now,
   which right-aligns itself. Deleted rather than left dormant, same call as the
   scroll dock and .nav-cta. */
.nav a{
  font-size:.8125rem;letter-spacing:.14em;text-transform:uppercase;
  /* WHITE, not var(--ink-dim). Rokas 2026-08-13: "just put the letters as
     white." Deliberately a literal rather than the ivory token, because it is a
     decision about type over film and must not silently follow a palette
     change. See the note on the removed wash above for the measured cost. */
  color:#FFF;padding:.3rem 0;transition:color .4s var(--ease-smooth)
}
.nav a:hover,.nav a:focus-visible{color:#FFF;text-decoration:underline;text-underline-offset:.35em}

/* ---- THE MINISTRY PANEL ----------------------------------------------------
   Eight themes on hover or on focus. Three things here are load-bearing:

   1. `visibility`, NOT opacity alone. A panel faded to 0 is still focusable, so
      a keyboard visitor would tab through eight invisible links to reach
      Contact. visibility:hidden takes them out of the tab order, and the
      :focus-within rule below puts them back the moment Ministry is focused.
   2. IT DOES NOT INHERIT `.nav a`'s WHITE. That white is a decision about type
      over the hero FILM (Rokas, 2026-08-13). This panel is a solid surface, and
      white on it would be invisible on Covenant — the exact defect the header
      already has open on that direction. It follows the tokens instead, so it is
      correct on all three.
   3. The bridge. There is a gap between the nav row and the panel, and a mouse
      crossing it would close the panel mid-journey. `::before` fills that gap
      with an invisible block so the pointer never leaves the group. */
/* DORMANT SINCE 2026-08-17: no element on the site carries `.navdrop` any more.
   Rokas removed the Ministry dropdown and the eight theme pages it opened onto.
   Kept rather than stripped because those pages are turned OFF behind
   `BUILD_THEMES` in build-pages.js rather than deleted, and this is the panel
   they came back to. js/nav.js's handler carries the same note. */
.navdrop{ position:relative; display:inline-flex }
.navpanel{
  position:absolute; top:calc(100% + .85rem); left:-1rem; z-index:60;
  display:grid; gap:.15rem; min-width:15rem; padding:.75rem 0;
  background:var(--ground-2); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:2px; box-shadow:var(--shadow);
  visibility:hidden; opacity:0; transform:translateY(-.25rem);
  transition:opacity .18s ease, transform .18s ease, visibility 0s linear .18s
}
.navdrop::before{
  content:''; position:absolute; top:100%; left:0; right:0; height:.9rem
}
.navdrop:hover .navpanel,
.navdrop:focus-within .navpanel{
  visibility:visible; opacity:1; transform:none;
  transition:opacity .18s ease, transform .18s ease, visibility 0s
}
/* Closed by Escape: the script sets this and clears it on the next pointer or
   focus move, so one keystroke does not disable the menu for the session. */
.navdrop[data-shut] .navpanel{ visibility:hidden; opacity:0 }
/* Reading size and sentence case, unlike the nav row above it. Eight themes at
   13px/.14em tracking in caps is a wall; these are content, not furniture. */
.navpanel a{
  color:var(--ink); font-size:.9375rem; letter-spacing:0; text-transform:none;
  line-height:1.35; padding:.5rem 1.25rem; text-shadow:none
}
.navpanel a:hover,.navpanel a:focus-visible{
  color:var(--ink); background:var(--ground-3); text-decoration:none
}
@media (max-width:900px){
  /* The row is display:none below 900 and the burger takes over, so the panel
     has nothing to hang off. The themes are reachable from ministry.html. */
  .navpanel{ display:none }
}
/* Bergh's active marker is a bullet. One character, no box, same grammar. */
.nav a[aria-current]::before{content:"\2022";margin-right:.45em;color:var(--ink)}
/* .nav-cta ("Book Traidy", underlined rather than pilled) went with the nav
   rework on 2026-08-14: it duplicated the Speaking route, and its action is now
   the solid button in the statement. Its rules are deleted rather than left
   dormant, same call as the scroll dock: dead code rots, git does not. */

/* ---- The socials, hard right ------------------------------------------------
   Rokas 2026-08-14, off the Matt Redman header he sent: pages left, mark centre,
   monochrome glyphs right, no labels, no capsules, no brand colours. The glyphs
   are drawn here as inline SVG rather than pulled from an icon set, because this
   file loads zero external anything and that is not being broken for four paths.
   Each was rendered at 120px and looked at before it went in, since a path
   recalled from memory is a guess until someone sees it.

   BRAND COLOURS ARE DELIBERATELY NOT USED. Four saturated logos in a row would
   be the loudest thing on a page whose whole argument is restraint, and they
   would fight all three colour directions at once. currentColor means they
   follow the nav, including whatever Rokas decides about white-on-Covenant.

   44x44 HIT AREA ON A 20px GLYPH. The icon is the size the reference runs; the
   link is the size a thumb needs. The padding is invisible and the gap is set
   against the 44px box, not against the glyph. */
.social{
  display:flex; align-items:center; justify-content:flex-end;
  gap:clamp(.15rem,.9vw,.7rem)
}
.social a{
  display:grid; place-items:center; width:44px; height:44px;
  color:#FFF; transition:transform .3s var(--ease-respond)
}
.social svg{ width:20px; height:20px; display:block }

/* ---- THE HEADER'S INK OVER THE PALE CONTACT --------------------------------
   js/hdr.js sets data-hdr="light" on <html> while the contact section is under
   the fixed header. The nav and the social icons are a hardcoded #FFF, which was
   correct while every ground was near-black and is about 1.2:1 on the studio
   grey, so they take the light-ground ink for exactly that stretch.

   COVENANT'S INK AGAIN, the same value the section itself uses, so the header
   and the words under it are one decision rather than two.

   SCOPED OUT OF THE OPEN MOBILE MENU. Below 900 the panel drops its own dark
   ground behind these links; flipping them dark there would put dark ink on dark
   and trade one unreadable state for another. The menu is the one case where the
   header has a plate, so it does not need the ink change. */
[data-hdr="light"]:not([data-nav="open"]) .nav a,
[data-hdr="light"]:not([data-nav="open"]) .social a{ color:#282562 }
[data-hdr="light"]:not([data-nav="open"]) .nav a[aria-current]{ color:#282562 }
/* The nav links underline on hover; a glyph cannot, so it lifts. 2px, the same
   grammar as the 4px the .arw routes use, scaled to a smaller object. It moves
   rather than dims on purpose: dimming would cost contrast over the film, which
   is the one thing the header has already had to pay for once. */
.social a:hover, .social a:focus-visible{ transform:translateY(-2px) }
@media (prefers-reduced-motion: reduce){
  .social a{ transition:none }
  .social a:hover, .social a:focus-visible{ transform:none }
}

/* ---- GLYPH-LEVEL SHADOW, and it is ROKAS'S CALL, not a shipped default ------
   Toggle: <html data-navshadow="on">. Set to "off" (or the attribute removed)
   and every rule below stops applying, with nothing else to unpick.

   WHY IT IS HERE. The nav row was mapped across the whole 17.5s loop, worst
   pixel per 60px column, white against the film (verify/nav-film-map.js):

     x 0-180      4.71 - 6.08:1   the only stretch that clears 4.5 for 13px text
     x 180-540    3.07 - 3.93:1   clears the 3:1 icon bar, fails 13px text
     x 540-1020   2.02 - 3.33:1   fails both
     x 1020-1320  1.77 - 1.83:1   the worst on the row, by a distance
     x 1320-1440  2.73 - 2.89:1   fails both

   TWO THINGS FOLLOW, AND THE SECOND ONE MATTERS MORE THAN THE FIRST.

   1. The four social glyphs now sit at x 1180-1404, which is the worst zone on
      the row. Measured 2.10 - 2.52:1 against a 3:1 non-text floor.
   2. THAT ZONE WAS ALREADY OCCUPIED. Before this rework it held Speaking,
      Contact and Book Traidy, so three 13px links were sitting at roughly
      1.8:1 against a 4.5 bar and nothing had reported it. The header suite
      could not see it: it only measures boxes that exist NOW, and the README's
      "worst 7.45:1" is stale by the suite's own admission further up that file.
      So this is not a defect the social row introduced. It is one it uncovered,
      and moving the text links leftward actually improved them, from ~1.8:1 to
      2.88-3.78:1.

   WHY A SHADOW AND NOT A SCRIM. The file already forbids the two easy answers:
   the band is capped at .30 ("Do not raise it past .30 without asking") and
   "Do not quietly reintroduce a scrim." It also names the three routes that DO
   work: a glyph-level shadow, a darker grade on the film, or re-cutting the loop
   from darker beats. Only the first is available without new footage, so that is
   what this is. It is tight and dark rather than broad and soft, so on the dark
   passages of the loop it is invisible and on the bright wall it reads as an
   edge on the letterform, never as a bar behind the row.

   IT NARROWS THE GAP, IT DOES NOT CLOSE IT. A shadow darkens what is directly
   under and around a stroke; it cannot make a blown-out wall dark. Treat this as
   the same knowing trade the .30 band already is, not as a pass. The routes that
   would actually close it both need the footage changed, which is a bigger
   decision than a header rework. */
[data-navshadow="on"] .nav a{
  text-shadow: 0 1px 2px rgba(0,0,0,.9), 0 0 6px rgba(0,0,0,.65)
}
[data-navshadow="on"] .social a{
  filter: drop-shadow(0 1px 2px rgba(0,0,0,.9)) drop-shadow(0 0 5px rgba(0,0,0,.65))
}
/* The lion is a raster with its own transparency and sits dead centre, x ~698,
   in the 2.02:1 column. Same treatment, same reason. */
[data-navshadow="on"] .top-slot img{
  filter: drop-shadow(0 1px 3px rgba(0,0,0,.8))
}
/* Skip link. Codex 2026-08-10 #25: there was none, so a keyboard visitor
   traversed the entire header before reaching any content. */
.skip{
  position:fixed;top:.6rem;left:.6rem;z-index:200;
  padding:.7rem 1.15rem;border-radius:4px;
  background:var(--ink);color:var(--ground);font-size:.875rem;
  transform:translateY(-180%);transition:transform .18s
}
.skip:focus{transform:none}
.foot-mark{display:inline-flex;align-items:center;gap:.6rem}
.foot-mark img{height:2.25rem;width:auto;flex:none}
.btn{
  display:inline-flex;align-items:center;gap:.5rem;padding:.8rem 1.5rem;border-radius:100px;
  border:1.5px solid var(--ink);font-size:.9375rem;letter-spacing:.01em;
  transition:background-color .4s var(--ease-smooth),color .4s var(--ease-smooth),border-color .4s var(--ease-smooth)
}
.btn:hover{background:var(--ink);color:var(--ground)}
.btn-solid{background:var(--ink);color:var(--ground)}
.btn-solid:hover{background:transparent;color:var(--ink)}
.nav-toggle{display:none}

/* ---------- hero: the BTS film, full bleed ----------
   Refs: dsgngroup.it for the shape (one full-bleed muted loop, huge type
   bottom-left, capsule nav floating over it, pill CTA) and jasonbergh.com
   for the register (warm dark ground, cream type, film-instrument furniture
   in tiny letterspaced caps).
   LEFT from dsgn: they run type straight onto footage with no wash, and their
   own sub-copy is illegible over the light floor. We keep a wash and measure
   it on rendered pixels.
   LEFT from bergh: the numbered nav (01. 02. 03.) is a house no. */
.hero{
  position:relative;overflow:hidden;display:flex;isolation:isolate;
  min-height:100svh
}
.hero-film{position:absolute;inset:0;z-index:0;background:var(--ground)}
.hero-film video{
  width:100%;height:100%;object-fit:cover;object-position:50% 34%;display:block
}
/* wash: heaviest bottom-left where the type lives. Ground colour only, no
   saturated tint, or it casts the studio's magenta and teal into mud. */
.hero-film::after{
  content:"";position:absolute;inset:0;z-index:1;
  /* The last three stops are the TOP SCRIM. Note this gradient runs `to top`, so
     100% is the TOP of the frame. It used to end at 16% ground there, which was
     fine while the header carried its own frosted capsules and became a real
     AA failure the moment they were removed: measured SPEAKING at 2.02:1 and
     BOOK TRAIDY at 2.04:1 in WebKit over the film. The name hero got its scrim
     first and passed; this is the same fix for the statement hero, which is
     preview-only and does not ship, but a measured failure left in the file is
     how a measured failure eventually ships. */
  background:
    linear-gradient(to top, var(--ground) 0%,
      color-mix(in srgb,var(--ground) 94%,transparent) 30%,
      color-mix(in srgb,var(--ground) 66%,transparent) 52%,
      color-mix(in srgb,var(--ground) 14%,transparent) 76%,
      color-mix(in srgb,var(--ground) 20%,transparent) 89%,
      color-mix(in srgb,var(--ground) 52%,transparent) 94%,
      color-mix(in srgb,var(--ground) 88%,transparent) 100%),
    linear-gradient(100deg, color-mix(in srgb,var(--ground) 82%,transparent) 0%,
      color-mix(in srgb,var(--ground) 44%,transparent) 30%,
      color-mix(in srgb,var(--ground) 8%,transparent) 56%, transparent 74%)
}
/* Measured off dsgn: their headline is 44px at 1470 wide, about 3vw, NOT the
   9rem monster I had. Their impact comes from uppercase in a tight display
   face at a controlled size, over three lines, hugging a 15px left margin.
   Over-scaling was the single biggest reason this did not read like the ref. */
.hero .wrap{max-width:none;padding-inline:clamp(1.25rem,1.6vw,2rem)}
.hero-in{
  position:relative;z-index:2;width:100%;display:flex;flex-direction:column;
  justify-content:flex-end;gap:clamp(1.75rem,4vh,3rem);
  padding:clamp(6rem,12vh,9rem) 0 clamp(2rem,4vw,3rem)
}
.hero .display{
  margin:0;font-family:var(--f-display);
  font-size:clamp(1.85rem,.7rem + 2.5vw,3.15rem);
  line-height:1.06;letter-spacing:var(--display-tracking);
  text-transform:var(--display-case);font-weight:var(--display-weight);
  max-width:22ch
}
.hero .display em{font-style:italic;color:var(--ink)}

/* ---- HERO B, the name block (vanmorrison.com pattern, our tokens) ---- */
/* Only one of the two heroes exists at a time. */
[data-hero="name"] .hero .display{display:none}
[data-hero="statement"] .hero-line{display:none}
/* Codex 2026-08-10 #24: the page carried TWO <h1>s, one hidden by CSS per preview
   state, so whichever mode you were in the document still had two level-1
   headings. The name is now the only h1 and the statement line is a <p>. In the
   statement PREVIEW mode the h1 is visually hidden rather than display:none, so
   the document keeps exactly one h1 with the right text in both modes. Hero
   shape is settled (Name, Rokas 2026-08-13); statement survives only so the two
   can still be compared, and it does not ship. */
[data-hero="statement"] .hero-name{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;transform:none!important
}
/* the name hero centres its stack rather than bottom-anchoring it: the block IS the
   composition, so hanging it off the bottom edge leaves a hole where the type used to be */
/* Everything in this hero centres, not just the name. A perfectly centred 12vw
   name with a 17px line hard left below it is two compositions arguing, and
   "centred perfectly" was the brief (Rokas, 2026-08-13). Scoped to name mode:
   the statement hero is bottom-left by design and must stay that way. */
[data-hero="name"] .hero-in{
  justify-content:center;align-items:center;text-align:center;
  gap:clamp(1.25rem,3vh,2.25rem)
}
[data-hero="name"] .hero-foot{justify-content:center}
[data-hero="name"] .hero-line{margin-inline:auto}
/* The default wash is weighted BOTTOM-LEFT because that is where the statement hero's type
   sits. Reuse it here and you get both failures at once: the name sits mid-frame where the
   wash is weakest, and the bottom 40% becomes a dead black band with nothing in it.
   So the wash follows the type: strongest through the middle where the name, line and CTA
   are, lighter top and bottom so the film reads as film instead of as a void. */
/* The first three stops are the TOP SCRIM and they are load-bearing, not taste.
   The header lost its three frosted capsules on 2026-08-13, and those capsules
   existed because the mark strap measured 3.69:1 over the footage with no
   backing. This band is what replaces them: strong at the very top where the
   nav row sits (~20-70px), gone by about 14% so the film still reads as film.
   Van Morrison solves the same problem with a #0F0F0F ground; Bergh's footage
   is simply dark; Clennon puts the header on white with the video below it.
   A scrim is the only one of the three available to us. Measured, not assumed:
   verify/webkit-film-contrast.js. Re-measure separately under Covenant, which
   washes the film pale and therefore fails differently. */
/* THE NAME NOW SITS FLAT ON THE FILM (Rokas, 2026-08-13): "there's some black
   shading behind it to make it more visible, but get rid of that because the
   text should be flat on the video, it just looks a lot better that way."

   So the mid-frame body wash is GONE. What survives is a top band and nothing
   else, and that band is not about the name — it is the only thing standing
   between the 13px nav and the footage now that the frosted capsules are gone
   (the mark strap measured 3.69:1 with no backing). It is done by 16%, well
   clear of the name, which is centred.

   THE NAME CAN AFFORD THIS AND THE NAV CANNOT, and the reason is the WCAG large
   -text rule rather than taste: at ~178px the name is large text and needs 3:1,
   while 13px nav links need 4.5:1. Measured after the change, all frames, both
   viewports: see the README. If the name is ever made smaller than 24px, or the
   nav is moved down over the picture, this stops being true and must be
   re-measured rather than assumed. */
/* NO WASH AT ALL. Rokas, 2026-08-13, twice: the shading behind the name went
   first, then the top band over the nav, then "just put the letters as white".
   So the film is completely unmodified and the type sits directly on it.

   THE MEASURED COST, recorded because it is a knowing trade and not an
   oversight. Sampled across the whole 17.5s loop at every nav item:
   the film runs L=0.005 (his dark background) to L=0.689 (the lit wall).
   White is L=1.0, so it swings between about 19:1 on dark frames and
   1.4:1 on the brightest ones. AA wants 4.5:1 for the 13px nav and 3:1 for
   the name. On the brightest passages of the loop this does not meet either.
   No flat colour can: a light one would need L>=3.28 and a dark one L<=-0.04,
   both outside the possible range. It is the footage, not the colour.
   His call, made after the numbers were put in front of him.
   If it is ever revisited, the three routes that DO work are a glyph-level
   shadow, a darker grade on the film, or re-cutting the loop from darker
   beats. Do not quietly reintroduce a scrim. */
/* A FAINT, SHORT BLACK BAND BEHIND THE NAV ONLY (Rokas, 2026-08-13, after the
   full wash and then the ground-coloured scrim were both removed). Deliberately
   black rather than var(--ground): it is a shadow under the nav, not a tint on
   the picture, and it must not shift with the colour direction.

   Peaks at .30 and is gone by 11% of the hero, roughly the top 100px at desktop,
   so it sits under the nav row (~20-70px) and nowhere near the name, which is
   centred. Everything below it is untouched film.

   WHAT THIS DOES AND DOES NOT BUY, measured rather than claimed. Full AA on the
   13px nav over the brightest frame needs about .40 alpha, which is visibly a
   bar. At .30 it lifts the worst frames without reading as a band. So this
   narrows the gap, it does not close it, and that is the deliberate trade.
   Numbers in the README. Do not raise it past .30 without asking. */
[data-hero="name"] .hero-film::after{
  background:
    /* THE TOP FADE, unchanged: keeps the header legible over the film. */
    linear-gradient(to bottom,
      rgba(0,0,0,.30) 0%,
      rgba(0,0,0,.20) 4%,
      rgba(0,0,0,.08) 8%,
      rgba(0,0,0,0)   11%),
    /* THE NAME BAND, added 2026-08-19. When the hero was re-cut to open on the
       keyboard shot (rotate-hero-start.sh), the loop's bright studio-wall frame
       moved under the centred white name, and verify/webkit-film-contrast.js
       measured it at 2.32:1 against a 3:1 bar at 390 — a real dip that the old
       cut also had at a time the check happened not to sample. This is a soft
       vertical scrim darkest behind the name and faded to nothing above and
       below, so the film stays clean at top and bottom and only the band the
       name sits in is protected. Tuned against the checker, not by eye. */
    linear-gradient(to bottom,
      transparent 20%,
      rgba(0,0,0,.42) 46%,
      rgba(0,0,0,.42) 56%,
      transparent 82%)
}
/* the long thesis is hidden here on purpose: under a 11.6vw name it reads as a wall,
   and the roles it carries are already in the mark strap under his name in the header */
[data-hero="name"] .hero-foot .thesis{display:none}

/* ---- THE NAME, THE INTRO, AND THE DOCK ----
   Mechanism measured live off vanmorrison.com 2026-08-13 (reference-capture.md),
   which CORRECTS the 2026-08-10 note in the README: that pass recorded "solid
   type, no knockout" and no hollow state. There is one, and it is two sibling
   spans inside one h1 —
     outline: -webkit-text-fill-color:transparent + 1px stroke, fading 1 -> 0
     fill:    solid, revealed by clip-path inset(0% 50%) -> inset(0%)
   inset(0% 50%) is 50% in from BOTH sides, so the fill opens from the centre
   OUTWARD in both directions at once. It is not a left-to-right wipe, and that
   symmetry is the whole reason it suits a centred name. Assuming "wipe" is the
   single easiest way to get this wrong.

   FAIL-SAFE BY DEFAULT: the settled state is the base rule and only
   [data-intro="run"] hides anything. No JS, no session support, a thrown error
   anywhere — the page still renders a solid name, a visible film and a visible
   header. The intro is an enhancement, never a gate. */
/* THE SCROLL DOCK IS GONE (Rokas, 2026-08-13): "the animation where it goes and
   dumps into the middle. It looks goofy." So the name is a plain in-flow block
   again — no fixed positioning, no transform, no measured slot, no scroll
   handler. The header's centre slot now holds the lion mark permanently instead
   of being a landing zone. Recoverable from git if it is ever wanted back. */
.hero-name{
  position:relative;margin:0;
  font-family:var(--f-name);font-weight:700;
  text-transform:uppercase;              /* a NAME, not a sentence: the all-caps bar does not apply */
  /* set by measurement, not by eye: see --name-size below */
  font-size:var(--name-size);
  line-height:.86;letter-spacing:var(--name-tracking);
  white-space:nowrap;text-wrap:nowrap;
  text-align:center;
  color:#FFF                              /* Rokas: white, not the ivory token */
}
.hero-name .hn{display:block}
.hn-out{
  position:absolute;inset:0;
  -webkit-text-fill-color:transparent;color:transparent;
  -webkit-text-stroke:1px #FFF;
  opacity:0;transition:opacity 300ms linear 1000ms
}
.hn-fill{clip-path:inset(0%);transition:clip-path 640ms cubic-bezier(.25,.85,.3,1) 460ms}

/* ---- the running intro. Everything below is the ONLY thing that hides. ---- */
[data-intro="run"] .hn-fill{clip-path:inset(0% 50%);transition:none}
[data-intro="run"] .hn-out{opacity:1;transition:none}
/* THE NAME DOES NOT PAINT UNTIL THE DISPLAY FACE HAS LOADED, and this is a fix
   rather than a nicety. Measured: the h1 sat at y=446 during the intro and y=311
   once settled, a 135px lurch, because the flow slot it is measured against
   moves when Atkinson replaces the fallback face. The hollow also spent the
   first frames outlined in the WRONG face, since Atkinson is materially wider.
   Waiting for fonts kills both at once. JS adds .fonts-in; a hard timeout in the
   JS guarantees it is added even if fonts.ready never settles, so this can never
   strand the page on a blank hero. */
[data-intro="run"] .hero-name{opacity:0}
[data-intro="run"].fonts-in .hero-name{animation:hnOut 340ms ease both}
@keyframes hnOut{from{opacity:0}to{opacity:1}}
/* The film is held back until the name is solid, and this is the finding that
   matters most from the reference: frames 0 to 2200ms measured mean 15.0 with
   standard deviation EXACTLY 0.00, i.e. flat ground, no imagery. The name
   arrives in silence on black and the world appears behind it once it is solid.
   Not incidental: a 1px hairline over his lit BTS footage would be mush. */
.hero-film video,.hero-film::after{opacity:1;transition:opacity 900ms ease 1180ms}
[data-intro="run"] .hero-film video,
[data-intro="run"] .hero-film::after{opacity:0;transition:none}
.top{opacity:1;transition:opacity 700ms ease 1300ms}
[data-intro="run"] .top{opacity:0;transition:none}
/* The hero is name-only now, so there is nothing left in .hero-in to hold back
   besides the name itself, and the name must NOT be held back. Kept scoped to
   the statement hero's copy, which still exists as a comparison mode.
   THE EXCLUSION IS LOAD-BEARING. The h1 is a direct child of .hero-in, so the
   first version of this rule set the NAME to opacity 0 and the intro rendered a
   black screen — while every check passed, because they asserted .hn-out's own
   opacity (a truthful 1) and never asked whether a pixel had been painted. Same
   class as the guide articles with a perfect DOM and zero glyphs. Do not
   simplify this selector. */
[data-intro="run"] .hero-in > :not(.hero-name){opacity:0;transition:none}
.hero-in > :not(.hero-name){opacity:1;transition:opacity 700ms ease 1300ms}
/* Skipping zeroes the DELAYS but keeps the durations, so an impatient visitor
   gets the ending played quickly rather than a hard cut. A cut here would flash
   the film in, which is the one thing the whole sequence exists to avoid. */
.intro-skip .hn-fill{transition-delay:0ms;transition-duration:260ms}
.intro-skip .hn-out{transition-delay:60ms;transition-duration:180ms}
.intro-skip .hero-film video,.intro-skip .hero-film::after,
.intro-skip .top,.intro-skip .hero-in > *{transition-delay:120ms;transition-duration:320ms}
/* The fill size differs per face and is TUNED FROM MEASUREMENT, not derived: the two faces
   carry different tracking here (-.035em vs -.01em), which moves the width more than the
   glyph shapes do. First guess underfilled Atkinson at 92% and overflowed Clash at 390.
   Measured fill now sits at 96-98% of the column at every width from 390 to 1440. */
/* 12.1 -> 12.4vw on 2026-08-13, and this is measured rather than nudged. The old
   number was tuned against a padded column; the h1 is now full-bleed, and a
   Range around the text node (the ELEMENT box is display:block and therefore
   always 100%, which made the previous fill check unfailable) put the real run
   at 93%. The reference is 1374/1440 = 95.4%. Swept 390/560/834/1024/1280/1440/
   1920: 12.4vw holds 95% at every desktop width with no overflow; 12.6vw is
   unstable and collapses to 90% at 1024. Do not raise it further. */
:root{--name-size:12.4vw;--name-size-m:22.2vw;--name-tracking:-.035em;
      /* the size the name lands at once docked in the header row */
      --dock-size:clamp(.95rem,1.15vw,1.2rem)}
.hero-line{
  margin:0;max-width:34ch;
  font-size:clamp(1rem,.72rem + .55vw,1.35rem);line-height:1.42;
  color:color-mix(in srgb,var(--ink) 88%,transparent)
}
/* sub-copy in a narrow column with the CTA BESIDE it, not under it. Theirs is
   a 356px column with the pill at x=391, bottom-aligned to the same baseline. */
.hero-foot{display:flex;align-items:flex-end;gap:clamp(1.25rem,3vw,2.5rem);flex-wrap:wrap}
.hero-foot .thesis{
  margin:0;max-width:24rem;flex:0 1 24rem;
  font-size:clamp(.9375rem,.9rem + .2vw,1.0625rem);line-height:1.5;color:var(--ink-dim)
}


.hero-cta{display:flex;gap:.875rem;flex-wrap:wrap;flex:none}


/* The preview-furniture CSS went with its markup on 2026-08-13. Dead CSS rots,
   git does not. */
@media (max-width:900px){
  .flag-long{display:none}
  .flag-short{display:block;min-width:0;flex:0 0 auto}
  .flag-in{gap:.5rem;justify-content:space-between}
}
.demo-flag{
  position:fixed;left:0;right:0;bottom:0;z-index:95;
  background:var(--a-story);color:#1a1000;font-size:.75rem;letter-spacing:.04em;
  padding:.45rem 0;line-height:1.4
}

@media (max-width:900px){
  /* MOBILE DROPS THE MORPH, deliberately. The name stacks to two lines here
     (see the 560 block), and docking a two-line name into a header row is a
     different animation, not a smaller one. So below 900 the wordmark is simply
     always visible at header size, which is what markclennon.com does anyway,
     and the hero name stays in normal flow. One behaviour each, both honest. */
  /* .social goes with .nav, not separately. Below 900 the row is lion + a
     hamburger that STILL has no handler anywhere in this file, so leaving the
     four glyphs visible would put them beside a dead button in a two-column
     grid built for two children. Mobile keeps exactly the behaviour it had; the
     header's real mobile defect is unchanged and still open. */
  .nav,.social{display:none}
  .top-in{grid-template-columns:1fr auto;gap:.75rem}
  .top-slot{justify-self:start}
  .hero-name{
    /* relative, NOT static: .hn-out is position:absolute inset:0, so with a
       static parent it escapes to .hero (which is positioned) and the outline
       renders offset from the fill by the whole padding. Caught by looking at a
       390 screenshot; every automated check passed with it present. */
    /* NO padding-inline here. The parent .hero-in already carries .wrap's
       padding, and adding it again put the content box 20px inside the padding
       box while .hn-out (absolute, inset:0) resolved against the PADDING box —
       so the outline rendered 20px off and 40px wide of the fill. */
    /* CENTRED, 2026-08-17 (Rokas, on a phone: the name is "off centre ...
       horizontally"). name mode already sets text-align:center on .hero-in, but
       this rule overrode it to left and the two block spans then hung left. */
    position:relative;transform:none!important;text-align:center
  }
  .nav-toggle{
    display:grid;place-items:center;justify-self:end;
    width:2.75rem;height:2.75rem;margin-right:-.5rem
  }
  .hero-copy{max-width:100%}
  .hero-film video{object-position:54% 30%}
  /* stacked type runs the full width, so the horizontal half of the wash
     cannot carry it. Measured on rendered pixels. */
  /* Top scrim here too. Mobile is where this was easiest to miss: the header
     wordmark is only VISIBLE below 900px (desktop hides it and docks the h1
     into its box instead), so this surface carries text that no other
     breakpoint has. Measured at 2.32:1 over the film and 2.61:1 over the
     poster before the last three stops existed. Name mode escaped it purely on
     specificity, which is not a reason to leave it. */
  .hero-film::after{
    background:linear-gradient(to top,
      var(--ground) 0%,
      color-mix(in srgb,var(--ground) 95%,transparent) 28%,
      color-mix(in srgb,var(--ground) 80%,transparent) 46%,
      color-mix(in srgb,var(--ground) 38%,transparent) 68%,
      color-mix(in srgb,var(--ground) 24%,transparent) 86%,
      color-mix(in srgb,var(--ground) 58%,transparent) 93%,
      color-mix(in srgb,var(--ground) 90%,transparent) 100%)
  }
}
@media (max-width:900px){
}
@media (max-width:560px){
  .switch b{display:none}
  /* "TRAIDY NAIDOO" on ONE line at 390 is only 41px in Atkinson, which is not a hero.
     Van Morrison keeps one line at 390 because Oswald is condensed; we cannot, so we
     stack and get the size back: 23vw a line, measured the same way. */
  .hero-name{white-space:normal;--name-size:var(--name-size-m);line-height:.92}
  .hero-name span{display:block}
  /* CENTRE IN THE FRAME BELOW THE HEADER, 2026-08-17 (Rokas, on a phone: the name
     is "off centre vertically"). `.hero-in`'s base padding is top-heavy (top
     clamp(6rem,12vh,9rem) against bottom clamp(2rem,4vw,3rem)), so name mode, which
     justify-content:centers, dropped the name low. Setting the top pad to the header
     height and the bottom pad to near zero centres the stack in the area under the
     nav rather than in the whole viewport. */
  [data-hero="name"] .hero-in{
    padding-top:calc(var(--header-h) + env(safe-area-inset-top));
    padding-bottom:calc(1.25rem + env(safe-area-inset-bottom))
  }
}
@media (prefers-reduced-motion:reduce){
  *{animation-duration:.001s!important;transition-duration:.001s!important}
  /* motion-sensitive visitors get the poster frame, not a looping film */
  .hero-film video{display:none}
  .hero-film{background:var(--ground) center/cover no-repeat url("video/traidy-bts-poster.jpg")}
  /* THE INTRO IS OFF, and this is where we part company with the reference.
     vanmorrison.com was traced under reducedMotion:reduce and plays the
     identical ~700ms fill anyway. His own brief makes readable, accessible
     typography a stated differentiator, so ours lands solid in the same tick.
     Belt and braces: the JS also never sets data-intro="run" here, but these
     !importants mean a stale attribute cannot strand anyone on a hidden page. */
  [data-intro="run"] .hn-fill{clip-path:inset(0%)!important}
  [data-intro="run"] .hn-out{opacity:0!important;animation:none!important}
  [data-intro="run"] .top,
  [data-intro="run"] .hero-film::after,
  [data-intro="run"] .hero-in > *{opacity:1!important}
  /* no scroll morph either: the name is simply where it is */
  .hero-name{position:relative;transform:none!important;text-align:center}
}

/* ==========================================================================
   BELOW THE HERO — integrated 2026-08-14 (ARCHITECTURE §6, piece 7).
   The old callings / release+tracklist / archive+chips / speaking-band CSS was
   deleted above and is replaced by the shared rig plus one block per section.
   Order matters and mirrors the piece pages exactly: the tokens above stand in
   for _tokens.css (it was extracted from them), then _rig.css, then each piece.
   ========================================================================== */

/* ---------- SHARED: pieces/_rig.css, verbatim, once ---------- */
/* ============================================================================
   THE RIG — ARCHITECTURE §3.0 + AMENDMENT A7. SHARED. Three later pieces
   inherit this file.

   Built against §3.1's payload (the ledger) because a rig with no payload is
   not judgeable, but nothing in here knows anything about the ledger.

   What lives here:  the frame, the 3-column rig, the gate + brackets, the two
                     rails, the display split and its measure, the FULL-BLEED
                     photographic field + scrim, the covenant polarity branch,
                     the two easing tokens, the entrance, reduced motion, focus.
   What does NOT:    anything a section owns (the ledger table, the lens
                     radiogroup, the shapes band, the doors). Those stay in
                     each piece's own <style id="piece"> block.

   Standing decisions, each held against a measurement recorded beside the rule
   that depends on it:

   1. One <img> is the section's ground, full bleed, never a band (A7.2). AA is
      held by the SCRIM, which is composition: the field is open where the
      aperture is and closes to solid --ground at the frame edges, which is
      where every string under 24px lives.
   2. The brackets sit OUTSIDE the gate at full ink. Inside, their backdrop is
      whatever the photograph happens to be, and 3 of 4 measured 1.68-1.78:1.
   3. The gate holds a DIFFERENT photograph from the field (A7.1). With one file
      the window is a filter and the depth is fake.
   4. THE VERTICAL BUDGET IS THE SECTION'S FIRST CONSTRAINT, NOT ITS LAST.
      §2 gives #story one measurable commercial job: the sixteen-programs
      credential and the first booking route land inside the FIRST SCREEN BELOW
      THE HERO. Every gap above the ledger is therefore capped in screen heights
      (--rig-v) as well as in viewport widths, and --gate-h is DERIVED from what
      is left rather than set to taste. See the derivation at --gate-h.
   5. Covenant keeps the photograph and runs it as a two-plate print: the field
      is a pale impression, the aperture is the same frame printed dark. §3.0's
      "delete the field on covenant" was driven and produced a greyed-out plate
      at rgb(160), the page's only mid-tone.
   ============================================================================ */

:root{
  --frame-pad: clamp(1.25rem, 3vw, 2.25rem);   /* 36px @1440 — identical to .top's
                                                  inline padding, which is what puts
                                                  our content edge on the header's */
  --gap:      0.5vw;                            /* 7.2px @1440 */

  /* THE VERTICAL BUDGET, DECLARED ONCE. Everything above the ledger is sized
     against this as well as against vw, so a short-and-wide window shrinks the
     whole head instead of shrinking only the gate. 1svh, so a mobile URL bar
     appearing cannot re-lay the section mid-scroll. */
  --rig-v: 1svh;

  /* MEASURED: .top renders 81.78px tall at 1440 and 79.44px at 390. */
  --header-h: 5.25rem;

  /* The aperture. 2:3 PORTRAIT, never anything else: the ratio is what makes it
     a window rather than a card, and every 2:3 frame on disk (perform-blue,
     bw-thought, editorial-apricot, editorial-red, editorial-rose) crops to
     nothing at all inside it.
     THE HEIGHT IS DERIVED FROM THE FOLD, NOT CHOSEN. §2 requires the credential
     and the first booking route inside the first screen below the hero. Driven
     at five viewports, measuring the route's own bottom against
     heroHeight + innerHeight and raising the coefficient until the tightest
     viewport ran out of room:
       1440x900  route bottom 1715 of 1800   gate 306 x 459   85px spare
       1366x768              1492 of 1536         261 x 392   44px spare
       1280x720              1411 of 1440         245 x 367   29px spare
     51 is the largest whole coefficient that still clears 1280x720, which is
     the binding case because a short window shrinks the gate and the fixed
     costs above the ledger with it, but not the ledger row itself.
     36vw governs only on a window wider than it is tall by 4:3; at every laptop
     ratio the svh term binds, which is the point. The previous 60svh produced a
     518px gate and put the route 51-125px UNDER the fold at every desktop size,
     which is the section failing its own stated job to keep a taller window. */
  --gate-h:   min(36vw, calc(46 * var(--rig-v)));
  --gate-w:   calc(var(--gate-h) / 1.5);
  /* THE SPINE. §2 stakes the argument on every right-aligned column on the page
     ending on ONE vertical line; §3.1 records it as "x=540", which was the
     arithmetic of a 345.6px gate rather than the invariant. The invariant is
     --col, and the three surfaces (this ledger's labels, §3.2's tag column,
     §3.3's shape names) all read it from here, so they agree at any gate size.
     MEASURED at 1440x900: 523.8, so the spine is x559.8 and the gate x567.0,
     and the ledger's labels end on the same pixel as display half A. */
  --col: calc((100% - var(--gate-w) - 2*var(--gap)) / 2);

  /* THE SAME COLUMN, WITHOUT THE PERCENTAGE, so the display type can be capped
     against the box it has to fit in. --col cannot be used for that: a % inside
     a font-size resolves against the PARENT FONT SIZE, not against the frame,
     so `font-size: calc(var(--col)/N)` would silently compute a number three
     orders of magnitude wrong rather than fail. Every term below is already
     percentage-free, and at 1440 this measures 523.8 against --col's own
     measured 523.8. See the cap at .dhalf. */
  --frame-w: min(var(--maxw), calc(100vw - 2 * var(--frame-pad)));
  --col-w:   calc((var(--frame-w) - var(--gate-w) - 2*var(--gap)) / 2);

  /* Two easing curves in the whole system. One reveals, one responds. */
  --ease-reveal:  cubic-bezier(.75, 0, 0, 1);
  --ease-respond: cubic-bezier(.75, 0, .25, 1);
  /* THE THIRD CURVE, added 2026-08-17 for the section arrival (`.ar`). The two
     above both START hard — a .75 first control point is a curve that leaves
     immediately — which is right for a control answering a click and wrong for
     type appearing under a reader who did not ask for it. This one leaves at once
     and decelerates the whole way in, so the movement is over before it is
     noticed. It is the difference "subtle" actually asks for. */
  --ease-arrive:  cubic-bezier(.22, 1, .36, 1);
  /* THE FOURTH CURVE, added 2026-08-17. A smooth, symmetric ease for HOVER
     transitions (colour and fill), so a list warms and cools by a gradient rather
     than snapping. Rokas: "make all the animations smooth and graceful ... make it
     like gradient over smoothly." It is paired with a ~.45s duration on the
     interactive hovers below, which is what turns a .2s snap into a fade. */
  --ease-smooth:  cubic-bezier(.4, 0, .2, 1);
}

/* Focus. --a-rose is 1.5:1 on the covenant ground and was carrying the whole
   indicator. Scoped to main/footer so the shared rule in _tokens.css governing
   the signed-off header is not silently rewritten. */
[data-theme="precipitation"]{ --focus:#F7F1E6 }
[data-theme="banquet"]     { --focus:#FBEEDF }
[data-theme="covenant"]    { --focus:#282562 }
main :focus-visible, footer :focus-visible{
  outline:2.5px solid var(--focus); outline-offset:3px; border-radius:2px
}

/* ---- The frame -------------------------------------------------------------
   1440: min(1400, 1440-72) = 1368 wide, x36 -> x1404. Pixel-identical to the
   header's .top-in box. NOT .wrap: the hero reads .wrap and is off limits.
   390:  min(1400, 390-40)  =  350 wide, x20 -> x370. */
.frame{ width: min(var(--maxw), calc(100% - 2*var(--frame-pad))); margin-inline:auto;
        position:relative; z-index:1 }

/* overflow-x:clip (never `hidden`, which would create a scroll container) is
   what lets the field run to the viewport's edges from inside a section without
   touching the document's scrollWidth. */
.section{
  position:relative; isolation:isolate; overflow-x:clip;
  /* A SECTION NEEDS NO scroll-margin: its own top padding IS the header's
     clearance, and adding --header-h on top pushes the first line 170px down.
     An anchor INSIDE a section has no such padding, so it needs the full
     header height. §3.3 moves focus to an <h3> on the route hand-off and would
     otherwise land it under the fixed header; the rule is here rather than in
     that piece because the defect belongs to the shared rig. */
  scroll-margin-top:0;
  /* MEASURED: .top renders 81.78px tall at 1440 and 79.44px at 390, so the
     floor is set by the taller of the two plus air: 5.25rem = 84px clears both,
     and the 6vw term keeps the head proportional on a wide window.
     TIGHTENED 2026-08-14 (the fit-a-screen pass). THE 5.25rem FLOOR IS NOT
     TOUCHED, because it is the header clearance and cutting it would put the
     first line under the fixed bar. Only the vw term and the ceiling come down,
     which is air on a wide window and nothing on a phone: 6vw/6.5rem was 86.4px
     at 1440, now 4.4vw/5.25rem holds the 84px floor at every width up to 1900. */
  padding-block: clamp(5.25rem, 4.4vw, 5.25rem) clamp(2.25rem, 2.8vw, 3rem)
}
main :is([id]):not(.section){ scroll-margin-top: var(--header-h) }

/* ---- #story, THE STATEMENT --------------------------------------------------
   REWORK 2026-08-14 (Rokas, off dsgngroup.it). The ledger composition that stood
   here — field photograph, epigraph, display split with the gate, four-row
   ledger — is preserved whole in `pieces/ledger.html` and is NOT deleted. Its
   CSS below is left in place rather than stripped, because `.rig`, `.rail` and
   `.dline` are shared with #messages and #speaking, which are not reworked yet.
   Stripping the story-only rules (.field, .epi, .gate, .ledger) is a cleanup for
   AFTER those two sections land, not a change to make blind today.

   THE EFFECT IS PORTED, NOT COPIED, and it was MEASURED off the reference rather
   than reasoned about (`verify/recon-dsgn.js`, Chromium @1440x900, 2026-08-14):
     - the statement splits to PER-CHARACTER units, grouped per word inside an
       inline-block wrapper so a word never breaks across a line (189 units);
     - each character animates TWO properties, not one: opacity, AND a 5px
       lateral offset, translate3d(-5px,0,0) -> translate3d(0,0,0). The 5px is
       theirs, measured off a computed matrix, not invented;
     - the dim state is the SAME ink at low alpha over the ground, never a second
       grey. Reference measures rgb(255,255,255) throughout, at opacity .2;
     - it is POSITION-SCRUBBED, not a triggered stagger. This was the one thing
       sampling alone could not settle, so it was tested directly: parked
       mid-reveal it FREEZES (120/189 lit, still 120/189 after 4s with no
       scroll), and scrolling back up REVERSES it (189 -> 120 at the same offset);
     - trigger window, measured: the reveal starts with the block top at ~.71 of
       viewport height and completes at ~.36. A window of ~.35vh.

   THREE DELIBERATE DEVIATIONS, and the reason for each:
     1. THE DIM FLOOR IS OURS, and it is the important one. The reference sits
        its unread text at alpha .20, which over our grounds measures 1.70:1
        (precipitation), 1.79:1 (banquet) and 1.46:1 (covenant) — all under the
        3:1 WCAG floor for large bold text. That would make the unrevealed
        sentence unreadable to a low-vision visitor, on a site set in Atkinson
        Hyperlegible precisely because his platform extends into disability
        advocacy, and the sentence is his own answer to who he is. Floors are the
        measured minimum clearing 3:1, plus margin: .38 on the dark directions
        (3.25:1, 3.24:1) and .55 on covenant (3.25:1). Covenant needs more
        because dark ink on ivory washes out faster than ivory on dark.
     2. NO SMOOTH-SCROLL LIBRARY, AND NO GSAP. The reference drives this with
        GSAP + ScrollTrigger + SplitText + Lenis, four libraries. This file loads
        ZERO external scripts, which is worth more than their easing; the port is
        one rAF-throttled scroll listener that touches only the characters that
        actually changed since the last frame, typically two or three.
     3. THE PER-CHARACTER RAMP IS OURS. The endpoints are SOURCE (alpha .2 and 1,
        offset -5px and 0); the shape of the boundary between them was never
        sampled, so a short per-character transition stands in for it. Graded
        PARTIAL in reference-capture.md rather than claimed as measured.
   ---------------------------------------------------------------------------- */
.stmt{
  --stmt-dim:.38;
  /* MORE AIR THAN THE SHARED .section RULE GIVES, and it is measured off the
     reference rather than felt: on dsgngroup.it the statement sits ~240px below
     the top of its section, and the shared rule was putting ours 86px below the
     hero's bottom edge. One sentence alone on a ground is the whole composition,
     so it gets the room to be that instead of looking like a caption the hero
     dropped. Overrides, not edits, the .section padding. */
  padding-block: clamp(7rem, 13vw, 13rem) clamp(5rem, 8vw, 8rem)
}
[data-theme="covenant"] .stmt{ --stmt-dim:.55 }
/* FLOW INTO THE RECORD ON A PHONE, 2026-08-17. Rokas, on #story's CTAs: "this looks
   like the end of the web page. make it flow better." The statement's foot is a
   desktop 8rem, and on a phone that pushed the record section (#music) fully below
   the fold, so the two CTAs read as the end of the page. A shorter foot lets the
   next section rise into view under them, so the page reads as continuing. */
@media (max-width:700px){
  .stmt{ padding-block-end: clamp(2.25rem, 7vw, 3.25rem) }
}

.stmt-p{
  /* No max-width. The frame is the measure, exactly as the reference runs it:
     at 1440 that is 1368px, which lands the sentence at ~52 characters a line. */
  margin:0;
  font-family:var(--f-display); font-weight:var(--display-weight);
  text-transform:var(--display-case); letter-spacing:var(--display-tracking);
  font-size:clamp(1.6rem,.62rem + 2.9vw,2.75rem); line-height:1.18;
  color:var(--ink);
}
/* NO-JS AND REDUCED-MOTION ARE THE SAME STATE, and it is the finished one: the
   split is built by JS from this paragraph's own text, so if the script never
   runs there are no .stmt-c spans to dim and the sentence renders whole. */
.stmt-w{ display:inline-block }
.stmt-c{
  display:inline-block;
  /* FALLBACK ADDED 2026-09-10. `--stmt-dim` is declared on `.stmt` and `.turn`
     (the home page's two statements) but on NEITHER inner page's `.open-stmt`, so
     on music.html and ministry.html the variable resolved to nothing, opacity fell
     back to 1, and the scrubbed reveal was invisible: every letter was already lit.
     .38 is the canonical dim, so a statement with no host declaration still reveals;
     a host that needs another value (covenant sets .55) still wins via the cascade. */
  opacity:var(--stmt-dim, .38); transform:translate3d(-5px,0,0);
  transition:opacity .34s var(--ease-respond), transform .34s var(--ease-respond);
}
.stmt-c.is-lit{ opacity:1; transform:translate3d(0,0,0) }
@media (prefers-reduced-motion: reduce){
  .stmt-c{ opacity:1; transform:none; transition:none }
}

/* ---- The two doors, under the statement and hard right ----------------------
   Rokas 2026-08-14. THE COMPOSITION IS WHY RIGHT WORKS: the statement is left
   aligned and its last line is short, so the bottom-right of the block is a
   ragged void. Putting the buttons there fills it and closes the section
   diagonally instead of stacking one more left-aligned thing under three.

   REUSING `.btn`, NOT INVENTING ONE. It was already in this file (100px radius,
   1.5px ink border, .5rem gap for the arrow) and had NO instance in the markup
   anywhere — dead CSS waiting for its first use. It also happens to be the
   shape the reference uses for its GET IN TOUCH pill, so the port needs no new
   component. The arrow-on-hover is the pattern .rlink, .srow, .submit and .f-go
   already run, at the same 4px and the same easing, and it is scoped to this
   section rather than added to the shared `.btn` because #messages and
   #speaking have not been reworked yet and should not inherit a decision made
   for this section.

   NOTE ON THE CAPSULE: the header dropped its three frosted capsules on
   2026-08-13 because both references Traidy shared run plain-text nav. That was
   a decision about the NAV, not about content CTAs, and the reference Rokas
   brought for THIS section carries a capsule button. So it is not a reversal. */
.stmt-do{
  display:flex; flex-wrap:wrap; justify-content:flex-end;
  gap:clamp(.75rem,1.4vw,1rem); margin-top:clamp(2.5rem,4.5vw,4rem)
}
.stmt-do .btn{ min-height:48px }
.stmt-do .btn:hover .arw,
.stmt-do .btn:focus-visible .arw{ transform:translateX(4px) }

/* At 390 they stretch and stack. Two 175px pills side by side in a 350px frame
   is a 44px tap target defended by nothing, and the arrow would have no room to
   travel. Stacked, each is full width and the arrow sits hard right. */
@media (max-width:640px){
  .stmt-do{ flex-direction:column; align-items:stretch }
  .stmt-do .btn{ justify-content:space-between }
}
@media (prefers-reduced-motion: reduce){
  .stmt-do .btn .arw{ transition:none }
}

/* ---- #music, THE RECORD -----------------------------------------------------
   Rokas 2026-08-14, off a Miyagi & Andy Panda layout: large cover hard left, a
   smaller plate to the right, and the prose about the record UNDER the image
   rather than beside it. Promoted out of #speaking, where it had been a .door.

   THE COVER IS CAPPED AT 453px AND THAT IS A MEASUREMENT, NOT A TASTE. The only
   artwork that exists is `album-i-am-chosen-PHOTOGRAPHED.jpg` at 906x926 — a
   photograph of a physical print held up to camera in two of his videos, not an
   exported press file, and CONTENT-INVENTORY records that both appearances top
   out around 900px with "no upgrade to be had from these files" and that it
   "must not be blown up". 906 / 2 = 453, so 453px is the largest this can be
   drawn and still be sharp on a 2x display. The reference runs its image far
   bigger; we cannot, until Traidy sends the digital original. That single ask is
   named in the inventory as the cheapest high-value ask in the project, and this
   cap is the thing it would lift.

   THE GRID IS 1.05 : 1 rather than the reference's heavier left bias, because a
   453px cap means a wider left column would just be empty gutter. */
/* THE LEFT COLUMN IS THE COVER'S OWN CAP, NOT A FRACTION. A 1.05fr column at
   1440 is 660px wide holding a 453px image, so 207px of it was dead gutter and
   the "smaller plate to the right" came out WIDER than the cover it was meant to
   sit beside — the reference's proportion inverted. Pinning the column to 453
   and capping the plate at 340 restores it: 453 / 340 wide, and 453 / 191 tall,
   against the reference's own measured 2.4:1 height ratio. */
.music-grid{
  display:grid;
  /* THE COVER COLUMN GREW, 2026-08-15. Rokas: "in the album cover section, make
     the album cover image bigger to take up more space." It was a flat 453px,
     which is exactly half the artwork's natural 906px, and it left the left
     column empty for 200px under the sleeve while the right column had 287px of
     unused width beside its plate. In the section that exists to show the record,
     the record's own cover was the smallest thing in the row.

     A CLAMP, NOT A BIGGER FLAT NUMBER. 620px at 1440 is a 37% increase and still
     under the artwork's natural width, so it never upscales. The 43vw middle term
     is what keeps it honest at narrower widths: a flat 620 would have taken 65%
     of the frame at 1024 and squeezed the studio plate beside it, and the plate
     is already the one that gives way there (`width:min(100%,580px)`). */
  grid-template-columns:minmax(0,clamp(20rem,43vw,620px)) minmax(0,1fr);
  /* SPLIT, NOT ONE `gap`. The column gap is the gutter between the cover and
     the plate and wants to stay generous; the ROW gap is the distance from the
     images down to his paragraph, and Rokas drew an arrow pulling that up
     ("move the paragraph up to save space"). One shorthand cannot do both. */
  column-gap:clamp(1.75rem,3.5vw,3rem);
  /* ROW GAP UP A STEP, 2026-08-14: "where the album cover picture is, give it a
     bit more space to the paragraph underneath, maybe give them a small bit of
     space." 20px -> 34px at 1440. Deliberately a step and not a reset: the same
     number was pulled DOWN by the note quoted above, so the paragraph is meant to
     sit close to the images, just not touching them. */
  row-gap:clamp(1.25rem,2.4vw,2.25rem);
  align-items:start;
  margin-top:clamp(1.5rem,3vw,2.5rem)
}
/* Explicit placement. The heading spans and leads, the two plates share the
   row, and the prose spans again UNDERNEATH them — which is the one piece of
   the reference's composition Rokas named in words ("the prose about the record
   under the image rather than beside it"). Source order is already the mobile
   order, so the single-column fallback only has to release the columns. */
.music-grid > .music-l  { grid-column:1 }
.music-grid > .music-r  { grid-column:2 }
.music-grid > .music-say{ grid-column:2 }

/* .ask, .record and .peek carry their own top margins, tuned for the vertical
   stack they had inside the door. In a grid those margins ADD to the gap and the
   rhythm goes uneven, so the grid takes ownership of the spacing here. The rules
   themselves are untouched, because #speaking still uses them. */
/* THE SECTION STARTS HIGHER THAN THE OTHERS, and that is safe only because of a
   companion change. Rokas: "move this section up a little more." `.section`'s
   top padding is normally the fixed header's clearance, which is why `.section`
   sets `scroll-margin-top:0` — so cutting it here would have made the nav's
   "Music" link land the artwork under the header. `#music` now carries an
   explicit `scroll-margin-top: var(--header-h)` instead (see that rule). The
   two must stay together: restore the padding and the scroll-margin becomes
   double clearance, cut the scroll-margin and the anchor breaks. */
.music{ padding-block-start: clamp(2.25rem,3.2vw,3.5rem) }

/* THE RIGHT COLUMN IS A BLOCK PINNED TO THE FRAME'S RIGHT EDGE, holding the
   plate and then the songs. It is capped at 440 so it stays visibly lighter
   than the 453 cover, and the plate inside it is capped tighter again at 340 so
   the reference's "smaller plate" reading survives. */
/* 580, AND THE NUMBER IS OFF ROKAS'S OWN MARK-UP rather than off a feel. He drew
   an L-bracket on the 2026-08-14 screenshot enclosing the empty box immediately
   left of the plate, from x 845 out to the frame's right edge at 1431 in that
   window's CSS pixels — 586 wide against the 340 that was there. "A little
   bigger" in words, 1.7x in the drawing; the drawing is the specific one, so it
   wins. Rounded to 580.

   IT IS NOW WIDER THAN THE 453 COVER, WHICH INVERTS THE REFERENCE. The Miyagi
   layout runs its right plate at about 0.7x its left image, and this is 1.28x.
   That is a deliberate departure on his instruction, not a drift, and it is
   worth knowing if the reference is ever consulted again: the cap on the cover
   is what makes the two impossible to satisfy at once. */
.music-r{
  width:min(100%,580px); justify-self:end;
  display:grid; gap:clamp(1.5rem,2.6vw,2.25rem)
}
.music-tail > .ask{ margin-top:0 }
.music-tail > .record{ margin-top:0 }

.plate{ margin:0; max-width:580px; justify-self:end; width:100% }
.plate img{
  width:100%; height:auto; display:block; border-radius:2px;
  box-shadow:var(--shadow)
}
/* `.plate-cap` WENT WITH THE LABELS IT STYLED. Rokas, 2026-08-14: "get rid of
   the words underneath the images, it lowers the perception of being a premium
   website." It was the reference's two-item row and it named what the picture
   already showed. The rule now lives in pieces/tracklist.html, which is the only
   other thing that used it and is parked. */

/* The tracklist's rules went WITH its markup to pieces/tracklist.html rather
   than being left here dormant. Nothing else on the page uses `.tracks*` or
   `.t-*`, so leaving them would be dead CSS in a file that is already long, and
   dead CSS is the kind of thing that gets edited by mistake later. The piece is
   self-contained and pastes back in one move. */

/* `.music-rail` and `.music-h` are GONE, not commented out. Both their elements
   were removed on 2026-08-14 and nothing else in the file uses either class. */

/* Both caps follow the column above rather than pinning the old 453. They are
   kept as explicit numbers instead of being deleted, because 620 is a real
   ceiling: the artwork is 906px wide and anything past that upscales it. */
.music .cover{ margin:0; display:grid; gap:0; justify-items:start; max-width:620px }
.music .cover img{
  width:100%; max-width:620px; height:auto; border-radius:2px;
  box-shadow:var(--shadow)
}
/* THE COVER CAPTION AND ITS TAB ARE GONE, 2026-08-14 ("get rid of the caption
   under album image"), and the rules went with the element rather than being
   left dormant.

   WORTH KEEPING THE MEASUREMENT, because it is the reason the tab existed and
   it will be the reason again if anyone ever tries to put text on this artwork.
   Set straight onto the cover, 12px ivory measured 1.16-1.83:1 against a 4.5
   bar. Mapping the artwork in 10px bands down its lower half showed there is no
   good spot, only bad ones: y 340-350 peaks at 0.99 luminance (the gold script),
   y 280-330 runs 0.94-0.98, and even the darkest band at y 440 is 0.45. Ivory
   12px text needs its ground under 0.16. Nothing on the lower half of that cover
   is close. Any future label over it needs its own ground, as that tab had. */
/* UNDER THE SECOND IMAGE, NOT ACROSS BOTH, 2026-08-14. Rokas: "on the cover
   album section, move the paragraph to the right, kind of underneath the second
   image, so it's all aligned."
   IT MOVES COLUMN AND LOSES ITS CENTRING IN ONE MOVE, because the two go
   together: centred text in a 705px box that started at x367 lined up with
   nothing on the page. In column 2 it takes the plate's own box exactly —
   x824 to x1404, measured — so its left edge is the plate's left edge and its
   right edge is the frame's. Quiet is still the point: reading size, not display
   size, so the artwork keeps the weight.
   (Was: centred across both columns, which is what the reference does with this
   block. The reference has no second image above it.) */
/* THE BOX IS .plate's BOX, DECLARED THE SAME WAY, and the first attempt got this
   wrong in a way only measuring caught: putting the block in column 2 is not the
   same as putting it under the image. Column 2 runs x537-1404 (867 wide); the
   plate is capped at 580 and pushed to the end of it, so it starts at x824. The
   block took the whole column and its left edge landed 287px to the LEFT of the
   picture it was supposed to sit under. Same cap, same justify-self, same edge. */
.music-say{
  margin:0; display:grid; gap:.85rem;
  max-width:580px; width:100%; justify-self:end; text-align:left
}
.music-say blockquote{ margin:0 }
.music-say p{
  margin:0; font-size:clamp(1rem,.95rem + .35vw,1.1875rem); line-height:1.6;
  color:var(--ink-dim); text-wrap:pretty
}

/* ---- BAND 2: the poster --------------------------------------------------
   Full bleed by being a sibling of .frame rather than a child of it. The height
   is a viewport fraction with a floor and a ceiling, so it stays a band and
   never becomes a full screen on a laptop or a stamp on a phone. */
/* THE BAND IS NOW CONTENT-DRIVEN, and that is the third shape it has had today.
   It began as a viewport fraction, which moved the crop with the window and at
   1440x960 showed only 32% of the source — his face filled the band and the
   title ran across his mouth. A fixed 8:5 ratio fixed that and was right while
   the band held three lines. It stopped being right when Rokas asked for the
   quote and the summary to sit in front of this photograph: a fixed ratio would
   either clip that copy or leave a hole under it depending on the window.

   So the height is whatever the copy needs, with a floor, and the photograph
   covers it. `min-height` is the floor for the case where the copy is short;
   `object-position` is what keeps the crop sane as the band grows. */
/* BACK TO 155vh, AND IT IS A DELIBERATE EXCEPTION TO THE FITS-A-SCREEN RULE.
   It was capped to a screen earlier on 2026-08-14 and put back the same day.
   Rokas: "let's return the I Am Chosen section back to its original size. I
   actually kinda like it being bigger... give it more space. like yeah, the
   shoulders in there and stuff."
   THE SHOULDERS ARE THE REASON AND THEY ARE A MEASUREMENT, not a preference
   about air. `object-position` is 0%, so the band shows the TOP of the source
   downwards: at 1488px it reaches about 69% of the photograph, which is his head
   and his shoulders; at a screen it reached about 43%, which cropped to a head.
   The height IS the crop here, so a cap on the height is a crop on the portrait.
   This is the ONE band on the site allowed past a screen, and
   verify/fits-screen.js names it as an exception rather than passing quietly. */
.poster{
  position:relative; isolation:isolate; display:grid;
  min-height:clamp(44rem,155vh,100rem);
  margin-top:clamp(2rem,4vw,3.25rem)
}
.poster-img{
  position:absolute; inset:0; z-index:0;
  width:100%; height:100%; object-fit:cover;
  /* 8%, RAISED FROM 18% WHEN THE BAND GREW. object-position is a percentage of
     the SLACK, not of the image, so a taller band has less slack and the same
     18% moved his face further down the frame — straight into the quote. At 8%
     the head sits near the top of the band whatever the copy length, and
     everything below it is jacket and black, which is what the text needs. */
  object-position:50% 0%
}
/* THE GRADIENT IS A MEASURED FIX, AND THE FIRST BUILD SHIPPED WITHOUT IT.
   The comment above used to say "no scrim, the jacket is black enough". It was
   wrong, and only measuring caught it: verify/poster-contrast.js returned
   1.03:1 against a 3:1 bar for the title. Sampling the box behind it explained
   the number — the ground is overwhelmingly black (median luminance 0.0003,
   90th percentile 0.0075) but the beaded jacket throws SPECULAR SPARKLES, and
   the brightest pixel under the title measured rgb(230,248,254). About 0.1% of
   the box sits bright enough to break a letterform edge.

   So this is not a wash over a photograph that was too light. It is the minimum
   needed to put the sparkles under the bar, over an area that was already near
   black, and the face in the top half is untouched: the gradient is fully
   transparent above 58%. Ratios after it, worst pixel, both engines and all
   three directions: see the run recorded in the README.

   NOT the hero's scrim rule. That rule ("do not quietly reintroduce a scrim")
   governs the nav over the hero FILM, where the band is capped at .30 and any
   raise needs asking. This is a different band, a still, and it is measured and
   written down rather than quiet. */
.poster::after{
  content:''; position:absolute; inset:0; z-index:0; pointer-events:none;
  /* REACHES TO 72% NOW, from 58%. When the band held three lines the text was a
     strip at the foot and the fade only had to cover that. It now carries a
     display quote and five paragraphs over most of the lower two thirds, so the
     fade has to arrive well before them. His face is above 30% at every size the
     band takes, so it is still untouched. */
  background:linear-gradient(to top,
    rgba(0,0,0,.80) 0%, rgba(0,0,0,.76) 34%, rgba(0,0,0,.58) 56%, rgba(0,0,0,0) 78%)
}
/* LEFT, NOT CENTRED, 2026-08-14 (Rokas: "if we moved the head to the left a
   little more"). The band now reads bottom-left the way the hero does, and it
   hands off to the pull-quote below it, which is also left-set. A centred title
   above a left-set quote was two different alignments in one breath.

   THE INNER WIDTH IS .frame's, NOT the padding's. The band is full-bleed, so
   padding-inline of `--frame-pad` would start the type at the VIEWPORT edge
   inset and leave it out of line with every other section on a wide window,
   where .frame is capped at --maxw and centred. Repeating .frame's own
   expression puts the title on the same left rule as the artwork above it. */
/* STRETCHED, NOT BOTTOM-ANCHORED, since 2026-08-14: the head goes to the top of
   the band and the passage stays at the bottom, with the photograph between
   them. `space-between` on TWO children does that in one declaration — which is
   why the head needed a wrapper. With four loose children it spread all four and
   put the prayer line across his face. */
.poster-in{
  position:relative; z-index:1; align-self:stretch;
  /* TWO EXPLICIT ROWS, not space-between, since 2026-08-14. The head takes what
     it needs and the passage takes ALL the rest, which is what lets its two
     columns be placed independently inside it: the paragraph vertically centred
     on the picture, the quote down at the foot. `space-between` could only push
     things apart; it could not put one child in the middle. */
  display:grid; grid-template-rows:auto 1fr; justify-items:start;
  text-align:left;
  width:min(var(--maxw), calc(100% - 2*var(--frame-pad))); margin-inline:auto;
  /* THE TOP OPENED UP 2026-08-17, from clamp(1.5rem,3vh,2.5rem). Rokas, on a
     capture of #consecration: "move the headline down a little, can give the text
     some space."

     IT WAS AT ITS FLOOR ON EVERY LAPTOP, which is why it read as cramped rather
     than tight. 3vh only clears 24px until the viewport passes 800px tall, so the
     clamp's 2.5rem maximum was doing nothing on the screens this is looked at on;
     the heading sat 38px into the band with the fixed header 84px above it.

     THE NOTE BELOW STILL GOVERNS: this group has to clear the top of his head,
     which sits at a fixed offset down the scaled photograph rather than a
     proportional one. So the new value is still a clamp with a ceiling rather than
     a percentage that keeps growing, and all three posters (#consecration, #night,
     the home page's record) were re-checked at 1440x800 and 1440x900 after it. */
  padding-block:clamp(2.5rem,6vh,4.5rem) clamp(1.75rem,4vh,3rem)
}
/* THE HEAD HAS TO CLEAR HIS HEAD, and the room for it is fixed rather than
   proportional. With object-position at 0 the top of his head lands at
   260 x (viewport width / 1600) pixels down the band — 234px at 1440 — because
   that offset is measured on the scaled image, not on the band. So this group
   gets a tight top padding and no more air than it needs. At narrower desktop
   widths that gap closes, and the title will graze the top of his HAIR rather
   than his face, which is the part of the trade worth accepting. */
.poster-head{
  display:grid; gap:clamp(.7rem,1.3vw,1.1rem); justify-items:start; width:100%
}
/* Hardcoded ivory rather than var(--ink): on Covenant --ink is a dark blue and
   this ground is a near-black photograph on ALL THREE directions. */
/* font-weight IS EXPLICIT BECAUSE THIS BECAME AN <h2> on 2026-08-14. As a <p> it
   inherited the body's regular weight, which is what was looked at and approved;
   promoting it to a heading would have silently made it bold. Same reason the
   margin is stated. */
.poster-t{
  margin:0; color:#F7F1E6; font-weight:400;
  font-size:clamp(2.25rem,1rem + 5.5vw,5.5rem); line-height:1;
  letter-spacing:-.03em; text-transform:var(--display-case)
}
.poster-s{
  margin:0; max-width:44ch; color:rgba(247,241,230,.88);
  font-size:clamp(1rem,.95rem + .35vw,1.1875rem); line-height:1.55;
  text-wrap:pretty
}
/* TWO CLASSES ON PURPOSE, AND THE ONE-CLASS VERSION WAS A REAL BUG THAT ONLY
   COVENANT SHOWED. `.rail` is declared twice in this file and the second copy
   (line ~2702) comes AFTER this block, so at equal specificity it won and put
   `--ink-faint` back. That reads ivory on the two dark directions, which is why
   it passed there, and dark blue on Covenant — measured at 1.09-1.26:1 against
   a 4.5 bar over a near-black photograph. Found by verify/poster-contrast.js
   running all three directions rather than just the default one. */
/* `.poster-f` and its rules went with the "Final production" rail on
   2026-08-14. Worth one line of history because it cost a measurement: when the
   poster type moved from centred to left, that label crossed a slightly
   brighter column of the frame and WebKit at 390 came back 4.38:1 against a 4.5
   bar while Chromium still passed — the usual sign of a value sitting on the
   line rather than a layout fault. It was fixed by lifting the ink to .84
   alpha, and then the element was cut anyway. */

/* ---- The facts and the ask, below the poster ---------------------------- */
/* ONE COLUMN SINCE 2026-08-16, because it has one child. This was a two-column
   grid holding the record readout and the email ask; Rokas removed the readout
   ("get rid of that on the left, just keep the contact"), and a two-column grid
   with one child leaves the ask in the left half of the frame with the right half
   empty, which is the "space waiting to be filled" fault this project has been
   caught by before. The grid is not kept dormant for a child that no longer
   exists.
   THE FORM IS CAPPED, NOT THE BLOCK. `.ask`'s rule runs the full frame and is
   what closes the page off; the field under it is one email input and would
   stretch to 1368px without this. */
.music-tail{ margin-top:clamp(2rem,3.5vw,3rem) }
.music-tail .ask form{ max-width:32rem }

/* At 900 every one of these grids becomes a single column. Source order is
   already the order a phone should read: title, artwork, plate, his words,
   poster, facts, ask. */
@media (max-width:900px){
  .music-grid,
  .music-tail{ grid-template-columns:1fr; gap:clamp(2rem,6vw,3rem) }
  /* The explicit desktop placement has to be released, or the children stay
     pinned to their named columns in a one-column grid and the rows collide. */
  .music-grid > .music-l,
  .music-grid > .music-r,
  .music-grid > .music-say{ grid-column:1; grid-row:auto }
  .music-say{ max-width:none }
  /* The right column's desktop pinning has to be released too, or the plate and
     the songs hug the right edge of a single-column layout with a gutter of
     dead space to their left. */
  .music-r{ width:100%; justify-self:start }
  .plate{ max-width:none; justify-self:start }
}

/* ---- The field: ONE photographic ground behind the whole composition --------
   AMENDMENT A7.2. Not a band, not an inset, not a letterbox. The quote, the
   rails, the display split and the first ledger rows all sit on it, and the
   ledger's rules break against it instead of spanning the frame.

   Two controls, and they do different jobs, which is why they are not one
   gradient:
     the MASK decides where the photograph exists at all (it fades in under the
       hero's own ground and out before the section ends, so the section has a
       beginning and an ending without a hairline announcing either);
     the SCRIM decides how much of it survives where the type is.

   MEASURED, worst case, precipitation (--ground #0B0A14, sRGB .043):
     a blown-white pixel of the field at brightness(.42) under the 55% plateau
     composites to sRGB .2127 -> relative luminance .0372. Against --ink
     #F7F1E6 (L .866) that is 10.5:1. AA asks 4.5 of the smallest string in the
     design and 3.0 of anything over 24px, so the plateau is safe by 2.3x even
     where the photograph is at its brightest. The frame edges are stronger
     still: at x36 the scrim is >98% ground.
     Re-measured on the shipped composition across all three directions and both
     breakpoints, on RENDERED pixels with the text hidden and the worst pixel in
     each box taken: the tightest string on the page is 7.99:1 against a 3.0
     requirement, and the tightest small string is 8.14:1 against 4.5.

   .42 IS NOT A TASTE SETTING AND IT IS NOT FREE TO LOWER. A/B'd at .42 / .34 /
   .26 and looked at: DIMMING THE FIELD MAKES THE GATE READ MORE LIKE A CARD,
   not less, because the step between the two planes is what the eye reads as an
   edge and dimming widens it. At .42 the field is closest to the aperture and
   the boundary is softest. Anyone tempted to darken this for contrast headroom
   is trading away the one thing the aperture depends on, and the headroom is
   already 2.7x. */
.field{ position:absolute; inset:0; z-index:0; pointer-events:none; overflow:hidden }
/* --field-z IS THE CROP, and it exists because object-position alone cannot
   deliver one. The section box is wider than the frame is tall, so cover
   width-fits and the visible source band is a fixed 55% of the photograph's
   height; a 2:3 portrait's face occupies 21-42% of it and the tabletop 80-100%,
   so a 55% window cannot exclude both and object-position only chooses WHICH
   one you get. Driven at --field-y:70 the jaw sat behind the quotation; at 100
   the ledger's whole ground was tabletop at luma sd 5.2, which is the wash the
   round-3 critic caught. Scaling narrows the window instead of moving it:
   at scale 1.35 with origin at the top edge the band is 40.5% of the source
   and --field-y:94 puts it at 42%-82.5%, below the chin and above the table.
   A transform, never a layout property, and the 1.21x upscale it costs is
   invisible at brightness .28-.42 under the scrim.
   AT 390 THE GOVERNING AXIS FLIPS and so does the origin. The section is
   taller than the photograph is wide, so cover HEIGHT-fits, object-position Y
   stops doing anything, and the whole frame including the face is in view: shot
   at z 1.35 the mobile field was a head twice the size of the aperture's,
   directly behind it. Anchoring the scale to the BOTTOM edge and raising it to
   1.75 leaves the bottom 57% of the frame, which starts below the chin at 43%.
   1.75 also costs no upscale at all here (1571px of a 1600px source). */
.field img{
  position:absolute; inset:0; width:100%; height:100%; max-width:none;
  object-fit:cover; object-position:var(--field-x, 50%) var(--field-y, 50%);
  transform: scale(var(--field-z, 1)); transform-origin:50% var(--field-o, 0);
  filter: brightness(var(--field-dim, .42));
  /* THE PLATEAU RUNS TO 88%, NOT 74%. At 74% the photograph was already fading
     out by the ledger's second row, so the bottom half of the section was a
     flat field with rules across it — the "content on a background" failure
     A7.2 exists to prevent, arriving 400px lower down than the round it was
     written about. Sampled at 74%: luma sd 9-13 under the last two rows, i.e.
     a wash. The fade now starts below the last rule and finishes before the
     section ends, so the section still has an ending and no hairline
     announces it. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 5%, #000 88%, transparent 99%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 5%, #000 88%, transparent 99%);
}
/* Horizontal, because at 1440 the type that needs protecting is at the two
   frame edges and the aperture is in the middle. 22%/78% of 1440 is x317/x1123:
   the whole open plateau sits inside the gate's own column plus the ledger's
   second column, and every rail, label, evidence line and route is outside it. */
.field-scrim{
  position:absolute; inset:0;
  background: linear-gradient(90deg,
    var(--ground) 0%,
    color-mix(in srgb, var(--ground) 55%, transparent) 22%,
    color-mix(in srgb, var(--ground) 55%, transparent) 78%,
    var(--ground) 100%);
}

/* COVENANT — the polarity flip, delivered as a two-plate print rather than as a
   deletion. The field is the same frame printed pale (an impression, the way an
   offset ghost sits under text on uncoated stock); the aperture is the same
   frame printed dark. Both values are tokens the direction already owns, so no
   new colour enters. See note 5 at the top of this file for what was measured
   before this replaced §3.0's "remove L1 and L1b entirely". */
[data-theme="covenant"] .field img{
  filter: grayscale(1) brightness(1.42) contrast(.34);
}
[data-theme="covenant"] .field-scrim{
  background: linear-gradient(90deg,
    var(--ground) 0%,
    color-mix(in srgb, var(--ground) 72%, transparent) 24%,
    color-mix(in srgb, var(--ground) 72%, transparent) 76%,
    var(--ground) 100%);
}

/* ---- The rig ---------------------------------------------------------------
   1440x900: 523.8 | 306.0 | 523.8, gaps 7.2. Four vertical lines come out of it
   and every surface in every section lands on one of them: x36 (frame), x559.8
   (left column's right edge — §2's spine), x567.0 (gate left, where every
   outcome and every display-half-B starts), x1404 (frame). All four driven and
   read back off the running page, in Chromium and WebKit. */
.rig{ display:grid; grid-template-columns: 1fr var(--gate-w) 1fr; gap: var(--gap);
      align-items:center; position:relative; margin:0 }

/* ---- The display split -----------------------------------------------------
   6.6vw = 95.04px @1440, capped at 10.6svh so a short window shrinks it (note 4
   at the top of this file). Weight 400 — there is no 200 in the kit and faking
   one with alpha breaks AA. Tracking 0, NEVER the hero's --display-tracking:
   tightening Atkinson removes the exact property it was chosen for.

   RE-MEASURED AT THE FOLD-DERIVED GATE WIDTH, which gave each side column back
   19.8px (504.0 -> 523.8). Real face loaded, control string "The Greatest"@100px
   = 568.50px in both Chromium and WebKit, matching the architecture's recorded
   ceiling exactly, so the ruler is honest before anything is measured with it.
   Read back off the running page at 95.04px in the 523.8px column:
     "He brought" 10ch = 467.6px = 89.3% of the column
     "me to"      5ch = 240.0px = 45.8%
     "the table." 10ch = 387.5px = 74.0%
   Every line inside the 11-character ceiling, none over its column, no reflow.
   At 390 the halves shrink-wrap to 184.2px and 152.6px in a 350px frame (the
   mobile size and why it is under §3.1's are at the .dhalf override below).

   THIRD TERM: THE MEASURE CAP, and it fixes a defect that was documented in
   room.html and escalated rather than fixed. Above 1472px of viewport width
   --maxw caps the frame at 1400 while --gate-h keeps growing with svh, so the
   TYPE grows while the COLUMN IT HAS TO FIT IN SHRINKS, the 11-character rule
   breaks, and the display line wraps inside .dline{overflow:hidden} — which
   costs the rig its signature move, identical baselines either side of the gate.
   MEASURED by rect count per line (a Range over a wrapped line reports the
   union of its boxes, which is narrower than the column and reads as a PASS, so
   the control has to be rect count, never width), both engines, before:
     1366x768 [1,1,1] ok   1440x900 [1,1,1] ok   1600x900 [1,1,1] ok
     1728x1117 [2,1,1] WRAPS (the 16in MacBook Pro's default scaled resolution)
     1920x1080 [2,1,1] WRAPS   2560x1440 [2,2,1] WRAPS twice
   5.30 is measured, not chosen. Real face loaded, both engines agreeing to four
   decimals, control string "The Greatest" = 5.685em confirming the ruler:
     "These don't" 5.1431em  <- the widest display line in the architecture (§3.2)
     "He brought"  4.9200em      §3.1        "He speaks." 4.7770em    §3.3
   The cap clears the widest of those with 3% of headroom, which is what absorbs
   100vw counting a classic scrollbar the frame's own 100% does not. It changes
   NOTHING at or below 1600: the cap there is 96.2 / 98.8 / 101.7px against a vw
   term of 81.4 / 95.0 / 95.4, so the existing terms still govern. */
.dhalf{ font-size:min(6.6vw, calc(10.6 * var(--rig-v)), calc(var(--col-w) / 5.30));
        font-weight:400; letter-spacing:0; line-height:1.06;
        text-transform:none; margin:0; display:flow-root;
        position:relative; z-index:1 }
.dhalf-a{ text-align:right }
/* Right half carries ONE line against the left half's two, and the sentence has
   to read straight across the gate. A one-line-height pad drops it onto the
   left half's SECOND baseline, so "me to" and "the table." share a baseline and
   both halves occupy an identical two-line box that centres identically. */
.dhalf-b{ text-align:left; padding-top:1.06em }

/* Each line is its own clip window so the entrance rises inside a line box
   rather than fading. Padding opens the window past the 1.06 line box so
   ascenders and descenders are never shaved; the matching negative margin
   keeps the outer geometry byte-identical. */
.dline{ display:block; overflow:hidden; padding:.12em 0 .18em; margin:-.12em 0 -.18em }
/* Those negative margins escape any holder that is not its own formatting
   context and quietly eat the section's top padding (measured: 79.20px of
   padding rendering as 75.05px). Anything holding a .dline needs flow-root. */
.dhold{ display:flow-root }
.dline > span{ display:block }

/* ---- The gate --------------------------------------------------------------
   312.0 x 468.0 @1440x900, 2:3. The ratio is fixed and the height is spent out
   of the fold budget declared at --gate-h, so the window can only ever be as
   tall as the section's commercial job leaves room for. */
.gate-cell{ position:relative; z-index:1; display:block; width:var(--gate-w);
            justify-self:center }
.gate{ position:relative; z-index:2; display:block;
       width:var(--gate-w); height:var(--gate-h);
       border-radius:3px; overflow:hidden; background:var(--ground-2) }
.gate img{ width:100%; height:100%; max-width:none; object-fit:cover;
           object-position:50% var(--gate-y, 50%);
           filter: brightness(var(--gate-b, 1));
           transform:scale(var(--gate-z, 1)); transform-origin:50% 0 }

/* The gate has to belong to the direction it is shown in. The field sits under
   a --ground scrim and so takes the direction's temperature; the gate has none,
   so without this the two planes read as two unrelated photographs instead of
   two frames of one shoot. FOUND BY LOOKING at banquet, where a warm brown
   field held a cold neutral plate. A 12% veil of the direction's own ground
   settles the family on both dark grounds. */
.gate::after{ content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
              background: color-mix(in srgb, var(--ground) 12%, transparent) }
/* Covenant: the other plate. brightness/contrast first so the frame keeps its
   modelling instead of crushing to a silhouette, then 30% of the direction's
   own INK so the plate is indigo-black rather than neutral-black on an ivory
   page. It overrides --gate-b entirely: on the two dark directions the gate is
   the field with the veil lifted, on covenant it is the field's negative, which
   is what a two-plate print is. */
[data-theme="covenant"] .gate img{ filter: grayscale(1) contrast(1.5) brightness(.4) }
[data-theme="covenant"] .gate::after{ background: color-mix(in srgb, var(--ink) 30%, transparent) }

/* Four mitred corner brackets. 0.8vw = 11.52px, sitting OUTSIDE the gate's
   corners at -0.85vw so their inner ends stop just short of it. --ink at full
   opacity (see note 2 at the top of this file for the measurements that moved
   them out and took the .6 off).
   They open outward on hover of the GATE CELL — done as a translate, never as
   an inset, because inset is a layout property and this project has already
   paid for animating three of those.
   They clear the display type because the display block is two lines centred in
   a much taller gate: at 1440x900 the type occupies the middle 201px of a 518px
   gate, so a corner mark sits 159px above or below the nearest glyph row. */
.bkt{ position:absolute; z-index:3; width:.8vw; height:.8vw; color:var(--ink);
      pointer-events:none; transition: transform .6s var(--ease-respond) }
.bkt svg{ display:block; width:100%; height:100% }
.bkt-tl{ top:-.85vw; left:-.85vw }   .bkt-tr{ top:-.85vw; right:-.85vw }
.bkt-bl{ bottom:-.85vw; left:-.85vw } .bkt-br{ bottom:-.85vw; right:-.85vw }
.gate-cell:hover .bkt-tl{ transform: translate(-.3vw,-.35vw) }
.gate-cell:hover .bkt-tr{ transform: translate( .3vw,-.35vw) }
.gate-cell:hover .bkt-bl{ transform: translate(-.3vw, .35vw) }
.gate-cell:hover .bkt-br{ transform: translate( .3vw, .35vw) }

/* ---- The rails -------------------------------------------------------------
   12px flat, never scaled by vw (0.76vw is 3.3px at 390). Uppercase is applied
   by CSS and the SOURCE stays in sentence case, so no all-caps sentence exists
   in the markup and a screen reader is never handed shouting. Fragments only.

   PINNED TO THE FRAME'S EDGES: two fragments, space-between, so the metadata
   brackets the frame instead of implying a grid, and it transposes rather than
   collapses at 390 where the label pair still holds both edges of a phone. */
.rail{ font-size:12px; line-height:1.5; text-transform:uppercase; letter-spacing:.16em;
       font-variant-numeric:tabular-nums; color:var(--ink-faint); margin:0 }
.rail-v{ letter-spacing:.05em }
.rig-rail{ display:flex; justify-content:space-between; align-items:baseline; gap:1rem }
.rig-rail > *{ overflow:hidden }
.rig-rail > * > span{ display:inline-block }
/* svh-capped like everything else above the ledger (note 4). Tightened from
   3.4 to 2.5 screen-height units: at 1440x900 that is 22.5px of air under a
   12px rail, still above the rail's own 18px line box, and it buys 16px of the
   fold budget across the pair. */
.rig-rail-t{ margin-bottom: min(1.8vw, calc(2.5 * var(--rig-v))) }
.rig-rail-b{ margin-top:    min(1.8vw, calc(2.5 * var(--rig-v))) }

/* ---- Entrance --------------------------------------------------------------
   Two beats, and that is the whole motion budget for a section. Display halves
   rise a line inside their clip windows with OPACITY FIXED AT 1 (no cross-fade);
   rails rise one line-height on the same curve, 120ms later. Rows never
   stagger — eight individually animated rows is the AI tell.

   The pre-state is scoped to .anim, which the piece JS sets synchronously and
   only when motion is allowed AND the section is below the fold at load. A
   visitor who arrives by the skip link or by a #story deep link lands on the
   end state, not on 800ms of empty photographic band. */
.anim .dline > span      { transform: translateY(1.1em) }
.anim .rig-rail > * > span{ transform: translateY(100%) }
/* The transition is armed ONE FRAME AFTER the pre-state, never with it. Measured:
   with it, adding .anim transitioned the type 0 -> 1.1em, so the display line slid
   DOWN into its hidden position over 800ms on load and only then rose. Below the
   fold nobody sees it, which is exactly why it would have survived to integration. */
.anim-ready .dline > span      { transition: transform .8s var(--ease-reveal) }
.anim-ready .rig-rail > * > span{ transition: transform .64s var(--ease-reveal) .12s }
.anim .is-in .dline > span      { transform:none }
.anim .is-in .rig-rail > * > span{ transform:none }

@media (prefers-reduced-motion: reduce){
  .dline > span, .rig-rail > * > span, .bkt{ transition:none }
}

/* ---- Entrance, part two: the half the piece JS never covered ---------------
   Added 2026-08-15. The entrance above is armed by the piece JS, and that JS is
   bound to #story, #messages and #speaking. The last two came off the HOME page
   on 2026-08-15 when the ministry half was rebuilt, so #ministryturn,
   #ministrywhy, #watch and #contact — about half the document — arrived with no
   motion at all, directly below a section that opens on a scrubbed
   per-character reveal. The page got quieter the further down it went, which is
   backwards: the turn into ministry is the most important moment on it.

   NO NEW VOCABULARY, and that is the point. Same two beats, same curve, same
   budget: the display line rises one line inside its clip window with OPACITY
   FIXED AT 1, the rail rises one line-height 120ms later, and nothing else in
   the section moves. Three films in the watch grid staggering in one after
   another is exactly the tell the rows rule above already names, so the grid,
   the pull-quote and the readout are deliberately still.

   ---------------------------------------------------------------------------
   `.rise` WAS THE CLIP-WINDOW SLIDE AND IT IS GONE, 2026-08-17. Rokas, having
   kept Atkinson: "it just looks very placed there, so maybe if we animate the
   paragraphs to come in nicer but keeping it subtle and aesthetic... have a nice
   subtle animation on all the headlines also to keep it consistent throughout."

   WHAT WENT: `overflow:hidden` on the heading with its text translated a full
   1.1em inside it (rails a full 100%), on `--ease-reveal`, which is a
   cubic-bezier with a zero third control point — a hard, mechanical arrival. It
   read as a departures board, and it is why he asked for the ministry page's
   header animations to be removed EARLIER THE SAME DAY. Four of those in a
   column is a performance, not a page.

   WHAT REPLACES IT: `.ar`, opacity 0 -> 1 over a 14px rise. No clip window, so
   no padding/margin pair is needed to buy ascender room and no element's box is
   touched. The whole reason this can now run on every section of every page,
   which is the "consistent throughout" half, is that it is quiet enough to.

   THE CURVE IS ITS OWN TOKEN AND NOT `--ease-reveal`. `--ease-arrive` is a plain
   ease-out that decelerates into place; --ease-reveal snaps. Reusing the snap at
   a smaller distance would have kept exactly the quality he asked to lose.

   THE STAGGER IS THE POINT OF `--ar-i`. js/rise.js numbers each element in a
   section — rail, heading, then paragraphs in source order — and the delay is
   that index times 60ms, so a band lands in reading order rather than as one
   block fading. The index is capped at 6 in the JS, which bounds the longest
   passage on the site (#first, eight paragraphs) at 360ms behind its heading.

   OPACITY AND TRANSFORM ONLY, both compositor properties, so a section arriving
   costs no layout and no paint. That was true of the old slide too and is the
   one thing worth carrying over. */
.ar{ will-change: opacity, transform }

/* Pre-state, armed one frame before the transition is allowed. Both steps are
   needed: adding the class and the transition in the same frame would animate the
   element INTO its hidden state on the way down. */
.anim .ar{ opacity:0; transform:translate3d(0,14px,0) }
/* COLOUR IS IN THE LIST TOO, 2026-08-17, and it is the fix for a hover that
   snapped. An armed element carries THIS transition, not its own, so a `.who-say`
   line that also reveals had no colour transition at all and its warm hover jumped
   rather than faded (Rokas: "too rugged ... make it gradient over smoothly"). The
   reveal never changes colour, so adding it costs the entrance nothing; it is given
   no stagger delay so a hover answers at once rather than after the element's turn
   in the cascade. */
.anim-ready .ar{
  transition: opacity .68s var(--ease-arrive) calc(var(--ar-i, 0) * 60ms),
              transform .68s var(--ease-arrive) calc(var(--ar-i, 0) * 60ms),
              color .45s var(--ease-smooth);
}
/* `will-change:auto` ONCE ARRIVED, 2026-09-10. `.ar` sets `will-change:opacity,
   transform` and nothing cleared it in the settled state, so Safari kept the
   paragraph on its own compositing layer and rendered it with grayscale (heavier-
   looking) antialiasing for a few seconds before collapsing the idle layer, which
   is the "bold for a few seconds then back to normal" flash Rokas saw on the music
   lede. Chromium never subpixel-antialiases text on macOS, so it never showed.
   Dropping the hint at rest lets the layer collapse the moment the fade ends. The
   reduced-motion path already did this (below). */
.anim .is-in .ar{ opacity:1; transform:none; will-change:auto }

/* THE GUARANTEE, and it is the same one the JS relies on: the finished state is
   what the stylesheet renders with no classes at all. Reduced motion, no-JS and
   a missing IntersectionObserver all land here. */
@media (prefers-reduced-motion: reduce){
  .ar{ opacity:1 !important; transform:none !important; transition:none !important;
       will-change:auto }
}

/* ---- IMAGES ARRIVING -------------------------------------------------------
   Added 2026-08-17, the companion to `.ar` above and driven by js/img-rise.js.
   Rokas: "the images to come in nicely in a premium way and not over the top."

   It is `.ar` for pictures, tuned richer: a 26px rise plus a .985 -> 1 settle
   over 0.9s, where the type takes 14px over 0.68s. A photograph settling can
   carry more travel than a line of type without reading as a mechanism, and the
   slower curve is what makes it read as premium rather than as motion. Same
   `--ease-arrive`, so the two reveals feel like one hand.

   THE SCALE GROWS FROM SMALLER (.985 -> 1), which is the whole reason no frame
   needs `overflow:hidden`: it never spills its box on the way in. Opacity and
   transform only, both compositor properties, so a frame arriving costs no
   layout. Which frames get this, and which full-bleed heroes deliberately do
   NOT, is decided in js/img-rise.js. `--ir-i` is the per-group stagger index the
   script sets, capped there. */
.ir{ will-change: opacity, transform }

.imganim .ir{
  opacity:0;
  transform: translate3d(0, 26px, 0) scale(.985);
}
.imganim-ready .ir{
  transition: opacity .9s var(--ease-arrive) calc(var(--ir-i, 0) * 90ms),
              transform .9s var(--ease-arrive) calc(var(--ir-i, 0) * 90ms);
}
/* Final state is a class deeper than the pre-state, so it wins on specificity
   regardless of source order — an already-arrived frame stays put while `.imganim`
   is being added around it. */
.imganim .ir.ir-in{ opacity:1; transform:none }

@media (prefers-reduced-motion: reduce){
  .ir{ opacity:1 !important; transform:none !important; transition:none !important;
       will-change:auto }
}

/* THE ABOUT INTRO PORTRAIT'S ON-LOAD ENTRANCE, 2026-08-19. The one frame on the
   site that arrives above the fold WITH motion, because Rokas asked the about image
   to "animate in smoothly" and it is always the first thing on the page. It is
   excluded from the `.ir` scroll reveal (js/img-rise.js skips `.ab-intro-img`) and
   driven by its own two classes, which js/img-rise.js toggles once its bitmap is
   ready. The effect matches `.ir` exactly, so the site has one arrival language. */
.ab-rise{ opacity:0; transform:translate3d(0,26px,0) scale(.985) }
.ab-rise.ab-rise-in{
  opacity:1; transform:none;
  transition:opacity .9s var(--ease-arrive), transform .9s var(--ease-arrive)
}
@media (prefers-reduced-motion: reduce){
  .ab-rise, .ab-rise.ab-rise-in{ opacity:1; transform:none; transition:none }
}

/* ---- 390 -------------------------------------------------------------------
   The rig collapses to a flex column and THE SPLIT POINT IS IDENTICAL: first
   display half above the gate, second half below it. It does not collapse to
   "title, then image". The field does not collapse either — it is still one
   photograph behind the whole section; only the scrim turns 90 degrees with the
   rig, because at 350 of 390 there are no side margins to hide type in and the
   type that needs protecting is above and below the aperture instead. */
@media (max-width:640px){
  :root{
    --gap:.5rem;
    /* 2:3 here too, and derived from the same fold budget as the desktop gate
       rather than from §3.1's 66vw x 99vw, which measured 257.4 x 386.1 and put
       the credential 190px under the first screen at 390x844.
       MEASURED at 390x844: 72vw = 280.8 and 32.5svh = 274.3, so the svh term
       binds and the gate is 182.8 x 274.3. The ratio is never what gives. */
    --gate-h: min(72vw, calc(32.5 * var(--rig-v)));
    --gate-w: calc(var(--gate-h) / 1.5);
  }
  .rig{ display:flex; flex-direction:column; align-items:center; gap:3vw }
  /* 9.6vw = 37.4px @390, against §3.1's 13.6vw = 53.04px. RECORDED RATHER THAN
     SILENT, because the last round dropped it to 11vw and said nothing: 13.6vw
     costs 34px of vertical above the ledger and the section's stated job at
     390x844 is decided by roughly that margin. Every line holds the
     11-character measure at either size ("He brought" reads back at 184.2px at
     9.6vw in a 350px frame), so the deviation buys fold budget and costs
     nothing structural. */
  .dhalf{ font-size:min(9.6vw, calc(4.8 * var(--rig-v))); text-align:center }
  .dhalf-b{ padding-top:0 }
  .bkt{ width:9px; height:9px }
  .bkt-tl{ top:-11px; left:-11px }    .bkt-tr{ top:-11px; right:-11px }
  .bkt-bl{ bottom:-11px; left:-11px } .bkt-br{ bottom:-11px; right:-11px }
  .gate-cell:hover .bkt-tl,.gate-cell:hover .bkt-tr,
  .gate-cell:hover .bkt-bl,.gate-cell:hover .bkt-br{ transform:none }
  /* THE FIELD IS DIMMER HERE AND THE PLATEAU IS LONGER, and the two go together.
     At 390 the section is taller than the photograph is wide, so cover
     height-fits and the frame's bottom fifth — the white tabletop, source luma
     mean 199 — lands behind the ledger and the bottom rail. Measured at
     --field-dim .42 with the plateau running to 58%, the two 12px rail
     fragments came back at 4.29 and 4.39:1 against a 4.5 requirement.
     Shortening the plateau to 49% fixed the rails and left the whole mobile
     ledger on flat ground at luma sd 2.7, which is the "content on a
     background" A7.2 exists to prevent. Buying the AA with exposure instead
     keeps one photograph behind the whole mobile section AND clears 4.5, and
     the gate/field step it costs is smaller here because the gate is 183px
     wide rather than 306. */
  /* The crop itself is the section's payload and is set in the piece, because a
     rule here loses the cascade to it: _rig.css is linked before the piece's own
     <style>, and both selectors are #story. Shot before this was noticed, the
     mobile field was a head twice the aperture's size. */
  .field-scrim{
    background: linear-gradient(180deg,
      var(--ground) 0%,
      color-mix(in srgb, var(--ground) 50%, transparent) 19%,
      color-mix(in srgb, var(--ground) 50%, transparent) 74%,
      var(--ground) 92%);
  }
  /* Covenant does not read --field-dim (its plate is a filter of its own), so
     its AA is bought in the scrim instead. MEASURED at 68%: the bottom rail
     came back 4.41 and 4.42:1 against 4.5. */
  [data-theme="covenant"] .field-scrim{
    background: linear-gradient(180deg,
      var(--ground) 0%,
      color-mix(in srgb, var(--ground) 78%, transparent) 19%,
      color-mix(in srgb, var(--ground) 78%, transparent) 74%,
      var(--ground) 92%);
  }
  .rig-rail-t{ margin-bottom:3vw }
  .rig-rail-b{ margin-top:3vw }
  /* ONLY THE BOTTOM RAIL STACKS, and the rule is scoped to the pair that was
     measured. MEASURED at 12px/+0.16em in a 350px frame: the bottom pair is
     169.8 + 188.1 = 357.9 and genuinely does not fit, and the alternative last
     round was dropping ", Australia" off a page for an international speaker.
     The top pair is 144.6 + 62.9 = 207.5 with 142.5px of slack and was being
     stacked by a rule written for the other one, which broke the
     four-professions/one-job pairing for nothing. */
  .rig-rail-b{ flex-direction:column; gap:.2rem }
  .rig-rail-b > *:last-child{ align-self:flex-end; text-align:right }
}

/* ---------- #story — the ledger (pieces/ledger.html) ---------- */
/* ============ THE PIECE. Everything below this line is what gets integrated. ============ */
/* #story — the ledger. ARCHITECTURE §3.1 + AMENDMENT A4 + A7.
   The rig itself (frame, grid, gate, brackets, rails, display split, the
   full-bleed field + scrim, easings, entrance) is in _rig.css because three
   later pieces inherit it. Only what #story owns lives here. */

/* TWO FRAMES, NOT ONE FILE (A7.1). The window has to reveal a second plane or it
   is a filter, and the reference shows a DIFFERENT image in the gate from the
   one behind it (reference-capture.md, measured).

   THE FIELD is editorial-apricot.jpg, 1600x2399. THE GATE is editorial-rose.jpg,
   1600x2398, exactly 2:3 so the aperture crops it by nothing. Same shoot, same
   terracotta backdrop, same warm family: one plane veiled by the scrim and one
   not, which is the relationship the bar has. Sampled on the bar itself
   (verify/ref/bergh-desktop-01.png, composited pixels, 34px strips inside and
   outside all four gate edges) the step is UPWARD on every edge and small,
   +9.7 to +32.8 of 255, and both planes are the same blue-grey. His gate is not
   a brighter photograph laid on a darker one, it is the same world with the veil
   lifted, which is why it needs no border, no shadow and no glow — none of which
   we are allowed to add either.
   portrait-rainbow-knit was the other same-backdrop candidate and is out on the
   colour cap: the rainbow is allowed once and §3.3 spends it in the record gate.

   --gate-b   The gate is exposed like the field, not like a print, so the whole
              step between the planes is made by the scrim's absence over it.
   --field-y / --field-z / --field-o  the crop. The section box is wider than the
              frame is tall, so cover width-fits and object-position alone can
              only choose which half of a 2:3 portrait you get; the mechanism and
              the arithmetic are at .field img in _rig.css. 94% + 1.35 lands the
              visible band at source 42%-82.5%: below the chin, above the
              tabletop, so the field is arms, chain, tank and the table edge, the
              display line is "He brought me to the table.", and the only face in
              the section is the one in the aperture. --field-x is unused because
              the width-fitted axis makes it inert; --gate-y and --gate-z stay as
              the rig's contract with the three sections that inherit it. */
#story{ --field-y:94%; --field-z:1.35; --field-o:0; --gate-y:50%; --gate-z:1; --gate-b:.5 }

/* ---- A4: his own words lead the page ---------------------------------------
   Full frame width above the rig, svh-capped like everything else above the
   ledger. His sentence is not shortened to fit the 11-character display
   measure; a client's sentence does not get edited to fit a grid, so it is set
   as a quotation and the rig's display split underneath is unchanged.
   MEASURED ON THE RUNNING PAGE, not from the source value: at 1440x900 the svh
   term binds and the quotation renders at 30.6px, where its 46 characters set
   625px of a 1368px frame. One line at 1440, two at 390.

   THE ATTRIBUTION SITS ON THE QUOTE'S OWN BASELINE, to its right. It was a
   block beneath the quote and read as a second rail, because .epi figcaption
   and .rig-rail were byte-identical on size, case, tracking and colour. On the
   same line, adjacent to the words it attributes, its role is unambiguous and
   it costs 32.4px less of the vertical budget than a line of its own. It keeps
   the leading rule that marks an attribution and sits at --ink-dim against the
   rails' --ink-faint. At 390 the quote fills the line and the caption wraps
   under it. */
.epi{ display:flex; flex-wrap:wrap; align-items:baseline;
      column-gap:1.15rem; row-gap:.5rem;
      margin:0 0 min(1.9vw, calc(2.6 * var(--rig-v))) }
.epi blockquote{ margin:0 }
.epi p{ margin:0; font-size:min(2.4vw, calc(3.4 * var(--rig-v)));
        line-height:1.24; letter-spacing:0; font-style:italic; color:var(--ink) }
.epi figcaption{ display:flex; align-items:center; gap:.7rem; color:var(--ink-dim) }
.epi figcaption::before{ content:""; flex:none; width:1.75rem; height:1px;
                         background:var(--line-strong) }

/* ---- The ledger table ------------------------------------------------------
   It sits BELOW the composition and does not straddle the gate: a headline
   survives one leap across the aperture, a four-row table does not.

   IT IS BUILT ON THE SPINE, which §2 stakes the whole "one instrument" argument
   on and which the last round did not build: the discipline label is right
   aligned and ends on --col, the same line display half A ends on, and the
   outcome starts on the gate's own left edge, the same line display half B
   starts on. One vertical seam runs from the quotation to the last rule, and
   the four labels hang off it with a ragged left edge set by their own lengths.

   THE CLAIM AND ITS PROOF ARE ONE BLOCK IN COLUMN 2, which is §3.1's own row
   and is not what the last round built. Built the alternative too (evidence at
   the frame's left edge under the label) and shot both: it puts the proof 530px
   from the claim it proves and the row reads as two disconnected fragments
   zigzagging across the frame. Stacked on the spine it reads as one entry.

   THE RULES RUN THE FULL FRAME WIDTH, x36 -> x1404. They stopped 511px short of
   it last round to leave "open photograph" in the last third; sampled on the
   rendered screenshot that band came back at luma sd 9-13 across all three
   directions, which is a wash, not a photograph. Edge-to-edge hairlines are
   also what the bar's LIST view does (reference-capture.md: "one row per work,
   edge to edge, hairline rule between rows"). The field runs behind them, so
   they break against a photograph instead of spanning a flat field, which is
   the thing A7.2 actually asks for. */
.ledger{ margin-top:min(1.9vw, calc(2.6 * var(--rig-v))) }
.lrow{
  position:relative;
  display:grid; grid-template-columns: var(--col) 1fr;
  column-gap:var(--gap); row-gap:.4rem;
  align-items:baseline; padding-block:1.15vw
}
.ledger::before, .lrow::after{
  content:""; position:absolute; left:0; right:0;
  height:1px; background:var(--line)
}
.ledger{ position:relative }
.ledger::before{ top:0 }
.lrow::after{ bottom:0 }

/* Explicit placement rather than DOM order, so the source stays
   label -> claim -> proof -> route, which is the order the row reads in at 390
   where it is one column. */
.lrow-label{ grid-column:1; grid-row:1 }
.lrow-out  { grid-column:2; grid-row:1 }
.lrow-ev   { grid-column:2; grid-row:2 }

.lrow-label{
  margin:0; text-align:right; font-size:12px; line-height:1.5;
  text-transform:uppercase; letter-spacing:.16em;
  /* --ink-dim, not the rails' --ink-faint. MEASURED on rendered pixels: at
     --ink-faint the covenant label came back 4.59:1 against a 4.5 requirement,
     which is a pass with 0.09 of margin and no room for a token nudge later. */
  font-variant-numeric:tabular-nums; color:var(--ink-dim)
}
/* THE RATIO IS THE FIX HERE. The largest string in the row was once the
   unsigned placeholder (37.44px) and the strongest credential in the brief was
   the smallest (14px) — 2.67x in favour of copy we wrote over a fact he owns.
   Now: outcome 2.0vw = 28.8px, evidence 17px, a ratio of 1.69x.
   The four outcomes stay LEFT-aligned on the gate's edge, all four starting on
   the same x, because they are punctuated as one sentence down the column and a
   ragged-left stack would read as four separate items instead. */
.lrow-out{ margin:0; font-size:max(21.25px, 2vw); font-weight:400; letter-spacing:0; line-height:1.1 }
/* MEASURED at 17px: the claim's column is 837px, which would set the credential
   as one 110-character line, so the measure is capped at 33em = 561px and it
   sets two lines of about 68. Driven at 1440, the longest evidence line ends at
   x1127.9 in a frame that ends at x1404, and the rendered-pixel worst case for
   it is 8.4:1 against a 4.5 requirement on all three directions. */
.lrow-ev{ margin:0; max-width:33em; font-size:17px; line-height:1.45; color:var(--ink) }

/* ---- The routes, INSIDE the sentence ---------------------------------------
   §3.1: "Rows 2 and 3 are <a>s. Their evidence line ends in an arrow." So there
   is no route column; each route is the last clause of its own evidence
   sentence, and the three arrows sit at three x positions set by three sentence
   lengths instead of stacking into a column of identical affordances.
   LAW carries none. An unavailable route is marked by carrying LESS metadata,
   never by a disabled control.

   TWO THINGS THAT WERE WRONG AND ARE THE ONLY CONVERSION SURFACE IN THE
   SECTION, SO NEITHER IS COSMETIC:

   (a) THE DOOR WAS QUIETER THAN THE WALL. The route was --ink-dim inside a
   sentence set at --ink, same size, same weight, no underline: measured on
   rendered glyph pixels against its own surrounding body text, 1.66-1.98:1 on
   all three directions, against the 3:1 that WCAG 1.4.1's colour-only-link
   exemption requires. Hover recovered it and there is no hover at 390, which is
   also the breakpoint where the header nav is display:none. So the route is now
   FULL --ink with a 1px underline: a non-colour distinction that does not
   depend on a pointer, and the strongest thing in its own sentence rather than
   the faintest. The arrow is inline-block, so text decoration does not
   propagate into it and it stays an arrow rather than an underscored glyph.

   (b) THE WHOLE 1368px ROW WAS THE TARGET. A stretched ::after made three rows
   navigate and one inert with nothing to tell them apart, cursor:auto across
   92% of the area, and clicking the identically-set phrase "a right you can
   name," did nothing. The stretch is deleted: the link is the link. What it was
   protecting against — a 157-character accessible name — is solved by the route
   being the anchor in the first place. Vertical padding on the inline box gives
   a 45px touch target at 390 without moving the line box or the underline. */
.rlink{
  color:var(--ink); white-space:nowrap;
  text-decoration-line:underline; text-decoration-thickness:1px;
  text-underline-offset:.2em;
  padding-block:.72rem;
  transition: text-decoration-thickness .45s ease-out
}
.arw{ display:inline-block; transition: transform .6s var(--ease-respond) }
.rlink:hover, .rlink:focus-visible{ text-decoration-thickness:2px }
.rlink:hover .arw, .rlink:focus-visible .arw{ transform:translateX(4px) }

/* The `.express` "go straight to the booking" line was removed 2026-08-19
   (parts/story.html) as a duplicate of the "Get in touch" button; its styles,
   here and in the max-width:640 block below, went with it. */

@media (max-width:640px){
  /* THE GOVERNING AXIS FLIPS HERE. At 390 the section is taller than the
     photograph is wide, so cover HEIGHT-fits, object-position Y stops doing
     anything, and the whole frame including the face comes into view. Anchoring
     the scale to the bottom edge and raising it to 1.75 leaves the bottom 57%
     of the source, which starts below the chin at 43%: arms, chain, tank, the
     table. It also costs no upscale at all (1571px of a 1600px source).
     --field-dim is in _rig.css, with the AA measurements that set it. */
  #story{ --field-z:1.75; --field-o:100% }
  .epi{ margin-bottom:3vw; column-gap:.9rem; row-gap:.4rem }
  .epi p{ font-size:4.6vw }                   /* 17.94px @390, two lines */
  .ledger{ margin-top:3vw }
  /* ONE COLUMN, not §3.1's "84px 1fr" and not the desktop spine. MEASURED: an
     84px label column plus its gutter takes 98px of a 350px frame, which pushes
     the outcome to two lines and the credential to three and makes the first
     row 199px tall — enough on its own to put its route off the first screen.
     Right-aligning the label against a 350px frame would put it 213px from the
     text it labels, so the spine is a desktop fact and does not survive here. */
  .lrow{ display:block; padding-block:.9rem }
  .lrow-label{ text-align:left }
  .lrow-out{ font-size:7.2vw; margin-top:.3rem }   /* 28.08px, one line */
  .lrow-ev{ margin-top:.3rem; font-size:15px; max-width:none }
  /* MEASURED: at .72rem the in-sentence routes give a 41px hit box at 15px here
     against 44px at 17px on desktop, because the box an inline element paints is
     its font's content box, not its line box. .85rem takes it to 45.2px, driven
     and confirmed in both engines. The express line is the same register — same
     ink, same underline, same arrow — and differs only in being a row, so it
     takes its 44px from min-height instead. */
  .rlink{ padding-block:.85rem }
}

/* ---------- #messages — the stream (pieces/stream.html) ---------- */
/* ============ THE PIECE. Everything below this line is what gets integrated. ============ */
/* #messages — the stream. ARCHITECTURE §3.2 + AMENDMENT A1 + A2.
   The rig itself (frame, 3-column grid, gate geometry, brackets, rails, the
   display split and its measure, the two easings, the entrance, focus) is in
   _rig.css and is shared. Only what #messages owns lives here.

   THE ARGUMENT THIS SECTION HAS TO MAKE, in one line: his five professions
   produce counts that REFUSE to partition — ALL 08 / MINISTRY 08 / MUSIC 05 /
   SOCIAL WORK 04 / LAW 03, twenty tags on eight themes. Bergh's filter
   partitions (19+16+7=42, a film is only ever one category) and confirms a
   taxonomy. Ours overlaps and refutes one. The tag column is right-aligned so
   its ragged LEFT edge is a bar chart of that convergence, readable before a
   single parenthesis is parsed.

   ------------------------------------------------------------------------
   THE ONE PLACE THIS DEVIATES FROM AMENDMENT A1, AND IT WAS FORCED BY THE
   FOOTAGE RATHER THAN CHOSEN.

   A1 says the media plate sits BEHIND the lenses, cover-filling the 2:3
   aperture at brightness(.42). The amendment was written before anyone opened
   the files. Measured on disk, then sampled frame by frame at 1fps:

     all four .webm are 720x406 (1.773, i.e. 16:9) and exactly 6.000s
     i-am-chosen           352 KB   caption-free t0-t3, then "UNTIL ONE / NIGHT",
                                    then "IN PORT" over a Gqeberha aerial
     when-heaven-meets-earth 225 KB "ETERNAL WORSHIP / SWELLING" then
                                    "HEAVENLY PRECIPITATION" for half the loop
     the-joy-of-revival    275 KB   captioned
     come-as-you-are       167 KB   captioned

   His captions are BURNED IN, large, all-caps and full-bleed to the 16:9 frame.
   Cover-cropping 720x406 into a 2:3 portrait keeps 270 of 720 px of width, so
   "HEAVENLY PRECIPITATION" renders as "AVENLY PRECIPITA" — a chopped word, in
   the aperture, on every loop. And our 12px lens labels would then sit on top of
   40px burned-in white type: two type systems fighting inside one 345px box.

   Round 1 answered that by stacking a 16:9 box ABOVE the instrument, which is
   the wrong half of A1 to give up: it read as a monitor sitting on a control
   panel, with a hard seam across the window and every control on flat ground.
   The film keeps its native width — mocked at the shipping size and looked at,
   the crop cuts his own words in half — and gives up the rest of A1's letter
   in exchange for all of its substance:
     the plate is the aperture's GROUND PLANE (inset:0), not an object in it;
     the film sits on that plane, dimmed to A1's brightness(.42), and DISSOLVES
       into it, so no seam exists anywhere to read as an edge;
     the rail is set OVER the film as its lower third, which is what the .42 is
       for and what makes the plate a plane rather than a picture;
     the caption and the live readout leave the window for the tail, so what is
       left inside is film and control, and the control is centred in it.
   Everything A1 binds is kept: poster first, video only on intent, never four
   files autoplaying on load, no <video> constructed at all under reduced
   motion, zero layout shift on swap (absolute stack, opacity cross-fade only),
   and no carousel at 390 — where the film is 56% of the window rather than 38%,
   because there the window is as wide as the frame.
   Two things are gained rather than traded: his captions read as whole words,
   and a 16:9 plate on the centre axis is the LIST view's own geometry — Bergh's
   list media measures 355x200.
   ------------------------------------------------------------------------ */

/* THE DIM RAMP, DECLARED ONCE, because "dim in place" is the whole interaction
   and one direction's tokens collapse it.
   MEASURED on rendered pixels, contrast against each direction's own ground:
     precipitation   --ink 17.5:1   --ink-dim 8.77:1   --ink-faint 5.86:1
     covenant        --ink 12.2:1   --ink-dim 6.05:1   --ink-faint 5.70:1
   Covenant's dim and faint are .76 and .74 alpha — two points apart — so on the
   ivory direction pressing a lens produced a change nobody could see. _tokens.css
   is read-only in a piece build, so the ramp is corrected HERE and scoped to this
   section. 70% of --ink over --ground measures 5.06:1: still AA at 12px with 0.56
   of margin, and a visible three-step drop from 12.2. */
#messages{
  --lit:  var(--ink);
  --half: var(--ink-dim);
  --off:  var(--ink-faint);
}
[data-theme="covenant"] #messages{
  --off: color-mix(in srgb, var(--ink) 70%, var(--ground));
}

/* THE ROW PITCH IS DERIVED FROM THE APERTURE, NOT CHOSEN.
   This is the rig's own method: §3.0 sizes the ledger's 9vw row so that four of
   them are exactly the gate's height. Here the aperture starts at the display
   line's top, so it closes on a rule when
       display line + 4 rows == gate height.
   MEASURED at 1440x900: gate 518.4, display line 95.04 x 1.06 = 100.74,
   (518.4 - 100.74) / 4 = 104.4px per row.
   IT IS ALSO A BUG FIX, and the bug was only visible by looking. With auto rows
   at 100.2 the aperture's bottom edge fell 17px INTO row five, which put the two
   bottom corner brackets straight through the words "The Joy of Revival ·
   Filmed, 60 sec" — a decorative mark struck through a real title. Closing the
   geometry lands both brackets in a row's padding instead, against a hairline.
   Every automated check passed on that frame. */
#messages{
  --dline-h: calc(min(6.6vw, calc(10.6 * var(--rig-v))) * 1.06);
  --srow-h:  calc((var(--gate-h) - var(--dline-h)) / 4);
}

/* No .field and no full-bleed photograph. A7.2's hard constraint is written
   against #story, whose job is to BE a photograph; this section's subject is an
   instrument, and the film in the aperture is the only image in it. Putting a
   second photographic plane behind an aperture that already holds moving image
   would put two films on one screen and make neither the subject. */

/* ---- The stream: ONE grid, so the display split, the aperture and the eight
   rows all land on the same six vertical lines the rig derives.
   1440x900: 504.0 | 345.6 | 504.0, gaps 7.2 -> x36, x540, x547.2, x892.8, x900,
   x1404. The tag column's right edge is x540 and the aperture's left edge is
   x547.2, which is §2's spine: three surfaces on one line. */
.stream{
  display:grid; grid-template-columns: 1fr var(--gate-w) 1fr;
  column-gap: var(--gap); position:relative
}

/* The display split. ONE line each side, sharing a baseline, so the sentence
   reads straight across the aperture rather than around it. The rig's
   .dhalf-b top pad exists for #story's two-line left half and is cancelled here.
   MEASURED with the real face loaded at 95.04px, by Range rather than by the
   block's width: "These don't" 11ch = 488.8px in a 504.0px column, 97.0% —
   inside the ceiling but with only 15.2px of air, so the
   §3.2 fallback ("They don't") is not needed and is not used. Any copy change
   here has to be re-measured, not estimated. */
.s-head{ grid-column:1 / -1; grid-row:1; margin:0; display:grid;
         grid-template-columns: var(--col) var(--gate-w) 1fr; column-gap:var(--gap);
         align-items:baseline }
.s-head .dhalf-a{ grid-column:1 }
.s-head .dhalf-b{ grid-column:3; padding-top:0 }

/* ---- The aperture ----------------------------------------------------------
   §3.2: the section whose job is proof puts the CONTROL where the picture was.
   Same box as the other two sections' gates (var(--gate-w) x var(--gate-h),
   2:3) and the same four brackets outside it, so the invariance holds: three
   sections, one composition, three payloads — a photograph, an instrument, a
   number.
   It spans from the display line down through the first rows and floats over
   their empty centre cells, which is what puts the control INSIDE the list it
   governs instead of 500px above it. The row rules run full frame width and
   disappear behind it, exactly as the media does in the reference's LIST view. */
.aperture{
  /* Rows 1-5 = the display line plus four rows, which is exactly --gate-h, so
     the aperture opens on the display line's top edge and closes on the rule
     between rows four and five. align-self:start, never center: centring left
     the closure to whatever the rows happened to measure. */
  grid-column:2; grid-row:1 / span 5; align-self:start; z-index:2;
  height:var(--gate-h); border-radius:3px;
  /* --ground, NOT --ground-2. MEASURED: at --ground-2 (#15132A on a #0B0A14
     page) the aperture rendered as a visibly lighter rectangle with a hard
     edge — a card pasted ON the page rather than a hole THROUGH it, which is
     the exact failure the ledger piece had to diagnose out of its own gate.
     Opaque is still required, because the row rules run full width and pass
     underneath; matching the ground gives opacity with no boundary, so the only
     things marking the aperture are the four brackets and the plate itself.
     That is the reference's grammar: corner ticks, no box. */
  background:var(--ground)
}
/* THE PLATE IS THE APERTURE'S GROUND PLANE, NOT AN OBJECT INSIDE IT.
   Round 1 built it as a 16:9 box stacked ABOVE the controls, which read as a
   monitor sitting on a panel: a hard seam across the window, and every control
   on flat ground with no image plane under it. A1 asks for the plate BEHIND the
   lenses at brightness(.42). So the plate is now inset:0 — it IS the window's
   ground — the film sits flush to its top edge and DISSOLVES into that ground
   with a mask, and the instrument is painted over the plate rather than under
   it. No seam exists to read as an edge.
   --ap-pad is declared once because three children read it. */
#messages{ --ap-pad: clamp(.7rem, 1.15vw, 1.05rem);
           --film-h: calc(var(--gate-w) * 406 / 720) }
.ap-plate{
  position:absolute; inset:0; z-index:0; overflow:hidden; border-radius:3px;
  background:var(--ground)
}
.ap-in{
  position:absolute; inset:0; z-index:1; overflow:hidden; border-radius:3px;
  display:grid; grid-template-rows: var(--film-h) 1fr
}
/* Centred in what the film leaves, so the control reads as the subject of the
   window at every gate height instead of drifting to one end of it. */
.ap-body{
  display:flex; flex-direction:column; justify-content:center;
  gap:clamp(.55rem, .95vw, .9rem); padding: var(--ap-pad); min-height:0
}

/* The film. NATIVE 16:9, never cropped, and this is a MEASURED constraint
   rather than a preference. His captions are burned in, large, all-caps and
   full-bleed to a 720x406 frame; cover-filling a 2:3 window keeps 306 of 814px
   of width, so "HEAVENLY PRECIPITATION" renders as "NLY PRECIPITA" and "UNTIL
   ONE / NIGHT" as "TIL ONE / IGH". Mocked at the shipping size, darkened to
   .42, and LOOKED AT before this was written: chopped words on every loop, at
   the centre of the composition. A 16:9 asset cannot fill a 2:3 window without
   cutting his words in half, so the film keeps its width and gives up its
   height, and the layering does the work the crop cannot.
   Each state is one absolutely-positioned cell in the same place, so a swap is
   an opacity cross-fade and cannot move one pixel. */
/* display:grid, and it is NOT cosmetic. A cell holds a poster AND, once the row
   is pointed at, a <video> built in beside it. As a plain block the two stacked
   VERTICALLY and the film rendered twice down the aperture — one frame in the
   plate and a second one under it, overflowing into the instrument. Every
   automated check passed on that frame: no console error, no overflow, the
   right file playing, the right title in the rail. It was found by reading the
   screenshot. One grid cell puts the poster and its film in the same place, on
   the same pixels, which is also what makes the swap a cross-fade. */
/* THE GRADE AND THE DISSOLVE SIT ON THE CELL, NEVER ON THE TWO CHILDREN.
   With the mask on each child, the video's own transparency at the foot let the
   POSTER UNDERNEATH show through it, so two different frames of the same clip
   were composited in the bottom third — his caption from one moment ghosted
   over his caption from another. Found by reading the screenshot; nothing
   automated could have flagged it. On the cell it is one grade, applied once,
   and the video covers its poster opaquely. */
.pl{ position:absolute; top:0; left:0; width:100%; aspect-ratio:720/406;
     display:grid; overflow:hidden;
     opacity:0; transition:opacity .45s ease-out;
     /* A1's number, and it is load-bearing now that the rail is set over the
        film: it is the same .42 the rig dims its field to, so the two planes in
        the section are graded alike. */
     filter: brightness(.42);
     /* The dissolve. The film reaches the rail's line at a quarter of its
        opacity and is gone by its own foot, so there is no seam anywhere and
        the rail is type on a film plane rather than type on a photograph. */
     -webkit-mask-image: linear-gradient(180deg,#000 0%,#000 56%,rgba(0,0,0,.25) 84%,transparent 100%);
             mask-image: linear-gradient(180deg,#000 0%,#000 56%,rgba(0,0,0,.25) 84%,transparent 100%) }
.pl.is-on{ opacity:1 }
.pl img, .pl video{
  grid-area:1 / 1;
  width:100%; height:100%; object-fit:cover; display:block
}
/* The plate has to belong to the direction it is shown in, the same way the
   rig veils its gate at 12%: without it a cold-lit studio frame sits on a warm
   brown ground as an unrelated photograph. */
.ap-plate::after{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background: color-mix(in srgb, var(--ground) 12%, transparent)
}
/* Covenant keeps the rig's polarity: the aperture is the plate printed dark in
   a bright field, graded exactly as _rig.css grades the ledger's gate, so the
   two sections read as one press run rather than as two decisions. */
[data-theme="covenant"] .pl{ filter: grayscale(1) contrast(1.5) brightness(.42) }
[data-theme="covenant"] .ap-plate::after{ background:none }

/* The film's own rail, set as a lower third ON the film's dissolving foot.
   Left is live and names what is on screen; right is the radiogroup's visible
   label. Two fragments bracketing the frame: the rig's rail grammar, at the
   rig's rail size, carrying real metadata.
   "clip" is load-bearing and not a flourish: the loop is 6.000s and the message
   it is cut from is 75 seconds. The row states the message's real length; the
   rail states that this is not it. */
/* BOTH fragments at full ink, and that is a measured rule rather than a taste:
   type set on an image plane does not get the ramp. MEASURED on rendered pixels
   with the label at --ink-dim, worst pixel under the string: precipitation
   7.39:1, banquet 7.10:1, covenant 2.94:1 — because covenant's plate is the
   direction's DARK plate and a dimmed indigo label lands on mid-grey. At full
   ink the same pixel measures 5.85:1. It is the same move _rig.css makes when
   it takes the 0.6 off the brackets for sitting over a photograph. */
.ap-rail{ align-self:end; display:flex; justify-content:space-between;
          align-items:baseline; gap:1rem; margin:0; color:var(--lit);
          padding: 0 var(--ap-pad) calc(var(--ap-pad) * .75) }
.ap-rail .ap-now{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.ap-rail .ap-scope{ flex:none }

/* ---- The tail: the centre axis does not stop where the window does ---------
   MEASURED on round 1 at 1440x900: the aperture closed at y1485.9, exactly on
   row 4's rule, and the four rows below it ran under a 306 x 358 hole with the
   hairlines crossing it unbroken. So the section's instrument governed the top
   half of the list it exists to govern, and a visitor reading row 8 had no
   filter, no count and no sign the list was filterable anywhere on screen.
   The axis now runs the full height of the list: the window holds the film and
   the lenses, the tail holds what the lenses MEAN — the caption at the top,
   under the counts it explains, and the live readout at the foot, level with
   the last row. It carries the aperture's own ground for the same reason the
   aperture does: the row rules pass behind it, so the channel is one unbroken
   column from the display line to the last theme instead of a gutter with
   rules running through it. No second set of brackets: the brackets mark the
   window, and there is exactly one window. */
.ap-tail{
  grid-column:2; grid-row:6 / span 4; z-index:2; position:relative;
  background:var(--ground);
  display:flex; flex-direction:column; justify-content:space-between;
  padding: calc(var(--ap-pad) * 1.6) var(--ap-pad) var(--ap-pad)
}

/* The five lenses. Real radios in a radiogroup, so arrow-key navigation, the
   roving tab stop and the group's own semantics are the platform's job and not
   ours. 28px pitch. */
.lenses{ display:flex; flex-direction:column; gap:0; margin:0 }
/* 33px pitch, up from §3.2's 28px. Two reasons, both measured: the five lenses
   are the SUBJECT of this aperture rather than a corner widget, and at 28px the
   instrument left 65px of slack under the plate that read as a hole. 5x33=165
   spends it on the control. */
.lens{
  position:relative; display:flex; align-items:center; gap:.42em;
  min-height: clamp(28px, 2.3vw, 33px);
  font-size:12px; line-height:1.5; text-transform:uppercase; letter-spacing:.16em;
  font-weight:700; color:var(--off); transition:color .45s ease-out; cursor:pointer
}
/* The input IS the hit area and it covers the label, so the shared
   main :focus-visible ring in _rig.css draws around the whole lens rather than
   around a 0x0 box. Transparent, so the text renders through it. */
.lens input{
  appearance:none; -webkit-appearance:none; position:absolute; inset:-3px;
  width:auto; height:auto; margin:0; border:0; background:none; cursor:pointer
}
.lens > span{ position:relative }
/* The active marker is Bergh's: one dot, no box. Centred on the label rather
   than nudged off the baseline — at translateY(-.34em) against a baseline
   alignment it rendered as a stray apostrophe floating above the A of ALL. */
.lens-dot{
  flex:none; width:3.3px; height:3.3px; border-radius:50%; background:currentColor;
  opacity:0; transition:opacity .3s ease-out
}
.lens input:checked ~ .lens-dot{ opacity:1 }
.lens:has(input:checked){ color:var(--lit) }
/* Counts: Atkinson 400 ITALIC, parenthesised, zero-padded, nudged down 12% so
   the parentheses sit optically on the uppercase label's baseline rather than
   below it. --nudge is the per-item optical correction §3.0 calls for: the
   labels end on Y, C, K and W, whose right side-bearings differ by up to 0.06em,
   so a fixed gap leaves the parentheses looking ragged when they are not. */
.lens-n{
  font-weight:400; font-style:italic; letter-spacing:.05em;
  font-variant-numeric:tabular-nums;
  transform: translate(var(--nudge, 0), 12%)
}

.ap-cap{ margin:0; font-size:clamp(13px, 1.05vw, 15px); line-height:1.45;
         letter-spacing:0; text-transform:none; color:var(--half) }
/* +0.09em, not the rail's +0.16em. MEASURED: at 12px/+0.16em the default
   readout is 290.6px in the channel's 272px column, so it wrapped mid-phrase;
   at +0.09em it is 259.4px and holds one line in every state, including the
   longest filtered one. It is the same exception the rig already makes for a
   rail carrying values rather than labels (.rail-v). */
.ap-read{ margin:0; color:var(--off); letter-spacing:.09em }

/* ---- The rows --------------------------------------------------------------
   Bergh's LIST row, ported: one row per item, edge to edge, a hairline between
   rows, media on the centre axis, the small metadata demoted to the far right.
   His rows are 206px because each carries its own 355x200 film; ours carry text
   and one shared plate, so the rhythm is held by the ratio of type to air rather
   than by copying his pitch. MEASURED at 1440: row 99.3px, eight rows 794px.
   §3.2 specified 5.5vw = 79.2px, which put 57px of type in a 79px row and read
   as a dense table; the extra 20px per row is what buys "unhurried" and it is
   also what lets the 518px aperture sit INSIDE the list with three rows below
   it rather than filling it end to end. */
.srow{
  grid-column:1 / -1;
  display:grid; grid-template-columns: var(--col) var(--gate-w) 1fr;
  column-gap:var(--gap); row-gap:.35rem; align-items:baseline;
  min-height:var(--srow-h); align-content:center
}
/* THE ROWS ARE PLACED EXPLICITLY, AND THIS IS NOT A STYLE PREFERENCE.
   MEASURED before the fix: every row landed at y1560.6 and the aperture ended
   at y1560.6 — zero overlap. Auto-placement will not put a 1/-1 item into any
   row whose centre column the aperture already occupies, so all eight rows were
   pushed clear of it and the composition silently became "a headline, a card,
   then a list" instead of a control sitting inside the list it governs. Nothing
   in the render looked broken, which is exactly why it needed measuring.
   Explicit rows let the two overlap, which is the reference's LIST view: the
   hairlines run edge to edge and the media sits on them.
   If a theme is ever added or removed, this block and the aperture's span both
   change. Eight rows is his list, so that is a content decision, not a drift. */
.stream > article:nth-of-type(1){ grid-row:2 }
.stream > article:nth-of-type(2){ grid-row:3 }
.stream > article:nth-of-type(3){ grid-row:4 }
.stream > article:nth-of-type(4){ grid-row:5 }
.stream > article:nth-of-type(5){ grid-row:6 }
.stream > article:nth-of-type(6){ grid-row:7 }
.stream > article:nth-of-type(7){ grid-row:8 }
.stream > article:nth-of-type(8){ grid-row:9 }
/* None above the first, none below the last. The rules run the FULL frame width
   and pass behind the aperture, which is the reference's own behaviour — in the
   LIST view the hairline runs edge to edge and the film sits on it. */
.srow + .srow{ border-top:1px solid var(--line) }

/* DOM order is theme -> tags -> state -> route, which is the order the row reads
   in at 390 and the order it should be heard in; desktop places the tag column
   to the left of the theme explicitly. The only focusable thing in a row is its
   route, so nothing about tab order depends on this. */
.stheme{ grid-column:3; grid-row:1; margin:0;
         font-size:1.95vw; font-weight:400; letter-spacing:0; line-height:1.1;
         color:var(--lit); transition:color .45s ease-out }

/* COLUMN 1 IS THE PRE-VERBAL PROOF. Right-aligned, ending at x540, so the LEFT
   edge is ragged by tag count and the list is a bar chart before it is a list.
   MEASURED at 12px/+0.16em with the real face: the run swings from
   "MINISTRY · LAW" (149.6px) to "MINISTRY · SOCIAL WORK · MUSIC · LAW" (386.0px)
   in a 504px column — a 236px swing, and Identity & belonging reaches furthest
   left because four professions meet in it. nowrap is load-bearing: a wrapped
   run destroys the edge. */
.stags{ grid-column:1; grid-row:1; margin:0; text-align:right; white-space:nowrap;
        font-size:12px; line-height:1.5; text-transform:uppercase; letter-spacing:.16em;
        font-variant-numeric:tabular-nums }
.stags span{ color:var(--lit); transition:color .45s ease-out }
.stags .sep{ color:var(--off); font-style:normal }

/* The state line and the route share one line: state at the theme's left edge,
   route right-aligned hard to the frame edge at x1404. §3.2 asks for the route
   at the frame edge and this is the only way to get it there without a third
   grid column, which is the shape the ledger already had to delete once. */
.smeta{ grid-column:3; grid-row:2; margin:0; display:flex; align-items:baseline;
        justify-content:space-between; gap:1.5rem }
.sstate{ font-size:12px; line-height:1.5; color:var(--half);
         transition:color .45s ease-out }
.sstate i{ font-style:italic }
.sroute{ flex:none; font-size:14px; line-height:1.5; color:var(--half);
         white-space:nowrap; transition:color .45s ease-out }
.arw{ display:inline-block; transition:transform .6s var(--ease-respond) }
.srow:hover .sroute, .srow:has(.sroute:focus-visible) .sroute{ color:var(--lit) }
.srow:hover .arw, .srow:has(.sroute:focus-visible) .arw{ transform:translateX(4px) }

/* ---- DIM IN PLACE. Never remove, never reflow ------------------------------
   Colour is the only property that changes, so an empty filtered set is
   structurally impossible and the layout cannot shift by a pixel.
   THIS IS ALSO WHAT FIXES "a control that does nothing on first press":
   MINISTRY is 08 of 08, so no row dims — but every non-ministry tag in all eight
   rows drops to --off and the readout re-derives, so the page visibly changes
   and what it shows is the thesis. Every theme is a talk. */
.stream[data-lens="ministry"] .srow:not([data-tags~="ministry"]) .stheme,
.stream[data-lens="music"]    .srow:not([data-tags~="music"])    .stheme,
.stream[data-lens="social"]   .srow:not([data-tags~="social"])   .stheme,
.stream[data-lens="law"]      .srow:not([data-tags~="law"])      .stheme{ color:var(--half) }

.stream[data-lens="ministry"] .srow:not([data-tags~="ministry"]) .sstate,
.stream[data-lens="music"]    .srow:not([data-tags~="music"])    .sstate,
.stream[data-lens="social"]   .srow:not([data-tags~="social"])   .sstate,
.stream[data-lens="law"]      .srow:not([data-tags~="law"])      .sstate{ color:var(--off) }

.stream:not([data-lens="all"]) .stags span{ color:var(--off) }
.stream[data-lens="ministry"] .stags span[data-tag="ministry"],
.stream[data-lens="music"]    .stags span[data-tag="music"],
.stream[data-lens="social"]   .stags span[data-tag="social"],
.stream[data-lens="law"]      .stags span[data-tag="law"]{ color:var(--lit) }

/* The routes never dim. A dimmed exit reads as a disabled control, and this
   architecture ships no disabled controls. */

@media (prefers-reduced-motion: reduce){
  .pl{ transition:none }
}

/* ---- 390 -------------------------------------------------------------------
   The rig's mobile split is kept: first display half ABOVE the aperture, second
   half BELOW it. It does not collapse to "title, then image" — reference-capture
   records that as the detail most likely to be lost, and it is the reference's
   signature move.
   .s-head goes display:contents so its two halves become direct children of the
   flex column and can be ordered around the aperture. Verified in the a11y tree
   at 390: the h2 still reports as heading level 2 with its full name. */
@media (max-width:640px){
  .stream{ display:flex; flex-direction:column; gap:0 }
  .s-head{ display:contents }
  .s-head .dhalf-a{ order:1 }
  .aperture{ order:2 }
  .s-head .dhalf-b{ order:3 }
  .srow{ order:4 }

  .s-head .dhalf-a, .s-head .dhalf-b{ width:100% }
  .aperture{ width:100%; height:auto; margin-block:5vw 0 }
  /* No absolute plane at 390: there is no vertical slack to hold one in, the
     film is 197px of a 350px-wide window (56% of it, against 38% at 1440), and
     the aperture's height is whatever its contents measure. The plate is flush
     to the aperture's own edges so the two top brackets still bracket the film,
     and the rail follows it in flow rather than sitting on it. */
  .ap-plate{ position:relative; inset:auto; aspect-ratio:720/406 }
  .ap-in{ position:static; display:block; padding:0 }
  .ap-rail{ padding:.85rem 0 0 }
  .ap-body{ padding:1rem 0 0; gap:1rem }
  /* The tail stops being a channel — there is no centre axis at 390 — and
     becomes what it says: the caption under the counts, the live readout under
     the caption. */
  .ap-tail{ order:2; background:none; padding:1rem 0 0; gap:.85rem;
            display:flex; flex-direction:column; margin-bottom:5vw }
  /* 44px pitch: real touch targets, §3.2. */
  .lens{ min-height:44px; align-items:center }
  .lens-dot{ transform:none }
  .ap-cap{ font-size:14px }

  /* Each row becomes four lines. The ragged left edge does not survive a
     left-aligned wrap, so convergence reads as ROW HEIGHT instead: a four-tag
     row is visibly taller than a two-tag row. Same fact, different channel. */
  .srow{ display:block; min-height:0; padding-block:1.15rem }
  .stheme{ font-size:5.5vw }                    /* 21.45px @390 */
  .stags{ text-align:left; white-space:normal; margin-top:.4rem }
  .smeta{ display:block; margin-top:.35rem }
  /* §3.2 asks for a top hairline on the route here. It is deliberately NOT set:
     with one rule on the route and one on the row, every row carried two
     identical hairlines, so the row boundary became ambiguous and the list
     stacked 16 rules down a phone — the reference's own list runs exactly one
     rule per row. The route is unmistakably a control without it: its own line,
     full width, 44px tall, arrow pinned to the frame edge. */
  .sroute{ display:flex; align-items:center; justify-content:space-between;
           gap:1rem; min-height:44px; margin-top:.35rem }
}

/* ---------- #speaking / #music — the room (pieces/room.html) ----------
   `.srow` was renamed to `.shape-row` here: the stream already owns `.srow`
   for its eight message rows and both selectors were unscoped. */
/* ============ THE PIECE. Everything below this line is what gets integrated. ============ */
/* #speaking + #music — THE ROOM. ARCHITECTURE §3.3 + AMENDMENT A3.
   ONE section. TWO doors. ONE rule. Both header format words land here, which is
   how the signed-off header stops being a menu of businesses without anyone
   touching it.

   The rig (frame, 3-column grid, gate + brackets, rails, display split, easings,
   entrance, focus token) is in _rig.css and is shared with two other pieces.
   Nothing below edits it. Only what THE ROOM owns lives here.

   THE ONE THING THE FIELD IS NOT: §3.3a is explicit that this head carries no
   photograph and no full-bleed layer on any theme. The ground is flat --ground.
   That is not the rig failing to apply here, it is the whole point of the third
   instance — same composition, third payload, and the payload is a NUMBER.
   §3.1 is the photograph, §3.2 is the instrument, §3.3 is the credential. */

/* ---- Visually hidden, for facts a numeral carries pre-verbally ---------------
   The 16 is a graphic. It is aria-hidden so the section's accessible name stays
   "He speaks. Then he sings." rather than "He speaks. 16 Then he sings.", and
   the word it stands for is restored into the rail that captions it, where it is
   read but not set twice. */
.vh{ position:absolute; width:1px; height:1px; margin:-1px; padding:0;
     overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0 }

/* ---- The head: the rig, third instance, holding a number --------------------
   THE DISPLAY SPLIT IS MIRRORED HERE, and it has to be. In §3.1 the left half
   carries two lines and the right one, so _rig.css pads .dhalf-b down onto the
   left half's SECOND baseline. Here the sentence runs "He speaks. | Then he /
   sings.", so the reading continuation is left line 1 -> right line 1 and the
   pad belongs on the LEFT. Both halves still occupy an identical two-line box,
   so they still centre identically against the gate. */
#speaking .dhalf-a{ padding-bottom:1.06em }
#speaking .dhalf-b{ padding-top:0 }

/* THE WIDE-VIEWPORT WRAP IS FIXED, IN _rig.css, WHERE IT LIVED. It was
   documented here and escalated rather than fixed, on the argument that editing
   the shared rig changes three pieces at once — but the defect is in the shared
   rig and it was wrapping all three, so the escalation was hiding it. Above
   1472px the frame caps at --maxw while the gate keeps growing with svh, so the
   type grew while its column shrank and the display line wrapped inside
   .dline{overflow:hidden}; at 1728x1117, the 16in MacBook Pro's default scaled
   resolution, "He speaks." broke to "He / speaks." and the rig lost its
   signature move, identical baselines either side of the gate.
   A third term now caps .dhalf against --col-w, the column expressed without a
   percentage. Nothing at or below 1600 moves. The measurements, the divisor and
   why --col itself could not be used are all recorded at .dhalf in _rig.css.
   The two mirrored pads below assume a ONE-LINE left half, which is the other
   reason the wrap mattered here: four display lines on four baselines. */

/* THE NUMERAL. §3.3a asks for 82-92% of the gate width and offers 18vw as a
   starting point with a tolerance rather than an answer.
   18vw is the wrong UNIT, not the wrong number: --gate-h is min(36vw, 60svh), so
   on a short-and-wide window the gate shrinks and a vw-sized numeral does not,
   and the ratio drifts out of tolerance exactly where the architecture's note 4
   says the head is already under pressure. Tying the numeral to --gate-w holds
   the ratio at EVERY viewport instead of at one.
   MEASURED with the real face loaded, and the first estimate was wrong by a
   third: "16" in Atkinson 400 tabular is 1.278em wide, not the ~1.12em a
   narrower grotesk would give. At the architecture's suggested 18vw it rendered
   at 99.4% of the gate on all three viewports, i.e. the digits were touching the
   brackets — the exact failure the 82-92% tolerance exists to catch, and it is
   only catchable by measuring the real face rather than estimating it.
   0.681 * 1.278 = 87.0% of the gate, mid-tolerance, and it holds at 1366x768
   and at 390 because both terms are gate-w.
   The optical nudge: tabular figures give "1" the same advance as "6", and
   Atkinson's 1 sits left-of-centre in its slot with no foot serif to balance it,
   so an advance-centred pair reads slightly right of true centre. */
#speaking .gate{ display:grid; place-items:center }
#speaking .num{
  position:relative; z-index:2;
  font-size:calc(var(--gate-w) * .681);
  font-weight:400; letter-spacing:0; line-height:1;
  font-variant-numeric:tabular-nums; color:var(--ink);
  transform:translateX(-.014em)
}
/* COVENANT — the polarity flip, delivered rather than asserted. On the two dark
   directions the plate is --ground-2, a step up from the page, and the numeral
   is ink. On ivory that same step is #FFFDF8 against #F7F1E6, which is a plate
   nobody can see, and §3.0 promises "the gate becomes a dark aperture in a
   bright field". So covenant prints the plate in the direction's OWN ink and
   reverses the numeral out of it: 12.3:1 measured, the darkest object on the
   page, and the same relationship the ledger's covenant gate already has. No new
   colour enters — both values are tokens the direction owns. */
[data-theme="covenant"] #speaking .gate{ background:var(--ink) }
[data-theme="covenant"] #speaking .gate::after{ background:transparent }
[data-theme="covenant"] #speaking .num{ color:var(--ground) }

/* ---- The shapes band: THREE ROWS, NOT THREE CARDS ---------------------------
   There is no repeat(3,1fr) anywhere in this architecture. A hairline where a
   card border would go is not a design decision, and three judges scored that
   shape at 2. Col 1 right-aligned ending at x540 — the same vertical line the
   ledger's labels and the stream's tags end on, so the three surfaces are
   visibly one instrument. Col 2 from x547.2, the gate's own left edge. */
.shapes{ margin-top:min(3vw, calc(4.2 * var(--rig-v))) }
.shape-row{
  display:grid; grid-template-columns: var(--col) 1fr; column-gap:var(--gap);
  align-items:baseline; min-height:4.5vw; padding-block:1.1vw
}
.shape-row + .shape-row{ border-top:1px solid var(--line) }
.shape-name{
  margin:0; display:flex; align-items:center; justify-content:flex-end; gap:.75rem;
  font-size:clamp(21px, 1.95vw, 30px); font-weight:400; letter-spacing:0; line-height:1.1
}
.shape-spec{
  margin:0; font-size:12px; line-height:1.5; text-transform:uppercase;
  letter-spacing:.16em; font-variant-numeric:tabular-nums; color:var(--ink-faint)
}
/* THE SEAM. 2px x 24px of plum, and it is the section's second of exactly three
   accent elements. It marks the joint: the middle row is the shape that is both
   a talk and a song, and it sits directly above the point where the two doors
   physically meet. The music is a delivery mode of the booking, not a second
   business, and this is the one mark on the page that says so structurally. */
.seam{ flex:none; width:2px; height:24px; background:var(--a-music) }

/* The dated-moments line. Australian bureaus merchandise speakers by dated
   awareness moment as well as by topic, and this costs one row. */
.moments{ margin-top:1.35rem; color:var(--ink-faint) }

/* ---- The two doors ---------------------------------------------------------
   A real 1px grid COLUMN, never a border, so the seam runs the full height of
   whichever door is taller. --line-strong rather than --line: measured on
   precipitation, --line composites to 1.39:1 against the ground and a structural
   seam nobody can see is not a seam. */
/* ONE COLUMN, 2026-08-14 (the fit-a-screen pass), and this was the root cause of
   #speaking measuring 2.27 screens rather than a padding problem.
   `1fr 1px 1fr` was written for TWO doors. #music left and became its own section
   on 2026-08-14, and the grid was left as it was on the note that #speaking had
   not been reworked yet. The consequence was never just cosmetic: the surviving
   door was rendering at ~48% of a 1368px frame, so a ten-field form was folding
   into a 640px column with the entire right half of the section empty. Half the
   width means about twice the height. */
.doors{
  display:grid; grid-template-columns:1fr;
  margin-top:min(4vw, calc(5.4 * var(--rig-v)))
}
.doors .rule{ background:var(--line-strong) }
.door{ min-width:0 }
/* THE DOOR NOW SPLITS ITSELF: the readout (what he is, where, what it costs) on
   the left, the enquiry on the right, so the two halves of "have him in the room"
   read side by side instead of one under the other. Below 900 it stacks, which is
   the order it has always had. */
@media (min-width:900px){
  .door-speak{
    display:grid; grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
    column-gap:clamp(2.5rem,5vw,4.5rem); align-items:start
  }
  .door-speak > .door-h{ grid-column:1 / -1 }
  .door-speak > .readout{ grid-column:1 }
  /* Level with the readout rather than 3.25rem under it: the margin existed to
     separate two stacked blocks, and they are no longer stacked. */
  .door-speak > .formblock{ grid-column:2; grid-row:2 / span 2; margin-top:0 }
}
.door-h{
  font-size:clamp(20px, 1.6vw, 26px); font-weight:400; letter-spacing:-.012em;
  line-height:1.2; margin:0 0 1.6rem; scroll-margin-top:7rem
}
/* THE HEADER'S FULL HEIGHT, NOT 7rem, AND THIS BECAME LOAD-BEARING TODAY. Every
   other .section relies on its own top padding to clear the fixed header, which
   is why .section sets scroll-margin-top:0. #music has had its top padding cut
   (Rokas, 2026-08-14: "move this section up a little more"), so that clearance
   no longer exists here and the nav's "Music" link would land the title under
   the header. 7rem = 112px was already under the 124px header; --header-h is
   the real number. */
#music{ scroll-margin-top: var(--header-h) }

/* ---- The readout: ONE component, TWO payloads -------------------------------
   The five facts an organiser filters on in the left door, and the six facts of
   the record in the right, are the same <dl> at the same pitch. That is what
   makes the two doors read as two doors on one product rather than as two
   catalogues: their most informational block is literally the same object.
   THE LABEL COLUMN IS FIXED PER PAYLOAD, AND THAT IS THE SECOND THING LOOKING
   CAUGHT. max-content is what this wants, but each .ro-row is its own grid so
   max-content sizes every row separately and the values stop lining up; a single
   fixed width then has to clear the longest label in EITHER door, which left
   "FEE" 100px from its value and read as two disconnected columns rather than as
   one row. So the width is declared once per readout against its own longest
   label. MEASURED with the real face at 12px/+0.16em:
     left door   LANGUAGES     85.9px  -> 5.5rem (88)
     right door  RELEASE DATE 103.2px  -> 6.6rem (105.6)
   Subgrid would do this without the two numbers and was rejected: it puts a
   client site's most informational block on a feature with a stacked-column
   failure mode in older Safari, to save a measurement this file takes anyway. */
.readout{ margin:0; --ro-l:5.5rem }
.record{ --ro-l:6.6rem }
.ro-row{
  display:grid; grid-template-columns:var(--ro-l) 1fr; column-gap:1.5rem;
  align-items:baseline; min-height:44px; padding-block:.7rem;
  border-bottom:1px solid var(--line)
}
.readout dt{
  margin:0; font-size:12px; line-height:1.5; text-transform:uppercase;
  letter-spacing:.16em; font-variant-numeric:tabular-nums; color:var(--ink-faint)
}
/* RELEASE DATE / Not set is set in the SAME ink as every other value. Never
   greyed, never badged, never a "coming soon" chip. It is a value, not a gap,
   and that is how "designed absence, never broken" gets passed structurally
   instead of asserted. */
.readout dd{
  margin:0; font-size:clamp(15px, 1.15vw, 18px); line-height:1.4; color:var(--ink)
}
.readout dd i{ font-style:italic }

/* ---- The enquiry form ------------------------------------------------------
   No boxes, no fills, no side-by-side pairs. Every control is bottom-rule-only,
   48px, transparent, so the form reads as part of the instrument rather than as
   a stock form dropped into it.
   THE RULE IS --ink-faint AND NOT --line, and that is a measurement, not taste:
   WCAG 1.4.11 asks 3:1 of a control's boundary, and the bottom rule is the ONLY
   thing marking these controls. --line composites to 1.39:1 on precipitation and
   --line-strong to 2.23:1. --ink-faint is 5.83 / 5.71 / (banquet) 5.5:1, i.e. it
   passes on all three directions, and at 1px it reads as the same instrument
   hairline as the rails rather than as a border. */
.formblock{
  /* 3.25rem, not 2.5. MEASURED by looking: at 2.5rem the readout's own closing
     hairline and the rust rule that opens the form sat 37px apart and read as
     one doubled rule rather than as the end of one block and the start of
     another. 52px is past the point where the eye pairs them. */
  margin-top:3.25rem; padding-top:1.75rem;
  /* The section's FIRST of exactly three accent elements. */
  border-top:1px solid var(--a-speak)
}
/* TWO COLUMNS ON A DESKTOP, 2026-08-14 (the fit-a-screen pass), and this is a
   ROOT-CAUSE fix rather than a squeeze. #speaking measured 2611px, of which the
   form alone was 1332px: ten fields stacked in ONE column across a 1368px frame.
   No amount of padding trimming reaches that; the column count does. The pairs
   fall out of the field order already in the markup — name/organisation,
   email/phone, date/where, size/budget — so nothing is reordered and the tab
   order is unchanged.
   THREE CHILDREN STAY FULL WIDTH: the event-kind fieldset (a radio group reads
   as one control and must not be cut in half), the note textarea (it is the one
   field people write into) and the submit. `:has()` is used rather than a class
   so the markup does not have to know about the layout. */
@media (min-width:900px){
  .formblock form{
    display:grid; grid-template-columns:1fr 1fr; align-items:start;
    column-gap:clamp(1.75rem,3.4vw,3rem)
  }
  .formblock form > .f-set,
  .formblock form > .f-field:has(textarea),
  .formblock form > .submit,
  .formblock form > .chip{ grid-column:1 / -1 }
}
/* 1.5rem -> 1.1rem, 2026-08-14 (the fits-a-screen pass). Ten fields carry ten of
   these, so 6px each is 64px off the block, which is most of what it needed to
   come under a screen. The label-to-field distance is untouched; only the gap
   BETWEEN fields closes, so the pairs still read as pairs. */
.f-field{ margin:0 0 1.1rem }
.f-label{
  display:block; margin:0 0 .3rem; font-size:12px; line-height:1.5;
  text-transform:uppercase; letter-spacing:.16em;
  font-variant-numeric:tabular-nums; color:var(--ink-faint)
}
.f-hint{ margin:.35rem 0 0; font-size:13px; line-height:1.4; color:var(--ink-dim) }
/* A FIELD, NOT A LINE, 2026-08-17. Rokas, on the contact form: "make this into a
   better form that's not just a list." The fields were bottom-border only, so a
   stack of them read as underlined labels rather than inputs. Each is now a real
   field: a soft box with a full hairline, a faint tint of the ground's own ink and
   a rounded corner, which reads as something to type into on both the pale ground
   (navy ink) and the black one (ivory ink) because every value is a token. The
   tint and the border deepen on hover and focus so the active field is obvious. */
.f-in{
  display:block; width:100%; height:52px; padding:0 14px;
  font:inherit; font-size:16px; color:var(--ink);
  background:color-mix(in srgb, var(--ink) 4%, transparent);
  border:1px solid var(--line); border-radius:12px;
  transition:border-color .22s, background-color .22s
}
.f-in:hover{ border-color:var(--line-strong) }
.f-in:focus{
  outline:none; border-color:var(--ink);
  background:color-mix(in srgb, var(--ink) 7%, transparent)
}
textarea.f-in{ height:auto; min-height:120px; padding-top:12px; line-height:1.5; resize:vertical }

/* THE DATE FIELD PAINTED A VALUE IT DID NOT HAVE, IN SAFARI ONLY, ON THE PAGE'S
   PRIMARY COMMERCIAL FORM. Reproduced against a control page in the same WebKit
   build: a bare <input type=date> renders its empty state dimmed on the UA
   plate, which reads as unfilled — but stripping that plate (which .f-in does,
   because a filled box is exactly what this form is not) leaves WebKit painting
   the digits at full strength in an off-palette amber, so an organiser reads
   "14/08/2026" and takes today's date to be already in the field. Verified on
   the data side in both engines: q-date.value is "" before focus, "" on focus,
   and FormData.get('date') is "" after tabbing through, so it SUBMITS EMPTY
   while the organiser believes they supplied a date. Chromium is honest here
   and renders "dd/mm/yyyy", so the rule is written for the empty state rather
   than for one engine: nothing is painted until the field is focused or has a
   real value, which is the same thing every other control in this form does —
   blank, with its label above it. The format lives in the hint underneath.
   The second half is the focus chip. WebKit gives the three segments the system
   blue, which made the date the only control in the form whose focus indicator
   was not in Traidy's palette; --focus is the direction's own token and is what
   every other control here already uses. */
/* opacity, NOT color:transparent, and that is a measurement rather than a
   preference. Driven in WebKit against the real field: `color` on the sub-field
   pseudos is ignored in the EMPTY state (the separators went transparent and
   the digits stayed amber, which is the half-fix that would have shipped
   looking like a fix), and honoured the moment the field holds a value — which
   is why the ink rule below is the one that does not need a guard. */
.f-in[type="date"]:not(.has-value):not(:focus)::-webkit-datetime-edit{ opacity:0 }
.f-in[type="date"]::-webkit-datetime-edit-text,
.f-in[type="date"]::-webkit-datetime-edit-day-field,
.f-in[type="date"]::-webkit-datetime-edit-month-field,
.f-in[type="date"]::-webkit-datetime-edit-year-field{ color:var(--ink) }
.f-in[type="date"]::-webkit-datetime-edit-day-field:focus,
.f-in[type="date"]::-webkit-datetime-edit-month-field:focus,
.f-in[type="date"]::-webkit-datetime-edit-year-field:focus{
  background:var(--focus); color:var(--ground); border-radius:2px; outline:none
}
/* A select with a box is still a box. appearance:none strips it back to the same
   bottom rule as every other control; the caret is drawn by the wrapper so it
   follows --ink on every direction instead of shipping a UA glyph. */
.f-sel{ position:relative; display:block }
.f-sel select{ appearance:none; -webkit-appearance:none; padding-right:2.5rem; cursor:pointer }
/* The caret sits INSIDE the field box now (right:14px matches the input's own
   inline padding), vertically centred in the 52px box. */
.f-sel::after{
  content:""; position:absolute; right:14px; top:23px; pointer-events:none;
  border:5px solid transparent; border-top:6px solid var(--ink-faint)
}
/* Real radios and a real checkbox, kept in the accessibility tree and driven by
   the keyboard; only the paint is ours. The visual is a sibling span so no
   pseudo-element is asked of a replaced element, which is where WebKit and
   Chromium disagree. */
.f-set{ border:0; margin:0 0 1.1rem; padding:0; min-width:0 }
/* THE FOUR OPTIONS RUN ACROSS, NOT DOWN, on a desktop (2026-08-14, the
   fits-a-screen pass). Stacked they were a 199px column inside a form that had
   to lose about 90px to bring the booking block under one screen; wrapped into
   rows of two they are ~90px, and this is the only element in the form whose
   options are short enough to sit side by side. The 44px tap target is kept, so
   nothing about the phone changes: below 900 they stack exactly as before. */
@media (min-width:900px){
  .f-set{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); column-gap:1.5rem }
  .f-set legend{ grid-column:1 / -1 }
}
.f-set legend{ padding:0 }
.f-opt{
  display:flex; align-items:center; gap:.8rem; position:relative;
  min-height:44px; cursor:pointer; font-size:16px; line-height:1.3
}
.f-opt input{ position:absolute; opacity:0; width:18px; height:18px; margin:0; left:0 }
.f-dot,.f-box{
  flex:none; position:relative; width:18px; height:18px;
  border:1px solid var(--ink-faint); transition:border-color .25s, background-color .25s
}
.f-dot{ border-radius:50% }
.f-box{ border-radius:2px }
.f-opt input:checked + .f-dot{ border-color:var(--ink) }
.f-opt input:checked + .f-dot::after{
  content:""; position:absolute; inset:3px; border-radius:50%; background:var(--ink)
}
.f-opt input:checked + .f-box{ background:var(--ink); border-color:var(--ink) }
.f-opt input:checked + .f-box::after{
  content:""; position:absolute; left:6px; top:2px; width:4px; height:9px;
  border:solid var(--ground); border-width:0 2px 2px 0; transform:rotate(45deg)
}
.f-opt input:focus-visible + .f-dot,
.f-opt input:focus-visible + .f-box{ outline:2.5px solid var(--focus); outline-offset:3px }
.f-opt:hover .f-dot,.f-opt:hover .f-box{ border-color:var(--ink) }

/* The carried-theme chip. It shows an organiser that the page remembered what
   they clicked in the stream, and it is the cheapest trust signal available.
   THE [hidden] RULE IS NOT BOILERPLATE, IT IS A BUG THIS FILE SHIPPED AND A
   SCREENSHOT CAUGHT. `display:flex` in a class beats the UA stylesheet's
   `[hidden]{display:none}`, so the chip rendered on load as an empty
   "THEME  x" with no theme in it — the page claiming to remember a click that
   never happened, sitting directly above the enquiry form, on both viewports.
   Every automated check passed: no console error, no overflow, the element's
   .hidden property read true. It was only visible by looking. */
.chip{
  display:flex; align-items:center; gap:.55rem; margin:0 0 1.5rem;
  font-size:12px; line-height:1.5; text-transform:uppercase; letter-spacing:.16em;
  color:var(--ink)
}
.chip[hidden]{ display:none }
.chip-k{ color:var(--ink-faint) }
.chip-x{
  /* 40px HIT AREA around a 16px glyph, 2026-08-17 (Codex responsive audit). The box
     and the glyph used to be the same 24px, under the touch floor; the negative
     margins keep the optical position while the tap area grows. */
  display:grid; place-items:center; width:40px; height:40px; margin:-8px -8px -8px -.55rem;
  font-size:16px; line-height:1; color:var(--ink-faint); transition:color .25s
}
.chip-x:hover{ color:var(--ink) }

/* Submit. It reads "Check availability", never "Book now" — a human answers
   next, and every credible speaker site uses availability language.
   IT IS A BOUNDED OBJECT AND NOT A FOURTH ROW, and that is a correction. It was
   built as "a variation on the row rather than an exception to it": 48px tall,
   transparent, one bottom rule — which is byte-for-byte what .f-in is, so the
   only thing the visitor had to submit with was visually the same object as the
   things they had just typed into. MEASURED before: subH 48 / inpH 48, both
   transparent, both bottom-rule-only, in a 655px-wide button whose arrow was
   float:right and therefore sat 558px from the end of its own words — an
   affordance at the opposite end of the control from its label.
   Now: shrink-wrapped to its words, a full 1px --ink boundary on all four sides
   (the fields carry --ink-faint on ONE side, so the two never read alike), and
   the arrow immediately after the label. No new colour: the hover fills with the
   direction's own ink and reverses the label out of it, which is the same
   two-token move the covenant gate makes. */
.submit{
  display:inline-flex; align-items:center; gap:.9rem; width:auto; height:48px;
  margin-top:1rem; padding:0 1.25rem;
  font:inherit; font-size:16px; color:var(--ink); background:transparent;
  border:1px solid var(--ink); border-radius:2px;
  transition:background-color .25s, color .25s
}
.submit .arw{ display:inline-block; transition:transform .6s var(--ease-respond) }
.submit:hover{ background:var(--ink); color:var(--ground) }
.submit:hover .arw{ transform:translateX(4px) }
/* The confirmation region stays RENDERED and empty rather than display:none
   until it fills. A role="status" that is display:none is not in the
   accessibility tree, so the change that populates it is the change that creates
   it, and that is the case screen readers announce least reliably. An empty div
   is already zero-height, so nothing is paid for keeping it. */
.done p{ margin:0; font-size:clamp(15px,1.15vw,18px); line-height:1.5; color:var(--ink) }
.onesheet{ margin:1.5rem 0 0; font-size:14px; line-height:1.4 }
.onesheet a{ border-bottom:1px solid var(--ink-faint); padding-bottom:2px;
             transition:border-color .25s }
.onesheet a:hover{ border-bottom-color:var(--ink) }

/* ---- The right door: A3, the artwork leads ---------------------------------
   The rainbow gate was designed as an artwork SUBSTITUTE. Artwork now exists, so
   the substitute stands down to what it always was underneath: his voice.
   480px HARD CEILING on the long side, and never upscaled. The file is 906x926,
   a photograph of a physical hardcover with glare and a visible book edge. At
   small-to-medium it reads as a real object being held, which is charming; at
   large it reads as a bad scan. 469px is the width whose LONG side lands under
   the ceiling rather than on it: 469 x 926/906 = 479.4. 470 measured 480.4, and
   a ceiling you round through is not a ceiling. 469 is 72% of a 654.7px door,
   which is an object on a page rather than a hero band. */
.cover{ margin:0 }
.cover img{ width:min(100%, 469px); height:auto; border-radius:2px }
.cover figcaption{ margin-top:.9rem }

/* The pull-quote. A3 demotes his six clauses from a standing type block to an
   attributed quotation under the artwork, so they are set as one sentence in his
   own voice rather than as a graphic.
   THE RAINBOW SURVIVES AS ONE ELEMENT, NOT SIX. §3.3c's six per-clause rules
   were the artwork's stand-in; six of them beside a real black-and-gold cover
   would compete with the object and re-add decoration next to a photograph.
   Compressed to a single six-stop bar it is still exactly his answer, still the
   accent set plus indigo, and it is the section's THIRD and last accent element,
   which is the whole colour budget spent: rust, plum, rainbow.
   Horizontal, matching the rust hairline that opens the left door's form block,
   so both doors are opened by a rule of the same weight in the same place. */
.pq{ margin:2.5rem 0 0 }
.rainbow{
  display:block; width:132px; height:2px; margin-bottom:1.1rem;
  background:linear-gradient(90deg,
    var(--a-speak)    0 22px,
    var(--a-story)  22px 44px,
    var(--a-rose)   44px 66px,
    var(--a-music)  66px 88px,
    var(--a-messages) 88px 110px,
    var(--a-indigo) 110px 132px)
}
/* Indigo #282562 is the BRAND anchor and is constant across all three
   directions, unlike every --ground / --ink token. It is declared once, here,
   as a token rather than inlined, so it is greppable and so nothing downstream
   mistakes it for a hardcoded direction colour. */
#speaking{ --a-indigo:#282562 }
.pq blockquote{ margin:0 }
.pq blockquote p{
  margin:0; font-size:clamp(17px, 1.4vw, 22px); line-height:1.45;
  font-style:italic; color:var(--ink)
}
.pq figcaption{ margin-top:1rem }

/* The ask. The page's ONLY email field; there is no second capture anywhere.
   IT NOW HAS AN OPENING MARK, AND IT SHIPPED WITHOUT ONE. The enquiry door
   opposite is opened by a rust hairline and a heading; the record's ask had
   nothing at all, and its rail "BE TOLD FIRST" and the field label "YOUR EMAIL"
   directly under it MEASURED BYTE-IDENTICAL in every rendered type property
   (12px / rgba(247,241,230,.56) / 1.92px / uppercase / 400, gap 17.6px), so
   they read as two labels for one field rather than as a block heading and a
   label. The scan ran quote -> attribution rail -> BE TOLD FIRST -> YOUR EMAIL
   -> rule -> Tell me first: five consecutive 12px grey uppercase runs with no
   size, weight, colour or rule change to say where the ask began — next to a
   469px album cover, which is the biggest and brightest object in the door.
   Three changes, none of which spends a fourth accent (the budget of three is
   already gone on rust, plum and the rainbow):
     1. a structural --line-strong hairline opening the block, the same weight
        and the same position as the rust one opposite;
     2. the rail at FULL INK and weight 700, which is the register the stream's
        active lens already uses for "this is the live thing" — so it now
        differs from the field label in both colour and weight;
     3. the purpose line moved ABOVE the field. It breaks the run of uppercase
        with a sentence-case line, and it says what the visitor is joining
        before it asks for anything, which is the order that converts. */
.ask{ margin-top:3.25rem; padding-top:1.75rem; border-top:1px solid var(--line-strong) }
.ask .rail{ margin-bottom:.55rem; color:var(--ink); font-weight:700 }
.purpose{ margin:0 0 1.75rem; font-size:14px; line-height:1.45; color:var(--ink-dim) }
.record{ margin-top:2.5rem }

/* ---- The BTS peek ----------------------------------------------------------
   17.5 silent seconds, muted, looping, nobody has to press anything. It is the
   only continuously moving element in the section, and on the reference a peek
   is the next item in the stream — here it is the next thing in the record's
   life, which is the same relationship.
   THE BLEED IS ANCHORED TO THE VIEWPORT, NOT TO THE FRAME. §3.3c's flat
   "right:-128px relative to the frame" is only correct while the frame is
   viewport-width-minus-72; above 1472 the frame caps at --maxw and centres, and
   a flat -128 leaves the peek floating in the right margin instead of running
   off the screen. Deriving the bleed from the frame's own outside gap keeps the
   video's right edge exactly 92px past the viewport at every width:
     1000 -> gap 36  -> -128   1440 -> gap 36  -> -128   1920 -> gap 260 -> -352
   .section already carries overflow-x:clip (never `hidden`, which would create a
   scroll container), so none of this touches document scrollWidth. */
.peek{ margin:2.5rem 0 0 }
.peek figcaption{ margin:0 0 .75rem; text-align:right }
.peek-v{
  --peek-bleed: calc(max(var(--frame-pad), (100vw - var(--maxw)) / 2) + 92px);
  width:17.8vw; aspect-ratio:16/9; margin-left:auto;
  margin-right:calc(-1 * var(--peek-bleed));
  border-radius:2px; overflow:hidden; background:var(--ground-2)
}
.peek-v video{ display:block; width:100%; height:100%; object-fit:cover }
@media (prefers-reduced-motion: reduce){
  .peek-v{ background:url("video/traidy-bts-poster.jpg") center/cover no-repeat }
  .peek-v video{ display:none }
}

/* Native controls (the date picker's own indicator and popup, the select's
   popup) have to know which way round the page is or they render a light widget
   on a dark page. Scoped to the section in a piece build; on integration this
   belongs on :root next to the three direction blocks. */
[data-theme="precipitation"] #speaking,
[data-theme="banquet"] #speaking{ color-scheme:dark }
[data-theme="covenant"] #speaking{ color-scheme:light }
/* THE CONTACT FORM NEEDED THE SAME AND NEVER GOT IT, 2026-08-17. The rule above
   was scoped to #speaking, the booking form on ministry.html; the contact page's
   own form lives in #contact, so it shipped its native date control in light mode
   on the dark precipitation field. In Safari that renders a light-appearance
   picker whose calendar indicator sits against the border box and clips at the
   12px rounded right corner (Rokas: "the event date box gets cut off on the right
   side"). Same fix, same three themes, so the two forms cannot drift again. */
[data-theme="precipitation"] #contact,
[data-theme="banquet"] #contact{ color-scheme:dark }
[data-theme="covenant"] #contact{ color-scheme:light }

/* ---- 390 -------------------------------------------------------------------
   LEFT DOOR FIRST, and it is the mobile-completion number that decides it:
   47.5% against 55.5% on desktop, so the enquiry gets the better position. The
   form is one unbroken column with no side-by-side pairs anywhere, every control
   full width at 48px, submit full width and last. */
@media (max-width:640px){
  #speaking .dhalf-a{ padding-bottom:0 }
  .shapes{ margin-top:6vw }
  .shape-row{ display:block; padding-block:1.1rem }
  .shape-name{ justify-content:flex-start; font-size:6.5vw }
  .shape-spec{ margin-top:.5rem }
  .moments{ margin-top:1.15rem }
  .doors{ grid-template-columns:1fr; row-gap:2.25rem; margin-top:2.5rem }
  .doors .rule{ height:1px; width:100% }
  .door-h{ margin-bottom:1.35rem }
  /* The label widths are absolute (12px never scales by vw) so they carry over
     unchanged; only the gutter tightens, which buys the values 16px of a 350px
     frame back. */
  .ro-row{ column-gap:1rem }
  /* §3.3's 390 order ends "submit full width and last". Full width, but the
     label and its arrow stay together in the middle rather than being pushed to
     opposite ends of a 350px control — the same reason the arrow came off
     float:right on desktop. */
  .submit{ display:flex; width:100%; justify-content:center }
  /* MEASURED: the one-sheet link was the only target in the section under 44px
     at 390 (20.0px, an inline <a> in a <p>). It is also the section's guaranteed
     conversion, so it is the last one that should be hard to hit on a phone. */
  .onesheet a{ display:inline-block; padding-block:.75rem }
  .cover img{ width:100% }
  /* The peek stops bleeding and goes full frame width: 350 x 197. */
  .peek-v{ width:100%; margin-right:0 }
  .peek figcaption{ text-align:left }
}

/* ---------- #contact — the close (pieces/close.html) ----------
   The close deliberately does NOT link _rig.css. A scrolling page has to end,
   and dropping the rig's grammar in exactly one place is what makes its three
   repetitions above read as a decision rather than as a template. */
/* ============ THE PIECE. Everything below this line is what gets integrated. ============

   #contact — THE CLOSE.  ARCHITECTURE §3.4 + AMENDMENT A5.

   WHAT THIS SECTION IS NOT, AND THE NOT IS THE DESIGN:
   no gate, no display split, no corner brackets, no photographic field, no
   IntersectionObserver entrance, no accent colour, no rig. A scrolling page has to
   END. If the footer keeps performing the instrument grammar there is no ending,
   only a fifth instrument.

   WHAT IT KEEPS is the TYPE SYSTEM, not the props: 12px uppercase tabular rails,
   1px hairlines, two inks, italic parenthesised counts. That is how it reads as the
   same machine while being visibly the last room in it. Same distinction §5 draws
   for the shapes band and the forms: built in the rig's type system, borrowing not
   one of its objects.

   THREE THINGS IT FIXES OUTRIGHT
   1. Codex #28 at the ROOT. The current footer runs <h4>s with no <h3> above them.
      They were never headings, they are labels — so they become <p class="rail">
      and THE FOOTER CONTAINS NO HEADINGS AT ALL. Document order is then
      h1 (hero) -> h2 x3 -> h3 x2, no skips, nothing hidden. Fixing the level would
      have been a restyle; deleting the heading is the root cause.
   2. The four .pending badges scattered across four sections collapse into ONE
      column. The page stops apologising three times mid-scroll next to its asks and
      states its position once, at the bottom, where a ledger belongs.
   3. A5: that ledger shrinks from five absences to THREE. "No artwork yet" and
      "No sermons published yet" are now false — both exist — and a page that
      apologises for something it is currently showing you is worse than one that
      says nothing.
   ========================================================================== */

/* --- tokens this piece needs and _tokens.css does not carry -----------------
   --frame-pad is duplicated VERBATIM from _rig.css / §3.0 (identical value, so a
   dedupe at integration is a deletion, never a reconciliation). It is what puts
   the content edge on the header's: 1440 -> 1368 wide, x36 -> x1404, pixel-
   identical to .top-in. 390 -> 350 wide, x20 -> x370.
   --focus is §3.0's per-theme token. --a-rose is 1.5:1 on the covenant ground and
   is the whole indicator in _tokens.css today; scoped to footer so the shared rule
   governing the signed-off header is not silently rewritten. */
:root{ --frame-pad: clamp(1.25rem, 3vw, 2.25rem); --ease-respond: cubic-bezier(.75,0,.25,1) }
[data-theme="precipitation"]{ --focus:#F7F1E6 }
[data-theme="banquet"]     { --focus:#FBEEDF }
[data-theme="covenant"]    { --focus:#282562 }
footer :focus-visible{ outline:2.5px solid var(--focus); outline-offset:3px; border-radius:2px }

.frame{ width: min(var(--maxw), calc(100% - 2*var(--frame-pad))); margin-inline:auto }

/* --- the close --------------------------------------------------------------
   ONE hairline above and no verticals. A four-column footer with rules between
   the columns is a table pretending to be a page; the columns are held apart by
   space, which is the same thing every other surface on this page does.
   scroll-margin-top: .top is fixed at ~82px, so the header's own #contact link
   would otherwise land under it. */
.close{
  padding-block: clamp(3rem,4.4vw,4.25rem) clamp(2rem,3vw,3rem);
  border-top:1px solid var(--line);
  scroll-margin-top:1.5rem
}
/* ---- THE CLOSING PHOTOGRAPH, AND THE SECTION IS THE PICTURE -----------------
   Rebuilt 2026-08-15 (second pass). Rokas, correcting the first: "you could
   extend the grey of the image and have the text over it maybe and then like
   make the form itself be as tall as the image itself, so like the whole section
   is just like him, and ... you can condense down the form."

   THE FIRST PASS HAD IT BACKWARDS. It kept the page's black and scrimmed the
   photograph down to fit, which meant fighting the picture: every contrast fix
   was another gradient stop dimming more of the studio. This inverts it. The
   studio's own grey becomes the section's ground, the type goes dark on it, and
   the picture is not a backdrop behind a black section any more, it IS the
   section. Nothing is being dimmed, so nothing has to be fought.

   MEASURED OFF THE FILE, NOT PICKED: the left 45% of the frame runs 186 to 234,
   with its darkest point at rgb(165,155,147). The extension colour and the wash
   are set from those numbers so the flat ground and the photographed ground are
   the same grey, which is what "extend the grey" has to mean to work at all.

   THE PAGE GOES BLACK, PALE, BLACK. This deliberately breaks the continuous
   black asked for earlier the same day, and it is the later instruction: the
   ending is a lit studio, so the lights come up for it. The cut at the top is
   left clean rather than blended, because a fade from black to pale grey over a
   hundred pixels reads as a rendering fault and a hard cut reads as a decision.

   SCOPED TO `.close-full`, NOT `#contact`, 2026-08-16. Rokas: "make the actual
   contact page to be without the photo background", asked WITH a gallery of him
   underneath the form, and asked for the HOME page's closing photograph to stay.
   So the two surfaces that share this section now differ, and the id cannot be
   what carries the difference: both wrappers claim `id="contact"` because both
   are the destination of the site's `#contact` route.

   THE CLASS WAS ALREADY THE HONEST CARRIER and this only finishes the job. The
   short contact on about.html has carried the same id since it was built and has
   always been kept out of these rules by NOT carrying `.close-full`; js/hdr.js
   already queries `.close-full` for exactly that reason, with a comment saying
   so. The selectors here were the one place still keyed on the id, which is why a
   third variant could not exist without an override. It can now: contact.html
   carries `.close-plain`, gets the page's black ground and the site's own ink,
   and needs no rule to undo a pale one it never received. */
.close-full{
  position:relative; border-top:0;
  /* THE LIGHT-GROUND TOKENS ARE COVENANT'S, NOT INVENTED. This site ships three
     directions and Covenant is the one written for a pale ground, so its ink,
     dim, faint and rule values are lifted whole rather than a dark ink being
     mixed here by eye. --ground is the exception: it is the studio's grey rather
     than Covenant's ivory, because the flat part of this section has to match
     the photographed part exactly. */
  --ground:#D4C8C4;
  --ink:#282562; --ink-dim:rgba(40,37,98,.76); --ink-faint:rgba(40,37,98,.74);
  --line:rgba(40,37,98,.20); --line-strong:rgba(40,37,98,.34);
  color:var(--ink);
  background:#D4C8C4;
  padding-block:clamp(3.5rem,7vw,6rem) clamp(3.5rem,7vw,6rem);
}
.close-full::before{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  background:
    /* THE WASH IS WHAT MAKES THE TYPE LEGAL. Dark ink on the darkest part of the
       left (165,155,147) measures 3.9:1, under the floor. A pale wash of the
       studio's own grey lifts that side to roughly 210 without touching him: it
       is gone by 58%, and he stands from 64%. It also does the extending, which
       is the same job from the other end. */
    linear-gradient(to right,
      color-mix(in srgb,#E9E0DC 82%,transparent) 0%,
      color-mix(in srgb,#E9E0DC 66%,transparent) 34%,
      color-mix(in srgb,#E9E0DC 30%,transparent) 48%,
      transparent 58%),
    /* 18%, COMPUTED NOT NUDGED. The section is now the form's height (about
       1358px at 1440), which is shorter than the frame's natural full-width
       height, so `cover` scales to the height and there is ~596px of horizontal
       crop to place. `right` threw away the empty left and put him at 28% to 58%,
       straight under the fact strip: the probe caught it as "In final
       production." at 4.35:1 on rgb(238,180,133), which is his orange collar.
       Placing the crop at 18% of that travel puts his left edge at ~62% of the
       viewport, clear of the 46vw type column and just past where the wash ends. */
    url("../img/contact-studio.jpg") 18% bottom / cover no-repeat,
    #D4C8C4;
}

/* ---- THE PHONE, UNCHANGED IN INTENT -----------------------------------------
   Still one column, so still no left and right to separate. The picture becomes
   a plate that closes the section and the words sit on the flat grey above it,
   which is the same answer as the first pass and the same one the poster block
   makes at this breakpoint. The ground stays pale here, so the type keeps the
   dark ink it has on desktop and the contrast holds without a scrim. */
.close-full::after{ content:none }
@media (max-width:900px){
  .close-full::before{ background:#D4C8C4 }
  .close-full{ padding-block-end:0 }
  .close-full::after{
    content:""; display:block; width:100%; aspect-ratio:4 / 5;
    margin-top:clamp(2.5rem,9vw,3.5rem);
    background:url("../img/contact-studio.jpg") 62% bottom / cover no-repeat;
    /* LIFTED ABOVE THE PALE GROUND, 2026-08-17. Rokas/Max, on a phone: "white space
       at the bottom of the contact." The plate is a STATIC block, so it painted in
       normal flow BEFORE `.close-full::before` (a positioned z-index:0 layer that
       covers the whole section), and the pale ground painted straight over the
       photograph, leaving an empty pale rectangle the height of the plate. Making the
       plate positioned with a z-index puts it back on top, so the photograph shows
       and the section closes on an image rather than a void. */
    position:relative; z-index:1;
  }
}

/* ---- THE PLAIN CONTACT (contact.html) --------------------------------------
   Added 2026-08-16. Rokas: "make the actual contact page to be without the photo
   background", and the home page keeps its photograph.

   IT IS FOUR LINES BECAUSE IT IS AN ABSENCE, not a second design. Everything the
   pale version does is now scoped to `.close-full` above, so this variant already
   arrives on the page's own black ground with the site's own ink and needs no
   token of its own. What is left is the two width caps, and both are RESTORED
   RATHER THAN INVENTED: 60rem and 48rem are the values this block ran at before
   2026-08-15, when the photograph narrowed them to keep the type off his jacket.
   Their own comments above record the reasoning and the fact that the narrowing
   was the picture's doing. No picture, no narrowing.

   NO TOP RULE. `.close` carries one, and `.close-full` turns it off. This section
   opens contact.html directly under a fixed header, where a hairline across the
   top of the page reads as a stray border rather than as a division between two
   sections. Same reason, same value. */
/* NO TOP RULE, AND IT IS ONE PAGE'S PROBLEM RATHER THAN THE COMPONENT'S.
   Added 2026-08-16 for music.html, where this section now follows a full-bleed
   photograph: `.close`'s hairline drawn under a picture that runs edge to edge
   reads as a seam in the image, not as the start of a new block. On about.html
   and ministry.html the block above is type and the rule is doing its job, so it
   stays there. The builder decides, per page, in `contactBrief`. */
.close-noline{ border-top:0 }
/* ---- .close-plate — the contact page's one photograph ---------------------
   Added 2026-08-17. Rokas: "remove the gallery on the contact page and insert one
   image that hasnt been used yet on the right (large) to fill space." The space
   is real and it was measured: at 1440 the heading, the three facts and the whole
   ten-field form live inside the left 810px of a 1368px frame, so the right 40%
   of a section about 1400px tall was empty black.

   IT WAS STICKY AND IT IS STATIC NOW, 2026-08-17. Rokas: "the image on the contact
   page should be static and not move with scrolling." Sticky was chosen the day
   before to fill the right column the whole way down without cropping a 2:3
   portrait to a 0.36 slice, and it did that — by travelling with the reader, which
   is the part he does not want. A photograph that slides against a form the reader
   is typing into is motion inside a task, and this site spends its motion on the
   scrubbed statements and nowhere else.

   WHAT IT COSTS, PLAINLY: black under the picture on a tall viewport. The form is
   taller than 74vh, so the plate now ends partway down the right column. That is
   the trade for a still frame, and the alternative (stretch to the form's height)
   is the vertical slice of a face this note has always ruled out.

   `grid-row:1 / -1` STAYS even though nothing travels any more. It is what makes
   the plate a peer of the whole left column rather than a child of the header row,
   and without it the picture's height inflates that row and opens a gap between
   the lede and the three facts — the exact bug the note below records.

   SCOPED TO THE SECTION THAT ASKS FOR IT. index.html builds this same component
   with `close-full`, where the section IS a photograph already. */
/* THE THREE ROWS ARE DECLARED, AND THAT IS NOT TIDINESS EITHER. `grid-row:1/-1`
   on the plate below resolves `-1` against the EXPLICIT grid, and with no
   explicit rows that is line 1 — so the span collapsed to row 1 alone and the
   plate's 672px inflated the header row, opening 570px of black between the lede
   and the three facts. Measured in a capture, not read off the spec. Three
   declared rows (head, facts, form) make `-1` mean what it says. */
/* THE RIGHT COLUMN WENT 24rem -> 30rem AND THE PLATE 42rem -> 58rem, 2026-08-17,
   AND THAT IS THE SECOND HALF OF KILLING THE STICKY. Held still at the old size
   the picture ran 384x666 against a 1279px form, so it stopped level with the
   radio buttons and left 613px of black under it — which is what "fill space"
   was asking about in the first place (Rokas, 2026-08-16: "insert one image that
   hasnt been used yet on the right (large) to fill space").
   480x828 is 0.58 against the source's 0.67, so it is a light crop rather than
   the vertical slice, and it puts his face, the chain and both hands in the frame
   instead of a head and a shoulder. Measured in a capture at 1440, then read.

   IT IS A clamp AND NOT A FLAT 30rem, AND A CAPTURE AT 1000px IS WHY. Held flat,
   480px of a 940px frame is half the row, and the form beside it broke: "Church or
   organisation" wrapped onto two lines, the three facts fell from one row to two,
   and every field lost about 100px. 33vw hits 30rem at 1440 and lets go on the way
   down. It is also WIDER than the 24rem this replaced at every width above the
   breakpoint, so the small-desktop case is better than it was before today, not
   merely no worse. */
.close-plate .frame{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,clamp(18rem,33vw,30rem));
  grid-template-rows:auto auto auto;
  column-gap:clamp(2rem,5vw,4.5rem); align-items:start
}
.close-plate .page-head,
.close-plate .c-info,
.close-plate .c-form{ grid-column:1 }
.c-plate{
  /* THE HEIGHT IS A RATIO, NOT A VIEWPORT FRACTION, 2026-08-17. `min(74vh,42rem)`
     was a height picked independently of the width, so the crop changed with the
     window: at 480 wide it was 0.58, at 330 wide it was 0.40, which is the slice
     this component exists to avoid. `aspect-ratio` derives the height from
     whatever the column resolves to, so the frame holds 0.58 at every width and
     the picture is the same picture on every screen. The phone overrides it to
     4/5 in the query below. */
  grid-column:2; grid-row:1 / -1; margin:0; align-self:start;
  aspect-ratio:.58; overflow:hidden
}
.c-plate img{
  width:100%; height:100%; display:block;
  object-fit:cover; object-position:50% 26%
}
@media (max-width:900px){
  /* One column, and the photograph takes its own ratio back rather than a
     viewport height, which on a phone is the whole screen.
     `position:static` WAS HERE AND CAME OFF WITH THE STICKY, 2026-08-17. It was
     the phone's opt-out of a behaviour the desktop no longer has either.

     THE RIGHT-EDGE CUT-OFF FIX, 2026-08-17 (Rokas, on a phone: "image and form are
     both cut off on the right"). `1fr` is `minmax(auto,1fr)`, so the single column
     grew to the widest min-content inside it: a WebKit date/select control and the
     multi-column `.c-info` both report a large intrinsic minimum, and that dragged
     the whole `.frame` past the 390px phone. `.section{overflow-x:clip}` then hid
     the overflow as a cut edge rather than a scrollbar. minmax(0,1fr) caps the
     column at the container and min-width:0 lets the controls reflow to fit. */
  .close-plate .frame{
    grid-template-columns:minmax(0,1fr); grid-template-rows:none;
    row-gap:clamp(1.75rem,5vw,2.5rem)
  }
  .close-plate .page-head,
  .close-plate .c-plate,
  .close-plate .c-info,
  .close-plate .c-form{ grid-column:1; min-width:0; max-width:100% }
  .c-plate{ grid-row:auto; aspect-ratio:4 / 5 }
  .f-two,.f-field,.f-in,.f-sel,.f-sel select{ min-width:0; max-width:100% }
  /* THE DATE FIELD STILL OVERRAN THE COLUMN AFTER THAT, 2026-08-17 (Rokas, on an
     iPhone: "the event date box gets cut off on the right side"). On iOS Safari a
     bare <input type=date> carries a native intrinsic min-width, the room the
     control reserves for MM/DD/YYYY plus its own furniture, and that minimum wins
     over min-width:0 and max-width:100% both, so the empty box grew past the phone
     column and .section{overflow-x:clip} sheared off its right edge, border and
     all. Only min-width:0 was never going to reach it; the intrinsic size comes
     from -webkit-appearance, so shedding the native appearance is what lets the
     field obey the container. Phone-scoped: desktop has room for the native
     control and keeps it. Tapping still opens the iOS date wheel, which iOS gives
     every date input regardless of appearance, so no affordance is lost. */
  .f-in[type="date"]{ -webkit-appearance:none; appearance:none }
}
/* ---- contact.html's OWN RHYTHM, tighter than the home page's ---------------
   Added 2026-08-17, the second half of "move everything else up so you dont need
   to scroll on this page."

   THE LEDE COMING OFF ONLY BOUGHT 111px AND THE GAPS WERE THE BIGGER SPEND. The
   band was measured at 1440x780 before touching anything: heading 92-177, ledes to
   288, then 128px of nothing before the three facts at 416, then 72px more before
   the form at 565. Two gaps, 200px, carrying no content.

   `.c-info` and `.c-form` set those gaps and BOTH ARE SHARED WITH index.html's
   closing band, which is why this is scoped to `.close-plate` rather than edited at
   source. On the home page the same gaps are correct: that band arrives after a
   full page of scrolling and its air is what separates it from the page above.
   Here they sit between a heading and the form the page exists for.

   NOT ZEROED, HALVED. 80px -> 40px and 72px -> 44px keeps the three-part reading
   (who to ask, the facts, the form) legible as three parts. At zero it reads as one
   undifferentiated column, which is a different fault, not a fix. */
.close-plate .c-info{ margin-top:clamp(1.75rem,3vw,2.5rem) }
.close-plate .c-form{ margin-top:clamp(1.75rem,3.2vw,2.75rem) }

.close-plain{ border-top:0 }
.close-plain .c-info{ max-width:60rem }
.close-plain .c-form{ max-width:48rem }
/* THE SUBMIT'S INK, AND IT IS A REAL BUG THIS VARIANT WOULD OTHERWISE SHIP.
   `.c-submit` fills with `var(--ink)` and hardcodes its label to `#F2EBE7`,
   which is correct on the pale variant where --ink is Covenant's dark blue. On
   this ground --ink is the site's own ivory, so the fill and the label are the
   same colour and the button reads as an empty bar: caught in the first capture
   at roughly 1.05:1, not by reasoning about it. `var(--ground)` is what
   `.btn-solid` uses for exactly this reason and resolves to the page's black
   here, so the one control on the page inverts correctly with its ground
   instead of being pinned to a colour that only suits the other variant. */
.close-plain .c-submit{ color:var(--ground) }

/* ---- #gallery — the frames under the form (contact.html only) ---------------
   Added 2026-08-16 with `.close-plain`, and the two are one instruction: the
   photograph stops being the page's ground and comes back as pictures. Markup
   and the picture-by-picture reasoning are in parts/contact-gallery.html.

   JUSTIFIED ROWS, SOLVED BY flex-grow. `flex: var(--ar) 1 0` gives each figure a
   zero basis and a grow factor equal to its own aspect ratio, so the free space
   is shared in proportion to width-over-height and every frame in a row resolves
   to the SAME height with the row filling the frame exactly. The row height is
   therefore an OUTPUT, not a number anyone types, which is what keeps the rows
   correct when an image is swapped. --ar is set per figure in the markup because
   it is a fact about that file, not a style.

   THE BAND BUDGET IS WHY THE PADDING IS ITS OWN. The standing rule is that no
   band is taller than a 1440x960 screen (verify/fits-screen.js). Six frames in
   two justified rows measure about 855px inside a 1368 frame, which leaves
   roughly 100px for the section's own air; `.section`'s default 84px top is
   HEADER CLEARANCE and this section is nowhere near the header, so spending it
   here would buy nothing and push the band over. The form's own generous
   padding-bottom sits directly above, so the gap the reader sees is the sum of
   the two and is wider than this number alone suggests. */
.gal{ padding-block: clamp(2rem,3.5vw,3rem) clamp(2rem,2.8vw,2.75rem) }
.gal-row{ display:flex; gap:var(--gal-gap) }
.gal-row + .gal-row{ margin-top:var(--gal-gap) }
.gal{ --gal-gap: clamp(.5rem,1vw,.875rem) }
.gal-f{ flex: var(--ar) 1 0; margin:0; min-width:0 }
.gal-f img{ display:block; width:100%; height:auto }
/* B&W IN PLACE, 2026-08-19. Rokas: "replace the two music pictures in the gallery
   with the B&W ones ... to keep it on theme." The two frames from the dramatic
   music shoot (crown-gaze, smoke-daffodils) are the only colour that reads as the
   artist persona rather than the ministry. There are only two graded B&W files on
   the site and both are spoken for (bw-thought is already in this gallery, bw-fist
   is the music page's #consecration ground), so rather than reuse a music-page
   frame these two are rendered monochrome here. The gold crown and blue smoke read
   as reverent in grey; a hair of added contrast keeps the greyscale from going
   flat. */
.gal-mono img{ filter:grayscale(1) contrast(1.04) }
/* ---- .mgal-wide — a landscape window on an upright file, 2026-08-16 --------
   ministry.html's gallery was given a register with no landscape in it: the
   frames Rokas asked for are the table set, the knit set and the record wall, and
   all of them are 2:3 uprights. Six uprights in two rows of three solve to two
   rows of one height, which contact.html's own gallery note calls a table of
   thumbnails. These two are cropped in CSS rather than re-cut, so there is still
   one file per picture and the crop can be moved by editing one number.
   THE TWO CHOSEN ARE THE ONLY TWO THAT SURVIVE IT. He is head-down laughing over
   a table in one and sitting on the floor with his knees up in the other, so both
   are horizontal compositions that happen to live in an upright file.
   `wall-stand` was tried and reverted: he is standing full length, so every wide
   window cuts him at the knee.
   The anchors are per image and both keep the SUBJECT rather than the centre. */
.mgal-wide img{ aspect-ratio:3 / 2; object-fit:cover }
/* Each anchor is derived, not guessed. `object-position` is a percentage of the
   SLACK, and a 3:2 window on a 2:3 file leaves 55.6% of the height as slack while
   showing 44.4%, so the visible band runs from (P x 55.6) to (P x 55.6 + 44.4).
   Subject spans were read off a percentage grid drawn over each frame:
     table-laugh  head 18 to hand 90  -> band 21-65   at P=38
     table-chin   head 10 to arm 82   -> band 12-56   at P=22
     table-aside  head 38 to hand 88  -> band 35-79   at P=63 */
.mgal-laugh img{ object-position:50% 38% }
.mgal-chin  img{ object-position:50% 22% }
.mgal-aside img{ object-position:50% 63% }
/* `knit-seated` is a mid-shot: head 12 to 42 per cent, arm and hand down at 60.
   A 3:2 window opened at 12 keeps the arm in, which is what stops it reading as a
   second close portrait beside `knit-stool` in the row above. */
.mgal-seated img{ object-position:50% 22% }

/* ---- The phone -------------------------------------------------------------
   A justified row of three collapses to about 170px a frame at 390, which is a
   thumbnail rather than a photograph. So the rows stop being justified and the
   six frames become two even columns, and every frame takes the SAME 4:5 crop:
   with equal widths and mixed aspect ratios the alternative is a ragged row of
   unequal heights, which reads as a broken grid rather than as a gallery.
   `cover` on a 4:5 window keeps the whole of every portrait and takes the sides
   off the landscapes, which is the axis those frames have room to lose.

   THE GRID IS ON THE WRAPPER, NOT ON EACH ROW, and that is the fix for a defect
   the first version shipped: making each row a two-column grid laid its THREE
   figures out as two-plus-one and left a hole at the end of every row, twice
   down the page. `display:contents` dissolves the row boxes so all six figures
   are direct grid items of one wrapper and fill three even rows. It is also why
   the row's own top margin needs no reset here: an element with `contents`
   generates no box, so it has nothing to apply a margin to. */
@media (max-width:760px){
  .gal-grid{
    display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--gal-gap)
  }
  .gal-row{ display:contents }
  .gal-f{ aspect-ratio:4 / 5 }
  .gal-f img{ height:100%; object-fit:cover }
}

/* ---- #gallery: ONE EVEN ROW OF THREE, at every width -----------------------
   2026-08-17. Rokas, on the ministry page: "make the 3 images in a line, bigger
   rather than having 3 and not being symmetrical." The gallery was proportional
   flex on desktop (an intentional "altering sizes" row, `.gal-f{flex:var(--ar)}`)
   and the shared phone rule above laid its three frames out as 2+1 with a hole.
   Both read as uneven. This scopes #gallery to three equal columns with one uniform
   crop, so the row is level everywhere and each frame is a third of the frame width.
   The wide landscape middle (mgal-wide) is pulled back into the same portrait crop;
   its object-position already holds his face, so a level row wins over the landscape
   window here. It wins by specificity, so its position after the phone rule is not
   load-bearing. */
#gallery .gal-grid{ display:block }
#gallery .gal-row{ display:flex; gap:var(--gal-gap) }
#gallery .gal-f{ flex:1 1 0; aspect-ratio:3 / 4; overflow:hidden; min-width:0 }
#gallery .gal-f img{ width:100%; height:100%; object-fit:cover }
@media (max-width:760px){
  /* Open the gutter and tighten the gap so three-across is as big as a phone allows. */
  #gallery .frame{ width:calc(100% - 1rem) }
  #gallery .gal-row{ gap:.4rem }
}

/* ---- .g-page — the standalone gallery, 2026-08-19 -------------------------
   Rokas: "move all of his gallery images to a separate gallery page ... make it
   like a lovely scrolling experience wheres some images are a little delayed and
   nicely animated." His best fifteen, mixed from the music and ministry sets plus
   the black-and-white frames, in two columns that arrive as they are scrolled to
   and drift at their own gentle rates (js/gallery.js). Own classes, not the ministry
   `.gal-*` grid above, because that one is a fixed even row and this is a masonry.
   Immersive measure, a touch wider than the reading frame. */
/* The bottom pad gives the column drift room to land in: at the foot of the scroll
   the left column is pushed down, and without this it would reach the contact block
   below. Sized past the max drift (half the larger data-speed in the part). */
.g-wrap{ width:min(100% - 2.5rem, 1240px); margin-inline:auto; padding-block-end:clamp(2.5rem,5vw,4.5rem) }
.g-grid{
  display:grid; grid-template-columns:1fr 1fr;
  column-gap:clamp(1rem,2.2vw,2.25rem); align-items:start
}
.g-col{ display:flex; flex-direction:column; gap:clamp(1rem,2.2vw,2.25rem); will-change:transform }
/* The right column starts lower so the two never land level: the offset that makes
   two columns read as a composition rather than a table, and the reason a frame on
   one side always arrives a beat after its neighbour. */
.g-col-b{ margin-top:clamp(2.5rem,7vw,6.5rem) }
.g-fig{ margin:0; overflow:hidden; border-radius:2px; background:var(--line) }
.g-fig img{ display:block; width:100%; height:auto }
.g-fig.g-mono img{ filter:grayscale(1) contrast(1.04) }

/* The arrival: fade, a 52px rise, a hair of scale, on the site's own ease. The
   pre-state is armed by js/gallery.js on <html>; the finished state below is what
   renders with no JS, so no-JS and reduced motion show every frame in place. */
.ganim .g-fig{ opacity:0; transform:translate3d(0,52px,0) scale(.955) }
.ganim-ready .g-fig{
  transition:opacity 1s var(--ease-arrive), transform 1s var(--ease-arrive)
}
.g-fig.g-in{ opacity:1; transform:none }

@media (max-width:760px){
  .g-grid{ grid-template-columns:1fr; row-gap:clamp(1rem,4vw,1.5rem) }
  .g-col{ gap:clamp(1rem,4vw,1.5rem) }
  .g-col-b{ margin-top:0 }
}
@media (prefers-reduced-motion: reduce){
  .g-fig{ opacity:1 !important; transform:none !important; transition:none !important }
  .g-col{ transform:none !important; will-change:auto }
}

/* ---- THE CONTACT, REBUILT ON THE dsgngroup.it PATTERN, 2026-08-15 -----------
   `.close-cols` (four link columns across the frame) was replaced by a centred
   single column: heading, lede, a three-fact strip, then the booking form. The
   take/leave against the reference is written out in parts/contact-cols.html.

   LEFT ALIGNED, REVERSED 2026-08-15 BY ROKAS: "we can make it left side alignment
   - middle doesnt suit the website."

   THE FIRST VERSION WAS CENTRED and the comment here argued for it: every other
   section is left aligned, so centring the last one would make it read as an
   ending. That argument was wrong in the way a reference port usually goes wrong.
   The centring is the strongest thing about dsgngroup's page, but it is strong
   THERE because their whole site is centred and symmetrical. This site is built
   on one hard left edge that every heading, rail and paragraph hangs from, hero
   to footer. Importing their axis put the last section at odds with the eleven
   above it, which is the reference-copied-rather-than-ported failure the craft
   rule names. What actually ports is the composition, the strip and the form
   proportion; the axis belongs to the host.

   SO THE ONLY THING THAT CHANGED IS THE AXIS. The strip is still wider than the
   form, both are still capped, and they now hang from the same left edge as the
   heading above them and every other section on the page. */
.close-head{ justify-items:start; text-align:left }
/* The functional line is quieter and wider than the lede above it: it is an
   instruction, not a summary, and it has to fit a sentence with two clauses. */
.close-lede{ max-width:66ch; color:var(--ink-faint) }

/* ---- The three facts -------------------------------------------------------
   `auto-fit` with a floor rather than `repeat(3,1fr)`: three is the count today,
   and a fourth fact (a phone number, a management address) is a plausible edit
   that should not require touching this rule. It also collapses on its own at
   narrow widths without a second breakpoint. */
/* CAPPED, NOT RUN TO THE FRAME, and the cap survives the switch to left
   alignment. Measured off the reference rather than guessed: their strip spans
   about 71% of the viewport and their form about 57%, so the strip is wider than
   the form and the two still read as one column. Run edge to edge in this site's
   1368px frame the three facts strand themselves at the corners while the form
   sits narrow beside them, which reads as two compositions rather than one. That
   was true when the block was centred and it is still true against the left edge,
   which is why 60rem against a 48rem form is kept rather than dropped with the
   centring. */
/* NARROWED TO THE FORM'S WIDTH, 2026-08-15, BY THE PHOTOGRAPH. At 60rem the
   third fact ("The record") ran to 69% of the frame, out past where the scrim
   has to release him, and the probe read it at 2.08:1 on the lit wall. At 40rem
   all three sit inside the protected zone. The composition is better for it as
   well: the strip and the form now share one left column instead of being two
   different widths stacked, which is the thing the centred version got right by
   accident and the first left-aligned version lost. */
/* `min(40rem,46vw)` AND NOT A FIXED 40rem. A fixed cap is a bigger share of a
   narrow viewport than of a wide one, so at 1024 the strip and form ran to 66%
   of the frame while the scrim releases him at 63%, and the probe read six
   labels under the floor at 1024 and five at 950. Tying the cap to the viewport
   keeps the same proportion of empty backdrop beside him at every width, which
   is the thing that actually has to stay constant. Measured after: 0 failures at
   950, 1024, 1280, 1440 and 1920. */
.c-info{
  list-style:none; margin:clamp(3rem,6vw,5rem) 0 0; padding:0; max-width:min(40rem,46vw);
  display:grid; grid-template-columns:repeat(auto-fit,minmax(10rem,1fr));
  gap:clamp(1.25rem,2.5vw,2rem); text-align:left
}
.c-info-col{ display:grid; gap:.4rem; align-content:start }
/* The label carries the whole labelling job, which is why the reference's
   circular icon badge above it was left behind rather than redrawn. */
.c-info-lab{ margin-bottom:.35rem }
.c-info .f-line{ margin:0 }
/* The second line of each fact is the qualifier, not the fact, so it steps back
   the way the lede does. */
.c-info-2{ color:var(--ink-faint) }

/* ---- The form --------------------------------------------------------------
   CAPPED INSIDE A WIDE FRAME, which is the reference's proportion: their form
   runs about 820px inside a 1440 page, so the empty margin beside it is doing as
   much composition work as the fields are. 48rem holds that relationship against
   this site's 1400px frame. The cap is the part that ported; after 2026-08-15 the
   empty margin is all on one side, because the block hangs from the left edge
   with everything else rather than being centred in the frame. */
/* NARROWED FROM 48rem TO 40rem, 2026-08-15, BY THE PHOTOGRAPH. He stands from
   about 49% of the frame; at 48rem the form's right column ran to 56% and its
   input rules crossed his jacket, which is the one thing the instruction ruled
   out ("obviously, not blocking trady"). 40rem ends at about 47% of a 1440
   frame, inside the empty studio backdrop beside him. This is still "spread
   across the image": the empty half IS the left half, so a form that fills it is
   spread across all of the picture that is not him. */
.c-form{ max-width:min(40rem,46vw); margin:clamp(3rem,6vw,4.5rem) 0 0; text-align:left }
/* Two-up for the short pairs, one-up under 620px. The pairing is by meaning and
   is set in the markup; this rule only decides when it survives. */
.f-two{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:0 clamp(1rem,2.5vw,1.75rem) }
@media (max-width:620px){ .f-two{ grid-template-columns:1fr } }
/* FULL WIDTH ON THE PHONE, 2026-08-17. Rokas, on the home page: "make the contact
   entries a bit bigger, like across the screen, so it's not just very narrow ...
   match the contact form on the home page to be like the contact page." The
   `min(40rem,46vw)` cap on both is a DESKTOP composition: it keeps the form in the
   empty studio half beside his photograph. On a phone the block stacks, there is no
   photograph beside it, and 46vw is just 179px of needless narrowness. So the cap
   comes off below the breakpoint, on the home band and the contact page alike. */
@media (max-width:900px){
  .c-info, .c-form{ max-width:100% }
}
/* Full width and SOLID, both taken from the reference, whose submit is a filled
   white bar across the form. `.submit` is the site's shared form button and is
   outlined; it stays outlined on ministry.html, because the room's form is one
   control among several on a long page. Here the form IS the page, so its one
   action is the page's primary action, and the site already has a word for that:
   the same ivory fill and black text `.btn-solid` gives the hero's "Get in touch"
   and the turn's "Book him to speak". So this reads as the reference AND as the
   third instance of a control this site already had, rather than as an import.
   Hover inverts to the outline it came from, which is `.btn`'s behaviour run
   backwards and keeps the pair legible as one family. */
.c-submit{
  width:100%; justify-content:center; margin-top:clamp(.75rem,1.6vw,1.25rem);
  background:var(--ink); color:#F2EBE7; border-color:var(--ink)
}
.c-submit:hover,
.c-submit:focus-visible{ background:transparent; color:var(--ink) }

/* ---- CONDENSED, so the form is as tall as the picture ----------------------
   Rokas: "make the form itself be as tall as the image itself ... you can
   condense down the form." The section is now the photograph's own height, so
   the form has to fit that rather than set it.

   SPACING, NOT FIELDS. Every field here was reasoned against his brief (why the
   event kind is a radio and not a select, why budget is free text and not
   brackets), so the condensing is taken out of the air between rows and none of
   it out of what is asked. A field removed to save 40px is a booking that
   arrives without the answer.

   SCOPED TO `.close-full` WITH THE PHOTOGRAPH IT SERVES, 2026-08-16. Every value
   below is a concession to a height this block does not set: the form is squeezed
   so it ends where the picture does. contact.html has no picture, so inheriting
   the squeeze would leave it carrying the compromise without the reason, and a
   form crammed to fit an image that is not there is worse than either version. It
   takes the site's normal form rhythm instead, which is the same rhythm the room
   on ministry.html uses. */
.close-full .f-field{ margin-bottom:.7rem }
.close-full .f-set{ margin-bottom:.7rem }
.close-full .f-hint{ margin-top:.15rem }
.close-full .f-opt{ margin-top:.3rem }
.close-full textarea.f-in{ min-height:0 }
/* The two ledes become one block with less air under the heading: on a section
   whose height is set by a photograph, the heading group is the only part that
   can give room back without touching the questions. */
.close-full .page-head{ margin-block-end:clamp(1.25rem,2.4vw,1.75rem) }
.close-full .page-lede{ margin-bottom:.35rem }
.close-full .c-info{ margin-top:clamp(1.5rem,3vw,2.25rem) }
.close-full .c-form{ margin-top:clamp(1.5rem,3vw,2.25rem) }

/* Column labels. NOT headings — labels, which is what they always were.
   Uppercase is applied by CSS and the SOURCE stays in sentence case, so no
   all-caps string exists in the markup and a screen reader is never handed
   shouting. Same rule the rig's rails run under. */
.rail{
  font-size:12px; line-height:1.5; text-transform:uppercase; letter-spacing:.16em;
  font-variant-numeric:tabular-nums; color:var(--ink-faint); margin:0;
  /* Progressive: where a rail is forced to wrap (390, where the professions run
     is 388.09px against a 350px frame) this splits it into two even lines instead
     of stranding one word. Older WebKit ignores it and wraps the way it does now,
     which is untidy but never broken, so it is an enhancement and not a load
     bearing rule. */
  text-wrap:balance
}
.f-lab{ margin-bottom:1.15rem }

.f-list{ list-style:none; margin:0; padding:0 }
.f-list > li + li{ margin-top:.55rem }
.f-line{ margin:0; font-size:14px; line-height:1.5 }
.f-dim{ color:var(--ink-dim) }

/* Routes. The arrow answers, it does not decorate: 4px on the same --ease-respond
   the gate brackets use, so every responding element on the page moves the same
   way. Underline on hover rather than a colour change, because the colour cap for
   this section is ZERO accents and ink cannot signal state by turning into itself.

   display:inline, NOT inline-flex. FOUND BY LOOKING at 390: as a flex box the text
   and the arrow are two flex ITEMS, so "Speaking one-sheet (PDF)" wrapped inside
   its own item and left the arrow stranded at the top right, pointing at nothing.
   Inline, the arrow is part of the sentence and follows the last word wherever it
   lands. */
.f-go{
  font-size:14px; line-height:1.5; color:var(--ink);
  /* WCAG 2.2 AA 2.5.8, and it is one rule for EVERY route in the footer rather
     than one for the stream. Each of these is a standalone list item with no
     non-target text on its line, so the inline exception does not apply and
     24x24 is a floor. 14px at 1.5 is a 21px line box, so the target carries the
     difference as padding-block instead of as line-height: the column's type
     rhythm is unchanged and only the touchable box grows.
     FOUND BY MEASURING at 390: `Have him in the room` and `Be told first` were
     17.4px tall while the five stream rows underneath them got 44. The two
     commercially loaded routes in the footer were the smallest touchable things
     in it. Accessibility is a stated differentiator of the brief. */
  display:inline-block; padding-block:2px
}
/* The gap before the arrow is a NON-BREAKING SPACE in the markup, not a margin,
   so the arrow can never be left on a line by itself. FOUND BY LOOKING at 390:
   "Speaking one-sheet (PDF)" measures 160.61px against a 163px column, so it fit
   and the arrow alone wrapped, leaving a route pointing at nothing on line two.
   Now the break falls before "(PDF)" and the arrow travels with it. */
.f-go .arw{ display:inline-block; margin-left:.15em; transition:transform .6s var(--ease-respond) }
.f-go:hover{ text-decoration:underline; text-underline-offset:.3em }
.f-go:hover .arw{ transform:translateX(4px) }

/* The count. Atkinson 400 ITALIC, 12px, parenthesised, zero-padded, tabular,
   translate(0,12%) — §3.0's spec, and the ONE piece of instrument grammar the
   close keeps, because §3.4 asks for these numbers a second time, live, which is
   what makes them read as an instrument rather than as decoration.
   inline-block because a transform does nothing to an inline box. */
.ct{
  font-style:italic; font-size:12px; font-variant-numeric:tabular-nums;
  color:var(--ink-faint); display:inline-block; transform:translate(0,12%)
}

/* THE STREAM. No label track: the count sits tight against its own label, one
   run of text per row, and the block reads as an index.

   TWO SHAPES BEFORE THIS ONE, BOTH READ AS A TABLE RATHER THAN AS AN INDEX, both
   found by reading the screenshot and not by any check:
   `1fr auto` threw the figures ~250px right of their labels, hard against THE
     LEDGER's first line, so at 1440 they read as that column's numbers.
   a shared 86px label track (widest label 70.11 + a 16px gutter) aligned the five
     parentheses in one column, but left ~235px of dead field to the right of every
     count at 1440 and ~245px at 390. A label, a gap, a figure and then nothing
     sets up a third column that never arrives.
   Bergh's per-item nudge aligns his parentheses because they are absolutely
   positioned inside a 145.95px widget. At footer-column width that alignment is a
   PROP, and §3.4's close keeps the type system (12px italic parenthesised counts)
   while dropping the props. Tight to the label is the same instrument, ended. */

/* THE LEDGER. Every absence on the site, stated once. --ink-dim, not --ink: it is
   the only column that converts nothing and asks for nothing, and it should not
   compete with three columns that do. No rules between the rows — the other three
   columns have none, and giving the ledger furniture the rest of the footer does
   not have would turn a statement into a warning panel. */
.f-ledger li + li{ margin-top:.5rem }

/* The base row. One size, one case, both ends on the frame's edges. Space alone
   separates it from the columns: a second hairline here would imply a second grid
   and §3.4 allows exactly one rule. */
.f-base{
  display:flex; justify-content:space-between; align-items:baseline;
  gap:1rem 2rem; flex-wrap:wrap; margin-top:clamp(2.5rem,4vw,3.75rem)
}

/* --- 390 --------------------------------------------------------------------
   §3.4: ENQUIRE and THE RECORD side by side, THE STREAM and THE LEDGER full width
   beneath, base row wrapped to two lines and left-aligned. Nothing sticky, nothing
   fixed. Breakpoint is 900px, which is where the header itself collapses, so the
   page changes shape once rather than twice.
   EVERY route goes to 44px here, not just the stream's. */
@media (max-width:900px){
  .close-cols{ grid-template-columns:1fr 1fr; gap:2.25rem 1.5rem }
  .close-cols > .f-wide{ grid-column:1 / -1 }
  /* .72rem top and bottom on a 21px line box is 44.04px. The enquiry route and
     the album route do not get to be smaller than a stream filter. */
  .f-go{ padding-block:.72rem }
  /* 44px rows ARE the rhythm here, so the list's own row gap comes off. With both,
     the five lenses ran a 53px pitch and took 265px of a phone to say five words. */
  .f-stream > li + li{ margin-top:0 }
  .f-base{ flex-direction:column; align-items:flex-start; gap:.6rem }
}

@media (prefers-reduced-motion: reduce){
  .f-go .arw{ transition:none }
}

/* ============================================================================
   THE MOBILE MENU, added 2026-08-14 with the split into pages.
   Everything here is scoped to [data-nav="open"], so with the script absent or
   thrown the header renders exactly as it did before: lion, hamburger, nothing
   else. The closed state is untouched.
   ========================================================================= */
@keyframes navReveal{ from{ opacity:0; transform:translateY(-10px) } to{ opacity:1; transform:none } }
@media (max-width:900px){
  /* The row floats over the hero film on the home page and over a section
     ground everywhere else. Open, it has to carry four links and four glyphs,
     so it takes a ground of its own rather than trusting whatever is behind. */
  [data-nav="open"] .top{
    background:color-mix(in srgb, var(--ground) 96%, transparent);
    backdrop-filter:blur(10px);
    padding-bottom:clamp(1.25rem,3vh,2rem)
  }
  [data-nav="open"] .top-in{ row-gap:clamp(.75rem,2vh,1.25rem) }

  /* `order` places these AFTER the lion and the button even though .nav is the
     first child in the DOM — which is the order that must not change, because
     the nav has to be the first thing a screen reader and a keyboard meet. */
  [data-nav="open"] .nav{
    display:flex; flex-direction:column; align-items:flex-start;
    gap:0; grid-column:1 / -1; order:3
  }
  [data-nav="open"] .social{
    display:flex; justify-content:flex-start;
    grid-column:1 / -1; order:4
  }

  /* var(--ink), NOT the #FFF the closed nav uses, AND THAT IS DELIBERATE.
     The white is a decision Rokas made about type sitting over the hero FILM
     (2026-08-13, "just put the letters as white"). This panel is not over the
     film, it is over a solid ground, and on Covenant that ground is ivory —
     white here would be the same defect the header already has open on that
     direction. Following the token is correct on all three. */
  [data-nav="open"] .nav a,
  [data-nav="open"] .social a{ color:var(--ink) }
  [data-nav="open"] .nav a{
    font-size:1rem; letter-spacing:.12em; padding:.7rem 0; width:100%
  }
  /* THE MENU COMES OUT, IT DOES NOT SNAP, 2026-08-17. Rokas: "on mobile the nav
     should come out more smoothly rather than just appear." Toggling `.nav` from
     display:none to flex cannot transition, but a keyframe ANIMATION runs the
     moment the element is displayed, so each link and the social row fade up in a
     short stagger when the panel opens. No @starting-style needed; on an engine
     without keyframe support the worst case is the old instant appear. */
  [data-nav="open"] .nav a{ animation:navReveal .34s var(--ease-arrive) both }
  [data-nav="open"] .nav a:nth-of-type(1){ animation-delay:.04s }
  [data-nav="open"] .nav a:nth-of-type(2){ animation-delay:.09s }
  [data-nav="open"] .nav a:nth-of-type(3){ animation-delay:.14s }
  [data-nav="open"] .nav a:nth-of-type(4){ animation-delay:.19s }
  [data-nav="open"] .social{ animation:navReveal .34s var(--ease-arrive) both; animation-delay:.23s }
  [data-nav="open"] .nav a:hover,
  [data-nav="open"] .nav a:focus-visible{ color:var(--ink) }
  /* The glyph shadow exists to survive the film. Over a solid ground it just
     smears the icon, so it comes off here. */
  [data-navshadow="on"][data-nav="open"] .nav a,
  [data-navshadow="on"][data-nav="open"] .social a{ filter:none; text-shadow:none }
}

/* ---- Page chrome for the split ------------------------------------------
   `.page-head` is the top of every page that is not the home page: a short
   label and the page's own title, sitting where the hero sits on index.html.
   It exists so an inner page has a first screen of its own instead of opening
   mid-composition. */
/* ---- THE MINISTRY BAND -----------------------------------------------------
   The hinge of the home page: the record above it, the messages below it, one
   photograph of him preaching in between, with his mark in front of it.

   IT WAS A CUT FILM FOR ONE HOUR AND IS NOW A STILL. Rokas: "having a black
   section just doesn't really look good... let's just put a big lovely image of
   him doing ministry things and a logo in front of it." Two things went wrong
   with the film and the still fixes both: it opened and closed on black, so a
   third of its twenty seconds was a dark rectangle, and the scrim it needed to
   hold type over moving footage darkened the good frames as well. A still needs
   far less covering because it never changes.

   IT IS A SCREEN, NOT A STRIP. A turn from one half of a page to the other that
   does not fill the window is something you scroll past. It still obeys the
   fits-a-screen rule: 100svh less the header is exactly one screen.

   HE IS ALREADY OUT OF THE MARK'S WAY, so no object-position gymnastics. In the
   frame he sits at 65-85% of the width; at 1440 the mark is 354px centred, which
   spans 543-897. They do not touch. Measured, not eyeballed. */
.mband{
  position:relative; isolation:isolate; overflow:hidden;
  min-height:calc(100svh - var(--header-h));
  display:grid; place-items:center
}
/* THE <picture> WRAPPER MUST NOT BE A GRID ITEM, and this one line is the whole
   of "move the ministry logo up higher to be centred vertically and
   horizontally" (Rokas, 2026-08-15).
   .mband is `display:grid; place-items:center`. The image is absolutely
   positioned, so as a direct child it was out of flow and the block holding the
   mark was the ONLY grid item, which is what centred it. Wrapping the image in
   a <picture> on 2026-08-15 to serve the portrait file quietly added a static,
   in-flow child: two auto rows, each stretching to half the band, and the mark
   centred in the SECOND one. Measured at 390x844 it sat at y 303 in a 544 band
   whose centre is 272, and at 1440 it sat 87px low. It read as a design choice
   and it was a layout bug I introduced.
   FIXED BY STACKING, NOT BY COUNTING. `display:contents` on the wrapper was the
   first attempt and it did NOT work — the computed style said `contents` and the
   grid still reported two rows, `174.984px 641.016px`, with the mark centred in
   the second. Rather than keep guessing which child is creating the extra row,
   every in-flow child is put in the same cell. That is the stack pattern, and
   its whole virtue here is that it does not care how many children there are:
   add a <picture>, a <noscript>, a caption, and the block stays centred.
   The image and the scrim are absolutely positioned and unaffected. */
.mband > *{ grid-area:1 / 1 }
.mband-img{
  position:absolute; inset:0; z-index:0;
  width:100%; height:100%; object-fit:cover; object-position:50% 38%
}
/* A VIGNETTE, NOT A WASH, and that is the whole difference from the film's
   scrim. The film needed a heavy radial because its brightest frames were
   unpredictable; a still is one known picture, so this only has to hold the rail
   and seat the mark. The numbers are measured against THIS photograph — see the
   note on .mband-rail. */
.mband::after{
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    radial-gradient(58% 52% at 50% 46%, rgba(0,0,0,.46) 0%, rgba(0,0,0,.28) 60%, rgba(0,0,0,0) 100%),
    linear-gradient(to bottom, rgba(0,0,0,.42) 0%, rgba(0,0,0,0) 20%)
}
.mband-in{
  position:relative; z-index:2; display:grid; justify-items:center;
  gap:clamp(1.25rem,3vw,2rem); padding:var(--frame-pad); text-align:center
}
/* Ivory rather than var(--ink): this sits on a photograph on all three
   directions, exactly like the poster's type does, so it must not follow a
   palette that goes dark on Covenant. Full alpha and a glyph shadow, both
   measured — the value is in the README. */
.mband-rail{
  margin:0; color:#F7F1E6;
  text-shadow: 0 1px 2px rgba(0,0,0,.9), 0 0 8px rgba(0,0,0,.7)
}
.mband-mark{
  display:block; height:auto;
  width:clamp(11rem, 26vw, 22rem);
  /* The mark is a raster with soft mane gradients and no ground of its own, so
     it gets a shadow rather than a plate behind it. Same call the nav glyphs
     made when they measured 2.1:1 over the hero film. */
  filter: drop-shadow(0 8px 30px rgba(0,0,0,.5)) drop-shadow(0 2px 6px rgba(0,0,0,.45))
}

/* THE ANIMATION IS TWO MOVES AND BOTH ARE SLOW.
   1. It arrives: the mark settles from 1.06 to 1 and fades up, once, when the
      band comes into view. js/mband.js adds the class; without JS the mark is
      simply there, because the animation is an enhancement and never a gate —
      the same rule the hero intro follows.
   2. It breathes: 1 to 1.018 over eighteen seconds, forever. Eighteen because
      anything faster reads as a pulse and starts asking to be looked at, and
      this is a mark over a photograph, not a loader. */
@keyframes mband-settle{
  from{ opacity:0; transform:scale(1.06) }
  to  { opacity:1; transform:scale(1) }
}
@keyframes mband-breathe{
  0%,100%{ transform:scale(1) }
  50%    { transform:scale(1.018) }
}
.mband-mark{ animation: mband-breathe 18s ease-in-out infinite }
[data-mband="in"] .mband-mark{
  animation: mband-settle 1.4s var(--ease-respond, cubic-bezier(.2,.7,.2,1)) both,
             mband-breathe 18s ease-in-out 1.4s infinite
}

@media (prefers-reduced-motion: reduce){
  .mband-mark{ animation:none }
  [data-mband="in"] .mband-mark{ animation:none }
}
@media (max-width:900px){
  /* A 1.63:1 photograph in a portrait window crops to a column of the lectern,
     so the band gives up some height and the mark comes down with it. */
  .mband{ min-height:min(72svh, 34rem) }
  /* 23vw, DOWN FROM 42vw ON 2026-08-15, and it is arithmetic rather than taste.
     The mark is centred in the band now that the rail above it is gone, so it
     has to fit in the paper ABOVE him or it lands on his head. Measured: the
     band is 390x544 at 390x844, its centre is y 272, and the portrait cut puts
     the top of his head at y 356. The mark's aspect is 1097:1228, so a centred
     mark clears him while its width stays under about 150px. 42vw drew it at
     164px, spanning 260 to 445, which was his forehead. 30vw is 117px, spanning
     206 to 337, and it is a slightly larger share of the band than the desktop
     mark's 356 of 1440.
     A note for anyone re-cutting img/band-tall.jpg: this number and that
     composition are a pair, and they trade against each other. Move him UP the
     frame and this has to come down; the build script carries the same note and
     the arithmetic behind it. */
  .mband-mark{ width:clamp(5rem, 30vw, 14rem) }
  /* The phone crop lands the rail over a brighter strip of the picture, so the
     vignette deepens. Same reasoning, and the same arithmetic, as the film it
     replaced: the value is solved against the measured worst pixel, not picked. */
  .mband::after{
    background:
      radial-gradient(72% 58% at 50% 44%, rgba(0,0,0,.56) 0%, rgba(0,0,0,.40) 60%, rgba(0,0,0,.10) 100%),
      linear-gradient(to bottom, rgba(0,0,0,.42) 0%, rgba(0,0,0,0) 20%)
  }
}

/* ---- THE EIGHT THEME PAGES -------------------------------------------------
   One page per theme under Ministry, built 2026-08-14. The page has exactly four
   things on it and they are in order of who owns them: the theme's name, HIS
   sentence, the film, then our drafted prose. That order is the argument — the
   largest type on the page is the only text that is verbatim him.

   THE PAGE IS ONE COLUMN AND IT IS NARROW. Every other composition on this site
   is two columns or full bleed; this one is a reading page and it is the first
   one the site has had. 62ch, not the frame's 1368px, because a 1368px measure
   is about 130 characters a line and unreadable at reading size. */
.theme .page-head{ max-width:34ch }
@media (min-width:900px){
  /* His words and the theme's name on the left; the recording and our drafted
     prose on the right. See the note in build-pages.js for why these are two
     wrapper divs and not five children placed on one grid. */
  .theme-grid{
    display:grid; grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);
    column-gap:clamp(2.5rem,5vw,4.5rem); align-items:start
  }
}
.theme-line{
  margin:clamp(2.5rem,5vw,4rem) 0 0; max-width:26ch;
  /* Display size, and the only display type below the header on this page. */
  font-size:clamp(1.75rem,1rem + 2.6vw,3.25rem); line-height:1.14;
  letter-spacing:-.025em; color:var(--ink)
}
.theme-line p{ margin:0; text-wrap:pretty }
.theme-film{
  margin:clamp(2.5rem,5vw,4rem) 0 0; max-width:62ch;
  display:grid; gap:.9rem
}
/* 16:9 held by the box rather than by the file, so a poster that has not loaded
   cannot collapse the page and shift everything under it. */
.theme-film video{
  width:100%; aspect-ratio:16/9; display:block;
  background:var(--ground-2); border-radius:2px
}
.theme-film figcaption{ color:var(--ink-faint) }
/* The four that are not filmed say so here. It reads as a fact on the page, not
   as an error and not as a placeholder box waiting for an asset. */
.theme-nofilm{
  margin:clamp(2.5rem,5vw,4rem) 0 0; padding:1.15rem 0;
  border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  max-width:62ch; color:var(--ink-faint)
}
.theme-body{
  margin:clamp(2rem,4vw,3rem) 0 0; max-width:62ch;
  display:grid; gap:1.1rem
}
.theme-body p{
  margin:0; color:var(--ink-dim); text-wrap:pretty;
  font-size:clamp(1.0625rem,1rem + .38vw,1.25rem); line-height:1.6
}
/* Which of his four professions this theme belongs to. Same component as the
   stream's row tags, on purpose: it is the same fact in a second place, and the
   thesis only reads if the two agree. */
/* `.stags` is a stream component and carries the stream's placement with it:
   grid-column 1, grid-row 1 and text-align:right, which are correct inside a
   .srow and wrong everywhere else. On a theme page it is a line under the quote,
   so the placement is released and the alignment turned back to the left edge
   the rest of the column sits on. The component is reused rather than copied
   because it is the same fact in a second place. */
.theme-tags{
  grid-column:auto; grid-row:auto; text-align:left; white-space:normal;
  margin:clamp(1.75rem,3.5vw,2.5rem) 0 0
}

/* ---- THE BOOKING BLOCK AT THE FOOT OF EVERY THEME PAGE --------------------
   Three fields, closed off by a rule so it reads as the end of the page rather
   than as one more paragraph.

   IT WAS `--ground-2` (#15132A, navy) UNTIL 2026-08-16. Rokas: "the background
   on all of the pages should be black so its all uniform." Once the home page's
   pale contact band went black in the same pass, this was the last non-black
   ground anywhere on the site, and it was on EIGHT pages, which is why a probe
   across every page shape found it and reading any one page would not have.

   THE BORDER WAS ALREADY DOING THE JOB THE COLOUR WAS ADDED FOR. The note here
   used to say the block needed "its own ground" so it did not read as one more
   paragraph; it has a full-width rule above it, and that is what separates it.
   The panel is what changed, not the separation.

   CONTRAST MOVED THE RIGHT WAY, so nothing below needed re-measuring: the ink is
   unchanged and the ground went from #15132A to #000000, which raises every
   ratio in the block rather than lowering it. */
.ask-band{
  background:var(--ground);
  border-top:1px solid var(--line)
}
.ask-head{ display:grid; gap:clamp(.5rem,1.2vw,.85rem); max-width:44ch }
.ask-h{
  margin:0; font-size:clamp(1.5rem,1rem + 1.6vw,2.25rem); line-height:1.1;
  letter-spacing:-.02em; font-weight:400; text-transform:var(--display-case)
}
.ask-lede{ margin:0; color:var(--ink-dim) }
.ask-carry{
  margin:clamp(1.5rem,3vw,2rem) 0 1.25rem; color:var(--ink-faint);
  font-size:.9375rem
}
.ask-carry strong{ color:var(--ink); font-weight:400 }
.ask-form{ margin:0; max-width:62ch }
@media (min-width:900px){
  /* Name and email pair; the one about the event takes the full width because
     it is the field with something to say. Same rule as the full enquiry form,
     for the same reason. */
  .ask-form{
    display:grid; grid-template-columns:1fr 1fr; align-items:start;
    column-gap:clamp(1.5rem,3vw,2.5rem); max-width:none
  }
  .ask-form > .ask-carry,
  .ask-form > .ask-note,
  .ask-form > .submit{ grid-column:1 / -1 }
  .ask-form > .submit{ justify-self:start; min-width:16rem }
}
.ask-more{ margin:clamp(1.5rem,3vw,2rem) 0 0 }

.page-head{ display:grid; gap:clamp(.6rem,1.4vw,1rem) }
.page-h{
  margin:0; font-size:clamp(2rem,1.2rem + 3.2vw,4rem); line-height:1.02;
  letter-spacing:-.03em; text-transform:var(--display-case); font-weight:400
}
.page-lede{
  margin:0; max-width:52ch; color:var(--ink-dim);
  font-size:clamp(1rem,.95rem + .35vw,1.1875rem); line-height:1.6
}
/* WIDER OPENING LEDE ON MUSIC + MINISTRY, 2026-09-10. Rokas: "push the opening
   paragraph over more to the right, so it doesn't go down along vertically." The
   52ch cap held these two multi-sentence openers to a narrow ~5-line column while
   the statement below them runs the full frame, so they read as an orphan column.
   Widened so they spread right and take fewer lines. Scoped to these two openers
   only (`.music-open`, ministry's `#highlights`); every other lede keeps 52ch. */
.music-open .page-lede,
#highlights .page-lede{ max-width:70ch }
/* The first section on an inner page sits under the FIXED header with no hero
   to clear it, so it carries the header's height plus its own air. */
/* The air above the header clearance came down a step 2026-08-14 (the
   fits-a-screen pass): at 3vh it put #messages at 1.02 screens on ministry.html
   while the SAME section fits on the home page, which is a section failing to
   fit because of where it sits rather than what is in it. The header clearance
   itself is untouched. */
.page-top{ padding-block-start:calc(var(--header-h) + clamp(1rem,1.6vh,1.5rem)) }

/* ---- "Why this album exists": the pull-quote and the prose ---------------
   Rokas's reference (kimwalkersmith.com) is two columns: a large quote set left
   with its attribution under it, and a column of running prose to its right.
   Ported as a pattern, not a copy — theirs is a display serif on a warm ground,
   ours stays in Atkinson on the section ground, because the face is settled and
   a second one would be a brand change for Traidy rather than for us.

   THE QUOTE IS THE WIDER COLUMN, which is the reverse of the record above it.
   In the reference the quote is the louder half and the prose is the quiet half,
   and inverting the grid from the band above keeps two stacked two-column blocks
   from reading as one repeated shape. */
/* .9 / 1.1, REVERSED FROM 1.1 / .9 on 2026-08-14 ("move the paragraph to the
   middle and expand it a little"). The prose column's left edge is what "the
   middle" means here: narrowing the quote moves that edge inward and the extra
   width goes to the paragraph. */
/* `align-items` REPLACES a single shared alignment, 2026-08-14: "make the
   paragraph in the middle of the image height-wise" and leave the quote low. The
   block now fills the band's second row, so each column can take its own
   vertical position inside it — .saying-p centres, .saying-q sits at the end. */
.saying{
  display:grid; grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);
  gap:clamp(2rem,5vw,4.5rem); align-items:center;
  margin-top:clamp(2.5rem,5vw,4rem);
  /* Full width of .poster-in, which is itself capped at .frame's width. The
     poster's grid uses justify-items:start so its short lines stay snug; this
     block has to opt back out of that. */
  width:100%
}
/* ---- ...and in front of the photograph -----------------------------------
   INK IS HARDCODED IVORY IN HERE, exactly as the title and the prayer line
   above it are. --ink is a dark blue on Covenant and this block now sits on a
   near-black photograph on all three directions, so following the token would
   make the whole passage vanish on one of them. Same call, same reason, same
   place: #F7F1E6 is Traidy's own Precipitation ink, not a Vantage token.
   The alphas are lighter than the token versions (.86 / .92 against .70 / .76)
   because a photograph is a noisier ground than a flat one. */
.poster .saying-q p{ color:#F7F1E6 }
.poster .saying-q figcaption{ color:rgba(247,241,230,.80) }
.poster .saying-p p{ color:rgba(247,241,230,.92) }
/* DROPPED BELOW THE PROSE'S TOP LINE ("the quote a little lower"). A deliberate
   stagger rather than two columns starting on the same rule: it gives the eye an
   order to read them in, prose first, and it stops the block reading as a table.
   The offset is proportional so it scales down with the type. */
/* CAPPED AND DROPPED TO THE FOOT. The cap is the point: "make the quote smaller
   to not cover his face that much". At 1440 his head occupies roughly x 420-700
   in the band, and the quote used to run to x 593 at 48px — straight across his
   jaw. 24rem holds its right edge near x 417, which clears the face while
   staying a display quote rather than a caption. `align-self:end` replaces the
   old top margin: with the block now filling the band, a margin could not put it
   at the bottom, it could only push it down from wherever it started. */
.saying-q{
  margin:0; align-self:end; max-width:24rem;
  display:grid; gap:clamp(1rem,2vw,1.5rem)
}
.saying-q blockquote{ margin:0 }
/* Down from a 3rem ceiling. Same reason as the width cap: less of his face
   under it. It is still the largest type in the passage, which is what keeps it
   reading as the pull-quote. */
.saying-q p{
  margin:0; font-size:clamp(1.375rem,.9rem + 1.7vw,2.25rem); line-height:1.18;
  letter-spacing:-.02em; color:var(--ink); text-wrap:pretty
}
/* The reference right-aligns the attribution under its quote. One line, and it
   is what stops the quote reading as body copy that happens to be large. */
.saying-q figcaption{
  justify-self:end; text-align:right;
  font-size:.9375rem; line-height:1.5; color:var(--ink-faint)
}
.saying-q figcaption::before{ content:"\2014\00a0" }

/* NARROWER AND PUSHED RIGHT, so it stops running into him. Its column starts
   near the middle of the frame, which is where his head is; capping the text and
   right-aligning it inside the column moves the left edge of the paragraph out
   past his shoulder instead. `align-self:center` is the "middle of the image
   height-wise" half of the same note. */
.saying-p{
  display:grid; gap:1.1rem;
  align-self:center; justify-self:end; max-width:40ch
}
/* Up a step ("expand it a little"). Still under the quote's scale, so the two
   stay a quote and a passage rather than two competing headlines. */
/* Leading went to 1.5 for the fits-a-screen pass and came back to 1.6 with the
   band's height, later the same day. The 31px it saved only ever mattered while
   the band was capped. */
.saying-p p{
  margin:0; font-size:clamp(1.0625rem,1rem + .38vw,1.3125rem); line-height:1.6;
  color:var(--ink-dim); text-wrap:pretty
}

/* THE READ-MORE FOLD (parts/ministry-why.html). Rokas: "text here can be formatted
   better with drop downs or made shorter." A native <details> holds the last three
   answers, so it opens on tap with no script. The label lives in ::before so it can
   flip Read more / Read less on [open] without a second element, and the caret flips
   with it. Whole paragraphs fold, never mid-sentence, so there is no ragged cut to
   fade. On desktop the passage runs in its own column beside the quote and reads
   fine, so the fold DISSOLVES (display:contents) and the toggle is hidden. */
.saying-more{ margin:0 }
.saying-more > summary{
  display:inline-flex; align-items:center; width:max-content;
  list-style:none; cursor:pointer; user-select:none;
  color:var(--ink); font-size:1rem; padding:.15em 0
}
.saying-more > summary::-webkit-details-marker{ display:none }
.saying-more-t{ text-decoration:underline; text-underline-offset:.3em }
.saying-more-t::before{ content:"Read more" }
.saying-more[open] .saying-more-t::before{ content:"Read less" }
.saying-more-t::after{
  content:"\25BE"; margin-left:.4em; font-size:.85em;
  display:inline-block; text-decoration:none;
  transition:transform .2s var(--ease-smooth)
}
.saying-more[open] .saying-more-t::after{ transform:rotate(180deg) }
.saying-more[open] > p{ margin-top:1.1rem }
@media (min-width:701px){
  .saying-more{ display:contents }
  .saying-more > summary{ display:none }
  .saying-more > p{ display:block }
}

@media (max-width:900px){
  /* The quote leads on a phone too: it is the reason to keep reading.
     The desktop caps and alignments all come off here: in one column, a
     right-aligned 40ch paragraph and a 24rem quote would just be two odd
     indents. They exist to keep clear of a face that is not behind them at
     this size. */
  .saying{ grid-template-columns:1fr; gap:clamp(1.75rem,5vw,2.5rem); align-items:start }
  .saying-q{ align-self:start; max-width:none }
  .saying-p{ align-self:start; justify-self:start; max-width:none }
  .saying-q figcaption{ justify-self:start; text-align:left }
}

/* ---- The route out of the home page's record section ---------------------
   Home only. It closes that section by handing the reader to the album's own
   page, so the last thing on it is a way further in rather than a form.

   NO RULE, 2026-08-15. Rokas, on a screenshot of this seam: "underneath the i
   am chosen section, remove the line, should be a seamless through the
   sections." It carried a 1px hairline across the full frame, and that hairline
   was the last thing above the turn into ministry — so the page announced a
   boundary a screen early, and then the turn itself, which IS the boundary,
   arrived second and read as a smaller event than the line.

   THIS IS THE SAME CALL `.turn` ALREADY MADE and the reasoning is quoted there
   verbatim: "a divider would announce a boundary that the words already are".
   The route keeps its space, because the space is what was doing the real
   separating; only the drawn line goes. */
.music-more{ margin:clamp(1.25rem,2.4vw,1.75rem) 0 0 }
/* IVORY, BECAUSE IT IS NOW ON THE PHOTOGRAPH. The route moved inside `.saying-p`
   on 2026-08-15 and that column sits on his portrait, where `.f-go`'s
   `color:var(--ink)` is a dark blue on Covenant and would vanish. This is the
   same hardcoded #F7F1E6 the passage and the pull-quote beside it already use,
   and the same reason: a token that is correct on a flat ground is wrong on a
   near-black picture. Scoped to `.poster` so the footer's routes, which are on
   flat ground, keep following the token.
   The spacing above also came down a step in the move. Against a full-frame
   divider it needed to clear a rule; at the foot of a paragraph it only needs to
   read as the next line, so it sits closer than it did. */
.poster .music-more .f-go{ color:#F7F1E6 }

/* ============================================================================
   THE POSTER ON A PHONE.

   THIS BLOCK LIVES AT THE END OF THE FILE ON PURPOSE, and putting it anywhere
   else was a real bug, caught by measuring. Its overrides and the desktop rules
   they override have IDENTICAL specificity (.poster .saying-q p is 0,3,0 either
   way), so the later one wins. The first version sat at line ~1139, several
   hundred lines ABOVE the ivory rules it was meant to override, and the ivory
   won at every width — including on Covenant, where the section ground is also
   ivory. The rig reported exactly 1.00:1, four times: ink luminance equal to
   ground luminance is not a near-miss, it is invisible text, and only the
   Covenant column showed it.

   A media query does not raise specificity. If a rule in here ever stops
   applying, check what comes after it before changing the value.
   ========================================================================= */
@media (max-width:900px){
  /* ---- THE PHONE TAKES ALL OF THE TEXT OFF THE PHOTOGRAPH, and a measurement
     forced it twice before it landed.

     At 1440 the whole passage sits on the picture and measures 6.6 to 12.9:1.
     At 390 the same CSS measured 1.20:1 for the title and 1.00:1 for the
     summary. THE CAUSE IS THE CROP, NOT THE GRADIENT, which is why the first
     attempt failed too: a 1600x2398 portrait set to `cover` a 390-wide box over
     a thousand pixels tall has NO vertical slack left, so `object-position`
     stops meaning anything and the frame becomes a tight column of his lit face
     sitting directly behind whatever is written on it. Moving the text further
     down just moved it onto a different part of the same face — the second run
     still failed at 1.61:1.

     A phone is a portrait and so is this photograph. There is no arrangement
     where both get the space they need. So below 900 the picture becomes a
     PLATE at the top of the band with a real crop of its own, and every word
     sits underneath it on the section ground, where the tokens already
     guarantee contrast on all three directions. */
  .poster{ min-height:0 }
  /* THE PLATE CAME DOWN A STEP, 2026-08-14 (the fit-a-screen pass): 58vh was
     490px of a 844px screen, and the words that belong with it all sit
     underneath, so the picture pushed its own caption off the phone. 42vh is
     354px, which still reads as a plate rather than a thumbnail and leaves the
     title and prayer line in the same view as the face. */
  .poster-img{
    position:relative; inset:auto;
    width:100%; height:clamp(18rem,42vh,24rem);
    object-position:50% 16%
  }
  /* The gradient existed to make type legible over the picture. Nothing sits on
     the picture here, and a fade over a plate that ends in mid-air reads as a
     rendering fault rather than as a treatment. */
  .poster::after{ content:none }
  .poster-in{ padding-block:clamp(1.75rem,4vh,2.5rem) 0 }
  .saying{ margin-top:clamp(1.75rem,5vw,2.5rem) }
  /* THE HARDCODED IVORY COMES OFF WITH THE PHOTOGRAPH. It exists so type
     survives a near-black picture on all three directions; over the section
     ground on Covenant it would be ivory on ivory, which is the same defect in
     the other direction. */
  .poster .poster-t{ color:var(--ink) }
  .poster .poster-s{ color:var(--ink-dim) }
  .poster .saying-q p{ color:var(--ink) }
  .poster .saying-q figcaption{ color:var(--ink-faint) }
  .poster .saying-p p{ color:var(--ink-dim) }
  /* THE MUSIC PAGE'S TWO NEW BANDS, ADDED TO THIS LIST 2026-08-16, and they were
     added because the probe caught them and not because the pattern was
     remembered. `.flow-p`, `.flow-lift` and the rail are pinned to ivory over
     their photographs exactly like the five above; below 900 the photograph
     becomes a plate and every one of them was measured at 1.00:1 on Covenant,
     which is ivory on ivory and is the same defect this block was written to
     fix. Anything new that hardcodes ink over `.poster` belongs on this list. */
  .poster .flow-p{ color:var(--ink-dim) }
  .poster .flow-lift{ color:var(--ink) }
  .poster .rail{ color:var(--ink-faint) }
}

/* ============================================================================
   THE MINISTRY HALF OF THE HOME PAGE, 2026-08-15.
   Two new sections, and between them they add three rules and reuse everything
   else. #ministrywhy is `.saying` and `.readout`, both already built for the
   record; #watch is `.theme-film`, already built for the eight theme pages. The
   only genuinely new thing on the page is the row of three.
   ========================================================================= */

/* ---- #ministrywhy: who he is ----------------------------------------------
   The record readout's column label is 6.6rem because its longest is "Release
   date". This one's longest is "Works as", so the shared 5.5rem default holds
   and no override is needed. What it DOES need is room for the values, which
   are sentences rather than the record's two- and three-word answers: "Christian
   artist, itinerant minister and spiritual guide" runs to 46 characters and
   wraps at any realistic width. `align-items:baseline` on .ro-row already keeps
   the label on the value's first line, so a wrapped value hangs correctly
   without touching the shared component. */
/* ---- WHO HE IS: the facts as a statement -----------------------------------
   Replaced the `.who` readout on 2026-08-15. Its three rules were deleted with
   the markup rather than left dormant: a rule that outlives its element is the
   one that gets copied into the next section by someone who assumes it is load
   bearing, which is the same call the `.mwhy-a` deletion made earlier today.

   DISPLAY SIZE, SENTENCE CASE, ONE FACT PER LINE. The scale sits between the
   page heading above it and the pull-quotes on the two sections before, so it
   reads as the page's closing declaration without competing with either. Line
   height is tight (1.25) because every line is a complete statement and the
   spacing between them, not inside them, is what separates the facts.

   NO max-width. The frame is the measure, the same rule the statement under the
   hero runs on, and none of these lines is long enough to need a cap. */
.who-say{
  margin-top:clamp(2.25rem,4.5vw,3.5rem);
  /* THE COLUMN IS CAPPED AT THE CONTAINER, 2026-08-17. minmax(0,1fr) stops the
     column growing to a child's max-content, and justify-items:start lets each fact
     shrink to its own text so the hover underline hugs it. Without the cap a long
     fact ("Licensed Solicitor in Australia. Studied law in South Africa.") pushed
     the column to ~538px and ran off the right of a 390px phone. */
  display:grid; grid-template-columns:minmax(0,1fr); justify-items:start;
  gap:clamp(.5rem,1.1vw,.85rem)
}
.who-say p{
  margin:0; color:var(--ink); text-wrap:pretty;
  font-family:var(--f-display); font-weight:400; letter-spacing:-.02em;
  font-size:clamp(1.375rem,.85rem + 1.8vw,2.375rem); line-height:1.25
}
/* THE LINE, NOT GOLD, 2026-08-17. These warmed to the aura's gold for a day, the
   same treatment as the music list; Rokas struck the gold across all three lists at
   once ("get rid of the gold colour. just have the same animation with the line").
   A statement set at display size is already full ink, so there is no brighter it
   can go, and the neutral gesture is the line itself: a hairline draws in from the
   left under the hovered fact, the same draw the #practice and deck lists take.

   IT IS A `transform:scaleX` PSEUDO, NOT AN ANIMATED background-size, AND THAT WAS
   A SAFARI BUG FIX, 2026-08-17. The first build drew the line by transitioning
   `background-size` 0% -> 100%. That animates in Chromium and SNAPS in Safari:
   WebKit does not interpolate a percentage background-size, so on the one browser
   the client uses the line just appeared, which is exactly what Rokas reported
   ("make this line animated, exactly like in the ministry"). scaleX on a ::after is
   the mechanism #practice and the deck already use, it is on the compositor, and it
   animates everywhere including Safari. The row is width:max-content so the pseudo,
   pinned left-to-right of that box, is exactly as wide as the text. */
.who-say p{
  position:relative; padding-bottom:.14em
}
.who-say p::after{
  content:""; position:absolute; left:0; right:0; bottom:0; height:1px;
  background:var(--ink); transform:scaleX(0); transform-origin:left;
  transition:transform .5s var(--ease-arrive)
}
@media (hover:hover){
  .who-say p:hover::after{ transform:scaleX(1) }
}
@media (prefers-reduced-motion: reduce){ .who-say p::after{ transition:none } }

/* ---- #watch: three films, side by side ------------------------------------
   auto-fit rather than a fixed three columns, so the row reflows to two and
   then one on its own. The 20rem floor is what decides where: below that a
   16:9 film is 320x180 and its caption starts wrapping to three lines, which is
   the point at which a card stops looking like a card.

   .theme-film IS REUSED WHOLE and its own margin-top is overridden here. On a
   theme page it is one element following a heading and carries its own air; in
   a grid, that air lands inside the cell and pushes the first card's video down
   past the other two. Same component, different neighbours. */
.watch-grid{
  list-style:none; margin:clamp(2rem,4vw,3rem) 0 0; padding:0;
  display:grid; gap:clamp(1.5rem,3vw,2.5rem);
  grid-template-columns:repeat(auto-fit, minmax(min(20rem,100%), 1fr))
}
.watch-card{ margin:0 }
.watch-grid .theme-film{ margin:0; max-width:none }
/* The caption is a rail, and a rail is uppercase with wide tracking. At three
   across, a title like "When Heaven Meets Earth" plus its duration runs past
   one line, and a two-line uppercase caption under a video reads as a block of
   shouting. Sentence case for these three only, so the title stays a title. */
.watch-grid figcaption{ text-transform:none; letter-spacing:.02em; font-size:13px }
.watch-grid figcaption i{ font-style:italic; color:var(--ink) }
.watch-more{ margin:clamp(1.75rem,3.5vw,2.5rem) 0 0 }

/* ---- .hl-grid — the long form, 2026-08-17 ---------------------------------
   Rokas: "we must include both his long sermons as highlights on the page." One
   exists. The reasoning is in parts/ministry-highlights.html; what is here is the
   arrangement, and its one job is to hold ONE well and TWO without being edited.

   `auto-fit` WITH A 34rem FLOOR DOES BOTH. With one card the track collapses to
   the full frame and the video runs 1368px wide, which is the size a
   twelve-minute sermon deserves against five one-minute shorts below it. Add a
   second and both land at about 660px, side by side. Nothing here has to change
   and no placeholder has to be shipped in the meantime.

   THE FLOOR IS 34rem, NOT the watch grid's 20rem, and that is the whole
   difference between the two components. At 20rem a second card could drop to a
   third of the frame on a wide window; these are the page's headline items and
   they never go smaller than half. */
.hl-grid{
  list-style:none; margin:clamp(2rem,4vw,3rem) 0 0; padding:0;
  display:grid; gap:clamp(1.5rem,3vw,2.5rem);
  grid-template-columns:repeat(auto-fit, minmax(min(34rem,100%), 1fr))
}
/* A LONE CARD CAPS ITSELF, and this is a measurement rather than a preference.
   At the full 1368px frame a 16:9 video is 770px tall, and with the heading and
   the section's own padding the band came out at about 1110px — over the 960px
   screen rule verify/fits-screen.js enforces, on the one page whose sections all
   fit today. 58rem lands the video near 522px and the band near 894.
   `:only-child` rather than a cap on the grid, because a cap on the grid would
   also squeeze the TWO-card case below the 34rem floor and collapse it to one
   column. This way one card is large and bounded, two are half the frame each,
   and neither arrangement is a special case. */
.hl-card{ margin:0 }
.hl-card:only-child{ max-width:58rem }
.hl-grid .theme-film{ margin:0; max-width:none }
/* Sentence case, same call and same reason as the watch grid's: an uppercase
   rail wrapping to two lines under a video reads as shouting. */
.hl-grid figcaption{ text-transform:none; letter-spacing:.02em; font-size:13px }
.hl-grid figcaption i{ font-style:italic; color:var(--ink) }

/* ---- .watch-five — the ministry page's five films, 2026-08-16 -------------
   THREE ACROSS, AND BOTH OTHER ARRANGEMENTS WERE BUILT AND MEASURED FIRST.
   The `auto-fit` above solves to three at this frame width anyway, so this rule
   exists to say three EXPLICITLY rather than to have five items land on whatever
   `minmax(min(20rem,100%),1fr)` happens to give when the frame or the gap moves.
   Two across ran the four-film version at 664px each and put the section at 1.29
   screens, which needed a named exception in verify/fits-screen.js; at five items
   two across is three rows and an orphan. Three across is two rows with a gap at
   the end of the second, each film at about 429px — the same size the home page
   already runs its three at — and the section fits a screen with no exception.
   `minmax(0,1fr)` rather than `1fr`, because a 16:9 <video> in a `1fr` track
   takes its own intrinsic width as the track minimum and the columns then refuse
   to shrink.
   TWO AT 1100 AND ONE AT 760. Below 1100 a third of the frame is under 340px and
   the burnt-in opening card in each poster starts to be unreadable, which is the
   only copy this section has (the note in parts/ministry-watch.html explains why
   it is the poster and not text). */
.watch-five{ grid-template-columns:repeat(3, minmax(0,1fr)); gap:clamp(1.25rem,2.4vw,2rem) }
/* The grid's own top gap, tightened from `.watch-grid`'s. Three across is a
   shorter block than the four-up it replaced and it sits under a three-line lede
   rather than a two-line one; at 48px the films read as a separate object from
   the heading they belong to. It is also the last 10px this section needed to
   come under a screen without touching the size of a film. */
.watch-five{ margin-top:clamp(1.75rem,3.2vw,2.5rem) }
@media (max-width:1100px){
  .watch-five{ grid-template-columns:repeat(2, minmax(0,1fr)) }
}
@media (max-width:760px){
  .watch-five{ grid-template-columns:1fr }
}

/* ---- The band's phone picture ---------------------------------------------
   THE PHONE FILE IS THE REAL PHOTOGRAPH NOW, 2026-08-17. Rokas, on a phone: the
   b&w ministry image "is blurred on left side," then: "for the mobile I think the
   original image would work fine." It did. `band-tall.jpg` was a synthesised
   portrait — a 10% slice of the studio wall stretched across a third of the frame
   to seat him at the foot with clean paper above — and that stretch is the blur he
   saw. It is now a plain sharp crop of the source (img/build-band-image.py's own
   SRC, 20250721_Traidy-22657), so there is no stretched paper to go soft.

   THE MARK IS CENTRED, 2026-08-17. Rokas: "center the logo so it's centered." A
   first pass seated it in the clean wall at the top to keep it off his face, but
   the call is centred. At `object-position:50% 50%` the crop holds his face to the
   right and the centred mark lands over his neck and collar rather than his
   features, which is the closest the real photograph gets to the synthetic file's
   clean-paper centre. The desktop wide file and its centred mark are untouched. */
@media (max-width:700px){
  .mband-img{ object-position:50% 50% }
}

/* ---- #ministrywhy overrides on the shared .saying block --------------------
   `.saying` was built inside the poster, where it fills a photographic band and
   its two columns are positioned around his face: the quote sits at the FOOT of
   the band (`align-self:end`), capped at 24rem so it clears his jaw, and the
   prose is pushed to the right edge so it starts past his shoulder. Every one of
   those is a rule about a picture that is not behind this copy.

   Dropped in unmodified here, the quote fell to the bottom of a row whose height
   is set by five paragraphs of prose, leaving 500px of empty ground under the
   heading before it started. It read as a broken grid, which is exactly what it
   was. So the section resets the three picture-driven rules and keeps
   everything the component actually is: the type scale, the stagger of sizes,
   the attribution, and the responsive collapse. */
/* ---- MIRRORED, 2026-08-15 --------------------------------------------------
   Rokas: "align the ministry info section to be the opposite layout of the i am
   chosen section. have the big paragraph left, quote on the right, make sure
   its all aligned with the top."

   WHAT IT IS THE OPPOSITE OF. In the record above, the passage runs down the
   RIGHT of his portrait and his quote sits low on the LEFT, because both are
   positioned around his face. Flipping this block puts the passage left and the
   quote right, so the two halves of the page mirror each other instead of
   repeating. That is the same instinct the component's own note already had
   ("inverting the grid from the band above keeps two stacked two-column blocks
   from reading as one repeated shape") — this carries it one step further, from
   inverted column WIDTHS to inverted column ORDER.

   THE RATIO FLIPS WITH THE CONTENT, NOT JUST THE POSITION. Moving the two
   columns without touching `grid-template-columns` would have handed the wider
   1.1fr to the quote and squeezed five paragraphs of prose into the narrow .9fr.
   The wide column belongs to whatever holds the most words, so the ratio is
   reversed here too and the passage keeps the room it had.

   EXPLICIT `grid-column`, NOT `direction:rtl` AND NOT `order`. rtl would flip the
   punctuation and the reading direction of the text itself, which is a bug
   dressed as a layout. `order` would leave the DOM order and the visual order
   disagreeing for anyone tabbing or reading with assistive tech, and the mobile
   rules below already use `order` for the stacked case, so reusing it here would
   put two order systems on the same three elements.

   TOP ALIGNED, AND IT IS LOAD BEARING. `align-items:start` and both
   `align-self:start` rules were already here for the reason the block above
   explains: dropped in unmodified, the quote fell to the foot of a row sized by
   five paragraphs. Keeping them is what makes the passage and the quote start on
   one line, which is the "aligned with the top" half of the instruction, and it
   is verified by measurement rather than by eye. */
#ministrywhy .saying{ align-items:start }
/* THE MIRROR IS SCOPED TO THE TWO-COLUMN CASE, and it has to be scoped by a
   MEDIA QUERY rather than by specificity. Below 900 `.saying` collapses to
   `grid-template-columns:1fr` inside `@media (max-width:900px)` — and a media
   query does not raise specificity, which this file has already paid for once
   and warns about in the poster block. `#ministrywhy .saying` is 1,1,0 against
   that rule's 0,1,0, so an unscoped override here would have kept two columns on
   a phone at every width. Nothing below is a phone concern anyway: stacked,
   there is no left and no right to mirror.

   901 AND NOT 900. The collapse is `max-width:900px` and the file's desktop
   blocks are `min-width:900px`, so both match at exactly 900 and the later one
   wins. That overlap is harmless for colour and spacing, which is where the file
   uses it; for grid-template-columns it would mean one viewport width that
   renders two columns inside the phone layout.

   THE ROW IS AS EXPLICIT AS THE COLUMN, and leaving it out was a measured bug,
   not a theoretical one. In the markup the quote block comes FIRST and the
   passage second, so naming only the columns let auto-placement keep its own
   cursor: the quote took row 1 column 2, the passage could no longer fit beside
   it, and it was pushed to ROW 2 — 570px below the quote instead of level with
   it. Measured at 1440: quote top 5448, passage top 6018. Both are pinned to row
   1 now, which is what actually delivers "aligned with the top". */
@media (min-width:901px){
  #ministrywhy .saying{ grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr) }
  #ministrywhy .saying-p{ grid-column:1; grid-row:1 }
  #ministrywhy .saying-q{ grid-column:2; grid-row:1 }
}
#ministrywhy .saying-q{ align-self:start; max-width:32rem }
/* THE COLUMN IS THE MEASURE, 2026-08-15. Rokas: "the paragraph looks too
   squeezed in to the section." Measured at 1440 it was: the column is 713px and
   the 46ch cap held the text to 507px, so 206px of its own column sat empty and
   then the 72px grid gap sat empty next to that. Nearly 280px of dead ground
   between the passage and the quote, with the passage crammed left of it. The
   cap was inherited from the record's version of this block, where it exists to
   keep the text off his face; there is no photograph here for it to clear.

   IT CANNOT RUN LONG, WHICH IS WHY REMOVING THE CAP IS SAFE RATHER THAN LAZY.
   `.frame` is capped at --maxw 1400px, so this column tops out at 1.1/2.0 of
   1328px, about 730px, at a 21px type size. That is roughly 69 characters a
   line, inside the 65 to 75 the body measure should hold, and it cannot grow
   past it however wide the screen gets. Same reasoning as the statement under
   the hero, which carries no max-width for the same reason. */
#ministrywhy .saying-p{ align-self:start; justify-self:start; max-width:none }
/* THE `.mwhy-a` AND `.who` RULES WERE DELETED HERE, 2026-08-15, with the markup
   they styled. The wrapper existed to stack the quote over the facts readout in
   one column; the readout moved to about.html and the wrapper had nothing left
   to group. Rules kept alive after their element is gone are the ones that get
   copied into the next section by someone who assumes they are load bearing. */
/* THREE PIXELS, AND THEY MATTER BECAUSE THE RULE IS THE RULE. verify/fits-screen
   measured this band at 963px against a 960px screen — the only one of 45 that
   failed, and by a third of a line. The site's standing rule is that no band is
   taller than the screen, and a band that misses it by three pixels is still a
   band that misses it. Trimmed from the air between the two blocks and the air
   above them, which is where a reader loses nothing, rather than from the
   leading of his own words. */
#ministrywhy .saying{ margin-top:clamp(2rem,3.6vw,3rem) }

/* ON ONE COLUMN THE FACTS GO LAST, and the wrapper that groups them on a desktop
   has to get out of the way to allow it. Stacked, the left column's own order
   put the readout BETWEEN his quote and his prose, so five rows of job titles
   interrupted the one passage on the page written in his voice. `display:contents`
   drops the wrapper from the box tree so its two children become children of
   .saying, and then the three can be ordered as they should be read. */
@media (max-width:900px){
  #ministrywhy .saying-q{ order:1 }
  #ministrywhy .saying-p{ order:2 }
}

/* ---- ABOUT: the portraits and the two passages -----------------------------
   Added 2026-08-15 with the full About page. Everything here is layout; the type
   is `.saying-q` and `.saying-p`, the same instruments the record and the
   ministry half already use, so About reads as the same site rather than as a
   page someone styled separately.

   THE SAME THREE RESETS `#ministrywhy` NEEDS, and for the same reason. `.saying`
   was built inside the poster, where the quote sits at the FOOT of a photographic
   band (`align-self:end`) capped at 24rem to clear his jaw, and the prose is
   pushed right to start past his shoulder. All three are rules about a picture
   that is not behind this copy. Dropped in unmodified the quote falls to the
   bottom of a tall row and leaves a hole under the heading. */
.ab-q{ align-self:start; justify-self:start; max-width:none; margin:0 }
.ab-q figcaption{ justify-self:start; text-align:left }
.ab-p{ align-self:start; justify-self:start; max-width:none }
.ab-text{ display:grid; gap:clamp(1.25rem,2.4vw,1.75rem); align-content:start }

/* Two portraits, one row. Sized 1:1 rather than staggered: they are a pair of
   equals, not a lead and a support, which is the whole reason there are two. */
.ab-pair{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:clamp(1rem,2.4vw,2rem) }
.ab-plate{ margin:0 }
/* THE SINGLE PORTRAIT, 2026-08-19 (Rokas: one photo on top, not two). Centred and
   capped so it is a portrait rather than a full-bleed wall; the aspect and crop
   match what .ab-pair gave the pair, so the frame reads the same, just alone. */
.ab-one{ max-width:min(30rem,74%); margin-inline:auto }
.ab-one img{ aspect-ratio:3 / 4; object-fit:cover; object-position:50% 22% }
.ab-plate img{ width:100%; height:auto; display:block; border-radius:2px }
/* ONE RATIO ACROSS THE PAIR. The two files are 1600x2200 and 1400x2098, so left
   to their own heights they start level and end 80px apart, which reads as a
   mistake rather than as a stagger. A shared 3:4 box crops the difference out.
   `50% 22%` rather than centre because both are head-and-shoulders portraits and
   a centred crop on a 3:4 box takes the top of his head off the taller one. */
.ab-pair .ab-plate img{ aspect-ratio:3 / 4; object-fit:cover; object-position:50% 22% }

/* THE TEXT COLUMN IS THE WIDER ONE. The picture is evidence and the words are
   the content, so the split favours the words; the near-even .95/1.05 keeps it
   from reading as an image with a caption beside it. */
.ab-row{
  display:grid; grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr);
  column-gap:clamp(2rem,5vw,4.5rem); row-gap:clamp(1.75rem,3.5vw,2.5rem);
  align-items:start; margin-top:clamp(1.75rem,3.5vw,2.75rem)
}
/* EXPLICIT PLACEMENT, NOT `order` OR `direction`. Same call the contact's mirror
   made: `direction:rtl` would flip the punctuation inside the text, and `order`
   would leave the DOM and the visual order disagreeing for anyone tabbing. Both
   rows keep the picture first in source, so the phone stacks image then words
   either way and the alternation is a desktop-only idea. */
.ab-row > .ab-plate-l{ grid-column:1; grid-row:1 }
.ab-row > .ab-text   { grid-column:2; grid-row:1 }
.ab-row-flip > .ab-plate-r{ grid-column:2; grid-row:1 }
.ab-row-flip > .ab-text   { grid-column:1; grid-row:1 }

/* THE ABOUT INTRO ROW, 2026-08-19. His portrait beside his own five-line
   description of himself (Rokas: "add a few lines of how traidy describes himself
   on the side to fill the empty space"). It reuses `.ab-row-flip` for placement
   and the tested 900px collapse, and only re-balances two things: the words lead
   in the wider column, and the portrait is capped, centred and set against the
   words so a tall frame and five short lines do not leave a hole under either. */
.ab-intro{
  grid-template-columns:minmax(0,1fr) minmax(0,1.02fr);
  align-items:center;
  margin-top:0
}
.ab-intro .ab-plate{ width:100%; max-width:28rem; justify-self:center; margin-inline:auto }
.ab-intro .ab-plate img{ aspect-ratio:3 / 4; object-fit:cover; object-position:50% 22% }
/* The readout followed a heading with a band of air of its own; here it sits in a
   tight column directly under the h1, so it takes a smaller opening gap. */
.ab-intro .who-say{ margin-top:clamp(1.25rem,2.4vw,1.75rem) }
/* SMALLER THAN THE STANDALONE READOUT, 2026-08-19. Rokas: "make the about
   sentences at the top a little smaller maybe and the picture a little bigger."
   The readout was set at statement size (up to 2.375rem) because it stood alone on
   a page; beside a portrait it is a caption to the face, so it drops to a calmer
   size and hands the extra room to the picture (max-width above, from 24 to 28rem). */
.ab-intro .who-say p{ font-size:clamp(1.0625rem,.85rem + .8vw,1.625rem) }

/* #first WITHOUT ITS PORTRAIT, 2026-08-19. The plate came off (Rokas: fewer
   pictures on the music page), so the two-column row becomes a single reading
   column: his eight-line script, capped for measure and centred so it does not
   hug the left edge of a wide dark page. On a phone the max-width:900 rule below
   still resets it to one full-width column. */
.first-solo{ grid-template-columns:minmax(0,58ch); justify-content:center }
/* The `.ab-row > .ab-text` rule pins the text to column 2 for the two-column
   layout; with the plate gone there is no column 2, so pull it back to column 1
   or it lands in an implicit track pushed to the right. */
.first-solo > .ab-text{ grid-column:1 }

@media (max-width:900px){
  .ab-pair{ grid-template-columns:1fr; gap:clamp(1.25rem,4vw,2rem) }
  .ab-row, .ab-row-flip{ grid-template-columns:1fr }
  /* EVERY SELECTOR IS SPELLED OUT, AND IT WAS `.ab-row > *` UNTIL 2026-08-16.
     THE SHORT VERSION NEVER WORKED. It is (0,1,0) and the four desktop
     placement rules above are (0,2,0), so the release lost the cascade at every
     width and the explicit `grid-column:2` survived into a ONE-column grid. The
     browser does what it is told: it creates an implicit second column for the
     picture, the implicit column takes its 318px, and the single `1fr` explicit
     column is left with none of the free space. Measured at 390 on both bands of
     about.html and both of music.html: text column 0px wide, plate 318px, the
     words overflowing their own box and running under the photograph.

     THIS SHIPPED ON about.html ON 2026-08-15 and was found on 2026-08-16 by
     screenshotting a NEW band on a phone and looking at it, not by reading the
     CSS. verify/pages-pass.js passed the page throughout, because a zero-width
     column that overflows produces no horizontal scroll, no console error and no
     failed request, which is everything that check looks at.

     Naming the four children matches the (0,2,0) of the rules being undone. The
     universal selector was the whole bug: `*` adds nothing to specificity. */
  .ab-row > .ab-plate-l, .ab-row > .ab-text,
  .ab-row-flip > .ab-plate-r, .ab-row-flip > .ab-text{ grid-column:1; grid-row:auto }
}

/* ---- Air under the contact heading -----------------------------------------
   .page-head is a heading group and carries no bottom margin of its own: on the
   theme pages and on about/music the block that follows it brings its own. The
   contact columns do not, so the lede sat directly on the four column labels
   with no gap at all. It reads as a rendering fault rather than as tight
   spacing. One rule, on the shared component, so contact.html gets it too — the
   defect was already there and only became visible when the same block was put
   in the middle of the home page where the eye reaches it mid-scroll. */
.close .page-head{ margin-block-end:clamp(2rem,4vw,3rem) }

/* ---- #ministryturn: the hinge, set in type ---------------------------------
   ONE SENTENCE AND NOTHING ELSE, so the band has to be sized by its air rather
   than by its content. `.section`'s own padding is tuned for sections that then
   go on to hold something; here it IS the section, and at the shared value the
   line sat in a thin strip that read as a stray paragraph rather than a turn.

   It is still under a screen at 960 (verify/fits-screen), which is the standing
   rule, and the padding is asymmetric on purpose: more above, so the record's
   poster is released before the eye reaches the line, and less below, so the
   line reads as belonging to the photograph it introduces.

   NO RULE, NO FILL, NO BAND. Rokas: "without like a black bar or something".
   The separation is the space and the type size; a divider would announce a
   boundary that the words already are. */
.turn{
  padding-block: clamp(5.25rem,9vw,9rem) clamp(3.5rem,6vw,6rem);
  /* THE SCRUB'S UNLIT LEVEL, added 2026-08-15 with js/scrub.js. `.stmt-c` reads
     this variable and #story sets it on `.stmt`, so the turn declares its own.
     (A `.stmt-c` fallback of .38 was added 2026-09-10 for the inner-page statements
     that declare none; the turn keeps its explicit value to reach the covenant
     override below.) Same .38 as #story deliberately: the two statements
     are the same device and a second dim level would make them read as two.
     The dim state is transient by construction — every character finishes at
     opacity 1, and under reduced motion the split is never built at all — so the
     contrast floor is judged on the lit state, which is plain --ink. */
  --stmt-dim:.38
}
[data-theme="covenant"] .turn{ --stmt-dim:.55 }
.turn .rail{ margin:0 0 clamp(1.5rem,3vw,2.25rem) }
/* Larger than the pull-quotes (2.25rem) and smaller than the poster's album
   title, which keeps the page's type hierarchy in the order the content is in:
   the record's name is the loudest thing on the half above, this is the loudest
   thing on the half below, and neither competes with the hero. */
/* THE FRAME IS THE MEASURE, 2026-08-15, and that is the whole change. Rokas:
   "extend the ministry header over a little more to match the section under the
   hero." The section under the hero is #story, and `.stmt-p` carries an explicit
   note that it has NO max-width because "the frame is the measure, exactly as
   the reference runs it". This heading was capped at 24ch, which held it to
   933px inside a 1368px frame and broke it over three lines while the statement
   above ran the full width over two. Two statements, the same device, two
   different measures: the cap was the thing making them not match.

   IT COSTS A LINE AND THAT IS THE GAIN. At 1440 the sentence goes from three
   lines to two, so the turn now lands as one confident block rather than a
   stack, and it reads at the same rhythm as the statement it is echoing. The
   character count per line is still under the statement's because the type is
   larger, so the measure never gets harder to read than the page's own opening.
   Nothing else moves: the clamp, the tracking and the wrap are untouched. */
/* THE STATEMENT'S TYPE, NOT ITS OWN, 2026-08-15. Rokas, pointing at the block
   under the hero: "match the ministry header to this style." So this takes the
   four display tokens `.stmt-p` takes, rather than approximating them: the
   display face, weight 700, uppercase, and -.035em tracking. It was 400 weight,
   sentence case and -.025em, which is why the two never read as the same
   instrument even after the width matched.

   THIS OVERTURNS A LOGGED DECISION AND THAT IS HIS CALL TO MAKE. The part file
   argued sentence case on the grounds that every other place his own words are
   set large is sentence case, and that the statement should stay the site's only
   all-caps sentence. That reasoning was sound and he has overruled it: the two
   sentences are the page's two hinges, one per half, and he wants them to look
   like a pair. The note in parts/ministry-turn.html is corrected rather than
   left contradicting the CSS.

   THE SIZE STAYS ITS OWN. `.stmt-p` tops out at 2.75rem and this at 3.75rem, and
   the gap is deliberate: uppercase at 700 is already much louder per character,
   so matching the case AND the size would have made the turn outshout the
   statement instead of answering it. Same voice, one step down. */
.turn-h{
  margin:0;
  font-family:var(--f-display); font-weight:var(--display-weight);
  text-transform:var(--display-case); letter-spacing:var(--display-tracking);
  font-size:clamp(1.875rem,1rem + 3.1vw,3.75rem); line-height:1.1;
  color:var(--ink); text-wrap:pretty
}
/* MIRRORS `.stmt-do`, WHICH IS RIGHT ALIGNED. One property, because the whole
   point is that the two blocks carry the identical control pair and differ only
   in which side it sits on. Left is also the better side here: the heading now
   runs the full frame and its last line ends short on the left, so the buttons
   close the block under the words instead of floating off the ragged edge. */
.turn-do{ justify-content:flex-start }
/* `.open-do` — the same row on the ministry page's opening, 2026-08-16. It is
   NOT `.turn-do` reused: that class is named for the hinge it belongs to, and a
   second section borrowing it would make any future change to the turn silently
   move a page it has nothing to do with. It also needs a shorter top gap, and
   that is the real difference: `.stmt-do` clears 4rem because it follows a
   sentence set at the full frame width, and this one follows a 46ch quote on a
   left-aligned head, where 4rem reads as the buttons having come loose. */
/* HARD RIGHT SINCE 2026-08-17, AND IT WAS LEFT UNTIL TODAY. Rokas: "move the
   buttons to the right side on the top of the ministry page." The old rule read
   "buttons on the right would float away from everything they belong to", and it
   was true of the composition it was written for: a 46ch quote on a left-aligned
   head. That composition is gone — the title is hidden and the quote is now
   `.stmt-p`, which runs the FULL frame — so the right edge of the buttons and the
   right edge of the sentence above them are the same rule. This is the home
   page's arrangement, which is where `.stmt-do` came from in the first place. */
.open-do{ justify-content:flex-end; margin-top:clamp(2rem,3.5vw,2.75rem) }
/* `.open-stmt` — the ministry page's line, in the statement voice, 2026-08-17.
   Rokas: "turn the statement into this style." It is `.stmt-p` unchanged — the
   same face, size, case, tracking and scrubbed reveal the home page's sentence
   uses — and this rule is ONLY the gap under the heading, which `.stmt` normally
   supplies as section padding and cannot here: this statement sits inside a
   `.page-top` header rather than alone on a band of its own. The measure is
   deliberately not capped: `.stmt-p` takes the frame, which is the whole reason
   the home page's sentence reads as a statement rather than as a paragraph, and
   capping it here would make this one a quotation again. */
/* CENTRED IN ITS BAND, 2026-09-10. Rokas: "move the statement sentence down a
   little so that it is equal distance from the hero image and the album cover."
   It sat 56px under the image and 128px above the cover (top-heavy). This top gap
   plus the album section's reduced top padding below now land it at ~92px each
   side at desktop. Paired with the `.albums-sec` top-padding cut below. */
.open-stmt{ margin-top:clamp(2.5rem,6.2vw,5.75rem) }
.turn-h blockquote{ margin:0; font: inherit; letter-spacing:inherit }


/* ---- THE SHORT CONTACT (About) ---------------------------------------------
   Added 2026-08-15. Rokas: "maybe can do a plain contact section, but shorter.
   full contact form on the contact page and homepage only."

   IT NEEDS ALMOST NO CSS, and that is the evidence it was built out of the parts
   that already existed rather than as a second component. It carries no
   photograph and no form, so none of `.close-full`'s pale tokens or condensed
   field spacing apply to it; it takes the page's black ground and the site's
   normal rhythm. Its id is `contactbrief`, not `contact`, which is what keeps
   every `#contact` rule off it without a single override being written.

   THE CAP IS THE ONE REAL DECISION. Three facts and a button run across a 1368px
   frame would strand the button on its own line a long way from the words above
   it. 40rem is the same cap the full version's fact strip uses, so the two read
   as one instrument at two lengths rather than as two designs. */
.c-info-brief{ max-width:40rem }
.c-brief-go{ margin:clamp(2rem,4vw,2.75rem) 0 0 }
.c-brief-go .btn{ min-height:48px }
.c-brief-go .btn:hover .arw,
.c-brief-go .btn:focus-visible .arw{ transform:translateX(4px) }

/* ============================================================================
   THE MUSIC PAGE'S ARTIST HALF, 2026-08-16
   Rokas: "build out the music page ... in the same style of the homepage but
   more focused on his artist persona. more free flowing, absolute vibe and
   creative."

   THE PAGE NOW HAS TWO HALVES, exactly as the home page does. Above the hinge
   is THE RECORD (artwork, poster, why the album exists); below it is THE ARTIST
   (the poem, the five named songs, the first song he ever wrote, rejection, the
   night). parts/music-*.html carry the reasoning for each.

   ALMOST NOTHING NEW IS INVENTED HERE, WHICH IS THE "SAME STYLE" HALF OF THE
   INSTRUCTION. The two photographic bands are `.poster`, the component the
   record already uses, with one height variant. The two text-and-plate bands are
   `.ab-row`, the component about.html already uses. What is genuinely new is
   `.flow` (his caption lines set as he broke them) and `.songs` (the five
   names), because the site had no component for either.
   ========================================================================= */

/* ---- #music and #made — the record's own opening, 2026-08-16 ---------------
   Rokas: "change up the first two sections so they're not the same as the
   homepage ... the way its structured too so its not exactly the same." The
   markup and the content reasoning are in parts/music-record.html and
   parts/music-made.html; what is here is only the two shapes.

   THE HOME PAGE'S TWO SHAPES, so the difference is legible: a GRID (artwork
   left, plate right, quote centred under both) and a full-screen POSTER (title
   at the top of a photograph, two columns of text at its foot). Neither is
   reused. This page opens on TYPE AT FULL WIDTH over a justified strip of
   photographs, and follows it with a shallow LETTERBOX carrying one line. */

/* His answer, set at display size because it is his. Sized between the page
   heading above it and the body below, and capped by measure rather than by the
   frame: it is four sentences, and at the full 1368px they would set at about
   150 characters a line, which is twice a readable measure. */
.open-say{ margin:clamp(2.25rem,4.5vw,3.5rem) 0 0 }
.open-say blockquote{ margin:0 }
/* 46ch, AND 30ch WAS THE FIRST TRY AND WAS WRONG. At 30 the four sentences set
   in seven short lines down the left edge with two thirds of a 1368px frame
   empty beside them, which reads as a column that failed to fill rather than as
   a measure. 46 sets it in four and the block sits under the heading at about
   the width the heading itself runs. Still well inside a readable measure, which
   is the only reason the cap exists — `.stmt-p` on the home page takes the frame
   as its measure and carries one sentence; this is four. */
.open-say p{
  margin:0; max-width:46ch; color:var(--ink);
  font-family:var(--f-display); font-weight:400;
  font-size:clamp(1.25rem,.95rem + 1.15vw,2rem); line-height:1.25;
  letter-spacing:-.025em; text-wrap:pretty
}
/* THE STRIP. Same flex-grow solve as the contact gallery — each figure grows in
   proportion to its own aspect ratio, so all three resolve to one height and the
   row fills the frame exactly — and written separately on purpose. The two
   blocks share a technique and nothing else, and one class serving both would
   mean a change to the gallery silently re-laying the record's page.
   ONE ROW, SO NO WRAPPER. The gallery needs `.gal-grid` because it has two rows
   that have to become one grid on a phone; this has three figures and collapses
   to a single column, which the row can do by itself. */
.strip{
  display:flex; gap:clamp(.5rem,1vw,.875rem);
  margin:clamp(2.5rem,5vw,4rem) 0 0
}
.strip-f{ flex:var(--ar) 1 0; margin:0; min-width:0 }
.strip-f img{ display:block; width:100%; height:auto }
/* `#ministry`'s STRIP RULES WERE HERE AND WENT WITH THE STRIP, 2026-08-16.
   The page opened on three frames forced to one 4:3 crop, which is exactly what
   Rokas asked to stop ("altering sizes to make it nice, more of a gallery
   style") — a matched crop makes a row of one height by definition. The pictures
   are the `.gal-grid` in parts/ministry-gallery.html now, which solves each row
   against the frames' real ratios and is what produces the varying sizes.
   `.strip` itself is untouched and still builds music.html's opening. */
/* The band's own air, and it now sets the FOOT ONLY, 2026-08-17. The foot is
   tightened because the letterbox below is a photograph that arrives on its own
   and needs no gap announcing it.
   THE FOOT IS SET BY A MEASUREMENT, NOT BY TASTE. At `3.5rem` the band came out
   963px against the 960px screen rule (verify/fits-screen.js) — three pixels,
   which is still a fail and still the difference between a composition that fits
   and one that does not. 2.75rem lands it at 956.

   THE TOP CAME OFF, AND THAT IS THE UNIFORMITY FIX IN CSS. This declared
   `padding-block: 5.25rem <foot>`, and the shorthand's first value silently beat
   `.page-top`'s `padding-block-start` — which is `--header-h` PLUS a clamp — even
   though the section carries both classes. So music.html's first line sat about
   20px higher than ministry.html's, on two pages that are meant to open
   identically. Measured off two captures at 1440, not reasoned about. Setting only
   `padding-block-end` hands the top back to `.page-top`, which is the one place
   that knows what the fixed header costs. */
.music-open{ padding-block-end: clamp(2rem,3vw,2.75rem) }

/* ONE SECTION RHYTHM, EVERY PAGE, PHONE ONLY, 2026-08-17. Rokas: "make sure that
   each section is the same distance from the other section across the whole web
   page ... some are further away from the top of the image above them and some are
   closer." The base `.section` opens with a flat 5.25rem (84px), but many sections
   overrode it (the music beats, the gallery, the closing bands), so the gaps down a
   page were uneven. This sets ONE value for every ordinary section below the
   breakpoint. It supersedes the music-only rule that used to sit here.

   THE 84px WAS ALSO THE HEADER CLEARANCE for an anchor jump (the base rule's own
   note), which is why it could not simply be cut: a smaller pad would land a
   jumped-to section under the fixed bar. `scroll-margin-top:var(--header-h)` hands
   that job to the scroll anchor instead, so the visual gap and the jump clearance
   are decoupled and the gap can be both tighter and uniform.

   `.page-top` (the first section of each page) keeps its own larger opening, and the
   full-bleed bands (`.hero`, `.mband`) carry no `.section` class so they are already
   outside this selector and stay flush. */
@media (max-width:700px){
  main > .section:not(.page-top){
    padding-block-start: clamp(3rem, 8vw, 3.75rem);
    scroll-margin-top: var(--header-h)
  }
}

/* ---- .lb — the letterbox --------------------------------------------------
   A THIRD OF A SCREEN, NOT A WHOLE ONE, and that is the whole point of it
   existing next to `.poster`. This page already carries two full-height
   photographic bands (#consecration, #night) and the home page's record carries
   a third. A fourth would have made the rework cosmetic. This is a beat: one
   sentence on a wide picture, over before the reader has to decide anything.

   HEIGHT IS THE FRAME'S OWN RATIO, capped. The file is 2200x943 (2.33:1) and at
   1440 that renders 617px, which is two thirds of a screen and too much for a
   beat; 34vh holds it near 326px at 960 and the crop takes the rest off the top
   and bottom, where the frame is black curtain either way. */
.lb{ position:relative; isolation:isolate; display:grid; min-height:min(34vh,22rem) }
.lb-img{
  position:absolute; inset:0; z-index:0;
  width:100%; height:100%; object-fit:cover; object-position:50% 42%
}
/* THE SCRIM IS HORIZONTAL, WHICH NO OTHER BAND ON THIS SITE DOES, and it is the
   frame that decides it. `.poster::after` fades bottom-to-top because that band
   is a portrait with the subject at the top and the words underneath. Here the
   subject is in the MIDDLE of a wide cut and the words are at the left, so the
   fade runs left-to-right and is gone before it reaches him. Measured, not
   guessed — see verify/poster-contrast.js, which carries this band as a target. */
.lb::after{
  content:''; position:absolute; inset:0; z-index:0; pointer-events:none;
  background:linear-gradient(to right,
    rgba(0,0,0,.86) 0%, rgba(0,0,0,.78) 26%, rgba(0,0,0,.42) 44%, rgba(0,0,0,0) 62%)
}
.lb-in{
  position:relative; z-index:1; align-self:center;
  width:min(var(--maxw), calc(100% - 2*var(--frame-pad))); margin-inline:auto;
  padding-block:clamp(1.75rem,4vh,2.75rem)
}
/* CAPPED AT THE SAME 24rem THE POSTER'S QUOTE USES, and for the same reason
   recorded there: it keeps the words inside the dark third and hands the middle
   of the frame back to the picture. Ivory is hardcoded rather than tokened
   because this is a near-black photograph on all three directions, the same call
   `.poster-t` and `.poster-s` make. */
.lb-line{
  margin:0; max-width:24rem; color:#F7F1E6;
  font-family:var(--f-display); font-weight:400;
  font-size:clamp(1.125rem,.95rem + .8vw,1.625rem); line-height:1.3;
  letter-spacing:-.02em
}
.lb-line p{ margin:0 }
.lb-sec{ padding-block:0 }

/* ---- .flow — his lines, set the way he wrote them --------------------------
   HE BROKE THESE LINES, NOT US, and that is the whole rule this component
   exists to keep. `scripts/09`, `scripts/02` and `scripts/08` are written one
   beat per line because they were written for the screen. Running them into
   paragraphs would read as prose with strange punctuation; setting them as he
   broke them reads as what they are.

   SO THERE IS NO `text-wrap:balance` AND NO MEASURE CAP TIGHT ENOUGH TO REWRAP
   THEM. A cap that forces his longest line onto two lines would put a break he
   did not write next to seven he did, and the stack would stop reading as
   authored. 34ch clears every line in all three stacks at every width from 1024
   up; below that they wrap, and wrapping honestly on a phone is fine because
   every line wraps there.

   SENTENCE CASE, NOT `--display-case`. The site's display tokens are uppercase
   at weight 700, which is right for a one-sentence hinge (`.turn-h`, `.stmt-p`)
   and wrong for eight consecutive lines: eight uppercase lines is not emphasis,
   it is shouting. This takes the display FACE and the tight tracking and leaves
   the case and the weight alone. */
.flow{
  margin:0; max-width:34ch;
  display:grid; gap:clamp(.35rem,.8vw,.6rem);
  font-family:var(--f-display); font-weight:400;
  font-size:clamp(1.375rem,.95rem + 1.6vw,2.5rem); line-height:1.16;
  letter-spacing:-.025em; color:var(--ink)
}
.flow p{ margin:0 }
/* The band is only this poem, so it is sized by its air rather than by content,
   the same way `.turn` is. More above than below: the record's poster is
   released before the eye reaches the first line. */
/* TIGHTENED 2026-08-16 and now UNUSED, which is recorded rather than deleted
   because #first and #consecration are still `.flow` stacks and the next one
   built will want this air. `#adventure` was its only user and moved to `.stmt`
   the same day. */
.flow-sec{ padding-block: clamp(3rem,5vw,4.5rem) clamp(2.75rem,4.5vw,4rem) }

/* `.flow-across` WAS HERE AND IS GONE, 2026-08-16, after about two hours. It
   laid #adventure's eight lines inline with a faint dot at each of his breaks,
   which was the right answer to "extend the text out across the site so its a
   narrow break" and the wrong one to the instruction that replaced it: "make
   this bit same font/type/animation as this part", pointing at the home page's
   statement. The band is `.stmt-p` now. Dead CSS rots, git does not. */

/* ---- .stmt-cut — a statement BETWEEN two photographs ----------------------
   Added 2026-08-16 for #responding, the second half of the hinge, which stands
   between #consecration and #night.

   IT IS THE AIR THAT DIFFERS AND NOTHING ELSE. `.stmt`'s padding was measured
   off dsgngroup.it for a statement that OPENS on a ground, with the sentence
   ~240px into its own section: right when the block above is a hero, far too
   much when the blocks above and below are edge-to-edge photographs a screen
   tall each. At `.stmt`'s ceiling this band would be 500px of black around three
   short lines, which is the gap that was deliberately removed between those two
   pictures earlier the same day, reintroduced with words in it.

   BOTH NEIGHBOURS CONTRIBUTE NOTHING. `#consecration{padding-block-end:0}` and
   `#night{padding-block-start:0}` are still in force above, so this band's own
   padding is the ENTIRE distance between the two photographs. That is the point:
   the break between them is type, not space. */
.stmt-cut{ padding-block: clamp(4rem,6.5vw,6.5rem) }

/* ---- .flow-p — the same stack, over a photograph ---------------------------
   A quieter register, and every difference is forced by the ground rather than
   chosen. It sits under a title inside a `.poster`, so it cannot be title-sized;
   the ink is pinned to ivory rather than `var(--ink)` because the ground is a
   near-black photograph on all three directions, which is the same reason
   `.poster-t` hardcodes its own; and the measure widens because the lines here
   are his longest. */
.flow-p{
  max-width:46ch;
  font-size:clamp(1.0625rem,.95rem + .55vw,1.5rem); line-height:1.35;
  letter-spacing:-.015em; color:rgba(247,241,230,.90)
}
/* ---- .flow-q — the same stack, in a column beside a photograph -------------
   #first's eight lines, which are on the page's own black ground rather than on
   a picture. So this takes the page's ink and the page's body scale, and its
   only quarrel with `.flow` is the measure: at 34ch his longest line here breaks
   in three, and the whole point of the component is that his breaks are the only
   ones. It is not `.flow-p` either, because that one's ivory is pinned for a
   photograph that is not behind these words. */
.flow-q{
  max-width:none;
  font-size:clamp(1.0625rem,.98rem + .45vw,1.375rem); line-height:1.4;
  letter-spacing:-.015em; color:var(--ink-dim)
}
.flow-q .flow-lift{ color:var(--ink) }
/* ONE LINE PER STACK IS LIFTED, and it is always the line the passage turns on:
   what God said in #first, what rejection taught him in #consecration. It is a
   change of ink and weight only, never of size, so the stack keeps one type
   size and the lift reads as emphasis rather than as a second heading. */
.flow-lift{ color:#F7F1E6; font-weight:700 }
/* The stack takes the poster's lower row and sits at the FOOT of it, under the
   photograph's darkest part, which is where `.poster::after` has measured the
   scrim to be strongest. */
.first-say{ width:100%; padding-top:clamp(1.5rem,4vh,3rem) }
/* THE RAIL IS PINNED TO IVORY OVER A PHOTOGRAPH, for the same reason `.poster-t`
   and `.poster-s` are and with the same one-line justification: on Covenant
   `--ink-faint` is a dark blue, and these bands are near-black pictures on all
   three directions. It was measured at 1.08:1 on Covenant before this rule
   existed. The record's poster never needed it because it carries no rail.

   THE min-width GUARD IS LOAD-BEARING AND IS NOT TIDINESS. Below 900 the
   photograph becomes a plate and the words move onto the section ground, where
   ivory is wrong in the other direction — that revert lives in the max-width:900
   block far above this line, at the SAME specificity (0,2,0). Whichever of the
   two comes last in the file wins, and this one comes last. Written bare it beat
   the revert and put ivory on ivory at 390 on Covenant, measured at 1.00:1.
   The media query makes the two mutually exclusive instead of racing. */
@media (min-width:901px){
  .poster .rail{ color:rgba(247,241,230,.86) }
}

/* ---- .poster-band — the poster at one screen ------------------------------
   The record's `.poster` is `min-height:155vh`, and that height is a NAMED
   EXCEPTION in verify/fits-screen.js: Rokas put it back the same day it was
   capped, because on that band the height IS the crop and a screen took his
   shoulders off. These two bands have earned no such exception, so they take a
   height that fits the standing rule. 88vh rather than 100 leaves the next
   section's top edge visible, which is what tells a reader the page continues.

   `object-position` IS HIGH, NOT CENTRED, AND THAT IS A MEASURED CORRECTION.
   Centring was the first build and it read fine: the crown at the top of one
   frame, the raised fist at the top of the other, both kept. It failed the
   contrast probe, at 2.15:1 against a 3.0 bar, and the reason is that centring
   puts his LIT FACE in the middle of the band, which is exactly where the type
   is. `.poster::after` is a bottom-up gradient tuned on the record, where
   `object-position:50% 0%` pins the face to the top and leaves the whole lower
   band as black jacket for the words to sit on. Borrowing the scrim without
   borrowing the crop borrowed half a solution. 12% hands the bottom two thirds
   back to the jacket, which is what the gradient was measured against, and it
   keeps the raised fist on one frame and his lifted head on the other, both of
   which sit inside the top fifth.

   A THIRD FRAME COULD NOT BE FIXED BY A CROP AT ALL, and it is recorded here so
   nobody re-tries it. `crown-reach` was on this component first and failed at
   2.15:1, then still 1.67:1 with the type moved to the foot. A one-screen band
   shows about 38% of a 2:3 portrait's height, and in that frame the crown, the
   face and the jacket do not fit inside any 38% window together, so every crop
   that darkened the words threw away the subject. It moved to a PLATE on #first,
   where it keeps its whole frame, and `smoke-blue` took the band because it is
   evenly dark corner to corner. The general rule: when type will not clear a
   photograph, ask whether the photograph belongs in this component before
   touching the scrim. */
.poster-band{ min-height:min(88vh,52rem); margin-top:0 }
.poster-band .poster-img{ object-position:50% 12% }
/* EVERY WORD SITS AT THE FOOT ON BOTH BANDS. `.poster-in` splits into a top row
   for the head and a bottom row for the copy, which is right on the record: that
   band is 155vh, so the two groups are a screen apart and the title has its own
   dark sky to sit in. These bands are one screen. Split across it, the head
   lands in the middle of the photograph — on the crown in one and on a lit grey
   wall in the other — and both measured under their bar. Together at the foot,
   the rail, the heading and the lines share the strongest part of the gradient
   and the picture gets its top two thirds uninterrupted. */
.poster-band .poster-in{ grid-template-rows:auto auto; align-content:end }
/* A FULL SENTENCE, NOT A TITLE, so it cannot take the album title's size. "I Am
   CHOSEN" is three words and runs at 5.5rem; "Rejection became my consecration."
   is four words but 33 characters, and at that size it takes three lines and
   swallows the band. Same component, one step down. */
.poster-t-s{ font-size:clamp(1.75rem,1rem + 2.6vw,3.25rem); line-height:1.06 }
/* THE BAND IS NOT ONE SCREEN ON THE PHONE, 2026-08-17. Rokas, on #consecration:
   "move that a bit up closer to the image." On desktop the band is 88vh and the
   words sit at its FOOT over the picture; on the phone the picture is a plate at the
   TOP (the .poster mobile block turns it into one) and the words fall on the ground
   below it, so the base 88vh height plus align-content:end drop a screen of black
   between the plate and the heading. Auto height and align-content:start bring the
   words up directly under the plate. Applies to both bands (#consecration, #night). */
@media (max-width:900px){
  .poster-band{ min-height:0 }
  .poster-band .poster-in{ align-content:start }
}
/* #night's HEADING TAKES THE QUOTE'S CAP, and it is the same fix this file has
   already made once on the same band. `.saying-q` is capped at 24rem with the
   note "make the quote smaller to not cover his face that much": that block is
   two columns, so the words keep to the left while the photograph keeps the
   middle. The heading was outside that arrangement and ran the full frame,
   straight across his lit jaw, and the probe read it at 1.20:1 against a 3.0
   bar. Sharing the cap puts the heading, the rail and the quote on one left
   rule and hands the face back to the picture.
   NOT applied to `.poster-t-s` itself, because #consecration's heading is his
   own sentence over a black jacket with nothing to avoid, and capping it there
   would break one line into three for no reason. */
#night .poster-t-s{ max-width:24rem }
/* ---- THE TWO BANDS BUTT UP, 2026-08-16 -------------------------------------
   Rokas, on a screenshot of #night: "remove the gap between the images."

   IT WAS 132px OF BLACK BETWEEN TWO PHOTOGRAPHS and it was `.section`'s own
   padding, twice: #consecration's foot (up to 48px) plus #night's head (84px).
   That head is the FIXED HEADER'S CLEARANCE and it is load-bearing on every
   section that can be the first thing under the bar — which is why the padding
   is not being cut globally. #night can never be that section: it is the sixth
   band on one page and there is a full-screen photograph directly above it, so
   the clearance it is paying for is clearance it does not need.

   WHY IT READ AS A FAULT RATHER THAN AS AIR. Both bands are full-bleed pictures
   running edge to edge, so the gap between them was a black stripe the width of
   the window with a hard edge top and bottom. Between two text sections the same
   padding reads as breathing; between two photographs it reads as a seam. The
   two now cut straight from the black and white frame into the blue one, which
   is the cut the page wanted at the point it drains the colour back in.

   ONLY BETWEEN THESE TWO. #consecration keeps its top padding (the band above it
   is type) and #night keeps its foot (the contact below it is type).

   A BAND OF TYPE NOW STANDS BETWEEN THEM AND THESE TWO RULES STILL HOLD — in
   fact they are what make it work. `#responding` was inserted between the two
   photographs later the same day ("split half of that and insert it inbetween
   these sections to break them up"). With both neighbours contributing zero, the
   whole distance between the two pictures is that band's own padding, so the
   break between them is three of his lines rather than three of his lines PLUS
   the 132px of black these rules exist to remove. */
/* THE ZERO GAP IS RETIRED, 2026-09-10. It butted #consecration's photo band against
   #night's, with #responding's type between them. #consecration is text now (its B&W
   photo removed) and #responding is gone, so both sections take their normal `.section`
   padding and the type sits off the blue photograph with air. */

/* ---- .night-say — the quote and the passage, side by side -----------------
   Added 2026-08-16. Rokas: "move the paragraph over to the right of the image."

   THE RIGHT OF THE IMAGE IS THE RIGHT OF THE BAND, because this is a full-bleed
   `.poster` and there is no outside to move to. Stacked, the quote and the two
   paragraphs ran down the left rule and pushed the band past a screen; split,
   the quote takes the left and the passage takes the right, and the band closes
   at its own height.

   IT IS `.saying`'S ARRANGEMENT, NOT A NEW ONE. The home page's record already
   sets these two components against each other this way, which is why
   `.saying-p` was written with `justify-self:end` — a rule that had nothing to
   end against while the two were stacked here. The columns are 1fr each rather
   than the record's split, because #night's own heading is capped at 24rem on
   the left already and matching that cap keeps the quote, the heading and the
   rail on one rule.

   THE PHONE KEEPS THE STACK. The `max-width:900` block far above this line
   already un-caps and un-aligns both components for one column, and the grid
   collapses to one column here for the same reason: the caps exist to keep clear
   of a face that is not behind the words at that size. */
.night-say{
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  column-gap:clamp(1.5rem,4vw,3.5rem); align-items:end
}
/* THE PASSAGE IS HARD RIGHT, AND IT WAS LEFT FOR ONE BUILD. I started it at its
   column's left rule so the quote and the passage would draw two clean left
   edges; Rokas, on a capture: "the right paragraph should be against the right
   side to avoid covering his face." He is right and the measurement says so —
   at 1440 his lit cheek runs to about x 1150 and the passage started at x 748,
   so a third of every line was crossing his face. Ended against the frame, its
   left edge lands at x 963 and clears him.
   A ragged left edge is the cost and it is the smaller one: this is type over a
   photograph, and the subject wins the argument with the grid. `align-self:end`
   stays, so both columns still sit at the foot of the band. */
.night-say .saying-p{ align-self:end; justify-self:end }
@media (max-width:900px){
  .night-say{ grid-template-columns:1fr; row-gap:clamp(1.5rem,4vw,2.25rem) }
}
/* THE FRAME IS ALREADY MONOCHROME; this only stops the gradient crushing it.
   `.poster::after` is a black scrim measured against the record's beaded jacket,
   where specular sparkle needed burying under a ground that was already near
   black. This frame is a plain grey studio wall: no sparkle, and far more
   mid-tone to lose. Same shape, less weight. */
.poster-bw::after{
  background:linear-gradient(to top,
    rgba(0,0,0,.78) 0%, rgba(0,0,0,.68) 30%, rgba(0,0,0,.34) 56%, rgba(0,0,0,0) 80%)
}
/* ---- #consecration WRITES AT THE TOP NOW, 2026-08-16 -----------------------
   Rokas, on a capture of this band: "the text in this section should be up at
   the top to not cover his face." The note below this block argued against
   exactly that and is KEPT: the trade it names is real, and it is now a trade he
   has chosen with the thing in front of him rather than one taken by default.

   THE BLANKET VERSION THAT NOTE REFUSES IS STILL REFUSED, and that is the whole
   design of the fix. "Raising the scrim to cover the top" would dim his raised
   fist, which is the top of this picture and half its subject. MEASURED off the
   frame with the type hidden (1440x792): the fist occupies x 600-1000, his face
   x 250-800 below y 180, and the top-LEFT corner — x 0-450, y 0-250 — is empty
   studio wall. So the scrim is a full-height gradient MASKED to the left third.
   It is dark where the words are and gone before the fist.

   IT HANGS OFF `.poster`, NOT off `.poster-in`, AND THAT IS A RIG FIX. It was on
   `.poster-in::before` for one run and verify/poster-contrast.js reported this
   band at 2.80:1 — WORSE than before the scrim existed. The scrim was fine; the
   probe blanks the type by setting `visibility:hidden` on `.poster-in`, which
   takes that element's pseudo-elements with it, so the rig was photographing a
   band whose scrim it had just switched off and rating the type against a state
   that never renders. A scrim belongs to the PICTURE, and putting it there is
   what makes the measurement true as well as what makes the code right.
   `z-index:1` puts it above `.poster-img` and `.poster::after` (both 0) and
   below `.poster-in` (also 1, and later in tree order, so it wins).

   WHY A SCRIM AT ALL, since a grey wall looks light enough to write on in dark
   ink: it is not light enough. The worst pixel where the type sits measured
   rgb(149,149,149) — 2.66:1 against ivory (bar 3.0) and 2.20:1 against a dark
   ink. A mid-grey that fails in BOTH directions is the one case where the answer
   is a scrim rather than a colour.

   THE STOPS ARE FROM A FAILED RUN, NOT FROM TASTE. The first build used a corner
   radial and the probe refused it at 3.92:1 against a 4.5 bar on "his script":
   the radial ran out at about y 430 while the stack runs to y 460, and an ellipse
   anchored in a corner is a poor cover for a rectangle of text. A vertical
   gradient plus a horizontal mask covers a rectangle exactly.

   EVERYTHING HERE IS ABOVE 900 ONLY, and that is not caution, it is the trap
   this file already documents two screens up: below 900 `.poster::after` is
   `content:none`, the photograph becomes a plate and the words move onto the
   section's own ground, where ivory is wrong in the other direction. Written
   bare, `#consecration .flow-p{color:#F7F1E6}` beat `.poster .flow-p` on
   specificity (an id outranks two classes) and the probe caught it immediately:
   1.00:1 at 390 on Covenant. Ivory on ivory. The same mistake, in the same
   component, that the `.poster .rail` rule carries a paragraph about. */
@media (min-width:901px){
  #consecration .poster::before{
    content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
    background:linear-gradient(to bottom,
      rgba(0,0,0,.90) 0%, rgba(0,0,0,.86) 46%, rgba(0,0,0,.55) 68%, rgba(0,0,0,0) 84%);
    -webkit-mask-image:linear-gradient(to right, #000 0%, #000 30%, rgba(0,0,0,0) 52%);
    mask-image:linear-gradient(to right, #000 0%, #000 30%, rgba(0,0,0,0) 52%)
  }
  /* NO MASK, NO BAND. Where `mask-image` is unsupported the gradient would paint
     across the whole width and dim the fist, which is the outcome the note below
     refuses. Better to render the band with its ordinary bottom scrim and type
     that has moved up than to ship the blanket tint. Both engines this site is
     tested on support it. */
  @supports not ((mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000))){
    #consecration .poster::before{ display:none }
  }
  /* THE WORDS GO TO THE TOP ON THIS BAND ONLY. `.poster-band .poster-in` anchors
     every band's type to the foot, which is still right on #night and on the
     record. It is overridden here together with the scrim, as ONE change:
     neither half works without the other, and separating them is how a future
     edit puts ivory letters back onto a lit grey wall. */
  #consecration .poster-in{ align-content:start }
  /* CAPPED SO THE WORDS STAY INSIDE THE MASK. The mask is solid to x 432 and
     gone by x 749; without a cap the heading runs the full frame and the stack
     runs to 46ch, and both walk out of the dark and onto the fist. 26rem ends
     every line by about x 452, inside the solid part.
     The heading steps down for the same reason and only here: at the shared
     `.poster-t-s` size it needs three lines in this measure. */
  #consecration .poster-head,
  #consecration .first-say{ max-width:26rem }
  #consecration .poster-t-s{ font-size:clamp(1.625rem,1rem + 1.9vw,2.375rem) }
  /* The stack sat under a title a screen away in the old arrangement, so it
     carried its own top padding. At the top of the band the head and the stack
     are one block and that padding is a gap inside it. */
  #consecration .first-say{ padding-top:clamp(1rem,2.2vh,1.75rem) }
  /* FULL IVORY, NOT THE COMPONENT'S 90%. `.flow-p`'s ink is stepped back for a
     stack sitting on the darkest part of a bottom scrim; this stack sits on a
     photographed wall under a masked one, where the 10% is contrast the band
     does not have to give away. */
  #consecration .flow-p{ color:#F7F1E6; max-width:none }
}
/* EVERY WORD GOES TO THE FOOT ON THIS BAND, AND IT IS A CONTRAST FIX, not a
   composition preference. `.poster-in` puts the head in a top row and the copy
   in the row below, which is right on the record: that photograph is black at
   the top and the title sits on it. This one is a LIT GREY WALL at the top, and
   the first build put ivory letters straight onto it. It was unreadable in the
   first capture, which is the whole reason the band is shot before it is judged.

   Raising the scrim to cover the top would have been the other fix and it is the
   wrong one twice over: the top of this frame is his face and his raised fist,
   so darkening it hides the subject, and a scrim that covers a band top to
   bottom is not a scrim, it is a tint over a photograph.

   The bottom-anchoring itself now lives on `.poster-band` above, because #first
   failed the same probe for the same reason and both bands needed it. What is
   left here is only the scrim weight, which is the part that is genuinely about
   this photograph rather than about the layout. */

/* `.songs` / `.song-t` / `.song-d` WERE HERE AND ARE GONE, 2026-08-16. They were
   the dl that set five titles against five of his sentences. Rokas: "turn the
   tracklist into an aesthetic player that can play the music", so the dl became
   `.pl-list` below and its rules went with its markup. Dead CSS rots, git does
   not. The type sizes were not thrown away: `.pl-t` and `.pl-d` carry the same
   two scales, because the split they encode — the title is the thing being
   listed, the sentence is what it is for — did not change. */

/* ---- .deck — the record as a player ---------------------------------------
   Added 2026-08-16 (third pass), replacing `.pl-list` / `.pl-row` from earlier
   the same day. Rokas, with a Vevo album-page reference: "this section can be
   made into a player like this."

   THE PORT, and what it takes and leaves, is argued in parts/music-named.html
   where the markup is. This block is the arrangement.

   THE SPLIT IS INSIDE THE FRAME, NOT FULL BLEED. The reference runs edge to edge
   inside a device; this site's measure is `--maxw` and every other band on the
   page keeps it. A full-bleed deck would be the one component that breaks the
   page's rhythm to look like a screenshot of something else.

   ROWS NOW CARRY A HAIRLINE AND THAT IS A REVERSAL. The `dl` this replaced
   refused rules between rows twice, with the reason written down: "a table of
   five songs on a record with seven is a tracklist that is missing two." The
   reference is a list of rows separated by rules, Rokas pointed at it, and the
   argument that was actually being made is weaker here than it was: what made
   the old block a table was five ROWS of two columns of prose. These rows are
   one line each with a control on them, which is a list of tracks in every
   player ever built, and the rule is what makes the row a hit area rather than
   floating type. Recorded as a reversal so it is not read as forgetfulness. */
.deck-sec{ padding-block: clamp(5.25rem,4.4vw,5.25rem) clamp(2.25rem,2.8vw,3rem) }
/* ---------------------------------------------------------------------------
   RESTYLED TOWARDS APPLE MUSIC (DARK) 2026-08-17. Rokas: "make the music player
   more apple style - dark mode."

   WHAT WAS ACTUALLY UN-APPLE ABOUT IT, because the colours were already dark and
   that was not the problem. Four things, all geometry and affordance:
     1. Square corners on a component that sits on the page ground. Apple's media
        surfaces are rounded objects; a square one reads as a section, not a card.
     2. A full-width hairline between every row. Apple insets its separators so
        they begin under the TITLE, which is what makes a list read as items in a
        container rather than as a table.
     3. A bordered circle around every row's play triangle. Apple never rings a
        list glyph; the triangle appears on hover and is otherwise quiet.
     4. No hover state on the rows at all, so nothing said a row was pressable
        until the cursor was already over the title.

   WHAT IS DELIBERATELY NOT CHANGED: the panel's colour. `--ground-2` is themed,
   and this site ships three directions; hardcoding Apple's neutral #1c1c1e would
   look right on Precipitation and wrong on the other two. The Apple quality here
   comes from the geometry and the controls, which are theme-independent.

   NO JAVASCRIPT CHANGED. Every hook js/player.js reads — `.deck-btn`, `.pl-ico`,
   `.pl-rail`, `.pl-fill`, `.deck-dur`, `--seek` — is still where it was, and the
   pause shape is still one box-shadow on the same pseudo-element. */
/* ---------------------------------------------------------------------------
   RESTYLED AGAIN, TOWARDS THE iOS "NOW PLAYING" GLASS CARD, 2026-08-17. Rokas,
   with an iOS lock-screen player reference: "make the style of the main player on
   the page same as this (apple aesthetic)."

   THE ONE STRUCTURAL MOVE is that the artwork now sits BEHIND THE WHOLE CARD, not
   in a column beside the panel, and the panel is a frosted glass laid over it.
   `.deck-art` goes absolute and fills the card; the panel is forced into the right
   column and made translucent, so the left half shows the sharp photograph and the
   right half shows the same photograph as a blurred glass wash under the controls.
   That is the reference exactly: one photo, one glass surface, no seam of a box.

   THE GLASS IS THE ISLAND'S OWN RECIPE, `--isl-bg` + backdrop blur, and that is
   deliberate: the deck and its docked island are now visibly the same material,
   and `--isl-bg` is themed, so this holds on all three directions where a
   hardcoded Apple neutral would not. Legibility never depends on the blur landing:
   `--isl-bg` is opaque enough (.80 to .88) to hold its own type, exactly as the
   island's note argues. */
.deck{
  position:relative;
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  align-items:stretch; min-height:24rem;
  /* One rounded object. Rounder than before to read as an iOS media card rather
     than a section, and a soft shadow so it lifts off the page ground. */
  border-radius:clamp(1.15rem,2vw,1.75rem); overflow:hidden;
  box-shadow:var(--shadow)
}
/* THE PHOTOGRAPH DOES NOT SET THE HEIGHT, THE PANEL DOES, which is the whole
   reason the image is absolutely positioned inside a relative box rather than
   laid out in flow. In flow a 2:3 portrait at half a 1368px frame is 1026px tall
   and the band becomes the tallest thing on the page; out of flow it takes
   whatever height the panel resolves to and crops to it, exactly as the two
   halves of the reference do. */
/* `min-width:0` IS LOAD-BEARING AND IS NOT TIDINESS. A grid item's default
   `min-width:auto` refuses to shrink below its content's minimum, and this
   figure resolved to 1014px inside a 175px track on a phone — the photograph
   escaped its half of the split and ran under the panel, which is what the first
   mobile capture showed. Measured, not guessed: `.deck-art` reported w:1014 in a
   grid whose computed tracks were `175px 175px`. */
/* THE LEFT HALF OF THE CARD, CROPPED AS A PORTRAIT. It was briefly absolute and
   full-span so the panel's blur could read it as a backdrop, but a 2:3 portrait
   cover-cropped into the full 2.4:1 card collapsed to a thin horizontal band —
   the "album cover got messed up" (Rokas, 2026-08-17). Back in its own column it
   crops to a portrait again. The panel keeps its glass; it blurs the card ground
   rather than the art now, which is the honest trade for a cover that reads. */
.deck-art{ position:relative; grid-column:1; margin:0; overflow:hidden; min-width:0; min-height:20rem }
.deck-art img{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; object-position:50% 20%; display:block
}
/* The panel is a component surface, which is what `--ground-2` is for: lifted
   off the page ground on all three directions so the split reads as two
   materials rather than as a photograph with a hole beside it. */
/* THE GLASS. `grid-column:2` forces it into the right half so the left half is
   the empty cell that shows the sharp photograph; z-index:1 lifts it over the art;
   `--isl-bg` + the blur below make it the frosted surface. Reset to auto on the
   phone, where the card is one column and the glass covers the whole photo. */
.deck-panel{
  position:relative; z-index:1; grid-column:2;
  background:var(--isl-bg);
  -webkit-backdrop-filter:blur(26px) saturate(1.4);
  backdrop-filter:blur(26px) saturate(1.4);
  padding:clamp(1.5rem,2.6vw,2.5rem);
  /* Six rows, one per child, and the LIST takes the 1fr so the bar is pushed to
     the foot of the panel however many songs are named. Written out rather than
     left to implicit rows, because an implicit row is where the next child
     silently lands. */
  display:grid; grid-template-rows:auto auto auto 1fr auto auto; align-content:start;
  min-width:0
}
.deck-rail{ margin:0 0 clamp(.75rem,1.6vw,1.1rem) }
/* THE CURRENT TRACK, and it is the biggest type in the block on purpose: on the
   reference the album's current song is the headline and the list is secondary.
   `text-wrap:balance` because these titles are two to four words and a lone
   orphan word under a five-word title is the failure mode at this size. */
.deck-now{
  margin:0; font-family:var(--f-display); font-weight:400;
  font-size:clamp(1.75rem,1.1rem + 1.9vw,2.75rem); line-height:1.08;
  letter-spacing:-.025em; color:var(--ink); text-wrap:balance
}
/* His line about the selected song. Reading size and stepped back, the same
   split `.pl-d` carried when the five sat in the rows. */
.deck-line{
  margin:clamp(.7rem,1.4vw,1rem) 0 clamp(1.25rem,2.4vw,1.75rem);
  max-width:46ch; color:var(--ink-dim);
  font-size:clamp(.9375rem,.9rem + .25vw,1.0625rem); line-height:1.55;
  text-wrap:pretty
}
.deck-list{ margin:0; padding:0; list-style:none; align-self:start }
/* `--deck-inset` IS THE ONE NUMBER THE ROW IS BUILT FROM: the glyph's width plus
   the gap after it, i.e. exactly where the title starts. The separator is inset by
   it and nothing else has to know about it. */
.deck-row{
  --deck-inset: calc(1.125rem + clamp(.6rem,1.2vw,.85rem));
  position:relative; display:grid;
  grid-template-columns:minmax(0,1fr) auto; align-items:center;
  column-gap:1rem; padding-block:clamp(.55rem,1.1vw,.75rem);
  /* The fill needs room to bleed past the text without indenting it, so the
     padding and the margin cancel. */
  padding-inline:.625rem; margin-inline:-.625rem;
  border-radius:.5rem;
  transition:background-color .45s var(--ease-smooth)
}
/* THE SEPARATOR IS A PSEUDO-ELEMENT, NOT A BORDER, because a border cannot be
   inset from the left edge of the box it is on. It sits at the TOP of each row
   and is suppressed on the first, which is the same result the old `border-top`
   gave and the same reason the last row still has none: the bar closes the list. */
.deck-row + .deck-row::before{
  content:""; position:absolute; top:0; left:var(--deck-inset); right:0;
  height:1px; background:var(--line)
}
/* THE HOVER IS THE LINE NOW, NOT A FILL, and not gold. It warmed toward the aura's
   gold for a day; Rokas struck the gold across all three lists at once ("get rid of
   the gold colour. just have the same animation with the line"), which returns the
   deck to the site's own no-accent rule. So on hover a bright hairline draws across
   the top of the row from the left, the same gesture the #practice list takes, and
   the title steps up to full ink (below). A static seam already sits at the row top
   (`.deck-row + .deck-row::before`); this `::after` is the bright one that grows over
   it, so the seam reads as lighting up rather than a second line appearing.
   The selected row keeps its neutral fill, so hover and now-playing stay distinct. */
.deck-row::after{
  content:""; position:absolute; top:0; left:var(--deck-inset); right:0;
  height:1px; background:var(--ink); transform:scaleX(0); transform-origin:left;
  transition:transform .5s var(--ease-arrive)
}
@media (hover:hover){
  .deck-row:has(.deck-btn:hover):not(.is-off)::after{ transform:scaleX(1) }
}
@media (prefers-reduced-motion: reduce){ .deck-row::after{ transition:none } }
.deck-row.is-cur{ background:rgba(247,241,230,.08) }
/* NO BOTTOM RULE ON THE LAST ROW. It had one for a build and it drew a second
   hairline about 25px above the bar's own top rule, which reads as a rendering
   fault rather than as two boundaries. The bar closes the list. */
/* A BUTTON THAT LOOKS LIKE A ROW. Every default a browser gives a button is
   stripped; the row is the control and the glyph is only where it starts. */
.deck-btn{
  appearance:none; -webkit-appearance:none;
  margin:0; padding:0; border:0; background:none; text-align:left;
  display:inline-flex; align-items:center; gap:clamp(.6rem,1.2vw,.85rem);
  color:var(--ink-dim); cursor:pointer; min-width:0;
  transition:color .45s var(--ease-smooth)
}
.deck-row.is-cur .deck-btn{ color:var(--ink) }
/* The other half of the hover: the title steps up from dim to full ink, the
   neutral version of the pop. currentColor carries it into the title AND the little
   play triangle at once, since both take it. Ink, not gold, since the site holds no
   accent colour and the line does the work of drawing the eye. */
@media (hover:hover){
  .deck-row .deck-btn:hover{ color:var(--ink) }
}
.deck-btn:focus-visible{ outline:2px solid var(--ink); outline-offset:4px; border-radius:2px }
.pl-t{
  font-size:clamp(.9375rem,.9rem + .3vw,1.125rem); line-height:1.3;
  letter-spacing:-.005em;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap
}
/* THE GLYPH IS DRAWN, NOT SET, and it is two shapes rather than two characters.
   A triangle typed as text ("\25B6") renders at a different weight and baseline
   in every face the direction toggle can swap in, and carries an emoji
   presentation on some engines. Borders make the same triangle everywhere.
   It is shared with the island and with the transport, so the three controls are
   visibly one control in three places. */
.pl-ico{
  flex:none; position:relative;
  width:1.625rem; aspect-ratio:1;
  border:1px solid var(--line-strong); border-radius:50%;
  transition:border-color .3s ease, transform .3s cubic-bezier(.2,.9,.25,1)
}
/* THE RING COMES OFF INSIDE THE LIST ONLY, 2026-08-17. Apple does not ring a
   glyph in a track list — the triangle sits bare and quiet, and brightens under
   the cursor. The ring is KEPT on the island and on the transport, where the glyph
   is a standalone control rather than an item marker and needs an edge to be
   findable. Same element, two jobs, so this is scoped to the row rather than
   changed at source. */
.deck-row .pl-ico{
  width:1.125rem; border:0; border-radius:0;
  opacity:.45; transition:opacity .2s ease, transform .3s cubic-bezier(.2,.9,.25,1)
}
.deck-btn:hover .pl-ico{ border-color:var(--ink); transform:scale(1.08) }
.deck-row .deck-btn:hover .pl-ico,
.deck-row.is-cur .pl-ico{ opacity:1; transform:none }
.pl-ico::before{
  content:""; position:absolute; inset:0; margin:auto;
  width:0; height:0;
  border-left:.4rem solid currentColor;
  border-top:.27rem solid transparent; border-bottom:.27rem solid transparent;
  transform:translateX(.07rem)
}
/* PAUSE IS THE SAME ELEMENT, RESHAPED. One box-shadow draws the second bar, so
   the playing state costs no extra node and cannot get out of step with the
   first. */
.deck-row.is-playing .pl-ico::before,
.isl.is-playing .isl-ico,
.deck-bar.is-playing .pl-ico::before{
  border:0; width:.15rem; height:.58rem;
  background:currentColor; box-shadow:.28rem 0 0 currentColor;
  transform:translateX(-.14rem)
}
.deck-row.is-playing .pl-ico{ border-color:var(--ink) }
/* The duration column stays empty until a file's metadata says otherwise. There
   is no audio in the project, so a printed run time would be an invented number;
   tabular figures keep the five rows aligned once real ones arrive. */
.deck-dur{
  font-size:.8125rem; color:var(--ink-faint);
  font-variant-numeric:tabular-nums
}
/* HIS SENTENCE IN THE ROW IS THE NO-JS RENDER. js/player.js adds `is-live` to
   the deck and the head takes over showing one at a time. Hidden by the enhanced
   state, never by the base one. */
.deck.is-live .pl-d{ display:none }
.pl-d{
  grid-column:1 / -1; margin:.35rem 0 0; color:var(--ink-dim);
  font-size:.9375rem; line-height:1.5
}
/* The playing row's own progress, drawn at its foot. Opacity rather than
   display, so it fades in with the press instead of the list reflowing by a
   pixel under the reader's hand. */
.pl-rail{
  position:absolute; left:0; right:0; bottom:-1px; height:1px;
  background:transparent; opacity:0; transition:opacity .35s ease
}
.deck-row.is-cur .pl-rail{ opacity:1 }
.pl-fill{ display:block; width:0; height:100%; background:var(--ink); transition:width .25s linear }
/* A track with no file. The row keeps its title, because that is content and it
   is still true; it loses the offer to play. */
.deck-row.is-off .deck-btn{ cursor:default; color:var(--ink-faint) }
.deck-row.is-off .pl-ico{ opacity:.35 }
/* THE REASON, ONCE, UNDER THE LIST. It was a tag beside each title first and
   that version moved the layout: the tag sat in the grid's first column, so the
   column grew on the first press and every sentence beside it jumped right and
   rewrapped. */
.pl-note{
  margin:clamp(.9rem,1.8vw,1.25rem) 0 0;
  font-size:.75rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-faint)
}

/* ---- .deck-bar — the now-playing strip ------------------------------------
   The reference's foot bar, minus its waveform and its volume control. The
   waveform is left because ours would be a decorative squiggle posing as the
   amplitude of a song that does not exist yet; volume is left because it belongs
   to the device, and a page-level slider is a second, disagreeing copy of a
   control the reader already has. */
.deck-bar{
  align-self:end;
  margin-top:clamp(1.25rem,2.4vw,1.75rem); padding-top:clamp(.9rem,1.8vw,1.25rem);
  border-top:1px solid var(--line-strong);
  display:grid; grid-template-columns:auto auto auto minmax(0,1fr) auto;
  align-items:center; gap:clamp(.5rem,1.2vw,.8rem)
}
.deck-tr{
  appearance:none; -webkit-appearance:none; margin:0; padding:0; border:0;
  background:none; color:var(--ink-dim); cursor:pointer;
  /* 44px THUMB TARGET, 2026-08-17 (Codex responsive audit): the box was 32px, under
     the touch floor. The glyph (.deck-tr-i) keeps its own size; only the hit area
     grows. place-items:center keeps the glyph centred. */
  min-width:44px; min-height:44px; display:grid; place-items:center;
  transition:color .4s var(--ease-smooth), opacity .4s var(--ease-smooth), transform .3s var(--ease-smooth)
}
.deck-tr:hover:not(:disabled){ color:var(--ink) }
.deck-tr:disabled{ opacity:.35; cursor:default }
.deck-tr:focus-visible{ outline:2px solid var(--ink); outline-offset:2px; border-radius:50% }
/* THE KEY CONTROL IS THE ONLY FILLED THING IN THE PANEL, which is how a reader
   finds play without reading any of it. Same inverse pair the island's button
   uses, from the same tokens. */
/* WHITE, NOT THE ISLAND'S GREY, 2026-08-17. `--isl-key` reads as a disabled
   control at this size against the panel — in the capture Rokas sent it looked
   like a placeholder rather than the one thing on the panel you are meant to
   press. Apple's play control is the brightest object in its bar. `--ink` and
   `--ground` are the site's own inverse pair, so this borrows no new colour. */
/* THE SIGNATURE PILL, 2026-08-17. The reference's play control is a large rounded
   SQUARE, not a circle, and it is the one big object on the surface. So this grows
   and its corners soften to a squircle; it stays the site's inverse pair (--ink on
   --ground) so it is the brightest thing on the glass, which is the point Apple's
   own play button makes. A soft shadow seats it on the glass. */
/* SMALLER, 2026-08-17. Rokas: "make the pause button smaller." It was clamped to
   3.6rem, which read oversized against Apple's own play control; this is closer to
   the ~44px it sits at there. Still the site's inverse pair and still the brightest
   thing on the panel. */
.deck-tr-key{
  background:var(--ink); color:var(--ground);
  border-radius:clamp(.8rem,1.2vw,1.05rem);
  width:clamp(2.5rem,3.2vw,2.9rem); height:clamp(2.5rem,3.2vw,2.9rem);
  box-shadow:0 5px 14px rgba(0,0,0,.26)
}
.deck-tr-key:disabled{ opacity:.28 }
/* The glyph fills the button, then scales up so the triangle and the pause bars
   read at the button size. Scaling the wrapper carries the ::before shape with it,
   so play and pause enlarge together. */
.deck-tr-key .pl-ico{ border-color:transparent; width:100%; scale:1.15 }
.deck-tr-key:hover:not(:disabled){ color:var(--isl-keyink); transform:scale(1.05) }
/* Prev and next: a triangle against a bar, drawn the same way as the play
   glyph so all three transport shapes share one construction. */
.deck-tr-i{ position:relative; display:block; width:.85rem; height:.7rem }
.deck-tr-i::before{
  content:""; position:absolute; top:50%; translate:0 -50%;
  width:0; height:0;
  border-top:.35rem solid transparent; border-bottom:.35rem solid transparent
}
.deck-tr-i::after{
  content:""; position:absolute; top:50%; translate:0 -50%;
  width:.11rem; height:.7rem; background:currentColor
}
.deck-tr-prev::before{ right:0; border-right:.45rem solid currentColor }
.deck-tr-prev::after{ left:0 }
.deck-tr-next::before{ left:0; border-left:.45rem solid currentColor }
.deck-tr-next::after{ right:0 }
/* THE SEEK IS A NATIVE RANGE, which is the one control on this site where the
   native element beats a styled div outright: it is draggable, arrow-keyable and
   announced as a slider with no keyboard code of ours. Only its skin is ours.
   `--seek` is written by js/player.js and paints the played portion, because a
   range input has no standard way to colour the track before the thumb. */
.deck-seek{
  appearance:none; -webkit-appearance:none;
  width:100%; height:1.25rem; margin:0; background:none; cursor:pointer
}
.deck-seek:disabled{ cursor:default; opacity:.5 }
.deck-seek:focus-visible{ outline:2px solid var(--ink); outline-offset:2px }
.deck-seek::-webkit-slider-runnable-track{
  height:5px; border-radius:3px;
  background:linear-gradient(to right, var(--ink) 0 var(--seek,0%), var(--line-strong) var(--seek,0%) 100%)
}
.deck-seek::-moz-range-track{
  height:5px; border-radius:3px;
  background:linear-gradient(to right, var(--ink) 0 var(--seek,0%), var(--line-strong) var(--seek,0%) 100%)
}
.deck-seek::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none;
  width:.85rem; height:.85rem; border-radius:50%; border:0;
  background:var(--ink); margin-top:-.27rem;
  box-shadow:0 1px 4px rgba(0,0,0,.3)
}
.deck-seek::-moz-range-thumb{
  width:.7rem; height:.7rem; border-radius:50%; border:0; background:var(--ink)
}
/* Tabular figures so the readout cannot jog the bar's last column as the seconds
   tick, and a dimmer ink than the controls because it is a readout rather than a
   thing to press. */
.deck-time{
  margin:0; font-size:.75rem; color:var(--ink-faint);
  font-variant-numeric:tabular-nums; white-space:nowrap;
  letter-spacing:.01em
}
/* THE SEEK SITS IN ITS OWN AIR, 2026-08-17. Apple's scrubber is not flush against
   the transport buttons; the gap is what stops a drag starting on `next`. */
.deck-bar .deck-seek{ margin-inline:clamp(.35rem,1vw,.75rem) }

/* ONE COLUMN WHEN THE DECK GETS NARROW. The photograph goes on top, which is the
   order the markup is already in.

   A MEDIA QUERY, AND IT WAS A CONTAINER QUERY FIRST. The list this replaced used
   `@container` because it sat in the narrow half of an `.ab-row` and the viewport
   did not decide whether it fit. The deck is different in two ways: it spans the
   whole frame, so its width tracks the window, and it IS the container — an
   element cannot query the container it establishes, so `container-type` on
   `.deck` plus `@container` targeting `.deck` never matched anything and the
   phone kept two 175px columns. Caught in a capture at 390. 900 is the site's own
   breakpoint, used by the nav, the posters and `.saying`. */
@media (max-width:900px){
  .deck{ grid-template-columns:1fr; min-height:0 }
  /* Art on top as a banner, panel below, both in the single column. */
  .deck-art{ min-height:0; aspect-ratio:16 / 10; grid-column:auto }
  .deck-panel{ grid-column:auto }
  .deck-bar{ grid-template-columns:auto auto auto minmax(0,1fr); row-gap:.6rem }
  .deck-time{ grid-column:4 }
}

/* ---- .isl — the island ----------------------------------------------------
   Added 2026-08-16. Rokas: "when you scroll off it, a dynamic island appears on
   the bottom of the page that moves with scrolling and continues playing the
   music."

   THE AUDIO NEVER NEEDED THIS TO KEEP PLAYING. An <audio> object is not part of
   the layout and does not stop because its list scrolled away, so "continues
   playing" was already true. What the island actually adds is that the CONTROLS
   stay reachable: without it, pausing means scrolling back three sections to a
   list you left, which is the reason a reader ends up hunting for the browser
   tab's mute button instead.

   IT IS BUILT BY js/player.js AND NOT BY THE BUILDER, and the reasoning is in
   that file: this project's rule that a JS-injected component is not that
   component is about NAVIGATION, and an island in the HTML would be an empty
   pill on a page whose player is dead.

   z-index 90: over the fixed header (60) and under the skip link (200). A pill
   that a Tab press could hide behind is the one element on the page that must
   not be. */
.isl{
  position:fixed; z-index:90;
  left:50%; bottom:clamp(1rem,3vh,1.75rem);
  width:min(26rem, calc(100vw - 2rem));
  /* Title on the left, then the transport cluster prev-play-next on the right,
     which is the Apple mini-player order (2026-08-17: "include next/back buttons
     like an apple player would"). */
  display:grid; grid-template-columns:minmax(0,1fr) auto auto auto;
  align-items:center; gap:clamp(.3rem,.9vw,.55rem);
  padding:.5rem .5rem .5rem 1.1rem;
  border-radius:999px;
  border:1px solid var(--isl-line); background:var(--isl-bg);
  box-shadow:var(--shadow);
  opacity:0; pointer-events:none;
  transform:translate(-50%, calc(100% + 2.5rem));
  transition:transform .55s cubic-bezier(.2,.9,.25,1), opacity .3s ease
}
/* THE ENTRANCE IS THE ONLY MOTION AND IT IS ONE SPRING, which is the borrowed
   idea rather than the borrowed asset: the thing that reads as a "dynamic
   island" is a pill rising from off screen on an overshoot curve, not a shape.
   No morph, no pulse, no waveform. */
.isl.is-up{ transform:translate(-50%,0); opacity:1; pointer-events:auto }
/* Reduced motion gets the same pill with no travel. It arrives, it does not
   fly. */
.isl.is-calm{ transition:opacity .2s ease }
/* THE BLUR IS PROGRESSIVE AND THE GROUND UNDER IT IS NOT. `backdrop-filter` is
   the reason the pill reads as glass over a photograph, and it is also the one
   property here that a browser is allowed to ignore. So `--isl-bg` is opaque
   enough to hold its own type without any blur at all, and the blur only makes
   it better. Nothing about legibility depends on it. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)){
  .isl{ -webkit-backdrop-filter:blur(20px) saturate(1.5); backdrop-filter:blur(20px) saturate(1.5) }
}
/* THE ISLAND'S TRANSPORT. Prev and next flank the play button, smaller and ghosted
   so play stays the emphasis. They reuse the deck's own prev/next glyph shapes
   (`.deck-tr-i`) so the two players draw the same arrows. */
.isl-tr{
  appearance:none; -webkit-appearance:none; margin:0; padding:0; border:0;
  background:none; color:var(--ink-dim); cursor:pointer; flex:none;
  width:1.75rem; height:1.75rem; display:grid; place-items:center;
  transition:color .4s var(--ease-smooth), transform .3s var(--ease-smooth)
}
.isl-tr:hover{ color:var(--ink); transform:scale(1.08) }
.isl-tr:focus-visible{ outline:2px solid var(--ink); outline-offset:2px; border-radius:50% }
.isl-btn{
  appearance:none; -webkit-appearance:none; margin:0; padding:0; border:0;
  flex:none; position:relative;
  /* A SQUIRCLE TO MATCH THE DECK'S PILL, and SMALLER 2026-08-17 ("make the pause
     button smaller") now that prev and next sit either side of it. */
  width:1.9rem; aspect-ratio:1; border-radius:.55rem;
  background:var(--isl-key); color:var(--isl-keyink); cursor:pointer;
  transition:transform .25s cubic-bezier(.2,.9,.25,1)
}
.isl-btn:hover{ transform:scale(1.06) }
.isl-btn:focus-visible{ outline:2px solid var(--ink); outline-offset:3px }
/* The same two shapes as the row's glyph, at the same weights, so the two
   controls are visibly one control in two places. */
.isl-ico{ position:absolute; inset:0; margin:auto; width:0; height:0;
  border-left:.44rem solid currentColor;
  border-top:.3rem solid transparent; border-bottom:.3rem solid transparent;
  transform:translateX(.08rem) }
.isl.is-playing .isl-ico{
  border:0; width:.16rem; height:.62rem;
  background:currentColor; box-shadow:.3rem 0 0 currentColor;
  transform:translateX(-.15rem)
}
/* THE TITLE IS A BUTTON BACK TO THE LIST, which is what makes the island a way
   into the page rather than a floating widget over it. Two lines: what is
   playing, and what it is from. */
.isl-txt{
  appearance:none; -webkit-appearance:none; margin:0; padding:0; border:0;
  background:none; text-align:left; cursor:pointer; min-width:0;
  display:grid; gap:.05rem
}
.isl-txt:focus-visible{ outline:2px solid var(--ink); outline-offset:3px; border-radius:2px }
.isl-t{
  font-size:.9375rem; line-height:1.3; color:var(--ink);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis
}
.isl-s{
  font-size:.75rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-faint)
}
/* THE RAIL IS THE PILL'S OWN EDGE. Inset to the border radius so it reads as
   part of the pill rather than as a bar laid across it. */
.isl-rail{
  position:absolute; left:1.1rem; right:1.1rem; bottom:.3rem;
  height:1px; background:var(--isl-line); border-radius:1px; overflow:hidden
}
.isl-fill{ display:block; width:0; height:100%; background:var(--ink);
  transition:width .25s linear }
@media (max-width:560px){
  /* Full width less the frame's own padding, because a 24rem pill centred on a
     390 screen leaves two margins too thin to read as deliberate. */
  .isl{ width:calc(100vw - 1.5rem); bottom:.75rem }
}

/* ---- The two plates -------------------------------------------------------
   Both are 2:3 portraits beside a text column, and `.ab-plate` alone would let
   each run to its own full height: 1.5 times the width of a column that is
   nearly half the frame, which is taller than the words beside it by a long way
   and pushes both bands past a screen. A 4:5 box holds them to the text.
   The crop positions are per-image and both keep the SUBJECT rather than the
   centre: the flame is at the top of one frame, and his face is in the upper
   third of the other. */
/* `.named-plate` WAS HERE AND IS GONE, 2026-08-16. It held `flame-flowers` to a
   4:5 box beside #named's text column. The band became `.deck` the same day and
   the photograph now fills its half of the split, cropped by `.deck-art` against
   the panel's height rather than against a fixed ratio. Its 50% 18% crop was not
   lost: `.deck-art img` carries the same intent at 50% 20%. */
/* THE CROP KEEPS THE TOP, WHICH IS THE WHOLE REASON THIS FRAME IS IN A PLATE.
   `crown-reach` was on the full-bleed band first and no crop of it worked there
   (the note at `.poster-band` records the numbers). A 4:5 box takes 17% off a 2:3
   portrait, and anchored at 0% it takes that 17% off the BOTTOM: the crown at
   the top of the frame, his lifted head and the raised hand all survive, and
   what is lost is the foot of the jacket. */
.first-plate img{ aspect-ratio:4 / 5; object-fit:cover; object-position:50% 0% }
/* THE THIRD PLATE IS A LANDSCAPE AND IT IS THE ONLY ONE, 2026-08-16. The two
   above are 2:3 portraits from the album shoot, held to 4:5 so they do not
   outrun the text beside them. `preaching-lectern` is the opposite problem: at
   1.63:1 it renders about 320px tall against six answers running past 700, so
   left alone it is a postage stamp at the top of a tall column. A SQUARE is the
   tallest crop the frame can give without cutting him, and 64% keeps him, the
   candle and the lectern hanging in the window while losing the empty staircase
   camera-right and most of the roses camera-left.
   THE COLUMN IS DELIBERATELY THE NARROW ONE, and that is resolution rather than
   composition. The file is 1600x982, cut from a 1920x1080 recording, so 1080px
   is the ceiling that exists anywhere in the project and a square takes 982 of
   it. At .8fr the plate renders near 520 CSS pixels, which keeps it above 2x;
   `.ab-row`'s own .95fr would render it at ~620 and take it under. Widening this
   plate is the one change to the section that would visibly soften it. */
/* ---- #practice — the picture, its title, and his notes under both ----------
   Added 2026-08-16 with the ministry page's build out. The composition and why
   it is not the `.ab-row` this section started as are in
   parts/ministry-practice.html; what is here is the three rules it needs.

   GUARDED TO DESKTOP, AND THE UNGUARDED VERSION IS A BUG THIS FILE HAS ALREADY
   SHIPPED ONCE — see #named's note below, where an id-scoped column override
   beat the phone rule at every width and left a photograph beside a narrow strip
   of type at 390. `.ab-row, .ab-row-flip{ grid-template-columns:1fr }` in the
   max-width:900 block is a plain class selector at (0,2,0) and it comes far
   EARLIER in this file than this line does, so at equal specificity this one
   would win at every width. The media query makes them mutually exclusive.
   The centring is inside the same guard for the same reason: stacked on a phone
   the heading has nothing to be centred against, and centring it there would only
   add air above and below it.

   `align-self`, NOT `align-content`, AND THE DIFFERENCE COST A CAPTURE. The first
   build set `align-content:center` on the column, which was measured as applied
   and still did nothing: `.ab-row` is `align-items:start`, so the column is only
   as tall as the heading inside it and there is no spare height for
   `align-content` to distribute. `align-self` is the property that decides where
   the column sits in the ROW, which is the thing that was actually wrong. The
   computed style said "center" the whole time. */
@media (min-width:901px){
  .practice-row{ grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr) }
  .practice-head{ align-self:center }
}
/* 5:4, AND THE SQUARE IT REPLACED WAS BOTH TALLER AND SOFTER. A square crop of a
   1600x982 frame uses 982 of the 1600 pixels across; a 5:4 uses 1227, so at the
   same 518px column it renders at 2.37x rather than 1.9x. It is also 104px
   shorter, which is most of what took this section over a screen. The two
   arguments point the same way for once, so the picture got better and the band
   got shorter in one change.
   60% ON THE HORIZONTAL. He stands right of centre in the frame. The window keeps
   him, the microphone, the lit Paschal candle and the lectern hanging, and loses
   the roses camera-left; vertically it uses the whole frame, so the staircase
   above him and the lectern below both stay in. */
/* 3/2 SINCE 2026-08-17, WAS 5/4, and it is what pays for the hairline grid below
   (see the note on `.craft`). The section was at 955px against a 960px rule, so
   the new air had to be bought rather than added. `object-position:60% 50%` keeps
   him where he was in the frame; what the shallower crop takes is the empty table
   below him and the wall above, not the man. */
.practice-plate img{ aspect-ratio:3 / 2; object-fit:cover; object-position:60% 50% }

/* ---- .craft — his six answers, as notes ------------------------------------
   NOT `.songs`, THOUGH IT IS THE SAME KIND OF CONTENT. That component is one
   grid with `display:contents` rows, built so five definitions land on a single
   left rule inside the narrow half of an `.ab-row`. This list is two columns of
   stacked pairs at the full frame, and making one component do both would have
   meant switching off `display:contents` from a page-scoped selector — which is
   how #named would quietly re-lay itself the next time either page moved.

   STACKED, NOT SIDE BY SIDE, and that is what the full frame buys. At 1368px a
   term-and-definition row would set his answers at about 130 characters a line.
   Term over answer keeps every one inside a readable measure and lets the columns
   solve independently, so a long answer never drags its neighbour down.

   THREE COLUMNS, AND TWO WAS THE FIRST BUILD. Two ran the six answers over three
   rows and took the section to 1.21 screens, which verify/fits-screen.js reports
   and which this site holds everything to one of. Three makes it two rows and
   brings the section under, and it costs nothing: at 1368px each column still
   sets at about 55 characters, well inside a readable measure. The fix was the
   grid rather than the picture, which is the order that keeps the one frame of
   him preaching at the size it needs.

   TWO AT 1100 AND ONE AT 700. Below 1100 three columns start setting his longer
   answers in six or seven lines, and two columns of answers in a phone frame are
   about 160px wide, which is four words a line. */
/* ---------------------------------------------------------------------------
   RELAID 2026-08-17. Rokas: "we can 100% make this section look alot nicer. lay
   it out more aesthetically."

   WHAT WAS WRONG, AND IT WAS NOT THE GRID. Three columns of two rows is the right
   arrangement and the note above is still the reason. The fault was that nothing
   said where one answer ended and the next began: six text blocks floating on
   black at two type sizes, with only white space between them, which reads as a
   dump of copy rather than as six deliberate things. It is the one block on the
   site with no structure of its own.

   WHAT IT GETS: A RULE PER ITEM. A hairline across the top of each answer, in
   `--line`, the same token the rest of the site draws its seams with. That single
   line does the whole job — it turns six paragraphs into six modules, it makes the
   two rows read as rows, and it borrows nothing this site does not already use.
   No boxes, no fills, no cards: on a black ground a filled card is a hole, and
   this page has none anywhere else.

   THE TERM SITS ON THE RULE and the answer hangs below it, so the eye gets
   label / rule / body in the same order in all six. Term size steps up a little,
   because it is now the top of a module rather than a line of text.

   IT IS PAID FOR, NOT ADDED. The section measured 955px against this site's
   960px one-screen rule before this change, so every pixel of the new air had to
   come from somewhere: `.practice-plate` goes from 5/4 to 3/2 and gives back more
   than the rules cost. The photograph is a seated portrait with room above and
   below him, so the crop takes air rather than the man. Measured after, not
   assumed — see verify/fits-screen.js. */
.craft{
  margin:clamp(2rem,4vw,3rem) 0 0;
  display:grid; grid-template-columns:repeat(3, minmax(0,1fr));
  column-gap:clamp(1.75rem,3.5vw,3rem); row-gap:clamp(1.75rem,3.2vw,2.5rem)
}
@media (max-width:1100px){
  .craft{ grid-template-columns:repeat(2, minmax(0,1fr)) }
}
.craft-row{
  position:relative;
  display:grid; gap:clamp(.45rem,.9vw,.65rem); align-content:start;
  border-top:1px solid var(--line); padding-top:clamp(.85rem,1.6vw,1.15rem)
}
.craft-t{
  margin:0; font-family:var(--f-display); font-weight:400;
  font-size:clamp(1.1875rem,1rem + .7vw,1.5625rem); line-height:1.18;
  letter-spacing:-.02em; color:var(--ink); text-wrap:balance
}
/* His answer, at reading size and stepped back, because the term is the label
   and the answer is the thing. Same split as `.song-t` / `.song-d`. */
.craft-d{
  margin:0; color:var(--ink-dim);
  font-size:clamp(.9375rem,.9rem + .25vw,1.0625rem); line-height:1.55;
  text-wrap:pretty
}
@media (max-width:700px){
  .craft{ grid-template-columns:1fr }
}

/* ---- .faq — his music answers, as an accordion, 2026-08-19 ------------------
   Native <details>. The summary is the question in the display face; the marker
   is drawn by us (a plus that becomes a minus on open) because the UA triangle is
   off-palette and sits on the wrong side. Two columns since 2026-08-19 (see
   .faq-cols below), because fifteen answers in one ran too long. */
.faq{ margin:clamp(2rem,4vw,3rem) 0 0; border-top:1px solid var(--line) }
.faq-item{ border-bottom:1px solid var(--line) }
/* TWO COLUMNS, 2026-08-19. Rokas, of the fifteen answers: "put the lines alongside
   each other too because its way too long." They are TWO separate `.faq` lists, not
   one grid, so opening an answer grows only its own column and the hairline rules
   stay aligned down each side. The wrapper carries the top gap so the lists sit
   level. One column on a phone, where side by side would be unreadable. */
.faq-cols{
  display:grid; grid-template-columns:1fr 1fr;
  column-gap:clamp(2rem,4.5vw,4rem);
  align-items:start;
  margin-top:clamp(2rem,4vw,3rem)
}
.faq-cols .faq{ margin-top:0 }
@media (max-width:720px){
  .faq-cols{ grid-template-columns:1fr; row-gap:0 }
  /* stacked, the second list's top rule would double the first list's last one */
  .faq-cols .faq:nth-child(2){ border-top:none }
  /* MOBILE SHOWS ONLY THE FIVE KEY QUESTIONS, 2026-08-19. Rokas: "on mobile, cut
     down the FAQ to like 5 most important questions." Fourteen in one column is a
     wall on a phone; the five carrying `.faq-key` (the album, how the songs come,
     who he writes for, his one message, and how he handles trends) stay, the rest
     are hidden. All fourteen still render on the two-column desktop layout. */
  .faq-item:not(.faq-key){ display:none }
}
.faq-q{
  list-style:none; cursor:pointer;
  display:flex; align-items:flex-start; justify-content:space-between; gap:clamp(1rem,3vw,2.5rem);
  padding:clamp(.95rem,1.7vw,1.35rem) 0;
  margin:0; font-family:var(--f-display); font-weight:400;
  font-size:clamp(1.0625rem,.95rem + .55vw,1.4375rem); line-height:1.22;
  letter-spacing:-.015em; color:var(--ink); text-wrap:balance
}
.faq-q::-webkit-details-marker{ display:none }
.faq-q:hover{ color:var(--ink) }
/* THE MARKER. A plus from two bars; the vertical one collapses on open so it
   reads as a minus. Colour and box size track the type. */
.faq-ic{ position:relative; flex:none; width:.72em; height:.72em; margin-top:.32em }
.faq-ic::before, .faq-ic::after{
  content:""; position:absolute; left:0; top:50%; width:100%; height:1.5px;
  background:var(--ink-faint); transform:translateY(-50%);
  transition:opacity .3s var(--ease-arrive), transform .3s var(--ease-arrive)
}
.faq-ic::after{ transform:translateY(-50%) rotate(90deg) }
.faq-item[open] .faq-ic::after{ opacity:0; transform:translateY(-50%) rotate(0) }
.faq-item[open] .faq-q{ color:var(--ink) }
.faq-a{
  padding:0 0 clamp(1rem,1.8vw,1.4rem); max-width:56ch;
  color:var(--ink-dim); font-size:clamp(.9375rem,.9rem + .25vw,1.0625rem); line-height:1.6;
  text-wrap:pretty
}
@media (prefers-reduced-motion: reduce){
  .faq-ic::before, .faq-ic::after{ transition:none }
}

/* ---- #practice, no longer flat -------------------------------------------
   Rokas, on a capture of this section: make the six points "hover or do
   something nice subtle animated so it's not flat."

   THE FLATNESS WAS REAL AND HAD A CAUSE. js/rise.js gives a section its arrival
   by walking its <p> and its heading; these six answers are <dt>/<dd> inside a
   <dl>, so that walk went straight past them. The heading landed and the six
   points sat still, which is the one block on the page with no life of its own.
   rise.js now arms the rows on the section's own reveal (the scroll half); this
   is the pointer half.

   THE HOVER IS A LINE, AND NO COLOUR. The first build warmed the rule and the
   term to the aura's gold, matching the deck's hover. Rokas struck it the same
   day, on all three lists at once: "get rid of the gold colour. just have the
   same animation with the line." So the whole site's hover is now one neutral
   gesture, which is the rule this site set for itself in the first place ("the
   site has no accent colour", the deck's own note). On hover a hairline draws
   across the top from the left in `--ink`, the term leans a few px after it, and
   the dimmed answer comes up to full ink. No colour, no box, no fill: on a black
   ground those are holes, and this page has none anywhere.

   THE ROW ITSELF IS NOT TRANSFORMED, on purpose. It carries `.ar` during the
   arrival and that owns its transform; a hover transform on the same element
   would fight the reveal. So the movement lives on the term and on a ::before
   rule, neither of which the reveal touches. */
.craft-row::before{
  content:""; position:absolute; top:-1px; left:0; right:0; height:1px;
  background:var(--ink); transform:scaleX(0); transform-origin:left;
  transition:transform .5s var(--ease-arrive)
}
.craft-t{ transition:transform .5s var(--ease-arrive) }
.craft-d{ transition:color .45s var(--ease-smooth) }
@media (hover:hover){
  .craft-row:hover::before{ transform:scaleX(1) }
  .craft-row:hover .craft-t{ transform:translateX(4px) }
  .craft-row:hover .craft-d{ color:var(--ink) }
}
/* The line still appears on hover, it just does not grow; the term does not
   lean. A brightening answer is not vestibular motion, a growing line and a
   slide are. */
@media (prefers-reduced-motion: reduce){
  .craft-row::before, .craft-t{ transition:none }
  .craft-row:hover .craft-t{ transform:none }
}

/* ---- #named's two corrections, and the first one is a rule this file already
   states. `.ab-row` splits .95fr / 1.05fr and its own comment says why: "the
   text column is the wider one ... the picture is evidence and the words are the
   content". `.ab-row-flip` places the text in column ONE, which is the .95fr
   side, so every flipped band silently runs that split backwards. It does not
   show on about.html because the plate there is a landscape frame that is
   shorter than its column either way.

   Here it showed twice over. The plate is a 2:3 portrait in a 4:5 box, so a
   wider column is a TALLER picture: at 1.05fr the band measured 1019px against
   the 960px screen rule (verify/fits-screen.js), and the five songs were set in
   the narrower half of their own section. Swapping the split for this band gives
   the list the room and takes 44px off the picture.

   The second correction is the padding. `.section`'s 84px top is the FIXED
   HEADER'S CLEARANCE and belongs to whichever section opens a page; #named is
   the third section down, so it was spending header clearance on a header that
   is six screens away. Measured after both: 940px, inside the rule. */
/* THE min-width GUARD IS THE SAME TRAP AS `.poster .rail`, CAUGHT THE SAME WAY.
   `.ab-row, .ab-row-flip{ grid-template-columns:1fr }` in the max-width:900 block
   is what collapses these bands to one column on a phone, and it is a plain class
   selector. An id-scoped override is (1,1,0) and beats it at EVERY width, so
   written bare this rule kept #named in two columns at 390: the songs in a narrow
   left strip with the photograph beside them. Seen in the mobile capture, not
   reasoned about. Guarding it to desktop leaves the phone rule untouched. */
@media (min-width:901px){
  #named .ab-row-flip{ grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr) }
}
#named{ padding-block: clamp(3rem,4.4vw,4rem) clamp(2.25rem,2.8vw,3rem) }

@media (max-width:900px){
  /* The stack over a photograph gets the same treatment the poster's own copy
     gets at this width: the band is narrower, so the type steps down rather than
     the band growing to fit it. */
  .flow-p{ max-width:none }
  /* NO FORCED BAND HEIGHT ON A PHONE, 2026-08-19. This was min(92vh,44rem) = 704px,
     and it was the source of a gap Rokas flagged: #consecration's plate-plus-words
     is only ~671px on a phone, so the grid STRETCHED the image row by ~33px to reach
     704, dropping a dead band between the B&W plate and the "Rejection" heading.
     Content height instead, so the plate and the words butt up. #night is taller
     than any such min anyway, so it never depended on this and does not change. */
  .poster-band{ min-height:0 }
  /* THE STRIP IS A COMPACT TWO-ACROSS, 2026-08-17, cut from three 2026-08-19 when
     the page dropped a photo (Rokas: "can we have less photos"). It used to STACK
     to one column at natural ratio, which put the imagery at roughly 1000px
     straight after the opening line, reading as a long dead gap between the copy
     and the page below. Even frames on one uniform crop keep the pictures a single
     compact band under the heading instead. Two columns now the strip holds two. */
  .strip{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.4rem }
  .strip-f{ flex:none; aspect-ratio:3 / 4; overflow:hidden }
  .strip-f img{ width:100%; height:100%; object-fit:cover }
  /* THE LETTERBOX TALLER AND THE SCRIM VERTICAL. At 390 a 2.33:1 frame is 167px
     tall and the sentence needs three lines of it; and the horizontal fade is
     pointless on a band whose text runs the full width, so it turns through 90
     degrees onto the axis the words actually occupy. */
  .lb{ min-height:min(46vh,20rem) }
  .lb::after{
    background:linear-gradient(to top,
      rgba(0,0,0,.84) 0%, rgba(0,0,0,.74) 40%, rgba(0,0,0,.44) 66%, rgba(0,0,0,.12) 100%)
  }
  .lb-line{ max-width:none }
}

/* ---- .albums — the covers, where the song deck used to be -----------------
   Added 2026-09-10 with parts/music-albums.html. An auto-fit grid so it centres
   one card today and lays out two the day the second cover arrives, no CSS edit.
   The image caps at the home page's `.cover` width (a photograph of a physical
   print that must not be blown up); the caption is the site's quiet status
   voice, not a badge. */
/* TOP PADDING CUT 2026-09-10 (5.25rem -> ~3rem) so the album cover sits closer to
   the statement above it; the other half of centring the statement in its band
   (see `.open-stmt`). Bottom padding unchanged. */
.albums-sec{ padding-block: clamp(3rem,3.3vw,3.25rem) clamp(2.25rem,2.8vw,3rem) }
.albums{
  list-style:none; margin:0; padding:0;
  display:grid; justify-content:center;
  grid-template-columns:repeat(auto-fit, minmax(min(100%,16rem), 469px));
  gap:clamp(2rem,4vw,3.5rem);
}
.album{ margin:0 }
.album-art{ margin:0 }
.album-art img{ display:block; width:100%; height:auto; border-radius:2px }
.album-cap{ margin-top:1rem; display:flex; flex-direction:column; gap:.4rem }
.album-name{ font-size:1.0625rem; color:var(--ink) }
.album-name em{ font-style:italic }
.album-soon{ color:var(--ink-faint) }
/* The album's explanation, under the cover. Centred here because the cover is
   centred. SIZE MATCHED TO THE PAGE'S BODY PROSE 2026-09-10 (Rokas: "make the
   paragraph under the album cover same size as the rest of the page"). It was the
   lede clamp (~19px); it now runs the same clamp as `.saying-p` / #night, the
   page's other long-form his-words prose (~21px), so the passages read as one. */
.album-say{ margin:clamp(2.25rem,4.5vw,3.5rem) auto 0; max-width:56ch; text-align:center }
.album-say blockquote{ margin:0 }
.album-say p{ margin:0; font-size:clamp(1.0625rem,1rem + .38vw,1.3125rem); line-height:1.6; color:var(--ink-dim); text-wrap:pretty }
/* The passage runs several paragraphs now (the "Why this album exists" block,
   2026-09-10), so it needs the gap `.album-say p{margin:0}` removed for a single
   quote. Same 1.1rem rhythm as the home page's `.saying-p`. */
.album-say p + p{ margin-top:1.1rem }

/* ---- .album-row — cover left, words right (2026-09-10) --------------------
   Rokas: "put the I am chosen paragraph to be on the right side" beside the cover. */
.album-row{ display:grid; grid-template-columns:minmax(0, var(--album-w,26rem)) 1fr;
  gap:clamp(2rem,5vw,4.5rem); align-items:center }
.album-row .album-art{ margin:0 }
.album-row .album-art img{ display:block; width:100%; height:auto; border-radius:2px }
.album-words{ margin:0; max-width:46ch }
.album-words blockquote{ margin:0 }
.album-words p{ margin:0; font-size:clamp(1rem,.95rem + .35vw,1.1875rem);
  line-height:1.6; color:var(--ink-dim); text-wrap:pretty }
@media (max-width:820px){
  .album-row{ grid-template-columns:1fr; gap:clamp(1.5rem,4vw,2.5rem); justify-items:start }
  .album-row .album-art img{ max-width:26rem }
}

/* ---- #consecration — his lines on the LEFT now (2026-09-10) ---------------
   The B&W photo came off; the text sits on the left half, the zigzag's second beat
   under the album words on the right. */
.conse-block{ max-width:min(100%,54ch); margin-inline-end:auto }

/* ---- #breath — one quiet line between two photographs (2026-09-10) --------
   Type between two photos, the site's own device. Display font, sentence case,
   centred, quieter than the opening `.stmt-p` so it does not read as a statement. */
.breath-sec{ padding-block:clamp(3.5rem,7vw,6rem) }
.breath-line{ margin:0 auto; max-width:24ch; text-align:center;
  font-family:var(--f-display); font-weight:400;
  font-size:clamp(1.5rem,1.1rem + 1.8vw,2.375rem); line-height:1.2;
  letter-spacing:-.02em; color:var(--ink); text-wrap:balance }

/* #night's HEADING TO THE TOP-LEFT, 2026-09-10. Rokas: "make sure that nobody could
   regulate it is top left corner, a bit up." The poster-band content hugged the foot
   (`align-content:end`); space-between lifts the heading to the top while the quote and
   passage stay at the foot, and a tighter top padding takes it "a bit up". The top-left
   of smoke-blue is dark (he is unlit on the left), so the type keeps its ground. */

/* ============================================================================
   SITE FOOTER — 2026-09-17 (Rokas), reworked to the reference layout.
   Row: lion mark + email/Subscribe pill together on the LEFT, the Explore /
   Connect / Based columns as an evenly-spaced block on the RIGHT. Then a short
   centred quote across the full width, then the centred base line. Dark ground,
   cream ink. FLEXBOX, not grid minmax: the first pass used `minmax(0,1fr) auto`
   and a column stack, which Safari squished (the reason this is a rebuild). The
   input carries min-width:0 + appearance:none so it shrinks inside the pill
   rather than forcing it wide, which is the WebKit half of the same bug.
   ========================================================================== */
.site-foot{ overflow:hidden; }
.sf-top{
  display:flex; flex-wrap:wrap; align-items:flex-start;
  justify-content:space-between; gap:clamp(2rem,4vw,3.5rem);
}
.sf-brand{ display:flex; flex-wrap:wrap; align-items:center; gap:clamp(1rem,2vw,1.75rem); }
.sf-logo{ height:clamp(3rem,4.2vw,3.85rem); width:auto; flex:none; }
.sf-signup{
  display:flex; align-items:center; gap:.5rem; width:min(22rem,100%);
  border:1.5px solid var(--line-strong); border-radius:100px;
  padding:.3rem .3rem .3rem 1.35rem;
  transition:border-color .35s var(--ease-smooth);
}
.sf-signup:focus-within{ border-color:var(--ink); }
.sf-in{
  flex:1 1 auto; min-width:0; width:100%; background:none; border:0; color:var(--ink);
  font:inherit; font-size:1rem; padding:.55rem 0; -webkit-appearance:none; appearance:none;
}
.sf-in::placeholder{ color:var(--ink-faint); }
.sf-in:focus{ outline:none; }
.sf-sub{ flex:none; padding:.62rem 1.4rem; }
.sf-cols{ display:flex; flex-wrap:wrap; gap:clamp(2.75rem,6vw,7rem); }
.sf-h{ margin:0 0 1.1rem; }
.sf-col ul{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:.7rem; }
.sf-col li{ font-size:1rem; line-height:1.3; color:var(--ink); }
.sf-col a{ color:var(--ink); text-decoration:none; transition:opacity .25s var(--ease-smooth); }
.sf-col a:hover{ opacity:.62; }
.sf-quote{
  margin:clamp(3rem,6vw,5rem) auto 0; max-width:64rem; text-align:center;
  font-family:var(--f-display); font-weight:400;
  font-size:clamp(1.2rem,1rem + 1.3vw,1.85rem); line-height:1.45;
  letter-spacing:-.01em; color:var(--ink); text-wrap:balance;
}
.site-foot .f-base{ justify-content:center; margin-top:clamp(1.75rem,3vw,2.5rem); }
@media (max-width:760px){
  .sf-top{ flex-direction:column; gap:2.75rem; }
  .sf-cols{ display:grid; grid-template-columns:1fr 1fr; gap:2.25rem 1.5rem; }
}
#night .poster-in{ align-content:space-between; padding-block-start:clamp(1.5rem,3.5vh,2.5rem) }