  /* Page-to-page crossfade (2026-08-02). This page is a real document load
     from every other page on the site — it is not in nav-swap.js's in-place
     set — so arriving here and leaving here is where Daniel saw the worst of
     the flash. Same declaration as the youth pages' critical <style> block
     and as assets/css/styles.css; the two sides of a navigation each have to
     opt in or the engine skips the transition. Same-origin only, Chromium
     126+ / Safari 18.2+, ignored everywhere else.
     The declarations needed for the 390px first viewport are also copied into
     critical.css and inlined; this complete sheet applies after that frame. */
  @view-transition { navigation: auto; }
  /* Reduced motion kills the MOVEMENT (the group, which can travel and
     scale) and keeps a brief opacity fade — matched to styles.css. */
  @media (prefers-reduced-motion: reduce) {
    ::view-transition-group(*) { animation: none !important; }
    ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 120ms; }
  }

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

  :root {
    /* ── OAK AND SHADOW (2026-08-10) ──────────────────────────────────────
       This page carried a SECOND palette: its own warm greys and its own
       brass family (#b8862f/#8a6220/#d9a441), a shade cooler and darker than
       the hub's. Two sites in one, and it showed at the seam — you left a
       cream page and arrived on a different cream. The scale below is the
       hub's, token for token, so the two now shrink and warm identically.
       Rule of the gold family, unchanged: gold FILLS carry ink text; gold as
       TEXT on a light surface uses --accent-deep, which is now the hub's
       --gold-text (#6f5210, 5.2:1 on cream — the old #8a6220 measured 3.9:1
       on the warmer oak and would have quietly failed AA here). */
    /* the logo's midnight navy, matched to the hub token for token
       (2026-08-10). These two were the last of the old neutral blacks left on
       this page, and since the header is now dressed by the hub's stylesheet
       they are read BY that header — a stale --ink here would have made the
       one shared bar a different colour on this page than on every other. */
    --ink: #0d1526;
    --charcoal: #141822;
    --paper: #fbf7ef;       /* the card: the lightest tone on the scale */
    --bone: #f6f0e3;        /* the page under it */
    --bone-soft: #fbf7ef;   /* out-of-month cells */
    --bone-dim: #f0e7d6;    /* past days, quiet surfaces */
    --line: #e2d8c6;
    --line-strong: #d2be9c;
    --steel: #54585f;
    --steel-deep: #54585f;  /* AA on every band on the oak scale */
    --steel-mist: #63676e;  /* cancelled text — still clears 4.6:1 on oak */
    --ink-soft: #2f3238;    /* quiet pill text */
    /* accent family — brass/gold (3 steps + gradient).
       Rule of the family: gold FILLS carry ink text (white fails on any gold
       bright enough to read as gold); gold as TEXT on light surfaces always
       uses --accent-deep (4.8:1); on ink, --accent-glow (8.7:1). */
    --accent: #c99a2e;
    --accent-deep: #6f5210;
    --accent-glow: #ecc35c;
    --accent-gradient: linear-gradient(135deg, var(--accent-glow), var(--accent) 50%, #9a7118);
    --on-accent: #15120a;   /* the ink for text sitting on gold */
    /* edit-UI semantics (admin surfaces only) */
    --danger: #dc2626; --danger-ink: #991b1b; --danger-wash: #fef2f2;
    --amber: #f59e0b; --amber-ink: #92400e; --amber-wash: #fef3c7;
    --ok-ink: #065f46;
    --shadow-accent-soft: 0 4px 24px -4px rgba(201,154,46,.25);
    --shadow-accent-glow: 0 12px 40px -8px rgba(201,154,46,.45);
    --ease: cubic-bezier(.4,0,.2,1);
    /* focus ring for gold surfaces — the hub's --gold-ring, same value */
    --gold-ring: #9a7118;
    /* The gold behind an event pill ends HERE, not at --accent-deep: near-black
       on #9a7118 measures 4.23:1 and fails AA at pill sizes, while #a87b1a
       measures 4.91:1 and still reads as the deep end of the same family. */
    --accent-pill: linear-gradient(135deg, var(--accent-glow), var(--accent) 50%, #a87b1a);
    /* iPhone home-indicator inset */
    --safe-b: env(safe-area-inset-bottom, 0px);
    /* legacy aliases still referenced by shared rules */
    --text-dark: #0d1526;   /* the logo navy, matched to --ink */
    --text-muted: #54585f;
  }

  /* ── THE DOCUMENT SCROLLS HERE (2026-08-10) ────────────────────────────────
     The hub's stylesheet, now linked above for the header, opens with
     `html, body { height: 100%; overflow: hidden; }`: on the hub the DOCUMENT
     never scrolls, a .page-scroll element does, which is the device-proven fix
     for the iOS 26 status-band bleed on pages that run cross-document view
     transitions. This page is not one of them — view-transition-check asserts
     the calendar is a real document load, and it has no .page-scroll wrapper —
     so inheriting that lock left the month unscrollable below the fold. Put
     back, explicitly, and kept next to the rule it answers so the pair is
     obvious the next time the hub's stylesheet moves. overflow-x stays clipped
     on body, as it always was. */
  html, body { height: auto; overflow: visible; }
  body { overflow-x: clip; }
  html { scroll-behavior: smooth; }

  body {
    font-family: 'Inter', sans-serif;
    background: var(--bone);
    color: var(--text-dark);
    min-height: 100svh;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    font-feature-settings: "ss01", "cv11";
    overflow-x: clip;
  }

  /* boot.js uses html.js for the shared reveal gate. This page never used the
     shared paper-grain overlay, so keep that unrelated side effect off. */
  html.js body::after { display: none; }

  ::selection { background: var(--accent-deep); color: var(--paper); }
  :focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 3px; }
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-thumb { background: rgba(107,114,128,.35); border-radius: 8px; }
  @media (hover: hover) { ::-webkit-scrollbar-thumb:hover { background: rgba(201,154,46,.6); } }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
  }

  /* ── The site header ───────────────────────────────────────────────────────
     THE BAR IS NOW THE HUB'S OWN, NOT A COPY OF IT (2026-08-10, Daniel: 'why
     not just use the same header as the other pages??'). Until today this file
     carried ~270 lines of PORTED header CSS under `site-nav-` class names, on
     the theory that linking the hub's stylesheet would restyle the whole app.
     What that actually bought was a bar that drifted: the hub added two
     destinations on 08-09 and this copy did not get them, and every spacing
     and colour fix upstream had to be made twice or not at all.

     So the header is now the hub's markup (<header class="navbar">, verbatim)
     dressed by the hub's stylesheet, linked in <head> BEFORE this block. The
     collision worry was measured rather than assumed: sixteen class names are
     shared between the two files and every one belongs to the header, which is
     exactly what we want unified. Everything else the hub styles is
     element-level (body, a, h1–h4, button), and this block wins
     those by class specificity and by load order.

     WHERE IT LIVES MATTERS — outside .cal-wrap, always. Both export paths
     render #export-wrap, which buildExportDom() assembles from a CLONE of
     .cal-wrap, so a header outside .cal-wrap can never reach a downloaded
     image. ?print=N wipes the body outright, and it is hidden in @media print. */
  /* ── menu open: pin the bar, freeze the page, KEEP THE SCROLL ──────────────
     Two things go wrong at deep scroll if this is done casually, both measured
     in WebKit on this page:
       1. `overflow: hidden` on the root alone un-sticks the bar — it paints at
          document top, 142px above the viewport, so the X is unreachable.
          Hence position: fixed, as the hub does.
       2. This page is SHORT (one month ≈ 990px on a phone). Pulling the bar
          out of the flow shortens the document under the current offset and
          the browser clamps it; plain overflow-locking loses the offset
          outright. Either way you close the menu somewhere else on the page.
     So the body is parked at a negative top while the sheet is open and the
     script scrolls back to the exact offset on close — the scroll position is
     carried, not preserved by luck.

     Everything here hangs off html.nav-open (the script), NEVER off
     `body:has(#navtoggle:checked)` the way the hub's sheet may: that
     selector matches the instant the checkbox flips, i.e. BEFORE the script
     can read the scroll offset, and pulling the bar out of the flow right
     then makes the browser clamp the offset the script is about to save. The
     no-JS path needs no rule of its own — with nothing locking the scroller,
     plain sticky already holds the bar at the top of the viewport. */
  html.nav-open .navbar { position: fixed; top: 0; left: 0; right: 0; }
  html.nav-open { overflow: hidden; }
  html.nav-open body { position: fixed; left: 0; right: 0; width: 100%; overflow: hidden; }
  /* the edit strip is sticky at z-index 900 — above the sheet's own stacking
     context, so it would hover over the menu. Park it. (It carries the AI
     assistant button since 2026-08-11; the old fixed .ai-fab is gone.) */
  html.nav-open .edit-banner { display: none; }

  /* No band of its own under the site header any more (Daniel, 2026-07-31):
     the old ink hero repeated the wordmark's words — "Sulamita", "Youth" —
     right under the bar that had just said them, and pushed the grid a whole
     screen down on a phone. The month itself is the page's title now; the
     surface order goes straight ink bar → bone canvas → paper card, which is
     the Streamlined stack anyway. Exports never used this band — they build
     their own header in buildExportDom. */

  /* Wrapper — 1400px is the house container (STREAMLINED §6). It was 960 until
     2026-08-10, which left a month grid the size of a phone screen floating in
     the middle of a 27" monitor while every cell clipped its own event text. */
  .cal-wrap {
    max-width: 1400px;
    margin: 0 auto;
    padding: 36px 16px 48px;
  }


  /* .month-bar groups the month row and its meta line as ONE block of month
     chrome. display:contents keeps it out of the layout entirely — desktop,
     phone and both export paths all see exactly the markup they saw before it
     existed. (It briefly became a sticky bottom bar on a phone the morning of
     2026-08-02; that was reversed the same day — see the mobile block.) */
  .month-bar { display: contents; }

  /* Month nav — grid keeps the month centered while the action buttons get
     real space on the right (absolute positioning made them overlap the next
     arrow on widths where the Share button shows). The left column carries a
     44px floor: on a tight phone width a bare 1fr let the empty left column
     collapse under the actions column and the month sat 70px off the center
     axis.

     THE ACTIONS COLUMN IS FLOORED AT ITS CONTENT, NOT AT 44px (2026-08-17,
     Daniel in desktop Safari: the next-month arrow sat ON TOP OF "Add to
     calendar"). This is the bug that kept coming back, and this line is the
     reason. Both outer columns used to read minmax(44px, 1fr): 1fr splits the
     LEFTOVER space evenly and knows nothing about what is inside the track, so
     an editor's three-button toolbar (~397px) in a track handed 378px simply
     OVERFLOWED — and justify-self:end throws that overflow LEFT, over the
     arrow, then over the month name. Measured in WebKit at every width from
     ~1275px down to 920px. max-content as the track's MINIMUM means the grid
     reserves what the buttons actually need before it hands anything to the
     mirror column, so the month gives up a few pixels of centering instead of
     the toolbar giving up its place. Guarded by
     /root/youth-assets/calendar-nav-overlap-check-2026-08-17.js. */
  .month-nav {
    display: grid;
    grid-template-columns: minmax(44px, 1fr) auto auto auto minmax(max-content, 1fr);
    gap: 16px;
    align-items: center;
    justify-items: center;
    margin-bottom: 8px;
    position: relative;
  }
  /* Explicit placement keeps the month label on the page's center axis:
     arrows hug it in the auto columns, actions live in the right 1fr,
     the left 1fr mirrors it empty */
  #prevBtn { grid-column: 2; }
  .month-label { grid-column: 3; }
  #nextBtn { grid-column: 4; }
  .nav-actions { grid-column: 5; }
  /* 44px is the floor, and the two month arrows are the ONLY way to change the
     month since the twelve-pill row went (2026-08-02) — so they get 48 on a
     phone, where they are also the thing a thumb reaches for. */
  .nav-btn {
    width: 44px; height: 44px;
    border-radius: 9999px;
    border: 1px solid var(--line);
    background: var(--paper);
    color: var(--ink);
    font-size: 18px;
    cursor: pointer;
    text-decoration: none;
    display: flex; align-items: center; justify-content: center;
    transition: border-color .15s var(--ease), color .15s var(--ease), background .15s var(--ease), transform .3s var(--ease), box-shadow .3s var(--ease);
    line-height: 1;
  }
  #prevBtn, #nextBtn { font-size: 20px; }
  @media (hover: hover) {
    .nav-btn:hover {
      border-color: var(--accent);
      color: var(--accent-deep);
      transform: scale(1.02);
      box-shadow: var(--shadow-accent-soft);
    }
  }
  .nav-btn:active { transform: scale(.98); }
  /* WHICH CALENDAR (2026-08-10). Two scopes on one page: the ministry year and
     the youth house wall. Two options means both stay on screen — a dropdown
     would hide half the page from the person looking for it. Sized off the
     content so longer labels simply take the room they need instead of being
     clipped. */
  .room-switch {
    display: flex;
    justify-content: center;
    gap: 4px;
    margin: 0 auto 14px;
    padding: 4px;
    width: max-content;
    max-inline-size: 100%;
    border: 1px solid var(--line);
    border-radius: 9999px;
    background: var(--paper);
  }
  .room-btn {
    min-height: 44px;
    padding-inline: 18px;
    border: 0;
    border-radius: 9999px;
    background: transparent;
    color: var(--steel-deep);
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(11px, 0.6875rem + 0.06vi, 12px);
    letter-spacing: .12em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .15s var(--ease), color .15s var(--ease);
  }
  /* active = two changes at once (fill AND ink), never colour alone */
  .room-btn[aria-pressed="true"] {
    background: var(--ink);
    color: var(--paper);
  }
  @media (hover: hover) {
    .room-btn:not([aria-pressed="true"]):hover { color: var(--accent-deep); background: var(--bone); }
  }
  .room-btn:active { transform: scale(.98); }
  /* the month-image controls, put away while the house calendar is on screen */
  .room-hidden { display: none !important; }
  .month-label {
    /* the page's title since the ink band left — h1 weight, not widget weight */
    font-family: 'Inter Tight', sans-serif;
    font-size: clamp(40px, 2.5rem - 40px + 6vi, 48px);
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--ink);
    min-width: 200px;
    text-align: center;
    margin: 0; /* it's an h1 now — UA margin would split it from its arrows */
    /* the year rides on the same line as the name — a wrap between them would
       recreate the orphaned "2026" this change removed */
    text-wrap: nowrap;
    white-space: nowrap;
  }
  /* THE YEAR IS PART OF THE TITLE (2026-08-12, second pass — Daniel on an
     iPhone: an 11px mono "2026" tucked behind a 34px "August" read as debris,
     "even worse than before"). It is now the same word in the same voice: the
     h1's own font, two thirds its size, lighter and in steel so the month
     still leads. em units keep the ratio through every clamp step, and it sits
     on the h1 baseline because it is plain inline text — the word space in the
     markup is the whole gap. */
  .month-year {
    font-size: 0.65em;
    font-weight: 500;
    letter-spacing: -0.01em;
    color: var(--steel-deep);
  }
  /* All that is left of the mono line under the title: 'could not load'. It
     used to carry '2026 · 7 EVENTS THIS MONTH' — the count came off 2026-08-10
     (the grid underneath IS the count) and the year moved into the h1 on
     2026-08-12, which left a blank line holding the calendar down. min-height
     overrides styles.css's `#monthLabel, .event-count-badge { min-height: 1lh }`
     reservation: there is nothing here to reserve a line for until the load
     actually fails, and the margins alone are the gap to the grid. */
  .event-count-badge {
    text-align: center;
    min-height: 0;
    margin-bottom: 20px;
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(11px, 0.6875rem + 0.06vi, 12px);
    color: var(--steel-deep);
    font-weight: 500;
    letter-spacing: .16em;
    text-transform: uppercase;
  }

  /* (The twelve-month pill row — .quick-nav / .qnav-pill — was deleted on
     2026-08-02. It was the third way to change the month on a page that only
     needs one, and on a phone its twelve targets pushed the calendar itself
     below the fold. The two arrows beside the month name are the mechanism
     now.) */

  /* Calendar card */
  .cal-card {
    background: var(--paper);
    border-radius: 16px;
    border: 1px solid var(--line);
    overflow: hidden;
    animation: fadeInUp .35s var(--ease) both;
  }
  @keyframes fadeInUp {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  /* Day-of-week row */
  .dow-row {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    background: var(--ink);
  }
  .dow-cell {
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(11px, 0.6875rem + 0.06vi, 11.5px);
    font-weight: 500;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: rgba(255,255,255,.55);
    text-align: center;
    padding: 11px 4px;
  }

  /* Calendar grid — hairlines carry the structure */
  .cal-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
  }
  .cal-cell {
    min-height: 88px;
    container-type: inline-size;   /* pills adapt to the CELL, not the site */
    border-right: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    padding: 6px 5px 5px;
    position: relative;
    vertical-align: top;
    background: var(--paper);
    transition: background .15s var(--ease);
  }
  .cal-cell:nth-child(7n) { border-right: none; }
  .cal-cell.empty {
    background: var(--bone-soft);
    opacity: 0.6;
  }
  /* Sundays get a faint gold wash — visibly different from out-of-month cells */
  .cal-cell.sunday {
    background: rgba(201,154,46,.045);
  }
  .cal-cell.today {
    background: rgba(201,154,46,.07) !important;
    box-shadow: inset 0 0 0 1px rgba(201,154,46,.4);
  }
  /* Today = the live halo dot day number (brass coin, ink numeral) */
  .cal-cell.today .day-num {
    background: var(--accent-glow);
    color: var(--on-accent);
    border-radius: 50%;
    width: 26px; height: 26px;
    display: flex; align-items: center; justify-content: center;
    font-weight: 600;
    box-shadow: 0 0 22px 4px rgba(217,164,65,.55);
  }
  .day-num {
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    font-weight: 500;
    color: var(--text-dark);
    margin-bottom: 5px;
    line-height: 1;
  }
  .cal-cell.empty .day-num {
    color: var(--steel);
    opacity: 0.4;
  }

  /* Event pills — real <button>s since 2026-08-10 (they were unfocusable divs
     with a click handler, so a keyboard or switch user could not open a single
     event on the page). Everything below the first four lines is the button
     reset; the LOOK is unchanged.
     TYPE SIZE: the old 9.5px desktop / 8px phone was below anything readable
     and had no rem term, so it did not respond to text zoom at all (WCAG F94).
     Every size here is a clamp with a rem term and a range well under 2.5x. */
  .event-pill {
    /* flex column + centring, not plain block: the 44px floor below (the ship
       target in design-standards §Mobile — the pills measured 46x40 on a
       390px phone, audit 2026-08-12 F3) leaves slack under a one-line title,
       and centred is the only way that slack does not read as a typo. The
       ::before hit-slop is absolutely positioned, so it stays out of the flex
       flow. */
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 44px;
    position: relative;
    width: 100%;
    text-align: left;
    font-family: inherit;
    /* an event_type the CSS has no rule for must still look like a pill, not
       like an operating-system button */
    background: var(--bone);
    color: var(--ink-soft);
    border: 0;
    border-radius: 8px;
    padding: 5px 8px 6px;
    margin-bottom: 3px;
    font-size: clamp(11.5px, 0.71875rem + 0.12vi, 13px);
    font-weight: 500;
    line-height: 1.3;
    border-left: 3px solid transparent;
    cursor: default;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
    transition: transform .15s var(--ease), box-shadow .15s var(--ease);
  }
  @media (hover: hover) {
  .event-pill:hover {
    transform: translateX(1px);
    box-shadow: 0 2px 8px rgba(0,0,0,.1);
  }
  }
  .event-pill:focus-visible {
    outline: 2px solid var(--gold-ring);
    outline-offset: 2px;
  }
  .event-pill .pill-text { display: block; }
  .event-pill .pill-sub {
    display: block;
    font-size: clamp(10.5px, 0.65625rem + 0.1vi, 12px);
    opacity: 0.86;
    white-space: pre-line;
    margin-top: 1px;
    line-height: 1.25;
  }
  /* Adapt to the CELL, not to the site (BILINGUAL.md: adapt locally). In a
     phone-width column a 7-day grid gives each cell about 38-48px of inside
     width — legible type cannot also carry a wrapped title AND a time there,
     so the title clamps to two lines and the time line stands down. Both are
     still read in full in the day's popup, in the month list under the grid
     and in the Next-up block; nothing is only in the cell. */
  @container (max-width: 88px) {
    /* 3px of side padding, not 5 (2026-09-04). The cell gives the pill 47.6px
       at 390px and the chrome was eating 13 of them, leaving a 34.6px text box.
       4px back is 39.6, and it buys whole words: measured on the live page at
       390px, the count of distinct titles with a cut line goes 7/8 -> 6/8 in
       Chromium and 8/8 -> 6/8 in WebKit. "Life Group" and "Youth Prayer" — the
       two most frequent nights of the month — now set complete in WebKit; in
       Chromium "Life Group" already did and "Youth Prayer" still loses its
       last letter by under a pixel. The left rule stays 3px: it is the
       recurring-vs-one-off signal, and side padding buys nothing a whole word
       needs. */
    .event-pill { padding: 4px 3px 5px; }
    .event-pill .pill-text {
      /* PLAIN BLOCK + text-overflow, not -webkit-box (2026-09-04, QA P2-1:
         "Choir Practi", "Youth Servic", "Youth Praye" — cut mid-word with no
         ellipsis at 390px). The clip was the `overflow: hidden` below doing
         its job on a word too wide for the line; nothing ever drew a mark to
         say a word had been cut.
         `text-overflow: ellipsis` is the mark, but it is IGNORED inside a real
         -webkit-box: measured 2026-09-04 against the live page, Chromium (which
         now blockifies line-clamp — computed display came back `flow-root`)
         honoured it and WebKit, still a true -webkit-box, did not. Adding the
         one declaration would have fixed Android and left the iPhone audience
         with the exact defect. So the box goes, and `max-height` holds the same
         two lines the clamp held: 2.6em is 2 x the 1.3 line-height above.
         Verified identical in Chromium AND WebKit at 390px — every truncation
         now ends in an ellipsis and no word is torn.
         `line-clamp: 2` stays for the engines that support it unprefixed
         (Safari 18.2+, recent Chrome): where it lands it restores the vertical
         ellipsis on a title long enough for a third line. Where it does not,
         max-height cuts on the line boundary and the horizontal ellipsis on
         line two already says the title continues. */
      display: block;
      max-height: 2.6em;
      line-clamp: 2;
      overflow: hidden;
      text-overflow: ellipsis;
      /* NORMAL, not break-word (2026-08-24, the owner: titles were coming apart
         mid-word — "Wat/er.." for Water Baptism, "YOU/T.." for YOUTH LEADS
         SUNDAY, "Outd/oor" for the outdoor service). The 2026-08-12 reasoning
         above assumed a cell gives 38-48px of inside width; MEASURED at 390px
         it gives 28-34px, which is narrower than most seven-letter words — so
         break-word was not a last resort any more, it was the normal case, and
         a calendar of shredded words is what he saw.
         `normal` never splits a word. A word too wide for the box overflows its
         line and is clipped by the `overflow: hidden` above, so the reader gets
         the START of a real word instead of a fragment of two. hyphens:auto
         stays right below: where the engine has a dictionary (real iOS Safari,
         which is the audience) a long word still breaks — but at a proper
         hyphenation point, with a hyphen, which is typography rather than a
         tear. Nothing is only in the cell: the full title is still read in the
         day's popup, the month list under the grid and the Next-up block. */
      overflow-wrap: normal;
      -webkit-hyphens: auto;   /* Safari still wants the prefix, and this is an iPhone audience */
      hyphens: auto;
    }
  }
  @container (max-width: 62px) {
    .event-pill .pill-sub { display: none; }
  }
  /* Below ~55px of cell there is no honest way to set words. The pill becomes
     a MARKER — the same colour that already says what kind of night it is — and
     the words are read by tapping it, in the day's popup. Nothing is lost that
     was ever legible, and the button keeps its full aria-label, so a screen
     reader still hears the date, the title and the time.

     42, not 55 (Daniel, 2026-08-12 night, overruling wave C): the wave widened
     marker mode to catch phone cells (46-52px) because words wrapped badly
     there — but a calendar of wordless colour bars read as a BROKEN page to
     the person it was built for. Imperfect words beat no words; 42 keeps
     marker mode only where text is truly impossible. */
  @container (max-width: 42px) {
    .event-pill {
      /* 44, not the old 26: with no words left the pill IS the tap target, and
         a 37x26 bar is the hardest thing on the site to hit — this is the
         narrowest phone, where accuracy is worst (audit 2026-08-12, F3). The
         cell grows with it; the month still fits seven columns with no
         horizontal scroll. */
      min-height: 44px;
      padding: 0;
      border-radius: 6px;
      border-left-width: 4px;
    }
    .event-pill .pill-text { display: none; }
  }
  /* the 1400px canvas gives the cells room to be square-ish again */
  @media (min-width: 1100px) {
    .cal-cell { min-height: 108px; }
  }

  /* Three looks, no legend needed: special events carry the accent,
     the weekly rhythm stays quiet on bone, cancelled looks cancelled */
  .pill-youth_leads, .pill-special, .pill-church {
    /* --accent-pill, not --accent-gradient: see the token note */
    background: var(--accent-pill);
    color: var(--on-accent);
    border-left-color: rgba(10,10,11,.35);
    font-weight: 700;
    font-size: clamp(12px, 0.75rem + 0.12vi, 13.5px);
    padding: 5px 8px 6px;
    box-shadow: var(--shadow-accent-soft);
  }
  /* Subtitles on accent pills stay full-opacity — dimmed ink on gold
     drops below the small-text contrast floor */
  .pill-youth_leads .pill-sub, .pill-special .pill-sub, .pill-church .pill-sub { opacity: 1; }
  /* The weekly rhythm. Its left rule moved from a dark rgba(10,10,11,.25) to
     --line-strong on 2026-08-11: the two families used to differ by FILL
     alone, and bone on paper measures 1.06:1 — not a distinction anyone can
     see. Quieting this rule makes recurring vs one-off differ in fill, ink,
     weight, rule colour and rule contrast at once, which is what the standards
     ask for and what lets the gold read from across a room. */
  .pill-life_group, .pill-friday, .pill-prayer, .pill-general {
    background: var(--bone);
    color: var(--ink-soft);
    border-left-color: var(--line-strong);
  }
  /* A wall viewer scans for WHICH DAY, not which pill, so the day carries the
     signal too — the exact inset ring .cal-cell.today already ships, no new
     colour. Same specificity as .today and the same value, so a day that is
     both simply keeps one ring. */
  .cal-cell:has(.pill-special) {
    box-shadow: inset 0 0 0 1px rgba(201,154,46,.4);
  }
  .pill-cancelled {
    background: var(--bone-dim);
    color: var(--steel-mist);
    border-left-color: var(--line-strong);
  }
  .pill-cancelled .pill-text { text-decoration: line-through; }

  /* THE NARROW-CELL PADDING HAS TO OUTRANK THE FAMILY (2026-08-24).
     `@container (max-width: 88px)` above already asks every pill for
     `padding: 4px 5px 5px` in a phone-width cell, but .pill-youth_leads /
     .pill-special / .pill-church re-set `padding: 5px 8px 6px` at the SAME
     specificity further down the file, so source order handed the accent
     family back its wide padding — and only in the family that carries the
     biggest type. Measured at 390px: an accent pill's text box was 28.2px
     against 33.1px for a bone one, which is why every shredded title in the
     owner's screenshot was a gold pill. This block sits after the families for
     exactly the reason the marker-mode look below does, and gives the accent
     family the same 34.2px the design intended for every pill in a small cell.
     It changes nothing at any width where the cell is wider than 88px. */
  @container (max-width: 88px) {
    .pill-youth_leads, .pill-special, .pill-church { padding: 4px 3px 5px; }
  }

  /* MARKER MODE, THE LOOK (audit 2026-08-12, wave C: "every event pill renders
     EMPTY — coloured rectangles with no text at all").

     The marker was right; its shape was not. A wordless 44px block of colour
     filling the cell reads as a page that failed to load, not as a mark. So the
     44px stays as the TAP TARGET and the colour shrinks to a 12px bar inside
     it: `background-clip: content-box` with 16px of vertical padding paints the
     family's own fill — gradient included — only through the middle band, and
     the pill keeps every other property it already had.

     This block sits AFTER the family rules on purpose. .pill-special and its
     siblings set background, padding and box-shadow at the same specificity a
     `.event-pill` rule has, so inside a container query only source order can
     win; putting it above would have left the accent family unchanged. */
  @container (max-width: 42px) {
    .event-pill {
      padding: 16px 0;            /* 16 + 12 + 16 = the 44px target, unchanged */
      background-clip: content-box;
      border-left-width: 0;       /* a left rule on a 12px bar is just noise */
      /* Two-axis radius, and it has to be: background-clip shrinks each radius
         by the padding on that side, so 6px/22px lands as a flat 6px on both
         axes of the painted bar. A plain 22px would come out 22 wide by 6 tall
         and the bar would read as a lens. */
      border-radius: 6px / 22px;
      box-shadow: none;
    }
    /* The quiet families are bone on paper — 1.06:1, which is not a mark anyone
       can see once it is 12px tall. They borrow the rule colour they already
       carry on their left edge, so recurring still reads quieter than gold.
       background-COLOR, never the `background` shorthand: the shorthand resets
       background-clip to border-box and the bar goes back to being the 44px
       block this whole rule exists to remove. */
    .pill-life_group, .pill-friday, .pill-prayer, .pill-general {
      background-color: var(--line-strong);
    }
    .pill-cancelled { background-color: var(--steel-mist); opacity: .5; }
  }

  /* Edit mode banner — AND the home of every edit-mode-only action.
     THE FLOATING STAR IS GONE (2026-08-11, Daniel: "when I click edit the star
     floats on the right side"). The AI assistant used to be a gold 48px circle
     pinned `position: fixed` to the bottom-right of the viewport, so in edit
     mode it sat ON TOP of the calendar it edits — measured at 390px it covered
     the Aug 14/15 cells and their event pills — and it carried a bare
     five-pointed star with no label, which reads as "favourite", not "AI".
     It is the same control, moved, exactly the way the floating edit pencil was
     retired on 2026-08-10: same openAiModal(), same modal. Its home is this
     banner, the one surface that exists only while editing, so the control
     appears and disappears with the mode it belongs to and never covers a day.
     The banner is sticky, so it stays reachable while the month scrolls. */
  .edit-banner {
    display: none;
    background: var(--amber-wash);
    color: var(--ink);
    text-align: center;
    padding: 8px 16px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .18em;
    text-transform: uppercase;
    border-bottom: 1px solid var(--amber);
    position: sticky;
    top: 0;
    z-index: 900;
  }
  .edit-banner.visible {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 8px 14px;
  }
  .edit-banner-text { min-width: 0; }
  /* the action reads as a control inside the strip, not as banner text: the
     paper pill on the amber wash is the same figure/ground the toolbar's
     .export-btn uses on bone. */
  .edit-banner-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: 32px;
    padding: 0 14px;
    border-radius: 9999px;
    border: 1px solid var(--amber);
    background: var(--paper);
    color: var(--ink);
    font: inherit;
    letter-spacing: .16em;
    cursor: pointer;
    position: relative;
    transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease);
  }
  /* 32px pill in an 11px strip — slop the touch target to 50px tall, clear of
     the 44px floor (nothing else in the strip is interactive, so the overlap
     into the copy above costs nothing) */
  .edit-banner-btn::before { content: ''; position: absolute; inset: -9px -6px; border-radius: 9999px; }
  .edit-banner-btn svg { flex: none; }
  @media (hover: hover) {
    .edit-banner-btn:hover {
      background: var(--accent-gradient);
      color: var(--on-accent);
      border-color: transparent;
      box-shadow: var(--shadow-accent-soft);
    }
  }
  .edit-banner-btn:active { transform: scale(.98); }
  .edit-banner-btn:focus-visible { outline: 2px solid var(--gold-ring); outline-offset: 3px; }

  /* Edit button */
  /* THE EDIT DOOR IS THE ACCOUNT'S, AND ONLY THE ACCOUNT'S (2026-08-13,
     Daniel's second telling: "It's only supposed to be showing on Arnold
     account or my account. That's it. I've told you this already.").

     html.leader is set in exactly one place now — after the server has
     answered /api/youth-calendar/session-status with canEditCalendar for THIS
     session, which is true for two accounts and no others. The page can no
     longer talk itself into leader chrome, and that is the whole of the fix:
     until today the class was also set by ?edit in the URL (any visitor could
     type it) and by a 30-day localStorage flag left behind by a past edit, so
     an editor's own phone kept the Edit button after signing out — which is
     what Daniel was looking at, twice.

     The Edit button, the two projection export formats and the
     share-the-poster button are tools for the few people who run the calendar;
     .leader is the only switch for all of them. The footer's quiet '✎ Edit
     calendar' line was removed 2026-08-12 (0447416d2), so the toolbar button
     is the one and only way in. */
  .leader-only { display: none; }
  html.leader .leader-only { display: flex; }
  /* [hidden] must still win — Share stays hidden where navigator.share can't take files */
  html.leader .leader-only[hidden] { display: none; }

  /* The answer, in flow under the toolbar — never a floating card over the
     calendar (standing rule, 2026-08-11). */
  .ro-note {
    margin: 10px auto 0;
    max-width: 34ch;
    text-align: center;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-muted);
  }
  .ro-note[hidden] { display: none; }

  /* Modal overlay */
  .modal-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,.55);
    z-index: 2000;
    align-items: center;
    justify-content: center;
    padding: 16px;
  }
  .modal-overlay.open { display: flex; }
  .modal-card {
    background: var(--paper);
    border-radius: 16px;
    padding: 32px;
    width: 100%;
    max-width: 340px;
    border: 1px solid var(--line);
    box-shadow: 0 20px 60px rgba(0,0,0,.3);
    animation: modalIn .3s cubic-bezier(.22,1,.36,1) both;
  }
  @keyframes modalIn {
    from { opacity: 0; transform: translateY(24px) scale(.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
  }
  .modal-title {
    font-family: 'Inter Tight', sans-serif;
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--ink);
    margin-bottom: 4px;
  }
  .modal-sub {
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--steel);
    margin-bottom: 20px;
  }
  .modal-inp {
    width: 100%;
    padding: 12px 16px;
    border: 1px solid var(--line);
    border-radius: 8px;
    font-family: 'Inter', sans-serif;
    font-size: 15px;
    color: var(--ink);
    background: var(--paper);
    outline: none;
    transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
    margin-bottom: 12px;
    box-sizing: border-box;
  }
  .modal-inp:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(201,154,46,.18); }
  .modal-inp.shake {
    animation: shake .35s var(--ease);
    border-color: var(--danger);
  }
  @keyframes shake {
    0%,100% { transform: translateX(0); }
    20% { transform: translateX(-6px); }
    40% { transform: translateX(6px); }
    60% { transform: translateX(-4px); }
    80% { transform: translateX(4px); }
  }
  .modal-err {
    color: var(--danger);
    font-size: 11px;
    margin-top: -8px;
    margin-bottom: 10px;
    min-height: 16px;
  }
  .modal-btn {
    width: 100%;
    padding: 12px;
    background: var(--accent-gradient);
    color: var(--on-accent);
    border: none;
    border-radius: 9999px;
    font-family: 'Inter', sans-serif;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: transform .3s var(--ease), box-shadow .3s var(--ease);
    box-shadow: var(--shadow-accent-soft);
    margin-bottom: 10px;
  }
  @media (hover: hover) { .modal-btn:hover { transform: scale(1.02); box-shadow: var(--shadow-accent-glow); } }
  .modal-btn:active { transform: scale(.98); }
  .modal-btn-del {
    width: 100%;
    padding: 11px;
    background: transparent;
    color: var(--danger);
    border: 1px solid var(--danger);
    border-radius: 9999px;
    font-family: 'Inter', sans-serif;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: background .15s var(--ease), transform .3s var(--ease);
    margin-bottom: 10px;
  }
  @media (hover: hover) { .modal-btn-del:hover { background: var(--danger-wash); } }
  .modal-btn-del:active { transform: scale(.98); }
  .modal-cancel {
    display: block;
    text-align: center;
    color: var(--steel);
    font-size: 13px;
    font-family: 'Inter', sans-serif;
    cursor: pointer;
    text-decoration: underline;
    background: none;
    border: none;
    width: 100%;
    transition: color .15s var(--ease);
  }
  @media (hover: hover) { .modal-cancel:hover { color: var(--accent-deep); } }
  .modal-select {
    width: 100%;
    padding: 12px 16px;
    border: 1px solid var(--line);
    border-radius: 8px;
    font-family: 'Inter', sans-serif;
    font-size: 15px;
    color: var(--ink);
    background: var(--paper);
    outline: none;
    margin-bottom: 12px;
    box-sizing: border-box;
    cursor: pointer;
    transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
  }
  .modal-select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(201,154,46,.18); }

  /* Edit mode cell hover */
  .cal-cell.edit-cell { cursor: pointer; }
  @media (hover: hover) { .cal-cell.edit-cell:not(.empty):hover { background: rgba(201,154,46,.05); } }
  .cal-cell.edit-cell .add-hint {
    display: none;
    color: var(--accent-deep);
    font-size: 18px;
    font-weight: 300;
    text-align: center;
    line-height: 1;
    margin-top: 4px;
    opacity: 0.4;
  }
  @media (any-hover: hover) { .cal-cell.edit-cell:not(.empty):hover .add-hint { display: block; } }

  /* Event pill edit indicator */
  .event-pill.editable { cursor: pointer; }
  .event-pill.tappable { cursor: pointer; }
  /* Clickable pills: hit-slop out to the middle of the 3px stack gap */
  .event-pill.editable::before, .event-pill.tappable::before {
    content: '';
    position: absolute;
    inset: -2px 0;
  }
  @media (any-hover: hover) {
    .event-pill.editable:hover::after {
      content: ' ✎';
      font-size: 8px;
      opacity: 0.7;
    }
  }

  /* (.ai-fab — the fixed bottom-right AI circle — was retired 2026-08-11; its
     control now lives in .edit-banner above. See the comment there.) */

  /* AI Modal card (wider than standard modal) */
  .ai-modal-card {
    background: var(--paper);
    border-radius: 16px;
    padding: 32px;
    width: 100%;
    max-width: 500px;
    border: 1px solid var(--line);
    box-shadow: 0 20px 60px rgba(0,0,0,.3);
    animation: modalIn .3s cubic-bezier(.22,1,.36,1) both;
    max-height: 90svh;
    overflow-y: auto;
  }
  .ai-textarea {
    width: 100%;
    min-height: 200px;
    padding: 12px 16px;
    border: 1px solid var(--line);
    border-radius: 8px;
    font-family: 'Inter', sans-serif;
    font-size: 14px;
    color: var(--ink);
    background: var(--paper);
    outline: none;
    transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
    margin-bottom: 12px;
    box-sizing: border-box;
    resize: vertical;
    line-height: 1.5;
  }
  .ai-textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(201,154,46,.18); }
  .ai-parse-btn {
    width: 100%;
    padding: 12px;
    background: var(--accent-gradient);
    color: var(--on-accent);
    border: none;
    border-radius: 9999px;
    font-family: 'Inter', sans-serif;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: transform .3s var(--ease), box-shadow .3s var(--ease);
    box-shadow: var(--shadow-accent-soft);
    margin-bottom: 10px;
  }
  @media (hover: hover) { .ai-parse-btn:hover { transform: scale(1.02); box-shadow: var(--shadow-accent-glow); } }
  .ai-parse-btn:active { transform: scale(.98); }
  .ai-parse-btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
  @keyframes ai-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }
  .ai-parse-btn.loading { animation: ai-pulse 1s ease infinite; }

  .nav-actions {
    justify-self: end;
    display: flex;
    align-items: center;
    gap: 8px;
    /* belt to the track's max-content floor above: even if this row is ever
       re-parented or the template columns are edited again, the toolbar refuses
       to be squeezed narrower than its own buttons, so it can never be pushed
       back on top of the month arrow (2026-08-17). */
    min-width: max-content;
  }
  /* Share is secondary — a round icon that pairs with the arrow buttons */
  #shareBtn { width: 40px; padding: 0; border-radius: 50%; }
  #shareBtn .export-label { display: none; }
  /* Mid widths: not enough room for labeled pills — icons only */
  @media (max-width: 900px) and (min-width: 681px) {
    .export-btn { width: 40px; padding: 0; border-radius: 50%; }
    .export-btn .export-label, .export-btn .export-caret { display: none; }
  }
  /* An EDITOR carries three of these pills, a visitor carries one. Below ~1000px
     three LABELLED pills plus the two arrows and a 48px month title no longer
     fit the row at all — and now that the toolbar refuses to be squeezed (see
     .month-nav), "does not fit" would mean the whole row pushing past the page
     instead of quietly sitting on the arrow. So the editor's toolbar drops its
     labels there while the visitor keeps "Add to calendar" — the one button the
     page is actually for — at every desktop width. (2026-08-17) */
  @media (max-width: 1000px) and (min-width: 681px) {
    html.leader .export-btn { width: 40px; padding: 0; border-radius: 50%; }
    html.leader .export-btn .export-label, html.leader .export-btn .export-caret { display: none; }
  }
  .export-btn {
    width: auto;
    padding: 0 14px;
    gap: 7px;
    border-radius: 9999px;
    border-color: var(--line);
    transition: border-color .15s var(--ease), color .15s var(--ease), background .3s var(--ease), transform .3s var(--ease), box-shadow .3s var(--ease);
  }
  .export-btn .export-label {
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: .16em;
    text-transform: uppercase;
    white-space: nowrap;
  }
  @media (hover: hover) {
    .export-btn:hover {
      background: var(--accent-gradient);
      color: var(--on-accent);
      border-color: transparent;
      box-shadow: var(--shadow-accent-soft);
    }
  }
  /* Round mobile export buttons are 32px — slop the touch target to 44 */
  .export-btn { position: relative; }
  .export-btn::before { content: ''; position: absolute; inset: -6px; border-radius: 9999px; }
  .export-btn.exporting {
    pointer-events: none;
    opacity: 0.4;
    animation: ai-pulse 1s ease infinite;
  }
  .export-caret { opacity: .55; transition: transform .15s var(--ease); }
  .export-menu-wrap { position: relative; }
  .export-menu-wrap.open .export-caret { transform: rotate(180deg); }
  .export-menu {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 236px;
    background: var(--paper, #fff);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: 0 10px 32px rgba(10,10,11,.14);
    padding: 6px;
    z-index: 60;
    opacity: 0;
    transform: translateY(-4px) scale(.98);
    transform-origin: top right;
    pointer-events: none;
    /* A CLOSED MENU IS HIDDEN, NOT JUST TRANSPARENT (2026-09-02). `opacity:0`
       alone leaves both items in the accessibility tree, in the tab order, and
       "visible" to every hit-test — the QA crawler duly clicked them, the click
       fell THROUGH `pointer-events:none` onto whatever day cell was underneath,
       and six "export item covered by div.dow-cell" failures were filed against
       a menu that was simply shut. `visibility` transitions at 0s AFTER the
       fade so the close still animates; the open rule below flips it back with
       no delay so the menu is interactive on the first frame. */
    visibility: hidden;
    transition: opacity .15s var(--ease), transform .15s var(--ease), visibility 0s linear .15s;
  }
  .export-menu-wrap.open .export-menu {
    opacity: 1;
    transform: none;
    pointer-events: auto;
    visibility: visible;
    transition: opacity .15s var(--ease), transform .15s var(--ease), visibility 0s linear 0s;
  }
  .export-menu-item {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: 48px;
    padding: 9px 12px;
    border: none;
    background: none;
    border-radius: 9px;
    cursor: pointer;
    text-align: left;
    color: inherit;
    font: inherit;
    transition: background .15s var(--ease);
  }
  @media (hover: hover) { .export-menu-item:hover { background: var(--bone); } }
  .export-menu-item:active { transform: translateY(1px); }
  .export-menu-item svg { flex-shrink: 0; color: var(--accent-deep, #B8862F); }
  .export-menu-title { display: block; font-size: 13px; font-weight: 600; line-height: 1.25; }
  .export-menu-desc { display: block; font-size: 11px; color: var(--steel, #8a857c); margin-top: 1px; }
  .ai-preview-list {
    max-height: 300px;
    overflow-y: auto;
    margin-bottom: 12px;
    border: 1px solid var(--line);
    border-radius: 8px;
  }
  .ai-event-card {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--line);
    transition: background .15s var(--ease);
  }
  .ai-event-card:last-child { border-bottom: none; }
  @media (hover: hover) { .ai-event-card:hover { background: var(--bone-soft); } }
  .ai-event-card.delete-card {
    border-left: 3px solid var(--danger);
    background: var(--danger-wash);
  }
  .ai-event-card.delete-card .ai-event-title {
    text-decoration: line-through;
    color: var(--danger-ink);
  }
  .ai-event-card .delete-badge {
    display: inline-block;
    background: var(--danger);
    color: #fff;
    font-size: 9px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 10px;
    margin-left: 6px;
  }
  .ai-event-card.update-card {
    border-left: 3px solid var(--amber);
    background: var(--amber-wash);
  }
  .ai-event-card .update-badge {
    display: inline-block;
    background: var(--amber);
    color: #fff;
    font-size: 9px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 10px;
    margin-left: 6px;
  }
  .ai-event-card .update-changes {
    font-size: 10px;
    color: var(--amber-ink);
    margin-top: 4px;
    padding: 4px 8px;
    background: rgba(245,158,11,.14);
    border-radius: 4px;
  }
  .ai-event-card .update-changes .change-label {
    font-weight: 600;
    color: var(--amber-ink);
  }
  .ai-event-card .update-changes .old-val {
    text-decoration: line-through;
    color: var(--amber-ink);
    margin-right: 4px;
  }
  .ai-event-card .update-changes .new-val {
    color: var(--ok-ink);
    font-weight: 600;
  }
  .ai-event-card .match-list {
    font-size: 10px;
    color: var(--steel);
    margin-top: 4px;
    max-height: 120px;
    overflow-y: auto;
  }
  .ai-event-card .match-list div {
    padding: 2px 0;
    border-bottom: 1px solid var(--line);
  }
  .ai-checkbox {
    margin-top: 3px;
    width: 16px;
    height: 16px;
    cursor: pointer;
    flex-shrink: 0;
    accent-color: var(--accent);
  }
  .ai-event-info { flex: 1; min-width: 0; }
  .ai-event-date {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    font-weight: 500;
    color: var(--steel);
    letter-spacing: .12em;
    margin-bottom: 2px;
    text-transform: uppercase;
  }
  .ai-event-title { font-size: 13px; font-weight: 600; color: var(--text-dark); margin-bottom: 2px; }
  .ai-event-subtitle { font-size: 11px; color: var(--text-muted); }
  .ai-type-badge {
    font-family: 'JetBrains Mono', monospace;
    font-size: 9px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 9999px;
    text-transform: uppercase;
    letter-spacing: .1em;
    flex-shrink: 0;
    margin-top: 2px;
  }
  /* Detail-modal badge (public view): neutral mono voice; only the featured
     tier carries the accent — the pastel families stay edit-UI-only */
  .detail-badge { background: var(--bone); color: var(--steel-deep); border: 1px solid var(--line); }
  .detail-badge.featured { background: var(--accent-gradient); color: var(--on-accent); border-color: transparent; }
  /* Detail-modal fact rows (2026-09-04): the hour and the address the popup
     used to leave out. Hairlines carry the structure, the mono voice carries
     the label, the value dominates it — and a row with nothing to say is
     absent, never an empty label. The [hidden] rule is not optional: the grid
     display would otherwise beat the attribute. */
  .detail-rows { margin-bottom: 16px; }
  .detail-row {
    display: grid;
    grid-template-columns: 62px 1fr;
    gap: 12px;
    align-items: center;
    min-height: 44px;
    border-top: 1px solid var(--line);
  }
  .detail-row[hidden] { display: none; }
  .detail-rows:not(:has(.detail-row:not([hidden]))) { margin-bottom: 0; }
  .detail-row-label {
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--steel);
  }
  .detail-row-value {
    font-family: 'Inter', sans-serif;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink);
  }
  /* The address is a link, so its OWN box is the 44px target — measured on the
     element, not on a pseudo painted over it. */
  .detail-row-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: var(--accent-deep);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: border-color .15s var(--ease), opacity .15s var(--ease);
  }
  @media (hover: hover) { .detail-row-link:hover { border-bottom-color: var(--accent-deep); } }
  .detail-row-link:active { opacity: .7; }
  .detail-row-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
  .ai-badge-youth_leads { background: var(--accent-gradient); color: var(--on-accent); }
  .ai-badge-life_group  { background: #e8f0e4; color: #2d4a22; }
  .ai-badge-friday      { background: #f7efe4; color: #7a4a20; }
  .ai-badge-special     { background: #ede6f4; color: #3c2d54; }
  .ai-badge-church      { background: #f8f0d8; color: #5c4a14; }
  .ai-badge-cancelled   { background: #f0e4e4; color: #7a3030; }
  .ai-badge-prayer      { background: #e2eded; color: #1e4d4d; }
  .ai-badge-general     { background: #f0f0f0; color: #444; }
  .ai-progress { text-align: center; font-size: 12px; color: var(--text-muted); padding: 8px 0; font-weight: 500; }
  .ai-import-btn {
    width: 100%;
    padding: 12px;
    background: var(--accent-gradient);
    color: var(--on-accent);
    border: none;
    border-radius: 9999px;
    font-family: 'Inter', sans-serif;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: transform .3s var(--ease), box-shadow .3s var(--ease);
    box-shadow: var(--shadow-accent-soft);
    margin-bottom: 10px;
  }
  @media (hover: hover) { .ai-import-btn:hover { transform: scale(1.02); box-shadow: var(--shadow-accent-glow); } }
  .ai-import-btn:active { transform: scale(.98); }
  .ai-import-btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
  .ai-err { color: var(--danger); font-size: 11px; margin-bottom: 10px; min-height: 16px; }
  .history-list { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; margin: 14px 0; }
  .history-row { display: flex; align-items: center; gap: 12px; padding: 11px 12px; border-bottom: 1px solid var(--line); }
  .history-row:last-child { border-bottom: 0; }
  .history-copy { flex: 1; min-width: 0; }
  .history-label { font-size: 13px; font-weight: 600; color: var(--ink); }
  .history-meta { margin-top: 2px; font: 500 10px/1.4 'JetBrains Mono', monospace; color: var(--text-muted); text-transform: uppercase; letter-spacing: .08em; }
  .history-revert { border: 1px solid var(--line); background: var(--paper); color: var(--ink); border-radius: 9999px; min-height: 36px; padding: 0 12px; font: 600 11px 'Inter', sans-serif; cursor: pointer; }
  .history-revert:disabled, .edit-banner-btn:disabled { opacity: .45; cursor: not-allowed; }
  .history-empty { padding: 18px 12px; color: var(--text-muted); font-size: 13px; text-align: center; }
  @media (max-width: 680px) {
    .ai-modal-card { padding: 24px 16px; }
    .ai-preview-list { max-height: 200px; }
  }
  /* (The two bottom-offset rules that used to sit here lifted the fixed AI
     circle clear of Safari's bottom toolbar band — the 2026-08-09 "calendar
     not letting us update" report. Nothing is pinned to the bottom of the
     viewport any more: the AI button moved into the sticky edit strip at the
     TOP of the page on 2026-08-11, which is out of that band by construction.) */

  /* Past day dimming */
  .cal-cell.past {
    background: var(--bone-dim);
  }
  .cal-cell.past .day-num {
    opacity: 0.35;
  }
  /* A past event still has to be READABLE. `opacity: .62` composited both the
     pill's text and its background toward the cell behind it and dropped the
     quiet pills to 3.83:1 — under AA (measured 2026-08-10). grayscale() maps
     each colour to its own luminance instead, so the pill visibly goes quiet
     while the text/background contrast ratio is preserved exactly (11.3:1 on
     the quiet pills, 4.9:1 on gold, 4.6:1 on cancelled). */
  .cal-cell.past .event-pill {
    filter: grayscale(.5);
    box-shadow: none;
  }

  /* Current week highlight */
  .cal-cell.current-week:not(.empty):not(.today) {
    background: rgba(201,154,46,.03);
  }

  /* Calendar + Sidebar layout */
  .cal-layout {
    display: flex;
    gap: 20px;
    align-items: flex-start;
  }
  .cal-layout .cal-card {
    flex: 1;
    min-width: 0;
  }

  /* Upcoming panel */
  .upcoming-panel {
    /* grows a little with the 1400px canvas so the readable type has room */
    width: clamp(220px, 18vw, 288px);
    flex-shrink: 0;
    background: var(--paper);
    border-radius: 16px;
    border: 1px solid var(--line);
    overflow: hidden;
    animation: fadeInUp .35s var(--ease) both;
    animation-delay: .1s;
    position: sticky;
    top: 20px;
  }
  .upcoming-header {
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(11px, 0.6875rem + 0.06vi, 11.5px);
    font-weight: 500;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: rgba(255,255,255,.7);
    background: var(--ink);
    padding: 12px 16px;
  }
  .upcoming-list {
    padding: 8px 0;
    max-height: 500px;
    overflow-y: auto;
  }
  .upcoming-item {
    padding: 10px 16px;
    border-bottom: 1px solid var(--line);
    transition: background .15s var(--ease);
    cursor: pointer;
  }
  /* Real <button>s since 2026-08-12 (audit F2): the live list was a click-only
     <div> — cursor:pointer and a hover background advertised a control that Tab
     never reached and a screen reader never announced. Only the LIVE list is a
     button; the export clone stays a div (it is a picture of the month, not a
     control), so the reset is scoped to the tag. The LOOK is unchanged. */
  button.upcoming-item {
    display: block;
    width: 100%;
    text-align: left;
    font: inherit;
    color: inherit;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--line);
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
  }
  button.upcoming-item:focus-visible {
    outline: 2px solid var(--gold-ring);
    outline-offset: -2px;
  }
  .upcoming-item:last-child { border-bottom: none; }
  @media (hover: hover) { .upcoming-item:hover { background: var(--bone); } }
  /* Nothing upcoming left in this month → the whole panel goes, heading and
     all. Hidden, not removed: the exports clone this element and refill it
     with the WHOLE month, and #export-wrap's own rule is !important. */
  .upcoming-panel.is-empty { display: none; }
  /* (.upcoming-item.past went on 2026-08-02 — a past highlight is no longer
     dimmed, it is simply not shown.) */
  /* display:block so these read the same whether their parent is the live
     <button> (where they are <span>s — a button may only hold phrasing
     content) or the export clone's <div> */
  .upcoming-date {
    display: block;
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(11px, 0.6875rem + 0.06vi, 12px);
    font-weight: 500;
    color: var(--accent-deep);
    letter-spacing: .1em;
    text-transform: uppercase;
    margin-bottom: 3px;
  }
  .upcoming-title {
    display: block;
    font-family: 'Inter', sans-serif;
    font-size: clamp(13.5px, 0.84375rem + 0.1vi, 15px);
    font-weight: 600;
    color: var(--text-dark);
    line-height: 1.3;
    text-wrap: balance;
  }
  .upcoming-sub {
    display: block;
    font-family: 'Inter', sans-serif;
    font-size: clamp(12px, 0.75rem + 0.08vi, 13px);
    color: var(--steel-deep);
    margin-top: 2px;
    line-height: 1.35;
  }
  .upcoming-empty {
    padding: 24px 16px;
    text-align: center;
    font-size: clamp(12.5px, 0.78125rem + 0.08vi, 13.5px);
    color: var(--steel-deep);
  }

  /* ── NEXT UP ────────────────────────────────────────────────────────────
     Added 2026-08-10. The page could not answer the one question everybody
     arrives with. The Highlights panel was not it: it lived BELOW the grid on
     a phone, it only ever listed three of the eight event types, and the
     weekly Friday gathering — the thing this ministry actually does — was one
     of the five it skipped. This block reads every type, looks past the end of
     the viewed month, and is the first thing on the screen on a phone.
     It is also the page's one bold surface: ink and gold against the bone
     canvas, everything else stays quiet. */
  .nextup {
    background: var(--ink);
    color: rgba(255,255,255,.82);
    border-radius: 16px;
    padding: 20px 22px;
    margin-bottom: 20px;
    /* ONE COLUMN since the two action controls came off the card
       (2026-08-10) — with nothing in the right-hand track the old two-column
       grid left the information floating in half the width it has. */
    display: grid;
    grid-template-columns: minmax(0,1fr);
    grid-template-areas: 'body' 'then';
    gap: 16px;
    align-items: center;
    animation: fadeInUp .35s var(--ease) both;
  }
  .nextup-body { grid-area: body; min-width: 0; }
  .nextup-eyebrow {
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(11px, 0.6875rem + 0.06vi, 11.5px);
    font-weight: 700;
    letter-spacing: .22em;
    text-transform: uppercase;
    color: var(--accent-glow);
    margin: 0 0 8px;
  }
  .nextup-when {
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(12px, 0.75rem + 0.1vi, 13.5px);
    font-weight: 500;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: #fff;
    margin: 0 0 4px;
  }
  .nextup-title {
    font-family: 'Inter Tight', sans-serif;
    font-size: clamp(24px, 1.5rem + 1.1vi, 38px);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.03;
    color: #fff;
    margin: 0;
    text-wrap: balance;
  }
  .nextup-line {
    font-size: clamp(14px, 0.875rem + 0.12vi, 15.5px);
    line-height: 1.5;
    color: rgba(255,255,255,.78);
    margin: 8px 0 0;
    text-wrap: pretty;
  }
  .nextup-where {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-height: 44px;
    margin-top: 4px;
    font-size: clamp(13.5px, 0.84375rem + 0.1vi, 15px);
    color: var(--accent-glow);
    text-decoration: none;
    border-bottom: 1px solid transparent;
  }
  .nextup-where svg { width: 15px; height: 15px; flex: none; }
  @media (hover: hover) { .nextup-where:hover { border-bottom-color: var(--accent-glow); } }
  .nextup-where:focus-visible { outline: 2px solid var(--accent-glow); outline-offset: 3px; }
  .nextup-then {
    grid-area: then;
    margin: 0;
    padding-top: 14px;
    border-top: 1px solid rgba(255,255,255,.14);
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(11px, 0.6875rem + 0.06vi, 12px);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: rgba(255,255,255,.55);
    line-height: 1.7;
  }
  .nextup-then:empty { display: none; }
  /* author `display` beats the UA's [hidden] rule — every element the render
     code hides needs this or it stays on screen with nothing in it */
  .nextup-where[hidden], .nextup-line[hidden] { display: none; }
  .nextup-then b { color: rgba(255,255,255,.85); font-weight: 500; }

  /* ── an empty month is a signpost, not a dead end ───────────────────────── */
  .month-empty {
    display: none;
    padding: 26px 20px 30px;
    text-align: center;
    border-top: 1px solid var(--line);
    background: var(--bone-soft);
  }
  .cal-card.is-empty .month-empty { display: block; }
  .month-empty-head {
    font-family: 'JetBrains Mono', monospace;
    font-size: clamp(11px, 0.6875rem + 0.06vi, 11.5px);
    font-weight: 500;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--steel-deep);
    margin: 0 0 8px;
  }
  .month-empty-line {
    font-family: 'Inter Tight', sans-serif;
    font-size: clamp(17px, 1.0625rem + 0.35vi, 21px);
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--ink);
    line-height: 1.25;
    margin: 0 0 16px;
    text-wrap: balance;
  }
  .month-empty-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 46px;
    padding: 0 22px;
    border: 1px solid var(--line-strong);
    border-radius: 9999px;
    background: var(--paper);
    color: var(--ink);
    font-family: 'Inter', sans-serif;
    font-size: 14.5px;
    font-weight: 600;
    cursor: pointer;
    touch-action: manipulation;
    transition: transform .3s var(--ease), border-color .15s var(--ease), box-shadow .3s var(--ease);
  }
  @media (hover: hover) { .month-empty-btn:hover { border-color: var(--accent); box-shadow: var(--shadow-accent-soft); transform: scale(1.02); } }
  .month-empty-btn:active { transform: scale(.98); }
  .month-empty-btn:focus-visible { outline: 2px solid var(--gold-ring); outline-offset: 3px; }
  .month-empty-btn:empty, .month-empty-btn[hidden] { display: none; }

  /* Print — the month title + meta line stay (they are the only place the
     month and year live now); the tappable chrome goes */
  @media print {
    .navbar, .nav-sheet,
    .month-nav .nav-btn, .month-nav .nav-actions { display: none; }
    .cal-card { box-shadow: none; border: 1px solid #ccc; }
    /* min-height:0 — paper has no fingers; the 44px touch floor would push a
       printed month onto a second page (same reasoning as #export-wrap) */
    .event-pill { print-color-adjust: exact; -webkit-print-color-adjust: exact; min-height: 0; }
    .upcoming-panel { display: none; }
    .cal-footer { display: none; }
    .nextup { display: none; }
  }

  /* Mobile */
  @media (max-width: 680px) {
    /* ORIENTATION FIRST (2026-08-02, second pass — this REVERSES the sticky
       bottom bar shipped that morning). With the month chrome pinned to the
       bottom of the viewport the first phone screen read: site header,
       Highlights (one event weeks away), day-of-week row — and NO month name
       anywhere. A visitor could not tell what month they were looking at until
       they scrolled to the very end. Thumb reach is worth less than knowing
       where you are, so ALL the month chrome is one block at the top, directly
       under the site header: arrows, the month, the meta line and the
       add-to-calendar button. The page is allowed to scroll again.
       Phone order: header → month block → grid → highlights → footer. */
    /* Phone order: header → NEXT UP → month block → grid → month list → footer.
       The 2026-08-02 lesson still holds — a visitor must be able to tell which
       month they are looking at without scrolling — so the month chrome is
       still at the top, directly under this block, not pinned to the bottom.
       .month-bar is display:contents, so its two children are flex items here
       and order works on them the same as on any sibling. */
    .cal-wrap { padding-top: 16px; display: flex; flex-direction: column; }
    /* WHICH CALENDAR COMES FIRST (2026-08-11). Next up was ordered above
       everything, so a phone read "Next up: Worship Group" before anything on
       the screen said which of the two calendars that belonged to — and the
       two rooms have completely different next events. The switcher leads, the
       answer follows, the month chrome still sits above the grid. */
    .room-switch { order: -2; }
    .nextup { order: -1; padding: 18px; margin-bottom: 16px; }
    .month-nav {
      /* centered like desktop (Daniel, 2026-08-09: month name centered).
         THE SIDE FLOORS ARE 0 NOW (2026-09-02, third pass). The 08-11 and
         08-17 fixes both left `minmax(24px, 1fr)` here and both failed at the
         narrow end, because the row's fixed cost — two 48px arrows, four
         gaps and 48px of mirror column — left the title less than it needs.
         Column 3 holds `white-space: nowrap` text, so its min-content IS its
         max-content: no track function can shrink it, and the centred item
         simply paints outside its track and onto the arrows. Measured 18.5px
         of overlap each side at 320, 4.3 at 375, 0.7 at 390 (WebKit,
         September 2026 — "September" is the widest month).
         So the only lever is to stop spending width the row does not have:
         the two mirror columns floor at 0 instead of 24px. They are still
         `1fr` and still equal, so the title is still centred and the arrows
         still sit symmetrically; they only stop reserving 48px. Above ~430px
         both tracks exceed 24px on their own, so nothing moves there — the
         title's box at 375 and 390 is unchanged to the pixel. Below 365 that
         is still not enough on its own, and the block at the end of this file
         finds the rest.
         NOT TRIED AGAIN, because it cannot work: `minmax(0, auto)` on column
         3 plus `overflow: hidden; text-overflow: ellipsis` on the title. It
         was written, measured and removed the same day — a nowrap item makes
         min-content equal max-content, so the track never shrinks, the label
         never exceeds its own box, and the ellipsis cannot fire at any width
         or in any month. Track sizes came out byte-identical with it and
         without it. If a real belt is ever wanted here, it has to be a track
         with a fixed or fr MAXIMUM, not `auto`, and the title has to be
         allowed to wrap or shorten — which is the orphaned "2026" Daniel
         rejected on 2026-08-12. The guard below is the belt instead.
         Guarded by /root/youth-assets/calendar-nav-overlap-check-2026-08-17.js,
         which carries a 320px case since 2026-09-02 (it had none below 390 —
         three fixes of this bug all passed it clean), and by the narrow-phone
         measurements in /root/reports/cal-narrow-0902/. */
      grid-template-columns: minmax(0, 1fr) auto auto auto minmax(0, 1fr);
      gap: 10px;
      margin-bottom: 0;
    }
    #prevBtn { grid-column: 2; }
    .month-label { grid-column: 3; }
    #nextBtn { grid-column: 4; }
    /* THE TOOLBAR GETS ITS OWN ROW ON A PHONE — FOR A VISITOR TOO (2026-08-17).
       The rule below has done this for editors since 2026-08-11 on the reasoning
       that "a visitor's toolbar holds one 44px button, which fits column 5".
       Measured, it does not: that column floored at 24px, so the single
       add-to-calendar circle hung 10px over the next-month arrow at 390px AND
       at 320px, in WebKit and Chromium alike — the phone half of the same
       overlap Daniel reported on desktop Safari. One rule for everyone now: row
       one is the arrows and the centered month, the action sits underneath with
       the whole width to itself.
       (The 24px floor itself is gone as of 2026-09-02 — see .month-nav above.
       That does not revive the premise: the column is now 6-13px on a phone,
       so a 44px button fits it even less well than it did.) */
    .nav-actions {
      grid-column: 1 / -1;
      grid-row: 2;
      justify-self: center;
      margin-top: 14px;
      /* AND IT WRAPS INSTEAD OF HANGING OFF BOTH EDGES (2026-09-02). The base
         rule's `min-width: max-content` is a belt against being squeezed back
         onto the month arrow — a >680px problem, because down here the toolbar
         already owns a full-width row of its own. At 320px that belt made the
         row 323.5px wide inside a 300px content box ("Add to calendar" 188.4 +
         8 gap + "Download" 127.1), so it overflowed 1.8px past BOTH gutters and
         the page scrolled sideways. Wrapping costs nothing at 375 and 390,
         where the pair still fits one line; at 320 the two pills stack, both
         centered, nothing clipped. It is also the headroom the Russian labels
         need, which are longer than the English ones on both buttons. */
      flex-wrap: wrap;
      justify-content: center;
      min-width: 0;
      max-width: 100%;
    }
    /* LEADER CHROME GETS ITS OWN ROW ON A PHONE (2026-08-11, found while
       moving the AI star out of the calendar). A visitor's toolbar holds one
       44px button, which fits column 5. An admin's holds three — 148px of
       buttons in a `minmax(24px, 1fr)` column, and a grid item wider than its
       track simply OVERFLOWS: measured at 390px the add-to-calendar, edit and
       download circles overlapped each other and covered the last two letters
       of "August". Row 1 keeps the arrows and the centered month; the leader
       buttons drop underneath, centered, where they have the whole width. */
    /* Only an editor's toolbar carries more than one button (2026-08-13): a
       signed-in account that cannot edit the calendar now gets the visitor's
       single add-to-calendar circle, which fits column 5 as it always did. */
    /* SUPERSEDED 2026-08-17 — the .nav-actions rule above now does this for
       every phone visitor, editor or not, because the "one button fits column
       5" premise measured false. Kept as a no-op declaration so an editor's
       toolbar still lands on row 2 even if the general rule is ever narrowed
       again; delete both or neither. */
    html.leader .nav-actions {
      grid-column: 1 / -1;
      grid-row: 2;
      justify-self: center;
      margin-top: 14px;
    }
    .nav-btn { width: 48px; height: 48px; }
    /* the desktop treatment scaled down — still the page's title, still an h1.
       clamp keeps the longest month on one line beside two 48px arrows and the
       action button, down to a 320px phone. */
    .month-label { font-size: clamp(26px, 1.625rem - 26px + 8vi, 34px); min-width: 0; }
    /* No font-size overrides here any more (2026-08-10). This block used to
       drop event text to 8-9px on exactly the device where it is hardest to
       read; the sizes now come from the clamps and the two container queries
       up at .event-pill, which shrink the PADDING and clamp the title to two
       lines instead of shrinking the letters. */
    /* every pixel of gutter is a pixel of legible event text: seven columns
       share whatever the wrapper does not take */
    .cal-wrap, .cal-footer { padding-left: 10px; padding-right: 10px; }
    .cal-cell { min-height: 72px; padding: 4px 2px; }
    .day-num { font-size: 11px; margin-left: 3px; }
    .cal-layout {
      flex-direction: column;
      /* align-items: flex-start is inherited from the desktop rule, and in a
         COLUMN it means shrink-to-fit: the calendar card was sized by the
         widest thing inside a cell instead of by the screen. That is the
         "grid collapses to 220px on an empty month" bug (2026-08-10) — with no
         events there is nothing to size to, so seven columns of nothing landed
         at ~31px each. Stretch is what a phone wants in every case. */
      align-items: stretch;
    }
    /* Highlights follow the grid on a phone (2026-08-02, second pass). They
       used to be pulled above it with order:-1, which put a card about one
       event weeks away between the header and the calendar. DOM order already
       says card-then-panel, so there is simply no order rule here now. */
    .upcoming-panel {
      width: 100%;
      position: static;
      max-height: none;
    }
    .upcoming-panel .upcoming-list {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
      overflow-x: visible;
      gap: 0;
      padding: 0;
      max-height: none;
    }
    .upcoming-panel .upcoming-item {
      width: auto;
      border-bottom: 1px solid var(--line);
      border-right: 1px solid var(--line);
      padding: 10px 14px;
    }
    .upcoming-panel .upcoming-item:last-child { border-right: none; }
    .export-btn {
      width: 44px;
      height: 44px;
      padding: 0;
      border-radius: 50%;
    }
    .export-btn .export-label, .export-btn .export-caret { display: none; }
    /* A VISITOR'S ONE BUTTON GETS ITS WORDS BACK (2026-08-17). It went
       icon-only because it was squeezed into a 24px grid column; now that it
       has a row of its own (see .nav-actions above) a bare calendar glyph
       floating under the month says nothing, so it reads "Add to calendar"
       again. An editor still gets three circles — three labelled pills would
       not fit a 390px row, and an editor knows their own toolbar. */
    html:not(.leader) .nav-actions .export-btn {
      width: auto;
      padding: 0 16px;
      border-radius: 9999px;
    }
    html:not(.leader) .nav-actions .export-btn .export-label { display: inline; }
    /* CENTERED UNDER THE MONTH IT DESCRIBES (2026-08-11). This was
       `text-align:left; padding-left:58px`, on the arithmetic "48px prev arrow
       + 10px gap = the month name's x". That arithmetic left out .month-nav's
       own mirror first column, which was never the 24px the arithmetic assumed
       — it MIRRORS the opposite column, and since 2026-09-02 it floors at 0 and
       measures 6-13px on a phone, so the old number is further wrong than ever.
       Measured at 390 the line landed at x=68 while "August" started at x=143.
       That is exactly why nothing here is offset by hand. The month label is
       centered here (Daniel, 2026-08-09), so centering the line under it is
       aligned by construction and cannot drift when a button changes size. */
    .event-count-badge {
      text-align: center;
      margin: 8px 0 18px;
    }
    /* the month block is back at the TOP of the page, so the download menu
       opens downward again like everywhere else — only its width is trimmed
       to stay inside a phone */
    /* `max-width` is the second half of the 2026-09-02 overflow fix: the wrap
       above keeps the BUTTON inside the gutters, this keeps the panel that
       hangs off it inside them too, whatever the longest label turns out to
       be. 20px is the 10px .cal-wrap gutter on each side. */
    .export-menu { right: -8px; min-width: 224px; max-width: calc(100vw - 20px); }
  }

  /* ────────────────────────────────────────────────────────────────────────
     THE NARROW PHONE (2026-09-02). Everything above is measured at 375 and
     390; this block is the last ~45px, where three separate rows run out of
     width at once. 364 is the cut, and it is the DAY CELLS that set it, not
     a device: a cell is (W − 22) / 7, so the pill inside it clears 44px from
     365px up and needs the padding rule below at 364 and under. (Written as
     360 first, on "a 360px Android is the widest phone that needs it" — 1px
     sweep 355→372 measured that false: at 361–364 the fix switched off while
     the pills were still 43.4–43.8. The number is measured now, not guessed.)
     Every rule here is a no-op from 365px up, so the 375 and 390 layouts are
     untouched. Numbers below are WebKit at 320×844, live page, Sept 2026.

     Note on the three HEADER rules: .burger, .wordmark and .nav-account are
     SITE-WIDE chrome from /assets/css/styles.css, and the squeeze they fix
     is on every youth page at this width, not just this one. They are copied
     here rather than fixed at the source because styles.css ships
     `cache-control: immutable` behind `?v=117` — changing it means bumping
     that query on all fourteen youth pages and their .br/.gz sidecars, which
     is its own change. Flagged for that sweep, and it is real work owed, not
     a note: until it happens this is the only youth page whose menu button
     meets the tap floor at 320. */
  @media (max-width: 364px) {
    /* THE MONTH TITLE. Row is 300px wide inside the 10px gutters. Two 48px
       arrows + four gaps is the fixed cost; at gap 10 that leaves 164px and
       "September 2026" at the clamp's 26px floor wants 173. Tightening the
       gap to 8 buys 8px and dropping the title to 24px costs it 13, so the
       title lands at ~160 in ~172 — about 12px of slack, and the mirror
       columns take ~6px each, which leaves the arrows 24px off the screen
       edge instead of on top of the month name.
       The size is a clamp, not a flat 24px, so the title still answers a
       reader who has raised their root font size — a flat px value is the
       WCAG F94 failure the house clamp rule exists to prevent, and this is
       the width where it would bite hardest. 34px is the same ceiling the
       wider phones already clamp to; 34/24 is 1.42, inside the 2.5× cap. */
    .month-nav { gap: 8px; }
    .month-label { font-size: clamp(24px, 1.5rem, 34px); }

    /* THE DAY CELLS. Seven columns share 298px inside the card (320 − two
       10px page gutters − the card's two 1px borders), so a cell is 42.56px
       and a 44px pill is arithmetically impossible here — the honest ceiling
       is 42.56, and only if a pill runs edge to edge into the column rule,
       which turns three events on consecutive days into one unreadable bar.
       Halving the cell's side padding is what is left: pills go 37.6 → 39.6
       wide at the full 44px height, adjacent days keep a 3px separation
       (2px of padding + the 1px rule), and the row does not move. That is
       90% of the Apple 44 and comfortably past the WCAG 2.5.8 AA target of
       24px, which is the line an audit at this width can actually hold. */
    .cal-cell { padding-left: 1px; padding-right: 1px; }
    /* the 1px the cell gave up is handed straight back to the day number:
       its left edge is the page's narrowest content gutter (11px of card +
       the cell padding + this margin) and mobile-qa's safe-area check holds
       it at 16px. Without this line the fix above lands it at 15 and trades
       one FAIL for another. */
    .day-num { margin-left: 4px; }

    /* THE HEADER ROW. .nav-inner is 280px inside its 20px gutters and holds
       the wordmark, the Sign in pill and the burger. Their natural widths
       total 312.7 with gaps, so flexbox took the 32.7px difference out of
       the two items that were allowed to give — and the burger, a circle
       with a fixed 44px in styles.css, was one of them: it rendered 37.5px
       wide, a sub-44 tap target on the site's main menu. The wordmark took
       the other 26px, which is why "Sulamita Youth" painted out of its own
       box and under the Sign in pill at this width.
       flex: none takes the burger out of the negotiation for good — a
       fixed-size round control is never the thing that gives — and the rest
       of this block finds the 32.7px elsewhere: 4px from each of the two row
       gaps, 12px off the Sign in pill's side padding, and the wordmark
       trimmed to a 32px seal and 15px type. Measured after: 44×44 burger,
       wordmark inside its box, ~10px of slack left in the row.
       (The pill's 10px side padding LIVES IN THE max-width:719 BLOCK BELOW
       as of the second pass — same value, every phone width, so it is
       declared once. 10px is the value the same pill already uses in the
       720–839 band.) */
    .nav-inner { --nav-row-gap: 8px; }
    .wordmark { font-size: 15px; gap: 6px; --seal-size: 32px; }
  }
  /* The burger never shrinks, at any width — see the header note above. */
  .burger { flex: none; }

  /* THE ACCOUNT PILL IS THE ITEM THAT GIVES — NOT THE WORDMARK (2026-09-02,
     second pass, the residual 8df8bc414 recorded rather than dropped).
     That pass took the burger out of the flex negotiation and shrank the
     wordmark, which was enough for the signed-out bar: at 320 "Sign in"
     leaves the wordmark's ink at 166.5 with the pill starting at 183.4.
     It was NOT enough for a signed-in one, because it never changed WHO
     gives. `.nav-account` is `flex: 0 0 auto` with `white-space: nowrap`,
     so it takes whatever its label needs; `.wordmark` is `flex: 0 1 auto`
     with the default `min-width: auto`, so it is the only item left that
     can shrink — and it does not shrink honestly. Its children are a
     `flex: none` seal and two anonymous text items, so squeezing its BOX
     does not shorten the words: the ink stays 166.5 wide and simply paints
     outside the box, on top of the pill. Measured at HEAD, WebKit, live
     page: 23.83px of "Sulamita Youth" under a "Your account" pill at 320,
     23.99 under a 12-character name, and the same squeeze reached 375
     (6.49 / 11.15 / 20.45 for the three labels) and even 390 with a
     Cyrillic name (5.45). A 12-char name also pushed the burger 4.5px past
     the row's right gutter at 320, and 13.8px with Cyrillic.
     So the roles swap. The wordmark is `flex: none` for the same reason the
     burger is — a brand mark is never the thing that gives — and the pill
     becomes the flexible item, with `min-width: 0` so it may actually go
     below its content and `overflow/text-overflow` so what it loses it
     loses as an ellipsis rather than as clipped letters. `display: block`
     is what makes that ellipsis possible at all: `text-overflow` needs a
     block container, and the pill's own `display: inline-flex` puts its
     label in an anonymous flex item that no selector can reach. The
     label's centring moves from `justify-content` to `text-align`, and
     `line-height: 1.7` (13px x 1.7 = 22.1, plus 10px padding and a 1px
     border each side) reproduces the 44px control to within 0.09px while
     still growing with the type if a reader raises it.
     The pill's 10px side padding moves up here from the max-width:364
     block: it is the same value in both places, and giving it to every
     phone width is what lets "Your account" (105.34) and a full 12-char
     Latin name (110) still render WHOLE at 390 once the wordmark stops
     yielding. The pill's own budget is what shrinks instead: 113.84 at
     390, 98.84 at 375, 73.5 at 320 — all of them still a 44px-tall target
     wider than 44px, and the label the person needs least, since a first
     name is already capped at 12 characters by leader-nav.js.
     Measured after, all four labels x 320/360/375/390: 0px of wordmark ink
     over the pill anywhere, 44x44 burger everywhere, 0 elements out of
     view, scrollWidth == innerWidth.
     Same site-wide-chrome caveat as the block above: this belongs in
     styles.css, which ships `cache-control: immutable` behind `?v=121`
     (verified live on 2026-09-02), so a source fix means bumping that
     query on all fourteen youth pages. Until that sweep this is the only
     youth page where a signed-in bar is right at 320. */
  @media (max-width: 719px) {
    .wordmark { flex: none; }
    .nav-account {
      flex: 0 1 auto;
      min-width: 0;
      display: block;
      line-height: 1.7;
      text-align: center;
      overflow: hidden;
      text-overflow: ellipsis;
      padding-left: 10px;
      padding-right: 10px;
    }
  }

  /* ===== EXPORT MODE STYLES ===== */
  /* Applied when .exporting class is on #export-wrap */
  /* html2canvas clones the stylesheet so these always work */

  #export-wrap { background: #fff; overflow: visible; padding: 0; }
  #export-wrap * { animation: none !important; transition: none !important; }

  /* Layout */
  /* Extra top padding drops the calendar lower under the ink header band;
     tight bottom padding + tall rows (below) keep the white space down */
  #export-wrap .cal-wrap { max-width: 1500px; padding: 46px 32px 22px 32px; background: #fff; overflow: visible; }
  /* Tall week rows so the grid fills the poster instead of ending high with
     a wide white band under it */
  #export-wrap .cal-cell { min-height: 112px !important; }
  #export-wrap .cal-layout { display: flex !important; flex-direction: row !important; gap: 20px !important; align-items: flex-start !important; }
  /* A real border, not just a shadow — soft shadows read as "missing edges"
     in a flat exported image */
  #export-wrap .cal-card { width: 1196px !important; max-width: 1196px !important; flex: 0 0 1196px !important; background: #fff !important; overflow: hidden !important; border-radius: 16px !important; border: 1px solid #c9c4bb !important; box-shadow: 0 4px 24px rgba(10,10,11,.08) !important; }

  /* Remove ALL dimming and temporal styling */
  #export-wrap .cal-cell { background: #fff !important; opacity: 1 !important; }
  #export-wrap .cal-cell.empty { background: #fff !important; opacity: 1 !important; }
  #export-wrap .cal-cell.sunday { background: #fff !important; }
  #export-wrap .cal-cell.past { background: #fff !important; opacity: 1 !important; }
  #export-wrap .cal-cell.today { background: #fff !important; box-shadow: none !important; }
  #export-wrap .cal-cell.current-week { background: #fff !important; }
  /* Alternating week rows — every other week gets a faint cream tint */
  #export-wrap .cal-grid .cal-cell:nth-child(n+8):nth-child(-n+14) { background: #f9f8f6 !important; }
  #export-wrap .cal-grid .cal-cell:nth-child(n+22):nth-child(-n+28) { background: #f9f8f6 !important; }
  #export-wrap .cal-grid .cal-cell:nth-child(n+36):nth-child(-n+42) { background: #f9f8f6 !important; }
  /* Stronger grid lines for print clarity */
  #export-wrap .cal-cell { border-color: #d9d4cc !important; }
  #export-wrap .day-num { opacity: 1 !important; color: #1a1a1a !important; font-family: 'JetBrains Mono', monospace !important; font-weight: 500 !important; font-size: 13px !important; margin-bottom: 6px !important; }
  #export-wrap .cal-cell.empty .day-num { opacity: 0.5 !important; color: #999 !important; }
  #export-wrap .cal-cell.past .day-num { opacity: 1 !important; color: #1a1a1a !important; }
  #export-wrap .cal-cell.today .day-num { background: none !important; color: #1a1a1a !important; box-shadow: none !important; width: auto !important; height: auto !important; display: block !important; border-radius: 0 !important; }

  /* ALL event pills - remove dimming, and bump text so dates read at a glance
     (cells used to be tall and mostly empty around 9.5px type) */
  /* min-height:0 — the 44px touch floor is for fingers; the export is a picture
     of the month and a 44px floor would balloon every cell in the PNG */
  #export-wrap .event-pill { opacity: 1 !important; filter: none !important; font-weight: 600 !important; font-size: 12px !important; line-height: 1.3 !important; padding: 5px 8px 6px !important; overflow: visible !important; min-height: 0 !important; }
  #export-wrap .event-pill .pill-sub { font-size: 10.5px !important; }
  #export-wrap .pill-youth_leads, #export-wrap .pill-special, #export-wrap .pill-church { font-size: 12.5px !important; }
  #export-wrap .cal-cell.past .event-pill { opacity: 1 !important; filter: none !important; }

  /* Sidebar - force desktop layout */
  #export-wrap .upcoming-panel { display: block !important; position: static !important; width: 220px !important; min-width: 200px !important; flex-shrink: 0 !important; opacity: 1 !important; overflow: hidden !important; background: #fff !important; border-radius: 16px; box-shadow: 0 4px 24px rgba(10,10,11,.10); border: 1px solid #c9c4bb; }
  #export-wrap .upcoming-header { background: #0d1526 !important; color: rgba(255,255,255,0.7) !important; padding: 12px 16px; font-size: 10px; font-weight: 500; letter-spacing: .18em; text-transform: uppercase; border-radius: 16px 16px 0 0; }
  #export-wrap .upcoming-list { display: block !important; overflow: visible !important; max-height: none !important; padding: 8px 0; }
  #export-wrap .upcoming-item { width: auto !important; flex: none !important; border-right: none !important; border-bottom: 1px solid #e8e5e0 !important; opacity: 1 !important; padding: 10px 16px; }
  #export-wrap .upcoming-date { color: #8a6220 !important; opacity: 1 !important; font-size: 10.5px; font-weight: 500; }
  #export-wrap .upcoming-title { color: #0d1526 !important; opacity: 1 !important; font-size: 13px; font-weight: 600; }
  #export-wrap .upcoming-sub { color: #6b7280 !important; opacity: 1 !important; font-size: 11px; }

  /* Months with no sidebar highlights: calendar takes the full width */
  #export-wrap.no-sidebar .cal-card { width: auto !important; max-width: none !important; flex: 1 1 auto !important; }

  /* Depth on day-of-week bar + brighter labels (55% white reads muddy in a
     flat exported image) */
  #export-wrap .dow-row { box-shadow: 0 2px 8px rgba(10,10,11,.12) !important; position: relative; z-index: 1; }
  #export-wrap .dow-cell { color: rgba(255,255,255,.88) !important; font-weight: 700 !important; }
  /* Hide chrome */
  #export-wrap .month-bar, #export-wrap .month-nav, #export-wrap .event-count-badge,
  #export-wrap #editBanner,
  #export-wrap .add-hint { display: none !important; }

  /* ── WIDE BANNER FORMAT ── exactly 1536×576 CSS (4608×1728 at 3x):
     ink rail on the left (brand, month, highlights, QR), calendar filling
     the rest, week rows stretched to use the full height. */
  #export-wrap.export-wide { width: 1536px; height: 576px; display: flex; overflow: hidden; }
  /* Bone canvas behind the white calendar card — Streamlined surface logic
     (ink rail | bone canvas | paper card) instead of white-on-white */
  #export-wrap.export-wide .cal-wrap { flex: 1 1 auto; min-width: 0; max-width: none; height: 576px; padding: 16px 20px; display: flex; background: #f5f2ed !important; }
  #export-wrap.export-wide .cal-layout { flex: 1 1 auto; min-width: 0; align-items: stretch !important; }
  #export-wrap.export-wide .cal-card { width: auto !important; max-width: none !important; flex: 1 1 auto !important; display: flex !important; flex-direction: column; border-radius: 12px !important; }
  #export-wrap.export-wide .cal-grid { flex: 1 1 auto; grid-auto-rows: 1fr; }
  #export-wrap.export-wide .cal-cell { min-height: 0 !important; overflow: hidden !important; padding: 5px 6px 4px !important; }
  #export-wrap.export-wide .day-num { font-size: 11px !important; margin-bottom: 3px !important; }
  #export-wrap.export-wide .event-pill { font-size: 10.5px !important; padding: 3px 6px 4px !important; margin-bottom: 2px !important; }
  #export-wrap.export-wide .event-pill .pill-sub { font-size: 9px !important; }
  #export-wrap.export-wide .pill-youth_leads, #export-wrap.export-wide .pill-special, #export-wrap.export-wide .pill-church { font-size: 10.5px !important; }
  #export-wrap.export-wide .dow-cell { padding: 8px 4px; }

  /* ── TV FORMAT ── the wide-banner layout re-proportioned to true 16:9
     (1536×864 CSS, 3840×2160 at 2.5x) so the /screen/ slideshow fills the
     church TV edge-to-edge. Gets .export-wide (layout above) + these
     overrides: 16:9 height and slightly bigger type for the taller cells. */
  #export-wrap.export-tv { height: 864px; }
  #export-wrap.export-tv .cal-wrap { height: 864px; padding: 20px 24px; }
  #export-wrap.export-tv .cal-cell { padding: 7px 8px 6px !important; }
  #export-wrap.export-tv .day-num { font-size: 13px !important; margin-bottom: 4px !important; }
  #export-wrap.export-tv .event-pill { font-size: 12.5px !important; padding: 5px 8px 6px !important; margin-bottom: 3px !important; }
  #export-wrap.export-tv .event-pill .pill-sub { font-size: 11px !important; }
  #export-wrap.export-tv .pill-youth_leads, #export-wrap.export-tv .pill-special, #export-wrap.export-tv .pill-church { font-size: 13px !important; }
  #export-wrap.export-tv .dow-cell { padding: 11px 4px; }
