/* GENERATED by scripts/build-thankyou-production.mjs — DO NOT EDIT BY HAND.
   Byte-for-byte the approved candidate's stylesheet, plus the stagger keys that
   could not ride on the element. Class names survive kses, which is why the
   shipped page is styled by the IDENTICAL css rather than a translation of it. */
/* ═══════════════════════════════════════════════════════════════════════════
   THANK-YOU — THE POST-DONATION SURFACE

   THE ONE DECISION EVERYTHING ELSE FOLLOWS FROM

   The campaign page is dark because it is ARGUING. Dark is the register of
   stakes and urgency — "this matters and it is at risk". The reader of this
   page has already agreed. Keeping them in that room asks them to keep
   worrying about a decision they have made, which is why the previous version
   of this page felt like a dimmer copy of the one before it.

   So this surface is PAPER, with exactly ONE dark moment, and that moment is
   the advocacy ask. The inversion is not decoration: on a light page the single
   dark band is the strongest thing on the screen, so the eye arrives at the
   primary action without a badge or a label doing the work. Hierarchy carries
   it instead.

   WHAT IS SHARED WITH THE CAMPAIGN IS THE DISCIPLINE, NOT THE LOOK. Same
   spine arithmetic, same reaches, same type families, same spacing scale, same
   gold. Different emotional register. Siblings, not clones.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── GROUND ─────────────────────────────────────────────────────────────
     Two paper values, not one: a page ground and one step warmer for the
     closing band, so the last section separates without a rule or a box.

     THE VALUES ARE THE CAMPAIGN'S OWN, NOT A PAIR TUNED FOR THIS PAGE.
     campaign.css ships `--paper: #fbf6e8` / `--warm: #f6efdc`, and the approved
     Convergence A renders on exactly those (dna.css copies them verbatim). This
     page previously shipped #f6f2e9 / #efe9dc — a cooler, greyer pair that is
     invisible in isolation and unmistakable beside the landing page and beside
     the approved composition. A donor crosses from one to the other in a single
     redirect; the paper must not change under them. */
  --paper:        #fbf6e8;
  --paper-warm:   #f6efdc;
  --stage:        #14120e;   /* the campaign's dark stage — used ONCE, for advocacy */
  --stage-2:      #1c1913;

  --ink:          #23221f;   /* campaign.css verbatim — see the note on --paper   */
  --ink-soft:     #4a453c;   /* secondary prose                                   */
  --ink-faint:    #655e53;   /* labels and legal — AA on both papers (5.7 / 5.3)   */
  --on-dark:      #f4efe2;
  --on-dark-mut:  rgba(244, 239, 226, .78);

  --gold:         #daa520;   /* the campaign's gold, unchanged                    */
  /* THE CAMPAIGN'S OWN --gold-deep, not a new value invented for this page.
     The first pass used #a87a12, which reads 3.44:1 on paper — and "Your gift
     is in." renders at 23.2px on a phone, 0.8px under WCAG's large-text
     threshold, so it needs the full 4.5:1 and did not have it. The design
     system already contained a gold that does: 5.47:1. */
  --gold-deep:    #7a5e0c;
  /* The ONLY gold that may carry text on paper (campaign.css verbatim);
     it is what the approved seal is set in. 5.9:1 on --paper. */
  --gold-text-light: #5a4a12;
  --sand:         #eace84;

  /* WRITTEN WITH THE LEADING ZERO, like campaign.css. The computed value of a
     custom property is its TEXT: `.14` and `0.14` paint identically and compare
     unequal, so a token diff that is really a formatting difference would sit in
     the parity matrix forever and teach everyone to ignore a red row. */
  --rule:         rgba(35, 34, 31, 0.14);
  --rule-strong:  rgba(35, 34, 31, 0.26);
  --rule-dark:    rgba(244, 239, 226, .16);

  --radius-md:    10px;
  --radius-lg:    16px;
  --radius-pill:  999px;

  /* THE CAMPAIGN'S SPACING SCALE, UNCHANGED. Sharing the steps is most of what
     makes two surfaces feel drawn by one hand. */
  --space-1: 4px;  --space-2: 8px;   --space-3: 12px; --space-4: 16px; --space-5: 20px;
  --space-6: 24px; --space-8: 32px;  --space-12: 48px; --space-16: 64px;
  --space-24: 96px;
  --section-pad-y: clamp(56px, 9vw, 128px);

  --dur-fast: 120ms; --dur-base: 200ms; --dur-slow: 400ms;
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --tap: 44px;

  /* ── THE SHARE DIALOG'S TOKENS STAY ON-DARK ───────────────────────────────
     The dialog is a dark surface and it always was; only the PAGE became paper.
     The first pass of this reset pointed --quiet and --muted at the new paper
     inks (#655e53, #4a453c), which are correct on paper and unreadable on the
     dialog's near-black ground — 16 contrast violations across every view of
     the share product, introduced by a page-level colour decision leaking into
     a component that never changed.

     They keep the values they had. A token named for a ROLE has to be defined
     per ground, not once for the document. */
  --ink-deep:   var(--stage);
  --cream:      var(--on-dark);
  --rule-soft:  rgba(244, 239, 226, .20);
  --quiet:      #949084;
  --muted:      #b3ada0;
}

/* ── THE SPINE ──────────────────────────────────────────────────────────────
   COPIED FROM THE CAMPAIGN, NOT APPROXIMATED. campaign.css computes its
   narrative left edge as

     max(--gutter, (100% - --container-content)/2 + --gutter)

   which lands at 144px on a 1440 viewport and collapses to the bare gutter
   below 1200. This page used `max-width:1180px; padding:0 clamp(20px,4vw,40px)`
   and therefore hung its content at 170px — 26px right of the campaign, on
   every wide screen, for no reason anybody chose. Two surfaces in one campaign
   should not have two left edges.

   The reaches come across with it, so a column here is the same column there. */
.ty {
  --container-content: 1200px;
  --gutter: 24px;
  --gutter-mobile: 16px;
  --spine: max(var(--gutter), calc((100% - var(--container-content)) / 2 + var(--gutter)));
  --reach-read: 660px;   /* prose measure                                  */
  --reach-text: 780px;   /* headline measure                               */
  --reach-band: 940px;   /* a wide element that is still not full-bleed    */
  --reach-full: 1152px;  /* the full narrative width at 1440               */

  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
.ty *, .ty *::before, .ty *::after { box-sizing: border-box; }

/* THE GLOBAL HIDDEN GUARD. `hidden` is a presentation hint that ANY `display`
   declaration silently outranks — and this document is full of grids and
   flexes that are toggled with the attribute: the share dialog's panels, its
   alternate views, the copy fallback, the donor's momentum reading.

   Dropping this one line while rewriting the page's styles left six hidden
   elements rendering inside the share dialog, which inflated it past the
   viewport on a phone and put two primary actions on screen at once. It is
   `!important` because it has to beat a component's own layout rule, which is
   exactly the situation the attribute exists for. */
[hidden] { display: none !important; }

.ty__wrap { padding-inline: var(--spine); }

/* THE CAMPAIGN STEPS TO ITS MOBILE GUTTER AT 768 AND SO DOES THIS.
   Measured live against post 3248: the two surfaces agreed at 1440, 1200 and
   1024 (144 · 24 · 24) and then parted at 768 — campaign 16, thank-you 24 —
   because this sheet had no mobile gutter at all. One campaign, one left edge,
   at every width and not merely at the wide ones. */
@media (max-width: 768px) {
  .ty { --gutter: var(--gutter-mobile); }
}

.ty h1, .ty h2, .ty h3 {
  font-family: Fraunces, "Iowan Old Style", Georgia, serif;
  font-weight: 500;
  letter-spacing: -.015em;
  line-height: 1.04;
  margin: 0;
  text-wrap: balance;
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE CLOSER — page composition (approved 2026-09-16)

   FIVE BLOCKS. The donor's name, their amount and Joe share ONE section
   separated only by air; then one sentence of collective context, one inverted
   band for sharing, one mount for following, and a colophon.

   THE VERTICAL BUDGET IS THE DESIGN. Every value below was chosen against a
   measured target — the name, the gift AND Joe must all sit inside the first
   viewport at 1440x900 and at 390x844 — rather than chosen for rhythm and
   measured afterwards. Information distance is the acceptance metric for this
   surface, not page height.

   GOLD MEANS ONE THING HERE: the share control, plus the confirmation tick.
   It is not on the donor's figures and not on Joe. On the previous composition
   the donor's own total and the share button were both gold, 300px apart, which
   tied "your money" to "press this".
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── MASTHEAD · RECONCILED TO THE CAMPAIGN'S OWN ───────────────────────────
   THIS IS THE LANDING PAGE'S MASTHEAD, MEASURED, NOT A SECOND BRANDED HEADER.
   Live values off post 3248 (campaign.css is another terminal's file and was
   NOT edited — see the reconciliation ledger at the end of this sheet):

       .camp .nav__brand   --tlm-wordmark: 44px
       .camp .nav__logo--onLight   height = 44 x 1.4325 = 63.03px   (dark tagline lockup)
       .camp .nav__row             padding-block: 14px  ->  91px bar
       .camp .nav.is-solid         rgba(251,246,232,.92) + blur(10px) + 1px --rule

   This page shipped the BARE wordmark at 30px in a 63px bar. A donor crosses
   from the campaign to here in ONE redirect, and the institution changed size
   and artwork under them. The paper ground means this surface is permanently in
   the campaign nav's `is-solid` state, so that is the state it copies.

   THE COST, STATED: the tagline's cap-height here is 44 x 0.1372 = 6.0px, under
   the 7px legibility floor that originally put the bare wordmark here. Product
   unity outranks that floor — this is the identical lockup at the identical
   size the donor saw thirty seconds ago — and the 264px second copy that used
   to sit in the colophon is gone, so the lockup is not rendered twice. */
.ty-nav {
  padding: 14px 0;
  background: rgba(251, 246, 232, .92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
}
.ty-nav__row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-5); }
.ty-nav__home { display: block; line-height: 0; }
.ty-nav__logo { height: 74px; width: auto; max-width: 62vw; display: block; }
@media (max-width: 1024px) { .ty-nav__logo { height: 57px; } }
@media (max-width: 768px)  { .ty-nav__logo { height: 49px; } }

/* THE SEAL is hidden until the server has verified the gift — see .ty--unverified.

   THE APPROVED TREATMENT IS A GOLD EM-DASH AND GOLD TEXT, NOT A BADGE.
   This page previously shipped a 17px gold disc with a tick knocked out of it.
   Two things are wrong with that. The campaign has NO CIRCLES ANYWHERE — a
   round token is the one shape its art direction does not contain — and a
   filled badge is a louder object than the approved seal, which is a quiet
   gold rule and four small words in the corner opposite the wordmark. The
   approved composition sets it as `— GIFT CONFIRMED`, and the dash is drawn in
   CSS content, which survives both the build's SVG strip and wp_kses_post. */
.ty-confirm { display: inline-flex; align-items: baseline; gap: 0; margin: 0; }
.ty-confirm__tick { display: none; }
.ty-confirm::before { content: "\2014\00a0"; color: var(--gold); }
.ty-confirm,
.ty-confirm__t {
  font-size: .66rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--gold-text-light); font-weight: 500;
}
.ty--unverified .ty-confirm { display: none; }

/* ══ THE CLOSER ════════════════════════════════════════════════════════════ */
/* The opening movement carries the greeting, the equation and Joe's media, so
   it takes the landing's paper-chapter breath at its low end. The TOP is
   smaller than the bottom by design: the masthead is already above it, and a
   greeting pushed a full chapter's padding down the page delays the one thing
   this donor came to read. */
.ty-close { padding-block: clamp(32px, 6vh, 72px) clamp(44px, 7vh, 88px); }

/* THE NAME.
   SHOUTING A PERSON'S NAME IS NOT GRATITUDE. The site ships a global
   `h1 { text-transform: uppercase }` that lives in none of the stylesheets this
   page owns — it is a bare author rule from the site's global CSS, findable only
   through CDP. The override below is (0,3,0) and beats it, while the money and
   section headings keep the campaign's display caps. */
.ty .ty-head { margin: 0; max-width: var(--reach-band); }
.ty .ty-head .ty-head__t {
  display: block;
  text-transform: none;
  font-family: Fraunces, "Iowan Old Style", Georgia, serif;
  font-weight: 500; font-optical-sizing: auto;
  font-size: clamp(2.5rem, 5.6vw, 4.6rem);
  line-height: .98; letter-spacing: -.03em;
  text-wrap: balance; max-width: 15ch;
  color: var(--ink);
}
/* NO VERIFIED GIFT IS NOT A DIFFERENT PRODUCT.
   This rule used to shrink the headline to 2.8rem and the paragraph below it —
   "This page belongs to a gift. It shows a contribution only to the person who
   made it…" — became the largest thing on the screen. The page then WAS that
   disclaimer: a legal notice with a share button under it. The disclaimer is
   still here and still true; it is now a NOTE, and the headline is the
   campaign's own, at the campaign's own display scale.

   The em is the landing hero's em — italic 400, `.camp .hero__headline em` —
   with the ONE substitution the ground forces. That hero sets it in --gold
   (#daa520) over a dark photograph; here the same value reads 2.07:1 on paper
   and axe fails it at 73px. The campaign already answers this: --gold-text-light
   (#5a4a12) is its paper gold and carries `.camp .chapter__num` and the drop
   caps on exactly this ground, at 5.9:1. Same role, same system, correct
   ground — which is the whole point of deriving rather than eyeballing. */
.ty--unverified .ty-head .ty-head__t { max-width: 13ch; }
.ty .ty-head .ty-head__t em { font-style: italic; font-weight: 400; color: var(--gold-text-light); }

/* THE DISCLAIMER, DEMOTED TO A NOTE — the gold hairline this page already uses
   for the cap explanation (.ty-note), because both say the same kind of thing:
   here is a fact about what you are looking at. */
.ty-unv {
  display: none; margin: clamp(18px, 2.6vh, 26px) 0 0;
  padding-left: var(--space-4); border-left: 2px solid var(--gold);
  color: var(--ink-soft); font-size: .93rem; line-height: 1.62; max-width: var(--reach-read);
}
.ty--unverified .ty-unv { display: block; }

/* ── THE GIFT, AS ONE LINE OF TYPE ─────────────────────────────────────────
   Three terms on one baseline with the label ABOVE each figure, so the line
   scans as a sentence rather than as a table. No rules, no cells, no chart. */
.ty-gift {
  display: flex; align-items: flex-end; flex-wrap: wrap;
  gap: clamp(12px, 2vw, 30px);
  margin: clamp(20px, 3.2vh, 34px) 0 0;
}
.ty-gift__term { display: flex; flex-direction: column; gap: .3em; }
.ty-gift__k {
  font-size: .64rem; letter-spacing: .15em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 600; line-height: 1.2;
}
.ty-gift__n {
  font-family: Fraunces, "Iowan Old Style", Georgia, serif;
  font-weight: 500; font-optical-sizing: auto;
  font-size: clamp(1.5rem, 2.8vw, 2.3rem); line-height: .92; letter-spacing: -.026em;
  font-variant-numeric: tabular-nums; color: var(--ink-soft);
}
/* THE SUM IS THE ONLY TERM IN FULL INK, and it is still a step below the
   greeting: on a page whose subject is gratitude the money must not become the
   joint protagonist. */
.ty-gift__term--sum .ty-gift__n { color: var(--ink); font-size: clamp(1.7rem, 3.2vw, 2.7rem); }
.ty-gift__op {
  font-size: clamp(1.1rem, 1.9vw, 1.5rem); color: var(--ink-faint); opacity: .55;
  padding-bottom: .22em; font-weight: 300;
}
/* WITH NO MATCH LEFT the three-term line is a falsehood with a zero in it.
   "$500 + $0 = $500" is arithmetically true and rhetorically a lie: the figure
   exists to show leverage, and rendering it when there is none tells a donor
   their gift was doubled by nothing. The runtime sets data-verified="1" only
   when there is a match to show. */
.ty-gift:not([data-verified="1"]) .ty-gift__op,
.ty-gift:not([data-verified="1"]) .ty-gift__term:nth-of-type(2),
.ty-gift:not([data-verified="1"]) .ty-gift__term--sum { display: none; }
.ty--unverified .ty-gift { display: none; }

.ty-note {
  margin: clamp(16px, 2.4vh, 22px) 0 0; padding-left: var(--space-4);
  border-left: 2px solid var(--gold);
  font-size: .93rem; line-height: 1.62; color: var(--ink-soft); max-width: var(--reach-read);
}
.ty-note.is-off { display: none; }

/* ── JOE ───────────────────────────────────────────────────────────────────
   A hairline and air, not a band. NOT a quotation — nothing on this page is
   presented as Joe's words; see the guardrail note in index.html. */
/* MARGIN, NOT `.ty__wrap` PADDING. The block carried both `ty__wrap` (which adds
   the spine as PADDING) and `max-width: --reach-band`, so its 940px box had the
   spine subtracted from the inside: the content box came out at 652 and Joe's
   text column at 482 rather than 770. The statement then wrapped to five lines
   and the block stood 46px taller than the approved composition.

   The approved block hangs on the spine with a MARGIN and caps at the band, so
   the cap describes the content and not the content plus its own gutters. */
/* THE PHOTOGRAPH, ALONE. This was a two-column block — film card beside his
   statement — and the statement has moved above the equation. A one-column grid
   rather than a deleted one: the rule, the spine and the vertical rhythm are
   the same object, and only the second track is gone. */
/* ── THE PAYOFF ROW — JOE AND WHAT THE GIFT MOVED ──────────────────────────
   The film card used to carry the spine margin, the hairline and the 940px cap
   ITSELF, while being 480px wide. A rule that spans 940 and a block that fills
   480 is a promise the composition does not keep, and the 460px of paper to the
   right of Joe was the largest unassigned field on the page. The thing that
   belonged in it was directly below, behind a ground change: the ledger.

   The rule, the spine and the cap move up here, to the object that actually
   fills them. Both halves keep their own internal composition.

   `flex-wrap` IS THE RESPONSIVE RULE AND THE DEGRADATION RULE AT ONCE. Either
   half can legitimately be absent — Joe when the gift was not matched, the
   ledger when /totals is unreadable or JS never ran — and in flex the survivor
   takes the row rather than sitting beside a hole. The same mechanic turns the
   row into a stack below ~900, in the locked order. */
.ty-payoff {
  display: flex; flex-wrap: wrap; align-items: flex-start;
  gap: clamp(26px, 3.4vw, 56px);
  margin: clamp(24px, 3.6vh, 40px) var(--spine) 0;
  padding-top: clamp(22px, 3.2vh, 32px);
  border-top: 1px solid var(--rule);
  max-width: var(--reach-band);
}
/* THE CARD GIVES UP 60px TO GAIN A PARTNER. 480 was the largest it could be
   while still reading as a card somebody composed rather than a webcam still
   somebody enlarged; that ceiling was set while it stood ALONE, where its only
   job was not to look stranded. Beside the ledger it is no longer at risk of
   looking stranded, and 420 leaves the figures a measure they can actually set
   in. The floor from the original reasoning (230–340 = thumbnail) is untouched. */
.ty-joe {
  flex: 0 1 420px; min-width: 0;
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: clamp(18px, 2.6vw, 34px); align-items: start;
}
/* ── JOE'S MEDIA MOMENT IS THE CAMPAIGN'S FILM CARD ────────────────────────
   THE COMPONENT IS `.camp .herofilm`, NOT A NEW ONE, and the asset is the
   campaign's own poster frame already hosted in uploads — nothing is uploaded,
   nothing is invented, and there is exactly ONE video implementation in this
   product (the runtime shell campaign-film.js on post 3248). Like the landing
   hero's card, this is A LINK TO THAT FILM, not a second player: kses strips
   <iframe> from this document anyway, and a donor who wants the film should
   land on the surface that has it.

   WHY THIS REPLACED THE 4:5 PORTRAIT. `joe-stocker.jpg` is itself a crop of
   this exact poster frame, taken above the burned-in lower-third — it existed
   because the page had no media component to put a real one in. It also
   rendered only for a VERIFIED donor, so the production base page had no Joe
   media at all. The film card is the same man, the same source frame, a public
   campaign asset, and it is legible in both states.

   The geometry is the landing card's, re-grounded: that one sits on the dark
   hero, this one on paper, so the frame's hairline and the poster's grade
   answer this ground instead of that one. The play disc is copied verbatim,
   borders-not-SVG, so it survives kses and renders with images and JS off. */
/* SIZED FOR A COLUMN IT NO LONGER SHARES. At 230-340px this card was the
   narrow half of a two-column block, balanced by Joe's statement beside it.
   The statement has moved above the equation and the card was left alone at
   thumbnail scale against 1,200px of empty paper — a stranded element reads as
   a mistake, not as restraint. It takes the READING MEASURE now: the same
   692px the statement above is set to, so the two Joe beats share one edge and
   one width down the page. */
.ty-joe__film {
  display: block; text-decoration: none; color: var(--ink);
  /* TWO WRONG ANSWERS BRACKET THIS ONE. At the 230-340px it inherited from the
     two-column block it was stranded against 1,200px of empty paper. At the
     692px reading measure it dominated the page — and the source is a WEBCAM
     FRAME with a burned-in gold lower-third, which is forgiving at moderate
     size and unflattering at display size. This is the largest it can be while
     still reading as a card someone composed rather than a still someone
     enlarged. */
  width: min(480px, 100%);
}
.ty-joe__frame {
  position: relative; display: block; overflow: hidden;
  border-radius: var(--radius-md);
  border: 1px solid var(--rule-strong); background: var(--paper-warm);
  box-shadow: 0 10px 26px rgba(35, 34, 31, .14);
}
.ty-joe__poster {
  width: 100%; aspect-ratio: 16 / 9; height: auto;
  object-fit: cover; object-position: 50% 42%; display: block;
  filter: saturate(.9) contrast(1.02);
  transition: filter var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}
/* ── THE PLAY MARK SITS IN THE CORNER, NOT ON HIS FACE ─────────────────────
   `inset: 0; margin: auto` centres it, and the source frame is a webcam shot
   with Joe centred — so the one affordance on the card was landing squarely on
   the face of the person the card exists to introduce. On a page thanking
   somebody for a gift HE matched, the photograph is the point and the control
   was covering it.

   BOTTOM-RIGHT IS THE ONLY CLEAR QUADRANT, and that is arithmetic rather than
   preference. The frame carries a burned-in gold lower-third ("JOE STOCKER /
   FOUNDER, CEO of PATRIOT CONSULTING") running from the left edge to ~76% of
   the width along the bottom, and Joe himself holds the middle. Anchored 12px
   from the right, a 44px disc begins at 84–88% of the width at every size this
   card takes (358px on a phone through 460px at 768) — clear of the caption bar
   at its narrowest, because the bar is part of the IMAGE and scales with it.

   `inset: auto 12px 12px auto` replaces `inset: 0` — all four sides are still
   stated, so nothing is left to the host. */
.ty-joe__play {
  position: absolute; inset: auto 12px 12px auto;
  width: 44px; height: 44px; border-radius: 50%;
  background: rgba(15, 13, 10, .62); border: 1.25px solid var(--gold); backdrop-filter: blur(2px);
  transition: background var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out);
}
.ty-joe__play::after {
  content: ""; position: absolute; inset: 0; margin: auto; width: 0; height: 0;
  border-style: solid; border-width: 6px 0 6px 11px;
  border-color: transparent transparent transparent var(--gold);
  transform: translateX(1px);
}
.ty-joe__meta { display: block; margin-top: 10px; }
.ty-joe__meta b {
  display: block; font-family: Fraunces, "Iowan Old Style", Georgia, serif;
  font-weight: 500; font-size: .95rem; line-height: 1.2; color: var(--ink);
}
.ty-joe__meta span {
  display: block; margin-top: 3px; font-size: .7rem; letter-spacing: .07em;
  text-transform: uppercase; font-variant-numeric: tabular-nums; color: var(--ink-faint);
}
.ty-joe__film:hover .ty-joe__poster,
.ty-joe__film:focus-visible .ty-joe__poster { filter: saturate(1) contrast(1); transform: scale(1.03); }
.ty-joe__film:hover .ty-joe__play { background: var(--gold); transform: scale(1.06); }
.ty-joe__film:hover .ty-joe__play::after { border-left-color: var(--stage); }
/* THE APPROVED WEIGHT, WITHOUT THE APPROVED VOICE.
   Convergence A set this in italic Fraunces at ~1.5rem with quotation marks —
   because there it was a quotation. It is not one here: Joe has supplied no
   post-donation words, and italic-serif-in-quotes is the typography of speech.

   The first release answered that by dropping it to body copy, which collapsed
   the beat: the Joe moment stopped being a moment. So the SCALE and the FACE of
   the approved composition come back — display type, generous measure, room
   around it — and only the marks of speech stay away. */
.ty-say__t {
  margin: 0;
  font-family: Fraunces, "Iowan Old Style", Georgia, serif;
  font-weight: 400; font-optical-sizing: auto;
  font-size: clamp(1.14rem, 1.75vw, 1.52rem);
  line-height: 1.5; letter-spacing: -.004em;
  /* 692px, MEASURED off the approved render rather than expressed in `ch`.
     `ch` resolves against the element's own font, and the approved block is set
     in ITALIC Fraunces whose `ch` is far wider than the roman used here: the
     same "44ch" computed to 692px there and 482px here, which wrapped the
     sentence to five lines and made the Joe block 46px taller than approved.
     A measured width reproduces the approved line length exactly. */
  color: var(--ink); max-width: 692px;
}
.ty-say__t strong { color: var(--ink); font-weight: 600; }
.ty-say__by { margin: var(--space-3) 0 0; font-size: .84rem; color: var(--ink-faint); font-family: Inter, system-ui, sans-serif; }
.ty-say__by b { font-weight: 600; color: var(--ink); }
.ty-say__by span::before { content: " · "; }
/* JOE IS PUBLIC CAMPAIGN CONTENT AND SHIPS IN BOTH STATES.
   This rule used to read `.ty--unverified .ty-joe { display: none }`, on the
   reasoning that with no contribution there was nothing for him to be beside.
   That reasoning deleted the campaign's film, its matching partner and the only
   photograph on the page from every visit the server could not verify — which
   is the entire base production surface. The match offer is stated on the
   landing hero to people who have given nothing; it cannot be private here.

   WHAT IS STILL CONDITIONAL is the SENTENCE, not the block: the document ships
   the campaign's standing match clause, and the runtime replaces it with this
   donor's actual match state. The one state with no Joe moment is a VERIFIED
   gift that was not matched (main.js hides the block when match_cents <= 0),
   because there the participation did not happen. That is still correct. */

/* `.ty-receipt` IS GONE. It set nine words of payment-processor status in 11px
   caps alone in a 460px field of paper under Joe's film — the page's clearest
   case of a tiny technical element floating in a large unassigned field. The
   fact is carried three times already and better placed each time: GIFT
   CONFIRMED in the masthead, the donor's real figures in the equation, and
   "Secure payment by Stripe." in the colophon's legal line. The rule that the
   page may not PROMISE a receipt is unchanged — it is now kept by saying
   nothing rather than by saying something weaker. */

/* ══ WHAT IT MOVED — one sentence, no instrument ═══════════════════════════ */
/* ── NO GROUND OF ITS OWN, AND THAT IS THE CORRECTION ──────────────────────
   This block shipped on `--paper-warm`, which made it the third band in a page
   of six. But a ground change is a CHAPTER TURNING, and this is not a new
   chapter: the greeting, the equation, Joe and this ledger are one continuous
   reading — what the gift was, who matched it, and what it moved. Splitting the
   consequence off onto its own ground is precisely what made the page read as
   stacked horizontal sections rather than as a journey.

   So the page now changes ground exactly twice: paper (what happened) → stage
   (what happens next) → ink (the colophon). The interval below is an interval
   within a chapter, not a chapter gap — it is deliberately short of the
   48–100px the stage chapter takes, and the separation is carried by the
   ledger's own hairline and its display total instead of by a colour. */
.ty-moved { flex: 1 1 400px; min-width: 0; }
.ty-moved__h {
  text-transform: uppercase;
  font-size: clamp(1.25rem, 2.1vw, 1.7rem); line-height: 1.06; letter-spacing: -.01em;
  margin: 0 0 var(--space-4);
}
/* THE ACCESSIBLE READING, CARRIED OFF-SCREEN. The approved sentence is still
   the thing a screen reader hears; it is the LEDGER that is aria-hidden. Not
   `display:none`, which would remove it from the accessibility tree too. */
.ty-moved__sr {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap;
}

/* ══ THE MOVEMENT, AS A STATEMENT OF ACCOUNT ═══════════════════════════════
   A single line of prose lay across this field: "Before your gift the campaign
   had raised $51,382. Your gift added $50. It now stands at $51,432." Every
   figure in it is real and the sentence is approved — but a sentence asked to
   be a composition sets one long measure in a large empty band and reads as a
   caption somebody forgot to design.

   THREE ROWS, ONE HAIRLINE, NO BOX. Labels at the spine, figures aligned right
   on tabular numerals so the digits stack, and a rule above the last row — the
   form a statement of account has had for a century, and the reason it works is
   that the eye reads DOWN the figures rather than along a line of type. Nothing
   here is a card, a tile or a panel; remove the rule and the alignment and
   there is no object left, which is the test.

   Only the last row is at display scale. The reading the gift produced is the
   point; the two above it are what make that reading mean something. */
.ty-ledger {
  margin: clamp(16px, 2.2vh, 24px) 0 0;
  /* THE MEASURE IS THE COMPOSITION, AND THE SPAN IS THE INSTRUMENT.
     At 520px the rows huddled against the spine and left 700px of the content
     column unassigned — the "stranded in a big field" failure. At the band's
     own reach the labels stay on the spine, the figures hang on the right, and
     the gap between them is the thing the eye travels: that distance IS the
     statement of account. `--reach-band` is the landing's own token (940px),
     not a width chosen here. */
  /* THE MEASURE IS NOW THE COLUMN'S. `--reach-band` was this block's own cap
     when it was a full-bleed band; inside the payoff row the column IS the
     measure, and capping at 940 inside a 484px track would have done nothing
     except make the rule read as arbitrary. The gap the eye travels between
     label and figure is preserved — it is the track, not a fixed width. */
  width: 100%;
  display: grid; gap: clamp(10px, 1.4vh, 14px);
}
.ty-ledger__row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 24px; align-items: baseline;
}
.ty-ledger__k {
  font-family: Inter, system-ui, sans-serif;
  font-size: .82rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-faint); margin: 0;
}
.ty-ledger__n {
  margin: 0; justify-self: end;
  font-family: Fraunces, "Iowan Old Style", Georgia, serif;
  font-weight: 500; font-size: clamp(1.2rem, 1.9vw, 1.55rem); line-height: 1;
  letter-spacing: -.02em; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* The donor's own line is the one that is gold, because it is the one they did. */
.ty-ledger__row--add .ty-ledger__n { color: var(--gold-text-light); }
/* THE RULE SITS ABOVE THE TOTAL, not around the block. */
.ty-ledger__row--now {
  padding-top: clamp(12px, 1.6vh, 18px);
  border-top: 1px solid var(--rule);
}
.ty-ledger__row--now .ty-ledger__k { color: var(--ink); }
.ty-ledger__row--now .ty-ledger__n {
  font-size: clamp(2rem, 4vw, 3.1rem); font-weight: 500; color: var(--ink);
}
@media (max-width: 480px) {
  /* The label and the figure stop sharing a baseline before they collide. */
  .ty-ledger__row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .ty-ledger__n   { justify-self: start; }
}

/* THE LEDGER IS UNREADABLE ⇒ THE BLOCK IS REMOVED, not blanked. If we cannot
   read /totals we may not say where the campaign stands, and we must never
   leave a loading state on the screen of somebody who has just paid. The marker
   is set by the runtime unconditionally, so this can never depend on a selector
   quietly falling out of sync with the markup. */
.ty-moved[data-ledger="unavailable"] { display: none; }
/* The no-JS resting state — see the note in index.html. */
.ty-moved.is-off { display: none; }

/* ══ JOE, FIRST — THE HUMAN MOMENT BEFORE THE ARITHMETIC ═══════════════════
   Set at reading measure directly under the greeting, in the page's display
   face, at a size BELOW the h1 and ABOVE body copy. It is the second thing read
   and it must not compete with the name — so the weight comes from measure and
   leading, not from scale.

   The lead clause carries `<strong>`, which is where the match attribution
   lives; the rest is the campaign's plain statement of what the money buys. */
.ty-say { margin: clamp(18px, 2.6vh, 28px) 0 0; }
.ty-say.is-off { display: none; }
/* A LANDSCAPE HANDSET IS NARROW AND SHORT, and the short half is what bites:
   at 375px of height the greeting, Joe's statement, the equation and the
   primary control do not fit at display scale. The block keeps its role and
   gives up its scale — this is the only viewport where Joe's statement is set
   at body size, and the alternative is the one action the page wants falling
   below the fold. Width-only breakpoints cannot express this. */
@media (max-height: 460px) {
  .ty-say { margin-top: 12px; }
  .ty-say__t { font-size: 1rem; line-height: 1.42; }
  .ty-say__by { margin-top: 6px; font-size: .78rem; }
}

/* ══ CHAPTER RHYTHM — THE LANDING'S, MEASURED ═════════════════════════════
   THIS IS WHY THE PAGE READ AS STACKED SECTIONS. The grounds were already
   right: paper #fbf6e8, warm #f6efdc and stage #14120e are the landing's own
   three, alternating in the same way. What was wrong was the BREATH. Measured
   on the hosted landing, its chapters carry 72-136px of block padding; this
   page was running 37-58px. At that compression a ground change stops reading
   as a chapter turning and starts reading as two panels butted together —
   which is exactly the "independent Elementor sections" feeling.

   The thank-you takes the LOW end of the landing's range, because its movements
   are shorter and it is a closer rather than an argument: enough air for each
   ground change to be a transition, not so much that a four-line movement
   floats in a field. Same system, different scale.

   THE MINIMA ARE THE LANDING'S MOBILE FIGURES (48px for the stage chapter,
   64-88 for paper and warm at <=768), and the clamps are keyed to `vh` rather
   than width. That is deliberately MORE responsive than the landing, which
   steps on width alone and therefore gives a 375px-tall landscape handset the
   same 64-112px chapter padding it gives a tall phone — a fifth of the screen
   per gap. Height is the constraint on a landscape handset, so height is what
   the rhythm answers to here. */
/* ══ THE CLOSING MOVEMENT — ONE CHAPTER, TWO BEATS ════════════════════════
   SHARE and FOLLOW used to be two full-bleed bands on two different grounds,
   with the paper one wedged between the dark one and the #100e0b colophon. The
   emotional sequence they carry is MOVE IT → SHARE IT → STAY WITH IT, and the
   composition was saying "here is a thing, here is an unrelated thing, here is
   the end". They are one chapter now, on the campaign's dark stage.

   THE GROUND IS THE CHAPTER'S, NOT EITHER BEAT'S. `.ty-share` and `.ty-follow`
   declare no background at all any more; each is a ROW inside this envelope.
   That is the whole mechanism by which a reader stops perceiving two sections:
   there is no edge between them to perceive.

   THE LIFT IS A CORNER, NOT A WASH. At 120% x 90% the warm stop reached across
   most of the top edge, so the band read as lit from above rather than as a
   dark ground with one warm corner. Pulled to 78% x 66% and stopped at 66%, so
   --stage-2 stays in its corner and the rest is the campaign's stage colour,
   flat. The geometry is unchanged; it now lights a chapter instead of a band. */
.ty-next {
  background: radial-gradient(78% 66% at 12% 2%, var(--stage-2), var(--stage) 66%);
  color: var(--on-dark);
  padding-block: clamp(48px, 8vh, 100px);
}

/* THE BRIDGE FROM THE NUMBER ABOVE IT. The ledger ends on NOW at display scale
   — the donor has just watched a figure change because of something they did.
   This line is what turns that into the next action, and it is not new copy:
   it was the first clause of the share paragraph, buried BELOW the head it was
   meant to introduce. Promoted to the chapter's eyebrow it introduces BOTH
   beats, which is what makes them one movement rather than two asks.

   The landing's eyebrow figures, verbatim: 11.52px at .14em. */
.ty-next__eyebrow {
  margin: 0 0 clamp(20px, 3vh, 34px);
  font-family: Inter, system-ui, sans-serif;
  font-size: .72rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--gold);
}

/* ── BEAT ONE · SHARE ──────────────────────────────────────────────────────
   THE ROW IS THE BINDING. Share is composed as the SAME shape Follow is — say
   on the spine, control hanging at the right of the band — because a shared
   geometry is what makes two blocks read as two verses of one statement. Set
   them as two stacked left-aligned columns and no amount of shared background
   stops them reading as two sections.

   `--reach-band` (940px) is deliberate and it is the page's right-hand spine:
   at 1440 it puts the Share control's right edge, the Follow control's right
   edge and the ledger's figure column all on x=1084. Before this, the ledger
   hung at 1084 and both controls ran to the 1152 container edge, so the three
   right-hand elements of the closing journey stood on three different lines. */
.ty-share {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(18px, 2.6vh, 28px) 32px;
  align-items: center;
  max-width: var(--reach-band);
}
.ty-share__say { grid-column: 1; min-width: 0; }
.ty-share__h {
  text-transform: uppercase; color: #fff;
  font-size: clamp(1.7rem, 3.4vw, 2.8rem); line-height: 1.02; letter-spacing: -.016em;
  margin: 0;
}
.ty-share__h em { font-style: italic; font-weight: 400; color: var(--on-dark-mut); }
.ty-share__p {
  margin: var(--space-3) 0 0; color: var(--on-dark-mut);
  font-size: clamp(1rem, 1.3vw, 1.1rem); line-height: 1.62; max-width: var(--reach-read);
}
/* THE CONTROL COLUMN. `justify-self:end` rather than a width: the track is
   sized by the button, exactly as the Follow row's is, so the two controls are
   the same object in the same place and not two coincidences. */
.ty-share__act { margin: 0; grid-column: 2; grid-row: 1; justify-self: end; }
.ty-share__fine {
  grid-column: 1 / -1;
  margin: 0; font-size: .84rem; color: rgba(244, 239, 226, .58);
  display: flex; align-items: center; gap: 10px;
}
/* THE PADLOCK, DRAWN FROM CSS — the production build strips every <svg> from
   this document, so a marked-up icon ships as nothing at all. A background
   image on a pseudo-element survives the build and wp_kses_post, needs no
   runtime, and is aria-hidden by construction. */
.ty-share__fine::before {
  content: ""; flex: 0 0 auto; width: 15px; height: 15px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='1.8' stroke-linecap='round'%3E%3Crect x='4.5' y='10.5' width='15' height='10' rx='2.5'/%3E%3Cpath d='M8 10.5V7a4 4 0 0 1 8 0v3.5'/%3E%3C/svg%3E") center / contain no-repeat;
  opacity: .58;
}

/* ── BUTTON LANGUAGE · THE CAMPAIGN'S CTA COMPONENT, DERIVED ───────────────
   THIS IS NOT A SIMILAR ROUNDED BUTTON. Every value below is the LIVE computed
   value of `.camp .btn` on post 3248, read with CDP at 1440 (campaign.css is
   another terminal's working file and is NOT edited from here — the exact
   figures and the reconciliation requirement are recorded in the ledger at the
   end of this sheet):

       border-radius   999px            (this page shipped 2px)
       font            600 16px/1 Inter (this page shipped 600 17px/1)
       padding         .95em 1.6em  ->  15.2px 25.6px   (shipped 1.15em 2em -> 19.5 / 33.9)
       border          1.5px solid transparent          (shipped 0)
       height          48.4px                           (shipped 58px)
       primary         bg #daa520 · fg #23221f · shadow 0 8px 24px rgba(218,165,32,.3)
       primary:hover   bg #7a5e0c · fg #fff · translateY(-1px)
       ghost           border-color currentColor · hover rgba(255,255,255,.12)
       focus-visible   3px solid --gold, offset 3px

   A square-cornered 58px slab under a page of pill CTAs is the single loudest
   "two teams built this" signal the donor journey had, and it was on the one
   control this page most wants pressed.

   Gold fill carries INK text, never white — the campaign's contrast law, and
   `.camp .btn--primary` already obeys it. The production build replaces the
   <button> with an <a> of the same classes, which with JavaScript disabled is a
   working link to the campaign rather than a control that does nothing. */
.ty-btn {
  --_bg: transparent; --_fg: currentColor;
  display: inline-flex; align-items: center; justify-content: center; gap: .5em;
  min-height: var(--tap);
  font: 600 1rem/1 Inter, system-ui, sans-serif;
  padding: .95em 1.6em;
  border-radius: var(--radius-pill);
  /* 1px, not 1.5px: the landing's control computes a 1px ring and stands 48px.
     At 1.5px the same padding and face produced 51px, so the donor crossed from
     Landing into a button three pixels taller for no reason anyone chose. */
  border: 1px solid transparent;
  background: var(--_bg); color: var(--_fg);
  text-decoration: none; cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out), background var(--dur-base),
              color var(--dur-base), border-color var(--dur-base);
}
.ty-btn--primary {
  --_bg: var(--gold); --_fg: var(--ink);
  box-shadow: 0 8px 24px rgba(218, 165, 32, .3);
}
.ty-btn--primary:hover { --_bg: var(--gold-deep); --_fg: #fff; transform: translateY(-1px); }
/* THE SECONDARY CTA, on paper. `.camp .btn--ghost` draws its ring in
   currentColor, which on the campaign's dark grounds is cream and on this
   paper is ink — the same component answering its ground, not a second one. */
.ty-btn--ghost { border-color: currentColor; }
.ty-btn--ghost:hover { background: rgba(35, 34, 31, .06); transform: translateY(-1px); }
/* The upload mark that leads the approved control. Same reasoning as the
   padlock above: drawn in CSS because the build removes SVG from the markup. */
.ty-share__act .ty-btn--primary::before {
  /* 1em TALL, NOT 19px. The control's line box is 16px; a 19px glyph became the
     tallest flex item and stretched the button to 51px against the landing's
     48px. The mark keeps its optical size through `background-size: contain`
     on a wider box, so nothing shrinks visually. */
  content: ""; flex: 0 0 auto; width: 19px; height: 1em; margin-right: 12px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2323221f' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7'/%3E%3Cpath d='M12 15V3'/%3E%3Cpath d='m8 7 4-4 4 4'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* ══ BEAT TWO · FOLLOW — the row the approved component mounts into ═══════
   The control is `campaign-follow.js`, the same reviewed component the campaign
   page runs; this page supplies only the BAND — its ground, its rhythm, and the
   one rule that keeps it away from visitors it must never appear to.

   IT IS NO LONGER A BAND. Follow had its own paper ground between the dark
   Share band and the #100e0b colophon — a thin light sliver between two dark
   masses, which is the most severed position on the page and the worst one for
   the movement that is supposed to read as the consequence of the one above it.
   It is now the SECOND ROW of the closing chapter, on the chapter's ground.

   THE RANKING IS MATERIAL, NOT GROUND AND NOT SIZE. Share is the FILLED
   control, at display scale, the only filled control on the page. Follow is the
   ghost ring at a fifth of that scale. They can therefore share a chapter
   without the reader losing track of which one is the primary act.

   `.jsf-follow-band { display:none }` in the component's stylesheet keeps an
   unmounted band at zero height, so a missing runtime collapses this section
   rather than leaving a heading over an empty rule. */
/* ══ THE COLLAPSE CONTRACT IS RETIRED ON THIS SURFACE ═════════════════════
   WHAT USED TO BE HERE:

     .ty .ty-follow.jsf-follow-band         { display: none; }
     .ty .ty-follow.jsf-follow-band.is-live { display: block; }

   That pair made the ENTIRE Follow movement's visibility a function of
   successful component hydration, and campaign-follow.js returned early for
   any visitor the server could not verify. One identity check therefore
   deleted a chapter of the closing journey — and because a collapsed section
   occupies zero height, the deletion was indistinguishable from a design
   decision and every gate stayed green.

   IDENTITY UNCERTAINTY MUST NOT BECOME LAYOUT UNCERTAINTY. So the two are
   separated: this section is ALWAYS DISPLAYED (it has no rule hiding it, and
   the markup no longer carries `jsf-follow-band` at all), and only the SHELL
   inside it retires once a live control has landed.

   `.jsf-follow-band { display:none }` is NOT deleted from campaign-follow.css.
   That sheet is shared with Landing, where the collapse is still correct: the
   feature can legitimately be switched off there, and a band with nothing in
   it should not draw its ground. What changed is which surfaces opt in.

   DECLARED AT (0,2,0) ANYWAY. The host has outranked this page's single-class
   selectors five times; `display` on this section is now the single property
   whose loss deletes the movement, so it is stated at a weight the host cannot
   reach rather than left as a default nobody declared. */
.ty .ty-follow                            { display: block; }
/* THE SHELL RETIRES ONLY WHEN A REAL CONTROL HAS LANDED. `is-hydrated` is added
   by campaign-follow.js at the TAIL of a successful mount — not at its start,
   not on DOMContentLoaded. If the runtime never loads, throws, or returns
   because the feature is unconfigured, this class never appears and the donor
   keeps the shell's heading, sentence and working link. Fail safe, never fail
   invisible. */
.ty .ty-follow.is-hydrated .ty-follow__shell { display: none; }
/* ── THE CONTINUATION MARK, CARRIED BY THE BEAT IT SEPARATES ───────────────
   One hairline at the band's reach — the same continuation mark the ledger
   draws above its total, NOT a band edge, and emphatically not a ground change.
   It is declared on `.ty-follow` rather than on the chapter or on a rule
   element of its own so that it LEAVES WITH THE MOVEMENT: a visitor with no
   verified gift gets no Follow control, `jsf-follow-band` keeps the block at
   `display:none`, and the hairline goes with it. A separately-drawn rule would
   have stayed behind as a line under Share pointing at nothing.

   `--on-dark` is DECLARED here, not inherited. That is the sixth instance of
   this page's recurring lesson (any property the composition depends on and
   does not declare will be supplied by the host) and it is also what
   build-thankyou-production.mjs §6b reads to establish that this ground is
   dark — which is what makes dropping `--onPaper` from the mount provable
   rather than merely intended.

   TIGHT, BECAUSE IT IS A ROW AND NOT A CHAPTER. The old `clamp(40px,7vh,80px)`
   was a band's padding; here it would reopen the gap this change closed. */
.ty-follow {
  color: var(--on-dark);
  margin-top: clamp(30px, 4.6vh, 52px);
  padding-top: clamp(28px, 4.2vh, 46px);
  border-top: 1px solid rgba(244, 239, 226, .12);
  max-width: var(--reach-band);
}
/* AN UNMOUNTED MOUNT ADDS NOTHING — it is not the movement, it is the slot the
   control lands in, and while it is empty the shell is the thing on screen. */
.ty-follow .jsf-follow-mount:empty { display: none; }
/* ── THE SHELL IS THE SAME ROW THE COMPONENT MOUNTS ────────────────────────
   `ty-stay__row` on the shell as well as on the mount, so both are literally
   the same grid declaration — say on the spine, control hanging at the right.
   That is what keeps the slot's geometry stable across the hand-off: the
   content changes, the composition does not move. */
.ty-follow .ty-follow__shell { display: grid; }

/* ── THE APPROVED FOLLOW ROW ────────────────────────────────────────────────
   The approved composition is a ROW, not a stack: the say column on the left,
   the control on the right at the same optical line, and the consent sentence
   across the full measure beneath both. The component mounts a stacked column
   (say · form) because that is what the campaign's dark band wants; on this
   paper it shipped heading → lede → button → consent all down the left edge,
   which reads as a fourth section instead of the page's quiet last line.

   `display: contents` on the form is what makes this possible without touching
   campaign-follow.css — that stylesheet is shared with the Landing surface and
   is another terminal's file. The form element keeps its semantics and its
   submit handler; only its box is removed, so its children become items of this
   grid. The done/error states re-render inside the same children, so they
   inherit the same placement.

   Everything here is scoped to `.ty-follow`. Nothing changes for the campaign
   page's copy of the component. */
.ty-follow .ty-stay__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(14px, 2vh, 20px) 32px;
  align-items: center;
}
/* STATEFUL UI, STABLE GEOMETRY. Four states land in this slot — the static
   shell, the typed-address invitation, the one-press verified invitation and
   the subscribed confirmation — and they carry different numbers of lines. The
   floor is the height of the two-line say beside a 48px control, which is the
   shell and both invitations; it stops the slot from CHANGING SIZE when the
   only thing that changed is which of them is showing. It is a floor and not a
   height: the confirmed state is allowed to be taller, and nothing is ever
   padded into an empty field to meet a number. */
.ty-follow .ty-stay__row { min-height: 84px; align-content: center; }
.ty-follow .ty-stay__row > .jsf-follow__say { grid-column: 1; }
.ty-follow .ty-stay__row > form.jsf-follow { display: contents; }
/* THE CONTROL COLUMN — ONE BUTTON WIDE. This used to cap the width of a
   560px email underline. There is no email field on this surface any more
   (follow.js refuses to build one here), so the track is sized by the control
   itself and hangs at the right of the say column. */
.ty-follow .jsf-follow__field {
  grid-column: 2; grid-row: 1; margin: 0;
  width: auto; justify-self: end; align-items: center; flex-wrap: nowrap;
}
/* ── THE TYPED-ADDRESS STATE KEEPS THE ROW ─────────────────────────────────
   The first attempt dropped this state to a full-width row beneath the say,
   which is what the campaign page does. It is the wrong answer HERE, and the
   reason is the product contract rather than taste: the three states must
   occupy the SAME compositional slot, and a state that relocates its control to
   a different row is a different composition — precisely the layout instability
   this release exists to remove.

   So the field stays in the control track and is widened instead. The track is
   sized for a real address (the input holds 240px of it, not the 190px left
   over from a button), and the say column keeps 408px at the 940 reach — which
   sets "Stay with the campaign." and its lede on one line each.

   Below 900 the rule further down turns BOTH rows into stacks together; that is
   a responsive decision about measure, and it moves Share and Follow as one. */
.ty-follow .jsf-follow__field--typed {
  grid-column: 2; grid-row: 1; justify-self: end;
  /* A DEFINITE WIDTH, NOT `min(100%, 500px)`. The control track is `auto`, so
     a percentage resolves against a track that is itself sized by this item —
     circular, and it settled at 416px, leaving the input 191. A definite width
     sizes the track. */
  width: 500px; max-width: 100%; flex-wrap: nowrap; gap: 12px;
}
.ty-follow .jsf-follow__field--typed .jsf-follow__input { flex: 1 1 240px; }
.ty-follow .jsf-follow__promise,
.ty-follow .jsf-follow__status,
.ty-follow .jsf-follow__count { grid-column: 1 / -1; margin: 0; }
/* The approved consent line runs to a 74ch measure, not to the column the
   button left behind. */
.ty-follow .jsf-follow__promise { max-width: 74ch; }
/* THE CONFIRMED STATE IS NOT A ROW. Once the band marks itself `is-done` the
   say column retires and the confirmation takes the whole measure — the
   behaviour the Follow product review settled. campaign-follow.css ALREADY
   collapses that grid for this surface by name:
     `.ty .ty-stay__row.jsf-follow-mount.is-done { grid-template-columns: 1fr }`
   which is why the mount carries `ty-stay__row` — the component was written
   expecting this page to set exactly this grid, and the class is the contract
   between the two stylesheets. */

/* Below the approved breakpoint the row becomes a stack and the control sits at
   the spine, exactly as Convergence A does at 768. */
/* BOTH ROWS TURN AT THE SAME WIDTH, because they are one composition. The
   Share head is display type at a 940px reach and the Follow control needs
   ~213px beside it; below 900 the say column is squeezed under its own measure
   before either control is, so the turn happens here rather than at the 768
   this row used to keep on its own. */
@media (max-width: 900px) {
  .ty-share { grid-template-columns: minmax(0, 1fr); }
  .ty-share__act { grid-column: 1; grid-row: auto; justify-self: start; }
  .ty-follow .ty-stay__row { grid-template-columns: minmax(0, 1fr); }
  .ty-follow .jsf-follow__field { grid-column: 1; grid-row: auto; justify-self: start; }
}
/* On a phone the approved composition gives BOTH controls the full measure —
   the share button and this one — because a 180px button floating at the left
   edge of a 390px screen reads as an afterthought beside a full-width primary.
   Convergence A does it at 560 (`.a-follow__row .btn { justify-self: stretch }`)
   and so does this. */
@media (max-width: 560px) {
  .ty-share__act { justify-self: stretch; }
  .ty-share__act .ty-btn { width: 100%; }
  .ty-follow .jsf-follow__field { justify-self: stretch; width: 100%; flex-wrap: wrap; }
  /* `.ty .jsf-follow-mount` to match the weight of the rule this widens — the
     (0,2,0) version of this line silently stopped applying when the base rule
     was raised to (0,3,0) for the campaign-CTA reconciliation. */
  .ty .ty-follow__shell .jsf-follow__submit,
  .ty .jsf-follow-mount .jsf-follow__submit { width: 100%; }
}
.ty-follow .jsf-follow__head {
  font-family: Fraunces, "Iowan Old Style", Georgia, serif;
  font-weight: 500; font-size: 1.26rem; letter-spacing: -.012em;
}
/* FOLLOW SHIPS IN BOTH STATES, AND THE COMPONENT ALREADY KNEW HOW.
   This rule used to be `.ty--unverified .ty-follow { display: none !important }`.
   It removed the approved "Stay with the campaign." movement from the base
   production page entirely — the donor journey's last beat, gone, on the one
   state most visitors to a bare URL actually see.

   campaign-follow.js has carried BOTH modes since it shipped: with a resolvable
   session it renders VERIFIED MODE (one decision, no email field, nothing asked
   that the payment already established); without one it renders the campaign
   page's own typed-address mode, validates the address, and posts
   { email, consent, source } to the same reviewed endpoint. That is the
   existing approved component behaviour on the existing approved contract —
   no new consent architecture, and nothing is ever subscribed silently.

   The one thing that WAS false in the unverified state is the lede: "You just
   moved it." addresses somebody who has. COPY.thankyou is keyed by surface in
   the component, so the correction is made there and reaches only this surface:
   `ledeUnverified` / `ledeEmUnverified` on the thankyou key alone. COPY.campaign
   has no such variant, so the Landing band renders byte-identically. */

/* ── COLOPHON · THE CAMPAIGN'S INSTITUTIONAL ENDING ────────────────────────
   WHAT WAS WRONG. The page ended on paper with a 264px TLM tagline lockup, a
   block of legal type beside it and a hairline above the lot. Read as pixels
   that is not an ending — it is the page stopping, and then a large logo being
   set down after it. The landing page's journey does not end that way, and the
   donor crosses between the two in one redirect.

   THE LANDING FOOTER, MEASURED LIVE ON POST 3248 (campaign.css is another
   terminal's working file and was NOT edited — see the ledger at the end of
   this sheet):

       .camp footer.foot   background #100e0b
                           padding-block clamp(52px,5.6vw,88px) clamp(44px,4.4vw,68px)
                           color rgba(244,239,226,.62)
       .foot__row          grid auto / 1fr / auto · gap clamp(32px,4.4vw,64px) · centred
       .foot__eyebrow      Inter .72rem · ls .14em · uppercase · rgba(244,239,226,.5)
       .foot__brand img    height 44px  (Patriot, brightness(0) invert(1), opacity .78)
       .foot__legal        Inter .8rem/1.6 · max 60ch · opacity .82
       .foot__back         #eace84 · 600 · no underline

   THE TLM LOCKUP IS NOT HERE ANY MORE, and that is the point rather than a
   loss: the landing footer carries no TLM mark either, because the MASTHEAD
   carries the institution and the legal line names it in full. This page's
   masthead now renders the same lockup at the same size the landing does, so
   the 264px copy down here was the duplicate, not the identity. */
/* THE SEAM. Follow took the inverted band, and the colophon beneath it is
   #100e0b — close enough that the two ran together into one undifferentiated
   dark mass with the page's last movement lost inside it. A hairline in the
   band's own cream separates them at the cost of one pixel: two dark
   movements, not one long one. `border-top: 0` was correct while the band
   above was paper. */
.ty-foot {
  background: #100e0b; color: rgba(244, 239, 226, .62);
  /* .18, NOT .1 — AND THE REASON IS THE CHAPTER ABOVE IT, NOT TASTE.
     This hairline was drawn under a PAPER band, where #100e0b arriving was
     itself the boundary and the rule only had to finish the edge. The closing
     chapter is now the campaign's dark stage, and #14120e against #100e0b is a
     four-unit step: without a readable rule the last 550px of the page merge
     into one undifferentiated dark mass and the colophon stops reading as a
     separate plate. It is deliberately STRONGER than the .12 continuation mark
     between Share and Follow — that one separates two beats of one chapter,
     this one separates two chapters, and the two must not read as equals. */
  border-top: 1px solid rgba(244, 239, 226, .18);
  /* 72px both ends — the landing footer's own figure, measured. This page was
     at 80.64/63.36, which is a different rhythm arriving one redirect later. */
  padding-block: 72px;
}
/* TWO CELLS, NOT THREE. The third carried "Give again →" and is gone with it;
   an `auto` track with nothing in it holds its gap open, so the page would have
   closed on a column of empty dark. */
.ty-foot__row {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(32px, 4.4vw, 64px); align-items: center;
}
.ty-foot__brand { display: flex; align-items: center; gap: 18px; }
/* .76rem / .18em / .62 — the landing's SECOND eyebrow rule
   (`.camp .foot .foot__eyebrow`), not its first. Deriving from the first gave
   12.16px at .14em where the live page sets .18em, and the parity matrix
   caught the 0.58px difference that no eye would have. */
.ty-foot__eyebrow {
  font-size: .72rem; letter-spacing: .14em; text-transform: uppercase;
  color: rgba(244, 239, 226, .62); white-space: nowrap;
}
.ty-foot__patriot { height: 44px; width: auto; display: block; filter: brightness(0) invert(1); opacity: .78; }
.ty-foot__fine { min-width: 0; }
.ty-foot__fine p {
  margin: 0; font-size: .8rem; line-height: 1.6;
  color: rgba(244, 239, 226, .62); opacity: .82; max-width: 60ch;
}
/* The record sentence is written by the runtime for a verified donor only, so
   empty it must occupy nothing rather than open a gap inside a grid cell. */
/* The one link a donor might still want, in the slot the landing gives
   "Back to The Last Mile →" and in the same sand. "Give again" is here and
   nowhere else: the most prominent control on a page whose reader has just paid
   must not be a request for more money. */
@media (max-width: 1024px) {
  .ty-foot__row { grid-template-columns: 1fr; gap: 22px; }
}

/* ── FOCUS ────────────────────────────────────────────────────────────────── */
.ty a:focus-visible, .ty button:focus-visible {
  outline: 3px solid var(--gold-deep); outline-offset: 3px; border-radius: 4px;
}
.ty-next a:focus-visible, .ty-next button:focus-visible { outline-color: var(--on-dark); }

/* ── ENTRANCE ───────────────────────────────────────────────────────────────
   One idea: things arrive. Nothing loops, nothing parallaxes, nothing moves on
   scroll after it has settled. The pre-state is scoped so that with no
   JavaScript `is-ready` never lands and the document is simply visible —
   [data-rise] elements are opaque by default and only the ready class animates
   them, which is why nothing can be hidden by a script that failed to run. */
.ty.is-ready [data-rise] {
  animation: ty-rise var(--dur-slow) var(--ease-out) both;
}
@keyframes ty-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ══ RESPONSIVE ART DIRECTION ══════════════════════════════════════════════
   Mobile is not the desktop stacked. The first viewport is composed
   deliberately at each width, because at each width a different thing threatens
   it:
     1024  the greeting steps down so the gift line does not wrap to two rows
           and push Joe out of the screen.
     768   unchanged structurally — Joe keeps his row, because a stacked Joe
           costs ~110px and that is what puts his statement below the fold.
     560   the gift line goes vertical and the operators are removed; the sum
           keeps a rule above it so the line still reads as a total. Joe's
           portrait shrinks but stays BESIDE his statement, for the same reason. */
@media (max-width: 1024px) {
  .ty .ty-head .ty-head__t { font-size: clamp(2.3rem, 5vw, 3.4rem); }
}
/* JOE STACKS BELOW 768, AND THIS IS A CHANGE OF ART DIRECTION, NOT A REFLOW.
   The old block held a 92-136px PORTRAIT beside the statement all the way down,
   because a portrait that narrow costs the text almost nothing. A 16:9 film card
   cannot do that: at 390 it took 230px of a 358px measure and left the statement
   setting at eleven characters a line — twenty lines of ragged column. The card
   goes full measure and the statement runs beneath it, which is also the order a
   phone reader wants: see him, then read what he did. */
@media (max-width: 768px) {
  .ty-joe { grid-template-columns: minmax(0, 1fr); gap: var(--space-5); }
  .ty-joe__film { width: min(460px, 100%); }
  /* Stacked, the card is no longer sharing a row and may take the measure
     back; the ledger below it takes the full column. */
  .ty-payoff { gap: clamp(26px, 4.5vh, 44px); }
  .ty-joe    { flex: 1 1 100%; }
  .ty-moved  { flex: 1 1 100%; }
}
/* A LANDSCAPE PHONE IS A NARROW VIEWPORT THAT IS ALSO A SHORT ONE, and the two
   want opposite things. Stacking costs ~190px of height, which at 375px tall is
   half the screen and pushes the Joe moment out of the first viewport entirely
   (measured: 253px -> 582px). So when height is the scarce axis the card goes
   back beside the statement, at the campaign hero card's own 174-198px scale
   rather than this page's 340. Width-only breakpoints cannot express this. */
@media (max-width: 768px) and (max-height: 560px) and (orientation: landscape) {
  .ty-joe__film { width: clamp(168px, 27vw, 210px); }
  .ty-close { padding-block: clamp(18px, 4vh, 30px) clamp(16px, 3vh, 24px); }
  /* THE PAYOFF ROW STAYS A ROW HERE, for the same reason and by the same
     arithmetic: stacking Joe above the ledger costs ~190px, which at 375 tall
     is half the screen. With the card at its 210px landscape scale the two
     halves fit the 720px measure side by side, so height is spent on reading
     rather than on a fold. This re-declares the 768 stack above it. */
  .ty-payoff { gap: var(--space-4); }
  .ty-joe    { flex: 0 1 210px; }
  .ty-moved  { flex: 1 1 320px; }
}
@media (max-width: 560px) {
  .ty-gift { flex-direction: column; align-items: flex-start; gap: var(--space-4); }
  .ty-gift__op { display: none; }
  .ty-gift__term--sum { padding-top: var(--space-4); border-top: 1px solid var(--rule); width: 100%; }
  .ty-joe { gap: var(--space-4); }
  .ty-joe__film { width: 100%; }
  /* The approved composition steps Joe's statement down on a phone; without the
     same step it holds 18.2px and runs a line longer than approved. */
  .ty-say__t { font-size: 1.1rem; }
  .ty-btn { width: 100%; }
  .ty-foot__row { gap: var(--space-6); }
}

/* ── REDUCED MOTION ─────────────────────────────────────────────────────────
   Reduced motion must still deliver everything — it snaps to the finished state
   rather than withholding it. */
@media (prefers-reduced-motion: reduce) {
  .ty.is-ready [data-rise] { animation: none; }
  .ty *, .ty *::before, .ty *::after { transition: none !important; animation: none !important; }
}
/* ═══════════════════════════════════════════════════════════════════════════
   THE HOST OVERRIDE LAYER — do not delete, and do not lower these selectors
   ───────────────────────────────────────────────────────────────────────────
   This page does not render alone. It renders inside a WordPress theme and
   Elementor, and three of their rules beat single-class selectors and silently
   rewrote the approved composition on the LIVE page. Each was found with CDP
   getMatchedStylesForNode against production — none is visible from this
   repository, and none showed up in a local render:

     1. `.elementor img { height: auto; max-width: 100% }`      (0,1,1)
        beat `.ty-nav__logo { height: 30px }` (0,1,0), so the masthead lockup
        rendered at its NATURAL 1152x240 — a logo eight times its intended size,
        and 78px of horizontal overflow from the footer mark doing the same.

     2. A bare `h2 { color: #DAA520 }` in the site's global stylesheet.
        `.ty-moved__h` never declared a colour, so "YOU MOVED IT." shipped GOLD —
        which also broke the page's one rule about gold meaning one thing.

     3. `a:link { color: #DAA520 }` (0,1,1) beat `.ty-btn--primary` (0,1,0).
        The production build turns the share <button> into an <a>, so the
        primary call to action rendered GOLD TEXT ON A GOLD FILL: invisible.

   The lesson is general: any property this composition depends on and does not
   declare will be supplied by the host, and any single-class selector can be
   outranked by a tag+class one. So the rules below are `.ty`-prefixed (0,2,x)
   and declare the value outright rather than inheriting it.

   qa/thankyou-release/drift.mjs measures these on the LIVE page and fails if
   any of them regresses.
   ═══════════════════════════════════════════════════════════════════════════ */
/* 74px — the LANDING'S measured lockup height, not a figure chosen here. This
   rule exists at (0,2,0) because `.elementor img { height:auto }` is (0,1,1)
   and renders the lockup at its natural 1152x240; it was carrying 63px, so the
   donor crossed one redirect into a masthead 11px shorter than the one they
   just left. */
.ty .ty-nav__logo  { height: 74px; width: auto; max-width: 62vw; }
@media (max-width: 1024px) { .ty .ty-nav__logo { height: 57px; } }
@media (max-width: 768px)  { .ty .ty-nav__logo { height: 49px; } }
/* The colophon's partner mark, at the LANDING's 44px. Same reason as the
   lockup above: `.elementor img { height:auto }` is (0,1,1) and would render
   both of these at natural size — that is what once put a 1152x240 masthead and
   78px of horizontal overflow onto this page. */
.ty .ty-foot__patriot { height: 44px; width: auto; max-width: 100%; }
/* The film poster is fluid inside its frame, so it states BOTH axes: the <img>
   carries width/height attributes for CLS and the UA maps them to explicit CSS. */
.ty .ty-joe__poster { width: 100%; height: auto; aspect-ratio: 16 / 9; }

/* MARGIN, TOO — and the rule this loses to is OURS, not the host's.
   `.ty h1, .ty h2, .ty h3 { margin: 0 }` in this sheet's own base is (0,1,1) and
   outranks `.ty-moved__h` (0,1,0), so every section heading's margin-bottom was
   silently zero and the approved 14px gap under "YOU MOVED IT." never existed.
   The same trap the host set for us, set for ourselves. */
/* TEXT-TRANSFORM, DECLARED. The site ships a bare `h2 { text-transform:
   uppercase }` alongside the `h2 { color: #DAA520 }` recorded above, and
   `.ty-moved__h` was inheriting the shout rather than asking for it — which is
   the same undeclared-property defect wearing a third costume. The approved
   composition sets this heading and the share heading in the campaign's display
   caps, so the value below is the approved one, now stated. */
.ty .ty-moved__h   { color: var(--ink); margin: 0 0 14px; text-transform: uppercase; }
.ty .ty-follow__h  { margin: 0; }
.ty .ty-ledger__n  { color: var(--ink-soft); }
.ty .ty-ledger__row--now .ty-ledger__n { color: var(--ink); }
.ty .ty-ledger__k  { color: var(--ink-faint); }
.ty .ty-gift__k    { color: var(--ink-faint); }
.ty .ty-say__t   { color: var(--ink-soft); }
.ty .ty-foot__fine p { color: rgba(244, 239, 226, .62); }
.ty .ty-foot__eyebrow { color: rgba(244, 239, 226, .62); }
/* SCOPED TO `.ty` ON PURPOSE. campaign-follow.css is shared with the LANDING
   page and is another terminal's surface — it is not edited here. These rules
   restyle the component only where it sits on this page.

   THE GROUND CHANGED UNDER THIS BLOCK, AND THE COMMENT SAID SO BEFORE THE
   VALUES DID. A previous round wrote "the colours return to the component's own
   palette" above a block that still declared `var(--ink)` throughout — true of
   the intent, false of the CSS, and invisible because the band was in fact
   still on paper. Follow now really is on the campaign's dark stage, as the
   second beat of the closing chapter, so every colour below is stated in the
   ON-DARK palette.

   THESE RULES ARE NOT OPTIONAL BECAUSE THE COMPONENT ALREADY DRAWS CREAM ON
   DARK. campaign-follow.css is all single-class (0,1,0) selectors in a sheet
   enqueued separately; a theme rule for `p`, `h2` or `button` outranks every
   one of them. These (0,2,x) declarations are what the HOST would otherwise
   supply — which on this page has meant a gold heading, an invisible control
   and a magenta ring, three times over.

   `text-transform: none` stays either way — the component's heading is an <h2>
   and the site's global heading rule uppercases it. "Stay with the campaign."
   is a sentence; shouted, it stops being one. */
.ty .jsf-follow__head {
  color: var(--on-dark); font-family: Fraunces, "Iowan Old Style", Georgia, serif;
  text-transform: none; font-size: 1.26rem; letter-spacing: -.012em;
}
.ty .jsf-follow__lede    { color: var(--on-dark-mut); font-size: .98rem; line-height: 1.6; }
/* GOLD MEANS THE SHARE CONTROL ON THIS PAGE, AND NOTHING ELSE — so the one
   emphasis in this lede is drawn by going BRIGHTER, not by going gold. */
.ty .jsf-follow__lede em { color: var(--on-dark); font-style: italic; font-weight: 400; }
.ty .jsf-follow__lede,
.ty .jsf-follow__promise,
.ty .jsf-follow__status,
.ty .jsf-follow__submit,
.ty .jsf-follow__done,
.ty .jsf-follow__doneSub { font-family: Inter, system-ui, sans-serif; }

/* THE COMPONENT'S OWN SHEET CANNOT BE RELIED ON TO WIN HERE.
   campaign-follow.css is enqueued as a separate stylesheet, and every rule in
   it is a single class — (0,1,0) — so any theme or Elementor rule for `p`,
   `button` or a wrapper class outranks it for the properties it happens to
   share. That is the same host-override trap the first release walked into,
   and it is visible the moment the load order shifts: in the production-
   faithful preview the consent sentence set at the theme's body size and the
   control drew a magenta ring, because neither value was ever DECLARED by
   anything that outranks the host.

   So the two properties this composition depends on are declared here at
   (0,2,0), against the approved values. campaign-follow.css itself is NOT
   edited — it is shared with the Landing surface. */
.ty .jsf-follow__promise {
  /* The SIZE is what the host takes (it set this at theme body size once); the
     COLOUR returns to the component's own dark-ground value now that the band
     is dark again. Subordinate, per the brief — readable, not a paragraph. */
  /* rgba(244,239,226,.58) is `.ty-share__fine` verbatim: the two pieces of fine
     print in this chapter are one register, so the consent line and the privacy
     line sit at the same weight instead of at two nearly-equal greys. */
  font-size: .75rem; line-height: 1.55; color: rgba(244, 239, 226, .58);
}
/* THE FOLLOW CONTROL IS THE CAMPAIGN'S SECONDARY CTA.
   It is still the page's one UNFILLED button — gold is the share control and
   nothing else here — but "unfilled" is a ROLE, and the role already has a
   drawn form on the landing page: `.camp .btn--ghost`, a 999px pill with a
   1.5px currentColor ring. This shipped a 2px-cornered inset-shadow ring
   instead, so the donor journey ended on its third button geometry.
   Values below are `.camp .btn` + `--ghost`, measured live; the component's own
   stylesheet is shared with the Landing surface and is NOT edited. */
/* (0,3,0), AND THAT IS NOT DECORATION. campaign-follow.css is enqueued AFTER
   this sheet on the live page, and its paper modifier
   `.jsf-follow-mount--onPaper .jsf-follow__submit` is (0,2,0) — the same weight
   as `.ty .jsf-follow__submit`, so on a tie the later sheet won and the control
   kept its own ring instead of the campaign's. Naming the mount as well puts
   this rule one class above it. Nothing in campaign-follow.css is edited: it is
   shared with the Landing surface, where the component keeps its own geometry.

   THE FIELD RULES THAT USED TO FOLLOW THESE ARE GONE WITH THE FIELD. This
   surface no longer collects a typed address — Follow here runs on the verified
   donor identity the payment established — so the underline overrides that kept
   the theme's form rules off `.jsf-follow__input` have nothing to protect. They
   remain in campaign-follow.css, which is the Landing surface's sheet, where the
   typed-address control still lives and is still reviewed. */
/* THE SHELL'S CONTROL IS THE SAME OBJECT. Adding the shell's selector here —
   rather than writing a second set of values for it — is what makes the
   hand-off from the static movement to the hydrated one invisible. Two sets of
   numbers for one control is how a shell and its component drift into two
   different-looking versions of the same movement. */
.ty .ty-follow__shell .jsf-follow__submit,
.ty .jsf-follow-mount .jsf-follow__submit {
  display: inline-flex; align-items: center; justify-content: center; gap: .5em;
  background: transparent; color: var(--on-dark);
  border: 1.5px solid currentColor; box-shadow: none;
  border-radius: var(--radius-pill);
  font: 600 1rem/1 Inter, system-ui, sans-serif;
  padding: .95em 1.6em; min-height: var(--tap);
  transition: transform var(--dur-fast) var(--ease-out), background var(--dur-base), color var(--dur-base);
}
/* `.camp .btn--ghost:hover` on the landing's own dark grounds: a 12% cream
   wash, not the 6% ink wash that belongs on paper — which on #14120e would have
   darkened the control on hover instead of lifting it. */
.ty .ty-follow__shell .jsf-follow__submit:hover,
.ty .jsf-follow-mount .jsf-follow__submit:hover {
  background: rgba(255, 255, 255, .12); color: var(--on-dark); border-color: currentColor; transform: translateY(-1px);
}
/* (0,3,0) to beat the component's own (0,2,0) modifier rules, as before — but
   asking for the component's OWN sand now, because the ground came back to it. */
.ty .jsf-follow-mount .jsf-follow__lede em { color: var(--on-dark); font-style: italic; font-weight: 400; }
/* ══ UNFOLLOW IS RELATIONSHIP MANAGEMENT, NOT A DESTRUCTIVE ACTION ═════════
   IT SHIPPED AS A FULL-WIDTH OUTLINED RECTANGLE IN A WARNING COLOUR, and it was
   the largest interaction in the section — bigger than the confirmation it sat
   under. Nothing in this product asked for that. The component draws it quietly
   (no background, no ring, a hairline under .82rem type) but every one of those
   declarations is a SINGLE CLASS, (0,1,0), in a stylesheet enqueued after this
   one; the theme's own `button` rules outrank it the moment they are more
   specific than one class. This is the fourth time that trap has been sprung on
   this page, and the cure is the same: state the values at a weight the host
   cannot reach.

   THE HIERARCHY IS THE POINT. Follow is the acquisition action and carries the
   campaign's button geometry. Unfollow is a text action — visible, keyboard
   reachable, 44px of touch target, and unmistakably subordinate. It is not
   styled as danger, because leaving a mailing list is not a destructive act and
   colouring it as one is a dark pattern pointed the other way. */
.ty .jsf-follow-mount .jsf-follow__unfollow {
  display: inline-flex; align-items: center;
  width: auto; justify-self: start; align-self: start;
  grid-column: 1;
  margin: 2px 0 0; padding: 11px 2px; min-height: var(--tap);
  background: none; border: 0; border-bottom: 1px solid currentColor;
  border-radius: 0; box-shadow: none;
  font: 400 .82rem/1.5 Inter, system-ui, sans-serif;
  color: rgba(244, 239, 226, .62); opacity: 1; cursor: pointer;
}
.ty .jsf-follow-mount .jsf-follow__unfollow:hover,
.ty .jsf-follow-mount .jsf-follow__unfollow:focus-visible { color: var(--on-dark); }
.ty .jsf-follow-mount .jsf-follow__unfollow[disabled] { cursor: default; opacity: .45; }
/* THE CONFIRMATION IS THE DOMINANT THING IN THIS STATE. */
/* `--sand`, not `--gold`. The subscribed confirmation is the dominant element
   of its own state and has to read as warm rather than as a status line — but
   #daa520 is the SHARE control on this page and means one thing. #eace84 is the
   campaign's on-dark register for exactly this role (it is what the component
   itself reaches for, and what the gift seal uses), so the confirmation is warm
   without claiming to be as important as the gift. */
.ty .jsf-follow__done    { color: var(--sand); font-size: 1.06rem; font-weight: 600; }
.ty .jsf-follow__doneSub { color: var(--on-dark-mut); font-size: .92rem; line-height: 1.55; }

/* ── THE SUBSCRIBED STATE IS A QUIET PARAGRAPH, NOT FOUR SPACED LINES ──────
   campaign-follow.css collapses this grid to one column by name when the mount
   marks itself `is-done` — the behaviour the Follow product review settled, and
   it is not reversed here. But the row gap that separates a heading from a
   control (14–20px) separates FOUR consecutive lines of the same sentence into
   four apparently unrelated statements, and the resolved state then ran 190px
   tall: taller than the invitation it replaced, on the page's quietest moment.

   Confirmation, its sub-line and the count are one utterance and close up.
   Unfollow keeps its distance, because a withdrawal control adjacent to the
   sentence confirming the subscription is a misread waiting to happen — and it
   keeps its 44px target. Subordinate by weight and colour, never by being hard
   to hit. */
.ty-follow .ty-stay__row.is-done { row-gap: 6px; }
.ty-follow .ty-stay__row.is-done .jsf-follow__unfollow { margin-top: 14px; }

/* ── THE ADDRESS FIELD, DECLARED — AND IT HAD TO COME BACK ─────────────────
   These overrides were deleted one round ago with the note "THE FIELD RULES
   THAT USED TO FOLLOW THESE ARE GONE WITH THE FIELD". The field is back,
   because the unverified visitor's ACTION is back, so the rules are back — and
   they are not optional. campaign-follow.css draws this control in single-class
   (0,1,0) selectors, and a WordPress theme's form styling is the most reliably
   specific thing on any page: `.elementor input[type="email"]`, `input:focus`
   and this site's own global form block all outrank it. The recorded symptom,
   the last time this was left to inheritance, was a MAGENTA focus ring on the
   follow control.

   The values are campaign-follow.css's own dark-ground values, restated: a
   ruled line and not a box, because a filled rounded input is the single most
   recognisable embedded-marketing-form signal there is, and this is the closing
   movement of a donor journey rather than a newsletter block. */
.ty .ty-follow .jsf-follow__input {
  flex: 1 1 220px; min-width: 0; min-height: var(--tap);
  padding: 0 2px; margin: 0;
  background: transparent; color: var(--on-dark);
  border: 0; border-bottom: 1px solid rgba(244, 239, 226, .28);
  border-radius: 0; box-shadow: none;
  font: 400 1rem/1.5 Inter, system-ui, sans-serif;
  -webkit-appearance: none; appearance: none;
}
.ty .ty-follow .jsf-follow__input::placeholder { color: rgba(244, 239, 226, .48); }
.ty .ty-follow .jsf-follow__input:hover { border-bottom-color: var(--on-dark); }
.ty .ty-follow .jsf-follow__input:focus { outline: none; border-bottom-color: var(--gold); }
/* THE COLOUR CHANGE ALONE IS NOT A FOCUS INDICATOR. The campaign's one ring. */
.ty .ty-follow .jsf-follow__input:focus-visible {
  outline: 3px solid var(--gold); outline-offset: 3px; border-radius: 3px;
}
.ty .ty-follow .jsf-follow[data-state="error"] .jsf-follow__input { border-bottom-color: #f3b2a0; }
.ty .ty-follow .jsf-follow[data-state="error"] .jsf-follow__status { color: #f3b2a0; }

/* THE SHELL'S CONTROL IS AN <a>, AND THAT IS THE HOST-OVERRIDE TRAP AGAIN.
   This site ships a bare `a:link { color: #DAA520 }` — the rule that once made
   the share button gold text on a gold fill, invisible. The shell's control is
   a real link (it has somewhere real to go, which is the whole point of it not
   being a dead button), so it is in range of exactly that rule and of
   `.elementor a { text-decoration: none }`. Both are answered here, at (0,3,0),
   rather than left to the cream the component's own sheet declares at (0,1,0). */
.ty .ty-follow__shell .jsf-follow__submit,
.ty .ty-follow__shell .jsf-follow__submit:link,
.ty .ty-follow__shell .jsf-follow__submit:visited { color: var(--on-dark); text-decoration: none; }

/* AN EMPTY LIVE REGION MUST OCCUPY NOTHING. `status` and `count` are grid items
   of the approved row; empty, they still opened two gaps and pushed the consent
   sentence 40px away from the control it belongs to. */
.ty .jsf-follow__status:empty,
.ty .jsf-follow__count:empty { display: none; }
/* ── ONE TYPE SYSTEM, DECLARED ELEMENT BY ELEMENT ─────────────────────────
   THE FACE WAS NOT ACTUALLY SHARED WITH THE LANDING PAGE. `.ty` sets
   `font-family: Inter` on the root and every descendant was assumed to inherit
   it. Inheritance loses to any rule that targets the element itself, and the
   theme ships bare `p`, `a` and `span` rules — so, measured live at 1440:

       .ty-unv        NeueHelveticaPro45Light   (landing body: Inter)
       .ty-foot__legal NeueHelveticaPro45Light
       .ty-nav__row / .ty__wrap  NeueHelveticaPro45Light

   Body copy on the thank-you page was set in a DIFFERENT TYPEFACE from body
   copy on the campaign page, on every paragraph that did not name one. That is
   the "made by different teams" reading, and no amount of matching sizes fixes
   it. Six rules previously patched six symptoms; the list below declares the
   system instead, at (0,1,1) — enough to beat the theme's bare tag rules —
   and the display roles are re-declared beneath it at (0,2,0)+ so they win
   back. Anything added to this page from now on is Inter unless it asks. */
.ty p, .ty a, .ty span, .ty li, .ty b, .ty strong, .ty em, .ty small,
.ty label, .ty input, .ty button, .ty textarea, .ty select {
  font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
}
/* THE DISPLAY ROLES, WON BACK. Each of these is a p/span/em/b that the rule
   above would otherwise flatten to body type. */
.ty .ty-head .ty-head__t,
.ty .ty-head .ty-head__t em,
.ty .ty-gift__n,
.ty .ty-ledger__n,
.ty .ty-say__t,
.ty .ty-say__t strong,
.ty .ty-joe__meta b,
.ty .ty-share__h em { font-family: Fraunces, "Iowan Old Style", Georgia, serif; }

/* ── THE CTAs, AGAINST `a:link` ────────────────────────────────────────────
   `a:link { color: #DAA520 }` is (0,1,1) and once shipped the primary control
   as gold text on a gold fill. The build turns two of these controls into
   anchors, so every one of them declares its own foreground at (0,2,0)+.
   The FILL, PADDING and RADIUS come from `.ty-btn` above — the campaign's own
   component — and only the colours are restated here. */
/* THE LANDING'S PRIMARY, MEASURED ON THE HOSTED PAGE (2026-09-17):
     radius 999px · height 48 · padding 15.2px 25.6px · border 1px
     bg #daa520 · fg #23221f · shadow rgba(218,165,32,.3) 0 8px 24px
   THE SHADOW IS PART OF THE ACTION GRAMMAR, and it was being stripped here:
   `.ty-btn--primary` declares it at (0,1,0) and the host's `.elementor a` is
   (0,1,1), so the computed value on the live page was `none` while the landing
   kept its lift. That is the fifth time a single-class declaration on this page
   has lost to a host rule. Declared at (0,2,0) against the landing's value. */
.ty .ty-btn--primary,
.ty .ty-btn--primary:link,
.ty .ty-btn--primary:visited  { background: var(--gold); color: var(--ink);
                                box-shadow: 0 8px 24px rgba(218, 165, 32, .3); }
.ty .ty-btn--primary:hover    { background: var(--gold-deep); color: #fff;
                                box-shadow: 0 8px 24px rgba(218, 165, 32, .3); }
/* THE SECONDARY CTA. On paper `currentColor` must be ink, and the host's link
   colour would otherwise draw both the label and the ring in gold — which would
   also break this page's one rule about what gold means. */
.ty .ty-btn--ghost,
.ty .ty-btn--ghost:link,
.ty .ty-btn--ghost:visited    { color: var(--ink); border-color: currentColor; text-decoration: none; }
.ty .ty-btn--ghost:hover      { color: var(--ink); background: rgba(35, 34, 31, .06); }
/* "Give again" now sits on the dark colophon in the landing footer's sand,
   undecorated, exactly as `.camp .foot__back` does. */
/* The film card is an anchor over a photograph; the host would paint its
   caption gold. */
.ty .ty-joe__film,
.ty .ty-joe__film:link,
.ty .ty-joe__film:visited     { color: var(--ink); text-decoration: none; }
.ty .ty-nav__home:link        { color: inherit; }

/* ═══════════════════════════════════════════════════════════════════════════
   THE SHARE PRODUCT
   ───────────────────────────────────────────────────────────────────────────
   A <dialog> composed as a WORKSPACE rather than a modal. The difference is
   not semantic — it behaves as a dialog, and should — it is compositional:
   wide, hairline-divided, campaign type, generous. "Modal" is a behaviour the
   platform provides; it is not a look this product has to wear.

   Reading order, which the composition exists to serve:
       SEE IT   (left)  — the message and the card, as they will actually arrive
       SAY IT   (right) — angle, then the words
       SEND IT  (right) — one button, named after the destination
   ═══════════════════════════════════════════════════════════════════════════ */

/* The dialog is a SIBLING of .ty in the top layer, so `.ty *` never reaches it
   — the same scoping trap that once left the old fallback surface rendering in
   undefined colours. It has to state box-sizing for itself; without this the
   1px border sat OUTSIDE a width:100% dialog and pushed 2px of horizontal
   scroll onto a 320px viewport. */
.sh, .sh *, .sh *::before, .sh *::after { box-sizing: border-box; }

.sh {
  /* Top layer: outside .ty, so it states its own type stack rather than
     inheriting one it cannot see. */
  font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--on-dark);
  background: var(--stage-2);
  border: 1px solid #3a372f;
  border-radius: var(--radius-lg);
  padding: 0;
  /* 560px is the measure the ARTIFACT wants — a message at 1.02rem sets to a
     comfortable 60-odd characters here, and the link card below it keeps the
     proportions a real unfurled link has. The dialog is sized by its content
     rather than by the screen, so it is the same object on a laptop and a
     phone instead of a wide panel that collapses into a narrow one. */
  width: min(560px, calc(100vw - 32px));
  max-height: calc(100dvh - 40px);
  box-shadow: 0 40px 100px rgba(0, 0, 0, .76);
  overflow: hidden;
}
/* Scoped to [open] so the UA's own display:none for a closed dialog survives.
   The column flex is what gives the scrolling region a height to be short of —
   max-height alone leaves a block dialog to spill past the viewport. */
.sh[open] { display: flex; flex-direction: column; }
.sh::backdrop { background: rgba(6, 5, 4, .84); backdrop-filter: blur(3px); }
/* The sub-views are one column of their own, so the composing width leaves a
   third of the dialog empty behind them. The shell narrows to the content
   instead — the width IS the transition, which is why it is animated rather
   than snapped. */
.sh { transition: width var(--dur-base) var(--ease-out); }
.sh[data-view="email"], .sh[data-view="qr"] { width: min(600px, calc(100vw - 32px)); }

/* Entrance. The dialog arrives from slightly below and slightly small — enough
   to read as "this came from the button you pressed", not enough to be a
   performance. Neutralised entirely under reduced motion. */
@keyframes sh-in { from { opacity: 0; transform: translateY(12px) scale(.985); } }
.sh[open] { animation: sh-in var(--dur-slow) var(--ease-out); }
.sh[open]::backdrop { animation: sh-fade var(--dur-slow) var(--ease-out); }
@keyframes sh-fade { from { opacity: 0; } }

.sh__head {
  flex: none; display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-6); padding: var(--space-5) var(--space-6) var(--space-4);
  border-bottom: 1px solid var(--rule);
}
.sh__eyebrow {
  margin: 0 0 6px; font-size: .66rem; letter-spacing: .15em; text-transform: uppercase;
  color: var(--quiet);
}
.sh__title {
  margin: 0; font-family: Fraunces, Georgia, serif; font-weight: 500;
  font-size: 1.55rem; letter-spacing: -.01em; line-height: 1.1;
}
.sh .sh__x {
  flex: none; display: grid; place-items: center;
  width: var(--tap); height: var(--tap); border-radius: 50%;
  background: transparent; border: 1px solid var(--rule-strong); color: var(--muted);
  cursor: pointer; transition: color var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.sh .sh__x:hover { color: var(--on-dark); border-color: var(--on-dark-mut); }

/* The two zones. Not 50/50: SEE IT is evidence and wants to be seen whole;
   SAY IT + SEND IT is where the hands go and wants the room. */
.sh__body, .sh__view {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: var(--space-6) var(--space-6) var(--space-8);
}
/* ── ONE COLUMN ───────────────────────────────────────────────────────────
   The two-zone composition existed because there were enough controls to need
   two homes for them; reducing the controls removed the reason for the split.
   What is left is the actual sequence — this is what you are sending · adjust
   it if you want to · send it — and a sequence wants one axis, not two panes
   the reader has to choose between. It is also the same composition at every
   viewport, which is why the landscape and phone rules below are now about
   SIZE rather than about rebuilding the layout. */
.sh__body { display: grid; gap: var(--space-5); align-content: start; }
.sh__see, .sh__do { display: contents; }

.sh__zh {
  margin: 0; padding: 0; font-size: .68rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--quiet);
}
.sh__fs { border: 0; margin: 0; padding: 0; min-width: 0; display: grid; gap: var(--space-3); }

/* ── THE COMPOSED ARTIFACT ─────────────────────────────────────────────────
   The single most useful object here. Not "a campaign card" — the message and
   the card TOGETHER, in the shape a link takes when it unfurls in Messages,
   Slack or LinkedIn. It answers "what am I actually about to put in front of
   someone?" in one glance, and because the message half is live, it also
   answers "what does the card have to do with my words?" without a caption
   explaining the relationship. */
.sh-post { display: grid; }
/* The MESSAGE is the thing being sent, so it is the thing that is large. It
   was previously the same size as the card's caption, under a full-width
   1.9:1 photograph — which put the accompaniment above the argument. */
.sh-post__msg {
  margin: 0; padding: var(--space-5, 20px); white-space: pre-wrap;
  font-size: 1.02rem; line-height: 1.66; color: var(--on-dark);
  background: var(--stage-3, #221f19);
  border: 1px solid var(--rule-strong); border-bottom: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 6px 6px;
}
/* The campaign card is an ATTACHMENT, and is drawn as one: a strip, the shape
   a link preview takes in a message thread rather than a hero image. It still
   answers "what will they see", which is its whole job. */
.sh-post__card {
  display: grid; grid-template-columns: 132px 1fr; align-items: stretch;
  border: 1px solid var(--rule-strong); border-top: 0;
  border-radius: 6px 6px var(--radius-lg) var(--radius-lg);
  overflow: hidden; background: var(--stage);
}
.sh-post__img { display: block; width: 100%; height: 100%; aspect-ratio: 1200 / 630; object-fit: cover; }
.sh-post__meta {
  display: grid; align-content: center; gap: 4px;
  padding: var(--space-3) var(--space-4); border-left: 1px solid var(--rule); min-width: 0;
}
/* Both are grid items in a `min-width:0` track, which bounds the TRACK and not
   the item — so a long host or title still painted straight through the card's
   right edge and got hard-clipped by the border, with no ellipsis to say it had
   been. Each item has to opt in to being shrinkable itself. */
.sh-post__host {
  font-size: .64rem; letter-spacing: .12em; text-transform: uppercase; color: var(--quiet);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sh-post__t {
  font-size: .88rem; font-weight: 600; line-height: 1.35; min-width: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;
}

.sh__privacy {
  display: flex; align-items: center; gap: 7px; margin: 7px 0 0;
  font-size: .76rem; line-height: 1.5; color: var(--muted);
}
.sh__privacy .sh-i { width: 15px; height: 15px; flex: none; margin-top: 1px; color: var(--quiet); }

/* ── ANGLE ────────────────────────────────────────────────────────────────
   Native radios under the skin: arrow-key navigation, correct grouping and the
   checked state come from the platform. A div-and-JS radiogroup gets this
   wrong in ways axe cannot see. */
.sh-angles { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.sh-angle { cursor: pointer; }
.sh-angle input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.sh-angle__b {
  display: grid; padding: 8px 15px; min-height: 40px; align-content: center;
  border: 1px solid var(--rule-strong); border-radius: var(--radius-pill);
  transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}
/* The chips must not change SIZE when selected. Carrying the description
   inside the chip made the selected one wider than the rest, so choosing an
   angle reflowed the row and the other four jumped under the cursor. The
   description moved out to a single line below, where it can change without
   moving anything. */
.sh-angle__b { font-size: .86rem; font-weight: 500; color: var(--muted); }
.sh-angle:hover .sh-angle__b { border-color: var(--on-dark-mut); color: var(--on-dark); }
.sh-angle input:checked + .sh-angle__b {
  background: var(--gold); border-color: var(--gold); color: var(--ink); font-weight: 600;
}
.sh-angle input:focus-visible + .sh-angle__b { outline: 3px solid var(--gold); outline-offset: 3px; }

/* ── MESSAGE ──────────────────────────────────────────────────────────────
   Text until asked to be a field. Most advocates never edit; making them look
   at a textarea to find that out is the cost of assuming they will. */
/* No box. Once the message itself moved to the left-hand artifact, what is
   left here is one dim line and a link — and a border around that reads as a
   card with nothing in it. The textarea brings its own frame when it opens,
   which is the only moment this area is actually a field. */
/* ── THE THREE TOOLS ──────────────────────────────────────────────────────
   One row, one species, one weight. None of them is on the path to sharing, so
   none of them is styled as though it were: no borders, no fills, nothing that
   competes with the single filled action below. */
.sh-tools { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-5); }
/* QUIET BY DEFAULT. Five gold underlined links around one gold button is five
   things claiming the same importance as the action. These recede to muted body
   weight with no rule under them, and earn the accent only on hover, focus, or
   while the panel they own is open. The button is then the only lit thing on
   the screen, which is the entire point of having one action. */
.sh .sh-tool {
  background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
  font-size: .82rem; font-weight: 500; color: var(--muted);
  text-decoration: none; min-height: var(--tap);
  transition: color var(--dur-base) var(--ease-out);
}
.sh .sh-tool:hover { color: var(--gold); }
.sh .sh-tool[aria-expanded="true"] { color: var(--gold); font-weight: 600; }

/* What a tool opens. One frame for all three, so opening any of them feels like
   the same gesture rather than three different surfaces. */
.sh-panel { display: grid; gap: 7px; }
.sh-panel__ta {
  display: block; width: 100%; font: inherit; font-size: .95rem; line-height: 1.6;
  color: var(--on-dark); background: var(--stage); border: 1px solid var(--rule-strong);
  border-radius: var(--radius-md); padding: var(--space-3); resize: vertical;
}
.sh-panel__ta:focus-visible { border-color: var(--gold); }
.sh-panel__ta::placeholder { color: var(--quiet); }
.sh-panel__p { margin: 0; font-size: .74rem; color: var(--quiet); }

/* SECONDARY DESTINATIONS. Collapsed by default at every viewport: with one
   obvious action on screen there is nothing to choose between, and the row only
   ever mattered to someone who had already decided to send it somewhere
   specific. Expanding puts the choice directly ABOVE the action it renames, so
   the decision reads downward into its own consequence. */
.sh-more { display: grid; gap: var(--space-4); justify-items: start;
  padding-top: var(--space-4); border-top: 1px solid var(--rule); }
.sh-more__wrap { display: grid; gap: var(--space-4); justify-items: start; width: 100%; }
.sh-more .sh__fs { width: 100%; }


/* ── DESTINATION ──────────────────────────────────────────────────────────
   A row of quiet choices, NOT six equal tiles. Choosing one does not arm a
   separate button — it renames the primary action below. One control, one
   decision. */
/* ── DESTINATIONS ─────────────────────────────────────────────────────────
   Six bordered pills, two of them carrying the LinkedIn and Facebook marks,
   is the exact silhouette of a third-party sharing widget — the one thing this
   product must not look like. The boxes are gone. What is left is an editorial
   row: one hairline above and below, the marks at text weight, and the chosen
   destination marked the way a chosen tab is marked, by a gold rule under it
   and by being the only item at full strength. The campaign's visual system
   stays dominant and the platform marks do only their one job, which is to
   identify a destination. */
.sh-dests {
  display: flex; flex-wrap: wrap; align-items: stretch;
  border-block: 1px solid var(--rule);
}
.sh-dest { cursor: pointer; }
.sh-dest input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.sh-dest__b {
  display: flex; align-items: center; gap: 8px; padding: 0 14px;
  min-height: var(--tap); color: var(--quiet); background: transparent;
  border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.sh-dest__l { font-size: .86rem; font-weight: 500; }
.sh-dest .sh-i { width: 17px; height: 17px; flex: none; opacity: .8; }
.sh-dest:hover .sh-dest__b { color: var(--on-dark); }
.sh-dest input:checked + .sh-dest__b {
  color: var(--on-dark); border-bottom-color: var(--gold); font-weight: 600;
}
.sh-dest input:checked + .sh-dest__b .sh-i { opacity: 1; color: var(--gold); }
.sh-dest input:focus-visible + .sh-dest__b { outline: 3px solid var(--gold); outline-offset: -3px; }
/* Your own tools and someone else's platforms are not the same kind of thing,
   and a single undifferentiated run of six says they are. One hairline splits
   the families; it is the quietest possible way to say so. */
.sh-dest[data-family="direct"] + .sh-dest[data-family="social"] .sh-dest__b {
  border-left: 1px solid var(--rule); margin-left: var(--space-3); padding-left: var(--space-4);
}


/* ── THE PRIMARY ACTION ───────────────────────────────────────────────────
   Named after the destination, because it IS the destination. */
.sh .sh__go {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 52px; padding: 0 1.2em; cursor: pointer;
  font: inherit; font-size: 1rem; font-weight: 600; text-decoration: none;
  background: var(--gold); color: var(--ink); border: 0; border-radius: var(--radius-pill);
  box-shadow: 0 8px 24px rgba(218, 165, 32, .3);
  transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.sh .sh__go:hover { background: var(--gold-deep); color: #fff; transform: translateY(-1px); }
.sh .sh__go .sh-i { width: 19px; height: 19px; flex: none; }
.sh .sh__go .sh-i--done { display: none; }
/* Done. The campaign owns no green, so the gold is SPENT to paper and a check
   is drawn — colour is never the only carrier, the label and the live region
   both change too. */
.sh .sh__go[data-done="1"] { background: var(--on-dark); color: var(--ink); box-shadow: none; }
.sh .sh__go[data-done="1"] .sh-i--go { display: none; }
.sh .sh__go[data-done="1"] .sh-i--done { display: block; }

/* THE ACTION AND EVERYTHING IT HAS TO SAY. The warning about what a platform
   will actually accept, and the confirmation of what just happened, are the
   same conversation and belong in the same place — directly under the button,
   ahead of any secondary utility. They were previously separated by an
   unrelated link, which put two sentences with the same content a hundred
   pixels apart with a different control between them. */
.sh__act { display: grid; gap: var(--space-2); }
.sh__count { margin: 0; justify-self: end; font-size: .74rem; color: var(--quiet); }
.sh__count[data-over="1"] { color: var(--gold); font-weight: 600; }
.sh__say { display: grid; gap: 4px; }
.sh__say:empty { display: none; }
.sh__hint { margin: 0; font-size: .78rem; line-height: 1.5; color: var(--quiet); }
.sh__status { margin: 0; font-size: .82rem; line-height: 1.5; color: var(--sand); }
.sh__status:empty { display: none; }
.sh .sh-phone { justify-self: start; }
.sh__fallback {
  display: block; width: 100%; font: inherit; font-size: .82rem; line-height: 1.5;
  color: var(--on-dark); background: var(--stage); border: 1px solid var(--gold);
  border-radius: 8px; padding: var(--space-3); resize: vertical;
}
.sh__fallback::selection { background: var(--gold); color: var(--ink); }

/* ── SUB-VIEWS ────────────────────────────────────────────────────────────── */
.sh__view { display: grid; gap: var(--space-4); align-content: start; max-width: 620px; }
.sh .sh-back { justify-self: start; }
.sh-mail { margin: 0; display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: var(--radius-md); overflow: hidden; }
.sh-mail > div { display: grid; grid-template-columns: 88px 1fr; gap: var(--space-4); padding: var(--space-3) var(--space-4); background: var(--stage); }
.sh-mail dt { font-size: .66rem; letter-spacing: .12em; text-transform: uppercase; color: var(--quiet); padding-top: 3px; }
.sh-mail dd { margin: 0; font-size: .88rem; line-height: 1.6; }
.sh-mail__dim { color: var(--quiet); font-style: italic; }
.sh-mail__body { white-space: pre-wrap; }
.sh-mail__url { color: var(--sand); word-break: break-all; }

/* A footnote, and it was rendering at body size — as loud as the email it was
   annotating. It exists to remove a worry, not to be read first. */
.sh__note { margin: 0; font-size: .82rem; line-height: 1.6; color: var(--quiet); max-width: 56ch; }

.sh__view--qr { justify-items: start; }
.sh-qr { width: 172px; height: 172px; background: #fff; border-radius: var(--radius-md); padding: 12px; }
.sh-qr__t { margin: 0; font-size: .86rem; line-height: 1.6; color: var(--muted); max-width: 46ch; }

.sh-i { width: 18px; height: 18px; }
.sh-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.sh a:focus-visible, .sh button:focus-visible, .sh textarea:focus-visible {
  outline: 3px solid var(--gold); outline-offset: 3px; border-radius: 6px;
}

/* ── MOBILE ───────────────────────────────────────────────────────────────
   Simpler, not smaller. Native share does the destination work, so the
   destination row is short and the composed artifact is a thumbnail — the
   action has to be reachable without scrolling past reassurance. */
@media (max-width: 899px) {
  .sh { width: calc(100vw - 24px); max-width: 560px; }
  /* On a phone the action is the whole point, and opening the note pushes it
     under the fold. It stays put instead — the same treatment landscape needs,
     for the same reason, applied wherever the column can outgrow the screen. */
  /* The default phone state fits on one screen, so this never engages. It
     engages when the advocate opens the note, the editor, or the platform
     disclosure — and then it has to be a BAR. A gold pill fading out over a
     half-visible row of destinations is the same defect that made landscape
     look broken: its own ground, a hairline, full bleed, so content passing
     behind it reads as content passing behind a bar. */
  .sh__act {
    position: sticky; bottom: calc(var(--space-4) * -1); z-index: 2;
    background: var(--stage-2); border-top: 1px solid var(--rule);
    margin-inline: calc(var(--space-4) * -1); padding-inline: var(--space-4);
    padding-block: var(--space-3) var(--space-4);
  }
  /* The bar is sticky, so "scroll it to the bottom" would park the row exactly
     underneath it. The margin is the bar's own height plus air. */
  .sh__fs--dest { scroll-margin-bottom: 128px; }
  .sh__head { padding: var(--space-4) var(--space-4) var(--space-3); }
  .sh__title { font-size: 1.3rem; }
  .sh__body, .sh__view { padding: var(--space-4); gap: var(--space-6); }
}
/* The card is a strip at every size now, so the phone only needs it smaller —
   there is no longer a separate compact treatment to switch to. */
@media (max-width: 560px), (max-height: 620px) {
  .sh-post__card { grid-template-columns: 104px 1fr; }
  .sh-post__msg { font-size: .95rem; padding: var(--space-4); }
  .sh-qr { width: 132px; height: 132px; }
}

/* ── LANDSCAPE / SHORT VIEWPORTS ──────────────────────────────────────────
   Declared LAST, and all of it in one place. The previous pass wrote this art
   direction up in the layout section, where the phone rule and the compact-card
   rule below it silently overrode half of it at equal specificity — the type
   never shrank, and the clamp meant to stop the message eating the screen was
   applied to a font-size that had already been overridden back up. Order is the
   whole fight, so there is now nothing after it to lose to. */
@media (max-height: 620px) and (min-width: 620px) {
  /* ONE COLUMN HERE TOO. This rule used to force a two-zone grid, which was
     right when the product had two zones. After the reduction pass the zone
     wrappers are `display: contents`, so their children became direct grid
     items and the two columns dealt them out like cards: artifact, tools,
     action, disclosure — the last of them clipped to "end another way". The
     product is one column at every size now; landscape needs it SMALLER, not
     rebuilt. */
  .sh { width: min(560px, calc(100vw - 32px)); }
  .sh__body { grid-template-columns: 1fr; gap: var(--space-3); }

  /* A 375px-tall window is not a small desktop; it is a different screen, and
     the previous pass tried to solve it by floating the action over whatever
     happened to be beneath. A gold pill lying across a half-visible row of
     destinations does not read as "content continues below" — it reads as a
     rendering fault, and it hid three of the six destinations behind itself.
     The fix is to stop needing the overlay: everything that is explanation
     rather than decision comes out, the artifact is clamped to the three lines
     that identify it, and what is left — angle, destination, action — actually
     fits the height available. */
  /* 375px of height, and the product is header + artifact + tools + action.
     That fits only if every one of them is sized for THIS screen, so each is —
     and then the action never has to float over anything. A bar lying across a
     half-visible row of tools is the same defect this product has shipped
     twice; the fix is to stop needing the bar, not to style it better. */
  .sh__head { padding: var(--space-2) var(--space-5); }
  .sh__title { font-size: 1rem; }
  .sh__privacy { font-size: .68rem; margin-top: 1px; }
  .sh__privacy .sh-i { width: 13px; height: 13px; }
  .sh__body, .sh__view { padding: var(--space-3) var(--space-5) var(--space-3); gap: var(--space-3); }
  .sh__fs { gap: var(--space-2); }
  .sh__zh { font-size: .62rem; letter-spacing: .1em; }
  /* The message stays legible and stops eating the screen. Three lines is
     enough to recognise which wording is loaded — it is identification here,
     not reading matter, and "Edit the message" still shows every word. */
  /* NOT `-webkit-line-clamp`. The message carries a blank line between the
     advocate's note and the campaign's wording, which means `white-space:
     pre-wrap` — and pre-wrap stops Chromium clipping a `-webkit-box`, so the
     clamp drew its ellipsis and then let the next line spill out from under the
     border and collide with the card. A height bound plus a fade clips for
     real, keeps the paragraph break, and says "there is more" honestly instead
     of asserting an end that is not one. */
  .sh-post__msg {
    font-size: .8rem; line-height: 1.5; padding: var(--space-3);
    max-height: calc(2 * 1.5em + var(--space-6)); overflow: hidden;
    -webkit-mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
            mask-image: linear-gradient(to bottom, #000 62%, transparent 100%);
  }
  .sh-post__card { grid-template-columns: 64px 1fr; }
  .sh-post__meta { padding: var(--space-2) var(--space-3); gap: 2px; }
  .sh-post__host { font-size: .58rem; }
  .sh-post__t { -webkit-line-clamp: 1; font-size: .78rem; }
  .sh-dest__b { min-height: 38px; padding: 0 9px; }
  .sh-dest__l { font-size: .8rem; }
  .sh__go { min-height: 44px; font-size: .9rem; }
  .sh-tools { gap: var(--space-2) var(--space-4); }
  .sh-tool { min-height: 28px; font-size: .76rem; }
  .sh__say { font-size: .74rem; }
  .sh-more { padding-top: var(--space-3); gap: var(--space-3); }
  .sh-phone { display: none; }   /* a phone in landscape IS the phone */

  /* The safety net, for the advocate who opens the note or the editor and
     outgrows the height anyway. It is now a BAR — full column width, its own
     ground, a hairline on top — so on the rare occasion it does cover
     something it reads as a fixed bar with content behind it, which is a thing
     screens do, rather than as a pill lying across a sliced row. */
  .sh__act {
    position: sticky; bottom: calc(var(--space-4) * -1); z-index: 2;
    background: var(--stage-2); border-top: 1px solid var(--rule);
    margin-inline: calc(var(--space-6) * -1); padding-inline: var(--space-6);
    padding-block: var(--space-3) var(--space-4);
  }
  .sh__act::before { content: none; }
}

@media (prefers-reduced-motion: reduce) {
  .sh[open], .sh[open]::backdrop { animation: none; }
  .sh { transition: none; }
  .sh *, .sh__go { transition: none !important; }
  .sh__go:hover { transform: none; }
}

/* ── THE HOST PAGE HAS OPINIONS ABOUT <button> ────────────────────────────
   The WordPress theme this product is embedded in ships reset.css, which
   styles EVERY button as a pink outlined control:

     [type="button"], [type="submit"], button {
       background-color: transparent; border: 1px solid #c36; border-radius: 3px;
       color: #c36; padding: .5rem 1rem; font-size: 1rem; font-weight: 400; … }

   Its attribute selector scores (0,1,0) — exactly what this file's own
   single-class control rules scored — so it did not lose on specificity, it WON
   on order. On the deployed page that gave the three tools pink text and pink
   borders and, far worse, stripped the PRIMARY ACTION's gold background: "Copy
   message & link" rendered as #23221f ink on the #1c1913 dialog ground at
   1.1:1. The most important control in the advocacy product was invisible.

   Nothing local could show this. The design lab has no theme, so the candidate
   is correct in the lab and broken in production — the same shape of failure as
   the relative asset paths, and the same cure: the product must own what it
   renders rather than inherit it from wherever it is embedded.

   This neutraliser scores (0,1,1) so it beats the theme; the product's own
   control rules above are anchored under `.sh` so they score (0,2,0) and beat
   this. No value is duplicated and no design decision is restated — the
   approved declarations remain the only place any colour or metric is set. */
.sh button {
  background-color: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  font: inherit;
  text-align: inherit;
  text-transform: none;
  color: inherit;
}

/* ── THE HOST PAGE ALSO HAS OPINIONS ABOUT <a> ────────────────────────────
   Same defect as the <button> armour above, one element type across. The theme
   ships `a:link { color: #DAA520 }`, which scores (0,1,1) and therefore beat
   this file's single-class control rules at (0,1,0). On the deployed page that
   recoloured every anchor-based control in the document:

     .ty-btn--primary  →  gold text on the gold pill: 1:1. THE PRIMARY CALL TO
                          ACTION OF THE ADVOCACY PRODUCT RENDERED AS A BLANK
                          GOLD SHAPE. It is the one control the entire release
                          exists to deliver.
     .ty-nav__give     →  gold instead of cream, and its hover is also gold, so
                          the hover state became invisible too.
     .ty-foot__back    →  gold instead of sand.

   axe did not report it as a violation: it returned "Element has a 1:1 contrast
   ratio with the background" under INCOMPLETE, because the surrounding scrim is
   a gradient it cannot resolve — and the hosted gate counted only violations.
   Both the CSS and that gate are fixed; see qa/share-e2e/hosted-verify.mjs.

   The rules above are anchored under `.ty` so they score (0,2,0) and win. No
   value is restated — the approved declarations remain the only source. */

/* ═══════════════════════════════════════════════════════════════════════════
   THE DONOR-EXPERIENCE RECONCILIATION LEDGER
   ───────────────────────────────────────────────────────────────────────────
   THE CAMPAIGN LANDING PAGE DEFINES THE DONOR-EXPERIENCE DESIGN SYSTEM, and
   the approved Convergence A composition defines this page's narrative. Every
   value in this sheet that belongs to the SYSTEM rather than to this
   composition was read off the LIVE landing page with CDP at 1440x900 —
   computed values, not source declarations — and copied here.

   WHY COPIED AND NOT SHARED. `assets/campaign.css` is the landing page's
   stylesheet and another terminal is working in it right now: at the time of
   this pass the worktree copy differs from the deployed copy by 432 lines of
   in-flight Section-V work. Editing it — even to extract a shared token layer —
   would either deploy that work or be destroyed by it. So the values are
   DERIVED here, in this page's own scope, and the debt is recorded rather than
   left implicit.

   THE DEBT. When the landing workstream lands, these belong in one shared
   donor-experience token/component layer that both surfaces load:

       --paper --paper-warm --ink --ink-soft --ink-faint --gold --gold-deep
       --gold-text-light --sand --stage --stage-2 --rule --rule-strong
       --radius-pill --container-content --gutter --gutter-mobile
       the spacing scale, and the .btn / .btn--primary / .btn--ghost component.

   Until then, the parity gate is the guard: qa/thankyou-release/parity.mjs and
   the donor-experience matrix compare COMPUTED values on the two hosted pages
   and fail on divergence, so a change to campaign.css cannot silently leave
   this file behind.

   WHAT WAS MEASURED, AND WHERE IT IS APPLIED
   ───────────────────────────────────────────────────────────────────────────
     .camp .btn            999px · 600 16px/1 Inter · .95em 1.6em · 1.5px
                           -> .ty-btn, .ty-btn--ghost, .jsf-follow__submit
     .camp .btn--primary   #daa520 on #23221f · 0 8px 24px rgba(218,165,32,.3)
                           hover #7a5e0c on #fff · translateY(-1px)
                           -> .ty-btn--primary
     .camp .nav__brand     --tlm-wordmark 44px · lockup 63.03px · bar 91px
                           .is-solid rgba(251,246,232,.92) + blur(10px)
                           -> .ty-nav, .ty-nav__logo
     .camp footer.foot     #100e0b · clamp(52,5.6vw,88) / clamp(44,4.4vw,68)
                           eyebrow .72rem/.14em · mark 44px · legal .8rem/1.6
                           back #eace84 600 undecorated
                           -> .ty-foot and children
     .camp .herofilm       10px frame · gold play disc · Fraunces .89rem meta
                           -> .ty-joe__film and children
     tokens                --paper #fbf6e8 · --ink #23221f · --gold #daa520
                           --container-content 1200px · --gutter 24/16px
                           -> the :root block at the top of this sheet
     type                  Fraunces 500 display · Inter body
                           -> the type-system block in the host override layer

   WHAT IS DELIBERATELY NOT THE LANDING PAGE'S, and why
   ───────────────────────────────────────────────────────────────────────────
     · SCALE. Body sets at 17px here against the landing's 20.16px, and the
       greeting at clamp(2.5,5.6vw,4.6rem) against the hero's 100px. The
       emotional job differs; the system does not.
     · GROUND. This surface is paper with two dark movements (share, colophon);
       the landing is dark with paper chapters. Same materials, inverted
       proportions — siblings, not clones.
     · NAVIGATION. The masthead carries the lockup and the seal and nothing
       else. A closer with a nav is a homepage.
   ═══════════════════════════════════════════════════════════════════════════ */

/* The entrance stagger, moved off the element: safecss_filter_attr drops the
   inline custom property the prototype used. */
.ty .js-rise:nth-of-type(1){--i:0}
.ty .js-rise:nth-of-type(2){--i:1}
.ty .js-rise:nth-of-type(3){--i:2}
.ty .js-rise:nth-of-type(4){--i:3}
.ty .js-rise:nth-of-type(5){--i:4}
.ty .js-rise:nth-of-type(6){--i:5}
.ty .js-rise:nth-of-type(7){--i:6}
.ty .js-rise:nth-of-type(8){--i:7}
