/*
 * Structural CSS for published Hallow sites — base cascade, block layout and
 * chrome. Shared by every theme and versioned with the block components that
 * emit these classes; it references design tokens only, never literal colors,
 * fonts or radii, so swapping the token layer restyles the whole site.
 *
 * Owned by @web/site-blocks so the editor canvas, the preview endpoint and the
 * publish render worker consume identical CSS. Everything is scoped under
 * `.site` so nothing leaks into a host app (the console/Storybook) that mounts
 * a block preview.
 *
 * The cascade layer order is declared here and restated by every other sheet
 * that contributes to it, because a layer's position is fixed by wherever it is
 * first named and a later statement cannot move it. So the order below is the
 * order everywhere: a theme always beats structure, a section scheme always
 * beats the theme, and the site's brand always beats both, no matter which
 * order the pieces happen to be concatenated or injected in. Puck's own canvas
 * CSS is unlayered and therefore still outranks all four, which is what keeps
 * its selection outlines drawn over the site's styling.
 *
 * The names carry a `site-` prefix because these sheets are also loaded in
 * documents that run Tailwind, whose own `theme` layer would otherwise claim the
 * name first and strand the site's theme below its structure.
 *
 * Widths are measured against a container, never the viewport, because the same
 * page renders in a 390px editor canvas, a preview pane and a phone, and only
 * the phone is the window. There are two containers, and which one a rule reads
 * is decided by whether the query names `site`:
 *
 * The page container, `site` on `.site-chrome`. Everything outside the header
 * queries it by name, at one of four widths, so the whole site collapses on one
 * vocabulary:
 *
 *   40rem   (640px)  Columns collapse; the collection grid drops to 2-up
 *   45rem   (720px)  the hero's split arrangement stacks
 *   48rem   (768px)  "narrow": band padding, hero heights, touch targets, footer
 *   56.25rem (900px) the collection grid drops to 3-up
 *
 * The header's own container on `.site-header`. The collapsed navigation queries
 * it by the name `site-header-collapsed`, which the header carries only while it
 * should be collapsed — see "Collapsing the navigation" below — so the tier is
 * decided once and the collapsed layout is written once. It is nested inside the
 * page container, so a rule that must follow the page rather than the header has
 * to name `site` even when it targets something in the header.
 *
 * One consequence worth knowing: `cqi` cannot name a container, so the type
 * scale resolves against whichever container is nearest. Inside the header that
 * is the header, whose content box is the page minus the header's own padding.
 *
 * Inline-size containment makes `.site-chrome` the containing block for any
 * fixed-position descendant, so an overlaid header positions against it rather
 * than the window.
 */

@layer site-structure, site-theme, site-schemes, site-brand;

@layer site-structure {
  /* ---------------------------------------------------------------- Base cascade */

  .site h1,
  .site h2,
  .site h3,
  .site h4,
  .site h5,
  .site h6 {
    margin: 0 0 var(--heading-gap);
    /* A parish name is one long word more often than not, and at 320px it is
     wider than the column. */
    overflow-wrap: break-word;
    color: var(--color-heading);
    font-family: var(--font-heading);
    line-height: var(--line-height-heading);
    font-weight: var(--heading-weight);
    letter-spacing: var(--heading-tracking);
    text-transform: var(--heading-case);
  }

  .site h1 {
    font-size: var(--text-4xl);
  }
  .site h2 {
    font-size: var(--text-3xl);
  }
  .site h3 {
    font-size: var(--text-2xl);
  }
  .site h4 {
    font-size: var(--text-lg);
  }

  .site p {
    margin: 0 0 var(--flow-gap);
  }

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

  .site a:hover {
    color: var(--color-link-hover);
  }

  .site img,
  .site video {
    max-width: 100%;
    height: auto;
  }

  .site :where(h1, h2, h3, h4, p):last-child {
    margin-bottom: 0;
  }

  /* ---------------------------------------------------------------- Icons */

  /* `SiteIcon` stamps `data-site-icon` with its size token; the geometry lives
   here so blocks reference the scale instead of hardcoding pixels. The 24px
   width/height attributes the component also emits are the fallback for any
   context this stylesheet doesn't reach. */
  .site [data-site-icon] {
    flex: none;
  }

  .site [data-site-icon="sm"] {
    width: var(--icon-sm);
    height: var(--icon-sm);
  }

  .site [data-site-icon="md"] {
    width: var(--icon-md);
    height: var(--icon-md);
  }

  .site [data-site-icon="lg"] {
    width: var(--icon-lg);
    height: var(--icon-lg);
  }

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

  .site .block {
    box-sizing: border-box;
    width: 100%;
    padding-block: var(--block-pad, 0);
    margin-block: var(--block-margin, 0);
  }

  /* Geometry for the `container` and `edge` widths shared by the Image, Video and
   Map blocks. Both re-anchor the block to the site container, which only lands
   right where the block's containing block is itself centred on the page — the
   page column and a section's inner. A container that boxes its children in
   something narrower re-points these at its own box (see `.columns__col`);
   inheritance carries the override however deeply the block is nested. Any new
   container block has to do the same, so read these through the variables
   rather than repeating the width math. */
  .site {
    /* The page's own text colors, captured before any band can override them:
     a custom property substitutes where it is declared, so a block sitting in a
     recolored band can still reach the page palette through these. */
    --page-text: var(--color-text);
    --page-text-muted: var(--color-text-muted);
    --breakout-container-width: min(100cqi, var(--mw-xl));
    --breakout-container-margin: calc(50% - min(50cqi, var(--mw-xl) / 2));
    --breakout-edge-width: 100cqi;
    --breakout-edge-margin: calc(50% - 50cqi);
    --band-bleed-inset: -100cqi;
  }

  /* The band padding scale and the hero heights, resolved for the current
   container width. A block reads one name; whether it resolves to the wide or
   the narrow value is settled here rather than per block. */
  .site {
    --band-pad-none: var(--space-none);
    --band-pad-xs: var(--space-xs);
    --band-pad-sm: var(--space-sm);
    --band-pad-md: var(--space-md);
    --band-pad-lg: var(--space-lg);
    --band-pad-xl: var(--space-xl);
    --band-pad-2xl: var(--space-2xl);
    --hero-h-small: var(--hero-h-small-wide);
    --hero-h-medium: var(--hero-h-medium-wide);
    --hero-h-large: var(--hero-h-large-wide);
    --hero-h-full: var(--hero-h-full-wide);
  }

  /* A phone spends one step of the padding scale on the band and gets it back as
   content. The narrow values land on the chrome's children, not on `.site`
   itself: a container query never matches its own container, and `.site` is
   that container, so the same rule written against `.site` silently never
   applies and a phone keeps the wide values. Inheritance carries them down. */
  @container site (width < 48rem) {
    .site > * {
      --band-pad-xs: var(--space-none);
      --band-pad-sm: var(--space-xs);
      --band-pad-md: var(--space-sm);
      --band-pad-lg: var(--space-md);
      --band-pad-xl: var(--space-lg);
      --band-pad-2xl: var(--space-xl);
      --hero-h-small: var(--hero-h-small-narrow);
      --hero-h-medium: var(--hero-h-medium-narrow);
      --hero-h-large: var(--hero-h-large-narrow);
      --hero-h-full: var(--hero-h-full-narrow);
    }
  }

  .site .block[data-pad="none"] {
    --block-pad: var(--space-none);
  }
  .site .block[data-pad="xs"] {
    --block-pad: var(--space-xs);
  }
  .site .block[data-pad="sm"] {
    --block-pad: var(--space-sm);
  }
  .site .block[data-pad="md"] {
    --block-pad: var(--space-md);
  }
  .site .block[data-pad="lg"] {
    --block-pad: var(--space-lg);
  }
  .site .block[data-pad="xl"] {
    --block-pad: var(--space-xl);
  }
  .site .block[data-pad="2xl"] {
    --block-pad: var(--space-2xl);
  }

  .site .block[data-margin="none"] {
    --block-margin: var(--space-none);
  }
  .site .block[data-margin="xs"] {
    --block-margin: var(--space-xs);
  }
  .site .block[data-margin="sm"] {
    --block-margin: var(--space-sm);
  }
  .site .block[data-margin="md"] {
    --block-margin: var(--space-md);
  }
  .site .block[data-margin="lg"] {
    --block-margin: var(--space-lg);
  }
  .site .block[data-margin="xl"] {
    --block-margin: var(--space-xl);
  }
  .site .block[data-margin="2xl"] {
    --block-margin: var(--space-2xl);
  }

  .site .block[data-mw="sm"] {
    max-width: var(--mw-sm);
  }
  .site .block[data-mw="md"] {
    max-width: var(--mw-md);
  }
  .site .block[data-mw="lg"] {
    max-width: var(--mw-lg);
  }
  .site .block[data-mw="xl"] {
    max-width: var(--mw-xl);
  }
  /* A reading column sizes itself against the band it sits in, rather than to a
   fixed measure the band knows nothing about. Two absolute caps used to stack
   here — the band's `innerMaxWidth` and this one — and the narrower always won,
   so the band's width did nothing for text and the difference showed up as
   padding nobody chose. `--block-measure` reads 80% of whatever contains it,
   floored at the theme measure so a band already at or below reading width is
   still filled, and capped at `--measure-max` so an XL or edge-to-edge band
   cannot stretch a line past legibility. Section inners keep the flat
   `--mw-prose`: a band asked for a reading width is one, and a block then fills
   it. */
  .site .block[data-mw="prose"] {
    max-width: var(--block-measure);
  }

  .site .block[data-block-align="left"] {
    margin-inline: 0 auto;
  }
  .site .block[data-block-align="center"] {
    margin-inline: auto;
  }
  .site .block[data-block-align="right"] {
    margin-inline: auto 0;
  }

  /* Centred text centres its own box. `align` positions a block that `maxWidth`
   made narrower than its container, and its default is flush left — which for a
   prose-width heading inside a wide band puts centred text off the band's
   centre, reading as a misalignment rather than an arrangement. Only `center` is
   derived this way: symmetry with the container is the whole point of centring,
   where left- and right-aligned text is just as readable in a column pinned
   anywhere.

   Named per block rather than written against `.block`, because the Hero is the
   third block carrying `data-text-align` and it defaults to centred text, flush
   left and full bleed at once. A `.block` selector outranks
   `.site .hero[data-bleed]`, so it would replace the negative margins that make
   the breakout and leave the hero pushed half out of the viewport. The reading
   column this centres only exists on the two prose blocks anyway. */
  .site .title[data-block-align="left"][data-text-align="center"],
  .site .text-html[data-block-align="left"][data-text-align="center"] {
    margin-inline: auto;
  }

  .site .block[data-text-align="left"] {
    text-align: left;
  }
  .site .block[data-text-align="center"] {
    text-align: center;
  }
  .site .block[data-text-align="right"] {
    text-align: right;
  }
  .site .block[data-text-align="justify"] {
    text-align: justify;
  }

  /* ---------------------------------------------------------------- Title */

  .site .title__eyebrow {
    margin: 0 0 var(--eyebrow-gap);
    color: var(--color-primary);
    font-weight: var(--eyebrow-weight);
    letter-spacing: var(--eyebrow-tracking);
    text-transform: var(--eyebrow-case);
    font-size: var(--eyebrow-size);
  }

  .site .title__heading {
    margin: 0;
    color: var(--title-color, inherit);
  }

  .site .title__subtitle {
    margin: var(--caption-gap) 0 0;
    color: var(--color-text-muted);
    font-size: var(--subtitle-size);
  }

  /* ---------------------------------------------------------------- Text / HTML */

  .site .text-html {
    color: var(--text-color, inherit);
    overflow-wrap: break-word;
  }
  .site .text-html > :first-child {
    margin-top: 0;
  }
  .site .text-html > :last-child {
    margin-bottom: 0;
  }
  .site .text-html ul,
  .site .text-html ol {
    margin: 0 0 var(--flow-gap);
    padding-inline-start: var(--list-indent);
  }
  .site .text-html ul {
    list-style: disc outside;
  }
  .site .text-html ol {
    list-style: decimal outside;
  }
  .site .text-html li {
    margin-block: var(--list-gap);
  }
  /* `list-style-position: outside` keeps the marker out of the principal block
   box, so `text-align` on a centred/right-aligned block never reaches it: the
   <li> box still spans the full prose column, its text centres inside that
   box, and the marker stays parked at the padding edge. Shrink-wrapping the
   box to its content and re-aligning the box itself (not the text) keeps the
   marker adjacent to its line while leaving `list-style-position: outside`
   intact for justify/left, where it already reads correctly. Scoped to direct
   children only: a descendant selector would also re-centre a nested list
   inside a <li>, sizing its indent off its own text width instead of the
   shared --list-indent, so two sibling sub-lists could disagree by tens of
   pixels depending on what their items happen to say. */
  .site .text-html[data-text-align="center"] > ul,
  .site .text-html[data-text-align="center"] > ol,
  .site .text-html[data-text-align="center"] > blockquote,
  .site .text-html[data-text-align="right"] > ul,
  .site .text-html[data-text-align="right"] > ol,
  .site .text-html[data-text-align="right"] > blockquote {
    width: fit-content;
    text-align: left;
  }
  .site .text-html[data-text-align="center"] > ul,
  .site .text-html[data-text-align="center"] > ol,
  .site .text-html[data-text-align="center"] > blockquote {
    margin-inline: auto;
  }
  .site .text-html[data-text-align="right"] > ul,
  .site .text-html[data-text-align="right"] > ol,
  .site .text-html[data-text-align="right"] > blockquote {
    margin-inline-start: auto;
    margin-inline-end: 0;
  }
  .site .text-html blockquote {
    margin: 0 0 var(--flow-gap);
    padding-inline-start: var(--quote-pad);
    border-inline-start: var(--quote-rule-w) solid var(--color-primary);
    color: var(--color-text-muted);
    font-style: italic;
  }
  .site .text-html pre {
    overflow-x: auto;
    padding: var(--space-md);
    border-radius: var(--radius-md);
    background: var(--color-surface);
  }

  /* ---------------------------------------------------------------- Hero

   Mechanics only. How the band is *arranged* — direction, alignment, density,
   type scale, rounding — belongs to the theme, so it lives in each theme's layer
   file alongside that theme's `[data-variant]` overrides. Everything here is
   either a reset or the layering the overlay/background image depend on, which
   no variant should have to restate. */

  .site .hero {
    position: relative;
    overflow: hidden;
    background: var(--hero-bg, var(--color-hero-bg));
    color: var(--hero-text, var(--color-on-hero));
    isolation: isolate;
  }

  /* Full-bleed hero: break out of the centred content column to span the whole
   site container, the same trick .section uses (kept from causing horizontal
   scroll by `overflow-x: clip` on .site-chrome). The inner wrapper keeps the
   content at a readable width. */
  .site .hero[data-bleed] {
    left: 50%;
    right: 50%;
    width: 100cqi;
    max-width: none;
    margin-left: -50cqi;
    margin-right: -50cqi;
  }

  .site .hero__bg {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .site .hero__overlay {
    position: absolute;
    inset: 0;
    z-index: -1;
    background: var(--color-scrim);
    opacity: var(--hero-overlay, 0);
    pointer-events: none;
  }

  .site .hero[data-bg="image"]:not([data-variant="split"]) {
    color: var(--hero-text, var(--color-on-scrim));
  }
  .site
    .hero[data-bg="image"]:not([data-variant="split"]):not([data-text-color])
    .cta--secondary {
    background: var(--color-on-scrim);
    color: var(--color-scrim);
  }
  .site
    .hero[data-bg="image"]:not([data-variant="split"]):not([data-text-color])
    .cta--secondary:hover {
    color: var(--color-scrim);
  }

  .site
    .hero[data-bg="image"]:not([data-variant="split"]):not([data-text-color])
    .hero__overlay {
    opacity: max(var(--hero-overlay, 0), 0.6);
  }

  .site .hero__inner {
    width: 100%;
  }

  .site .hero__headline {
    margin: 0;
    color: inherit;
  }

  .site .hero__subhead {
    color: inherit;
    opacity: 0.92;
  }

  .site .hero__actions {
    display: flex;
    flex-wrap: wrap;
  }

  /* Once a theme stacks its split hero, `order` reaches the auto-placed image
   without touching the tracks that theme assigns, so which end the image lands
   on stays the block's setting. Unqualified by variant because the arrangements
   that keep the image absolutely positioned have no grid item to order. */
  @container site (width < 45rem) {
    .site .hero[data-split-image="last"] .hero__bg {
      order: 1;
    }
  }

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

  .site .cta {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--cta-pad-y) var(--cta-pad-x);
    border: var(--border-w) solid transparent;
    border-radius: var(--radius-cta);
    font-weight: var(--btn-weight);
    text-transform: var(--btn-case);
    text-decoration: none;
    line-height: var(--btn-leading);
  }

  @media (prefers-reduced-motion: no-preference) {
    .site .cta {
      transition:
        background-color var(--dur-base) var(--ease),
        color var(--dur-base) var(--ease);
    }
  }

  .site .cta--primary {
    background: var(--color-primary);
    color: var(--color-on-primary);
  }
  .site .cta--primary:hover {
    background: var(--color-primary-hover);
    color: var(--color-on-primary);
  }

  /* An inverted chip: it fills with the hero's own foreground, so its label
   cannot be `--color-text` inside a scheme, where the two are the same color.
   Schemes set `--cta-secondary-fg`; everywhere else the fallback is today's. */
  .site .cta--secondary {
    background: var(--color-on-hero);
    color: var(--cta-secondary-fg, var(--color-text));
  }
  /* It had no hover of its own, so it took the anchor rule's — which in a dark
   scheme paints a light link color onto this chip's light fill. */
  .site .cta--secondary:hover {
    filter: brightness(0.94);
    color: var(--cta-secondary-fg, var(--color-text));
  }

  .site .cta--outline {
    border-color: currentcolor;
    color: inherit;
  }
  .site .cta--outline:hover {
    background: var(--cta-outline-hover-bg);
    color: inherit;
  }

  /* ---------------------------------------------------------------- Button block */

  .site .button-block {
    display: flex;
  }
  .site .button-block[data-align="left"] {
    justify-content: flex-start;
  }
  .site .button-block[data-align="center"] {
    justify-content: center;
  }
  .site .button-block[data-align="right"] {
    justify-content: flex-end;
  }

  @container site (width < 48rem) {
    .site .button-block[data-full-mobile="true"] .btn {
      width: 100%;
    }
  }

  .site .btn {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: var(--border-w) solid transparent;
    font-weight: var(--btn-weight);
    text-transform: var(--btn-case);
    line-height: var(--btn-leading);
    text-decoration: none;
    cursor: pointer;
  }

  @media (prefers-reduced-motion: no-preference) {
    .site .btn {
      transition:
        background-color var(--dur-base) var(--ease),
        color var(--dur-base) var(--ease),
        border-color var(--dur-base) var(--ease);
    }
  }

  .site .btn--size-s {
    padding: var(--btn-pad-y-s) var(--btn-pad-x-s);
    font-size: var(--btn-text-s);
  }
  .site .btn--size-m {
    padding: var(--btn-pad-y) var(--btn-pad-x);
    font-size: var(--btn-text-m);
  }
  .site .btn--size-l {
    padding: var(--btn-pad-y-l) var(--btn-pad-x-l);
    font-size: var(--btn-text-l);
  }

  .site .btn--solid {
    background: var(--btn-accent, var(--color-primary));
    color: var(--btn-text, var(--color-on-primary));
  }
  /* Every hover below restates the variant's own color, because `.site a:hover`
   is more specific than the class that set it and these buttons are anchors:
   left to the cascade, a hovered solid button drew its label in the site's link
   hover color over its own background and disappeared. */
  .site .btn--solid:hover {
    filter: brightness(0.94);
    color: var(--btn-text, var(--color-on-primary));
  }
  .site .btn--outline {
    border-color: var(--btn-accent, var(--color-primary));
    color: var(--btn-text, var(--btn-accent, var(--color-primary)));
    background: transparent;
  }
  .site .btn--outline:hover {
    background: color-mix(
      in srgb,
      var(--btn-accent, var(--color-primary)) 10%,
      transparent
    );
    color: var(--btn-text, var(--btn-accent, var(--color-primary)));
  }
  .site .btn--link {
    padding-inline: 0;
    color: var(--btn-text, var(--btn-accent, var(--color-primary)));
    text-decoration: underline;
    background: transparent;
  }

  .site .btn--corners-square {
    border-radius: 0;
  }
  .site .btn--corners-soft {
    border-radius: var(--radius-btn);
  }
  .site .btn--corners-round {
    border-radius: var(--radius-round);
  }
  .site .btn--corners-pill {
    border-radius: var(--radius-pill);
  }

  /* Thumb-sized tap targets on a phone, taken as overlay rather than as layout:
   a button under 44px gets the height it is missing from a transparent child,
   so the small and text-only variants stay exactly the size the theme drew
   them. Giving them the height for real is what made every button on a phone
   taller than the same button on a laptop. */
  @container site (width < 48rem) {
    .site .btn,
    .site .cta {
      position: relative;
    }

    .site .btn::after,
    .site .cta::after {
      content: "";
      position: absolute;
      inset-inline: 0;
      top: 50%;
      height: var(--touch-target);
      translate: 0 -50%;
    }
  }

  /* ---------------------------------------------------------------- Image block */

  .site .image-block[data-width="thumb"] {
    max-width: var(--media-w-thumb);
  }
  .site .image-block[data-width="xs"] {
    max-width: var(--media-w-xs);
  }
  .site .image-block[data-width="small"] {
    max-width: var(--media-w-sm);
  }
  .site .image-block[data-width="medium"] {
    max-width: var(--media-w-md);
  }
  /* Container: match a section's XL content width (--mw-xl). The image sits in the
   narrower page column, so it breaks out and re-caps at XL, staying centred at
   every width (full-bleed once the site container is under XL). */
  .site .image-block[data-width="container"] {
    max-width: none;
    width: var(--breakout-container-width);
    margin-inline: var(--breakout-container-margin);
  }
  /* Edge-to-edge: break out of the centred content column to span the site
   container, the same trick .hero / .section use (horizontal scroll clipped by
   `overflow-x: clip` on .site). Only the inline margins are overridden, so the
   block's vertical margin still applies. */
  .site .image-block[data-width="edge"] {
    max-width: none;
    width: var(--breakout-edge-width);
    margin-inline: var(--breakout-edge-margin);
  }

  .site .image-block__figure {
    margin: 0;
  }

  .site .image-block__frame {
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
  }

  /* Auto height: the photo keeps its natural proportions. */
  .site .image-block__img {
    display: block;
    width: 100%;
    height: auto;
  }

  /* A named shape: the frame holds the ratio at every width and the photo fills
   it, so a row of photos lines up whatever their sources measure. The crop is
   taken from above centre because the subject of a parish photo is usually a
   face, and a centred crop is what takes the top of someone's head off. */
  .site .image-block__frame[data-aspect] .image-block__img {
    height: 100%;
  }
  /* Only a cropping frame has anything to anchor: on a letterboxed one this
   would push the whole photo up inside its own bars. */
  .site
    .image-block__frame[data-aspect].image-block__frame--fit-cover
    .image-block__img {
    object-position: 50% 25%;
  }
  .site .image-block__frame[data-aspect="1:1"] {
    aspect-ratio: 1 / 1;
  }
  .site .image-block__frame[data-aspect="4:3"] {
    aspect-ratio: 4 / 3;
  }
  .site .image-block__frame[data-aspect="3:2"] {
    aspect-ratio: 3 / 2;
  }
  .site .image-block__frame[data-aspect="16:9"] {
    aspect-ratio: 16 / 9;
  }
  .site .image-block__frame[data-aspect="3:4"] {
    aspect-ratio: 3 / 4;
  }

  .site .image-block__frame--fit-cover .image-block__img {
    object-fit: cover;
  }
  .site .image-block__frame--fit-contain .image-block__img {
    object-fit: contain;
  }

  .site .image-block__frame--corners-square {
    border-radius: 0;
  }
  .site .image-block__frame--corners-soft {
    border-radius: var(--radius-media);
  }
  .site .image-block__frame--corners-round {
    border-radius: var(--radius-round);
  }
  .site .image-block__frame--corners-pill {
    border-radius: var(--radius-pill);
  }

  .site .image-block__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 9;
    color: var(--color-text-muted);
    background: color-mix(in srgb, var(--color-text-muted) 12%, transparent);
  }
  .site .image-block__frame[data-aspect] .image-block__placeholder {
    aspect-ratio: auto;
    height: 100%;
  }

  .site .image-block__caption {
    margin: var(--caption-gap) 0 0;
    color: var(--color-text-muted);
    font-size: var(--caption-size);
    text-align: center;
  }

  .site .image-block__caption--overlay {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    margin: 0;
    padding: var(--caption-overlay-pad);
    color: var(--color-on-scrim);
    text-align: left;
    background: var(--caption-overlay-scrim);
  }

  /* ---------------------------------------------------------------- Video block */

  /* Widths mirror the Image block: the block caps its own width and centres,
   breaking out of the page column for the container/edge options. */
  .site .video-block[data-width="small"] {
    max-width: var(--media-w-sm);
  }
  .site .video-block[data-width="medium"] {
    max-width: var(--media-w-md);
  }
  .site .video-block[data-width="container"] {
    max-width: none;
    width: var(--breakout-container-width);
    margin-inline: var(--breakout-container-margin);
  }
  .site .video-block[data-width="edge"] {
    max-width: none;
    width: var(--breakout-edge-width);
    margin-inline: var(--breakout-edge-margin);
  }

  /* Responsive 16:9 frame the iframe / video fills. */
  .site .video-block__frame {
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
    aspect-ratio: 16 / 9;
    background: var(--color-scrim);
  }

  .site .video-block__iframe,
  .site .video-block__video {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
  }
  .site .video-block__video {
    object-fit: cover;
  }

  .site .video-block__frame--corners-square {
    border-radius: 0;
  }
  .site .video-block__frame--corners-soft {
    border-radius: var(--radius-media);
  }
  .site .video-block__frame--corners-round {
    border-radius: var(--radius-round);
  }
  .site .video-block__frame--corners-pill {
    border-radius: var(--radius-pill);
  }

  .site .video-block__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: var(--color-text-muted);
    background: color-mix(in srgb, var(--color-text-muted) 12%, transparent);
  }

  /* ---------------------------------------------------------------- Map block */

  /* Widths and frame mirror the Image / Video blocks: the block caps its own
   width and centres, breaking out of the page column for container/edge. */
  .site .map-block[data-width="small"] {
    max-width: var(--media-w-sm);
  }
  .site .map-block[data-width="medium"] {
    max-width: var(--media-w-md);
  }
  .site .map-block[data-width="container"] {
    max-width: none;
    width: var(--breakout-container-width);
    margin-inline: var(--breakout-container-margin);
  }
  .site .map-block[data-width="edge"] {
    max-width: none;
    width: var(--breakout-edge-width);
    margin-inline: var(--breakout-edge-margin);
  }

  /* Default 16:9 frame the snapshot fills; a fixed pixel height is set inline. */
  .site .map-block__frame {
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
    aspect-ratio: 16 / 9;
    background: color-mix(in srgb, var(--color-text-muted) 12%, transparent);
  }
  .site .map-block__frame[data-fixed-height] {
    aspect-ratio: auto;
  }

  /* The live map stacks above the fallback so the mounted (opaque) map always
   covers it; while the canvas is empty (pre-hydration / no-JS) it is
   transparent and lets the fallback show through, and pointer-events:none keeps
   the fallback link clickable until the boot script mounts the map. */
  .site .map-block__canvas {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    pointer-events: none;
  }
  .site .map-block__fallback {
    position: absolute;
    inset: 0;
    z-index: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    color: var(--color-text-muted);
    text-decoration: none;
  }
  .site .map-block__fallback-text {
    font-size: var(--caption-size);
  }

  .site .map-block__frame--corners-square {
    border-radius: 0;
  }
  .site .map-block__frame--corners-soft {
    border-radius: var(--radius-media);
  }
  .site .map-block__frame--corners-round {
    border-radius: var(--radius-round);
  }
  .site .map-block__frame--corners-pill {
    border-radius: var(--radius-pill);
  }

  .site .map-block__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: var(--color-text-muted);
  }

  /* ------------------------------------------------------ Hallow content block */

  /* Free Hallow sessions and collections, in one of three presentations. The
   layout follows Hallow Pray; every colour, corner, space and type step comes
   from the parish's own theme, so the play pill takes their brand rather than
   Hallow's black and all three modes follow a section's scheme band. */
  .site .hallow-content img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .site .hallow-content__line {
    display: flex;
    align-items: center;
    gap: var(--hallow-meta-gap);
    margin: 0;
    color: var(--color-text-muted);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    line-height: var(--line-height-body);
  }

  /* Deliberately the same step as the line above rather than one smaller: the
   xs step compiles to 8.16px on Gather, and this line carries the duration and
   the session count on every card and row. HAL-461 covers that step being too
   small generally; the block does not wait for it. */
  .site .hallow-content__line--meta {
    font-size: var(--text-sm);
  }

  .site .hallow-content__line-icon {
    display: inline-flex;
    align-items: center;
  }

  .site .hallow-content__empty {
    margin: 0;
    color: var(--color-text-muted);
    font-family: var(--font-body);
    font-size: var(--text-sm);
  }

  /* The editor's empty state. A dashed frame and a faux version of the chosen
   mode, so a freshly dragged block has the height and the shape of what it will
   hold instead of collapsing to one line of text. Never reaches a published
   page: the block renders nothing there. A literal hairline rather than
   --border-w, since a theme may set that to zero and the outline is the point. */
  .site .hallow-content[data-placeholder] {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-card);
    padding: var(--card-pad);
  }

  .site .hallow-ph__fill {
    background: color-mix(in srgb, var(--color-text-muted) 12%, transparent);
  }

  .site .hallow-ph__bar {
    display: block;
    height: var(--space-sm);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--color-text-muted) 12%, transparent);
  }

  .site .hallow-ph__bar--full {
    width: 100%;
  }

  .site .hallow-ph__bar--half {
    width: 60%;
  }

  .site .hallow-ph__bar--third {
    width: 35%;
  }

  .site .hallow-ph__dot,
  .site .hallow-ph__round {
    background: color-mix(in srgb, var(--color-text-muted) 12%, transparent);
  }

  .site .hallow-ph__dot {
    height: var(--space-sm);
    border-radius: var(--radius-pill);
  }

  /* Stands in for the play control without reusing its class, which paints
   itself the parish brand colour: a skeleton in full brand reads as content. */
  .site .hallow-ph__round {
    display: inline-block;
    flex: none;
    width: var(--icon-lg);
    height: var(--icon-lg);
    border-radius: var(--radius-pill);
  }

  /* The pill sits on artwork the parish does not control, so it takes the raised
   surface rather than a literal white: on a dark band it inverts with the scheme
   instead of staying a bright patch. */
  .site .hallow-content__pill {
    display: inline-flex;
    align-items: center;
    gap: var(--hallow-meta-gap);
    border-radius: var(--radius-pill);
    padding: var(--btn-pad-y-s) var(--btn-pad-x-s);
    color: var(--color-heading);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-weight: var(--btn-weight);
    line-height: var(--btn-leading);
    text-decoration: none;
    background: var(--color-surface-raised);
  }

  .site .hallow-content__round {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: var(--icon-lg);
    height: var(--icon-lg);
    border-radius: var(--radius-pill);
    color: var(--color-on-primary);
    text-decoration: none;
    background: var(--color-primary);
  }

  .site .hallow-content__round:hover {
    background: var(--color-primary-hover);
  }

  .site .hallow-content__pill:focus-visible,
  .site .hallow-content__round:focus-visible,
  .site .hallow-row:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  /* Browse cards: no border, no surface, no padding — the artwork is the card,
   exactly as in Hallow Pray. Anything boxing it would read as a second frame
   inside the band it already sits in. */
  .site .hallow-content__grid {
    display: grid;
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(var(--hallow-card-min), 100%), 1fr)
    );
    align-items: start;
    gap: var(--space-md);
  }

  /* The max-width belongs on the card, not the grid track. One picked session
   used to get a full-width track and grew its artwork to fill the page. A track
   cap looks like the same fix and is not: with a fixed max, auto-fit fits fewer
   columns, so a row of three wrapped to two. Tracks stay 1fr, so a full row
   still fills the column and only a short row leaves space beside it. */
  .site .hallow-card {
    display: flex;
    max-width: var(--hallow-card-max);
    flex-direction: column;
    gap: var(--card-text-gap);
  }

  .site .hallow-card__art {
    position: relative;
    overflow: hidden;
    margin-bottom: var(--card-gap);
    border-radius: var(--radius-card);
    aspect-ratio: 3 / 2;
    background: var(--color-surface);
  }

  .site .hallow-card__play {
    position: absolute;
    display: grid;
    inset: 0;
    place-items: center;
  }

  .site .hallow-card__title {
    margin: 0;
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-size: var(--text-lg);
    font-weight: var(--heading-weight);
    line-height: var(--line-height-heading);
  }

  /* List rows: the row itself is the play target, so it is the link — there is
   no control on its right. */
  .site .hallow-content__rows {
    display: grid;
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(var(--hallow-row-min), 100%), 1fr)
    );
    gap: var(--space-md);
  }

  .site .hallow-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    color: inherit;
    text-decoration: none;
  }

  .site .hallow-row__art {
    display: block;
    flex: none;
    overflow: hidden;
    width: var(--hallow-thumb);
    height: var(--hallow-thumb);
    border-radius: var(--radius-thumb);
    background: var(--color-surface);
  }

  .site .hallow-row__text {
    display: flex;
    flex: 1;
    min-width: 0;
    flex-direction: column;
    gap: var(--hallow-text-gap);
  }

  .site .hallow-row__title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-size: var(--text-base);
    font-weight: var(--heading-weight);
    line-height: var(--line-height-heading);
  }

  /* Collection detail: the artwork and description beside the session list. */
  .site .hallow-detail {
    display: grid;
    grid-template-columns: minmax(0, var(--hallow-panel-w)) minmax(0, 1fr);
    align-items: start;
    gap: var(--space-xl);
  }

  .site .hallow-detail__panel {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
  }

  .site .hallow-detail__title {
    margin: 0;
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-size: var(--text-2xl);
    font-weight: var(--heading-weight);
    letter-spacing: var(--heading-tracking);
    line-height: var(--line-height-heading);
  }

  .site .hallow-detail__art {
    overflow: hidden;
    border-radius: var(--radius-card);
    aspect-ratio: 4 / 3;
    background: var(--color-surface);
  }

  .site .hallow-detail__desc {
    margin: 0;
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: var(--text-base);
    line-height: var(--line-height-body);
  }

  .site .hallow-detail__count {
    margin: 0 0 var(--space-sm);
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-size: var(--text-base);
    font-weight: var(--heading-weight);
  }

  .site .hallow-detail__list {
    margin: 0;
    border-top: var(--border-w) solid var(--color-border);
    padding: 0;
    list-style: none;
  }

  .site .hallow-detail__item {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding-block: var(--space-sm);
  }

  .site .hallow-detail__index {
    flex: none;
    width: var(--hallow-index-w);
    color: var(--color-text-muted);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
  }

  .site .hallow-detail__item-body {
    display: flex;
    flex: 1;
    min-width: 0;
    flex-direction: column;
    gap: var(--hallow-text-gap);
    font-family: var(--font-body);
  }

  .site .hallow-detail__item-title {
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-size: var(--text-base);
    font-weight: var(--heading-weight);
  }

  .site .hallow-detail__item-desc {
    overflow: hidden;
    color: var(--color-text);
    font-size: var(--text-sm);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .site .hallow-detail__item-meta {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
  }

  /* One collapse point, on the page container the rest of the site reads. */
  @container site (width < 48rem) {
    .site .hallow-detail {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--space-lg);
    }
  }

  /* Thumb-sized tap targets, taken as overlay exactly as .btn / .cta do: a
   transparent child supplies the missing size so the controls stay the size the
   theme drew them. The round control is icon-only, so unlike .btn it needs the
   width too, and max() leaves anything already wide enough alone. */
  @container site (width < 48rem) {
    .site .hallow-content__pill,
    .site .hallow-content__round {
      position: relative;
    }

    .site .hallow-content__pill::after,
    .site .hallow-content__round::after {
      content: "";
      position: absolute;
      top: 50%;
      left: 50%;
      width: max(100%, var(--touch-target));
      height: max(100%, var(--touch-target));
      translate: -50% -50%;
    }
  }

  @container site (width < 40rem) {
    .site .hallow-detail__item-desc {
      display: none;
    }
  }

  /* ---------------------------------------------------------------- Liturgical Calendar block */

  /* Today's observances, fetched and filled by the `liturgical-day` runtime — a
   published site is static, so nothing here can be baked in. The shell renders
   with its date, season and rows empty, and reads as the link-out to Hallow
   until the runtime has a full list to swap in; a failed fetch therefore looks
   exactly like a visitor without JS rather than a half-built list.

   Every color comes from the theme except the liturgical color itself, which the
   runtime sets per row as `--liturgical-color` — the one value on the page that
   carries meaning the theme cannot supply. */
  .site .liturgical-block {
    --liturgical-rail-w: 4px;
    /* Inset by the card's own corner radius, so the rail starts where the card's
     straight edge does rather than partway around the curve, and rounded past
     half its own width so both ends are full caps. Reads the radius rather than
     fixing a value: it is 12px on two of the three themes and 6px on the third. */
    --liturgical-rail-inset: var(--radius-card);
    --liturgical-rail-radius: 2px;
    --liturgical-thumb-w: 6.5rem;
    /* The width the text column stops shrinking at, and therefore the point at
     which the Pray link drops to its own line. */
    --liturgical-text-basis: 12rem;
    /* Relative to the badge's own (small) type rather than the spacing scale, so
     the pill keeps its proportions under every theme's text sizes. */
    --liturgical-badge-pad: 0.15em 0.6em;
    /* A liturgical color is data, not a theme choice, so it arrives per row and
     nothing guarantees it reads against the tile it lands on: gold on a light
     tile, and red or black on a dark one, all start at or under 2:1 against the
     3:1 a meaningful graphic needs. Pushing it toward the end of the scale the
     tile is not fixes that — 30% suffices on a light tile, a dark one needs 40%
     because liturgical black starts closest to it. Measured in Chromium across
     three themes, four bands and the six colors of the rite: all 72 pairings
     clear 3:1, worst 3.08, each hue still recognizable. */
    --liturgical-push: #000;
    --liturgical-keep: 70%;

    /* The link chevron is drawn rather than typed: a text arrow depends on the
     theme font carrying U+2192, and the three site fonts do not all cover it. */
    --liturgical-chevron-size: 0.36em;
    --liturgical-chevron-w: 0.09em;
    --liturgical-chevron-gap: 0.45em;
  }

  .site .liturgical-block__header {
    display: flex;
    flex-direction: column;
    gap: var(--eyebrow-gap);
  }

  /* Only shown once there is a date under it: while pending the header is the
   eyebrow alone, and a rule under it would read as a dropped section. */
  .site .liturgical-block[data-state="ready"] .liturgical-block__header {
    padding-block-end: var(--space-md);
    border-block-end: var(--border-w) solid var(--color-border);
  }

  .site .liturgical-block__eyebrow {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--eyebrow-size);
    font-weight: var(--eyebrow-weight);
    letter-spacing: var(--eyebrow-tracking);
    text-transform: var(--eyebrow-case);
  }

  .site .liturgical-block__date {
    margin: 0;
  }

  .site .liturgical-block__season {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--subtitle-size);
  }

  /* The runtime fills these, so an unfilled one is not an empty line to style
   around — it is a line that does not exist yet. */
  .site .liturgical-block__date:empty,
  .site .liturgical-block__season:empty {
    display: none;
  }

  .site .liturgical-block__rows {
    display: flex;
    flex-direction: column;
    gap: var(--card-gap);
    margin: var(--space-md) 0 0;
    padding: 0;
    list-style: none;
  }

  .site .liturgical-block__rows:empty {
    display: none;
  }

  /* Wraps rather than switching to a narrow grid: a phone-width row puts the
   Pray link on its own line, and the same rule handles the slim (no artwork)
   variant with no second template. */
  .site .liturgical-block__row {
    --liturgical-ink: color-mix(
      in srgb,
      var(--liturgical-color, var(--color-primary)) var(--liturgical-keep),
      var(--liturgical-push)
    );
    position: relative;
    box-sizing: border-box;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--card-gap);
    padding: var(--card-pad);
    padding-inline-start: calc(
      var(--card-pad) + var(--liturgical-rail-inset) + var(--liturgical-rail-w)
    );
    border: var(--border-w) solid var(--color-border);
    border-radius: var(--radius-card);
    background: var(--color-surface);
  }

  /* A tile has to read as a tile, and `--color-surface` is the role a band paints
   itself with — so on a bright or dark band the card came out the same color as
   the band it sits on. Re-pointing the role on the row rather than restyling its
   parts means the badge, type and link follow without knowing a band is there.

   Bright bands borrow the pair the theme reserves for a filled element on that
   band: `--color-primary` is the white (or warm white) it flips to, and
   `--color-on-primary` is the foreground guaranteed legible on it. Dark bands
   only lift to the raised surface, because a white card in a dark band would
   read as a hole rather than a tile. */
  .site [data-scheme="bright"] .liturgical-block__row {
    --color-surface: var(--color-primary);
    /* The tile is the theme's own white, so it takes the page's dark text rather
     than the band's accent: accent-on-white was legible by the numbers but hard
     to read in practice. Links keep the accent so they still read as links. */
    --color-text: var(--page-text);
    --color-heading: var(--page-text);
    --color-text-muted: var(--page-text-muted);
    --color-link: var(--color-on-primary);
    --color-link-hover: var(--color-on-primary);
    --color-border: color-mix(
      in srgb,
      var(--color-on-primary) 14%,
      var(--color-primary)
    );
  }

  .site [data-scheme="dark"] .liturgical-block__row,
  .site [data-scheme="darkest"] .liturgical-block__row {
    --color-surface: var(--color-surface-raised);
    --liturgical-push: #fff;
    --liturgical-keep: 60%;
  }

  /* The liturgical color rail: a capsule sitting inside the card, painted the
   true color. The corrected `--liturgical-ink` is layered over it below, behind a
   support query — applied unconditionally, a browser without `color-mix()` gets
   an invalid value and loses the rail and the pill outline entirely. */
  .site .liturgical-block__row::before {
    content: "";
    position: absolute;
    inset-block: var(--liturgical-rail-inset);
    inset-inline-start: var(--liturgical-rail-inset);
    width: var(--liturgical-rail-w);
    border-radius: var(--liturgical-rail-radius);
    background: var(--liturgical-color, var(--color-primary));
  }

  .site .liturgical-block__art {
    flex: none;
    width: var(--liturgical-thumb-w);
    aspect-ratio: 4 / 3;
    border-radius: var(--radius-thumb);
    object-fit: cover;
  }

  /* `1 1 12rem` is what makes the row responsive: the text column grows to push
   the Pray link right, and drops the link to the next line once it cannot hold
   its basis. */
  .site .liturgical-block__text {
    flex: 1 1 var(--liturgical-text-basis);
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--card-text-gap);
    align-items: flex-start;
  }

  .site .liturgical-block__rank {
    margin: 0;
    padding: var(--liturgical-badge-pad);
    border-radius: var(--radius-pill);
    /* A tinted fill came out at 1.18:1 against a white tile — invisible — and
     tinting behind the label cost it contrast too. The hue reads from the
     corrected ink as a border instead, and the label sits on the tile in the
     band's own text color, so it is exactly the contrast the theme guarantees
     for body copy rather than something derived from a liturgical hue. */
    border: var(--border-w) solid var(--liturgical-color, var(--color-primary));
    color: var(--color-text);
    font-size: var(--eyebrow-size);
    font-weight: var(--eyebrow-weight);
    letter-spacing: var(--eyebrow-tracking);
    text-transform: uppercase;
  }

  /* The correction is an enhancement over the true color, never a prerequisite
   for painting it. */
  @supports (color: color-mix(in srgb, red 50%, blue)) {
    .site .liturgical-block__row::before {
      background: var(--liturgical-ink);
    }

    .site .liturgical-block__rank {
      border-color: var(--liturgical-ink);
    }
  }

  .site .liturgical-block__title {
    margin: 0;
    font-size: var(--text-lg);
  }

  .site .liturgical-block__subtitle {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--card-body-size);
  }

  .site .liturgical-block__pray,
  .site .liturgical-block__fallback {
    flex: none;
    color: var(--color-link);
    font-size: var(--card-body-size);
    font-weight: var(--btn-weight);
    text-decoration: none;
  }

  .site .liturgical-block__pray::after,
  .site .liturgical-block__fallback::after {
    content: "";
    display: inline-block;
    width: var(--liturgical-chevron-size);
    height: var(--liturgical-chevron-size);
    margin-inline-start: var(--liturgical-chevron-gap);
    border-top: var(--liturgical-chevron-w) solid currentColor;
    border-inline-end: var(--liturgical-chevron-w) solid currentColor;
    transform: rotate(45deg);
    vertical-align: middle;
  }

  .site .liturgical-block__pray:hover,
  .site .liturgical-block__fallback:hover {
    color: var(--color-link-hover);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
  }

  /* On a phone the row keeps its artwork beside the text — at the wide thumbnail
   size that alone would push the text past its basis and send the artwork to its
   own line — and the Pray link always takes a line of its own, so it does not sit
   inline on the slim rows and below the text on the ones with artwork. Declared
   after the rules it overrides: a container query adds no specificity, so source
   order is what settles it against `flex: none`. */
  @container site (width < 48rem) {
    .site .liturgical-block {
      --liturgical-thumb-w: 4.5rem;
      --liturgical-text-basis: 8rem;
    }

    .site .liturgical-block__pray {
      flex: 0 0 100%;
    }
  }

  .site .liturgical-block__attribution {
    margin: var(--space-md) 0 0;
    color: var(--color-text-muted);
    font-size: var(--caption-size);
  }

  .site .liturgical-block__attribution-link {
    color: var(--color-link);
    font-weight: var(--btn-weight);
    text-decoration: none;
  }

  /* The two ends of the same state: the link out stands in for the list until
   there is one, and the attribution only belongs under a list that rendered. */
  .site
    .liturgical-block:not([data-state="ready"])
    .liturgical-block__attribution,
  .site .liturgical-block[data-state="ready"] .liturgical-block__fallback {
    display: none;
  }

  .site .liturgical-block__fallback {
    display: inline-block;
    margin-block-start: var(--space-sm);
  }

  /* ---------------------------------------------------------------- Daily Quote block */

  /* Today's Hallow quote, fetched and rendered by the `daily-quote` runtime — a
   published site is static, so nothing here can be baked in. The card renders
   empty and the block reads as the link out to Hallow until the runtime has a
   whole quote, so a 204, a failed request and a visitor without JS all look the
   same rather than showing half a card.

   Two arrangements, picked in the inspector rather than by the theme: `centered`
   is the app-faithful pull quote and `panel` sits it in a card with an accent
   edge. Every colour, corner, space and type step is a theme token, so the card
   takes the parish's own face and palette rather than Hallow's. */
  .site .daily-quote-block {
    --daily-quote-round-size: 2.75rem;
    --daily-quote-glyph-size: 1.125rem;
    --daily-quote-skeleton-h: 1em;
    --daily-quote-skeleton-eyebrow-w: 7rem;
    --daily-quote-skeleton-action-w: 12rem;
    --daily-quote-skeleton-short: 70%;
    /* The share and Hallow actions sit under their own labels, so a long wording
     wraps inside the column instead of stretching the disc out of round. */
    --daily-quote-action-w: 8rem;
  }

  .site .daily-quote-block__figure {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-md);
    margin: 0;
    text-align: center;
  }

  .site .daily-quote-block__reference {
    color: var(--color-text-muted);
    font-family: var(--font-body);
    font-size: var(--eyebrow-size);
    font-weight: var(--eyebrow-weight);
    letter-spacing: var(--eyebrow-tracking);
    text-transform: var(--eyebrow-case);
  }

  /* The quote is the one piece of content the parish did not write, so it takes
   the heading face rather than body copy: the app's display serif expressed as
   whatever the theme's own display face is. Quotes arrive from the API already
   wrapped in quote marks, so nothing here adds its own. */
  .site .daily-quote-block__quote {
    margin: 0;
    max-inline-size: var(--measure);
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-size: var(--text-3xl);
    font-weight: var(--heading-weight);
    letter-spacing: var(--heading-tracking);
    line-height: var(--line-height-heading);
  }

  .site .daily-quote-block__quote p {
    margin: 0;
  }

  .site .daily-quote-block__actions {
    display: flex;
    align-items: start;
    justify-content: center;
    gap: var(--space-lg);
    margin-block-start: var(--space-xs);
  }

  /* A tinted disc rather than the app's thin ring: Gather ships borderless, so a
   ring drawn with the theme's own border width disappears there and the icon
   reads as bare. A mix of the text colour is visible under every theme and
   re-points itself on a band. */
  .site .daily-quote-block__round {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, currentColor 10%, transparent);
    padding: 0;
    color: var(--color-heading);
    block-size: var(--daily-quote-round-size);
    inline-size: var(--daily-quote-round-size);
    cursor: pointer;
  }

  .site .daily-quote-block__round svg {
    block-size: var(--daily-quote-glyph-size);
    inline-size: var(--daily-quote-glyph-size);
  }

  .site .daily-quote-block__action {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
    border: 0;
    background: transparent;
    padding: 0;
    max-inline-size: var(--daily-quote-action-w);
    color: var(--color-text-muted);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    line-height: 1.3;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
  }

  .site .daily-quote-block__action:hover .daily-quote-block__round,
  .site button.daily-quote-block__round:hover {
    background: color-mix(in srgb, var(--color-primary) 16%, transparent);
    color: var(--color-primary);
  }

  .site .daily-quote-block__action:hover .daily-quote-block__action-label {
    color: var(--color-heading);
  }

  .site .daily-quote-block__action:focus-visible,
  .site button.daily-quote-block__round:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  /* Panel: the only arrangement that draws a container — the theme's surface and
   card corner, so the quote reads as a quotation on a page that already has
   sections above and below it. */
  .site .daily-quote-block[data-variant="panel"] .daily-quote-block__figure {
    align-items: start;
    gap: var(--space-md);
    border-radius: var(--radius-card);
    background: var(--color-surface);
    padding: var(--card-pad);
    text-align: start;
  }

  /* On a band `--color-surface` is the colour the band paints itself with, so a
   panel taking it came out the same colour as the band it sits on. The raised
   surface is the role a theme guarantees sits above its own band, and every
   scheme keeps body text readable on it. */
  .site
    [data-scheme]
    .daily-quote-block[data-variant="panel"]
    .daily-quote-block__figure {
    background: var(--color-surface-raised);
  }

  .site .daily-quote-block[data-variant="panel"] .daily-quote-block__quote {
    font-size: var(--text-xl);
  }

  .site .daily-quote-block[data-variant="panel"] .daily-quote-block__actions {
    flex-wrap: wrap;
    align-items: center;
    justify-content: start;
    gap: var(--space-sm);
    margin-block-start: 0;
  }

  /* The share control is a peer of the Hallow button on this row, so it takes
   that button's exact box. `font-size` first, because a `<button>` does not
   inherit it: left to the UA's 13.33px default every `em` below resolved
   against the wrong number and the disc came out 7 to 12px short on every
   theme. With it set, the three terms are the button's own — leading, padding,
   border — and reproduce 48.38 / 46.19 / 41.56px on Modern, Cathedral and
   Gather. The border is carried as a real border rather than a term in the
   calc, because Gather's `--border-w` is a unitless `0` and adding that to a
   length voids the whole expression. A flex row cannot do this by stretching
   instead: it resolves the main size before the cross size, so `aspect-ratio`
   never sees the stretched height. */
  .site .daily-quote-block[data-variant="panel"] .daily-quote-block__round {
    box-sizing: content-box;
    border: var(--border-w) solid transparent;
    font-size: var(--btn-text-m);
    --daily-quote-round-size: calc(
      1em * var(--btn-leading) + var(--btn-pad-y) * 2
    );
  }

  /* One part shows per state. `pending` is what the server renders, so a visitor
   without JS keeps a working link; the runtime marks `loading` before it
   fetches, which is the shape a staffer drags onto the canvas; `failed` returns
   to the link. Only `ready` has a card. */
  .site .daily-quote-block .daily-quote-block__card,
  .site .daily-quote-block .daily-quote-block__placeholder,
  .site .daily-quote-block[data-state="ready"] .daily-quote-block__fallback,
  .site .daily-quote-block[data-state="loading"] .daily-quote-block__fallback {
    display: none;
  }

  .site .daily-quote-block[data-state="ready"] .daily-quote-block__card {
    display: block;
  }

  /* The placeholder holds the card's footprint rather than collapsing to a line,
   so dropping the block on the canvas reads as the thing it is about to become
   instead of a stray link. */
  .site
    .daily-quote-block[data-state="loading"]
    .daily-quote-block__placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-md);
  }

  .site .daily-quote-block__placeholder > * {
    display: block;
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, currentColor 12%, transparent);
    block-size: var(--daily-quote-skeleton-h);
  }

  .site .daily-quote-block__placeholder-eyebrow {
    inline-size: var(--daily-quote-skeleton-eyebrow-w);
    block-size: var(--eyebrow-size);
  }

  .site .daily-quote-block__placeholder-line {
    inline-size: min(100%, var(--measure));
    block-size: var(--text-3xl);
  }

  /* The second line only. `:last-of-type` matched the action span instead, since
   every placeholder part is a span. */
  .site
    .daily-quote-block__placeholder-line
    + .daily-quote-block__placeholder-line {
    inline-size: min(var(--daily-quote-skeleton-short), var(--measure));
  }

  .site .daily-quote-block__placeholder-action {
    inline-size: var(--daily-quote-skeleton-action-w);
    block-size: var(--daily-quote-round-size);
    border-radius: var(--radius-pill);
  }

  .site
    .daily-quote-block[data-variant="panel"]
    .daily-quote-block__placeholder {
    align-items: start;
    border-radius: var(--radius-card);
    background: var(--color-surface);
    padding: var(--card-pad);
  }

  .site
    [data-scheme]
    .daily-quote-block[data-variant="panel"]
    .daily-quote-block__placeholder {
    background: var(--color-surface-raised);
  }

  .site
    .daily-quote-block[data-variant="panel"]
    .daily-quote-block__placeholder-line {
    block-size: var(--text-xl);
  }

  /* With the Hallow link turned off there is nothing to fall back to, so a block
   that could not load leaves no empty band behind. */
  .site .daily-quote-block[data-state="failed"]:not([data-pray-link]) {
    display: none;
  }

  .site .daily-quote-block__fallback {
    display: inline-block;
    color: var(--color-link);
    font-size: var(--card-body-size);
    font-weight: var(--btn-weight);
    text-decoration: none;
  }

  .site .daily-quote-block__fallback:hover {
    color: var(--color-link-hover);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
  }

  /* Thumb-sized tap targets, taken as overlay exactly as `.btn` does, so the
   controls stay the size the theme drew them. */
  @container site (width < 48rem) {
    .site .daily-quote-block__quote {
      font-size: var(--text-2xl);
    }

    .site .daily-quote-block[data-variant="panel"] .daily-quote-block__quote {
      font-size: var(--text-lg);
    }

    .site .daily-quote-block__action,
    .site button.daily-quote-block__round {
      position: relative;
    }

    .site .daily-quote-block__action::after,
    .site button.daily-quote-block__round::after {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: 50%;
      block-size: max(100%, var(--touch-target));
      inline-size: max(100%, var(--touch-target));
      translate: -50% -50%;
    }
  }

  /* ---------------------------------------------------------------- Events block */

  /* The parish's upcoming events, fetched and filled by the `events` runtime. A
   published site is static, so nothing here can be baked in: the shell renders
   with its heading and an empty list, and reads as the link out to the parish
   calendar until the runtime has rows to swap in. A failed fetch therefore looks
   exactly like a visitor without JS rather than a half-built list.

   The arrangements follow the block's own width, not the screen's. The same
   block is dropped full-width, into one of two columns and into one of three, so
   it declares itself a container and the tiers below query it: a phone at 390px
   and a third of a desktop page are close to the same width, and the point is
   that they get the same layout.

   The list is flat at every width: rows separated by a hairline, no card behind
   them. What the tiers change is how much fits on a row. Narrowest is a text
   agenda, with the date on a line above the title and no RSVP. From ~26rem the
   date moves beside the text and RSVP joins it. From ~42rem the description
   joins them. A theme that wants cards instead adds them in its own arrangement
   sheet; Cathedral is the one that does. */
  .site .events-block {
    container: events / inline-size;
    /* The date column's width, in em so it holds its proportions under every
     theme's text scale rather than only the one it was measured on. */
    --events-date-w: 3.6em;

    /* The calendar view's own metrics, all in em so a theme's text scale takes
     the grid with it rather than leaving it sized for one theme. The cell
     height is what the dot tier needs; the titled tier raises it. */
    /* The grid's own rule. A theme that sets its border width to zero puts a
     hairline back in its arrangement sheet: seven columns cannot separate on
     spacing the way the list's rows do. */
    --events-grid-line: var(--border-w);

    --events-cell-h: 3.1em;
    --events-cell-pad: 0.35em;
    --events-cell-gap: 0.25em;
    --events-daymark-size: 1.7em;
    --events-dot-size: 0.36em;
    --events-pill-pad: 0.15em 0.4em;
    --events-hover-inset: 2px;
    --events-step-pad: 0.85em;
    --events-step-chevron: 0.55em;
    --events-step-chevron-w: 0.1em;
  }

  .site .events-block__heading {
    margin: 0;
  }

  .site .events-block__rows {
    display: flex;
    flex-direction: column;
    margin: var(--space-md) 0 0;
    padding: 0;
    list-style: none;
  }

  /* The runtime fills this, so an unfilled list is not an empty box to style
   around: it is a list that does not exist yet. */
  .site .events-block__rows:empty {
    display: none;
  }

  /* Declared as the agenda tier, the narrowest arrangement, and widened by the
   container queries below: a container query adds no specificity, so the tiers
   have to come after what they override. */
  .site .events-block__row {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: var(--card-text-gap);
    padding-block: var(--space-sm);
    border-block-end: var(--border-w) solid var(--color-border);
  }

  /* Muted text, as the liturgical eyebrow uses, rather than the theme's accent:
     the accent is tuned to read as a graphic against a band, and at this size on
     a dark band it lands at 2.69:1 on Cathedral against the 4.5:1 small text
     needs. */
  .site .events-block__date {
    display: flex;
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--eyebrow-size);
    font-weight: var(--eyebrow-weight);
    letter-spacing: var(--eyebrow-tracking);
    text-transform: var(--eyebrow-case);
  }

  /* On one line in the agenda tier the two halves need a space between them that
   the markup does not carry, because the tiers above stack them instead. */
  .site .events-block__month::after {
    content: " ";
    white-space: pre;
  }

  .site .events-block__text {
    display: flex;
    min-width: 0;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--card-text-gap);
  }

  .site .events-block__title {
    margin: 0;
    font-size: var(--text-base);
  }

  .site .events-block__title-link {
    color: inherit;
    text-decoration: none;
  }

  .site .events-block__title-link:hover {
    color: var(--color-link-hover);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
  }

  .site .events-block__meta {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--card-body-size);
  }

  /* Each is shown only from the tier that has room for it, so both ship in the
   DOM at every width and are revealed there rather than rendered twice. */
  .site .events-block__description,
  .site .events-block__rsvp {
    display: none;
    margin: 0;
  }

  .site .events-block__description {
    font-size: var(--card-body-size);
  }

  /* The shared button rule only reaches a thumb-sized target once the site
     container is phone-width. This control appears from the block's own 26rem
     tier, which a tablet reaches inside a column, and it sits a few pixels from
     the next row's title link, so a short tap target is a mis-tap. Overlay
     rather than real height, so the pill stays the size the theme drew it: the
     shortest is Gather's 42px. */
  .site .events-block__rsvp {
    position: relative;
  }

  .site .events-block__rsvp::after {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    inset-inline-end: auto;
    block-size: max(100%, var(--touch-target));
    inline-size: max(100%, var(--touch-target));
    translate: -50% -50%;
  }

  .site .events-block__empty {
    margin: var(--space-md) 0 0;
    color: var(--color-text-muted);
    font-size: var(--card-body-size);
  }

  /* Belongs to exactly one state, and it is not the one a shell still waiting on
   its first fetch is in. */
  .site .events-block:not([data-state="empty"]) .events-block__empty {
    display: none;
  }

  .site .events-block__actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-md);
    margin-block-start: var(--space-md);
  }

  .site .events-block__fallback,
  .site .events-block__subscribe {
    display: inline-flex;
    align-items: center;
    gap: var(--events-action-gap);
    min-block-size: var(--touch-target);
    color: var(--color-link);
    font-size: var(--card-body-size);
    font-weight: var(--btn-weight);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
  }

  .site .events-block__fallback:hover,
  .site .events-block__subscribe:hover {
    color: var(--color-link-hover);
  }

  .site .events-block__subscribe-mount {
    position: relative;
  }

  .site .events-block__subscribe-icon {
    flex: none;
    width: var(--events-subscribe-icon-size);
    height: var(--events-subscribe-icon-size);
  }

  .site .events-block__subscribe-menu > summary {
    list-style: none;
    cursor: pointer;
  }

  .site .events-block__subscribe-menu > summary::-webkit-details-marker {
    display: none;
  }

  .site .events-block__subscribe-panel {
    position: absolute;
    z-index: 2;
    inset-block-start: calc(100% + var(--space-xs));
    inset-inline-end: 0;
    box-sizing: border-box;
    display: grid;
    min-width: var(--dropdown-min-w);
    padding: var(--space-xs);
    border: var(--border-w) solid var(--color-border);
    border-radius: var(--radius-card);
    background: var(--color-surface);
    box-shadow: var(--dropdown-shadow);
    color: var(--color-text);
  }

  .site .events-block__subscribe-item {
    padding: var(--dropdown-item-pad-y) var(--dropdown-item-pad-x);
    border: 0;
    border-radius: var(--radius-thumb);
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: start;
    text-decoration: none;
    cursor: pointer;
  }

  .site .events-block__subscribe-item:hover {
    background: color-mix(in srgb, var(--color-primary) 10%, transparent);
    color: inherit;
  }

  .site .events-block__subscribe-item:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: calc(-1 * var(--focus-w));
  }

  .site .events-block__subscribe-fallback {
    display: grid;
    gap: var(--space-xs);
    padding: var(--dropdown-item-pad-y) var(--dropdown-item-pad-x);
  }

  .site .events-block__subscribe-fallback p {
    margin: 0;
    font-size: var(--text-sm);
  }

  .site .events-block__subscribe-fallback input {
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    padding: var(--space-xs);
    border: var(--border-w) solid var(--color-border);
    border-radius: var(--radius-thumb);
    background: var(--color-bg);
    color: var(--color-text);
    font: inherit;
  }

  /* Compact tier: the date moves beside the text, and RSVP joins the row. */
  @container events (width >= 26rem) {
    .site .events-block__row {
      flex-direction: row;
      align-items: center;
      gap: var(--card-gap);
      padding-block: var(--space-md);
    }

    .site .events-block__date {
      flex: none;
      flex-direction: column;
      align-items: flex-start;
      width: var(--events-date-w);
      line-height: 1.1;
    }

    .site .events-block__month::after {
      content: none;
    }

    .site .events-block__day {
      font-family: var(--font-heading);
      font-size: var(--text-lg);
      letter-spacing: normal;
    }

    .site .events-block__title {
      font-size: var(--text-lg);
    }

    .site .events-block__rsvp {
      display: inline-flex;
      flex: none;
    }
  }

  /* Wide tier: the same rows, with the description restored. */
  @container events (width >= 42rem) {
    .site .events-block__description {
      display: block;
    }

    /* The description makes a row tall enough that a centred date and RSVP float
     away from the title they belong to. */
    .site .events-block__row {
      align-items: flex-start;
    }
  }

  /* ------------------------------------------------ Events block, calendar view

   The same block set to a month. One DOM covers all three tiers, as the list
   view's do: every cell carries both its dot and its event pills, and the
   month's events are rendered twice more below the grid, once for the day in
   view and once as the whole month. Declared here as the narrowest arrangement,
   a month-by-month agenda, because a seven-column grid in a 380px column is
   unreadable; the containers below widen it into the dot grid and then the
   titled grid. Both lists below the grid are `events-block__rows`, so they are
   the list view's own rows and follow every theme decision made there.

   The runtime owns everything inside the frame: nothing here can be
   server-rendered, since a published page is static and would bake in whichever
   month it was published in. */
  .site .events-block__calendar:empty {
    display: none;
  }

  .site .events-block__monthbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    margin-block-start: var(--space-md);
  }

  .site .events-block__month-label {
    margin: 0;
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-size: var(--text-lg);
    text-align: center;
  }

  /* Drawn rather than typed, for the reason the fallback chevron is: the three
   site fonts do not all carry U+2039. A thumb-sized target on a control this
   small is the overlay the RSVP pill uses, so the glyph keeps its own size. */
  .site .events-block__step {
    position: relative;
    flex: none;
    border: var(--border-w) solid var(--color-border);
    /* The radius the day mark and the dot use, not the button one: that is `0`
     on a square-cornered theme, which drew this as a box. */
    border-radius: var(--radius-round);
    /* A tint rather than the border alone: on a theme whose border width is zero
     the control was two bare chevrons at the ends of the bar. Mixed from the
     text role, so it reads on a bright band and on a dark one, and heavy enough
     to actually carry that job: at 7% it measured 1.01 to 1.17:1 against the
     bands and was doing nothing. */
    background: color-mix(in srgb, var(--color-text) 18%, transparent);
    padding: var(--events-step-pad);
    color: var(--color-text);
    cursor: pointer;
  }

  .site .events-block__step::before {
    content: "";
    display: block;
    width: var(--events-step-chevron);
    height: var(--events-step-chevron);
    border-top: var(--events-step-chevron-w) solid currentColor;
    border-inline-end: var(--events-step-chevron-w) solid currentColor;
  }

  /* Two of the square's four edges are painted, so its ink is the half nearest
   the corner they meet at: rotated, that lands a quarter-turn's worth to one
   side and each chevron leaned away from the month name. The shift is half the
   ink's own width, `sqrt(2)/4` of the square, as a percentage of the square so
   it follows the glyph token rather than a pixel guess. */
  .site .events-block__step--prev::before {
    transform: translateX(35.36%) rotate(225deg);
  }

  .site .events-block__step--next::before {
    transform: translateX(-35.36%) rotate(45deg);
  }

  .site .events-block__step::after {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    block-size: max(100%, var(--touch-target));
    inline-size: max(100%, var(--touch-target));
    translate: -50% -50%;
  }

  .site .events-block__step:hover {
    border-color: var(--color-link-hover);
    background: color-mix(in srgb, var(--color-primary) 14%, transparent);
    color: var(--color-link-hover);
  }

  .site .events-block__step:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  /* The grid and the day in view belong to the two wider tiers. The agenda is
   the narrow one and ships shown, so a container query only ever has to hide
   it: a query adds no specificity, and the tiers come after what they override. */
  .site .events-block__grid,
  .site .events-block__picked {
    display: none;
  }

  .site .events-block__grid {
    border-block-end: var(--events-grid-line) solid var(--color-border);
  }

  .site .events-block__week {
    display: grid;
    /* `minmax(0, …)` rather than `1fr`: a column that cannot shrink below its
     content lets one long event title widen its whole column. */
    grid-template-columns: repeat(7, minmax(0, 1fr));
  }

  .site .events-block__weekday {
    padding-block: var(--space-xs);
    color: var(--color-text-muted);
    font-size: var(--eyebrow-size);
    font-weight: var(--eyebrow-weight);
    letter-spacing: var(--eyebrow-tracking);
    text-align: center;
    text-transform: var(--eyebrow-case);
  }

  .site .events-block__cell {
    position: relative;
    /* So the panel below can sit under the day's own content without escaping
     the cell or falling behind the grid's surface. */
    isolation: isolate;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--events-cell-gap);
    box-sizing: border-box;
    min-width: 0;
    min-height: var(--events-cell-h);
    border-block-start: var(--events-grid-line) solid var(--color-border);
    padding: var(--events-cell-pad);
    color: var(--color-text);
    font-size: var(--card-body-size);
  }

  /* Every day opens, whether or not it carries anything, so every day is a
   target. The padding squares either side of the month are not days. */
  .site .events-block__cell:not(.events-block__cell--blank) {
    cursor: pointer;
  }

  /* The panel a day lights up with, inset from the rule so the grid's lines stay
   unbroken and the tint reads as one rounded target rather than a filled square.
   Painted in the site's accent, so it is whatever colour the parish set. */
  .site .events-block__cell:not(.events-block__cell--blank)::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: var(--events-hover-inset);
    border-radius: var(--radius-thumb);
    background: transparent;
  }

  .site .events-block__cell:not(.events-block__cell--blank):hover::before {
    background: color-mix(in srgb, var(--color-primary) 12%, transparent);
  }

  .site .events-block__cell:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: calc(-1 * var(--focus-offset));
  }

  /* Today is marked by its number alone, so the mark survives on a day that is
   also the selected one and on a day with a pill in it. */
  .site .events-block__cell[data-today] .events-block__cell-day {
    border-radius: var(--radius-round);
    background: var(--color-primary);
    color: var(--color-on-primary);
  }

  .site .events-block__cell-day {
    display: grid;
    place-items: center;
    min-width: var(--events-daymark-size);
    min-height: var(--events-daymark-size);
  }

  .site .events-block__cell-dot {
    width: var(--events-dot-size);
    height: var(--events-dot-size);
    border-radius: var(--radius-round);
    background: var(--color-primary);
  }

  /* Absent, not transparent: an empty circle still takes a row's height and left
   every cell in the grid taller than the number it holds. */
  .site .events-block__cell:not([data-events]) .events-block__cell-dot {
    display: none;
  }

  .site .events-block__pills {
    display: none;
    min-width: 0;
    width: 100%;
    flex-direction: column;
    gap: var(--events-cell-gap);
  }

  .site .events-block__pill {
    display: block;
    overflow: hidden;
    border-radius: var(--radius-thumb);
    background: color-mix(in srgb, var(--color-primary) 12%, transparent);
    padding: var(--events-pill-pad);
    color: var(--color-text);
    font-size: var(--text-xs);
    line-height: 1.3;
    text-align: start;
    text-decoration: none;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .site .events-block__pill:hover {
    background: color-mix(in srgb, var(--color-primary) 22%, transparent);
  }

  .site .events-block__picked-heading {
    margin: var(--space-md) 0 0;
    color: var(--color-heading);
    font-size: var(--text-base);
  }

  /* The day in view is a heading over its own rows, so the rows under it do not
   also open with the gap the standalone list wants. */
  .site .events-block__picked .events-block__rows {
    margin-block-start: 0;
  }

  .site .events-block__note {
    margin: var(--space-md) 0 0;
    color: var(--color-text-muted);
    font-size: var(--card-body-size);
  }

  /* Dot tier: the grid arrives, and the day in view is the list under it. The
   month's own agenda goes away, because the grid is now the month. */
  @container events (width >= 30rem) {
    .site .events-block__grid,
    .site .events-block__picked {
      display: block;
    }

    .site .events-block__agenda {
      display: none;
    }
  }

  /* Titled tier: pills replace the dot, and the cells grow to hold them. The grid
   is the month on its own here, with no list stacked under it: the titles are
   already in the cells. Picking a day still opens it, which is how a keyboard
   reaches the links, the pills inside a cell being out of the tab order. */
  @container events (width >= 42rem) {
    .site .events-block__picked {
      display: none;
    }

    .site .events-block__picked--chosen {
      display: block;
    }

    .site .events-block__cell {
      --events-cell-h: 5.5em;
      --events-cell-pad: var(--space-xs);
      align-items: stretch;
    }

    /* Kept to its own size against a stretched cell, so the mark on today stays
     a circle rather than becoming a bar across the column. */
    .site .events-block__cell-day {
      align-self: flex-start;
      place-items: center;
    }

    .site .events-block__cell-dot {
      display: none;
    }

    .site .events-block__pills {
      display: flex;
    }
  }

  /* ---------------------------------------------------------------- Form block */

  /* A parish form, mounted by the `form` runtime; the only block that takes input. */
  .site .form-block {
    container: form-block / inline-size;
    /* Fixed steps: the theme space scale is page rhythm, too wide between questions. */
    --form-section-gap: 0.875rem;
    --form-intro-gap: 1rem;
    --form-field-gap: 1.125rem;
    --form-note-gap: 0.4375rem;
    --form-control-pad-y: 0.65em;
    --form-control-pad-x: 0.875rem;
    --form-control-height: 3.25rem;
    /* 16px keeps iOS from zooming the page into a field the visitor taps. */
    --form-control-size: 1rem;
    --form-control-weight: 500;
    --form-control-radius: var(--radius-md);
    --form-control-border-w: 1px;
    --form-control-bg: var(--color-surface-raised);
    --form-card-bg: var(--color-surface-raised);
    --form-card-border: var(--color-border);
    --form-card-radius: var(--radius-card);
    --form-card-pad: 1.625rem 1.75rem;
    --form-card-pad-narrow: 1.25rem 1.125rem;
    --form-title-size: clamp(1.875rem, 1.4rem + 2.4cqi, 2.5625rem);
    --form-description-size: clamp(0.96875rem, 0.9rem + 0.4cqi, 1.0625rem);
    --form-section-name-size: 0.75rem;
    --form-section-name-weight: 700;
    --form-section-name-tracking: 0.12em;
    --form-section-name-case: uppercase;
    --form-intro-inner-gap: 0.875rem;
    --form-description-measure: 48ch;
    --form-section-description-gap: 0.1875rem;
    --form-section-description-size: 0.8125rem;
    --form-row-gap: 0.75rem;
    --form-optional-gap: 0.4375rem;
    --form-textarea-min-h: 6.125rem;
    --form-textarea-pad-y: 0.8125rem;
    --form-option-pad: 0.8125rem 0.875rem;
    --form-check-pad: 0.6875rem 0.8125rem;
    --form-actions-gap: 0.5rem;
    --form-actions-offset: 0.25rem;
    --form-submit-pad-x: 1.875rem;
    --form-label-case: none;
    --form-label-tracking: normal;
    --form-submit-case: none;
    --form-submit-tracking: normal;
    --form-submit-size: 1rem;
    --form-submit-height: 3.375rem;
    --form-submit-min-w: 12.5rem;
    --form-submit-radius: var(--radius-btn);
    --form-control-border: var(--color-border);
    --form-control-border-hover: var(--color-text-muted);
    /* Mixed toward the text so an empty field's only marker clears WCAG 1.4.11's 3:1. */
    --form-control-edge: color-mix(
      in srgb,
      var(--color-text) 50%,
      var(--form-control-border)
    );
    --form-label-weight: 600;
    --form-label-size: 0.84375rem;
    --form-note-size: 0.78125rem;
    --form-notice-size: 0.875rem;
    /* The seed `compile.ts` solves per band; only reached without a theme. */
    --form-danger: #b3261e;
    --form-danger-edge: var(--form-danger);
    --form-danger-on-card: var(--form-danger);
    --form-danger-on-card-edge: var(--form-danger-edge);
    --form-option-gap: 0.5rem;
    --form-option-inner-gap: 0.6875rem;
    --form-option-size: 0.90625rem;
    --form-option-leading: 1.4;
    --form-choice-size: 1.25rem;
    --form-choice-border-w: 2px;
    /* Mixed further than a field's edge: a tick box has no text to place it. */
    --form-choice-border: color-mix(
      in srgb,
      var(--color-text) 40%,
      var(--form-control-border)
    );
    --form-skeleton-label-w: 8rem;
    --form-skeleton-action-w: var(--form-submit-min-w);
    --form-skeleton-action-h: var(--form-submit-height);
    --form-select-arrow-space: 0.6em;
    --form-currency-gap: 0.75rem;
    --form-phone-gap: 0.5rem;
    --form-phone-chip-gap: 0.375rem;
    --form-phone-code-min-w: 4ch;
    --form-choice-dot: 0.2em;
    /* A mask rather than an image, so the tick carries no colour of its own. */
    --form-check-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
    --form-check-size: 65%;
    --form-option-text-gap: 0.15em;
    /* The trimmed label has no descender room of its own below it. */
    --form-option-text-gap-trimmed: 0.45em;
    /* Heavier only where a description sits under the label, so it reads as its heading. */
    --form-option-label-weight: var(--form-label-weight);
    --form-strong-weight: 700;
    --form-success-w: 36.25rem;
    --form-success-gap: 1.625rem;
    --form-success-pad: 2.5rem 2.5rem 2rem;
    --form-success-pad-narrow: 1.625rem 1.25rem 1.375rem;
    --form-success-title-size: 2.0625rem;
    --form-success-title-size-narrow: 1.75rem;
    --form-success-body-size: 0.96875rem;
    --form-summary-label-w: 8rem;
    --form-summary-label-w-narrow: 6.5rem;
    --form-summary-value-size: 0.90625rem;
    --form-summary-value-weight: 600;
    --form-success-title-measure: 22ch;
    --form-success-body-measure: 42ch;
    --form-success-actions-gap: 0.625rem;
    --form-success-actions-pad: 1.25rem;
    --form-summary-pad: 0.25rem 1rem;
    --form-summary-radius-max: 1rem;
    --form-summary-row-gap: 1rem;
    --form-summary-row-pad: 0.8125rem;
    --form-error-gap: 0.35em;
    --form-date-gap: 0.6em;
    --form-calendar-gap: var(--space-xs);
    --form-calendar-pad: var(--space-sm);
    --form-calendar-offset: var(--space-xs);
    --form-calendar-cell: 2.4em;
    --form-calendar-nav-size: 1.9em;
  }

  /* A scheme on the block makes the block the band, as in Section and Columns. */
  .site .form-block[data-scheme] {
    background: var(--color-bg);
    color: var(--color-text);
  }
  /* One part shows per state; the no-JavaScript note waits for `.no-js` below. */
  .site .form-block__mount,
  .site .form-block__placeholder,
  .site .form-block__fallback,
  .site .form-block__failed {
    display: none;
  }

  .site .form-block[data-state="ready"] .form-block__mount,
  .site .form-block[data-state="submitting"] .form-block__mount,
  .site .form-block[data-state="success"] .form-block__mount {
    display: block;
  }

  .site .form-block[data-state="failed"] .form-block__failed {
    display: block;
  }

  /* Holds a real form's footprint so the page does not jump when the definition lands. */
  .site
    .form-block:is([data-state="pending"], [data-state="loading"])
    .form-block__placeholder {
    display: flex;
    flex-direction: column;
    gap: var(--form-field-gap);
  }

  /* `html` in front so these outrank the state rules above, which carry one class more. */
  html.no-js .site .form-block .form-block__placeholder {
    display: none;
  }

  html.no-js .site .form-block .form-block__fallback {
    display: block;
  }

  .site .form-block__placeholder > * {
    display: block;
    border-radius: var(--form-control-radius);
    background: color-mix(in srgb, currentColor 12%, transparent);
  }

  .site .form-block__placeholder-heading {
    inline-size: var(--form-skeleton-label-w);
    block-size: var(--text-xl);
    border-radius: var(--radius-pill);
  }

  .site .form-block__placeholder-field {
    inline-size: 100%;
    block-size: max(var(--touch-target), var(--form-control-height));
  }

  .site .form-block__placeholder-action {
    align-self: end;
    inline-size: var(--form-skeleton-action-w);
    block-size: var(--form-skeleton-action-h);
  }

  .site .form-block__fallback,
  .site .form-block__failed {
    margin: 0;
    color: var(--color-text-muted);
    font-family: var(--font-body);
    font-size: var(--card-body-size);
  }

  .site .form-block__fallback-note,
  .site .form-block__failed-note,
  .site .form-block__fallback-text {
    margin: 0 0 var(--form-note-gap);
  }

  .site .form-block__fallback-text:last-child,
  .site .form-block__failed .form-block__fallback-text {
    margin-block-end: 0;
  }

  .site .form-block__fallback-link {
    color: var(--color-link);
    font-weight: var(--btn-weight);
    text-decoration: none;
  }

  .site .form-block__fallback-link:hover {
    color: var(--color-link-hover);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
  }

  /* ------- The form itself */

  .site .form-block__form {
    display: flex;
    flex-direction: column;
    gap: var(--form-section-gap);
    font-family: var(--font-body);
  }

  /* Locked while in flight, but not dimmed: the answers stay legible. */
  .site .form-block__form--submitting {
    cursor: progress;
  }

  /* A step above a section heading, so the two read as a title and its parts. */
  .site .form-block__intro {
    display: flex;
    flex-direction: column;
    gap: var(--form-intro-inner-gap);
    margin-block-end: var(--form-intro-gap);
  }

  .site .form-block__title {
    margin: 0;
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-size: var(--form-title-size);
    font-weight: var(--heading-weight);
    letter-spacing: var(--heading-tracking);
    line-height: 1.1;
  }

  .site .form-block__description {
    margin: 0;
    max-inline-size: var(--form-description-measure);
    color: var(--color-text-muted);
    font-size: var(--form-description-size);
    line-height: 1.6;
  }

  /* Each section is its own card, so a long form reads as a few short steps. */
  .site .form-block__section {
    display: flex;
    flex-direction: column;
    gap: var(--form-field-gap);
    border: 1px solid var(--form-card-border);
    border-radius: var(--form-card-radius);
    background: var(--form-card-bg);
    padding: var(--form-card-pad-narrow);
  }

  @container form-block (width >= 26rem) {
    .site .form-block__section {
      padding: var(--form-card-pad);
    }
  }

  .site .form-block__section-name {
    margin: 0;
    color: var(--color-heading);
    font-family: var(--font-body);
    font-size: var(--form-section-name-size);
    font-weight: var(--form-section-name-weight);
    letter-spacing: var(--form-section-name-tracking);
    line-height: var(--line-height-heading);
    text-transform: var(--form-section-name-case);
  }

  /* A single-section form has no name of its own to print here. */
  .site .form-block__section:first-child .form-block__section-name:empty {
    display: none;
  }

  .site .form-block__section-description {
    margin: calc(
        var(--form-field-gap) * -1 + var(--form-section-description-gap)
      )
      0 0;
    max-inline-size: var(--measure);
    color: var(--color-text-muted);
    font-size: var(--form-section-description-size);
    font-weight: 500;
    line-height: 1.45;
  }

  .site .form-block__fields {
    display: flex;
    flex-direction: column;
    gap: var(--form-field-gap);
  }

  /* Half-width questions pair only once there is room; stacked at 375px. */
  .site .form-block__row {
    display: flex;
    flex-direction: column;
    gap: var(--form-field-gap) var(--form-row-gap);
  }

  @container form-block (width >= 26rem) {
    .site .form-block__row {
      flex-direction: row;
    }

    .site .form-block__row > * {
      flex: 1 1 0;
      min-inline-size: 0;
    }
  }

  /* ------- One question */

  .site .form-block__field {
    display: flex;
    flex-direction: column;
    gap: var(--form-note-gap);
    margin: 0;
    border: 0;
    padding: 0;
    min-inline-size: 0;
  }

  .site .form-block__label {
    display: block;
    padding: 0;
    color: var(--color-heading);
    font-family: var(--font-body);
    font-size: var(--form-label-size);
    font-weight: var(--form-label-weight);
    letter-spacing: var(--form-label-tracking);
    line-height: 1.4;
    text-transform: var(--form-label-case);
  }

  /* A legend sits in the fieldset's own slot, out of reach of the column gap. */
  .site legend.form-block__label {
    margin-block-end: var(--form-note-gap);
  }

  /* Set in the label's case, so an uppercase label does not shout it. */
  .site .form-block__optional {
    margin-inline-start: var(--form-optional-gap);
    color: var(--color-text-muted);
    font-size: var(--form-note-size);
    font-weight: 500;
    letter-spacing: normal;
    text-transform: none;
  }

  .site .form-block__help,
  .site .form-block__error {
    margin: 0;
    max-inline-size: var(--measure);
    font-size: var(--form-note-size);
    font-weight: 500;
    line-height: 1.45;
  }

  .site .form-block__help {
    color: var(--color-text-muted);
  }

  /* Inside the section card, which a scheme fills lighter than its canvas. */
  .site .form-block__error {
    display: flex;
    gap: var(--form-error-gap);
    align-items: flex-start;
    color: var(--form-danger-on-card);
    font-weight: var(--form-label-weight);
  }

  .site .form-block__error .form-block__icon {
    flex: none;
    color: var(--form-danger-on-card-edge);
  }

  .site .form-block__error-summary .form-block__icon {
    flex: none;
    color: var(--form-danger-edge);
  }

  /* ------- Controls */

  .site .form-block__control {
    box-sizing: border-box;
    display: block;
    appearance: none;
    margin: 0;
    border: var(--form-control-border-w) solid var(--form-control-edge);
    border-radius: var(--form-control-radius);
    background: var(--form-control-bg);
    padding: var(--form-control-pad-y) var(--form-control-pad-x);
    inline-size: 100%;
    min-block-size: max(var(--touch-target), var(--form-control-height));
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: var(--form-control-size);
    font-weight: var(--form-control-weight);
    line-height: 1.5;
  }

  @media (prefers-reduced-motion: no-preference) {
    .site .form-block__control {
      transition:
        border-color var(--dur-base) var(--ease),
        background-color var(--dur-base) var(--ease);
    }
  }

  .site .form-block__control::placeholder {
    color: var(--color-text-muted);
    opacity: 1;
  }

  /* Editor-canvas text standing in for `::placeholder`, so it takes that colour. */
  .site .form-block__control-placeholder {
    color: var(--color-text-muted);
  }

  .site .form-block__control:hover:not(:disabled) {
    border-color: var(--form-control-border-hover);
  }

  .site .form-block__control:focus-visible,
  .site .form-block__option-input:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  .site .form-block__control:disabled {
    background: color-mix(in srgb, currentColor 6%, var(--form-control-bg));
    cursor: not-allowed;
  }

  .site .form-block__textarea {
    min-block-size: var(--form-textarea-min-h);
    padding-block: var(--form-textarea-pad-y);
    resize: vertical;
  }

  /* Holds the renderer's chevron, which replaces the UA arrow no theme can paint. */
  .site .form-block__select-wrap {
    display: block;
    position: relative;
  }

  .site .form-block__select {
    padding-inline-end: calc(
      var(--form-control-pad-x) * 2 + var(--form-select-arrow-space)
    );
  }

  .site .form-block__select-wrap .form-block__icon {
    position: absolute;
    inset-inline-end: var(--form-control-pad-x);
    inset-block-start: 50%;
    transform: translateY(-50%);
    color: var(--color-text-muted);
    pointer-events: none;
  }

  /* ------- A date */

  .site .form-block__date {
    position: relative;
  }

  .site .form-block__date-trigger {
    display: flex;
    gap: var(--form-date-gap);
    align-items: center;
    justify-content: space-between;
    cursor: pointer;
    text-align: start;
  }

  .site .form-block__date-trigger .form-block__icon {
    flex: none;
    color: var(--color-text-muted);
  }

  .site .form-block__date-value {
    overflow: hidden;
    min-inline-size: 0;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .site .form-block__date-value--empty {
    color: var(--color-text-muted);
  }

  .site .form-block__calendar {
    position: absolute;
    inset-block-start: calc(100% + var(--form-calendar-offset));
    inset-inline-start: 0;
    z-index: 20;
    inline-size: max-content;
    padding: var(--form-calendar-pad);
    border: var(--form-control-border-w) solid var(--form-control-border);
    border-radius: var(--radius-dropdown);
    background: var(--color-surface-raised);
    box-shadow: var(--shadow-lg);
  }

  .site .form-block__calendar-head {
    display: flex;
    gap: var(--form-calendar-gap);
    align-items: center;
    justify-content: space-between;
    margin-block-end: var(--form-calendar-gap);
  }

  .site .form-block__calendar-nav,
  .site .form-block__calendar-select,
  .site .form-block__calendar-day,
  .site .form-block__calendar-clear {
    appearance: none;
    padding: 0;
    border: 0;
    border-radius: var(--form-control-radius);
    background: none;
    color: inherit;
    font-family: var(--font-body);
    cursor: pointer;
  }

  .site .form-block__calendar-nav {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    inline-size: var(--form-calendar-nav-size);
    block-size: var(--form-calendar-nav-size);
    color: var(--color-text-muted);
  }

  .site .form-block__calendar-selects {
    display: flex;
    gap: var(--form-calendar-gap);
  }

  .site .form-block__calendar-select {
    font-size: var(--form-label-size);
    font-weight: var(--form-label-weight);
  }

  .site .form-block__calendar-weekdays,
  .site .form-block__calendar-week {
    display: grid;
    grid-template-columns: repeat(7, var(--form-calendar-cell));
  }

  .site .form-block__calendar-weekday {
    display: flex;
    align-items: center;
    justify-content: center;
    block-size: var(--form-calendar-cell);
    color: var(--color-text-muted);
    font-size: var(--form-note-size);
    font-weight: var(--form-label-weight);
  }

  .site .form-block__calendar-day {
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--form-calendar-cell);
    block-size: var(--form-calendar-cell);
    font-size: var(--form-note-size);
  }

  .site .form-block__calendar-nav:hover,
  .site .form-block__calendar-select:hover,
  .site .form-block__calendar-day:hover,
  .site .form-block__calendar-clear:hover {
    background: var(--color-surface);
    color: var(--color-text);
  }

  .site .form-block__calendar-day--outside {
    color: var(--color-text-muted);
  }

  /* An outline rather than a fill, so today cannot be read as the day chosen. */
  .site .form-block__calendar-day--today {
    box-shadow: inset 0 0 0 var(--form-control-border-w)
      var(--form-control-border);
  }

  .site .form-block__calendar-day--selected,
  .site .form-block__calendar-day--selected:hover {
    background: var(--color-primary);
    color: var(--color-on-primary);
    font-weight: var(--form-label-weight);
    box-shadow: none;
  }

  .site .form-block__calendar-foot {
    display: flex;
    justify-content: flex-end;
    margin-block-start: var(--form-calendar-gap);
    padding-block-start: var(--form-calendar-gap);
    border-block-start: var(--form-control-border-w) solid
      var(--form-control-border);
  }

  .site .form-block__calendar-clear {
    color: var(--color-text-muted);
    font-size: var(--form-note-size);
    font-weight: var(--form-label-weight);
  }

  .site .form-block__calendar-nav:focus-visible,
  .site .form-block__calendar-select:focus-visible,
  .site .form-block__calendar-day:focus-visible,
  .site .form-block__calendar-clear:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  /* The wrapper draws the box so the symbol and the bare input read as one control. */
  .site .form-block__currency {
    display: flex;
    align-items: center;
    gap: var(--form-currency-gap);
    border: var(--form-control-border-w) solid var(--form-control-edge);
    border-radius: var(--form-control-radius);
    background: var(--form-control-bg);
    padding-inline-start: var(--form-control-pad-x);
  }

  .site .form-block__currency .form-block__control {
    border: 0;
    background: transparent;
    padding-inline-start: 0;
  }

  /* Muted so it does not read as a typed character. */
  .site .form-block__currency-prefix {
    color: var(--color-text-muted);
    font-size: var(--form-control-size);
    font-weight: var(--form-strong-weight);
  }

  .site .form-block__currency:focus-within {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  .site .form-block__currency:focus-within .form-block__control:focus-visible {
    outline: 0;
  }

  /* ------- A phone number */

  .site .form-block__phone {
    display: flex;
    gap: var(--form-phone-gap);
  }

  .site .form-block__phone-input {
    flex: 1;
    min-inline-size: 0;
  }

  /* Drawn like a control; the native select over it does the choosing. */
  .site .form-block__phone-country {
    box-sizing: border-box;
    display: flex;
    position: relative;
    flex: none;
    align-items: center;
    border: var(--form-control-border-w) solid var(--form-control-edge);
    border-radius: var(--form-control-radius);
    background: var(--form-control-bg);
    padding-inline: var(--form-control-pad-x);
    min-block-size: max(var(--touch-target), var(--form-control-height));
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: var(--form-control-size);
    font-weight: var(--form-control-weight);
  }

  .site .form-block__phone-country:hover:not(:has(:disabled)) {
    border-color: var(--form-control-border-hover);
  }

  .site .form-block__phone-chip {
    display: flex;
    align-items: center;
    gap: var(--form-phone-chip-gap);
  }

  /* Room for a three-digit code, so changing country never moves the number box. */
  .site .form-block__phone-code {
    min-inline-size: var(--form-phone-code-min-w);
    font-variant-numeric: tabular-nums;
  }

  .site .form-block__phone-chip .form-block__icon {
    color: var(--color-text-muted);
  }

  /* Must stay covering the chip at zero opacity: it is the real control. */
  .site .form-block__phone-select {
    position: absolute;
    inset: 0;
    opacity: 0;
    margin: 0;
    inline-size: 100%;
    block-size: 100%;
    cursor: pointer;
    font: inherit;
  }

  .site .form-block__phone-select:disabled {
    cursor: default;
  }

  .site .form-block__phone-country:focus-within {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  /* ------- Choices */

  .site .form-block__options {
    display: flex;
    flex-direction: column;
    gap: var(--form-option-gap);
  }

  /* Only pick-several pairs: a pick-one option carries a description. */
  @container form-block (width >= 26rem) {
    .site .form-block__field--multi-choice .form-block__options {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }

  /* The whole option is the target, not the 16px dot. */
  .site .form-block__option {
    box-sizing: border-box;
    display: flex;
    align-items: start;
    gap: var(--form-option-inner-gap);
    border: var(--form-control-border-w) solid var(--form-control-edge);
    border-radius: var(--form-control-radius);
    background: var(--form-control-bg);
    padding: var(--form-option-pad);
    min-block-size: var(--touch-target);
    color: var(--color-heading);
    font-size: var(--form-option-size);
    line-height: var(--form-option-leading);
    cursor: pointer;
  }

  .site .form-block__field--multi-choice .form-block__option {
    padding: var(--form-check-pad);
  }

  /* A pick-several label is one line, so the box centres on it. */
  .site .form-block__field--multi-choice .form-block__option {
    align-items: center;
  }

  .site .form-block__option:hover {
    border-color: var(--form-control-border-hover);
  }

  .site .form-block__option--selected {
    border-color: var(--color-primary);
    background: color-mix(
      in srgb,
      var(--color-primary) 8%,
      var(--form-control-bg)
    );
  }

  /* Drawn here rather than by the UA, whose colour a parish accent cannot reach. */
  .site .form-block__option-input {
    appearance: none;
    flex: none;
    margin: 0;
    border: var(--form-choice-border-w) solid var(--form-choice-border);
    background: var(--form-control-bg);
    block-size: var(--form-choice-size);
    inline-size: var(--form-choice-size);
    /* Aligned to the label's first line, so a wrapped option still lines up. */
    margin-block-start: calc(
      (1em * var(--form-option-leading) - var(--form-choice-size)) / 2
    );
    cursor: pointer;
  }

  /* The centring above already holds these together; the offset would fight it. */
  .site .form-block__field--multi-choice .form-block__option-input {
    margin-block-start: 0;
  }

  .site .form-block__option-input[type="radio"] {
    border-radius: var(--radius-pill);
  }

  /* Capped so a round theme cannot turn the tick box into the radio beside it. */
  .site .form-block__option-input[type="checkbox"] {
    border-radius: min(var(--radius-sm), calc(var(--form-choice-size) / 3));
  }

  .site .form-block__option-input:checked {
    border-color: var(--color-primary);
    background: var(--color-primary);
  }

  .site .form-block__option-input[type="radio"]:checked {
    /* The dot is the fill seen through a ring of the option's own background. */
    box-shadow: inset 0 0 0 var(--form-choice-dot) var(--form-control-bg);
  }

  /* The tick is knocked out of the fill, so it carries no colour of its own. */
  .site .form-block__option-input[type="checkbox"]:checked {
    /* The second layer keeps the whole box; only its alpha is read. */
    mask-image:
      var(--form-check-mark), linear-gradient(currentColor, currentColor);
    mask-position: center, center;
    mask-size: var(--form-check-size), cover;
    mask-repeat: no-repeat, no-repeat;
    mask-composite: exclude, add;
  }

  .site .form-block__option-text {
    display: flex;
    flex-direction: column;
    gap: var(--form-option-text-gap);
  }

  .site .form-block__option-label {
    font-weight: var(--form-option-label-weight);
  }

  .site .form-block__field--single-choice .form-block__option-label {
    font-weight: var(--form-strong-weight);
  }

  /* Trimming to cap height centres the letters rather than the line box. */
  @supports (text-box: trim-both cap alphabetic) {
    .site .form-block__option-label {
      text-box: trim-both cap alphabetic;
    }

    .site .form-block__option:not(:has(.form-block__option-description)) {
      align-items: center;
    }

    .site .form-block__option .form-block__option-input {
      margin-block-start: 0;
    }

    /* A described option hangs from the top, so its label drops onto the dot. */
    .site
      .form-block__option:has(.form-block__option-description)
      .form-block__option-text {
      gap: var(--form-option-text-gap-trimmed);
      padding-block-start: calc((var(--form-choice-size) - 1cap) / 2);
    }
  }

  .site .form-block__option-description {
    color: var(--color-text-muted);
    font-size: var(--form-note-size);
    font-weight: 500;
  }

  /* ------- A question that came back wrong */

  .site .form-block__field--invalid .form-block__control,
  .site .form-block__field--invalid .form-block__currency,
  .site .form-block__field--invalid .form-block__option,
  .site .form-block__field--invalid .form-block__option-input {
    border-color: var(--form-danger-on-card-edge);
  }

  .site .form-block__error-summary {
    border-inline-start: var(--space-xs) solid var(--form-danger-edge);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--form-danger-edge) 8%, transparent);
    padding: var(--space-md);
    color: var(--color-text);
    font-size: var(--card-body-size);
  }

  .site .form-block__error-summary:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  .site .form-block__error-summary-title {
    margin: 0;
    color: var(--form-danger);
    font-weight: var(--form-label-weight);
  }

  .site .form-block__error-summary-note {
    margin: var(--form-note-gap) 0 0;
    color: var(--color-text-muted);
    font-size: var(--form-note-size);
  }

  .site .form-block__error-summary-list {
    margin: var(--space-sm) 0 0;
    padding-inline-start: var(--space-lg);
  }

  .site .form-block__error-summary-list a {
    color: var(--color-link);
  }

  .site .form-block__submit-error {
    margin: 0;
    color: var(--form-danger);
    font-size: var(--card-body-size);
    font-weight: var(--form-label-weight);
  }

  /* ------- The draft notice */

  .site .form-block__notice {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--color-primary) 8%, transparent);
    padding: var(--space-sm) var(--space-md);
    font-size: var(--form-notice-size);
  }

  .site .form-block__notice-text {
    margin: 0;
    color: var(--color-text);
  }

  .site .form-block__notice-action {
    border: 0;
    background: transparent;
    padding: 0;
    min-block-size: var(--touch-target);
    color: var(--color-link);
    font: inherit;
    font-weight: var(--btn-weight);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
    cursor: pointer;
  }

  /* ------- Sending it */

  .site .form-block__actions {
    display: flex;
    flex-direction: column;
    align-items: end;
    gap: var(--form-actions-gap);
    margin-block-start: var(--form-actions-offset);
  }

  .site .form-block__submit {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: var(--border-w) solid transparent;
    border-radius: var(--form-submit-radius);
    background: var(--color-primary);
    padding: 0 var(--form-submit-pad-x);
    min-inline-size: var(--form-submit-min-w);
    min-block-size: max(var(--touch-target), var(--form-submit-height));
    color: var(--color-on-primary);
    font-family: var(--font-body);
    font-size: var(--form-submit-size);
    font-weight: 700;
    letter-spacing: var(--form-submit-tracking);
    text-transform: var(--form-submit-case);
    line-height: var(--btn-leading);
    cursor: pointer;
  }

  @media (prefers-reduced-motion: no-preference) {
    .site .form-block__submit {
      transition: background-color var(--dur-base) var(--ease);
    }
  }

  .site .form-block__submit:hover:not(:disabled) {
    background: var(--color-primary-hover);
  }

  .site .form-block__submit:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  /* Unavailable rather than a different button, so "Sending…" stays legible. */
  .site .form-block__submit:disabled {
    opacity: 0.6;
    cursor: progress;
  }

  /* Full width where a button beside nothing else reads as an afterthought. */
  @container form-block (width < 26rem) {
    .site .form-block__submit {
      inline-size: 100%;
    }
  }

  .site .form-block__privacy {
    margin: 0;
    max-inline-size: var(--measure);
    color: var(--color-text-muted);
    font-size: var(--form-note-size);
    line-height: var(--line-height-body);
  }

  /* Hidden without `display: none`, which the bots this catches look for. */
  .site .form-block__honeypot {
    position: absolute;
    clip-path: inset(50%);
    block-size: 1px;
    inline-size: 1px;
    overflow: hidden;
    white-space: nowrap;
  }

  /* ------- What the island shows on its own */

  .site .form-block__status {
    color: var(--color-text-muted);
    font-family: var(--font-body);
    font-size: var(--card-body-size);
  }

  .site .form-block__status-text,
  .site .form-block__status-action {
    margin: 0;
  }

  .site .form-block__status-action {
    margin-block-start: var(--form-note-gap);
  }

  .site .form-block__status-action a {
    color: var(--color-link);
  }

  /* ------- Sent */

  /* The form's own section dress, so the thank-you reads as its last step. */
  .site .form-block__success {
    display: flex;
    flex-direction: column;
    gap: var(--form-success-gap);
    box-sizing: border-box;
    inline-size: 100%;
    max-inline-size: var(--form-success-w);
    margin-inline: auto;
    border: 1px solid var(--form-card-border);
    border-radius: var(--form-card-radius);
    background: var(--form-card-bg);
    padding: var(--form-success-pad-narrow);
    font-family: var(--font-body);
    text-align: center;
  }

  .site .form-block__success-title {
    max-inline-size: var(--form-success-title-measure);
    margin: 0 auto;
    color: var(--color-heading);
    font-family: var(--font-heading);
    font-size: var(--form-success-title-size-narrow);
    font-weight: var(--heading-weight);
    letter-spacing: var(--heading-tracking);
    line-height: 1.15;
  }

  .site .form-block__success-title:focus {
    outline: none;
  }

  .site .form-block__success-title:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  /* Pulled up under the title, since the two read as one heading. */
  .site .form-block__success-body {
    max-inline-size: var(--form-success-body-measure);
    margin: calc(var(--form-intro-gap) - var(--form-success-gap)) auto 0;
    color: var(--color-text-muted);
    font-size: var(--form-success-body-size);
    line-height: 1.6;
  }

  /* Mixed rather than `--color-surface`, which on some canvases is the card's own. */
  .site .form-block__summary {
    display: flex;
    flex-direction: column;
    margin: 0;
    border: 1px solid var(--form-card-border);
    border-radius: min(var(--form-card-radius), var(--form-summary-radius-max));
    background: color-mix(in srgb, var(--color-text) 3%, var(--form-card-bg));
    padding: var(--form-summary-pad);
    text-align: start;
  }

  .site .form-block__summary-row {
    display: flex;
    align-items: baseline;
    gap: var(--form-summary-row-gap);
    border-block-start: 1px solid var(--form-card-border);
    padding-block: var(--form-summary-row-pad);
  }

  .site .form-block__summary-row:first-child {
    border-block-start: 0;
  }

  .site .form-block__summary-label {
    flex: none;
    inline-size: var(--form-summary-label-w-narrow);
    color: var(--color-text-muted);
    font-size: var(--form-note-size);
    font-weight: 700;
  }

  .site .form-block__summary-value {
    flex: 1 1 0;
    min-inline-size: 0;
    margin: 0;
    color: var(--color-text);
    font-size: var(--form-summary-value-size);
    font-weight: var(--form-summary-value-weight);
    line-height: 1.45;
    overflow-wrap: anywhere;
  }

  /* The one row a visitor may be asked to read back over the phone. */
  .site .form-block__summary-row--reference .form-block__summary-value {
    color: var(--color-heading);
  }

  .site .form-block__receipt {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--form-note-size);
  }

  .site .form-block__success-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--form-success-actions-gap);
    border-block-start: 1px solid var(--form-card-border);
    padding-block-start: var(--form-success-actions-pad);
  }

  /* The screen's next step wears the form's own submit button, per theme. */
  .site .form-block__success-back,
  .site .form-block__success-again {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Not --border-w: Gather's unitless 0 voids any math function it enters. */
    border: var(--form-control-border-w) solid var(--color-primary);
    border-radius: var(--form-submit-radius);
    background: var(--color-primary);
    padding: 0 var(--form-submit-pad-x);
    min-inline-size: var(--form-submit-min-w);
    min-block-size: max(var(--touch-target), var(--form-submit-height));
    color: var(--color-on-primary);
    font-family: var(--font-body);
    font-size: var(--form-submit-size);
    font-weight: 700;
    letter-spacing: var(--form-submit-tracking);
    text-transform: var(--form-submit-case);
    text-decoration: none;
    line-height: var(--btn-leading);
    cursor: pointer;
  }

  @media (prefers-reduced-motion: no-preference) {
    .site .form-block__success-back,
    .site .form-block__success-again {
      transition:
        background-color var(--dur-base) var(--ease),
        border-color var(--dur-base) var(--ease);
    }
  }

  .site .form-block__success-back:hover,
  .site .form-block__success-again:hover {
    border-color: var(--color-primary-hover);
    background: var(--color-primary-hover);
    color: var(--color-on-primary);
  }

  .site .form-block__success-back ~ .form-block__success-again {
    background: transparent;
    color: var(--color-primary);
  }

  .site .form-block__success-back ~ .form-block__success-again:hover {
    border-color: var(--color-primary);
    background: color-mix(in srgb, var(--color-primary) 12%, transparent);
    color: var(--color-primary);
  }

  .site .form-block__success-back:focus-visible,
  .site .form-block__success-again:focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  @container form-block (width >= 26rem) {
    .site .form-block__success {
      padding: var(--form-success-pad);
    }

    .site .form-block__success-title {
      font-size: var(--form-success-title-size);
    }

    .site .form-block__summary-label {
      inline-size: var(--form-summary-label-w);
    }
  }

  /* ---------------------------------------------------------------- Spacer block */

  /* A presentational vertical gap. The scale maps to the shared --space-* tokens;
   a fixed pixel height is set inline and wins over --spacer-h. */
  .site .spacer-block {
    width: 100%;
    height: var(--spacer-h, var(--space-md));
  }
  .site .spacer-block[data-height="none"] {
    --spacer-h: var(--space-none);
  }
  .site .spacer-block[data-height="xs"] {
    --spacer-h: var(--space-xs);
  }
  .site .spacer-block[data-height="sm"] {
    --spacer-h: var(--space-sm);
  }
  .site .spacer-block[data-height="md"] {
    --spacer-h: var(--space-md);
  }
  .site .spacer-block[data-height="lg"] {
    --spacer-h: var(--space-lg);
  }
  .site .spacer-block[data-height="xl"] {
    --spacer-h: var(--space-xl);
  }
  .site .spacer-block[data-height="2xl"] {
    --spacer-h: var(--space-2xl);
  }

  /* Editor-only affordance: a dashed outline + subtle tint marks the gap on the
   canvas so it stays findable. `outline` (not a border or min-height) never
   affects layout, so the placeholder shows the spacer at its true published
   height. The published render omits data-editing, so none of this applies. */
  .site .spacer-block[data-editing] {
    outline: var(--border-w) dashed var(--color-border);
    outline-offset: calc(-1 * var(--border-w));
    background: color-mix(in srgb, var(--color-text-muted) 6%, transparent);
  }

  /* ---------------------------------------------------------------- Section band */

  /* The one query container every responsive rule in the site measures against
   (see the sheet header), and the containment that keeps the full-bleed
   breakout from becoming page-level horizontal scroll. */
  .site-chrome {
    container: site / inline-size;
    overflow-x: clip;
    display: flex;
    flex-direction: column;
    min-height: 100dvh;
  }

  /* Full-bleed band: spans the site container regardless of the centred page
   column, while its inner wrapper keeps content at a readable max-width. */
  .site .section {
    box-sizing: border-box;
    position: relative;
    left: 50%;
    right: 50%;
    width: 100cqi;
    margin-left: -50cqi;
    margin-right: -50cqi;
    padding-block: var(--section-pad, 0);
  }

  /* A scheme re-declares the color roles on the band itself (see the
   site-schemes layer), so painting it is just reading them back — and every
   block inside inherits the same tokens with no rule of its own. Before the
   explicit background below, so a hand-picked color still wins. */
  .site .section[data-scheme] {
    background: var(--color-bg);
    color: var(--color-text);
  }

  .site .section[data-has-bg="true"] {
    background: var(--section-bg);
  }

  .site .section[data-pad="none"] {
    --section-pad: var(--band-pad-none);
  }
  .site .section[data-pad="xs"] {
    --section-pad: var(--band-pad-xs);
  }
  .site .section[data-pad="sm"] {
    --section-pad: var(--band-pad-sm);
  }
  .site .section[data-pad="md"] {
    --section-pad: var(--band-pad-md);
  }
  .site .section[data-pad="lg"] {
    --section-pad: var(--band-pad-lg);
  }
  .site .section[data-pad="xl"] {
    --section-pad: var(--band-pad-xl);
  }
  .site .section[data-pad="2xl"] {
    --section-pad: var(--band-pad-2xl);
  }

  /* content-box, so a band delivers the width it advertises: under border-box
     the gutter came out of the cap and every band ran two gutters narrow. */
  .site .section__inner {
    box-sizing: content-box;
    width: auto;
    margin-inline: auto;
    padding-inline: var(--site-gutter);
  }
  .site .section__inner[data-mw="sm"] {
    max-width: var(--mw-sm);
  }
  .site .section__inner[data-mw="md"] {
    max-width: var(--mw-md);
  }
  .site .section__inner[data-mw="lg"] {
    max-width: var(--mw-lg);
  }
  .site .section__inner[data-mw="xl"] {
    max-width: var(--mw-xl);
  }
  .site .section__inner[data-mw="prose"] {
    max-width: var(--mw-prose);
  }

  /* ---------------------------------------------------------------- Columns */

  /* The edge colour lives here rather than inline on the element so a band can
   hand it to the layer and the two rules below can hand it back. */
  .site .columns {
    display: grid;
    gap: var(--columns-gap, var(--space-none));
    grid-template-columns: var(
      --columns-template,
      repeat(var(--columns-count, 2), minmax(0, 1fr))
    );
    border-color: var(--band-border-c, var(--color-border));
  }

  /* An unequal split, as a token rather than a `grid-template-columns` of its
   own: the phone rules below re-set that property at the same specificity as the
   base rule, so a ratio declared directly would outrank them and survive onto a
   390px screen. `minmax(0, …)` on both tracks is what stops a long email address
   in the narrow one from pushing past its share. */
  .site .columns[data-ratio="1:2"] {
    --columns-template: minmax(0, 1fr) minmax(0, 2fr);
  }
  .site .columns[data-ratio="1:3"] {
    --columns-template: minmax(0, 1fr) minmax(0, 3fr);
  }
  .site .columns[data-ratio="1:4"] {
    --columns-template: minmax(0, 1fr) minmax(0, 4fr);
  }
  .site .columns[data-ratio="2:1"] {
    --columns-template: minmax(0, 2fr) minmax(0, 1fr);
  }
  .site .columns[data-ratio="3:1"] {
    --columns-template: minmax(0, 3fr) minmax(0, 1fr);
  }
  .site .columns[data-ratio="4:1"] {
    --columns-template: minmax(0, 4fr) minmax(0, 1fr);
  }

  /* A content-width track instead of a share, so a 96px thumbnail is followed by
   the gap and then its text at every viewport width, which no fraction can do.
   The 50% ceiling is load-bearing: a bare `auto` track takes its content's
   max-content width, and an Image at `container` or `edge` carries
   `--breakout-container-width: 100%` with no `max-width`, so it claimed the whole
   row and left its neighbour a 0px track with text wrapping one character per
   line. The ceiling is the widest Image width that has a width of its own, so
   `thumb`, `xs` and `small` still measure exactly what they measure; anything
   wider stops there instead of taking the row. A length, not a percentage: a
   percentage resolves against an indefinite width on some themes and collapses
   the track to 0. */
  .site .columns[data-ratio="auto:1"] {
    --columns-template: fit-content(var(--media-w-sm)) minmax(0, 1fr);
  }
  .site .columns[data-ratio="1:auto"] {
    --columns-template: minmax(0, 1fr) fit-content(var(--media-w-sm));
  }

  .site .columns[data-scheme] {
    background: var(--color-bg);
    color: var(--color-text);
  }

  .site .columns[data-has-bg="true"] {
    background: var(--columns-bg);
  }

  /* A filled row is a band, so its fill reaches the page edges the way a
   Section's does while the columns keep the width they measure. Painted as a
   layer behind the grid rather than by widening the block: the block's own
   width, ratio and alignment all resolve against its container, and stretching
   the element would drag every one of them out with it. A page width of
   overhang on each side reaches both edges from a block sitting anywhere
   inside the page, whatever its width, and `.site-chrome` clips the rest,
   the same containment the Section band leans on. `isolation` keeps the layer
   above a coloured Section it sits in and below its own columns; without it a
   z-index of -1 drops it behind that band and the fill vanishes. */
  .site .columns[data-bleed="true"] {
    position: relative;
    isolation: isolate;
    border-color: transparent;
  }

  /* The layer reaches over the block's own edges rather than stopping inside
   them: anchored to the padding box it left the page showing through in the
   two strips a border occupies, outside the block's width, as notches at each
   end of the rule. Reaching that far means painting over those edges, so the
   layer carries them, which also settles what a bordered band looks like: the
   rules run the width of the band, and the side edges go, having no edge of
   the band to sit on. */
  .site .columns[data-bleed="true"]::before {
    content: "";
    position: absolute;
    z-index: -1;
    box-sizing: border-box;
    inset-block: calc(-1 * var(--band-border-w, 0px));
    inset-inline: var(--band-bleed-inset);
    border-block: var(--band-border-w, 0px) solid
      var(--band-border-c, transparent);
    background: inherit;
  }

  /* Handing the edges over is only right where the layer paints them as a band.
   A row in a column fills that column instead, so it stays the box it was: it
   draws its own four edges and the layer stops short of them. */
  .site .columns__col .columns[data-bleed="true"] {
    border-color: var(--band-border-c, var(--color-border));
  }

  .site .columns__col .columns[data-bleed="true"]::before {
    inset-block: 0;
    border-block-width: 0;
  }

  .site .columns[data-gap="none"] {
    --columns-gap: var(--space-none);
  }
  .site .columns[data-gap="xs"] {
    --columns-gap: var(--space-xs);
  }
  .site .columns[data-gap="sm"] {
    --columns-gap: var(--space-sm);
  }
  .site .columns[data-gap="md"] {
    --columns-gap: var(--space-md);
  }
  .site .columns[data-gap="lg"] {
    --columns-gap: var(--space-lg);
  }
  .site .columns[data-gap="xl"] {
    --columns-gap: var(--space-xl);
  }
  .site .columns[data-gap="2xl"] {
    --columns-gap: var(--space-2xl);
  }

  .site .columns[data-valign="top"] {
    align-items: start;
  }
  .site .columns[data-valign="center"] {
    align-items: center;
  }
  .site .columns[data-valign="bottom"] {
    align-items: end;
  }
  .site .columns[data-valign="stretch"] {
    align-items: stretch;
  }

  /* A cell is a grid track, not a page-centred column, so the breakout measures
   from the wrong origin: every cell's `container`/`edge` child lands at the same
   position, several times the cell's width, stacked over its neighbours and
   their text. Inside a cell the two widths mean "fill the cell" instead. */
  .site .columns__col {
    min-width: 0;
    --breakout-container-width: 100%;
    --breakout-container-margin: 0;
    --breakout-edge-width: 100%;
    --breakout-edge-margin: 0;
    --band-bleed-inset: 0;
    /* A ratio is one row's own split, and a custom property inherits: without
     this a Columns nested inside a ratio'd cell takes the outer template and
     ignores its own count, wrapping a third column onto a second row. */
    --columns-template: initial;
  }

  /* A Columns block is a band, so its padding follows the band scale rather than
   the shared block one. */
  .site .columns[data-pad="none"] {
    --block-pad: var(--band-pad-none);
  }
  .site .columns[data-pad="xs"] {
    --block-pad: var(--band-pad-xs);
  }
  .site .columns[data-pad="sm"] {
    --block-pad: var(--band-pad-sm);
  }
  .site .columns[data-pad="md"] {
    --block-pad: var(--band-pad-md);
  }
  .site .columns[data-pad="lg"] {
    --block-pad: var(--band-pad-lg);
  }
  .site .columns[data-pad="xl"] {
    --block-pad: var(--band-pad-xl);
  }
  .site .columns[data-pad="2xl"] {
    --block-pad: var(--band-pad-2xl);
  }

  /* Phone behaviour: the theme's own choice through the two tokens, and each
   named behaviour as an override a block can pin. `scroll` is the only one that
   turns the row into a scroll container, so both properties are always set
   together — half an override leaves a stacked grid trying to scroll. */
  @container site (width < 40rem) {
    .site .columns {
      grid-template-columns: var(--columns-mobile-template, minmax(0, 1fr));
      overflow: var(--columns-mobile-overflow, visible);
    }
    .site .columns[data-mobile="stack"] {
      grid-template-columns: minmax(0, 1fr);
      overflow: visible;
    }
    .site .columns[data-mobile="two-up"] {
      grid-template-columns: repeat(
        auto-fit,
        minmax(max(0px, 50% - var(--columns-gap, 0px)), 1fr)
      );
      overflow: visible;
    }
    .site .columns[data-mobile="scroll"] {
      grid-template-columns: repeat(
        var(--columns-count, 2),
        minmax(var(--columns-scroll-w), 1fr)
      );
      overflow: auto hidden;
    }

    /* A swiping row is its own scroll container, which clips the band layer and
     counts the overhang as something to scroll to. With no layer there is
     nothing to hand the edges to, so the row keeps drawing them, along with the
     plain fill it has always had here. */
    .site .columns[data-mobile="scroll"][data-bleed="true"] {
      border-color: var(--band-border-c, var(--color-border));
    }

    .site .columns[data-mobile="scroll"][data-bleed="true"]::before {
      display: none;
    }
  }

  /* ------------------------------------------------------- Collection grid/card */

  /* The shared grid for every block that tiles a collection of linked items
   (Quick Links, Card Grid, Sacraments). The column count comes from a
   `data-columns` attribute rather than an inline `--collection-columns` so the
   responsive rules below can override it — an inline custom property would
   outrank every media query. */
  .site .collection-grid {
    display: grid;
    grid-template-columns: repeat(var(--collection-columns, 3), minmax(0, 1fr));
    align-items: stretch;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .site .collection-grid[data-columns="2"] {
    --collection-columns: 2;
  }
  .site .collection-grid[data-columns="3"] {
    --collection-columns: 3;
  }
  .site .collection-grid[data-columns="4"] {
    --collection-columns: 4;
  }
  .site .collection-grid[data-columns="5"] {
    --collection-columns: 5;
  }
  .site .collection-grid[data-columns="6"] {
    --collection-columns: 6;
  }

  .site .collection-grid[data-gap="none"] {
    gap: var(--space-none);
  }
  .site .collection-grid[data-gap="xs"] {
    gap: var(--space-xs);
  }
  .site .collection-grid[data-gap="sm"] {
    gap: var(--space-sm);
  }
  .site .collection-grid[data-gap="md"] {
    gap: var(--space-md);
  }
  .site .collection-grid[data-gap="lg"] {
    gap: var(--space-lg);
  }
  .site .collection-grid[data-gap="xl"] {
    gap: var(--space-xl);
  }
  .site .collection-grid[data-gap="2xl"] {
    gap: var(--space-2xl);
  }

  .site .collection-grid[data-item-align="left"] {
    text-align: left;
  }
  .site .collection-grid[data-item-align="center"] {
    text-align: center;
  }

  /* Fixed collapse, not configurable: desktop count → at most 3 on tablet → at
   most 2 on phones. It never drops to 1 column — every parish site sampled
   keeps its quick links 2-up on a phone, which is what makes them usable with
   a thumb. */
  @container site (width < 56.25rem) {
    .site .collection-grid[data-columns="4"],
    .site .collection-grid[data-columns="5"],
    .site .collection-grid[data-columns="6"] {
      --collection-columns: 3;
    }
  }

  @container site (width < 40rem) {
    .site .collection-grid[data-columns="3"],
    .site .collection-grid[data-columns="4"],
    .site .collection-grid[data-columns="5"],
    .site .collection-grid[data-columns="6"] {
      --collection-columns: 2;
    }
  }

  .site .collection-card {
    position: relative;
    box-sizing: border-box;
    display: flex;
    height: 100%;
    min-width: 0;
    flex-direction: column;
    gap: var(--card-gap);
    border-radius: var(--radius-card);
  }

  .site .collection-card[data-density="compact"] {
    padding: var(--card-pad);
    border: var(--border-w) solid var(--color-border);
    background: var(--color-surface);
  }

  .site .collection-card__icon {
    display: inline-flex;
    align-items: center;
    color: var(--color-primary);
  }

  .site .collection-card__image {
    display: block;
    width: 100%;
    aspect-ratio: 3 / 2;
    border-radius: var(--radius-thumb);
    object-fit: cover;
  }

  .site .collection-card[data-density="compact"] .collection-card__image {
    width: auto;
    max-height: var(--card-thumb-h);
    aspect-ratio: auto;
  }

  /* The card is a column flex container, so its children are blockified and
   stretch the full width — `text-align` cannot position them. The grid's
   `itemAlign` has to reach the icon and the compact image through `align-self`
   instead. The editorial image is exempt: it spans the card by design. */
  .site .collection-card__icon,
  .site .collection-card[data-density="compact"] .collection-card__image {
    align-self: start;
  }

  .site .collection-grid[data-item-align="center"] .collection-card__icon,
  .site
    .collection-grid[data-item-align="center"]
    .collection-card[data-density="compact"]
    .collection-card__image {
    align-self: center;
  }

  /* Lets the text column absorb the card's leftover height, so equal-height cards
   don't leave a gap between the copy and the card's bottom edge. Children stay
   top-packed — body copy follows its heading rather than sinking to the base. */
  .site .collection-card__text {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--card-text-gap);
  }

  .site .collection-card__eyebrow {
    margin: 0;
    color: var(--color-primary);
    font-weight: var(--eyebrow-weight);
    letter-spacing: var(--eyebrow-tracking);
    text-transform: var(--eyebrow-case);
    font-size: var(--eyebrow-size);
  }

  .site .collection-card__heading {
    margin: 0;
    font-size: var(--text-lg);
  }

  .site .collection-card[data-density="compact"] .collection-card__heading {
    font-size: var(--text-base);
  }

  .site .collection-card__body {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--card-body-size);
  }

  /* Whole-card linking: the anchor stays inline in the heading (so its accessible
   name is just the label) and a stretched pseudo-element grows the click target
   to the entire card. */
  .site .collection-card__link {
    color: inherit;
    text-decoration: none;
  }

  .site .collection-card__link::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
  }

  .site .collection-card[data-linked]:hover .collection-card__heading {
    color: var(--color-link-hover);
  }

  /* Focus is drawn on the card, not the heading text, so the ring matches what a
   keyboard user will actually activate. Without :has() support the anchor keeps
   its own UA focus ring, so the state stays visible either way. */
  .site .collection-card:has(.collection-card__link:focus-visible) {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  .site
    .collection-card:has(.collection-card__link:focus-visible)
    .collection-card__link:focus-visible {
    outline: none;
  }

  /* ---------------------------------------------------------------- Site chrome

   As with the hero, only the parts no arrangement should have to restate live
   here: resets, the content container, and the colors each theme's tokens
   already drive. The header and footer *arrangement* — and the `[data-variant]`
   alternatives — belong to the theme layer. */

  .site .site-header {
    container-type: inline-size;
    border-bottom: var(--border-w) solid var(--color-border);
    background: var(--header-bg, var(--color-bg));
  }

  .site .site-header.is-sticky {
    position: sticky;
    top: 0;
    z-index: 30;
  }

  .site .site-header.is-sticky.is-scrolled {
    box-shadow: var(--shadow-md);
  }

  .site .site-header__brand {
    color: var(--header-text, var(--color-heading));
    font-family: var(--font-heading);
    text-decoration: none;
  }

  /* Definite width, not auto: the flex column's cross-axis auto margin (for centering) disables stretch, so auto would shrink to content instead of filling. */
  .site .site-main {
    box-sizing: content-box;
    flex-grow: 1;
    width: calc(100% - (var(--site-gutter) * 2));
    max-width: var(--site-container);
    margin-inline: auto;
    padding-inline: var(--site-gutter);
  }

  .site-chrome[data-layout="not-found"] .site-main {
    --not-found-badge-size: 3.375rem;
    --not-found-badge-icon-size: 1.625rem;
    --not-found-body-max-width: 30rem;
    --not-found-body-size: 1.0625rem;
    --not-found-cta-icon-size: 1.1em;
    --not-found-cta-icon-gap: 0.5em;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--space-md);
    padding-block: var(--space-xl);
    text-align: center;
  }

  /* The band's full-bleed offset (`left: 50%` + `-50cqi` margins) lands off-center inside this flex column; the band has no fill to bleed. */
  .site-chrome[data-layout="not-found"] .section {
    left: auto;
    right: auto;
    width: 100%;
    margin-inline: 0;
  }

  .site-chrome[data-layout="not-found"] .section__content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-md);
  }

  .site .site-not-found__badge {
    display: flex;
    align-self: center;
    align-items: center;
    justify-content: center;
    width: var(--not-found-badge-size);
    height: var(--not-found-badge-size);
    border-radius: var(--radius-pill);
    background: color-mix(in srgb, var(--color-primary) 14%, transparent);
    color: var(--color-primary);
  }

  .site .site-not-found__badge svg {
    width: var(--not-found-badge-icon-size);
    height: var(--not-found-badge-icon-size);
  }

  .site-chrome[data-layout="not-found"] .text-html {
    max-width: var(--not-found-body-max-width);
    color: var(--color-text-muted);
    font-size: var(--not-found-body-size);
    line-height: 1.55;
    text-wrap: pretty;
  }

  /* ::after is the touch-target overlay on coarse pointers. */
  .site-chrome[data-layout="not-found"] .btn::before {
    content: "";
    order: 1;
    width: var(--not-found-cta-icon-size);
    height: var(--not-found-cta-icon-size);
    margin-inline-start: var(--not-found-cta-icon-gap);
    background-color: currentColor;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M12 5l7 7-7 7'/%3E%3C/svg%3E")
      center / contain no-repeat;
  }

  .site .site-footer {
    border-top: var(--border-w) solid var(--color-border);
    background: var(--footer-bg, var(--color-surface));
    color: var(--color-text-muted);
  }

  .site .site-header__logo {
    display: block;
    max-height: var(--header-logo-h);
    width: auto;
  }

  .site .site-header__menu-summary,
  .site .site-header__menu-toggle,
  .site .site-header__cta--mobile-bar {
    display: none;
  }

  /* The brand, restated inside the menu panel: once the panel is a modal dialog
     the header it normally sits in is behind a backdrop, and a menu with no
     parish name on it is a menu you cannot place. Server-rendered, because that
     is where the name and the logo are.

     Hidden at every width until the panel is actually in the dialog: on a wide
     screen the panel is the nav bar itself, and inline on a phone it opens
     directly beneath the brand it would repeat. */
  .site .site-header__menu-panel-head {
    display: none;
  }

  .site .site-header__sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  /* Resting ink is a share of the header's own text color, never the theme's
   link color: the generic one is tuned for body links on theme surfaces and
   turns unreadable on custom header colors. `position: relative` is here rather
   than in a theme sheet because it is what every theme's active-page mark is
   positioned against. */
  .site .site-header__link {
    position: relative;
    color: color-mix(
      in srgb,
      var(--header-text, var(--color-text)) var(--nav-ink-strength),
      transparent
    );
    text-decoration: none;
  }

  .site .site-header__link:hover {
    color: var(--header-text, var(--color-text));
  }

  /* The current page reaches full ink, which is the whole of the state on a
   theme that marks it by contrast alone. The weight step goes through
   `--nav-weight`, because the property that reads it is declared in the theme
   layer and would outrank a `font-weight` written here.

   A group holding the current page reads as current too: its panel is shut, so
   the label is the only place the state can show. Read off the children with
   `:has()` rather than marked in the markup, so it holds for a page whose
   `aria-current` the nav boot filled in after load as well as for one the render
   marked. A group whose own destination is the current page marks that label
   directly instead, and lands in the same state through the rule above: the
   label is not a link, but it is still what the menu calls that page. */
  .site .site-header__link[aria-current="page"],
  .site
    .site-header__dropdown:has([aria-current="page"])
    .site-header__dropdown-parent-link {
    color: var(--header-text, var(--color-text));
    --nav-weight: var(--nav-weight-active);
  }

  .site .site-header__nav > .site-header__link,
  .site .site-header__dropdown-parent {
    border-radius: var(--radius-nav-link);
    padding: var(--nav-link-pad-y) var(--nav-link-pad-x);
  }

  .site .site-header__nav > .site-header__link:hover {
    background: var(--nav-link-hover-bg);
    text-decoration: var(--nav-hover-decoration);
    text-underline-offset: var(--nav-underline-offset);
  }

  /* An open group takes the neutral fill in every theme, including the ones
   whose links hover without one: here the fill says "open", not "hovered". It
   sits under the current-group mark rather than in place of it — a group can be
   both the section the visitor is in and the one they have just opened. */
  .site .site-header__dropdown-parent:hover,
  .site .site-header__dropdown.is-open .site-header__dropdown-parent,
  .site .site-header__dropdown.is-hover-open .site-header__dropdown-parent,
  .site .site-header__dropdown:focus-within .site-header__dropdown-parent {
    background: var(--nav-hover-bg);
    text-decoration: var(--nav-hover-decoration);
    text-underline-offset: var(--nav-underline-offset);
  }

  .site .site-header__brand:hover {
    color: color-mix(
      in srgb,
      var(--header-text, var(--color-heading)) 70%,
      transparent
    );
  }

  /* A dropdown parent keeps its link and uses a separate disclosure control. */
  .site .site-header__dropdown {
    position: relative;
    display: inline-block;
  }

  /* Positioned so a theme's current-page mark can hang off the whole group row,
   caret included, the way it hangs off a plain link's padded box. */
  .site .site-header__dropdown-parent {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--nav-caret-gap);
    /* The row is the control — it opens the submenu on hover on the bar and on
     click in the panel — but it is built from spans, which would leave a caret
     over the label and a pointer over the chevron beside it. */
    cursor: pointer;
  }

  .site .site-header__dropdown-toggle {
    appearance: none;
    box-sizing: border-box;
    border: 0;
    padding: var(--nav-disclosure-pad);
    margin: calc(-1 * var(--nav-disclosure-pad));
    background: transparent;
    color: var(--header-text, var(--color-text));
    cursor: pointer;
  }

  .site .site-header__dropdown-chevron {
    display: block;
    width: var(--dropdown-chevron-size);
    height: var(--dropdown-chevron-size);
    opacity: 0.7;
    transform: rotate(0deg);
  }

  /* A marked group's caret comes up to meet its label. At rest the caret is the
   quieter half of the row, which is right; left there under a label the state
   has reached, it reads as the one part of the group the mark missed. */
  .site
    .site-header__dropdown:has([aria-current="page"])
    .site-header__dropdown-chevron {
    opacity: 1;
  }

  @media (prefers-reduced-motion: no-preference) {
    .site .site-header__dropdown-chevron {
      transition: transform var(--dropdown-duration) var(--dropdown-ease);
    }

    @keyframes site-header-dropdown-panel-in {
      from {
        opacity: 0;
        translate: 0 var(--dropdown-rise);
      }

      to {
        opacity: 1;
        translate: 0 0;
      }
    }

    .site .site-header__dropdown.is-open > .site-header__dropdown-panel,
    .site .site-header__dropdown.is-hover-open > .site-header__dropdown-panel,
    .site
      .site-header__dropdown:not(.is-runtime):hover
      > .site-header__dropdown-panel {
      animation: site-header-dropdown-panel-in var(--dropdown-duration)
        var(--dropdown-ease) both;
    }
  }

  .site
    .site-header__dropdown:is(.is-open, .is-hover-open)
    .site-header__dropdown-chevron,
  .site
    .site-header__dropdown:not(.is-runtime):hover
    .site-header__dropdown-chevron {
    transform: rotate(180deg);
  }

  /* The panel follows the header's own colors so the header color settings cover
   everything, falling back to the theme when unset. */
  .site .site-header__dropdown-panel {
    display: none;
    /* So the two widths below mean the panel, not its text column: a theme that
     drops the inline padding would otherwise get a narrower floor. */
    box-sizing: border-box;
    position: absolute;
    top: calc(100% + var(--dropdown-gap));
    left: 50%;
    transform: translateX(-50%);
    z-index: 20;
    /* Sized to its longest label, then clamped. Stated rather than left to
     shrink-to-fit, whose available width here is the trigger's own — a few
     characters wide — which would wrap every row instead. */
    width: max-content;
    min-width: var(--dropdown-min-w);
    max-width: var(--dropdown-max-w);
    margin: 0;
    padding: var(--dropdown-pad);
    gap: var(--dropdown-item-gap);
    list-style: none;
    background: var(--header-bg, var(--color-surface-raised));
    color: var(--header-text, var(--color-text));
    border: 0;
    border-radius: var(--radius-dropdown);
    box-shadow: var(--dropdown-shadow);
    text-align: left;
  }

  /* Bridges the gap between the trigger and the panel so crossing it is not a
   pointerleave that closes the menu. On the dropdown rather than inside the
   panel, because a theme may clip the panel's overflow — Cathedral does, to keep
   its full-width rows off the rounded corners — which cuts away anything the
   panel draws outside itself. Zero-height until open, so a closed dropdown has
   no invisible strip catching the pointer. */
  .site .site-header__dropdown::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    top: 100%;
    height: 0;
  }

  .site .site-header__dropdown.is-open::after,
  .site .site-header__dropdown.is-hover-open::after {
    height: var(--dropdown-gap);
  }

  /* A long list turns two columns rather than running down the viewport. Both
   still take their width from the longest label, so a column pair of short ones
   stays narrow. The wrap point rises with them: held at the one-column width,
   each column is half as wide and a label that fitted on one line now wraps —
   and a wrapped row is a row for both columns, so the panel reads as uneven
   spacing rather than as a wrap.

   One authored submenu only. The overflow panel is a run of headings each
   followed by its own links, and the grid fills across rows: two columns there
   deal the rows out alternately, so a heading lands beside a link from the group
   before it. It stays one column, which is the order the rows were built in. */
  .site
    .site-header__dropdown:not(.site-header__overflow)
    > .site-header__dropdown-panel:has(> :nth-child(7)) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: var(--dropdown-max-w-wide);
  }

  .site .site-header__dropdown.is-open > .site-header__dropdown-panel,
  .site .site-header__dropdown.is-hover-open > .site-header__dropdown-panel {
    display: grid;
  }

  .site
    .site-header__dropdown:is(.is-open, .is-hover-open)
    > .site-header__dropdown-panel:has(> :nth-child(7)) {
    display: grid;
  }

  @media (hover: hover) and (pointer: fine) {
    .site
      .site-header__dropdown:not(.is-runtime):hover
      > .site-header__dropdown-panel {
      display: grid;
    }

    .site
      .site-header__dropdown:not(.is-runtime):hover
      > .site-header__dropdown-panel:has(> :nth-child(7)) {
      display: grid;
    }

    .site .site-header__dropdown:not(.is-runtime):hover::after {
      height: var(--dropdown-gap);
    }
  }

  /* Escape sets .is-closed (see the nav boot): it must beat the hover/focus
   reveal above, since the pointer or focus is usually still inside when the
   key is pressed. Cleared on pointer-leave/focus-out so the menu re-arms. */
  .site .site-header__dropdown.is-closed > .site-header__dropdown-panel {
    display: none;
  }

  .site .site-header__dropdown.is-closed .site-header__dropdown-chevron {
    transform: rotate(0deg);
  }

  /* The last nav item's centered panel would overhang the viewport edge; anchor
   it to the right instead. This is the no-JS heuristic — the nav boot measures
   the real geometry and re-anchors any panel about to overflow via the
   --edge-* classes below, which also unset a stale last-child anchor. */
  .site .site-header__dropdown:last-child .site-header__dropdown-panel {
    left: auto;
    right: 0;
    transform: none;
  }

  .site .site-header__dropdown--edge-right > .site-header__dropdown-panel {
    left: auto;
    right: 0;
    transform: none;
  }

  .site .site-header__dropdown--edge-left > .site-header__dropdown-panel {
    left: 0;
    right: auto;
    transform: none;
  }

  .site .site-header__dropdown-link {
    display: block;
    padding: var(--dropdown-item-pad-y) var(--dropdown-item-pad-x);
    border-radius: var(--radius-dropdown-item);
    font-size: var(--text-sm);
    font-weight: 500;
    letter-spacing: normal;
    text-transform: none;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  .site .site-header__dropdown-link:hover {
    background: color-mix(in srgb, currentColor 12%, transparent);
  }

  .site .site-header__dropdown-label {
    display: block;
  }

  .site .site-header__dropdown-link[aria-current="page"] {
    background: var(--dropdown-current-bg);
    color: var(--dropdown-current-color);
    text-decoration: none;
  }

  .site
    :where(
      .site-header__link,
      .site-header__brand,
      .site-header__dropdown-toggle,
      .site-header__menu-summary,
      .site-header__menu-toggle
    ):focus-visible {
    outline: var(--focus-w) solid var(--color-focus);
    outline-offset: var(--focus-offset);
  }

  @supports (anchor-name: --header-menu-anchor) {
    .site .site-header__dropdown {
      anchor-scope: --header-menu-anchor;
    }

    .site .site-header__dropdown-parent {
      anchor-name: --header-menu-anchor;
    }

    .site .site-header__dropdown-panel {
      position-anchor: --header-menu-anchor;
      top: calc(anchor(bottom) + var(--dropdown-gap));
      left: anchor(center);
      right: auto;
      transform: translateX(-50%);
    }
  }

  /* -------------------------------------------------- Collapsing the navigation

   A phone gets the menu button, full stop: nothing useful fits beside a parish
   name at that width. The tier is chosen by *naming* the header's own container,
   because `container-name` is an ordinary property a selector can set, which
   lets the collapsed layout below be written once instead of once per trigger.

   Above that floor the links stay on the row and `mobile-nav` moves the ones
   that do not fit behind "More" — the priority+ pattern. That decision needs the
   rendered labels, not a width: the same four links need about 584px when they
   read "Home" and about 1323px when they read "Mass & Confession Times". With no
   script the full row renders and a crowded one wraps, as it does today.

   Widths for reference, measured per theme by binary-searching the narrowest
   container that still fits one row with parish-typical labels: about 8rem a
   link on a row shared with the brand and the button, about 6rem on a row of its
   own. */

  @container site (width < 36rem) {
    .site .site-header {
      container-name: site-header-collapsed;
    }
  }

  /* --------------------------------------------------------- Overflow ("More")

   The overflow dropdown ships empty and hidden, so a page with no script — and
   the first paint of every page — carries no stray control. `display` on the
   dropdown would otherwise beat the `hidden` attribute. */

  .site .site-header__dropdown[hidden] {
    display: none;
  }

  /* Hidden rather than moved: the authored dropdown keeps the listeners
   `dropdown-nav` gave it, and a link that is off the row is out of the tab order
   with it. */
  .site .site-header__nav-item--overflowed {
    display: none;
  }

  /* An overflowed dropdown arrives as its own label above its children, which is
   all the header's one level of nesting allows, so the label reads as a heading
   rather than one more link. It holds no link or button, so nothing here is
   hoverable; the inline padding the item rows get from `__dropdown-link` is
   restated so the heading still lines up with them.

   No block padding, and set at the foot of its cell, so the caption sits against
   the links it names rather than mid-row at whatever height the other column
   set. The room that leaves above it is the break from the group before, which
   the first heading has no need of. */
  .site .site-header__overflow-group {
    align-self: end;
    padding: 0 var(--dropdown-item-pad-x);
  }

  .site .site-header__overflow-group:not(:first-child) {
    padding-block-start: var(--dropdown-item-pad-y);
  }

  .site .site-header__overflow-group .site-header__dropdown-label {
    font-size: var(--caption-size);
    letter-spacing: var(--nav-tracking);
    text-transform: uppercase;
    opacity: 0.65;
  }

  /* A group whose own page is the one being viewed. Its rows are here rather
     than behind a shut flyout, so this heading is the only part of the group the
     state can be read off — a caption left at rest reads as the one row the mark
     missed. Contrast alone, which is what the heading has: the themes' rules and
     dots are drawn on link rows, and this is not one. */
  .site .site-header__overflow-group[aria-current="page"] {
    color: var(--header-text, var(--color-text));
  }

  .site
    .site-header__overflow-group[aria-current="page"]
    .site-header__dropdown-label {
    opacity: 1;
  }

  @container site-header-collapsed (inline-size >= 0px) {
    .site .site-header__inner,
    .site .site-header[data-variant] > .site-header__inner {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: var(--space-sm);
      padding: var(--space-sm) var(--site-gutter);
      text-align: left;
    }

    .site .site-header.is-sticky.is-scrolled > .site-header__inner,
    .site
      .site-header.is-sticky.is-scrolled[data-variant]
      > .site-header__inner {
      padding-block: var(--space-xs);
    }

    .site .site-header__logo {
      max-height: var(--header-mobile-logo-h);
    }

    .site .site-header__menu {
      display: block;
      flex: none;
      min-width: 0;
      width: auto;
      margin: 0;
    }

    .site .site-header__menu:not(.is-upgraded) {
      margin-inline-start: var(--header-mobile-menu-margin);
    }

    .site .site-header__menu-summary,
    .site .site-header__menu-toggle {
      display: inline-flex;
      align-items: center;
      gap: var(--space-xs);
      border: 0;
      padding-block: var(--btn-pad-y-s);
      padding-inline: var(--btn-pad-x-s) 0;
      margin: 0;
      background: transparent;
      color: var(--header-text, var(--color-text));
      cursor: pointer;
      font: inherit;
      font-weight: var(--nav-weight);
      list-style: none;
    }

    .site .site-header__menu-toggle {
      margin-inline-start: var(--header-mobile-menu-margin);
    }

    .site .site-header__menu--cta-only,
    .site .site-header__menu-toggle--cta-only {
      display: var(--header-mobile-menu-cta-display);
    }

    .site .site-header__menu-summary::-webkit-details-marker {
      display: none;
    }

    .site .site-header__menu.is-upgraded > .site-header__menu-summary {
      display: none;
    }

    /* The glyph draws itself — see `MenuIcon` in Header.tsx — so this only sizes
       it. Square rather than the wide, flat three-bar box the pseudo-elements
       used to paint, which is what the rest of the product's Lucide icons are. */
    .site .site-header__menu-icon {
      display: block;
      width: var(--icon-md);
      height: var(--icon-md);
    }

    .site .site-header__menu-panel {
      box-sizing: border-box;
      position: absolute;
      top: 100%;
      left: 0;
      z-index: 30;
      display: none;
      width: 100%;
      padding: var(--space-md) var(--site-gutter);
      border-block: var(--border-w) solid var(--color-border);
      background: var(--header-bg, var(--color-bg));
      box-shadow: var(--shadow-md);
    }

    .site
      .site-header__menu[open]:not(.is-upgraded)
      > .site-header__menu-panel {
      display: block;
    }

    .site .site-header__nav,
    .site .site-header[data-variant] .site-header__nav {
      display: flex;
      flex-direction: column;
      align-items: stretch;
      gap: 0;
      width: 100%;
      margin: 0;
      padding: 0;
      border: 0;
      text-align: left;
    }

    .site .site-header__nav > .site-header__link,
    .site .site-header__dropdown-parent {
      width: 100%;
      padding-block: var(--dropdown-pad);
    }

    .site .site-header__dropdown-parent-link {
      flex: 1;
    }

    .site .site-header__dropdown {
      display: block;
      width: 100%;
    }

    .site .site-header__dropdown-panel {
      position: static;
      width: auto;
      min-width: 0;
      max-width: none;
      padding: 0 0 0 var(--space-md);
      border: 0;
      border-radius: 0;
      box-shadow: none;
      transform: none;
      transition: none;
    }

    /* The panel is in the flow here, so there is no gap to bridge. */
    .site .site-header__dropdown::after {
      content: none;
    }

    /* A narrow editor canvas still reports a mouse as its primary pointer. */
    .site
      .site-header__dropdown:hover:not(.is-open)
      > .site-header__dropdown-panel,
    .site .site-header__dropdown:not(.is-open) > .site-header__dropdown-panel {
      display: none;
    }

    .site .site-header__dropdown:not(.is-open) .site-header__dropdown-chevron {
      transform: rotate(0deg);
    }

    .site .site-header__dropdown.is-open > .site-header__dropdown-panel {
      display: block;
    }

    .site .site-header__dropdown.is-open .site-header__dropdown-chevron {
      transform: rotate(180deg);
    }

    .site .site-header__menu:not(.is-upgraded) .site-header__dropdown-toggle {
      display: none;
    }

    .site .site-header__menu:not(.is-upgraded) .site-header__dropdown-panel {
      display: block;
    }

    .site .site-header__dropdown-link {
      white-space: normal;
    }

    .site .site-header__cta--mobile-bar {
      display: var(--header-mobile-bar-cta-display);
      margin: 0 0 0 auto;
    }

    .site .site-header__cta--menu {
      display: var(--header-mobile-menu-cta-display);
      margin: var(--space-md) 0 0;
    }

    /* ----------------------------------------------------- The menu panel

       One panel at every setting, and one decision left to the author: how its
       surface meets the edge. Coverage is the width's to decide — the panel
       covers the screen on a phone and becomes a side panel on a tablet — so the
       three `[data-layout]` variants differ only in inset, radius and shadow.

       Phone and tablet are told apart by the header's own inline size, which
       needs no second breakpoint: the stylesheet names this container below
       36rem, and `mobile-nav` names it above 36rem only when not one link fits
       beside the brand. Querying the same name against the same width therefore
       separates the two triggers exactly. */

    .site .site-header__menu-dialog {
      box-sizing: border-box;
      inset: 0;
      width: 100%;
      height: 100%;
      max-width: none;
      max-height: none;
      margin: 0;
      border: 0;
      padding: 0;
      background: transparent;
      color: inherit;
    }

    .site .site-header__menu-dialog[open] {
      display: block;
    }

    /* Padding on the dialog rather than a margin on the panel, so the panel is
       always simply the dialog's content box whichever variant is showing. */
    .site .site-header__menu-dialog[data-layout="inset-card"] {
      padding: var(--nav-panel-inset);
    }

    .site .site-header__menu-dialog[data-layout="sheet"] {
      padding-block-start: var(--nav-panel-sheet-reveal);
    }

    /* Drawn for every variant: `full-bleed` covers it completely, and the other
       two leave page showing, which is what makes a tap outside a way out. */
    .site .site-header__menu-dialog::backdrop {
      background: var(--color-scrim);
      opacity: 0.55;
    }

    .site .site-header__menu-dialog > .site-header__menu-panel {
      position: static;
      z-index: auto;
      display: flex;
      flex-direction: column;
      /* The flexing nav is what normally holds the button at the bottom, but a
         header with a primary button and no navigation renders no nav at all —
         then this is what keeps the button off the panel head. With a nav
         present it has no free space to distribute and changes nothing. */
      justify-content: space-between;
      width: 100%;
      height: 100%;
      padding: 0;
      border: 0;
      overflow: hidden;
      box-shadow: none;
    }

    .site
      .site-header__menu-dialog[data-layout="inset-card"]
      > .site-header__menu-panel {
      border-radius: var(--radius-nav-panel);
      box-shadow: var(--shadow-lg);
    }

    .site
      .site-header__menu-dialog[data-layout="sheet"]
      > .site-header__menu-panel {
      border-start-start-radius: var(--radius-nav-panel);
      border-start-end-radius: var(--radius-nav-panel);
    }

    /* Tablet: a side panel against the inline-end edge, under the button that
       opened it. A sheet keeps its rounded leading edge rather than its top. */
    @container site-header-collapsed (inline-size >= 36rem) {
      .site .site-header__menu-dialog {
        inset-inline: auto 0;
        width: var(--nav-panel-w);
      }

      .site .site-header__menu-dialog[data-layout="sheet"] {
        padding-block-start: 0;
      }

      .site
        .site-header__menu-dialog[data-layout="sheet"]
        > .site-header__menu-panel {
        border-start-end-radius: 0;
        border-start-start-radius: var(--radius-nav-panel);
        border-end-start-radius: var(--radius-nav-panel);
      }
    }

    /* ------------------------------------------------ The panel's contents

       Three bands: the brand and the close button, the links, and the primary
       button held at the bottom however few links there are. */

    .site .site-header__menu-dialog .site-header__menu-panel-head {
      box-sizing: border-box;
      display: flex;
      flex: none;
      align-items: center;
      justify-content: space-between;
      gap: var(--space-sm);
      padding: var(--space-md) var(--site-gutter) var(--space-sm);
    }

    /* Wraps rather than shrinking, so a long parish name costs a second line and
       never moves the close button. */
    .site .site-header__menu-panel-brand {
      min-width: 0;
      color: var(--header-text, var(--color-heading));
      font-family: var(--font-heading);
      font-size: var(--nav-panel-size);
      font-weight: var(--heading-weight);
      letter-spacing: var(--heading-tracking);
      line-height: var(--line-height-heading);
      overflow-wrap: anywhere;
      text-decoration: none;
    }

    /* `max-width` as well as the height cap: a wide logo keeps its intrinsic
       width otherwise, and the brand link's `min-width: 0` resizes the link, not
       the image inside it — so a letterbox logo grew into the close button. */
    .site .site-header__menu-panel-logo {
      display: block;
      width: auto;
      max-width: 100%;
      max-height: var(--header-mobile-logo-h);
    }

    .site .site-header__menu-dialog-close {
      box-sizing: border-box;
      display: flex;
      flex: none;
      align-items: center;
      justify-content: center;
      width: var(--touch-target);
      height: var(--touch-target);
      margin: 0;
      border: 0;
      padding: 0;
      /* Round whatever shape the theme gives its buttons: a close affordance is
         a glyph in a target, not a button, and a square one beside the parish
         name reads as a second block of chrome. */
      border-radius: var(--radius-pill);
      background: var(--color-surface);
      color: var(--header-text, var(--color-text));
      cursor: pointer;
      font: inherit;
    }

    .site .site-header__menu-dialog-close-icon {
      display: block;
      width: var(--icon-sm);
      height: var(--icon-sm);
    }

    .site .site-header__menu-dialog .site-header__nav {
      /* The theme gives the nav `width: 100%`, so without this its own inline
         padding lands outside the panel and the chevrons are clipped away. */
      box-sizing: border-box;
      flex: 1;
      gap: var(--nav-gap);
      padding-inline: var(--site-gutter);
      overflow-y: auto;
      overscroll-behavior: contain;
    }

    /* Sized by min-height, never by font size: the themes set their nav in
       everything from a 700-weight grotesk to a display serif, and a row that
       tracks the type is a different tap target on each. */
    .site .site-header__menu-dialog .site-header__nav > .site-header__link,
    .site .site-header__menu-dialog .site-header__dropdown-parent {
      box-sizing: border-box;
      display: flex;
      align-items: center;
      gap: var(--space-sm);
      min-height: var(--nav-panel-row-h);
      padding-block: 0;
      padding-inline: var(--nav-link-pad-x);
      border-radius: var(--radius-nav-link);
      font-family: var(--font-heading);
      font-size: var(--nav-panel-size);
    }

    /* Every label in the panel starts on the same line as the parish name above
       it: the row's own inline padding is pulled straight back out again, so
       what that padding buys is a tint that bleeds past the text rather than an
       indent. A group carries the pull for its whole block, which is why the
       parent row inside one does not repeat it.
       `width: auto` because the rows are handed `width: 100%` for the inline
       fallback panel: a resolved width leaves the negative margins
       over-constrained, and the box shifts by both rather than growing by
       them — a tint that bleeds on the leading edge and stops short of the
       other. */
    .site .site-header__menu-dialog .site-header__nav > .site-header__link,
    .site .site-header__menu-dialog .site-header__nav > .site-header__dropdown {
      width: auto;
      margin-inline: calc(-1 * var(--nav-link-pad-x));
    }

    /* A label with nowhere to go ends in an ellipsis: wrapping would break the
       row height the floor above depends on, and letting it push would carry the
       chevron off the panel. */
    .site .site-header__menu-dialog .site-header__link-label,
    .site .site-header__menu-dialog .site-header__dropdown-parent-link,
    .site .site-header__menu-dialog .site-header__dropdown-label {
      flex: 1;
      min-width: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .site .site-header__menu-dialog .site-header__dropdown-toggle {
      flex: none;
    }

    /* An open group is one tinted block, so its children read as belonging to
       the parent rather than as more rows of the same list. One tint and not
       three: the bar's own rules light the parent row of an open dropdown, and
       the submenu still carries the floating panel's surface, both of which
       would otherwise print on top of this one. */
    .site .site-header__menu-dialog .site-header__dropdown.is-open {
      border-radius: var(--radius-nav-link);
      background: var(--color-surface);
    }

    .site
      .site-header__menu-dialog
      .site-header__dropdown.is-open
      .site-header__dropdown-parent,
    .site .site-header__menu-dialog .site-header__dropdown-panel {
      background: transparent;
    }

    /* No block padding: the sub-rows carry their own height, so the tint ends on
       the last one rather than on a strip of empty surface below it. */
    .site .site-header__menu-dialog .site-header__dropdown-panel {
      padding: 0 var(--nav-link-pad-x);
    }

    /* Padded and pulled back out like the rows above, so a hovered sub-row
       lights the same band the parent row's own hover does. With no padding of
       its own the tint stopped dead on the first glyph, which reads as a
       misaligned block rather than as a row. */
    .site .site-header__menu-dialog .site-header__dropdown-link {
      box-sizing: border-box;
      display: flex;
      align-items: center;
      width: auto;
      min-height: var(--touch-target);
      padding-inline: var(--nav-link-pad-x);
      margin-inline: calc(-1 * var(--nav-link-pad-x));
      border-radius: var(--radius-nav-link);
      color: var(--color-text-muted);
      font-family: var(--font-body);
      font-size: var(--text-base);
      white-space: nowrap;
    }

    /* The panel's sub-rows rest muted, and that rule is later in this sheet than
       the shared full-ink one, so the current row has to take it back here. */
    .site
      .site-header__menu-dialog
      .site-header__dropdown-link[aria-current="page"] {
      color: var(--header-text, var(--color-text));
    }

    /* In the panel whichever way `mobileCta` is set. Opening the menu takes the
       header behind the backdrop, and with it a bar-placed button, so the
       setting decides whether the button is reachable without opening the menu —
       not whether the menu has one. */
    .site .site-header__menu-dialog .site-header__cta--menu {
      box-sizing: border-box;
      display: flex;
      flex: none;
      justify-content: center;
      min-height: var(--nav-panel-row-h);
      margin: var(--space-md) var(--site-gutter) var(--site-gutter);
    }
  }

  .site .site-footer__column-title {
    color: var(--color-heading);
    margin: 0 0 var(--space-sm);
  }

  /* Footer link columns are `<details name="footer">` so a phone can fold them
   into an exclusive accordion with no script. Nothing forces a closed
   `<details>` open, so the links are revealed through `::details-content`
   instead: shown at every width by default, hidden on a narrow container when
   the theme asks for the accordion. A browser without the pseudo-element keeps
   the columns closed at every width, which leaves every link one tap behind its
   title rather than unreachable. */
  .site summary.site-footer__column-title {
    box-sizing: border-box;
    list-style: none;
  }

  .site summary.site-footer__column-title::-webkit-details-marker {
    display: none;
  }

  .site summary.site-footer__column-title::after {
    content: "";
    display: none;
    width: var(--dropdown-chevron-size);
    height: var(--dropdown-chevron-size);
    border-right: var(--caret-w) solid currentColor;
    border-bottom: var(--caret-w) solid currentColor;
    transform: rotate(45deg) translate(-15%, -15%);
  }

  .site .site-footer__column[open] > summary::after {
    transform: rotate(-135deg) translate(-15%, -15%);
  }

  @supports selector(::details-content) {
    .site .site-footer__column::details-content {
      content-visibility: visible;
    }

    @container site (width < 48rem) {
      .site .site-footer__column:not([open])::details-content {
        content-visibility: var(--footer-narrow-content, hidden);
      }

      .site summary.site-footer__column-title {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--nav-caret-gap);
        min-height: var(--touch-target);
        margin-bottom: 0;
        cursor: var(--footer-narrow-cursor, default);
      }

      .site summary.site-footer__column-title::after {
        display: var(--footer-narrow-marker, none);
      }
    }
  }

  .site .site-footer__links {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
  }

  .site .site-footer__link {
    color: var(--color-text-muted);
    text-decoration: none;
  }

  .site .site-footer__text {
    margin: 0;
  }
}

/* Above `site-theme`, where the compiled theme sets the band's own roles. */
@layer site-schemes {
  /* A bright band's fill leaves an empty field's edge the least room to read. */
  .site [data-scheme="bright"] .form-block,
  .site .form-block[data-scheme="bright"] {
    --form-control-edge: color-mix(
      in srgb,
      var(--color-text) 65%,
      var(--form-control-border)
    );
  }
}

@layer site-structure, site-theme, site-schemes, site-brand;

@layer site-theme {
  .site {
    /* Palette: the five brand colors every role is derived from */
    --palette-primary: #1f4fd8;
    --palette-secondary: #1a3fae;
    --palette-accent: #eef2fd;
    --palette-neutral: #101114;
    --palette-canvas: #ffffff;

    /* Color roles */
    --color-bg: #ffffff;
    --color-surface: #f7f8fa;
    --color-text: #101114;
    --color-text-muted: #63666e;
    --color-heading: #101114;
    --color-border: #e4e6ea;
    --color-primary: #1f4fd8;
    --color-primary-hover: #1a42b8;
    --color-on-primary: #ffffff;
    --color-link: #1f4fd8;
    --color-link-hover: #1a42b8;
    --color-on-hero: #ffffff;
    --color-secondary: #1a3fae;
    --color-secondary-hover: #0c2b9a;
    --color-on-secondary: #ffffff;
    --color-accent: #eef2fd;
    --color-accent-hover: #dadee9;
    --color-on-accent: #101114;
    --color-primary-subtle: #e2ebfd;
    --color-on-primary-subtle: #101114;
    --color-border-strong: #949596;
    --color-surface-raised: var(--color-bg);
    --color-focus: var(--color-link);
    --color-hero-bg: var(--color-primary);
    --cta-secondary-fg: var(--color-hero-bg);
    --form-danger: #b3261e;
    --form-danger-edge: #b3261e;
    --form-danger-on-card: #b3261e;
    --form-danger-on-card-edge: #b3261e;
    --color-scrim: #000000;
    --color-on-scrim: #ffffff;
    --color-scrim-floor: #666666;

    /* Type */
    --font-heading: "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif;
    --font-body: "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif;
    --line-height-body: 1.6;
    --line-height-heading: 1.15;
    --heading-weight: 700;
    --heading-tracking: normal;
    --heading-case: none;
    --text-xs: 0.694rem;
    --text-sm: 0.833rem;
    --text-base: 1rem;
    --text-lg: clamp(1.12rem, 1.093rem + 0.134cqi, 1.2rem);
    --text-xl: clamp(1.254rem, 1.192rem + 0.311cqi, 1.44rem);
    --text-2xl: clamp(1.404rem, 1.296rem + 0.54cqi, 1.728rem);
    --text-3xl: clamp(1.572rem, 1.404rem + 0.837cqi, 2.074rem);
    --text-4xl: clamp(1.76rem, 1.517rem + 1.214cqi, 2.488rem);
    --text-hero: clamp(1.97rem, 1.632rem + 1.693cqi, 2.986rem);
    --measure: 75ch;
    --heading-gap: 0.4em;
    --flow-gap: 1em;
    --list-indent: 1.4em;
    --list-gap: 0.25em;
    --quote-pad: 1em;
    --quote-rule-w: 3px;
    --underline-offset: 2px;

    /* Spacing scale, indexed by the shared spacingScale enum */
    --space-unit: 5px;
    --space-none: 0;
    --space-xs: 0.313rem;
    --space-sm: 0.625rem;
    --space-md: 1.25rem;
    --space-lg: 2.5rem;
    --space-xl: 4.375rem;
    --space-2xl: 7.5rem;

    /* Shape */
    --radius-sm: 6px;
    --radius-md: 12px;
    --radius-lg: 20px;
    --radius-pill: 999px;
    --radius-btn: var(--radius-md);
    --radius-media: var(--radius-md);
    --radius-round: var(--radius-lg);
    --radius-cta: var(--radius-btn);
    --radius-card: var(--radius-md);
    --radius-thumb: var(--radius-sm);
    --radius-dropdown: var(--radius-sm);
    --border-w: 1px;
    --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.06);
    --shadow-md: 0 4px 12px rgb(0 0 0 / 0.08);
    --shadow-lg: 0 18px 44px rgb(36 27 46 / 0.16);
    --focus-w: 2px;
    --focus-offset: 3px;

    /* Components the structural sheet sizes from */
    --btn-weight: 600;
    --btn-case: none;
    --btn-leading: 1.2;
    --btn-pad-y: 0.85em;
    --btn-pad-x: 1.7em;
    --btn-pad-y-s: 0.55em;
    --btn-pad-x-s: 1.2em;
    --btn-pad-y-l: 1.1em;
    --btn-pad-x-l: 2.3em;
    --btn-text-s: var(--text-sm);
    --btn-text-m: var(--text-base);
    --btn-text-l: var(--text-lg);
    --cta-pad-y: var(--btn-pad-y);
    --cta-pad-x: var(--btn-pad-x);
    --cta-outline-hover-bg: rgb(255 255 255 / 0.14);
    --nav-gap: 2px;
    --nav-row-gap: 0.5rem;
    --nav-size: var(--text-base);
    --nav-weight: 600;
    --nav-case: none;
    --nav-tracking: normal;
    --nav-caret-gap: 0.4em;
    --nav-disclosure-pad: 0.5em;
    --caret-w: 1.5px;
    --nav-link-pad-y: 0.625rem;
    --nav-link-pad-x: 0.875rem;
    --radius-nav-link: var(--radius-sm);
    --nav-panel-size: var(--text-lg);
    --nav-panel-row-h: calc(var(--touch-target) + var(--space-sm));
    --nav-panel-inset: var(--space-md);
    --radius-nav-panel: var(--radius-lg);
    --nav-panel-w: min(88%, 24rem);
    --nav-panel-sheet-reveal: var(--touch-target);
    --nav-hover-bg: color-mix(in srgb, currentColor 8%, transparent);
    --nav-link-hover-bg: var(--nav-hover-bg);
    --nav-hover-decoration: none;
    --nav-underline-offset: 0.25em;
    --nav-ink-strength: 100%;
    --nav-ink-strength-custom: 85%;
    --nav-weight-active: 600;
    --nav-current-marker: #1a3fae;
    --nav-current-marker-lifted: #8bafff;
    --dropdown-chevron-size: 0.625em;
    --dropdown-rise: 6px;
    --dropdown-duration: 75ms;
    --dropdown-ease: ease-out;
    --header-logo-h: 40px;
    --header-mobile-logo-h: 36px;
    --header-mobile-layout: inset-card;
    --header-mobile-bar-cta-display: inline-flex;
    --header-mobile-menu-cta-display: none;
    --header-mobile-menu-margin: 0;
    --header-sticky: 1;
    --dropdown-gap: 0.375rem;
    --dropdown-min-w: 13rem;
    --dropdown-max-w: min(20rem, calc(100cqi - 2 * var(--site-gutter)));
    --dropdown-max-w-wide: min(38rem, calc(100cqi - 2 * var(--site-gutter)));
    --dropdown-pad: var(--space-sm);
    --dropdown-item-gap: 5px;
    --dropdown-item-pad-y: 0.5625rem;
    --dropdown-item-pad-x: 0.75rem;
    --radius-dropdown: var(--radius-card);
    --radius-dropdown-item: var(--radius-md);
    --dropdown-shadow: var(--shadow-lg);
    --dropdown-current-bg: color-mix(in srgb, var(--color-surface-raised) 92%, var(--color-primary));
    --dropdown-current-color: currentColor;
    --card-pad: var(--space-md);
    --card-gap: var(--space-sm);
    --card-text-gap: var(--space-xs);
    --card-body-size: var(--text-sm);
    --card-thumb-h: 4rem;
    --events-action-gap: 0.45em;
    --events-subscribe-icon-size: 1em;
    --media-w-thumb: 6rem;
    --media-w-xs: 10rem;
    --media-w-sm: 22rem;
    --media-w-md: 42rem;
    --caption-size: var(--text-sm);
    --caption-gap: 0.6em;
    --caption-overlay-pad: 2.4em 1.2em 0.9em;
    --caption-overlay-scrim: linear-gradient(to top, rgb(0 0 0 / 0.62), rgb(0 0 0 / 0) 100%);
    --eyebrow-size: max(0.75rem, var(--text-xs));
    --eyebrow-gap: 0.5em;
    --eyebrow-weight: 600;
    --eyebrow-tracking: 0.08em;
    --eyebrow-case: uppercase;
    --subtitle-size: var(--text-lg);
    --hallow-card-min: 15rem;
    --hallow-card-max: 26rem;
    --hallow-row-min: 17rem;
    --hallow-thumb: 3.5rem;
    --hallow-panel-w: 20rem;
    --hallow-index-w: 1.25rem;
    --hallow-text-gap: 0.15em;
    --hallow-meta-gap: 0.35em;
    --touch-target: 44px;

    /* Narrow containers: what this theme changes once the site is phone-width */
    --hero-h-small-wide: 220px;
    --hero-h-small-narrow: 180px;
    --hero-h-medium-wide: 380px;
    --hero-h-medium-narrow: 300px;
    --hero-h-large-wide: 560px;
    --hero-h-large-narrow: 420px;
    --hero-h-full-wide: 100svh;
    --hero-h-full-narrow: 100svh;
    --columns-mobile-template: minmax(0, 1fr);
    --columns-mobile-overflow: visible;
    --columns-scroll-w: 16rem;
    --footer-narrow-content: hidden;
    --footer-narrow-marker: block;
    --footer-narrow-cursor: pointer;

    /* Max-width scale, indexed by the shared maxWidth enum */
    --mw-sm: 640px;
    --mw-md: 768px;
    --mw-lg: 1024px;
    --mw-xl: 1280px;
    --mw-prose: 75ch;
    --measure-max: 84ch;
    --block-measure: max(75ch, min(80%, var(--measure-max)));

    /* Icon scale, indexed by the shared SiteIconSize tokens */
    --icon-sm: 20px;
    --icon-md: 24px;
    --icon-lg: 32px;

    /* Motion */
    --dur-fast: 120ms;
    --dur-base: 150ms;
    --dur-slow: 240ms;
    --ease: ease;

    /* Content container: caps overall content width so blocks don't stretch too wide on large viewports, and a responsive gutter so nothing touches the edge */
    --site-container: 1120px;
    --site-gutter: clamp(1.25rem, 5cqi, 3rem);

    background-color: var(--color-bg);
    color: var(--color-text);
    font-family: var(--font-body);
    line-height: var(--line-height-body);
    -webkit-font-smoothing: antialiased;
  }
}

@layer site-structure, site-theme, site-schemes, site-brand;

@layer site-schemes {
  /* The theme's default, and the fallback for a scheme it does not offer. */
  .site [data-scheme]:not([data-scheme="light"]):not([data-scheme="bright"]):not([data-scheme="dark"]):not([data-scheme="darkest"]) {
    --color-bg: #ffffff;
    --color-surface: #f7f8fa;
    --color-surface-raised: #ffffff;
    --color-text: #101114;
    --color-text-muted: #63666e;
    --color-heading: #101114;
    --color-border: #e4e6ea;
    --color-border-strong: #949596;
    --color-primary: #1f4fd8;
    --color-primary-hover: #1a42b8;
    --color-on-primary: #ffffff;
    --color-primary-subtle: #e2ebfd;
    --color-on-primary-subtle: #101114;
    --color-on-secondary: #ffffff;
    --color-on-accent: #101114;
    --color-link: #1f4fd8;
    --color-link-hover: #1a42b8;
    --color-hero-bg: #ffffff;
    --color-on-hero: #101114;
    --cta-secondary-fg: #ffffff;
    --form-danger: #b3261e;
    --form-danger-edge: #b3261e;
    --form-danger-on-card: #b3261e;
    --form-danger-on-card-edge: #b3261e;
  }

  .site [data-scheme="light"] {
    --color-bg: #f7f8fa;
    --color-surface: #ffffff;
    --color-surface-raised: #ffffff;
    --color-text: #101114;
    --color-text-muted: #4b4c50;
    --color-heading: #101114;
    --color-border: #dcdde0;
    --color-border-strong: #8f9093;
    --color-primary: #1f4fd8;
    --color-primary-hover: #103ac3;
    --color-on-primary: #f7f8fa;
    --color-primary-subtle: #dbe5f8;
    --color-on-primary-subtle: #101114;
    --color-on-secondary: #f7f8fa;
    --color-on-accent: #101114;
    --color-link: #1f4fd8;
    --color-link-hover: #103ac3;
    --color-hero-bg: #f7f8fa;
    --color-on-hero: #101114;
    --cta-secondary-fg: #f7f8fa;
    --form-danger: #b3261e;
    --form-danger-edge: #b3261e;
    --form-danger-on-card: #b3261e;
    --form-danger-on-card-edge: #b3261e;
  }

  .site [data-scheme="bright"] {
    --color-bg: #1f4fd8;
    --color-surface: #2454d9;
    --color-surface-raised: #2d5edc;
    --color-text: #ffffff;
    --color-text-muted: #c9dbff;
    --color-heading: #ffffff;
    --color-border: #3464de;
    --color-border-strong: #88aeff;
    --color-primary: #ffffff;
    --color-primary-hover: #ebebeb;
    --color-on-primary: #1f4fd8;
    --color-primary-subtle: #3868df;
    --color-on-primary-subtle: #ffffff;
    --color-on-secondary: #ffffff;
    --color-on-accent: #1f4fd8;
    --color-link: #ffffff;
    --color-link-hover: #ebebeb;
    --color-hero-bg: #1f4fd8;
    --color-on-hero: #ffffff;
    --cta-secondary-fg: #1f4fd8;
    --form-danger: #ffc8bf;
    --form-danger-edge: #ff9081;
    --form-danger-on-card: #ffe0da;
    --form-danger-on-card-edge: #ffa79a;
  }

  .site [data-scheme="dark"],
  .site [data-scheme="darkest"] {
    --color-bg: #101114;
    --color-surface: #151619;
    --color-surface-raised: #1d1e21;
    --color-text: #ffffff;
    --color-text-muted: #aeafb0;
    --color-heading: #ffffff;
    --color-border: #232427;
    --color-border-strong: #5f6063;
    --color-primary: #2253dc;
    --color-primary-hover: #2253dc;
    --color-on-primary: #ffffff;
    --color-primary-subtle: #131929;
    --color-on-primary-subtle: #ffffff;
    --color-on-secondary: #ffffff;
    --color-on-accent: #101114;
    --color-link: #497eff;
    --color-link-hover: #4377f8;
    --color-hero-bg: #101114;
    --color-on-hero: #ffffff;
    --cta-secondary-fg: #101114;
    --form-danger: #d84c3e;
    --form-danger-edge: #b62a21;
    --form-danger-on-card: #e35648;
    --form-danger-on-card-edge: #c0352a;
  }
}

/*
 * Theme "one" ("Modern"): the arrangement half of the theme.
 *
 * The tokens this theme used to declare here are compiled from its manifest
 * (`../themes/one/manifest.ts`) and concatenated in front of this file at seed
 * time. What is left is the part a token cannot express: the arrangement this
 * theme gives the blocks whose layout a theme owns (hero, header, footer), and
 * the dark-mode palette, which stays hand-written because the manifest has no
 * dark fields yet. Mechanics shared by every theme (resets, positioning, the
 * full-bleed trick) live in site-structure.css, which is loaded first.
 *
 * Arrangement is written twice over: once with bare selectors, which is this
 * theme's default and therefore what a block with no `variant` gets, and once
 * per named variant as a `[data-variant="…"]` override for blocks that pin one
 * arrangement across theme switches. Attribute selectors outrank the bare rules
 * on specificity, so the variant blocks can appear in any order below.
 *
 * The obligation this puts on a theme: every variant in the shared vocabulary
 * (see `heroVariant` / `headerVariant` / `footerVariant` in sharedSchemas.ts)
 * has to be reachable, because a block can pin any of them and expects it to
 * survive a theme switch. A variant that happens to *be* this theme's default
 * needs no rule of its own — the bare selectors already are it — which is why
 * "overlay", "inline" and "columns" have no block below. Anything else does, and
 * has to undo enough of the default to stand on its own.
 *
 * Fonts are intentionally not loaded here: the token stack degrades to system
 * fonts, and actual webfont delivery (preload / self-host / asset pipeline) is
 * the consuming renderer's job, so this stylesheet stays free of any runtime
 * network dependency.
 *
 * The sheet sits in the `site-theme` cascade layer, alongside the token block
 * compiled from the manifest and concatenated in front of it. The order
 * statement is repeated here because Storybook loads this sheet on its own and
 * a layer's position is fixed wherever it is first named.
 */

@layer site-structure, site-theme, site-schemes, site-brand;

@layer site-theme {
  /* Modern marks the current page with weight and ink and nothing else: the rest
     of the bar sits a touch off full and the current one is full ink two steps of
     the weight axis above it, which is what carries the state — the ink step is
     only there to settle the rest of the bar behind it. A hovered link therefore
     carries no fill and a current dropdown child no tint — either would be a
     second, competing signal. `-custom` matches the rest, so a header wearing the
     parish's own colors drops no further. The compiled token block sits earlier
     in this layer, so these override it. */
  .site {
    --nav-weight-active: 800;
    --nav-ink-strength: 80%;
    --nav-ink-strength-custom: var(--nav-ink-strength);
    --nav-link-hover-bg: transparent;
    --dropdown-current-bg: transparent;
  }

  /* Hover carries no fill here and the ink step above it is the current page's,
     so hover takes the other direction and dims. */
  .site .site-header__link:hover {
    color: color-mix(
      in srgb,
      var(--header-text, var(--color-text)) 70%,
      transparent
    );
  }

  /* Core after dark: the page becomes the same near-black the light palette
     paints its band in, and the accent lifts just far enough to clear it. Only
     just — the hero band is filled with the primary and its label is
     `--color-on-hero`, so a brighter blue would take white off the headline.
     The hover therefore goes down rather than up, for the same reason. */
  .dark .site,
  .site.dark {
    --color-bg: #101114;
    --color-surface: #191a1e;
    --color-text: #e7e9ed;
    --color-text-muted: #9a9da4;
    --color-heading: #f4f5f7;
    --color-border: #2b2d31;
    --color-primary: #3a6bf0;
    --color-primary-hover: #3163ee;
    --color-on-primary: #ffffff;
    --color-link: #7ea6ff;
    --color-link-hover: #98baff;
    --color-on-hero: #ffffff;
  }

  /* ================================================================ Hero
     Modern's default: a generous full-width band with the content column
     vertically centred over the background. */

  .site .hero {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-inline: var(--space-lg);
    border-radius: var(--radius-lg);
  }

  /* Square edges read cleaner where the band meets the viewport. */
  .site .hero[data-bleed] {
    border-radius: 0;
  }

  .site .hero[data-height="small"] {
    min-height: var(--hero-h-small);
  }
  .site .hero[data-height="medium"] {
    min-height: var(--hero-h-medium);
  }
  .site .hero[data-height="large"] {
    min-height: var(--hero-h-large);
  }
  .site .hero[data-height="full"] {
    min-height: var(--hero-h-full);
  }

  .site .hero__inner {
    max-width: var(--mw-lg);
    margin-inline: auto;
  }

  .site .hero__headline {
    font-size: var(--text-hero);
  }

  .site .hero__subhead {
    margin: 0.75em 0 0;
    max-width: 46ch;
    font-size: var(--text-xl);
  }

  .site .hero__actions {
    gap: var(--space-sm);
    margin-top: var(--space-lg);
  }

  .site .hero[data-text-align="center"] .hero__subhead {
    margin-inline: auto;
  }
  .site .hero[data-text-align="right"] .hero__subhead {
    margin-inline: auto 0;
  }
  .site .hero[data-text-align="center"] .hero__actions {
    justify-content: center;
  }
  .site .hero[data-text-align="right"] .hero__actions {
    justify-content: flex-end;
  }

  /* Hero variant: split — the background image becomes a real side panel rather
     than a backdrop, so it needs unwinding from the absolute layering that the
     overlay arrangement depends on. */
  .site .hero[data-variant="split"] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    gap: var(--space-xl);
    padding-inline: var(--space-xl);
  }

  .site .hero[data-variant="split"] .hero__bg {
    position: relative;
    inset: auto;
    z-index: auto;
    grid-column: 2;
    grid-row: 1;
    border-radius: var(--radius-md);
  }

  /* The image no longer sits behind the text, so the scrim would only mute it. */
  .site .hero[data-variant="split"] .hero__overlay {
    display: none;
  }

  .site .hero[data-variant="split"] .hero__inner {
    grid-column: 1;
    grid-row: 1;
    max-width: none;
    margin-inline: 0;
  }

  .site .hero[data-variant="split"] .hero__headline {
    font-size: clamp(1.9rem, 4cqi, 2.75rem);
  }

  @container site (width < 45rem) {
    .site .hero[data-variant="split"] {
      grid-template-columns: minmax(0, 1fr);
    }
    .site .hero[data-variant="split"] .hero__bg,
    .site .hero[data-variant="split"] .hero__inner {
      grid-column: 1;
      grid-row: auto;
    }
  }

  /* Hero variant: minimal — a quiet lead-in rather than a banner. */
  .site .hero[data-variant="minimal"] {
    padding-block: var(--space-lg);
    padding-inline: var(--space-md);
  }

  .site .hero[data-variant="minimal"][data-height="small"],
  .site .hero[data-variant="minimal"][data-height="medium"],
  .site .hero[data-variant="minimal"][data-height="large"],
  .site .hero[data-variant="minimal"][data-height="full"] {
    min-height: 0;
  }

  .site .hero[data-variant="minimal"] .hero__headline {
    font-size: clamp(1.75rem, 3.5cqi, 2.25rem);
  }

  .site .hero[data-variant="minimal"] .hero__subhead {
    font-size: 1.05rem;
  }

  /* ================================================================ Header
     Modern's default: brand left, navigation pushed to the right. */

  .site .site-header__inner {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
  }

  .site .site-header__brand {
    flex: 0 0 auto;
    font-weight: 800;
    font-size: var(--text-xl);
    white-space: nowrap;
  }

  .site .site-header__menu {
    flex: 1;
    min-width: 0;
  }

  .site .site-header__menu-panel {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* Row gap only, matching the button's own horizontal margin: the navigation
       and the button sit on separate rows whenever this row wraps or a variant
       stacks the panel, and nothing was holding them apart there — a hovered
       link's background ran straight into the button. */
    row-gap: var(--space-sm);
    width: 100%;
  }

  .site .site-header__nav {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--nav-row-gap) var(--nav-gap);
    margin-left: auto;
  }

  .site .site-header__link {
    font-weight: var(--nav-weight);
    font-size: var(--nav-size);
    letter-spacing: var(--nav-tracking);
    text-transform: var(--nav-case);
  }

  /* The bar's own labels, which are measured against the width beside the parish
     name and have to stay one line each. Not every link: a dropdown row is a
     stacked line of text sized to the panel and must be free to wrap. */
  .site .site-header__nav > .site-header__link,
  .site .site-header__dropdown-parent-link {
    white-space: nowrap;
  }

  .site .site-header__cta {
    flex: 0 0 auto;
    margin-left: var(--space-md);
    white-space: nowrap;
  }

  /* Header variant: stacked — brand on its own line, navigation beneath it. */
  .site .site-header[data-variant="stacked"] > .site-header__inner {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-sm);
  }

  .site .site-header[data-variant="stacked"] .site-header__nav {
    margin-left: 0;
    flex-wrap: wrap;
  }

  .site .site-header[data-variant="stacked"] .site-header__menu {
    flex: none;
    width: 100%;
  }

  .site .site-header[data-variant="stacked"] .site-header__menu-panel {
    flex-direction: column;
    align-items: flex-start;
  }

  .site .site-header[data-variant="stacked"] .site-header__cta {
    margin-left: 0;
  }

  /* Header variant: centered — brand above a centred navigation row. */
  .site .site-header[data-variant="centered"] > .site-header__inner {
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    text-align: center;
  }

  .site .site-header[data-variant="centered"] .site-header__nav {
    margin-left: 0;
    justify-content: center;
    flex-wrap: wrap;
  }

  .site .site-header[data-variant="centered"] .site-header__menu {
    flex: none;
    width: 100%;
  }

  .site .site-header[data-variant="centered"] .site-header__menu-panel {
    flex-direction: column;
    align-items: center;
  }

  .site .site-header[data-variant="centered"] .site-header__cta {
    margin-left: 0;
  }

  /* ================================================================ Footer
     Modern's default: link columns above a centred copyright line. */

  .site .site-footer {
    padding: var(--space-lg);
    text-align: center;
    font-size: var(--caption-size);
  }

  .site .site-footer__columns {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xl);
    justify-content: center;
    margin-bottom: var(--space-lg);
    text-align: left;
  }

  /* The parish-name anchor is Gather's footer arrangement; this theme keeps the
     columns-over-colophon shape it already had. */
  .site .site-footer__brand {
    display: none;
  }

  .site .site-footer__column-title {
    font-weight: 700;
  }

  /* One full-width row per column on a phone, so each accordion title is a
     tappable row rather than a chip in a wrapped flex line. */
  @container site (width < 48rem) {
    .site .site-footer__columns {
      flex-direction: column;
      gap: var(--space-sm);
      margin-bottom: var(--space-md);
    }
  }

  /* Footer variant: compact — one row, columns beside the copyright line. */
  .site .site-footer[data-variant="compact"] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
    text-align: left;
  }

  .site .site-footer[data-variant="compact"] .site-footer__columns {
    gap: var(--space-lg);
    margin-bottom: 0;
    justify-content: flex-start;
  }

  .site .site-footer[data-variant="compact"] .site-footer__links {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-md);
  }

  .site .site-footer[data-variant="compact"] .site-footer__column-title {
    margin-bottom: var(--space-xs);
  }

  .site .site-footer[data-variant="compact"] .site-footer__text {
    margin-left: auto;
  }

  @container site-header-collapsed (inline-size >= 0px) {
    .site .site-header__inner,
    .site .site-header[data-variant] > .site-header__inner {
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: var(--space-sm);
      padding: var(--space-sm) var(--site-gutter);
      text-align: left;
    }

    .site .site-header.is-sticky.is-scrolled > .site-header__inner,
    .site
      .site-header.is-sticky.is-scrolled[data-variant]
      > .site-header__inner {
      padding-block: var(--space-xs);
    }

    /* `[data-variant]` as well, because the arrangements that stretch the menu on
       a wide screen (`stacked`, `centered`, cathedral's `inline`) outrank a bare
       selector here. Before the script upgrades it the menu is a real box holding
       the summary, so a stretched one held the button away from the right edge —
       which is the first paint of every page, and the whole of it without JS. */
    .site .site-header__menu,
    .site .site-header[data-variant] .site-header__menu {
      display: block;
      flex: none;
      min-width: 0;
      width: auto;
      margin: 0;
    }

    /* Upgraded, the menu element is only a container: its summary is hidden and
       its panel is positioned or has moved into the dialog. Leaving it in the
       flex row still cost a slot, so the row's gap landed after the menu button
       instead of before it — and an arrangement that stretches the menu
       (`stacked`, `centered`, cathedral's `inline`) pushed the button further
       still, since those rules outrank the reset above on specificity. Generating
       no box at all settles both: the button becomes the last thing on the row
       and sits against the content edge. */
    .site .site-header__menu.is-upgraded,
    .site .site-header[data-variant] .site-header__menu.is-upgraded {
      display: contents;
    }

    /* And the box the browser wraps an open `details`' content in, which
       `display: contents` on the element does not remove: it stayed a zero-width
       item on the row, so the row's gap still landed after the menu button. */
    .site .site-header__menu.is-upgraded::details-content {
      display: contents;
    }

    /* The brand absorbs the row's free space, so whatever follows it sits against
       the content edge whichever items the header renders. An auto margin on the
       last item cannot do that, because which item is last depends on props: a
       header with navigation but no primary button left its menu button beside
       the parish name.

       And a parish name is `nowrap` and cannot shrink, so a long one pushed the
       menu button past the right edge, where `overflow-x: clip` on the page
       cropped it. Let the name wrap instead: it costs a second line only on the
       headers that were losing their button. */
    .site .site-header__brand {
      min-width: 0;
      flex-shrink: 1;
      margin-inline-end: auto;
      white-space: normal;
      overflow-wrap: anywhere;
    }

    .site .site-header__menu-panel {
      display: none;
      align-items: initial;
      width: 100%;
    }

    .site
      .site-header__menu[open]:not(.is-upgraded)
      > .site-header__menu-panel {
      display: block;
    }

    /* The next three are restated here for one reason only: this sheet's own
       narrow rules above sit in a later cascade layer than the structural
       sheet's, and a layer outranks a selector, so they shadow it even where its
       selector is the more specific one. The panel's own `display: none` shadows
       the flex column; the nav's `gap: 0` and `padding: 0` are right for the
       inline fallback, whose panel carries the padding itself, and wrong once the
       panel is the dialog's full-height content box; and the panel's own
       `align-items: center` and `flex-wrap: wrap`, which stopped its bands being
       stretched to its width and let them size to max-content instead.
       `[data-variant]` again because the nav rule above is restated at that
       strength. */
    .site .site-header__menu-dialog > .site-header__menu-panel {
      display: flex;
      flex-direction: column;
      flex-wrap: nowrap;
      align-items: stretch;
    }

    .site .site-header__menu-dialog .site-header__nav,
    .site
      .site-header[data-variant]
      .site-header__menu-dialog
      .site-header__nav {
      justify-content: flex-start;
      /* The bar's nav wraps; the panel's is one scrolling column. Left wrapping
         it splits into a second column as soon as the links outrun the panel's
         height, instead of scrolling the one it has. */
      flex-wrap: nowrap;
      gap: var(--nav-gap);
      padding-inline: var(--site-gutter);
    }

    /* And the nav's voice, for the same layer reason. The bar's is a bar's:
       small, tracked, and on some themes uppercase — a full-screen list of that
       reads as a row of labels rather than as somewhere to go. The panel takes
       the theme's heading voice at reading size, and its sub-rows the body voice,
       which is the one hierarchy the panel has. */
    .site .site-header__menu-dialog .site-header__nav > .site-header__link,
    .site .site-header__menu-dialog .site-header__dropdown-parent-link,
    .site
      .site-header[data-variant]
      .site-header__menu-dialog
      .site-header__nav
      > .site-header__link {
      font-size: var(--nav-panel-size);
      letter-spacing: normal;
      text-transform: none;
    }

    .site .site-header__menu-dialog .site-header__dropdown-link,
    .site .site-header__menu-dialog .site-header__dropdown-label {
      font-family: var(--font-body);
      font-size: var(--text-base);
      letter-spacing: normal;
      text-transform: none;
    }

    .site .site-header__nav,
    .site .site-header[data-variant] .site-header__nav {
      display: flex;
      flex-direction: column;
      align-items: stretch;
      gap: 0;
      width: 100%;
      margin: 0;
      padding: 0;
      border: 0;
      text-align: left;
    }

    /* The brand's auto margin does the pushing, so the button only has to shed
       the inline margin the wide arrangement gives it. `[data-variant]` as well,
       to match the menu-placed button below: a variant's own
       `.site-header__cta { margin-left: … }` outranks a bare selector here. */
    .site .site-header__cta--mobile-bar,
    .site .site-header[data-variant] .site-header__cta--mobile-bar {
      margin: 0;
    }

    .site .site-header__cta--menu,
    .site .site-header[data-variant] .site-header__cta--menu {
      margin: var(--space-md) 0 0;
    }

    /* In the dialog the button is inset off the panel's own edges instead, the
       inline fallback's flush-inside-its-padding being wrong once the panel is
       the whole content box. Restated at `[data-variant]` strength for the same
       two reasons as the rule above: the layer, and the wide arrangement's
       `.site-header__cta { margin-left: … }`, which reaches in from outside
       every container query and would reset the leading edge on its own. */
    .site .site-header__menu-dialog .site-header__cta--menu,
    .site
      .site-header[data-variant]
      .site-header__menu-dialog
      .site-header__cta--menu {
      margin: var(--space-md) var(--site-gutter) var(--site-gutter);
    }
  }

  /* ------------------------------------------------------------------- Form */

  /* Quiet boxes: a hairline rule, a tighter corner, and state on the rule alone. */
  .site .form-block {
    --form-control-border-w: 1px;
    --form-control-border: var(--color-border);
    --form-control-border-hover: var(--color-text);
    --form-control-radius: 10px;
    --form-control-height: 3.125rem;
    --form-control-size: 0.96875rem;
    --form-card-radius: 0.875rem;
    --form-submit-height: 3.125rem;
    --form-submit-size: 0.96875rem;
    --form-submit-tracking: -0.01em;
  }

  /* Dark mode only: a band's own red is solved from its canvas in `compile.ts`. */
  .dark .site .form-block,
  .site.dark .form-block {
    --form-danger: #f2857f;
    --form-danger-edge: var(--form-danger);
    /* Dark mode fills the card with the canvas, so both surfaces are one red. */
    --form-danger-on-card: var(--form-danger);
    --form-danger-on-card-edge: var(--form-danger);
  }

  /* The focused field takes the accent's rule as well as the ring. */
  .site .form-block__control:focus-visible,
  .site .form-block__currency:focus-within,
  .site .form-block__phone-country:focus-within {
    border-color: var(--color-primary);
  }

  .site .form-block__title {
    letter-spacing: -0.025em;
  }
}

@layer site-structure, site-theme, site-schemes, site-brand;

@layer site-brand {
  .site {
    --palette-primary: #154fa1;
    --palette-accent: #154fa1;
    --color-surface: #f8f8f8;
    --color-text-muted: #4d4e51;
    --color-border: #e3e4e4;
    --color-primary: #154fa1;
    --color-primary-hover: #003d8d;
    --color-accent: #154fa1;
    --color-accent-hover: #003d8d;
    --color-on-accent: #ffffff;
    --color-primary-subtle: #e2e9f5;
  }

  .dark .site,
  .site.dark {
    --palette-primary: #154fa1;
    --palette-accent: #154fa1;
    --color-surface: #f8f8f8;
    --color-text-muted: #4d4e51;
    --color-border: #e3e4e4;
    --color-primary: #154fa1;
    --color-primary-hover: #003d8d;
    --color-accent: #154fa1;
    --color-accent-hover: #003d8d;
    --color-on-accent: #ffffff;
    --color-primary-subtle: #e2e9f5;
  }

  .site [data-scheme]:not([data-scheme="light"]):not([data-scheme="bright"]):not([data-scheme="dark"]):not([data-scheme="darkest"]) {
    --color-surface: #f8f8f8;
    --color-text-muted: #4d4e51;
    --color-border: #e3e4e4;
    --color-primary: #154fa1;
    --color-primary-hover: #003d8d;
    --color-primary-subtle: #e2e9f5;
    --color-on-accent: #ffffff;
  }

  .site [data-scheme="light"] {
    --color-bg: #f8f8f8;
    --color-text-muted: #4c4c4f;
    --color-border: #ddddde;
    --color-border-strong: #909092;
    --color-primary: #154fa1;
    --color-primary-hover: #003d8d;
    --color-on-primary: #f8f8f8;
    --color-primary-subtle: #dce3ef;
    --color-on-secondary: #f8f8f8;
    --color-on-accent: #f8f8f8;
    --color-hero-bg: #f8f8f8;
    --cta-secondary-fg: #f8f8f8;
  }

  .site [data-scheme="bright"] {
    --color-bg: #154fa1;
    --color-surface: #1c54a4;
    --color-surface-raised: #285ca8;
    --color-text-muted: #b9cce8;
    --color-border: #2f61ab;
    --color-border-strong: #79a2dd;
    --color-on-primary: #154fa1;
    --color-primary-subtle: #3464ad;
    --color-on-accent: #ffffff;
    --color-hero-bg: #154fa1;
    --cta-secondary-fg: #154fa1;
    --form-danger: #ffb0a4;
    --form-danger-edge: #ff7665;
    --form-danger-on-card: #ffc9c0;
    --form-danger-on-card-edge: #ff9082;
  }

  .site [data-scheme="dark"],
  .site [data-scheme="darkest"] {
    --color-primary: #265fb2;
    --color-primary-hover: #255fb1;
    --color-primary-subtle: #141a24;
    --color-on-accent: #ffffff;
  }
}
