/* toddtemaat.com
 *
 * A visual port of the live WordPress build, not a redesign.
 *
 * HOW THESE NUMBERS WERE OBTAINED. Every value below was read with
 * getComputedStyle() off the LIVE site on 2026-08-29 — the homepage,
 * /about-me/, and /us-air-force-coin-in-cyberspace/ as the post template — not
 * copied from the theme's stylesheet and not matched by eye.
 *
 * That distinction cost a rewrite and is worth recording. The first draft of
 * this file was built from Blocksy's generated global.css, which declares a
 * blue-heavy palette and a 40px h1. Almost none of it is what the site
 * actually paints: the real h1 is 30px, h2 is weight 800 rather than 600, the
 * buttons are black rather than blue, and the nav is 12px uppercase. Those
 * were theme DEFAULTS sitting under per-block overrides. Measure the rendered
 * page, not the stylesheet that feeds it.
 *
 * Cached `immutable` for a year by _headers, which is only safe because
 * stamp.mjs pins every page to /styles.css?v=<hash>. Run `node stamp.mjs`
 * after ANY edit here.
 *
 * Breakpoints are 999.98px and 689.98px — the theme's own, and also
 * servantpublishing.com's. A third would have to be maintained everywhere.
 */

/* ---------------------------------------------------------------- tokens */

:root {
  --text: #000000;          /* body copy — 41 elements on the homepage */
  --navy: #192a3d;          /* every heading */
  --slate: #3a4f66;         /* muted text, form borders */
  --line: #e1e8ed;          /* hairlines */
  --surface: #f2f5f7;       /* footer and card fill */
  --white: #ffffff;
  --black: #000000;         /* button fill — the CTAs are black, not blue */

  /* Live's own two values, read off the theme:
       --theme-link-initial-color: #2872fa
       --theme-link-hover-color:   #1559ed

     These were previously shifted one step darker — #1559ed at rest and an
     invented #1250c9 on hover — because #2872fa on white is 4.31:1 against
     WCAG AA's 4.5:1 for body text. That was a real contrast finding but it was
     never Todd's call to make, and it changed the colour of every prose link
     on every page. Live's values stand; the contrast note is in the register
     for Todd to decide on. */
  --link: #2872fa;
  --link-hover: #1559ed;

  /* The live site uses the system stack. There is no webfont to host, no
     Google Fonts request, and no FOUT. Do not add one. */
  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
    sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';

  --fs-body: 18px;
  --lh-body: 1.65;          /* 29.7px, measured */
  --gap: 1.5em;             /* 27px at 18px, measured */

  --w-normal: 1200px;

  /* THE SITE CONTAINER RULE, read from the live theme's own CSS rather than
     fitted to measurements:
       .ct-container { max-width: 1200px }
       --theme-container-edge-spacing: 90vw
       @media (min-width: 1000px) { ... calc(100vw - 60px) }
     Verified: live's mobile text is 324 at a 360 viewport, which is 90vw
     exactly. Everything on the page is centred inside this width — this is why
     fixing it per-component never converged. */
  --edge-space: 90vw;
  /* 700. Confirmed against live's HOMEPAGE, whose running-text column measures
     exactly 700. Do NOT raise this to 710: the theme also exposes
     --theme-narrow-container-max-width: 710px, but that is the SECTION-PAGE HERO
     container, not this one. They are different containers with different values,
     and conflating them widened every page by 10px. */
  --w-text: 700px;
  --edge: 90vw;

  --radius-btn: 10px;       /* measured on the Start Here CTA */
}

/* ------------------------------------------------------------------ base */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-padding-top: 1rem;
  -webkit-text-size-adjust: 100%;
  /* Both html AND body. The full-bleed band is 100vw, which counts the vertical
     scrollbar, so the document overflows by its width. Clipping body alone left
     html still overflowing and the horizontal scrollbar still present. */
  overflow-x: clip;
}

body {
  margin: 0;
  /* .band is 100vw, which counts the vertical scrollbar and overflows the
     document by its width — a horizontal scrollbar the live site does not have.
     clip contains it without creating a scroll container the way hidden would.
     Removed once before when the hero stopped being full-bleed; the band needs
     it again. */
  overflow-x: clip;
  background: var(--white);
  color: var(--text);
  /* Live declares this on body and every element inherits it, so a word longer
     than its column breaks instead of overflowing. Nothing in today's copy is
     long enough to show the difference, which is why a geometry diff never
     flagged it -- but it is a real behavioural gap against live, and one long
     URL in a future post would open it. */
  overflow-wrap: break-word;
  font-family: var(--font);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

img,
picture,
video,
svg {
  max-width: 100%;
  height: auto;
}

/* Every <img> carries width and height in the markup so the browser reserves
   space before the file arrives. Without that each image is a layout shift,
   which is the cheapest Core Web Vitals (CLS) failure there is to avoid. */
img {
  display: block;
}

/* ------------------------------------------------------------ typography */

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0 0 0.5em;
  color: var(--navy);
}

/* Live leaves 20.8px under an h2, which is 0.8em at 26px — not the 0.5em the
   shared heading rule gives. */
h2 {
  margin-bottom: 0.8em;
}

/* 30px, not the 40px the theme file declares — measured on both a post and a
   page. Weight 600, letter-spacing -0.3px, line-height 36px. */
h1 {
  font-size: 30px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.3px;
}

/* 600, from the site's own generated stylesheet — h2 {600; 26px; 1.25}.
   This was 800 for a while, taken from ONE rendered heading that carried a
   block-level override, and applied to every h2 on the site. Read the global
   scale, not a single element. */
h2 {
  font-size: 26px;
  font-weight: 600;
  line-height: 1.25;
}

h3 {
  font-size: 20px;
  font-weight: 600;
  line-height: 1.3;
}

h4 {
  font-size: 18px;
  font-weight: 600;
  line-height: 1.45;
}

h5 {
  font-size: 20px;
  font-weight: 700;
  line-height: 1.5;
}

h6 {
  font-size: 18px;
  font-weight: 700;
  line-height: 1.5;
}

/* A heading sitting over a hero image, as /about-me/ does today. */
.on-image {
  color: var(--white);
}

p,
ul,
ol,
blockquote,
figure,
table {
  margin: 0 0 var(--gap);
}

/* 45px flat, not 1.25em. Measured on live at 400 and 1400 — both exactly 45,
   so it does not scale with the font. 1.25em gave 22.5px and set every list
   item 23px too far left.

   And NO top margin between items: live measures 0 on all three. The 0.4em
   here was 7.2px an item, which is where the intro list picked up 14px of
   height the live one does not have. */
ul,
ol {
  padding-left: 45px;
}

blockquote {
  margin-left: 0;
  padding: 0.25em 0 0.25em 1.25em;
  border-left: 3px solid var(--slate);
  color: var(--slate);
}

/* Live's divider is a SHORT centred bar, not a full-width line: 100px wide,
   2px tall, #3a4f66. This drew 1px of --line across the whole 1200px column,
   which is a different mark entirely. The classed dividers (.feed-rule,
   .media-rule, .page-body > .rule) already carried these values; only the bare
   <hr> in posts and /privacy/ fell through to the wrong default.

   color as well as background: the painted bar is the background, but a UA
   leaves an <hr> at color:gray, and color feeds border-color, outline-color
   and text-decoration-color -- five spurious entries in every comparison of
   this element otherwise. */
hr {
  width: 100px;
  max-width: 100px;
  height: 2px;
  margin: 0 auto 27px;
  border: 0;
  background: var(--slate);
  color: var(--slate);
}

/* The one above an opt-in is spaced differently -- 16 above, 7.04 below,
   against 0 and 27 everywhere else. The assembler marks it; see placeOptin. */
.optin-rule {
  margin: 16px auto 7.04px;
}

figcaption {
  font-size: 14px;
}

code,
pre {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9em;
}

pre {
  overflow-x: auto;
  padding: 1em;
  background: var(--surface);
}

/* Wide content must scroll inside its own box rather than making the page
   scroll sideways. Wrap any wide table in <div class="scroll-x">. */
.scroll-x {
  overflow-x: auto;
  margin-bottom: var(--gap);
}

table {
  width: 100%;
  border-collapse: collapse;
}

th,
td {
  padding: 0.6em 0.75em;
  border-bottom: 1px solid var(--line);
  text-align: left;
}

th {
  color: var(--navy);
  font-weight: 600;
}

/* The publication date on a post. 12px on the live site. The <time datetime>
   in the markup is what machines read; this only styles it. */
.meta {
  color: var(--slate);
  font-size: 12px;
}

/* 700, not the UA's `bolder`. Inside a 600-weight heading `bolder` resolves to
   900, which is wider than live's 700 -- enough that a numbered h3 on
   /what-ai-actually-teaches/ wrapped to two lines where live keeps it on one.
   Live computes 700 for strong everywhere. */
strong,
b {
  font-weight: 700;
}

/* ----------------------------------------------------------------- links */

a {
  color: var(--link);
  text-decoration: underline;
}

/* Live's hover is the OPPOSITE of what this had: the colour does NOT change,
   the UNDERLINE goes away. Measured on the live homepage with a real pointer --
   at rest a prose link is #2872fa underlined, on hover it is #2872fa with
   text-decoration: none. This turned the colour to #1559ed and kept the
   underline, so every link in running text hovered wrong. */
a:hover {
  text-decoration: none;
}

/* The underline on running-text links is how the live site already behaves,
   and it is kept: it is the robust cue, independent of colour vision. */
main p a,
main li a,
main blockquote a {
  text-underline-offset: 0.15em;
}

/* Never remove a focus ring without replacing it with something equally
   visible.

   #2872fa, NOT var(--link). This is live's own ring, read off the site:

     a:focus, button:focus                 { outline: none }
     a:focus-visible, button:focus-visible { outline: 2px solid
                                             var(--theme-palette-color-1);
                                             outline-offset: 2px }

   and palette-color-1 is #2872fa. Confirmed by keyboard: tabbing on live
   computes 2px solid rgb(40, 114, 250) at offset 2px.

   This is now the same value as var(--link); it stays spelled out because it
   is palette-color-1 in its own right, not the link colour, and would survive
   any later change to the link colour. */
:focus-visible {
  outline: 2px solid #2872fa;
  outline-offset: 2px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.6em 1em;
  background: var(--navy);
  color: var(--white);
  text-decoration: none;
}

.skip:focus {
  left: 0;
}

/* ---------------------------------------------------------------- layout */

/* NO desktop override. --edge-space is 90vw at every width, which is what live
   actually does.
 *
 * There used to be a @media (min-width: 1000px) block here setting
 * calc(100vw - 60px). It put the header, the footer and every full-width block
 * 25px too far left on every page: at a 1100 viewport live's shell container
 * measures 990@55 (= 90vw, centred) and this rebuilt one measured 1040@30.
 *
 * Read off live's own header container, which only exists at 1000 and up
 * because the theme swaps to a mobile header below that:
 *
 *   1000 -> 900.0@50.0    90vw
 *   1100 -> 990.0@55.0    90vw
 *   1400 -> 1200.0@100.0  capped by --w-normal, since 90vw would be 1260
 *
 * so the rule is min(90vw, 1200px) throughout, with no second regime.
 *
 * This was missed for a long time because every verification pass compared only
 * <main>. The shell around it was never diffed until both pages were rendered
 * side by side at a controlled width. */

/* No padding here. The container width itself is the rule; children are capped
   at min(edge-space, their own max) and centred. */
header,
main,
footer {
  padding-inline: 0;
}

/* The centred 1200px container. header and footer each hold ONE .wrap; main
   has no wrapper, because its direct children need individual widths — most
   are held to the text column below, while a .wide block spans the full 1200. */
.wrap,
main > * {
  max-width: min(var(--edge-space), var(--w-normal));
  margin-inline: auto;
}

/* Running text sits in a 710px column, centred — measured, not chosen. At 18px
   that is about 72 characters a line, inside the 45–85 that reads comfortably.
   Do not widen it: the full 1200px at this size runs about 122 characters. */
main > :is(h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote, figure, pre, .scroll-x, .meta) {
  max-width: min(var(--edge-space), var(--w-text));
}

/* A POST's running text sits in 710px, not the 700 every other page uses.
   Both are measured: live's homepage intro column is max-width 700, and a post
   body paragraph is 710 wide at x=597.5. One token could not say both, and
   --w-text at 700 made every post's text wrap a column narrower than live's.

   Post headings also carry a 57.2px top margin, the same value the section
   headings use elsewhere. They were arriving with 0, so every h2 down a post
   sat tight against the paragraph above it. */
/* A post's header. Every value measured on live at 1920; the h1's -0.3px
   tracking and the description's 22px/22px (line-height equal to font-size,
   which is unusual and deliberate on live) are both live's. */
/* Embeds never exceed their column. The per-embed aspect-ratio comes from the
   iframe's own width/height at build time (see responsiveEmbeds), so the
   height follows the width down instead of staying at the attribute value. */
/* The Spotify player is 21:9 on live -- WordPress marks its figure
   wp-embed-aspect-21-9, which is padding-top 42.85%, and 710 x 0.4285 is the
   304.2 live renders. It is the one embed whose width attribute is "100%"
   rather than a number, so responsiveEmbeds cannot derive its ratio and it
   was drawing at its bare 152px height attribute, 152px short.

   The three YouTube embeds are wp-embed-aspect-16-9, which their own
   1200x675 attributes already express. */
.embed-21-9 iframe {
  width: 100%;
  height: auto;
  aspect-ratio: 100 / 42.85;
}

main iframe {
  /* block, or the iframe sits on the text baseline and its figure is ~8.7px
     taller than the embed -- live's wrapper measures exactly the embed. */
  display: block;
  max-width: 100%;
}

/* max-width, NOT width: an embed narrower than the column stays at its own
   size on live. The Edmond Business player is 600x338 in a 710px column and
   was being stretched to 710x400. The YouTube embeds are 1200 wide, so the
   cap still brings them down to the column and the ratio takes the height
   with it. */
main iframe[style*="aspect-ratio"] {
  max-width: 100%;
  height: auto;
}

/* /about-me/'s three-up picture row. Live lays it out in a 1200px block --
   WIDER than the 710 text column around it -- as three equal flex columns
   with a 27px gap, stacking at the WordPress columns breakpoint of 781. The
   fragment carried three bare <figure>s, so they stacked full-width at every
   size and the page ran 1085px long.

   Two of the three images carry their own width inline, as live's do; the
   third fills its column. */
/* /email-ty/'s "Recent Posts" list. Live's is a narrower variant of the same
   row: a 379.5px container with 45/55 columns and a square 128.8px thumbnail,
   against the posts' 710px container with 25/75 and a 3:2 crop.

   The fragment carried this flattened -- bare anchors, a loose <h2>, unwrapped
   text -- so the thumbnails drew 1905x1270 and the page ran 3474px long. It
   also carried links to /faith-category/ and /author/toddtemaat/, both of
   which are settled 404s under MIGRATION.md 6a, and a "No comments" counter
   from a comment system this site does not have. The labels are kept as text;
   the dead links and the counter are gone. */
/* .page-hero.page-hero--large, not the bare modifier: `.page-hero h1` is the
   same (0,1,1) and comes later in the file, so this never applied. */
.page-hero.page-hero--large h1 {
  font-size: 40px;
  font-weight: 700;
  line-height: 48px;
}

/* main > .panel.panel--recent, not main > .panel--recent: the generic
   `main > .panel` is the same (0,1,1) and appears later in the file, so it
   won on source order and this box drew at the homepage's 1125. Same trap
   .panel--narrow hit. */
main > .panel.panel--recent {
  /* The page's own text column, NOT a fitted 379.5px.
     379.5 was measured off a build in which this box had already collapsed, and
     then everything inside it was fitted to that wrong width. Live measures the
     block at 762 at a 1920 viewport -- the same column the prose above it sits
     in -- with a 272.3px square thumbnail and its text starting at x=879.75.
     At 379.5 the thumbnail came out 128.8.
     The 45/75 bases below were right all along; they were just shrinking inside
     a box half the size. In a 762 row they resolve to 272.25 and 453.75, which
     is live to 0.05px. */
  max-width: min(var(--edge-space), var(--w-text));
  margin-inline: auto;
  background: none;
  border-radius: 0;
}

/* .posts.posts--compact, not a bare modifier: `.posts > li` is the same
   (0,1,1) and comes first in the file, so the gap override never applied.
   Live's is 27px row / 36px column. */
/* /contact/'s list: the same 25/75 row as a post's, but with NO padding
   inside the columns and a SQUARE thumbnail -- live measures 170.8x170.8 in a
   710px row, which is 25% shrunk by the 27px gap. */
/* The row is 710 wide here, not the 1125 `main > .posts` gives the homepage's
   -- which made the 25% column 274.5 instead of live's 170.8. */
main > .posts.posts--square {
  max-width: min(var(--edge-space), 710px);
}

/* THE 25/75 SPLIT BELONGS ABOVE 782 ONLY, and writing it unconditionally is
   why /contact/'s articles were a quarter of the size they should have been on
   a phone. Todd reported it 2026-09-02.

   The stacking rule already existed: @media (max-width: 781.98px) sets
   `.posts-thumb, .posts-body { flex-basis: 100% }`. It never applied here.
   `.posts-thumb` is (0,1,0); `.posts.posts--square .posts-thumb` is (0,3,0),
   so the modifier won at EVERY width and the thumbnail stayed at 25% of the
   stacked row — 96px inside a 384px row at a 412px viewport. A media query
   loses to specificity; it is not a later-wins situation.

   MEASURED against live at matched clientWidth, before: live /contact/ 384x384
   at 412, this build 96x96. At 1200 both were already 171x171, which is why it
   only ever looked wrong on a phone.

   .rows -- the same component on /faith/ and /health/, and the same
   wp-block-columns 25%/75% on live -- has always had this right: its base is
   100% and the 25/75 sits behind @media (min-width: 782px). This now matches
   it exactly, which is the point: live uses ONE component for both pages, and
   measured on live /contact/ and /faith/ are identical (171x171, 25% column,
   710px row, 20px title at 1200; full-width square at 412).

   782 abuts 781.98 cleanly -- the same pairing D26 records. */
.posts.posts--square .posts-thumb,
.posts.posts--square .posts-body {
  padding: 0;
}

@media (min-width: 782px) {
  .posts.posts--square .posts-thumb {
    flex: 0 1 25%;
  }

  .posts.posts--square .posts-body {
    flex: 0 1 75%;
  }
}

.posts.posts--square .posts-thumb img {
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

/* This row's type is fixed, not the fluid clamps a post-feed row uses: live
   measures the title 20px/25, the date 18px/29.7 with 27 below it, and the
   excerpt 18px/29.7 with no margin at all. */
.posts.posts--square .posts-body h3 {
  font-size: 20px;
  line-height: 25px;
}

.posts.posts--square .meta {
  margin-bottom: 27px;
}

.posts.posts--square .posts-excerpt {
  margin: 0;
  font-size: 18px;
  line-height: 29.7px;
}

.posts.posts--compact > li {
  gap: 27px;
}

.posts.posts--compact .posts-thumb,
.posts.posts--compact .posts-body {
  padding: 0;
}

/* 45% and 75%, which sum to 120% deliberately: the columns overflow their row
   and flexbox shrinks them back weighted by basis. In live's 762 row that lands
   on 272.25 and 453.75 against live's measured 272.3 and 453.75. Stating the
   RESULT as 37.5/62.5 instead gets close and then stops, because the shrink is
   weighted by BASIS, not by the result.

   ABOVE 782 ONLY, and it used to be unconditional. Todd reported the same
   symptom here as on /contact/ on 2026-09-02, and it is the same cause: the
   stacking rule at @media (max-width: 781.98px) sets
   `.posts-thumb, .posts-body { flex-basis: 100% }` at (0,1,0), and this
   selector is (0,3,0), so the modifier won at EVERY width and the thumbnail
   never stacked. A media query loses to specificity.

   MEASURED ON LIVE at matched clientWidth, and live stacks: flex-basis
   computes to 100% at 412 and 360, giving a 246x246 image in a 246px row and
   215x215 in 215. This build gave 111x111 and 97x97 — 45% of a row that should
   have been full width. WordPress's own columns stylesheet is what does it
   there: `.wp-block-columns:not(.is-not-stacked-on-mobile) > .wp-block-column
   { flex-basis: 100% !important }` under max-width 781px, and !important beats
   the inline 45%.

   The row widths were already right — 247 here against live's 246 at 412, 216
   against 215 at 360 — so the column model D20a closed is untouched. Only the
   split inside the row was wrong. */
/* 25/75 and a 27px gap, which is .rows -- the component every other post list on
   the site uses. It was 45/75 with a 36px gap, both of them live's, and D34 is why
   they are not any more. */
@media (min-width: 782px) {
  .posts.posts--compact .posts-thumb {
    flex: 0 1 25%;
  }

  .posts.posts--compact .posts-body {
    flex: 0 1 75%;
  }
}

/* This row's own type, measured on live: the category line is 15px/24.75 and
   the title 20px/25 with 24px above it and nothing below -- against the
   posts' fluid 24px title with a 10.72px bottom margin. */
.posts.posts--compact .posts-cat {
  font-size: 15px;
  line-height: 24.75px;
}

/* .rows h2's values exactly: 20px, the 1.3 ratio rather than a pinned 25px, and
   the 10.72px below it. D34. */
.posts.posts--compact .posts-body h3 {
  margin: 0 0 10.72px;
  color: var(--navy);
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
}

/* .rows h2 steps to 1.25 above the theme breakpoint and this must too, or the
   title sits at 26px where every other list draws 25. */
@media (min-width: 690px) {
  .posts.posts--compact .posts-body h3 {
    line-height: 1.25;
  }
}

.posts--compact .posts-thumb img {
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

.posts-cat {
  margin: 0;
}

/* The closing notes block six posts carry -- "References", "Recommended
   Resource", "Recommended Listen", "Interesting links". Live wraps each in a
   full-bleed group tinted with palette-colour-6 and padded 3rem top and
   bottom. The fragments had the paragraphs bare, so each post was 96px short
   and had no tint behind that section at all.

   Full-bleed like .band: max-width none and 100% of main, with the text held
   to the page's own column inside it. */
/* A 33/66 two-column block: live sets the columns flex-basis 33.33% and
   66.66% with the row's 27px gap, so inside a post's 710px column they land
   on 227.6 and 455.3. The fragment had the paragraph and the figure stacked,
   which drew the picture at the full 710 and cost 192px of height.

   Stacks at 781, like every other WordPress columns block on this site. */
main > .cols-33-66 {
  max-width: min(var(--edge-space), 710px);
  margin-inline: auto;
  display: flex;
  gap: 27px;
}

main > .cols-33-66 > :first-child {
  flex: 0 1 33.33%;
  min-width: 0;
}

main > .cols-33-66 > :last-child {
  flex: 0 1 66.66%;
  min-width: 0;
  margin: 0;
}

main > .cols-33-66 img {
  width: 100%;
  height: auto;
}

/* 781.98, not 781 - and the difference is not cosmetic.
   A viewport can be a FRACTION. Measured in a 781px-wide frame:
     innerWidth              781
     (max-width: 781px)      FALSE
     (max-width: 781.98px)   TRUE
   so every rule written at the integer silently drops out in the band between
   781 and 782, and the columns unstack a viewport early. On a post that showed
   up as .posts-thumb flipping flex-basis from 100%% to 25%% between 780 and 781,
   six painted runs adrift of live, at that one width and nowhere else.
   This file already had ONE rule at 781.98 and four at 781; whoever wrote the
   first one hit this and fixed only what they were looking at. It is also why
   the theme's own breakpoints are 999.98 and 689.98 rather than 1000 and 690 -
   the convention exists for exactly this, and these four were the exceptions.
   The paired @media (min-width: 782px) still abuts cleanly: 781.98 < 782. */
@media (max-width: 781.98px) {
  main > .cols-33-66 {
    flex-wrap: wrap;
  }

  /* (0,2,1), the same specificity as the two column rules above. The old
     `> *` was (0,1,1) and lost to them at every width, so below 782 the
     figure wrapped onto its own row while the paragraph stayed at flex-basis
     33.33% -- measured 211.5px inside a 634.5px column at 690 -- and wrapped
     to roughly three times the lines. That was the whole of this post's +10
     to +19 painted runs below 782 (CLAUDE.md, "still runs +10 to +19"), the
     one post of nineteen that carries .cols-33-66. Live stacks with
     flex-basis:100% !important. */
  main > .cols-33-66 > :first-child,
  main > .cols-33-66 > :last-child {
    flex-basis: 100%;
  }
}

/* A pullquote inside a POST. Same component as the homepage's -- 22px/30.8
   weight 500 with a 16px/22.4 cite -- but live centres it in a 550px block
   inside the 710 column rather than running it full width, and gives it no
   rule and no tint. This was rendering as a generic bordered blockquote at
   18px, which cost 149px on the one post that has one. */
main.post > .pullquote {
  max-width: min(var(--edge-space), 710px);
  /* 33px below, not the usual 27, and 70px of padding all round -- both
     measured; the figure is 327.6 tall around a 167.6 quote. */
  margin: 0 auto 33px;
  padding: 70px;
  /* A 10px blue rule above and below -- live's pullquote carries one in
     palette-colour-1, which is var(--link). It is a visible element this had
     none of, and the two borders are the 20px the box was short. */
  border-top: 10px solid var(--link);
  border-bottom: 10px solid var(--link);
  background: none;
  color: var(--text);
  font-size: 22px;
  font-weight: 500;
  line-height: 1.4;
}

/* 100% of the figure's content box, not a fixed 550: at 1920 that IS 550
   (710 less 140 of padding and 20 of border), but at 768 live measures 531.2
   and a hard number stayed at 550. */
main.post > .pullquote blockquote {
  width: 100%;
  margin: 0 auto;
  padding: 0;
  border: 0;
}

main.post > .pullquote blockquote p {
  margin: 0 0 22px;
  font-size: 22px;
  font-weight: 500;
  line-height: 30.8px;
}

main.post > .pullquote cite {
  display: block;
  font-size: 16px;
  font-weight: 500;
  font-style: normal;
  line-height: 22.4px;
}

/* A WordPress oEmbed card's fallback link. Live shrink-wraps it to the link
   and rules it 4px in palette-colour-1 with a 27px inset -- 334.6 wide, no
   bottom margin. The generic blockquote treatment made it a full-width box
   with a 3px slate rule and 27px below. */
/* Centred in the text column, not offset by a hard-coded number: live's card
   is 334.6 wide at x=785.2, whose centre is 952.5 -- the column's. */
/* A GenerateBlocks media element, not a figure: live wraps these two images
   in a div and gives the image a 25px bottom margin. Every other in-post
   image sits in a <figure> with margin 0 -- checked on five posts -- so this
   is the block type, not a rule about images. */
main.post > .media-block {
  max-width: min(var(--edge-space), 710px);
  margin: 0 auto 27px;
}

/* display:inline, which is what live's is. The ~25px under the image is not a
   margin at all -- it is the descender space an inline replaced element
   leaves below the baseline, which is why live's wrapper measures 504.3
   around a 479.3 image. Setting it as a margin on a block image did nothing:
   it just collapsed into the following heading's 57.2. */
main.post > .media-block img {
  display: inline;
  /* middle, not baseline -- that removes the descender gap. An IMG is a
     replaced element, so its VERTICAL margin does apply while inline, and
     live's 25px is a real margin sitting inside the wrapper rather than
     collapsing into the heading that follows. */
  vertical-align: middle;
  width: 100%;
  height: auto;
  margin-bottom: 25px;
}

/* /contact/ sets its title at 40px/48 with 40px under it, not the 30/36 a
   post header uses -- the same size /email-ty/'s hero heading takes. It is
   the only page whose h1 is a bare child of main. */
main > h1 {
  margin: 0 auto 40px;
  font-size: 40px;
  font-weight: 700;
  line-height: 48px;
}

/* The social links are a ROW on live -- flex with a 36px gap, 24px/39.6 --
   not the bulleted column this was rendering. */
main > .social-row {
  display: flex;
  /* WRAP, which is live's own value. Without it the four links are one nowrap
     row of 36px gaps that cannot fit a 288px column, and the last one -- "X",
     the narrowest and so the easiest to miss -- painted to 308 in a 303px
     viewport at 320: clipped, and unreachable to tap.
     Measured on live at 320: it wraps to two rows, Facebook and LinkedIn then
     YouTube and X, with zero elements past the right edge. */
  flex-wrap: wrap;
  gap: 36px;
  margin: 0 auto 36px;
  padding: 0;
  list-style: none;
}

main > .social-row li {
  font-size: 24px;
  line-height: 39.6px;
}

/* The label inside each item is 15.6px on live, not the 24 the item carries --
   the 24 sizes the icon. */
main > .social-row li span {
  font-size: 15.6px;
  /* .5em of the SPAN's own 15.6px, not of the item's 24 -- 7.8px. */
  margin-inline: 7.8px;
}

/* Live paints these as brand-coloured pills with a white glyph and a white
   label, not as text links. WordPress's own social-links block, read out of
   wp-includes/blocks/social-links/style.min.css:
     li            border-radius: 9999px, and the brand background
     a             display:flex; align-items:center; line-height:0; padding:.25em
     svg           height/width 1em  -> 24px at the item's 24px
     span          font-size .65em -> 15.6px, margin-inline .5em -> 7.8px
   6 + 24 + 6 = 36px tall, which is what live measures, and it is why the
   rebuild's text-only "X" was 9.2x32 -- the one control on this site smaller
   than live's own. The block carries no is-style-logos-only, so the
   :where(...:not(.is-style-logos-only)) brand rules apply. */
main > .social-row li {
  border-radius: 9999px;
}

main > .social-row li a {
  display: flex;
  align-items: center;
  padding: 6px;
  color: inherit;
  line-height: 0;
  text-decoration: none;
}

main > .social-row li svg {
  width: 24px;
  height: 24px;
  fill: currentColor;
}

/* Live's own four values, from the same stylesheet. */
main > .social-row .is-facebook {
  background: #0866ff;
  color: #fff;
}

main > .social-row .is-linkedin {
  background: #0d66c2;
  color: #fff;
}

main > .social-row .is-youtube {
  background: red;
  color: #fff;
}

main > .social-row .is-x {
  background: #000;
  color: #fff;
}

main > .embed-card {
  width: fit-content;
  max-width: min(var(--edge-space), var(--w-text));
  margin: 0 auto 27px;
}

main > .embed-card blockquote {
  margin: 0;
  padding: 0 0 0 27px;
  border-left: 4px solid var(--link);
  background: none;
}

main > .notes {
  max-width: none;
  width: 100%;
  /* 48px 27px, measured identical on all six posts that carry this block. */
  padding: 48px 27px;
  background: var(--surface);
}

main > .notes > * {
  max-width: min(var(--edge-space), var(--w-text));
  margin-inline: auto;
}

/* The block's opening line -- "References", "Recommended Listen" -- is 500
   weight with a 15px bottom margin on live, not the 27 a paragraph gets. */
main > .notes > p:first-child {
  margin-bottom: 15px;
  font-weight: 500;
}

/* A heading INSIDE the block keeps its 57.2px, but not when it opens the
   block -- live measures 0 there and 57.2 on a second one further down.
   /living-in-healthy-tension/ carries both a "Recommended Resource" and a
   "References" heading in one group. */
main > .notes > h2 {
  margin-top: 57.2px;
}

main > .notes > :first-child {
  margin-top: 0;
}

/* The last child's bottom margin belongs to the GROUP, not to it: live's box
   measures 488.9 where carrying the paragraph's own 27 inside made it 515.9,
   and live puts that 27 below the group instead. */
main > .notes > :last-child {
  margin-bottom: 0;
}

main > .notes {
  margin-bottom: 27px;
}

main.post > .notes > * {
  max-width: min(var(--edge-space), 710px);
}

main > .gallery {
  max-width: min(var(--edge-space), 1200px);
  /* 27px below, the same gap live puts under every block. */
  margin: 0 auto 27px;
  display: flex;
  gap: 27px;
}

main > .gallery > figure {
  flex: 1 1 0;
  min-width: 0;
  margin: 0;
}

main > .gallery img {
  display: block;
  width: 100%;
  height: auto;
  /* Centred in its column. Two of the three carry a fixed width inline, so
     without this they sat hard against the left edge of their column where
     live centres them -- 59.5px and 88.5px out at 1920. */
  margin-inline: auto;
}

@media (max-width: 781.98px) {
  main > .gallery {
    flex-wrap: wrap;
  }

  main > .gallery > figure {
    flex-basis: 100%;
  }
}

/* Headings in running content carry a top margin on live -- h2 57.2/20.8,
   h3 30/16 -- and this build gave them none, so every one sat tight against
   the paragraph above it. On /privacy/, with ten h2s, that was 30.2px each
   after margin collapsing against the preceding paragraph's 27.

   main > , so the panel and band headings keep their own rules: .panel > h2
   and .band > h2 are (0,1,1) and .section-title (0,1,0), all of which outrank
   this (0,0,2). main.post > :is(h2,h3) at (0,1,2) also still wins. */
/* Live wraps a page's content in a second .ct-container-full whose 10px top
   padding sits between the hero and the first content element. /faith/ and the
   other section pages already land right because their first element is a
   panel carrying its own 10px; pages whose first element is a bare paragraph
   -- /privacy/, /about-me/, /email-ty/ -- were starting 10px high. Setting the
   margin rather than adding to it keeps the panel pages where they are. */
main > .page-hero + *:not(.page-body) {
  margin-top: 10px;
}

main > h2 {
  margin-top: 57.2px;
}

main > h3 {
  margin-top: 30px;
  margin-bottom: 16px;
}

/* An h2 painted as an h3.
   Two pages had a heading-level SKIP: /email-ty/ ran h1 -> h3 ("What to
   Expect") and /what-ai-actually-teaches-you-that-no-one-mentions/ ran h1 ->
   ten h3s before its first h2. In both, the h3s are top-level sections of the
   page with no h2 above them, so the outline presented them as children of
   nothing -- which is what a screen-reader user navigating by heading actually
   hears. CLAUDE.md lists "no heading-level skips" as an accessibility
   invariant.
   The tag is corrected and the TYPE is not: live paints these at the h3 scale
   and §0 says the site looks like live. So the element becomes an h2 and this
   restores h3's 20px/1.3 and its 30/16 margins, against h2's 26px/1.25 and
   57.2/0.8em. Outline fixed, pixels unchanged.
   Written at (0,2,2) -- `main > h2.as-h3` and `main.post > h2.as-h3` -- because
   `main > h2` and `main.post > h2` are both (0,1,2), and a bare `.as-h3` would
   tie on specificity and lose on source order. */
main > h2.as-h3,
main.post > h2.as-h3 {
  margin-top: 30px;
  margin-bottom: 16px;
  font-size: 20px;
  line-height: 1.3;
}

/* No trailing margin on a post's last block. Live's content container ends
   flush; this carried the last paragraph's own 27px past it, which showed on
   the one post that ends in prose rather than in the recent-work panel. */
/* Live's content container ends flush: its last block measures margin-bottom
   0 on every page checked -- four media pages ending in a figure and the one
   post ending in prose. This carried the block's own 27px past the content. */
main.post > :last-child {
  margin-bottom: 0;
}

/* Except where live keeps it. This is genuinely inconsistent on live -- the
   trailing paragraph on /why-i-was-working/ measures margin-bottom 0 and the
   one on /fox-25-the-junk-platoon-on-a-mission/ measures 27, which is block
   type rather than any rule. Marked in the fragment rather than guessed at. */
main.post > .keeps-margin:last-child {
  margin-bottom: 27px;
}

main.post > .post-header {
  max-width: min(var(--edge-space), 710px);
  margin: 0 auto 80px;
}

.post-header h1 {
  margin: 0;
  color: var(--navy);
  font-size: 30px;
  font-weight: 600;
  line-height: 36px;
  letter-spacing: -0.3px;
}

/* inline-block, as live's .page-description is. It matters: an inline-block
   sits on the parent's text baseline, so the line box holding it is ~6.7px
   taller than the box itself, and that space is what separates the
   description from the date. Reproducing the box model gets the gap at every
   width; hard-coding 26.7px would only have been right where the body
   line-height is 1.65. */
.post-description {
  display: inline-block;
  max-width: 100%;
  margin: 20px 0 0;
  color: var(--navy);
  font-size: 22px;
  font-weight: 600;
  line-height: 22px;
}

.post-meta {
  margin: 20px 0 0;
  color: #000;
  font-size: 12px;
  font-weight: 600;
  line-height: 15.6px;
}

/* img/iframe/video are in this list, and are NOT in the sitewide one above:
   a post's pictures sit in the text column at 710 like everything else, and
   without them here a bare <img> child of main fell through to --w-normal
   and drew 1200 wide. On one post that was 331px and 276px of extra height
   from two images, which was the whole of its remaining error. */
main.post > :is(h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote, figure, pre, img, iframe, video, .scroll-x, .meta) {
  max-width: min(var(--edge-space), 710px);
}

/* h2 and h3 do NOT share a value: live gives a post's h2 57.2px above and its
   h3 30px. Lumping them cost 27.2px per h3, and one post carries twelve. */
main.post > h2 {
  margin-top: 57.2px;
}

main.post > h3 {
  margin-top: 30px;
  margin-bottom: 16px;
}

main {
  /* --theme-content-vertical-spacing: 10px. This was 2.5rem, which pushed
     everything 30px down the page relative to live.

     CORRECTED 2026-08-31 — the bottom was 0, on the strength of live measuring
     padding:0px on main.site-main. That reading is true and was still wrong:
     live's 10px is on the INNER wrapper, div.ct-container-full, which carries
     padding:10px 0 and holds everything below the hero. Reading the outer
     element and stopping there cost 10px at the foot of EVERY page — live's
     main ran 2415.8 against 2405.8 here on /faith/, 2555.2 against 2545.2 on
     /health/, 1375.6 against 1365.6 on /projects/ — so the footer sat 10px
     high sitewide. A walk of main's CHILDREN can never see this; it is main's
     own padding, and it only shows if you compare main's height itself.

     The top 10px is cancelled by the hero's -10px margin so the hero stays
     flush, which is what live does; the gap below the hero comes from the
     first block's own margin-top. */
  padding-block: 10px 10px;
}

/* Opt out for a block that genuinely needs the full width — a hero, a card
   grid, a gallery. */
main > .wide {
  max-width: var(--w-normal);
}

/* ---------------------------------------------------------------- header */

/* body > header, NOT a bare `header`. The site header is the one direct child
   of body; a post's header is a <header> too, and a bare selector caught it —
   which made the post title, its description and its date flex ITEMS sitting
   in a row across the page instead of stacked, and forced a 120px min-height
   on the block. That was ~550px of accumulated error on the longest posts. */
body > header {
  /* 120px tall above 1000px, 70px below — measured on live at 400, 800, 1400. */
  min-height: 120px;
  display: flex;
  align-items: center;
  padding-block: 0;
  /* NO border-bottom. Live's header has none — computed border-bottom-width 0px
     — and this drew a visible 1px rule under it that live does not have. It also
     ate 1px off the content box, which is why the inner container measured 119
     against live's 120. */
}

header .wrap {
  /* width:100% is load-bearing. header is display:flex and .wrap is a flex
     ITEM, which is content-sized on the main axis unless told otherwise. It
     measured 209px inside a 345px header at 360, and 890 inside 1400 — so
     space-between had almost nothing to distribute, and the whole nav sat
     310px left of live's while the brand sat 57px right of it.

     Live's container measures 324 at 360 (90vw) and 1200 at 1400 — which the
     max-width on .wrap already gives; it just never filled it. */
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  /* Fill the header's height, as live's container does: it measures 990x120 at
     a 1100 viewport while this one measured 990x37.5. Two consequences, both
     real — the brand sat 2.5px high (y 40.8 against live's 43.3), and the nav
     links were only 15.6px tall instead of the full 120px strip live makes
     clickable. */
  align-self: stretch;
  display: flex;
  align-items: center;
}

/* Read off live's own header: 25px, weight 700, letter-spacing NORMAL. The
   size does not change with the breakpoint even though the header height does
   (120px at 1100, 70px at 800 and 420). An earlier pass set -0.01em here, which
   made the brand 148.3px wide against live's 151.0. See the line-height note
   below before touching it. */
.brand {
  color: var(--navy);
  font-size: 25px;
  /* SETTLED — measured with a Range over the text node, which reports the
     painted glyph box independent of display type. Do not change this again.

         live          151.01x33.33 @ top 42.36  (1100)  17.36 (800, 420)
         build lh:33   151.01x33.33 @ top 42.38  (1100)  17.38 (800, 420)
         build lh:37.5 151.01x33.33 @ top 42.36

     Both land within 0.02px, so this is not a visual choice. Comparing ELEMENT
     boxes instead suggests a 1.1px error that is not real: live's brand is an
     inline <a> inside a block wrapper, so its rect is the 33.3px inline content
     area, while .brand here is a direct flex child whose rect is its whole
     37.5px line box. Two different boxes, same glyphs. 37.5px would also be
     defensible — it reproduces live's line box exactly — but 33px is already
     correct and reads as a regression to anyone diffing element rects. */
  line-height: 33px;
  font-weight: 700;
  text-decoration: none;
}

/* NO hover treatment. Live's brand does not change on hover -- measured with a
   real pointer at rest and hovered, colour and decoration identical both
   times. The earlier note here claimed live turned it #1559ed; that came from
   reading the theme's rule rather than the rendered state. The rule exists but
   never applies. */

nav ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* 0, not 1.5rem. Live butts the links together and spaces them with 12.5px
     of padding on each link instead — measured at 1400: the links start at
     877, 971, 1030, 1101, 1183, 1249 with widths 94, 59, 71, 82, 66, 51, which
     is exactly adjacent with no gap. */
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Live's header links fill the header's full height — 120px at desktop, not
   the 16px of their own text — so the whole strip is clickable. */
/* header nav itself must stretch, not just its ul and li. Without it the ul has
   no height to fill: live's nav measures 423.4x120 and the links are the full
   120px strip, while this one measured 423.4x15.6 with the links only as tall as
   their own text. Same x and width, very different hit area. */
header nav,
header nav ul,
header nav li {
  align-self: stretch;
  height: 100%;
}

header nav {
  display: flex;
}

header nav a {
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 12.5px;
  line-height: 15.6px;
}

/* The last link is flush with the container's right edge: live's nav ends at
   1300, which is the 1200 container's right edge exactly. */
header nav li:last-child a {
  padding-right: 0;
}

nav li + li {
  margin-top: 0;
}

/* 12px, weight 700, uppercase, black — and letter-spacing NORMAL.
   The 0.02em here widened the nav to 432.8px against live's 423.4 and pushed
   its left edge to 612.3 against live's 621.6, so every header link sat ~9px
   off. Read off live, not measured off a rendered width. */
nav a {
  color: var(--text);
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
  text-transform: uppercase;
}

/* NO hover treatment on the nav either. Live's nav links are black at rest and
   black on hover, with no underline in either state -- measured with a real
   pointer. The theme DOES define an animated underline bar on ::before and
   ::after in #1559ed, which is where the earlier note's claim came from, but
   both compute `content: none` and `opacity: 0` at rest AND on hover, so they
   never generate a box. Reading the rule is not the same as reading the page. */

/* The current page IS coloured, and this comment used to say the opposite.
   Read out of live's own stylesheets rather than measured, because the earlier
   claim came from a measurement that had caught the wrong element:

     blocksy/static/bundle/main.min.css
       nav[class*=menu] li[class*=current-menu-] > .ct-menu-link {
         color: var(--theme-link-active-color, var(--theme-link-hover-color)) }
     blocksy/css/global.css
       --theme-link-hover-color: var(--theme-palette-color-2)   -> #1559ed

   No --theme-link-active-color is set for the menu, so it falls through to the
   hover colour. The selector matches BOTH navs -- the desktop menu and the
   mobile drawer -- because each carries "menu" in its class, so the drawer's
   current item is blue too. Live's markup for it is li.current-menu-item; ours
   is the aria-current the assembler already sets, which is the same set of
   elements and the one a screen reader reads. MIGRATION.md D7 said "live
   leaves every nav link black" -- true of the REST state, wrong about the
   current item, and corrected there. */
nav a[aria-current="page"] {
  color: var(--link-hover);
}

/* ------------------------------------------------------- mobile nav drawer */

/* Live's header has two rows — data-device="desktop" and "mobile" — swapping at
 * 1000px, with a hamburger that opens a right-side offcanvas panel:
 *
 *   .ct-panel { position:fixed; inset:0; z-index:999999; opacity:0;
 *     transition:opacity .25s ease-in-out }
 *   [data-behaviour*=side] .ct-panel-inner { position:absolute; inset-block:0;
 *     max-width:var(--side-panel-width); transition:transform .25s ease-in-out;
 *     box-shadow:0 0 70px rgba(0,0,0,.35) }
 *   #offcanvas .ct-panel-inner { background:rgba(18,21,25,.98) }
 *   --side-panel-width: 500px / 65vw under 1000 / 90vw under 690
 *   .ct-panel-content { padding:35px }   items margin 0 0 20px
 *   [data-id=mobile-menu] { 20px / 700 / #fff }
 *
 * PROGRESSIVE ENHANCEMENT: the trigger is hidden and the inline nav visible
 * until site.js adds .has-nav-toggle to <html>. With JavaScript off every link
 * stays reachable rather than sealed behind a button that cannot open.
 */
.nav-trigger {
  display: none;
  align-items: center;
  gap: 8px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  cursor: pointer;
}

/* Square, against the icon's own height="14" attribute. Live renders it 18x18,
   stretching the 0 0 18 14 viewBox so the three bars sit further apart. Left to
   the attribute it came out 18x14 and 2px lower, because the flex button
   centres a shorter box in the 70px mobile header. */
.nav-trigger-icon {
  fill: currentColor;
  width: 18px;
  height: 18px;
}

/* Live's mobile trigger is the icon ALONE. Probing its header at 360 returns
   the brand and a bare 18x18 svg at x=317, flush to the container's right
   edge — the theme's "Menu" label element is present in the markup but not
   rendered. This one was visible, which put a word next to the icon that live
   does not show and pushed the icon inward.

   Hidden in CSS rather than dropped from the markup: the button still needs an
   accessible name, and it already carries aria-label="Menu", so this span is
   redundant to a screen reader either way — but keeping it means the markup
   still reads as a labelled control if this rule is ever removed. */
.nav-trigger-label {
  display: none;
}

/* Live's hover vocabulary, measured with a real pointer on both pages:
     - an underlined link loses its UNDERLINE and keeps its colour
     - everything else does not react at all
     - the only exception is the two GenerateBlocks CTA buttons (the hero's and
       the mid-page one), whose black fill goes to rgba(0,0,0,0.76)
   Nothing recolours on hover. The rules below used to add underlines and swap
   colours, both of which live does neither of. */
.nav-trigger:hover {
  color: inherit;
}

/* The page does not scroll while the drawer is open.
   Live does this - measured, its body goes from overflow `hidden auto` to
   `hidden` the moment the panel opens - and this build did not, so the content
   behind an open drawer scrolled freely. On a phone that reads as the site
   coming apart.
   On HTML, not body: `overflow-x: clip` makes html the scroll container here,
   and a lock on body measurably did nothing (the page still scrolled 0 -> 600).
   Live locks body because live html is `overflow: visible`, which makes body
   the scroller there. Same intent, different element.
   overflow-y only, NOT the shorthand: `overflow-x: clip` on html and body is
   load-bearing, because .band is 100vw and would otherwise put a horizontal
   scrollbar on every page. The shorthand would overwrite it. */
html.drawer-open {
  overflow-y: hidden;
}

.offcanvas {
  position: fixed;
  inset: 0;
  z-index: 999999;
  display: none;
  opacity: 0;
  pointer-events: none;
  background: rgba(0, 0, 0, 0.25);
  transition: opacity 0.25s ease-in-out;
}

.offcanvas.is-open {
  display: block;
  opacity: 1;
  pointer-events: auto;
}

.offcanvas-inner {
  position: absolute;
  inset-block: 0;
  right: 0;
  width: 100%;
  max-width: var(--panel-w, 500px);
  padding: 35px;
  background: rgba(18, 21, 25, 0.98);
  box-shadow: 0 0 70px rgba(0, 0, 0, 0.35);
  transform: translateX(100%);
  transition: transform 0.25s ease-in-out;
}

.offcanvas.is-open .offcanvas-inner {
  transform: translateX(0);
}

/* The mark is an SVG now, so nothing here sizes type. font-size/line-height
   used to set a 32px glyph; live draws a 12x12 path. */
/* The mark is an SVG now, so nothing here sizes type. font-size/line-height
   used to set a 32px glyph; live draws a 12x12 path inside 10px of padding,
   which is what makes the button a 32x32 target -- this was 12x12, a tap
   target well under the 24px minimum. */
.offcanvas-close {
  display: block;
  margin-left: auto;
  padding: 10px;
  border: 0;
  background: none;
  /* 70% white, which is live's fill. Solid white read brighter than live's. */
  color: rgba(255, 255, 255, 0.7);
  line-height: 0;
  cursor: pointer;
}

.offcanvas-close svg {
  display: block;
  fill: currentColor;
}

.offcanvas nav ul {
  display: block;
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
}

.offcanvas nav li + li {
  margin-top: 20px;
}

.offcanvas nav a {
  color: #fff;
  font-size: 20px;
  font-weight: 700;
  text-decoration: none;
}

/* The drawer needs its OWN current-page rule, at (0,2,3). The sitewide
   nav a[aria-current="page"] is (0,1,2) and .offcanvas nav a above is (0,1,3),
   so the plain white wins on specificity and the drawer item stayed white after
   the markup started marking it. This is the trap CLAUDE.md names: at the point
   of writing a rule, grep for every rule matching the same element first.

   Live paints this the same #1559ed as the desktop menu. Its mobile menu sets
   only --theme-link-initial-color (#ffffff) via [data-id="mobile-menu"], with
   no active or hover override, so the current-item rule falls through to the
   global --theme-link-hover-color exactly as the header does.

   NOTE FOR TODD: on the drawer's near-black panel that is about 2.6:1, which
   is below WCAG AA for text. It is live's own value and matching live is what
   was asked for, so it is not changed here -- but a lighter blue in the drawer
   only is a one-token change if you want it. */
.offcanvas nav a[aria-current="page"] {
  color: var(--link-hover);
}

/* Only once the toggle is wired does the inline nav give way to the button. */
.has-nav-toggle .nav-trigger {
  display: none;
}

@media (max-width: 999.98px) {
  :root {
    --panel-w: 65vw;
  }

  .has-nav-toggle header nav {
    display: none;
  }

  .has-nav-toggle .nav-trigger {
    display: flex;
  }
}

@media (max-width: 689.98px) {
  :root {
    --panel-w: 90vw;
  }
}

/* ---------------------------------------------------------------- footer */

/* margin-top 0 and padding-top 0 on live, measured on main.site-main and
   footer at 1400. The 4rem here put the footer 64px below where live has it. */
footer {
  margin-top: 0;
  padding-block: 2.5rem;
  background: var(--surface);
  color: var(--text);
}

/* One bullet-separated row: socials, Privacy Policy, Contact, the year, and the
   attribution. Wraps on narrow viewports rather than overflowing, and the row
   stays centred whether it is on one line or three. */
.footer-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  font-size: 15px;
}

.footer-row a {
  color: var(--text);
  text-decoration: none;
}

.footer-row a:hover {
  text-decoration: underline;
}

.footer-row .copy {
  color: var(--slate);
}

/* The separators are inert: aria-hidden in the markup, and non-selectable here
   so a copy of the footer text does not come away peppered with bullets. */
.footer-row .dot {
  color: var(--slate);
  user-select: none;
}

.socials {
  display: flex;
  align-items: center;
  /* 15px, measured between live's icon boxes at 390. Was 0.75rem (12px), set
     when the boxes were 20 wide rather than 38. */
  gap: 15px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.socials a {
  display: flex;
  align-items: center;
  justify-content: center;
  /* 38x38, which is live's own tap area — measured on live at a 390 viewport,
     six icons at 38x38 with 15px between them. This build had been letting the
     <a> shrink-wrap its 20x20 icon, giving a quarter of live's target and
     landing under WCAG 2.2 AA's 24x24 minimum, which live clears comfortably.
     Nothing moves visually: the icon is still 20x20 and still centred; only the
     hit area grows, which is invisible on a desktop and is the whole difference
     on a thumb. Found by measuring tap targets at phone widths — a desktop
     comparison cannot see it, because the pointer is exact. */
  width: 38px;
  height: 38px;
  color: var(--slate);
}

.socials a:hover {
  color: var(--navy);
}

/* currentColor, so the icons follow the link colour on hover and in forced-
   colours mode instead of being locked to a fill. */
.socials svg {
  display: block;
  width: 20px;
  height: 20px;
  fill: currentColor;
}

/* --------------------------------------------------------------- buttons */

/* Black fill, white text, 10px radius, 12px/15px padding, 18px weight 500 —
   all measured off the live Start Here CTA. White on black is 21:1. */
.btn {
  display: inline-block;
  padding: 12px 15px;
  border: 0;
  border-radius: var(--radius-btn);
  background: var(--black);
  color: var(--white);
  font-family: inherit;
  font-size: 18px;
  font-weight: 500;
  line-height: 1.35;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}

/* The DEFAULT button does not react -- live's 'Read More' in the about band
   measures #000 at rest and #000 hovered. Only the two GenerateBlocks CTAs
   below lighten. This used to go to --slate, which is neither. */
.btn:hover {
  background: var(--black);
  color: var(--white);
  text-decoration: none;
}

/* The mid-page CTA lightens exactly as the hero's does. */
.cta > .btn:hover {
  background: rgba(0, 0, 0, 0.76);
  color: var(--white);
}

.btn--ghost {
  background: transparent;
  border: 1px solid var(--black);
  color: var(--text);
}

.btn--ghost:hover {
  background: var(--black);
  color: var(--white);
}

/* ----------------------------------------------------------------- forms */

input,
textarea,
select,
button {
  font: inherit;
}

input[type='email'],
input[type='text'],
textarea {
  width: 100%;
  max-width: 100%;
  padding: 0.6em 0.75em;
  border: 1px solid var(--slate);
  background: var(--white);
  color: var(--text);
}

/* No border-color change on focus. Live's input measures 1px solid #3A4F66
   focused and unfocused alike; the :focus-visible ring above is the whole of
   its focus treatment. */

label {
  display: block;
  margin-bottom: 0.35em;
  color: var(--navy);
  font-weight: 600;
}

/* Available to a screen reader, invisible on screen. Used for the opt-in's
   email label: a placeholder is not an accessible name, and it vanishes the
   moment someone starts typing. Not `display: none`, which would hide it from
   assistive technology too and defeat the point. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------------------------ hero */

/* The homepage hero. Measured off the live page 2026-08-29: full-bleed, 640px
   tall, `stop-drifting-header.webp` (2048x640) drawn at its natural size from
   the top-left, with the Start Here button 500px down and sitting at the start
   of the middle third.
 *
 * The artwork carries the headline — "Stop Drifting." is inside the image — so
 * the h1 is real but visually hidden. It is the image's text alternative, not a
 * trick: the live page has NO h1 at all, which is defect 6, and rendering one
 * above the artwork would change the layout the port is supposed to preserve. */
/* The homepage hero, copied from the live rules rather than measured:
 *
 *   .gb-element-9946362c {
 *     align-items:center; justify-content:center; background-repeat:no-repeat;
 *     background-image:url(stop-drifting-header.webp);
 *     min-height:640px; padding-top:500px }
 *   @media (max-width:767px) {
 *     background-image:url(stop-drifting-hero-mobile2.webp);
 *     background-size:contain; min-height:125px;
 *     padding-top:85px; padding-right:40px; text-align:center }
 *
 * It is alignfull — full viewport width. It was briefly narrowed to the text
 * column on request; the standing instruction is that the rebuild matches live
 * exactly, so it is full-bleed again.
 *
 * There is a SEPARATE mobile artwork. fetch-reference.sh missed it because it
 * is referenced only from CSS, never from markup — worth remembering for any
 * other asset that lives in a stylesheet.
 */
/* NOT display:flex. The live rule does carry align-items and justify-content,
   but the element they sit on computes display:block, so both are inert there —
   copying them across as a flex container centred the button and put it 252px
   right of and 56px below where live draws it. Live lays the button out in a
   three-column row instead; see .hero-cta. */
.hero {
  max-width: none;
  width: 100%;
  display: block;
  min-height: 640px;
  padding-top: 500px;
  background-image: url('/images/stop-drifting-header.webp');
  background-repeat: no-repeat;
  /* Live leaves the standard content spacing under the hero. */
  margin-bottom: var(--gap);
}

/* Live puts the button in the middle of three equal columns with 27px
   gutters, so its left edge tracks the viewport: (100% - 54px)/3 + 27px.
   Reproducing that box directly is what makes the button land correctly at
   every width rather than at the one that was measured — the two outer
   columns hold nothing, so they are expressed as the offset they amount to
   instead of as empty elements.

   The width matters as much as the offset: below 768 live centres the button
   inside this column (the hero switches to text-align:center), which is only
   the same as live if the box is exactly one column wide. */
.hero-cta {
  width: calc((100% - 54px) / 3);
  margin-left: calc((100% - 54px) / 3 + 27px);
  margin-bottom: 31.5px;
}

/* .gb-text-9b4dbf9e — the live CTA, including its 100px offset.
   display:inline, not the .btn default of inline-block: an inline box takes
   its height from the font's content area (24px at 18px) rather than from
   line-height, which is what makes live's pill 48.0 tall and not 48.3.
   line-height is inherited rather than restated: the theme steps body from
   1.65 to 1.75 below 690, so a hard 1.65 here computed 19.8 where live
   computes 21. */
.hero .btn {
  display: inline;
  position: relative;
  left: 100px;
  line-height: inherit;
  padding: 12px 15px;
  border-radius: 10px;
  background: #000;
  color: #fff;
  font-size: 18px;
  font-weight: 500;
  text-align: center;
}

.hero .btn:hover {
  background: rgba(0, 0, 0, 0.76);
  color: #fff;
}

@media (max-width: 767px) {
  .hero {
    background-image: url('/images/stop-drifting-hero-mobile2.webp');
    background-size: contain;
    min-height: 125px;
    padding-top: 85px;
    padding-right: 40px;
    text-align: center;
  }

  .hero .btn {
    padding: 5px 7px;
    font-size: 12px;
  }
}

/* ----------------------------------------------------------- cover cards */

/* "My Top Three". Every value measured off the captured live homepage
 * 2026-08-29/30 — backgrounds, radii and alignment as well as boxes. An
 * earlier pass measured only x/w/h and inferred the rest, which produced black
 * text sitting directly on the photograph: unreadable on the first card, and
 * nothing like the live design.
 *
 * Live, desktop:
 *   band          full-bleed, #f2f5f7, headings centred
 *   card          356x650, border-radius 20px, padding 30px
 *   white box     inset 30px, bottom-anchored, #fff, radius 10px, padding 30px
 *   card heading  20px/600 black; body 18px/400 navy; cta 18px/500 black underlined
 */

/* The grey band runs the full width of the viewport, not the container. */
main > .band {
  /* main carries no padding, so it IS the document width — 100% here is
     full-bleed without 100vw, which counts the vertical scrollbar. */
  max-width: none;
  width: 100%;
  /* Live measures padding 0 on the band, with margin 0 0 27px. The section
     heading's own top margin (57.2 desktop, 48 mobile) COLLAPSES out through
     the band, so live's band top and heading top are the same y — 1916 at 400,
     1881 at 1400, identical in both.

     3rem/3.5rem here blocked that collapse and then added its own 48px on top
     of the margin, putting the heading 96px below where live has it. */
  padding-block: 0 20px;
  /* Must use the SAME edge rule as header/main/footer. A flat 1rem here made
     the card container ~1118 at a 1150px viewport where live's is ~1035, so
     cards came out 356 against live's 320 — the "dev looks zoomed" difference. */
  /* The band itself carries NO inline padding. Its two containers are
     different widths on live and the single 55px here could only ever be
     right for one of them:

       heading + lede   max-width 1250, padding-inline 28  -> content 1194
       card row         max-width 1250, padding-inline 55  -> content 1140

     Measured on live at 400, 800 and 1400 — heading w/x of 329/28, 744/28 and
     1194/103, cards 275/55, 206/55 and 356/130. Both fall out of that one
     model at every width, which is why neither needs a per-width value. */
  padding-inline: 0;
  /* 27px, which the note above already records as live's value and which was
     never actually declared. Its absence closed the gap between this band and
     the pullquote below it to zero, against live's 27. */
  margin-bottom: 27px;
  background: var(--surface);
}

/* The about band is the last block on the page and live gives it no trailing
   margin. */
main > .band--about {
  margin-bottom: 0;
}

/* Live's .gb-text-4bb00369 and .gb-text-178ca38d — the only two headings that
   override the global h2 weight. "My Most Recent Work" carries no such rule and
   stays at the global 600. Setting 800 on every h2, as this file did for a
   while, was generalising from these two. */
/* Live measures margin 57.2px 0 20.8px and padding-top 30px on BOTH headings
   that carry this treatment — the band's "My Top Three" and the About block's
   "In Case We Haven't Met" — at 400, 800 and 1400. The 10px bottom margin here
   was neither, and the missing top margin is what the band collapses out to
   space itself from the block above. */
.section-title {
  font-weight: 800;
  text-align: center;
  margin: 57.2px 0 20.8px;
  padding-top: 30px;
}

.band > h2,
.band > .lede {
  max-width: 1250px;
  padding-inline: 28px;
  margin-inline: auto;
  text-align: center;
}

/* Live: 20px/26, weight 600, NAVY — not the global .lede's 20px/1.65 slate.
   It goes to 24px/28.8 under 690, which is LARGER than desktop. Measured
   twice at 400 to be sure that was not a misreading. */
.band > .lede {
  /* auto, not 0: the shorthand above resets margin-inline and the rule that
     centres this box (.band > h2, .band > .lede) has the same specificity and
     loses on source order, so a 0 here left the sub-heading's box at x=0 and
     its centred text 327px to the left of live's. */
  margin: 0 auto 30px;
  color: var(--navy);
  font-size: 20px;
  font-weight: 600;
  line-height: 26px;
}

main > .band > .covers {
  /* Same 1250 container as the heading above, with 55px of padding instead of
     28. That difference — 27px — is the whole of the card row's extra inset,
     and it holds at every width: at 1400 the box is 1250 centred at x=75, so
     the content is 1140 at x=130, which is 3x356 + 2x36 exactly; at 400 it is
     385-110 = 275 at x=55. Both match live. */
  max-width: 1250px;
  padding-inline: 55px;
  /* 16px, read off live's .wp-block-group: padding 0 27px 16px. The 27
     confirms the 28+27=55 model above; the 16 was missing, and it is the whole
     of the band's 16px shortfall. */
  padding-bottom: 16px;
  display: grid;
  /* minmax(0, 1fr), not 1fr: a bare 1fr floors at min-content, so once the
     card became a flex box its panel's min-content width pushed the columns
     apart — 211/225/209 where live has three equal 201s. Same fix .cards and
     .media-cards already carry. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 36px;
  /* 0, not 2rem: live spaces the cards from the lede with the LEDE's 30px
     bottom margin and gives the list none of its own. */
  margin: 0 auto;
  list-style: none;
}

.covers > li {
  margin-top: 0;
}

/* Height is a MINIMUM, not a ratio. Measured across four widths: the card is
   650 tall at 405, 1009 and 1385, and grows to 746 at 785 where the copy needs
   more room. An aspect-ratio of 356/650 gives 367px at 785 — half the live
   height — and only diverges between the breakpoints, which is why testing at
   420, 1400 and 1920 never caught it. Width is fluid, capped by the container. */
/* RULE: height is a constant 650, width is fluid.
   Measured at ten viewports — 235x650, 305x650, 575x650, 276x650, 301x650,
   334x650, 356x650 — the height never moves. An aspect-ratio gives 524 to 622
   in the 1000-1200 range where live is 650. */
.cover {
  /* Live's .wp-block-cover sets white text for the card. Every element inside
     names its own colour, so this shows up only on the image's alt text if
     the photograph fails to load — white over the black overlay, as live has
     it, rather than black on black. */
  color: #fff;
  position: relative;
  /* Live's card is a flex box that bottom-aligns the white panel inside 30px
     of padding. That is what lets the CARD grow when the copy needs more room
     than 650 leaves — the note above already recorded live doing exactly that
     at 785, but an absolutely positioned panel cannot push its parent, so the
     panel grew UPWARDS instead and overflowed the top of the card. With
     overflow:hidden here, the first lines of every card heading were being cut
     off below about 900px wide. */
  display: flex;
  align-items: flex-end;
  min-height: 650px;
  padding: 30px;
  border-radius: 20px;
  overflow: hidden;
}

.cover > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The dim layer over the photograph. Live puts a separate element on top of the
   image — <span class="wp-block-cover__background has-black-background-color">
   with background #000 at opacity 0.3 — which is why reading filter/opacity on
   the <img> itself found nothing and this got missed entirely. */
.cover::after {
  content: '';
  position: absolute;
  inset: 0;
  background: #000;
  opacity: 0.3;
}

/* Above the dim layer. */
.cover-body {
  z-index: 1;
}

/* The white panel. An in-flow flex item now, so its height is whatever the
   copy needs and the CARD grows to hold it — 279px on the live first card at
   1920, 686px at 800 where the card itself goes to 746. position:relative
   keeps it above the ::after scrim; the 30px inset is the card's padding. */
.cover-body {
  position: relative;
  width: 100%;
  padding: 30px;
  background: var(--white);
  border-radius: 10px;
}

/* Live's card title is an h2 with 20px/25 and a 16px bottom margin, and it
   carries its text DIRECTLY — the only link on the card is the CTA line under
   the paragraph. This was an h3 wrapping an <a> at 20px/26 with a 10px margin,
   which made every card body 12.5px short and put a link where live has none. */
.cover-body h2 {
  margin: 0 0 16px;
  font-size: 20px;
  font-weight: 600;
  line-height: 25px;
  color: var(--text);
}

/* 30px, which is where the gap above the CTA comes from on live. It was 0
   here with the space moved onto .cover-cta as a 22.5px top margin — 7.5px
   short, and on the wrong element. */
.cover-body p {
  margin-bottom: 30px;
  font-size: 18px;
  font-weight: 400;
  /* No line-height of its own — it follows the body, which is 1.65 above 690
     and 1.75 below. Measured on live: 29.7px at 1400 and 31.5px at 360, which
     is exactly those two ratios at 18px.

     This was pinned to 1.65, and the comment that pinned it called an earlier
     1.75 "a misread". The 1.75 was correct FOR MOBILE; the error was reading
     one width and hard-coding it for all of them. */
  color: var(--navy);
}

/* The card title is no longer a link — live's is plain text in an h2 — so the
   two rules that styled it are gone with it. One of them added an underline on
   hover, which was the same invention removed from the nav and post links: live
   changes colour on hover and never underlines. */

/* 0 on every side. Live puts the gap above the CTA on the PARAGRAPH's bottom
   margin, not here — 1.25em on this element made it 22.5px against live's 30px
   and put the space on the wrong box. The margin-bottom reset matters too:
   .cover-body p now carries 30px, and this is a <p>, so without it the card
   body ran 30px long. */
.cover-cta {
  margin: 0;
}

/* The reset above never applied: `.cover-body p` is (0,1,1) and beats a bare
   `.cover-cta` at (0,1,0), so this paragraph kept the 30px the description
   carries and the white box ran exactly 30px long — the thing the comment
   above says it exists to prevent. (0,2,0) settles it. */
.cover-body > .cover-cta {
  margin-bottom: 0;
}

.cover-cta a {
  color: var(--text);
  font-size: 18px;
  font-weight: 500;
  text-decoration: underline;
}

/* THE CARD'S HORIZONTAL PADDING IS A DESKTOP VALUE AND A PHONE PAYS FOR IT. D32.
 *
 * Todd, 2026-09-02: the CTA links "break onto the second line" on his phone.
 * MEASURED at a 412px viewport: 230 of those 412 pixels are horizontal padding
 * -- 55 either side of the card row, then the card's own 30, then the white
 * panel's 30 -- leaving 182px of text. The longest CTA, "Making healthier
 * choices ->", needs 224. So it cannot fit at any phone width: two lines at 412,
 * three at 360, and at 320 "Projects I'm working on ->" took four.
 *
 * LIVE IS IDENTICAL, and that is why this is a DEVIATION and not a repair.
 * Measured on toddtemaat.com in the same iframe at matched clientWidth: the CTA
 * paragraph is 182 / 160 / 130 wide at 412 / 390 / 360, the same line splits to
 * 0.1px, the same 18px. This build reproduced live exactly. These are desktop
 * paddings WordPress never stepped down for a phone.
 *
 * Nothing else moves: no font-size, no line-height, no colour, and every value
 * above 690 is untouched. The room comes from the two INNER paddings, 30 -> 18.
 * The row keeps 28 so the cards stay flush with the section heading above them,
 * which uses the same 1250 container at 28 -- taking the row down further would
 * make the cards wider than the heading and read as a mistake.
 *
 * Budget at 360: 360 - 56 = 304 card, - 36 = 268 panel, - 36 = 232 text, against
 * the 224 the longest CTA needs. At 320 there is still not room and it wraps to
 * two lines -- which is why the fragment binds the arrow to the last word with
 * &nbsp;. An arrow alone on its own line is the thing that actually looked
 * broken, and that cannot happen now at any width.
 *
 * 689.98 and not 781.98 on purpose: between 690 and 782 the cards are already
 * stacked and have 470px of text at 700, so there is nothing to fix there and
 * no reason to restyle it. */
@media (max-width: 689.98px) {
  main > .band > .covers {
    padding-inline: 28px;
  }

  .cover {
    padding: 18px;
  }

  .cover-body {
    padding: 18px;
  }
}

/* ------------------------------------------------------------- post rows */

/* "My Most Recent Work". Measured: a vertical stack of horizontal rows, each a
 * 227x151 thumbnail on the left and the title plus date on the right, vertically
 * centred, in a container ~1125 wide. It is NOT a card grid. */
/* Same specificity reason as .covers above. Live measures 1125 here.
 *
 * Both selectors: the list is a .panel child on the homepage, so `main > .posts`
 * alone stopped matching when the panel went in and the ul fell back to the UA's
 * padding-left with no max-width.
 */
main > .posts,
.panel > .posts {
  max-width: 1125px;
  margin: 0 auto;
  padding: 0 0 16px;
  list-style: none;
}

/* Measured on live: rows are 27px apart with NO rule between them and NO
 * vertical padding — h=151, exactly the thumbnail, m=0 0 27px, p=0, and border
 * widths of 0 on every one of the five. This drew a 1px separator under each
 * row and padded 24px top and bottom, which set the pitch at 200 against
 * live's 178 and put a line on the page that live does not have.
 *
 * The 24px inline padding replaces the UA padding the ul used to supply: it
 * keeps the thumbnail at x=154 inside a row that spans the panel's full 1125.
 */
/* Live's row is two flex columns with a 27px gap and 24px of padding INSIDE
   each column — not one padded row with a fat gap. The two models agree on
   where the thumbnail sits and nowhere else: folding the inner 24s into a
   51px gap put the whole inset on the left of the body, so the text started
   24px early and ran 24px wide.

   The columns are proportional (25% / 75% of the row less the gap), so the
   thumbnail scales with the viewport; it was pinned at 227px, which is only
   right at 1920. min-width:0 is what lets a 2048px-wide image shrink to its
   column instead of holding it open. */
.posts > li {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 27px;
  margin: 0 0 27px;
  padding: 0;
}

.posts > li:last-child {
  margin-bottom: 0;
}

.posts-thumb {
  flex: 0 1 25%;
  min-width: 0;
  padding: 0 24px;
}

/* height:auto, not a forced 3/2 with object-fit. Live's thumbnails are plain
   images at their own intrinsic ratio — 2048x1365 is 1.50037, not 1.5 — so a
   hard 3/2 was 0.1px tall at every width and would crop outright any future
   image that is not 3:2. The width/height attributes on the tag carry the
   ratio, so nothing reserves less space than before. */
.posts-thumb img {
  /* block, which is how live's is displayed. It is also what makes the
     line-height:0 that used to sit on the link unnecessary: an inline image
     would otherwise leave descender space under it. */
  display: block;
  width: 100%;
  height: auto;
}

.posts-body {
  flex: 0 1 75%;
  min-width: 0;
  padding: 0 24px;
}

/* The post title is FLUID, not a pair of hard-coded sizes. This clamp is
   copied verbatim from the live stylesheet, where it governs .entry-title:

     clamp(15.747px, 0.984rem + ((1vw - 3.2px) * 0.645), 24px)

   Measured against it: 16.002px at 360 and 22.71px at 1400, both exact. The
   rule this replaces was 24px with a 16.4px mobile override, which was right
   at neither width and wrong at every one in between. */
/* 10.72px, not 0.25em. At the 24px this title reaches on the homepage 0.25em
   is 6px, and live measures 10.72 — the same 10.72 the /faith/ row titles use.
   Live also puts a 7.04px top margin on the date beneath, but adjacent margins
   collapse and 10.72 wins, so the title's margin is the whole of it. Worth 4.72
   per row and the last of the posts panel's shortfall. */
.posts-body h3 {
  margin-bottom: 10.72px;
  font-size: clamp(15.747px, 0.984rem + ((1vw - 3.2px) * 0.645), 24px);
  font-weight: 600;
  line-height: 1.25;
}

/* The one-line excerpt under each post. Live carries one on every row and the
   rebuild had none at all, on the homepage and on all 19 posts.

     clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.469), 20px)

   Measured 14.1876px at 360 and 19.0652px at 1400. Line-height comes from the
   body — 1.75 under 690, 1.65 above — which live does too: 24.83 and 31.46
   measured are exactly those ratios. Colour is --text, not the muted --slate
   the date uses. */
.posts-excerpt {
  margin: 7.04px 0 0;
  color: var(--text);
  font-size: clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.469), 20px);
}

/* In the RECENT-WORK PANEL, and only there, the excerpt is the same type as the
   title. Live carries TWO different sizes on its excerpt-shaped elements and
   this build had applied the homepage one to both places:

     live, homepage excerpt   14.1876 / 24.8283 at 360   <- the clamp above
     live, panel excerpt      16.002  / 20.8026 at 360   <- the TITLE clamp

   Measured on live at 360, 420, 690, 768, 1100 and 1400: the panel title and
   its excerpt are identical at every one, to the ten-thousandth. This build was
   ~2px small and ~3px loose at every width, which wrapped the text differently
   and left the post 20 painted runs adrift of live at 420 — the only one of the
   nineteen that was materially off, and off because a clamp measured on the
   homepage was applied to a different component.

   Same declarations as .posts-body h3 on purpose, including the 1.25 -> 1.3
   step below 690 further down this file. */
/* ONLY BELOW 690. The 360 measurement above is right, but it does not hold
   at wider viewports: measured on live at 690, 900 and 928 the panel excerpt
   is 15.8056/26.08, 16.7906/27.70 and 16.9219/27.92 -- the homepage excerpt
   clamp within 0.07px, at the body's own 1.65 -- while this rule was giving
   18.23/22.78, 19.58/24.48 and 23.06/28.83. That was one extra painted run on
   every opt-in post at 1200 and above (the ChatGPT excerpt wrapped to four
   lines against live's three) and 32.7px of missing height between 768 and
   1100. Above 690 the base .posts-excerpt rule is the measured one. */
@media (max-width: 689.98px) {
  .panel--narrow .posts-excerpt {
    font-size: clamp(15.747px, 0.984rem + ((1vw - 3.2px) * 0.645), 24px);
    line-height: 1.25;
  }
}

.posts-body h3 a {
  color: var(--navy);
  text-decoration: none;
}

.posts-body h3 a:hover {
  text-decoration: none;
}

/* Live measures 18px and rgb(0,0,0) here — the body size and colour, not the
   12px --slate the global .meta rule sets for a post byline. */
.posts-body .meta {
  margin-bottom: 0;
  color: var(--text);
  font-size: 18px;
}

/* The date is a link (live wraps it in one) but live paints it as body text:
   .ct-dynamic-data a sets --theme-link-initial-color: initial, so the anchor
   inherits black and carries no underline, and only the unscoped a:hover
   turns it #1559ed. Measured on live at 1440 on /, /take-the-next-step/ and
   /contact/: rgb(0,0,0), text-decoration none. This build was painting it as
   an ordinary prose link -- #2872fa and underlined -- on all 14 pages that
   carry the panel. */
.posts-body .meta a {
  color: inherit;
  text-decoration: none;
}

.posts-body .meta a:hover {
  color: var(--link-hover);
}

/* Mobile, measured off the captured live homepage framed at 420px — which
 * renders at 405 effective once the scrollbar is counted. Every number here
 * came from that measurement, not from scaling the desktop rules down. */
/* RULE: the grid collapses at 782px — WordPress's default columns breakpoint,
 * which is what live uses. Measured: 685 shows one card at 575, 885 shows three
 * at 234. Neither 690 nor 1000 is right.
 *
 * A width sweep of live against dev across eight viewports found the worst
 * divergence on the page at 768: live shows ONE card at 643 wide, dev showed
 * three at 221. Live uses the 999.98 breakpoint here, and testing only at
 * 420/1400/1920 never touched the range where they differ.
 *
 * Card width is `100vw - 110px`, which reproduces live at every mobile width
 * measured: 360→250, 375→265, 397→287, 415→305. A fixed 295 was too wide below
 * 430 and too narrow above it. Height still comes from the 650px min-height. */
@media (max-width: 781.98px) {
  /* Must match the desktop selector's specificity. `main > .band > .covers`
     is (0,2,1); a bare `.covers` at (0,1,0) loses and the grid stays three
     across. */
  main > .band > .covers {
    /* No gap override: live's columns keep their 36px when they wrap. 1.5rem
       took 24 off the stack. */
    /* minmax(0, 1fr), not 1fr, for the same reason as the desktop rule: a
       bare 1fr floors at the items' min-content, and at 320 the health card's
       "relationships." held the track at 224.67 inside a 210px content box.
       Live's column is flex-basis 100% and simply fills the 210. Measured
       2026-09-01: this was the whole of the homepage's -7 painted runs at 320,
       and the only width at which it read anything but +2. */
    grid-template-columns: minmax(0, 1fr);
  }

  /* Stacked: the column is flex-basis 100%, so the card fills the row. No
     100vw arithmetic — that counted the scrollbar and needed a magic 110px. */
  .cover {
    width: 100%;
    margin-inline: auto;
  }
}

/* 781, not 690. These are WordPress columns and they wrap at the columns
   breakpoint, which is 781 — measured on live either side of it: at 782 the
   row is still two columns, at 781 both are 100% and stacked. Stacking at 690
   left every row two-across between 690 and 781, where live has them one above
   the other. The vertical gap is the row's own 27px, not 16. */
@media (max-width: 781.98px) {
  .posts > li {
    flex-wrap: wrap;
    align-items: flex-start;
  }

  .posts-thumb,
  .posts-body {
    flex-basis: 100%;
  }
}

/* A call-to-action paragraph holding a single .btn, mid-page.
 *
 * Centred at every width — measured on live at x=136 in a 360 column at 400,
 * and x=644 in the 700 column at 1400, both dead centre. The rebuild ranged it
 * left at both.
 *
 * This is deliberately a class rather than a rule on every p holding a .btn:
 * the About block's Read More is also a lone button in a paragraph and live
 * ranges THAT one left. The two blocks genuinely differ.
 */
/* The mid-page Start Here button. Live wraps it in a shrink-to-fit block
   centred in the 700px text column; a full-width centred paragraph puts the
   button at the same x, so the box stays one element.

   margin-inline must be auto: `main > *` supplies it at (0,0,1) and this
   rule at (0,1,0) overrode it, which left the paragraph at x=0 and the button
   602px left of live's.

   The 20px is padding, not margin, because live's is: as a margin it would
   collapse against the band heading's 57.2px and vanish, leaving the band
   20px high. */
.cta {
  margin: 0 auto;
  padding-bottom: 20px;
  text-align: center;
}

/* Same reason as the hero button: live's is display:inline, so its 48px pill
   overflows the 29.7px line box instead of setting it. As inline-block it was
   48.3 tall AND made the line box 48.3, which is where 18.6px of the band's
   offset came from. */
.cta > .btn {
  display: inline;
  line-height: inherit;
}

/* ---------------------------------------------------------------- opt-in */

/* Rendered by the shell on the 14 pages that carry it. Centred, on the surface
   tone, inside the text column so it never runs wider than the prose above it. */
/* PORTED, not designed. Live's opt-in is the same Turnstile form this build
   posts to /api/subscribe — the capture carries cf-turnstile and the sendiio
   markup — so it should look like live's, and it did not. Measured at 1100:

                    live                        was
     container      transparent, no padding     #f2f5f7, 32px 24px, capped at
                                                --w-text
     heading        26px / 32.5                 22px / 29.7
     input          full width, 40 tall, 16px,  380 wide, 53.3 tall, 18px,
                    3px radius, start-aligned   square, centred
     button         40 tall, 5px 20px, 3px      53.7 tall, 12px 15px, 10px
                    radius, blue                radius, black
     stack gap      24px (live's <br>s)         16px

   The button fill is var(--link), which is now live's own #2872fa — the same
   value the theme uses for --theme-button-background-initial-color. White
   18px/500 on it is 3.4:1, which fails AA for body-size text; that is noted in
   the register rather than fixed here on my own authority. */
/* Live's opt-in container is capped at 710 AND at 90vw, like every other
   block. `none` let it run the full width of main, which only looks the same
   because the form inside is centred -- below 690 the form then had more room
   than live gives it and the heading wrapped a line short. */
.optin {
  max-width: min(var(--edge-space), 710px);
  margin: 0 auto 27px;
  padding: 0;
  background: none;
  text-align: center;
}

/* Live's heading sits INSIDE the form, so it takes the form's 549.6 width
   rather than the container's. Both wrap to two lines on the markup's own <br>
   and both centre, so the painted text is the same either way — matched so the
   boxes compare equal too. */
/* 24px below 690 and 26 above, like every other heading — and NO line-height,
   which the global h2 already steps 1.25 -> 1.3. Pinning 26/1.25 held the
   heading a size too large on phones: live measures 93.6 tall there (three
   lines at 31.2) against 97.5 here. */
/* width: max-content, NOT max-width: 549.6px.
   549.6 is the right number and it was the wrong way to say it. The heading's
   longest line measures 549.6 at 26px -- exactly, to the tenth -- so pinning the
   BOX to 549.6 puts the text on a knife edge, and above 690 this build lost it:
   live painted two lines (549.6 then 369.5) and this painted THREE
   (452, 90.5, 369.5), making the heading 97.5 tall against live's 65. On all
   fourteen opt-in pages, at every width from 690 up, unseen because the opt-in
   had only ever been measured at 1920 and on a phone.
   max-content is the mechanism rather than the measurement: the box becomes the
   text's own width, so it cannot reflow. Measured against live, this reproduces
   every row -- 549.6 at 26px above 690, 507.3 at 24px from 600 to 689, then
   90vw at 500/420/360 where live wraps too (450, 378, 324). */
.optin h2 {
  width: max-content;
  max-width: var(--edge-space);
  margin: 2em auto 0.7em;
  font-size: 24px;
}

@media (min-width: 690px) {
  .optin h2 {
    font-size: 26px;
  }
}

@media (min-width: 769px) {
  .optin h2 {
    margin-top: 2.2em;
    margin-bottom: 0.8em;
  }
}

/* 549.6px, and the rows are spaced by ONE BODY LINE each, not by 24px.
   Sendiio separates them with <br>, and a <br> occupies a full line box —
   29.7px above 690, 31.5px below. 24px is the <br> element's own height inside
   that line, which is what an earlier pass here measured and used, leaving the
   block 11.3px short. Walked on live at 1100 from the container's top:

     center (h2)      0.00 -> 65.00
     br              87.80 -> 111.80      h2 bottom to input top = 50.48
     input          115.48 -> 155.48        = 20.8 h2 margin + one line
     br             157.48 -> 181.48
     turnstile      185.17 -> 312.55      input bottom to wrapper top = 29.69
     button         312.55 -> 352.55      wrapper bottom to button top = 0

   The gaps are therefore explicit, not uniform: one line after the heading's
   own margin, one line before the widget, and nothing before the button — the
   widget's wrapper already closes with its own trailing line. */
/* SHRINK-TO-FIT, not a hard 549.6. Live's form takes its width from the
   heading's longest unbroken line, so it scales with the heading: 549.609 at
   26px and exactly 507.328 at 24px, which is the same number times 24/26. A
   fixed 549.6px was right only above 690, where the heading is 26px. */
.optin form {
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
}

.optin input[type='email'] {
  margin-top: 29.7px;
  width: 100%;
  max-width: none;
  height: 40px;
  padding: 0 15px;
  border: 1px solid #3a4f66;
  border-radius: 3px;
  background: none;
  font-size: 16px;
  text-align: start;
}

/* The Turnstile widget renders a fixed-size iframe; centre it and let it keep
   its own dimensions rather than stretching it. */
/* Live wraps the widget in a 12px-padded box, making 324 around the 300x73.7
   Turnstile iframe — and closes that box with a <br>, which adds exactly ONE
   body line under the widget. Measured on live: the wrapper runs 127.4 tall
   against a 73.7 widget and 24px of padding above 690, and 130.0 against 74.5
   below it. The remainder is 29.7 then 31.5 — 18px at the body's 1.65 and 1.75.
   That whole 30px was missing here, and it is the entire reason the opt-in came
   out 41px shorter than live's. Written as the two measured values because the
   step lives on body's line-height, not on --lh-body. */
.optin .cf-turnstile {
  display: flex;
  justify-content: center;
  padding: 12px;
  margin-top: 29.7px;
  margin-bottom: 29.7px;
}

/* Both gaps and the wrapper's trailing line follow the body's 1.65 -> 1.75
   step at the theme's 689.98. */
@media (max-width: 689.98px) {
  .optin input[type='email'] {
    margin-top: 31.5px;
  }

  .optin .cf-turnstile {
    margin-top: 31.5px;
    margin-bottom: 31.5px;
  }
}

.optin button {
  /* No fixed height: live's is padding plus one line, so it grows with the
     body line-height — 40.0 above 690 and 41.5 below. A pinned 40 held it
     short on phones.

     inline-flex, matching live: as a block this measured 39.69 against live's
     40.00 with every other value identical — flex resolves the content height
     where a block's line box rounds down. Worth 0.31px, and it also settles
     the display / justify-content / align-items / position differences that
     were being carried as inert. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  /* Live sets min-height 40px. Above 690 that is what holds the button at
     40.00 against a 29.7 line plus 10 of padding, which is 39.69 on its own —
     the last 0.31px. Below 690 the line grows to 31.5 and the content wins at
     41.5, so the minimum stops mattering, which is why the two agreed there
     and not here. */
  min-height: 40px;
  padding: 5px 20px;
  border: 0;
  border-radius: 3px;
  background: var(--link);
  color: var(--white);
  font-size: 18px;
  font-weight: 500;
  cursor: pointer;
}

/* No change on hover. Live's subscribe button measures #2872fa at rest and
   #2872fa hovered. */
.optin button:hover {
  background: var(--link);
}

/* ------------------------------------------------------------- /email-ty/ */

/* THE COLUMN MODEL, read out of live's own stylesheet rather than fitted to a
   measurement. reference/assets/wp-content__uploads__generateblocks__style-741.css
   is 527 bytes long and says, in full, what matters here:

     .gb-element-c48fa95c { max-width: 40% }
     @media (max-width: 767px) { .gb-element-c48fa95c { max-width: 80% } }
     .gb-element-dc0999a8 { ...centred...; max-width: 40% }
     @media (max-width: 767px) { .gb-element-dc0999a8 { max-width: 60% } }
     .gb-text-ccc315b5 { text-align: center }

   Mapped against the captured markup: c48fa95c wraps ALL the prose and ends
   with the "Recent Posts" heading; dc0999a8 is the post list beside it, on its
   own width; ccc315b5 is that heading, centred.

   This build pinned the column at a flat 762px, which is 40% of 1905 -- correct
   at a 1920 viewport and at no other width. At 360 it drew 324 where live draws
   288. Percentages now, and the 767 step, which is the GenerateBlocks
   breakpoint CLAUDE.md already lists and is NOT interchangeable with the
   theme's 689.98.

   Written as the same element list `main > :is(...)` already uses for the text
   column, deliberately: a bare `main.page--email-ty > *` would also catch the
   full-bleed .page-hero and squash it to 40%. */
main.page--email-ty > :is(h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote, figure, pre, .scroll-x, .meta) {
  max-width: 40%;
}

/* THE RECENT POSTS LIST MATCHES THE REST OF THE SITE, NOT LIVE. D34.
 *
 * Todd, 2026-09-02: "make the Recent Posts section on /email-ty/ have the same
 * size pictures and text as it does everywhere else on the site."
 *
 * This REPLACES the live-derived model, and deliberately. What was here before
 * reproduced live exactly -- a 40% wrapper with the list capped at 379.5px and
 * centred, giving a 129px thumbnail at 1200 -- and that fidelity is precisely
 * what made this page the odd one out: every other post list on the site draws a
 * 171x171 thumbnail in a 710px row. Live was consistent with WordPress; the site
 * is now consistent with itself, which is what was asked for.
 *
 * So the panel takes the same column every other row list takes, and the 379.5
 * cap is gone. The empirical 379.5 and the investigation behind it are kept in
 * the git history rather than here, because a number nothing uses is a trap for
 * the next reader.
 *
 * The 40% model still governs the PROSE above -- the h1, the paragraphs, the
 * instruction line -- which is untouched. Only the list moved. */
main.page--email-ty > .panel.panel--recent {
  /* 710 SPELLED OUT, not var(--w-text): this page declares no textWidth, so the
     variable falls back to the 700 default and the row came out 10px narrow with a
     168px thumbnail against everyone else's 171. This is the same expression
     main.post > .panel--narrow uses for the recent-work list, which is the closest
     thing on the site to this block. */
  max-width: min(var(--edge-space), 710px);
}

@media (max-width: 767px) {
  main.page--email-ty > :is(h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote, figure, pre, .scroll-x, .meta) {
    max-width: 80%;
  }

  /* The panel no longer steps to 60% here. That was live's value and D34 replaces
     it: the list follows the site, and min(90vw, 710px) already fills a phone. The
     PROSE above still steps to 80%, which is untouched and still live's. */
}

/* This page's h1 is NOT the site's h1. Live runs it 40px/48px weight 700 at
   letter-spacing -0.4px and ITALIC, against every other page's 30px/36px/600 at
   -0.3px upright — measured on live at 1920, both pages, same session. The size
   and weight were already right here; the slant and the last 0.1px were not, and
   between them they ran the painted title 8.01px wide of live's.

   Scoped to the page rather than written at `h1`, because a global edit here
   would italicise the title of all 29 pages to fix one. */
main.page--email-ty .page-hero-body h1 {
  font-style: italic;
  letter-spacing: -0.4px;
}

/* The whitelist instruction carries 24px under it on live, where every other
   paragraph on the page carries 27. With the h3 below it at margin-top 0, the
   collapsed gap is 24 — and this build was producing 30, because its h3 brings
   30 of its own and 30 beats 27. That 6px then pushed every remaining element on
   the page down by exactly 6. */
main.page--email-ty > .instruction {
  margin-bottom: 24px;
}

main.page--email-ty > h3 {
  margin-top: 0;
}

/* Live centres this one. Measured: the heading box is 762 wide at x=571.5 with
   text-align center, so the words sit at x=878.13 against this build's 571.5. */
main.page--email-ty > h2:not(.as-h3) {
  text-align: center;
}

/* ------------------------------------------------------------ contact form */

/* New on this site — live has no contact form, only an obfuscated mailto that
   never decoded once the Cloudflare email-protection script stopped shipping.
   So there is no parity target here: this follows the site's OWN conventions
   (the opt-in's control metrics, the body's 1.65 -> 1.75 step at 689.98) so it
   reads as part of the site rather than as a bolted-on widget.

   Full-width and left-aligned, unlike .optin: that one sits centred on a tinted
   panel, this one sits in the running text column. */
.contact-form {
  /* The same column every paragraph on the page sits in. `form` is NOT in the
     `main > :is(h1, h2, p, ul, ...)` list that carries the text column, so a
     bare form is full-bleed -- and `max-width: 100%` here made it worse by
     beating `main > *`'s 1200 on specificity. Measured: the form ran 1905 wide
     at a 1920 viewport with the prose beside it at 710. */
  max-width: min(var(--edge-space), var(--w-text));
  margin-top: var(--gap);
}

.contact-form .field {
  display: flex;
  flex-direction: column;
  margin-top: var(--gap);
  margin-bottom: 0;
}

/* The submit sits in .actions, NOT .field. `.field` is a column flex box for a
   label above its control, and a flex item stretches to the line by default --
   which took the inline-flex button to the full 710 column. Measured. */
.contact-form .actions {
  margin-top: var(--gap);
  margin-bottom: 0;
}

.contact-form label {
  margin-bottom: 8px;
  color: var(--slate);
  font-size: 16px;
  font-weight: 500;
}

/* Same control metrics as the opt-in: 40px tall, 15px inset, the --slate
   border, 3px radius, 16px text. A second look for the same job would read as
   two different sites. */
.contact-form input[type='text'],
.contact-form input[type='email'],
.contact-form textarea {
  width: 100%;
  padding: 0 15px;
  border: 1px solid var(--slate);
  border-radius: 3px;
  background: none;
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: var(--lh-body);
}

.contact-form input[type='text'],
.contact-form input[type='email'] {
  height: 40px;
}

/* The textarea needs symmetric vertical padding the single-line inputs get
   from their fixed height. */
.contact-form textarea {
  padding: 10px 15px;
  resize: vertical;
}

.contact-form input:focus-visible,
.contact-form textarea:focus-visible {
  outline: 2px solid var(--link);
  outline-offset: 1px;
}

/* Left-aligned, not centred like the opt-in's — this widget sits in a
   left-aligned column. The 12px padding matches the opt-in's wrapper so the
   iframe is inset the same way on both forms. */
.contact-form .cf-turnstile {
  display: flex;
  justify-content: flex-start;
  padding: 12px 0;
  margin-top: 29.7px;
}

.contact-form button[type='submit'] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 5px 20px;
  border: 0;
  border-radius: 3px;
  background: var(--link);
  color: var(--white);
  font-family: var(--font);
  font-size: 18px;
  font-weight: 500;
  cursor: pointer;
}

.contact-form button[type='submit']:hover {
  background: var(--link);
}

/* Disabled while a submit is in flight — the handler sets it, so it has to
   read as unavailable or a second click looks like a dead button. */
.contact-form button[type='submit']:disabled {
  opacity: 0.6;
  cursor: default;
}

/* Empty at rest. margin-top rather than a reserved height: an empty <p> with a
   top margin collapses to nothing, so the button keeps its normal 27px of
   space below it until there is actually a message to show. */
.contact-form .form-status {
  margin-top: var(--gap);
  margin-bottom: 0;
  font-size: 16px;
}

.contact-form .form-status:empty {
  margin-top: 0;
}

.contact-form .form-status.is-ok {
  color: var(--navy);
  font-weight: 500;
}

/* #b3261e, not the theme's blue: an error has to be distinguishable from a
   success without relying on colour alone, which the wording already does. */
.contact-form .form-status.is-error {
  color: #b3261e;
  font-weight: 500;
}

.contact-form .form-status.is-sending {
  color: var(--slate);
}

/* The body's line-height steps 1.65 -> 1.75 at the theme's 689.98, and the
   Turnstile gap is a body line. Same step the opt-in takes. */
@media (max-width: 689.98px) {
  .contact-form .cf-turnstile {
    margin-top: 31.5px;
  }
}

/* The Turnstile widget is a FIXED 300px iframe; the text column is not.
   `justify-content: flex-start` is right wherever the column can hold it and
   wrong the moment it cannot. At a 320px viewport the column is 288 and the
   widget painted 7.7 -> 307.7 inside a 303px viewport, so about 5px of the
   CAPTCHA was clipped off the right — on the one control a visitor MUST be able
   to complete.
   Centring makes the overflow symmetric, which puts the whole widget back on
   screen (1.7 -> 301.7), and it is exactly what the opt-in has always done for
   the same reason.
   The column is min(90vw, 710), so it drops under 300 at a 333px viewport.
   359.98 rather than 333 leaves room for the scrollbar and for a phone that
   reports an odd width, and a 300px widget centred in a 324px column at 360
   reads as deliberate rather than accidental. */
@media (max-width: 359.98px) {
  .contact-form .cf-turnstile {
    justify-content: center;
  }
}

/* ----------------------------------------------------------------- cards */

.cards {
  display: grid;
  /* 200px lets three sit across the 710px text column and drops to one on a
     phone without a breakpoint. */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1.75rem;
  margin-bottom: var(--gap);
  padding: 0;
  list-style: none;
}

.cards > li {
  margin-top: 0;
}

.card {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--white);
  border: 1px solid var(--line);
  overflow: hidden;
}

/* Cropped to a consistent shape. WITHOUT this the source images render at
   their natural size — the Top Three art is 1600x2000, which is 500px tall at
   phone width and swamps the page. */
.card img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.cards--portrait .card img {
  aspect-ratio: 4 / 5;
}

.card-cta {
  margin-top: 0.6em;
  font-weight: 600;
}

.card-cta a,
.card h3 a {
  text-decoration: none;
}

.card-cta a:hover,
.card h3 a:hover {
  text-decoration: none;
}

/* A section subtitle. It reads as a heading but is not one — making it an h3
   under an h2 that has no other h3 siblings would imply a level of structure
   the page does not have. */
.lede {
  margin-top: -0.25em;
  color: var(--slate);
  font-size: 20px;
}

/* ------------------------------------------------------------------- about */

/* The "In Case We Haven't Met" block at the foot of the homepage.
 *
 * Its prose is NOT in the 710px text column. Measured on live at 1400: the
 * container is 1250px wide with 28px of inline padding, and the paragraphs run
 * the full 1194px inside it, which is why they set in two lines there and three
 * here. Clamped to 700 the block ran 195px taller than live's.
 *
 * The narrow column still governs the intro at the top of the page — measured
 * 700/700 on both sides. The two blocks genuinely differ; do not unify them.
 */
main > .about {
  max-width: min(var(--edge-space), 1250px);
  margin-inline: auto;
  padding: 0 28px 20px;
}

/* 20px/26 and centred here, against the global .lede's 20px/1.65 ranged left. */
.about > .lede {
  /* Navy, like every other heading. Without this it fell through to the
     global .lede's --slate and rendered #3a4f66 against live's #192a3d. */
  color: var(--navy);
  margin: 0 0 30px;
  font-size: 20px;
  line-height: 26px;
  text-align: center;
}

/* The paragraph holding the Read More button. Measured on live: its container
   is text-align:start at x=96, i.e. ranged LEFT with the prose above it, and
   carries no bottom margin. An earlier read of this took the button element's
   own text-align:center — which centres the label inside the button — for the
   container's, and put the button 537px to the right of where live has it. */
.about > p:last-child {
  margin-bottom: 0;
}

/* ------------------------------------------------------------------- panel */

/* The "My Most Recent Work" block. Live renders it as a rounded #f2f5f7 panel,
 * not the plain white the rebuild had:
 *
 *   .gb-element-569ee669 { justify-self:center; max-width:1125px }
 *   .gb-element-988955bf { background:#f2f5f7; max-width:1150px;
 *                          border-radius:25px }
 *   .gb-text-2fc96f6a    { padding-top:10px; text-align:center }
 *
 * Measured 1125px wide at 1400 and 360px at 400 — the outer 1125 clamp wins at
 * desktop, --edge-space at mobile. Same edge rule as header, main and footer;
 * a flat value here is what put the cards at the wrong width before.
 *
 * It is a .band sibling in spirit but NOT the same component: .band is
 * full-bleed to the viewport, this one is an inset rounded card.
 */
/* The posts' copy of the recent-work panel. Same component as the homepage's
   and the same 25/75 row model, in a narrower box: live measures it 710 wide
   with a 25px radius and NO tint, against the homepage's tinted 1125. The
   thumbnails fall out of that on their own -- 123.8 wide here against 227
   there -- because the columns are proportional.

   Until this existed the tails were flat markup with no list around them, so
   none of the row CSS applied and every thumbnail rendered full-bleed at
   1905x1270 instead of 124x83. That was ~5,900px of extra height on each of
   twelve posts. */
/* main.post >, not main >: `main > .panel` is the same (0,2,1) and appears
   LATER in the file, so it won on source order and this box drew at the
   homepage's 1125 with 227px thumbnails instead of live's 710 and 124. */
main.post > .panel--narrow {
  max-width: min(var(--edge-space), 710px);
  background: none;
}

main > .panel {
  max-width: min(var(--edge-space), 1125px);
  margin-inline: auto;
  background: var(--surface);
  border-radius: 25px;
  padding: 0;
}

.panel > h2 {
  padding-top: 10px;
  margin: 57.2px 0 20.8px;
  color: var(--navy);
  font-size: 26px;
  font-weight: 600;
  text-align: center;
}

/* --------------------------------------------------------------- pullquote */

/* Measured on live at 1400 and 400, not inferred from the block CSS:
 *
 *   wrapper  max-width:900px, centred, transparent
 *   figure   padding:80.96px 0 16px 12px; margin:0 0 22px; NO background;
 *            22px/500/1.4  (20px/1.4 under 690)
 *   title p  22px/500, colour #000, margin-bottom 22px
 *   cite     16px, line-height 22.4px, colour #000 — NOT italic
 *
 * The earlier version had a --surface panel, 2rem of padding and navy text,
 * none of which live has, and it was clamped to the 710px text column, so it
 * rendered 636px wide against live's 888 and ran 128px taller.
 *
 * `main > .pullquote` because `main > *` sets max-width at the same specificity
 * and would otherwise win on source order.
 */
/* padding-bottom and margin-bottom are 0 here, NOT live's 16 and 22.
   Live closes the figure after the quote and puts the byline in a SEPARATE
   sibling group, so those two land BETWEEN the quote and the byline. This
   markup keeps the byline inside the figure where it belongs, so the same
   38px is carried by .pullquote figcaption's margin-top instead. Measured:
   live leaves 38px there, this left 1px. */
main > .pullquote {
  max-width: min(var(--edge-space), 900px);
  /* 27px bottom, matching live's .gb-element-0a744600 (margin 0 auto 27px).
     With 0 here the only thing separating it from the feed was the separator's
     own 16px top margin collapsing through, so the gap read 16 against 27. */
  margin: 0 auto 27px;
  /* The 12px left inset belongs to the blockquote alone. Live closes the
     quote before the byline and only the quote is indented; carrying the
     inset on the figure indented the byline too, and because the byline is
     centred inside whatever width it is given, that narrowed box moved the
     avatar 27px to the right of where live draws it. */
  padding: 80.96px 0 0;
  background: none;
  color: #000;
  font-size: 22px;
  font-weight: 500;
  line-height: 1.4;
}

.pullquote blockquote {
  /* Live's blockquote box is 888 wide starting 12px in from the 900px
     wrapper. A margin reproduces that box exactly; padding would leave the
     box 900 wide and only move the text. */
  margin: 0 0 0 12px;
  padding: 0;
  border: 0;
  color: inherit;
  /* The glyph below is absolutely positioned against this box and sits
     behind the text on its own stacking context, exactly as Blocksy does it. */
  position: relative;
  isolation: isolate;
}

/* The decorative opening quotation mark. Blocksy's own rule, lifted verbatim
   from the captured main.min.css (figure.wp-block-pullquote blockquote:before):
   a 70px square at 15% opacity in the text colour, masked to a quote-mark SVG,
   hung 1/3 of its size above and to the left of the blockquote. 40px under
   690. Measured on live at 1440: content "", 70x70, top/left -23.3333px,
   opacity 0.15, on both pullquotes (/ and /deep-slow-work-of-contentment/).
   This build had none, which no painted-run count could see -- a pseudo-element
   is not a text node. Found by looking. */
.pullquote blockquote::before {
  position: absolute;
  z-index: -1;
  content: "";
  width: var(--quote-icon-size, 70px);
  height: var(--quote-icon-size, 70px);
  top: calc(var(--quote-icon-size, 70px) / -3);
  inset-inline-start: calc(var(--quote-icon-size, 70px) / -3);
  opacity: 0.15;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M0,19.04v-8.62c-0.04-1.24,0.14-2.4,0.54-3.49S1.49,4.88,2.2,4.06C2.92,3.23,3.77,2.55,4.76,2C5.76,1.46,6.84,1.11,8,0.96 v3.72C6.58,5.17,5.6,5.89,5.07,6.85C4.55,7.8,4.29,8.98,4.29,10.37H8v8.67H0zM12,19.04v-8.62c-0.04-1.24,0.14-2.4,0.53-3.49 s0.95-2.05,1.66-2.87c0.71-0.83,1.57-1.51,2.56-2.06c0.99-0.54,2.08-0.89,3.24-1.04v3.72c-1.43,0.49-2.4,1.21-2.93,2.17c-0.53,0.96-0.79,2.13-0.79,3.52H20v8.67H12z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M0,19.04v-8.62c-0.04-1.24,0.14-2.4,0.54-3.49S1.49,4.88,2.2,4.06C2.92,3.23,3.77,2.55,4.76,2C5.76,1.46,6.84,1.11,8,0.96 v3.72C6.58,5.17,5.6,5.89,5.07,6.85C4.55,7.8,4.29,8.98,4.29,10.37H8v8.67H0zM12,19.04v-8.62c-0.04-1.24,0.14-2.4,0.53-3.49 s0.95-2.05,1.66-2.87c0.71-0.83,1.57-1.51,2.56-2.06c0.99-0.54,2.08-0.89,3.24-1.04v3.72c-1.43,0.49-2.4,1.21-2.93,2.17c-0.53,0.96-0.79,2.13-0.79,3.52H20v8.67H12z'/%3E%3C/svg%3E");
}

@media (max-width: 689.98px) {
  .pullquote blockquote::before {
    --quote-icon-size: 40px;
  }
}

.pullquote-title {
  color: inherit;
  font-size: inherit;
  font-weight: 500;
  line-height: inherit;
  margin: 0 0 22px;
}

/* Live carries this text in a single <cite> whose paragraphs are separated by
 * doubled <br>. Real paragraphs say the same thing to a screen reader and to a
 * parser; a margin of exactly one line reproduces the blank line those <br>
 * pairs draw. */
.pullquote blockquote p:not(.pullquote-title) {
  font-size: 16px;
  line-height: 22.4px;
  margin: 0;
}

.pullquote blockquote p:not(.pullquote-title) + p {
  margin-top: 22.4px;
}

.pullquote figcaption {
  /* Live's byline group inherits the page's 18px/1.65/400 rather than the
     22px/500 of the quote above it. Nothing here shows those values — the name
     sets its own and the avatar is an image — but leaving them unstated meant
     the box reported the quote's 500 and a stray 14px from elsewhere. */
  font-size: 18px;
  font-weight: 400;
  line-height: 1.65;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 27px;
  padding-bottom: 54.08px;
  margin-top: 38px;
  color: #000;
}

/* Live sets the name in 18px/27px bold black. This was 14px/19.6 semibold in
   --navy, which rendered the byline visibly smaller and a different colour;
   the block happened to keep the same total height because the avatar is
   taller than either and sets it. Live marks the name up as an h5, which is
   not a section heading and would break the page's heading order, so the
   level stays out of the markup and only the appearance is matched. */
.pullquote figcaption span {
  /* FLUID, like the post titles — not the flat 18px this was pinned at, which
     is only the value above ~1598px where the clamp tops out. Derived from
     live at ten widths from 360 to 2400 and exact at every one: 15.4022 at
     768, 16.4414 at 1100, 17.3804 at 1400. line-height is 1.5 throughout. */
  font-size: clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.313), 18px);
  font-weight: 700;
  line-height: 1.5;
}

.pullquote figcaption img {
  /* 100% is live's value. It draws the same ellipse as 50% on this box, but
     there is no reason to state a different number than the site does. */
  border-radius: 100%;
}

.card-body {
  padding: 1.25rem;
}

/* A card heading is an h2 on a listing page, where it sits between the h1 and
   the first h2, and an h3 deeper in. Both render identically, which is what
   lets the markup keep a correct heading order instead of picking a level for
   visual reasons. */
.card :is(h2, h3) {
  margin-bottom: 0.35em;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.3;
}

.card p {
  margin-bottom: 0;
  font-size: 16px;
}

.card a {
  text-decoration: none;
}

.card a:hover {
  text-decoration: none;
}

/* ------------------------------------------------------------ breakpoints */

@media (max-width: 999.98px) {
  /* body > header, matching the base rule. Scoping that rule to body > header
     (so it stopped catching a post's <header>) raised it to (0,0,2), which a
     bare `header` at (0,0,1) can no longer override -- the site header stayed
     120px tall below 1000px on EVERY page, pushing main and everything in it
     50px down. */
  body > header {
    min-height: 70px;
  }

  nav ul {
    gap: 1rem;
  }
}

/* The card heading's bottom margin steps with them: 16 at 769, 14 at 768.
   Its leading steps later, at 690, from 25 to 26 — the only one of these that
   moves the text INSIDE the white panel rather than the panel itself. */
@media (max-width: 768px) {
  .cover-body h2 {
    margin-bottom: 14px;
  }
}

@media (max-width: 689.98px) {
  .cover-body h2 {
    line-height: 26px;
  }
}

/* Live steps the section headings' margins at 768 with the font unchanged —
   measured either side of it: 57.2/20.8 at 769, 52/18.2 at 768. Both the band
   heading and the panel heading do it. This build stepped nothing until 690,
   so every section from the band down sat 5.19px low across the whole
   768-690 range. */
@media (max-width: 768px) {
  .panel > h2,
  .band > h2,
  .about > h2 {
    margin: 52px 0 18.2px;
  }

  /* A post's body headings step with them -- live measures 52/18.2 here
     against 57.2/20.8 above. Missing it cost 5.2px per heading, which on a
     post with nine of them is most of a screen. */
  main.post > h2 {
    margin: 52px auto 18.2px;
  }

  /* Content headings on any page step the same way. */
  main > h2 {
    margin: 52px auto 18.2px;
  }
}

/* One pixel lower, at the GenerateBlocks breakpoint, the BAND heading alone
   tightens its leading to 31.2 and halves its padding-top, and the band's
   sub-heading goes UP from 20/26 to 24/28.8. The panel heading is untouched
   here — it holds 32.5 until 690. */
@media (max-width: 767px) {
  .band > .section-title,
  .about > .section-title {
    line-height: 31.2px;
    padding-top: 15px;
  }

  .band > .lede,
  .about > .lede {
    font-size: 24px;
    line-height: 28.8px;
    /* 25 above and 15 below, which live adds at the same breakpoint as the
       size change. padding-block so the 28px inline inset is untouched. */
    padding-block: 25px 15px;
  }
}

@media (max-width: 689.98px) {
  /* @media(max-width: 689.98px){ :root { --theme-line-height: 1.75 } } in the
     site's own stylesheet. Body copy is looser on a phone than on desktop —
     a global rule, and every paragraph on every page depends on it. */
  body {
    line-height: 1.75;
  }

  /* 22px/1.4 -> 20px/1.4 on the figure, which the title inherits. The cite
     stays 16px/22.4 at every width — measured identical at 1400 and 400. */
  main > .pullquote {
    font-size: 20px;
  }

  .pullquote-title {
    margin-bottom: 20px;
  }

  /* Live's quote figure drops its bottom margin from 22 to 20 here, which is
     the whole of the gap between the quote and the byline: 38 above 690, 36
     below. This build carries that gap as the byline's own top margin, so the
     step has to be restated on it.

     The line-height is restated for the same reason the margin is: live's
     byline group is a SIBLING of the quote figure and inherits the body's
     1.65/1.75, while this one sits inside the figure and would otherwise
     inherit its 1.4. */
  .pullquote figcaption {
    margin-top: 36px;
    line-height: 1.75;
  }

  /* The post title's leading steps with everything else here: 1.25 down to
     690, 1.3 below it. The size is fluid and already correct on both sides —
     only the ratio moves. */
  .posts-body h3,
  .panel--narrow .posts-excerpt {
    line-height: 1.3;
  }

  /* Live drops the section headings to 24px here, from 26. The two headings
     take DIFFERENT leading at this size — the band's is 28.8 (1.2) and the
     panel's is 31.2 (1.3) — which one shared declaration could not say. */
  .panel > h2,
  .band > h2,
  .about > h2 {
    font-size: 24px;
    margin: 48px 0 16.8px;
  }

  main.post > h2 {
    margin: 48px auto 16.8px;
  }

  /* The pullquote tightens here, not at 767: 50px of padding rather than 70,
     20px/28 rather than 22/30.8, and 30px below rather than 33. Measured
     either side -- 690 still has the wide values, 689 has these. */
  main.post > .pullquote {
    margin-bottom: 30px;
    padding: 50px;
  }

  main.post > .pullquote blockquote p {
    font-size: 20px;
    line-height: 28px;
  }

  /* 24px/31.2 here -- the 1.3 ratio the panel heading takes, not the band's
     1.2 -- and h3's leading goes 26 -> 27. Measured on /privacy/ either side
     of the breakpoint. */
  main > h2 {
    font-size: 24px;
    line-height: 31.2px;
    margin: 48px auto 16.8px;
  }

  main > h3 {
    line-height: 27px;
  }

  /* The post description steps here too: 22px/22 becomes 20px/35, which is
     20 at the body's mobile 1.75. Same line COUNT either side -- 3 at 420 --
     so the whole difference was 13px of leading per line, and the header ran
     39px short on a phone. */
  .post-description {
    font-size: 20px;
    line-height: 1.75;
  }

  /* .section-title, not h2: the 767 rule above sets 31.2 through
     `.band > .section-title` at (0,2,0), which outranks a `.band > h2` at
     (0,1,1) no matter which comes later, so the leading never stepped back
     down here. Same specificity, later in the file, wins. */
  .band > .section-title,
  .about > .section-title {
    line-height: 28.8px;
  }

  .panel > h2 {
    line-height: 31.2px;
  }

  /* The band lede is BIGGER here than at desktop — 24px/28.8 against 20/26 —
     and picks up 25px above and 15px below. Measured on live at 400. */
  .band > .lede {
    /* 28px inline, NOT 0 — the shorthand was wiping the container inset the
       base rule sets, so the lede measured 385 wide against live's 329 and set
       in two lines where live takes three. */
    padding: 25px 28px 15px;
    font-size: 24px;
    line-height: 28.8px;
  }

  /* One card per row here, so a 4:5 portrait image is about 490px tall and
     dwarfs the 3:2 post cards further down the page. Match them, so the two
     card grids read as the same component at every width. */
  .cards--portrait .card img {
    aspect-ratio: 3 / 2;
  }

  /* The theme's own mobile scale. h1 is already 30px, so only the lower levels
     move here. */
  h2 {
    font-size: 24px;
    line-height: 1.3;
  }

  h3 {
    line-height: 1.35;
  }

  h4 {
    line-height: 1.5;
  }

  /* No mobile override on main or footer. Measured on live at 360: content
     starts at y=80 under a 70px header, i.e. 10px of top padding — the same as
     desktop — and the footer follows the last block with no margin.

     These two rules were 2rem/3rem and 3rem. They pushed the WHOLE PAGE down
     22px, every landmark from the hero to the footer, and left 96px of dead
     space above the footer that live does not have. */
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ----------------------------------------------------------------- print */

@media print {
  header nav,
  footer nav,
  .skip {
    display: none;
  }

  body {
    color: #000;
    font-size: 12pt;
  }
}

/* Banner aligned to the text column 2026-08-29. */

/* ═══ Section pages ═══════════════════════════════════════════════════════
 *
 * Every value below was read off the LIVE page with getComputedStyle, not
 * measured from geometry and not estimated. Where live is fluid, the fluid
 * expression is reproduced; nothing is pinned per viewport.
 *
 * The hero is live's `.hero-section[data-type="type-2"]`: a full-bleed figure
 * behind a scrim, with the title block sitting on top inside the text column.
 * `port.mjs` never saw it — it extracts only `.entry-content`, and the hero is
 * outside it — so all five section pages lost their image and their tagline.
 *
 *   container    flex column, justify-content center, text-align start,
 *                padding 50px 0, min-height 400px
 *   figure       position absolute, inset 0, overflow hidden
 *   img          object-fit cover, height 100%
 *   scrim        rgba(1, 1, 1, 0.35)   <- the single_page variant. The
 *                categories prefix uses 0.26; this page is a page, not an
 *                archive, so 0.35 is the one that applies.
 *   h1           30px/36px, 600, #fff, letter-spacing -0.3px, margin 0
 *   tagline      18px/21.6px (line-height 1.2), #fff, margin-top 20px
 */
main > .page-hero {
  position: relative;
  max-width: none;
  width: 100%;
  /* main carries padding-block: 10px 0 for pages whose first block is text —
     live's content container supplies the same 10px there. A hero page puts it
     somewhere else: live measures main->hero 0 and hero->panel 10, i.e. the hero
     sits FLUSH against the header and the gap falls after it. Cancelling main's
     top padding here, and restoring the 10px below, puts the space where live
     has it and makes the hero genuinely full-bleed. */
  margin-top: -10px;
  background: var(--surface);
  overflow: hidden;
  isolation: isolate;
}

main > .page-hero > img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The scrim. Live paints it on a pseudo-element over the figure, so the image
   keeps its own colours and only the overlay darkens. */
main > .page-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: rgba(1, 1, 1, 0.35);
}

.page-hero-body {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: start;
  min-height: 300px;
  padding-block: 50px;
  /* 710, not --w-text. Live's hero sits in ct-container-narrow, whose
     --theme-narrow-container-max-width is 710px — a different container from the
     700px running-text column the homepage uses. Both values read off live. */
  max-width: min(var(--edge-space), 710px);
  margin-inline: auto;
}

/* Live steps min-height at the theme's own lower breakpoint rather than scaling
   it. 689.98 is one of the two breakpoints this site already uses everywhere —
   not a third invented for this page. */
@media (min-width: 690px) {
  .page-hero-body {
    min-height: 400px;
  }
}

.page-hero-body h1 {
  margin: 0;
  color: #fff;
  font-size: 30px;
  line-height: 36px;
  font-weight: 600;
  letter-spacing: -0.3px;
}

.page-hero-desc {
  margin: 20px 0 0;
  max-width: none;
  color: #fff;
  font-size: 18px;
  line-height: 1.2;
}

/* ── "Foundational Articles": live's ct-query-template-default ──────────────
 *
 * A two-column flex row per entry. Read off live:
 *   .wp-block-columns   display:flex; flex-wrap:nowrap; gap:27px
 *   column 1            flex-basis:25%; flex-grow:0; flex-shrink:1
 *   column 2            flex-basis:75%; align-self:center
 *   image               aspect-ratio:1/1; object-fit:cover; border-radius:0
 *   title               20px/25px, 600, --navy
 *
 * The percentages ARE the sizing — no width is hard-coded, so the row tracks
 * the column at every viewport. Below 782px WordPress's own rule sets both
 * bases to 100% and lets the row wrap, which stacks them; that is reproduced
 * with the same breakpoint rather than a new one.
 */
.rows {
  list-style: none;
  margin: 0;
  padding: 0;
}

.rows > li {
  display: flex;
  flex-wrap: wrap;
  gap: 27px;
  margin-bottom: 27px;
}

.rows > li:last-child {
  margin-bottom: 0;
}

.rows .row-media {
  flex: 0 1 100%;
  line-height: 0;
}

.rows .row-body {
  flex: 0 1 100%;
  align-self: center;
}

@media (min-width: 782px) {
  .rows > li {
    flex-wrap: nowrap;
  }

  .rows .row-media {
    flex-basis: 25%;
  }

  .rows .row-body {
    flex-basis: 75%;
  }
}

.rows .row-media img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
}

.rows h2 {
  max-width: none;
  margin: 0 0 10.72px;
  color: var(--navy);
  font-size: 20px;
  /* Live: 1.3 below the theme breakpoint, 1.25 above — 26px and 25px at 20px,
     both read off the live page. A ratio, not two pinned pixel values. */
  line-height: 1.3;
  font-weight: 600;
}

@media (min-width: 690px) {
  .rows h2 {
    line-height: 1.25;
  }
}

/* Colour on hover, no underline — see the correction on nav a:hover.
 *
 * These inherit navy at rest on live and here, and live's unscoped a:hover
 * recolours them to #1559ed. The earlier claim that live's post links do not
 * react to hover was wrong. An underline on hover would still be an invention:
 * live changes the colour only.
 *
 * color: inherit at (0,2,1) outranks a:hover at (0,1,1), so the hover rule has
 * to be spelled out here or the resting colour wins even while hovered. */
.rows h2 a,
.cards h2 a {
  color: inherit;
  text-decoration: none;
}

.rows h2 a:hover,
.cards h2 a:hover {
  color: inherit;
}

/* The row date is plain body text with a 27px gap under it — read off live:
   h2 25 + 10.72 + date 29.7 + 27 + excerpt 59.4 = 151.8, which is exactly the
   text-column height live measures. It is NOT uppercase and NOT weight 500;
   that styling belongs to the GRID card date only, where live sets it inline. */
.rows .row-meta {
  max-width: none;
  margin: 0 0 27px;
}

/* Live hyperlinks the row date but styles it as plain body text: color
   rgb(0, 0, 0), text-decoration none, 18px. Without this it inherits the site
   link colour and underline and reads as a link, which live's does not. */
.rows .row-meta a {
  color: var(--text);
  text-decoration: none;
}

/* Live's date link takes the same #1559ed on hover as every other link. */
.rows .row-meta a:hover {
  color: inherit;
}

.cards .card-meta {
  max-width: none;
  /* Read off live: 13px, line-height 21.45px, margin-bottom 19.5px. 21.45/13 is
     exactly the body ratio, so line-height is left to inherit and follows the
     1.65 -> 1.75 change under 690 on its own. */
  margin: 0 0 19.5px;
  font-size: 13px;
  font-weight: 500;
  text-transform: uppercase;
}

.rows .row-excerpt,
.cards .card-excerpt {
  max-width: none;
  margin: 0;
  color: var(--text);
}

/* ── "Recent Articles": live's ct-query-template-grid ───────────────────────
 *
 * Read off live:
 *   grid        display:grid; gap:30px; max-width 860px (it breaks OUT of the
 *               710px text column once there is room, and centres)
 *   card body   background #fff; padding 27px; border-radius 0; box-shadow none
 *   image       object-fit:cover, natural 2048/1365 ratio (NOT squared like
 *               the rows)
 *   title       clamp(15.747px, 0.984rem + ((1vw - 3.2px) * 0.645), 24px)
 *   excerpt     clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.469), 20px)
 *
 * Both clamps are set INLINE on live's markup, not in any stylesheet, and are
 * copied verbatim. Checked against the live page: 16.389px at 420 and 24px at
 * 1920, both exact.
 *
 * The column count is an explicit per-page count, not `auto-fit`. Live sets
 * --grid-columns-width in an inline <style> on each page and steps it at the
 * theme's own 999.98/689.98 breakpoints:
 *
 *     /faith/, /health/   3 -> 2 -> 1   in a 900px panel (860px grid)
 *     /projects/          4 -> 3 -> 1   in a 1000px panel (960px grid)
 *
 * auto-fit reproduced 3/2/1 by arithmetic accident and cannot reach 4: at the
 * 960px grid, minmax(240px,1fr) with a 30px gap fits floor(990/270) = 3. The
 * count is data, so it is carried on the panel rather than inferred from width.
 */
.cards {
  max-width: none;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: 30px;
}

.cards > li {
  display: flex;
  flex-direction: column;
}

.cards .card-media {
  line-height: 0;
}

/* NO aspect-ratio here, deliberately. Live sets only width/height 100% and
   object-fit:cover, so each card image keeps its OWN intrinsic ratio from its
   width/height attributes. Forcing 2048/1365 on all of them made every card
   with a differently-shaped image 2.26px short, which is exactly the residual
   that survived four other fixes. */
.cards .card-media img {
  width: 100%;
  height: auto;
  object-fit: cover;
}

.cards .card-body {
  flex: 1 1 auto;
  background: #fff;
  padding: 27px;
}

/* Both are RATIOS on live, not pinned pixels — but they step at DIFFERENT
   breakpoints, which an earlier pass here got wrong by sampling 420/660/800/2133
   and never once between 690 and 768:

     line-height    1.30 at <=689,  1.25 at >=690   -> the theme's 689.98
     margin-bottom  0.70em at <=768, 0.80em at >=769 -> a separate 768

   Bisected on the live page one pixel at a time: at innerWidth 769 the margin
   ratio is 0.800 and at 768 it is 0.700, while the line-height ratio is already
   1.250 at both and only changes between 689 and 690. Tying the margin to 690
   left every card heading 1.8634px short across the whole 690-768 band, which
   is a real 1.8px error in card height on tablets and small laptops.

   The base is live's own .entry-content h2 { margin-bottom: 0.7em }, found by
   walking the matched CSS rules rather than inferred from the numbers. */
.cards h2 {
  max-width: none;
  margin: 0 0 0.7em;
  color: var(--navy);
  font-size: clamp(15.747px, 0.984rem + ((1vw - 3.2px) * 0.645), 24px);
  line-height: 1.3;
  font-weight: 600;
}

@media (min-width: 690px) {
  .cards h2 {
    line-height: 1.25;
  }
}

/* 769, not 690 — see the note above the .cards h2 base rule. */
@media (min-width: 769px) {
  .cards h2 {
    margin-bottom: 0.8em;
  }
}

.cards .card-excerpt {
  font-size: clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.469), 20px);
}


/* ── The two section panels ────────────────────────────────────────────────
 *
 * Live wraps each section in a GenerateBlocks element, and these two rules are
 * why the content sits where it does. Copied verbatim from the live stylesheet
 * (reference/assets/wp-content__uploads__generateblocks__style-1139.css):
 *
 *   .gb-element-6e3c39a3 { background-color: var(--theme-palette-color-6,#f2f5f7);
 *                          max-width: 750px; border-radius: 20px; padding: 30px 20px }
 *   .gb-element-8ad66167 { max-width: 900px; border-radius: 20px; padding: 30px 20px }
 *
 * The panels ARE the width system on this page, and missing them is what made
 * an earlier pass of this rebuild look like a sitewide container defect:
 *   750 - 2x20 = 710   the text column and the Foundational rows
 *   900 - 2x20 = 860   the Recent grid, which is why it looks "wider"
 *   at 420: min(750, 90vw) - 40 = 338, which is exactly what live measures
 * There is no global discrepancy and nothing outside this page needs changing.
 *
 * style-1139.css contains NO media queries, so these hold at every width and
 * the panels narrow with the viewport on their own.
 */
main > .section-panel {
  max-width: min(var(--edge-space), 750px);
  /* 27px between the panels — live puts it on the FIRST panel as margin-bottom
     and leaves the last at 0. Fixed at both 420 and 1100, so it is not fluid;
     it is --theme-content-spacing, the same 27px the row gap uses. Without it
     the two panels sat flush and main was 47px short of live. */
  margin: 0 auto 27px;
  border-radius: 20px;
  padding: 30px 20px;
}

/* Live wraps both panels in a container with padding 10px 0, so there is 10px
   above the first panel and 10px below the last:
     10 + 823.1 + 27 + 1145.7 + 10 = 2015.8, live's exact container height. */
main > .page-hero + .section-panel {
  margin-top: 10px;
}

/* ZERO, matching live, which zeroes its last panel's margin and lets the
   container's padding-bottom provide the 10px.

   This was margin-bottom:10px, on the reasoning that main supplied the top 10
   and the panel could supply the bottom. The arithmetic was right and the
   result was not: with main at padding-bottom 0 the margin COLLAPSED straight
   through main's bottom edge, so it added nothing and every page ran 10px
   short. main now carries the padding for real, which both stops the collapse
   and makes this margin redundant. Measured after: live's last panel ends
   2405.8 into main on /faith/ with a 10px tail, and so does this. */
main > .section-panel:last-of-type {
  margin-bottom: 0;
}

main > .section-panel--tinted {
  background: var(--surface);
}

main > .section-panel--wide {
  max-width: min(var(--edge-space), 900px);
}

/* /projects/ is the wide one: 1000px against 900px, and four columns against
   three. Same .gb-element-8ad66167 class as /faith/ and /health/ on live, with
   different values — the rule is inline per page, so it cannot be read off one
   page and assumed for the rest. */
main > .section-panel--projects {
  max-width: min(var(--edge-space), 1000px);
}

.section-panel--tinted .cards,
.section-panel--wide .cards {
  --cols: 3;
}

.section-panel--projects .cards {
  --cols: 4;
}

@media (max-width: 999.98px) {
  .section-panel--tinted .cards,
  .section-panel--wide .cards {
    --cols: 2;
  }

  .section-panel--projects .cards {
    --cols: 3;
  }
}

/* Same specificity as the per-panel rules above (0,2,0). A bare `.cards`
   here is 0,1,0 and loses to them even inside the media query, which silently
   left /projects/ at four columns on a phone. */
@media (max-width: 689.98px) {
  .section-panel--tinted .cards,
  .section-panel--wide .cards,
  .section-panel--projects .cards {
    --cols: 1;
  }
}

/* Inside a panel the heading and intro fill it — they are no longer direct
   children of main, so the text-column cap does not reach them. */
/* Read off live: the section heading carries margin 57.2px 0 20.8px and the
   intro paragraph 0 0 27px. The 57.2px top margin is what separates the two
   sections; without it the panels sit ~57px tighter than live.

   Live's h2 is shrink-wrapped (250.6px for "Foundational Articles") rather than
   filling the panel, but its text-align is start and it carries no auto margins,
   so it paints identically to a full-width left-aligned heading. Not reproduced,
   because it is not visible. */
.section-panel > :is(h2, p) {
  max-width: none;
}

/* The section heading is CENTRED on live, and my first pass left it aligned
   left — a visible difference that a geometry-only diff called "not visible"
   because the text-align is start either way.

   Live's used values: width 250.625px (fit-content) at x 827.2, which is exactly
   centred in the 710px panel interior — 597.5 + (710 - 250.6) / 2 = 827.2.
   The heading is class="gb-text", which matches NO rule in any served
   stylesheet, so the mechanism could not be located; these are the used values
   reproduced directly, not an approximation of them. fit-content + auto margins
   also keeps it centred when it wraps, which text-align:center would not. */
.section-panel > h2 {
  width: fit-content;
  /* Live's section-heading margins are em RATIOS, and they step at 768 — NOT at
     the 690 the font-size uses. Reading only 420 and 1100 cannot tell the two
     apart, which is how an earlier pass put both on 690. Bisected on live:

       width   font-size   margin-top      margin-bottom
        1920      26       57.2 (2.2em)    20.8 (0.8em)
         769      26       57.2 (2.2em)    20.8 (0.8em)
         768      26       52.0 (2.0em)    18.2 (0.7em)   <- margins step here
         690      26       52.0 (2.0em)    18.2 (0.7em)
         689      24       48.0 (2.0em)    16.8 (0.7em)   <- font-size steps here
         420      24       48.0 (2.0em)    16.8 (0.7em)

     Tying the margins to 690 left each heading 7.8px tall across the 690-768
     band — 5.2 of margin-top plus 2.6 of margin-bottom — so /faith/ ran 7.8px
     long per panel there. Same split the card heading needs. */
  margin: 2em auto 0.7em;
}

@media (min-width: 769px) {
  .section-panel > h2 {
    margin-top: 2.2em;
    margin-bottom: 0.8em;
  }
}

/* The intro paragraph shrink-wraps and centres, exactly like the heading above
   it. This is invisible on a long intro — panel A's fills the full 710px and so
   reads as left-aligned — and only shows up on a short one. Measured on live at
   1100 in panel B, whose interior starts at x 120 and is 860 wide:

     live   p x332.3 w435.4    120 + (860 - 435.4) / 2 = 332.3, i.e. centred
     build  p x120.0 w860.0    full width, text starting 212px further left

   fit-content reproduces both cases from one rule: the long paragraph still
   fills 710, the short one collapses to its text and centres. */
.section-panel > p {
  width: fit-content;
  margin: 0 auto 27px;
}


/* ── /media/ ───────────────────────────────────────────────────────────────
 *
 * Structurally unlike the other listing pages, and measured rather than
 * assumed from them:
 *
 *   - the two h2s are OUTSIDE the panels, full content width and centred,
 *     where /faith/, /health/ and /projects/ put theirs inside at fit-content
 *   - the panels are bare 750px width constraints: no padding, no radius, no
 *     background, against the 900/1000px padded panels elsewhere
 *   - a visible <hr> separates the two sections
 *   - the first list is cover cards, the second a wide two-column row
 *
 * Live's inline <style> sets only .gb-element-1933c9dc / -eccc3df9 to
 * max-width 750px and [data-id='daeb969d'] to --grid-columns-width:3. The
 * column STEPS are the theme's, at its own 999.98 and 689.98.
 */

/* main > * caps children at --w-normal and main > h2 at --w-text (700px).
   Live's heading is 990 at 1100 — 90vw, capped at 1200 — so this has to beat
   both, which is why it is written as main > .media-heading. */
main > .media-heading {
  max-width: min(var(--edge-space), 1200px);
  text-align: center;
  /* Live's global content-h2 rhythm, confirmed on two post pages as well as
     here: 2em / 0.7em at or below 768, 2.2em / 0.8em above it. Same 768 the
     card and panel headings use, and NOT the 689.98 the font-size uses. */
  margin-top: 2em;
  margin-bottom: 0.7em;
  padding-top: 40px;
}

@media (min-width: 769px) {
  main > .media-heading {
    margin-top: 2.2em;
    margin-bottom: 0.8em;
  }
}

/* The first heading opens the page: live gives it neither the section top
   margin nor the 40px pad. The 10px is div.ct-container-full's padding-top on
   live, which sits below the hero — the same 10px .section-panel carries as a
   margin on the other listing pages. */
main > .media-heading--first {
  margin-top: 10px;
  padding-top: 0;
}

/* 100x2, centred, navy — a background bar, not a border. Fixed at every width. */
main > .media-rule {
  width: 100px;
  max-width: 100px;
  height: 2px;
  margin: 36px auto 27px;
  border: 0;
  background: var(--navy);
  /* The painted bar is the background, so this changes nothing visible — but
     the UA default leaves an <hr> at color:gray, and live's is navy. color
     feeds border-color, outline-color and text-decoration-color, so leaving it
     grey put five spurious entries in every comparison of this element. */
  color: var(--navy);
}

main > .media-panel {
  max-width: min(var(--edge-space), 750px);
  margin: 0 auto 27px;
}

/* 3 / 2 / 1 at the theme's breakpoints. Every rule here is a single class so
   the overrides cannot lose on specificity the way the /projects/ grid did. */
/* Live caps each COLUMN at 359.375px and centres the result, which is why the
   grid is narrower than its 750px panel at some widths and not others:

     cols  cap = cols*359.375 + (cols-1)*30   grid width
       3            1138.125                  750  (panel wins, cols 230)
       2             748.75                   748.75
       1             359.375                  359.375, centred

   Measured against live at 320, 360, 420, 500, 600, 689, 700, 782, 800, 820,
   834, 900, 950, 999, 1000, 1100, 1400: it holds at every one, including the
   fallbacks where the panel is narrower than the cap (324 at 360, 288 at 320).
   An earlier pass here pinned 748.75 for the two-column band alone, which was
   the same number arrived at without the rule behind it, and it left the
   one-column band stretching full width against live's centred 359.375.

   Where 359.375 itself comes from is still unidentified — the sheet that sets
   it is served cross-origin in the capture, so its matched rules cannot be
   read — but one constant reproduces all three column counts. */
.media-cards {
  max-width: calc(var(--cols, 3) * 359.375px + (var(--cols, 3) - 1) * 30px);
  margin-inline: auto;
  list-style: none;
  margin-block: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr));
  gap: 30px;
}

@media (max-width: 999.98px) {
  .media-cards {
    --cols: 2;
  }
}

@media (max-width: 689.98px) {
  .media-cards {
    --cols: 1;
  }
}

/* Fixed 400px box, content anchored to the bottom and centred. */
.media-card {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 400px;
  padding: 24px;
  overflow: hidden;
  color: var(--white);
}

.media-card > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Live's scrim is its own element — <span class="wp-block-cover__background">
   at #000 / opacity .5. The homepage's .cover uses .3; these are darker, so
   reading one and reusing it for the other would be wrong. */
.media-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: #000;
  opacity: 0.5;
}

.media-card-body {
  position: relative;
  z-index: 1;
  width: 100%;
  text-align: center;
}

/* 0.7em / 0.8em stepping at 769 — the same content-h2 rhythm the listing
   cards and the section headings use, and the third place it has turned up.
   Measured here: 14px at 700, 16px at 1100. Pinning 16px left every card title
   2px low across the whole band at or below 768. */
/* No line-height: the global h2 already steps 1.25 -> 1.3 at 689.98 and live
   does the same here (25px at 1100, 26px at 689 against a 20px font). Pinning
   1.25 held it at 25px below the breakpoint. */
.media-card-body h2 {
  margin: 0 0 0.7em;
  font-size: 20px;
  font-weight: 600;
  color: var(--white);
}

@media (min-width: 769px) {
  .media-card-body h2 {
    margin-bottom: 0.8em;
  }
}

/* 10px uppercase with 1.3px tracking — the same label live uses for the
   publication date below. */
/* No line-height either — it follows the body ratio, 1.65 above 690 and 1.75
   below, which live matches exactly (16.5px then 17.5px at 10px). */
.media-card-meta {
  max-width: none;
  margin: 0;
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 1.3px;
  text-transform: uppercase;
  color: var(--white);
}

.media-card-body a {
  color: inherit;
  text-decoration: none;
}

.media-card-body a:hover {
  color: inherit;
}

/* ── Publications and Cited Works ── */

.pubs {
  max-width: none;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* 45% and 75% basis against a 7.04px column gap. They sum past 100% and shrink
   proportionally, which is what lands them at 280.4 and 462.6 in a 750 panel —
   carried as the basis values so they stay right at every width. */
.pub-row {
  display: flex;
  gap: 27px 7.04px;
}

.pub-media {
  flex: 0 1 45%;
  padding-right: 16px;
  display: flex;
}

/* CONTAIN, not cover: these are logos and covers, and cropping them is wrong. */
.pub-media img {
  width: 100%;
  height: auto;
  object-fit: contain;
}

.pub-body {
  flex: 0 1 75%;
  align-self: center;
}

/* 781, WordPress's own column-stacking breakpoint — not one of the theme's
   999.98/689.98, and not the 768 the headings use. Bisected on live: at 782
   the row is nowrap with 45%/75% bases, at 781 it flips to wrap with both at
   100%. Missing it left /media/ 258.6px short at 420 with the row still side
   by side. The 16px padding-right on the image column and the centred body
   both survive the stack, so neither is reset here. */
@media (max-width: 781.98px) {
  .pub-row {
    flex-wrap: wrap;
  }

  .pub-media,
  .pub-body {
    flex-basis: 100%;
  }
}

.pub-body h2 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.1;
  color: var(--navy);
}

.pub-meta {
  max-width: none;
  margin: 7.04px 0 0;
  /* left, not the initial start — live sets it explicitly inline. Identical in
     LTR; matched so the property stops showing up as a difference. */
  text-align: left;
  font-size: 10px;
  font-weight: 600;
  /* Live's date element is an <h2>, so it inherits the global heading
     line-height and steps 1.25 -> 1.3 at 689.98. This is a <p> — better
     semantics than a second h2 per entry, and the same swap the card excerpts
     make — so the step has to be written out rather than inherited. */
  line-height: 1.25;
  letter-spacing: 1.3px;
  text-transform: uppercase;
  color: var(--navy);
}

@media (max-width: 689.98px) {
  .pub-meta {
    line-height: 1.3;
  }
}

.pub-body a {
  color: inherit;
  text-decoration: none;
}

.pub-body a:hover {
  color: inherit;
}


/* ── /start-here/ ──────────────────────────────────────────────────────────
 *
 * A prose page, and the widest one: its paragraphs run the full 1200px content
 * container, NOT the 710px reading column the rest of the site uses. Live puts
 * them at 990 inside a 1085 main at 1100 — that is min(90vw, 1200), the same
 * --w-normal every other block gets — so .page-body takes the container width
 * from main > * and its children fill it.
 *
 * Its GenerateBlocks rules come from style-192.css, and they introduce a FIFTH
 * breakpoint to the site: 767px, GenerateBlocks' own, alongside the theme's
 * 999.98/689.98, the content-h2 768 and WordPress's column 781. Five different
 * boundaries, none of them interchangeable.
 */
main > .page-body {
  max-width: min(var(--edge-space), var(--w-normal));
  margin-inline: auto;
  /* Live's div.ct-container-full carries padding: 10px 0 and holds everything
     below the hero. main's own padding-top is cancelled by the hero's -10px
     margin, so the gap under the hero has to come from the first block here —
     the third page that has needed it, after .section-panel's margin-top on
     /faith/ and the first heading's on /media/. main supplies the matching
     10px at the foot. */
  padding-top: 10px;
}

/* The children fill the container rather than falling back to the reading
   column — main > :is(h1,h2,p,…) does not reach them, since they are no longer
   direct children of main. */
.page-body > :is(h2, p, hr, div) {
  max-width: none;
}

/* Live's global content-h2 rhythm: 2.2em / 0.8em above 768, 2em / 0.7em at or
   below it. Confirmed on this page and on two post pages. The global h2 rule
   here sets only margin-bottom and no margin-top at all, which left every
   heading on this page 57.2px high and the error accumulating down the page.

   Scoped to .page-body rather than fixed on the global h2, because the global
   reaches the homepage, the opt-in and the post pages, none of which are built
   to parity yet — changing it there would be unverifiable in both directions. */
.page-body > h2 {
  margin-top: 2em;
  margin-bottom: 0.7em;
}

/* Live marks this one has-text-align-left, which computes to `left` rather
   than the initial `start`. Identical in LTR; matched so it stops showing up
   as a difference. */
.page-body > .align-left {
  text-align: left;
}

@media (min-width: 769px) {
  .page-body > h2 {
    margin-top: 2.2em;
    margin-bottom: 0.8em;
  }
}

/* #3A4F66 (palette colour 3) and margin 0 auto 27px — NOT /media/'s navy rule
   at 36/27. Live uses two different separators: .has-alpha-channel-opacity
   here, .has-text-color there. Same 100x2 box, different colour and top
   margin, so they cannot share a class. */
.page-body > .rule {
  width: 100px;
  max-width: 100px;
  height: 2px;
  margin: 0 auto 27px;
  border: 0;
  background: #3a4f66;
  color: #3a4f66;
}

/* .gb-element-4da3e1c3 — two columns, 1em both gaps, one column at 767. */
.page-body > .split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1em;
  /* Live wraps the grid in a .wp-block-group that carries the standard 27px
     content spacing. The grid itself has margin 0, so without this the next
     separator and everything after it sat 27px high. */
  margin-bottom: 27px;
}

@media (max-width: 767px) {
  .page-body > .split {
    grid-template-columns: 1fr;
  }
}

/* .gb-media-aaf22edc — max-width 95% with width:auto, so the image is 95% of
   its column (400.5 of 421.7 at 1100), not the full width. And it stays
   INLINE: the global img rule here is display:block, which removes the
   baseline descender space live's inline image sits on and shortened the
   column. Both were worth 21.2px of width and 14.1px of height. */
.page-body > .split img {
  display: inline;
  /* middle, not the initial baseline: on an inline image this moves it off
     the text baseline and changes the line box height it sits in. */
  vertical-align: middle;
  max-width: 95%;
  width: auto;
  height: auto;
  object-fit: cover;
}

/* .gb-element-988955bf — the tinted Foundational panel. Note max-width 1150,
   which is --gb-container-width and NOT the 1200 the prose uses, and that live
   gives it no bottom margin: it is the last block on the page. */
.page-body > .foundational-panel {
  max-width: 1150px;
  margin: 0 auto;
  padding: 20px;
  border-radius: 25px;
  background: var(--surface);
}

/* GenerateBlocks' 767, not the theme's 689.98. */
@media (max-width: 767px) {
  .page-body > .foundational-panel {
    padding-top: 5px;
  }
}

/* .gb-text-2fc96f6a — centred with a 10px top pad, on top of the same
   2.2em/0.8em content-h2 rhythm that steps at 769. */
.page-body > .foundational-panel > h2 {
  padding-top: 10px;
  text-align: center;
  margin-top: 2em;
  margin-bottom: 0.7em;
}

@media (min-width: 769px) {
  .page-body > .foundational-panel > h2 {
    margin-top: 2.2em;
    margin-bottom: 0.8em;
  }
}

.page-body > .foundational-panel > p {
  margin: 0 0 27px;
}

/* The SAME .rows markup, styled differently from /faith/ and /health/ — live
   gives this page's foundational list its own scale, and reusing the listing
   pages' values made every row the wrong height (151.8 and 132.4 here against
   live's uniform 146.4). Measured at 1100 on an 821.3 panel:

     image        3:2, not the 1:1 the listing pages use (170.8 square there)
     media link   margin-bottom -10px
     title        26px / 32.5, margin-top -15px   (20px on /faith/)
     date         18px / 23.4, margin-top 3px
     excerpt      18px / 23.4, margin-top 12px
     list         padding-bottom 16px

   The -15px and -10px are live's own inline styles on the block; they are
   pulls, not spacing, and dropping them alone accounts for 25px a row. */
.page-body .rows {
  padding-bottom: 16px;
}

/* Live's columns are are-vertically-aligned-center, so BOTH columns centre in
   the row. It only shows in a row whose text runs taller than its image —
   there is one at 800 — where the image dropped 28.7px on live and stayed put
   here. Invisible at 1100 and 1920, where the image column is always the
   taller of the two and centring is a no-op. */
.page-body .rows > li {
  align-items: center;
}

.page-body .rows .row-media img {
  aspect-ratio: 3 / 2;
}

/* 24 ABOVE and -10 BELOW, not a net +14 below.
   
   Live's <a> is inline-level inside a block column, so it sits in a line box
   whose ascent puts the image 24px down, and its own margin-bottom:-10px lets
   it hang 10px past the column. Walked on live: image top=24.00, height
   132.38, column 146.38 — so 24 + 132.38 - 10.
   
   An earlier pass here collapsed that to margin-bottom:14px. The row came out
   the right HEIGHT and the image sat 24px too high inside it, which a
   height-only check cannot see. The distribution is the visible part. */
.page-body .rows .row-media {
  margin-top: 24px;
  margin-bottom: -10px;
}

/* 26px above 690 and 24px below — the global heading step, which live follows
   here (32.5 then 31.2 against a 1.25/1.3 line-height). Pinning 26/1.25 held
   the title a size too large on phones and left every stacked row 20px short.
   line-height is deliberately absent: .rows h2 already steps it. */
.page-body .rows h2 {
  margin: -15px 0 0;
  font-size: 24px;
}

@media (min-width: 690px) {
  .page-body .rows h2 {
    font-size: 26px;
  }
}

/* 21.4px of leading, SPLIT — not all at the bottom.
   
   Live's text column runs 21.4 taller than its children sum to, another
   line-box remainder of its block-and-inline markup. Putting the whole 21.4
   below gave the column the right height with its text 10.7px too high, the
   same distribution error the media column had. Walked on live at 1100: the
   title sits 6.84 from the column's top, not -3.84.
   
   Split evenly because the column is vertically centred in the row
   (is-vertically-aligned-center on live, align-self: center here), so the
   leading falls either side. */
.page-body .rows .row-body {
  padding: 10.7px 0;
}

.page-body .rows .row-meta {
  margin: 3px 0 0;
  font-size: 18px;
  line-height: 1.3;
}

.page-body .rows .row-excerpt {
  margin: 12px 0 0;
  font-size: 18px;
  line-height: 1.3;
}

/* The opt-in is centred on this page — .gb-element-2178dee4 sets text-align,
   align-items and justify-content to center. */
.page-body > .optin {
  text-align: center;
}


/* ── homepage: the feed container ──────────────────────────────────────────
 *
 * Live's .gb-element-569ee669 holds the separator, the opt-in and the posts
 * panel together at max-width 1125. Without it the opt-in was a direct child
 * of main and ran the full 1905px against live's 1125 — .optin carries
 * max-width:none for /start-here/, where it sits inside .page-body instead. */
main > .feed {
  max-width: min(var(--edge-space), 1125px);
  margin-inline: auto;
}

/* 100x2 at #3A4F66 with margin 16px auto 7.04px — live's own values here, and
   NOT the 0/27 the same separator uses on /start-here/. */
.feed > .feed-rule {
  width: 100px;
  max-width: 100px;
  height: 2px;
  margin: 16px auto 7.04px;
  border: 0;
  background: #3a4f66;
  color: #3a4f66;
}

/* Inside the feed the opt-in fills the container, as live's does. */
.feed > .optin {
  max-width: 1200px;
  margin: 0 auto 27px;
}

/* Live's homepage panel has NO padding and caps at 1150 — /start-here/'s copy
   of the same class has padding 20px and caps at --gb-container-width. Same
   class name, two stylesheets, two sets of values. */
main > .feed > .panel {
  max-width: 1150px;
  padding: 0;
  /* The tint and the radius are live's, and this box never got them: they are
     on `main > .panel`, and the homepage's panel is a child of .feed, so that
     selector does not match it. The box was already the right 1125x966 -- it
     was just drawing on white where live draws a rounded #f2f5f7 card behind
     the whole recent-work list. */
  background: var(--surface);
  border-radius: 25px;
}


/* Live's "In Case We Haven't Met" is a tinted FULL-BLEED band
   (.gb-element-e0e47f5d) wrapping a 1250px container (.gb-element-a8edb87d),
   exactly like "My Top Three" above it. This build had the container alone,
   with no background at all — a whole section missing its tint. The band
   supplies the 20px bottom padding live puts on the inner container; the net
   is the same and it keeps .band's one model. */
.band--about > .about {
  max-width: 1250px;
  margin-inline: auto;
  padding: 0 28px;
}


/* The about CTA specifically is INLINE, as all three of live's buttons are:
   display:inline with the inherited 29.7px line-height, which makes it exactly
   48.00 tall and — the part that matters — contributes a 29.7px LINE BOX to
   the section rather than its whole 48.3px block. Wrapped in a <p> as an
   inline-block it put this section 18.6px over live.

   Scoped rather than applied to .btn globally: doing it globally moved the
   hero and band CTAs too, and took the page from 25.6px short to 62.8px. */
.band--about > .about > .btn {
  display: inline;
  line-height: inherit;
}

/* ---------------------------------------------------------- back to top --- */

/* Live ships Blocksy's back-to-top and this build had none. Read off
   blocksy/static/bundle/back-to-top.min.css and blocksy/css/global.css:
     position:fixed; z-index:110; right/bottom 25px; padding:12px
     --theme-icon-size:12px, so 12+12+12 = 36px square
     background var(--top-button-background-color, --theme-palette-color-3)  #3A4F66
     hover       var(--top-button-background-hover-color, --palette-color-4)  #192a3d
     icon fill   --theme-icon-color #ffffff  (global.css sets hover to #ffffff too)
     border-radius var(--theme-border-radius, 2px)   [data-shape=square]
     box-shadow  var(--theme-box-shadow)  0px 10px 20px rgba(41,51,61,.1)
     hidden:  opacity 0, visibility hidden, translate3d(0,15px,0)
     .ct-show: opacity 1, visible, translate3d(0,0,0)
     transition opacity/color/background/transform/visibility .3s ease
   Live's element also carries ct-hidden-sm, and main.min.css hides that below
   689.98 -- so there is deliberately NO back-to-top on a phone, which is also
   why it raises no tap-target question at 390/360.

   THE ONE VALUE NOT IN THE CAPTURE is the scroll distance that reveals it:
   Blocksy sets .ct-show from a lazy-loaded script that fetch-reference.sh did
   not fetch. One viewport is used here, and it is consistent with the two
   observations available -- in an 850px frame live's button was absent at
   scrollY 850 and present at 1700. Treat it as observed, not measured. */
.to-top {
  position: fixed;
  right: 25px;
  bottom: 25px;
  z-index: 110;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px;
  border-radius: 2px;
  background: #3a4f66;
  box-shadow: 0 10px 20px rgba(41, 51, 61, 0.1);
  opacity: 0;
  visibility: hidden;
  transform: translate3d(0, 15px, 0);
  transition:
    opacity 0.3s ease,
    background 0.3s ease,
    transform 0.3s ease,
    visibility 0.3s ease;
}

.to-top svg {
  width: 12px;
  height: 12px;
  fill: #fff;
}

.to-top:hover {
  background: var(--navy);
}

.to-top.is-shown {
  opacity: 1;
  visibility: visible;
  transform: translate3d(0, 0, 0);
}

/* ct-hidden-sm. Live has no back-to-top below 690 and neither does this. */
@media (max-width: 689.98px) {
  .to-top {
    display: none;
  }
}
