/* ---------------------------------------------------------------------
   Ward Bulletin: shared bulletin rendering and print imposition.
   Used by index.html (public view) and edit.html (moderator editor),
   so the two can never drift apart. Editor chrome lives in edit.html.
   --------------------------------------------------------------------- */

/* ---------- Tokens ---------- */
:root {
  --paper: #ffffff;
  --ink: #1d2433;
  --muted: #5b6472;
  --rule: #c9ced6;
  --accent: #2b5f46;   /* digital only; print stays grayscale-safe.
                          Matches the stake site's Evergreen. */
  --desk: #eef0f3;
  /* A recessed surface, one step back from --paper: the inside of a form
     panel sitting on a card. It was used in three places before it was
     ever defined, which in dark mode fell back to a hardcoded light grey
     and put near-white labels on a near-white panel. A token that is not
     defined does not inherit the theme; it either falls back to whatever
     literal is written beside it, or makes the whole declaration invalid
     and does nothing at all. Both happened. */
  --sink: #f2f4f7;
  /* Shadows as tokens so the dark theme can soften them rather than
     painting black on near-black, where they read as dirt. */
  --lift: 0 1px 2px rgba(22, 30, 46, .05), 0 6px 20px rgba(22, 30, 46, .07);
  --lift-sm: 0 1px 2px rgba(22, 30, 46, .06);
  --warn: #b3261e;
  --ok: #2f6b4f;
  --serif: "Spectral", Georgia, "Times New Roman", serif;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
/* Dark is opt-in, never automatic. There is deliberately no
   `prefers-color-scheme` block: light is the default for everyone, and a
   reader who wants dark presses the button, which stores the choice. */
:root[data-theme="dark"] {
  --paper: #171b22; --ink: #e7eaf0; --muted: #a3abb8; --rule: #394150;
  /* Slightly LIGHTER than paper. In a dark interface a recessed panel
     reads by separating from its card, and going darker here would walk
     into --desk (#0f1217) and lose the edge entirely. */
  --sink: #1f2530;
  --accent: #7db894; --desk: #0f1217; --warn: #f2b8b5; --ok: #7fc0a0;
  --lift: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px rgba(0, 0, 0, .34);
  --lift-sm: 0 1px 2px rgba(0, 0, 0, .3);
}

/* ---------- Page ---------- */
*, *::before, *::after { box-sizing: border-box; }
:root { padding-top: env(safe-area-inset-top, 0px); padding-bottom: env(safe-area-inset-bottom, 0px); }
html { scroll-padding-top: env(safe-area-inset-top, 0px); }
body {
  margin: 0; background: var(--desk); color: var(--ink);
  font-family: var(--serif); font-size: 17px; line-height: 1.55;
}

/* These three render bulletin content, and say so themselves rather than
   depending on the host page. The editor sets `.ed { font-family: sans }`
   on the body for its own chrome, which used to cascade in here: the
   preview and the print sheets were laid out in system-ui while the
   published page and the paper came out in Spectral. Different wrapping,
   different line lengths -- and `checkFit` deciding whether a panel
   overflows 8.5 inches while measuring the wrong face. */
.digital, .stake-program, .sheets { font-family: var(--serif); }

/* ---------- Toolbar (chrome, never printed) ---------- */
.toolbar {
  position: sticky; top: env(safe-area-inset-top, 0px); z-index: 5;
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem;
  padding: .6rem 1rem; font-size: .95rem;
  /* Translucent over the page rather than a solid band across it. The
     flat colour is the fallback where backdrop-filter is unsupported;
     the transparent one only applies where it is. */
  background: var(--paper);
  border-bottom: 1px solid color-mix(in srgb, var(--rule) 60%, transparent);
}
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .toolbar {
    background: color-mix(in srgb, var(--paper) 78%, transparent);
    -webkit-backdrop-filter: saturate(1.6) blur(12px);
    backdrop-filter: saturate(1.6) blur(12px);
  }
}
.seg { display: inline-flex; border: 1px solid var(--rule); border-radius: 999px; overflow: hidden; }
.seg button, .btn {
  font: inherit; color: var(--ink); background: transparent; border: 0;
  padding: .35rem .9rem; cursor: pointer;
}
.seg button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
.btn { border: 1px solid var(--rule); border-radius: 999px;
       transition: border-color .12s ease, color .12s ease,
                   background-color .12s ease, box-shadow .12s ease; }
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn:active { transform: translateY(.5px); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
button:focus-visible, a:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.status { flex-basis: 100%; margin: 0; color: var(--muted); font-style: italic; font-size: .9rem; }
.status.bad { color: var(--warn); font-style: normal; }

/* ---------- Shared panel typography ---------- */
.panel h2 { font-weight: 500; font-size: 1.3em; margin: 0 0 .9em; line-height: 1.2; }
.panel h3 { font-weight: 400; font-style: italic; font-size: 1em; color: var(--muted); margin: 1.4em 0 .5em; }
.panel p { margin: 0 0 .6em; }

/* Cover */
.p-cover { display: flex; flex-direction: column; justify-content: space-between; text-align: center; }
.ward { font-size: 1.45em; font-weight: 500; margin: 0; }
.stake { margin: .15em 0 0; color: var(--muted); font-style: italic; }
.cover-date { display: flex; flex-direction: column; align-items: center; line-height: 1; margin: 1.5em 0; }
.cover-date .month { font-style: italic; font-weight: 300; font-size: 2.1em; }
.cover-date .day { font-weight: 300; font-size: 9em; letter-spacing: -.03em; line-height: .92; }
.cover-date .year { color: var(--muted); letter-spacing: .25em; margin-top: .5em; }
.meeting { margin: 0 0 1.6em; }
.quote { margin: 0 auto; max-width: 21em; font-style: italic; font-weight: 300; font-size: 1.05em; }
.quote cite { display: block; margin-top: .4em; font-style: normal; font-size: .9em; color: var(--muted); }

/* Program: dot leaders read as the traditional printed order of meeting */
/* No rule under the officers. The space does the separating; a line as
   well read as a box drawn round the four of them, and on paper it was
   the heaviest mark on a panel whose other horizontals are all dot
   leaders. The printed stake program sets its own border and is
   unaffected -- see `.sheet .p-stake .officers`. */
.officers { margin-bottom: 1.1em; }
.row { display: flex; align-items: baseline; gap: .4em; margin: 0 0 .42em; }
.row .lbl { white-space: nowrap; }
.row .dots { flex: 1; min-width: 1.2em; border-bottom: 1px dotted var(--muted); transform: translateY(-.28em); opacity: .7; }
.row .val { text-align: right; max-width: 66%; }
.row .val em { font-style: italic; }
.row .num { font-variant-numeric: oldstyle-nums; margin-right: .2em; }
.row .note { display: block; font-size: .85em; color: var(--muted); font-style: italic; }
/* A calling sits under the name in italics -- the same treatment as a
   note, because it is the same kind of secondary line. Absent entirely
   when nobody typed one. */
.row .calling { display: block; font-size: .85em; color: var(--muted); font-style: italic; }
.officers .calling { display: block; font-size: .88em; color: var(--muted); font-style: italic; }
.interlude { text-align: center; font-style: italic; color: var(--muted); margin: .9em 0; }

/* Announcements */
.ann { margin: 0 0 1em; }
.ann strong { font-weight: 600; display: block; }

/* Back panel */
/* One line per event. The weekday used to sit under the date and the
   place under the title, so five events cost fifteen lines of a panel
   that also carries the leaders, the notices and the QR code. The date
   column still aligns, which is what makes a column of dates scannable;
   everything else flows after the title and wraps only if it must. */
.cal { list-style: none; margin: 0; padding: 0; }
.cal li { display: grid; grid-template-columns: 5.8em 1fr; gap: .6em; margin: 0 0 .3em; }
.cal .d { font-variant-numeric: oldstyle-nums; white-space: nowrap; }
.cal .dow { color: var(--muted); font-size: .85em; font-style: italic; }
.cal .where { color: var(--muted); font-size: .9em; }
.cal .where::before { content: " \00b7  "; }
.leaders { list-style: none; margin: 0; padding: 0; }
.leaders li { display: flex; justify-content: space-between; gap: 1em; margin: 0 0 .3em; }
.leaders .who { text-align: right; }
.leaders .phone { display: block; color: var(--muted); font-size: .9em; }
.online { display: flex; align-items: center; gap: 1em; margin-top: 1.4em; padding-top: 1em; border-top: 1px solid var(--rule); }
.qr { width: .95in; height: .95in; flex: none; }
.qr img, .qr canvas { width: 100% !important; height: 100% !important; }
.online p { margin: 0; font-size: .9em; }
.online a { color: inherit; word-break: break-all; }

/* ---------- Digital view: the same panels, as cards ----------
   On paper these four are one folded sheet and are drawn as one slab.
   On a screen they are four separate things -- a cover, an order of
   meeting, announcements, a back page -- and reading one long bordered
   column of them is what made this feel like a photocopy.

   Everything here is scoped to `.digital`. The printed sheet is the
   program a ward hands out and is not touched by any of it. */
.digital {
  max-width: 38rem; margin: 1.6rem auto 4rem; padding: 0 1rem;
  background: none;
  display: grid; gap: 1rem;
}
.digital .panel {
  background: var(--paper);
  border: 0; border-radius: 16px;
  padding: 2.4rem 1.9rem;
  box-shadow: var(--lift);
}
.digital .panel:last-child { border-bottom: 0; }

/* A cover that grows to its contents should not also try to spread them.
   `space-between` is right inside a fixed 8.5in panel and wrong here,
   where it only pushes the artwork to the floor. */
.digital .p-cover { justify-content: flex-start; gap: 1.1rem; padding-block: 3rem 2.6rem; }
.digital .p-cover > * { margin: 0; }
.digital .cover-date .day { font-size: 7em; color: var(--accent); }

/* A rule under each heading, which on a long scroll is what says where
   one part of the bulletin ends and the next begins. */
.digital .panel h2 {
  font-size: 1.22em; letter-spacing: .005em;
  padding-bottom: .5rem; margin-bottom: 1.1rem;
  border-bottom: 2px solid color-mix(in srgb, var(--accent) 35%, transparent);
}

.digital .print-only, .digital .qr { display: none; }
.digital .online { border-top: 0; margin-top: 0; padding-top: 0; }

@media (max-width: 40rem) {
  /* Edge to edge on a phone, but still cards rather than one column. */
  .digital { margin: .75rem auto 2.5rem; padding: 0 .6rem; gap: .75rem; }
  .digital .panel { padding: 1.8rem 1.3rem; border-radius: 14px; }
  .digital .p-cover { padding-block: 2.2rem 2rem; }
}

/* ---------- Print sheets: letter landscape, two 5.5x8.5 panels per side ---------- */
.sheets { padding: 1.5rem 1rem 3rem; overflow-x: auto; }
.sheets.offscreen { position: absolute; left: -10000px; top: 0; }
.caption { max-width: 11in; margin: 0 auto .5rem; color: var(--muted); font-style: italic; font-size: .9rem; }
.sheet-wrap { margin: 0 auto 2rem; }
.sheet {
  --paper: #ffffff; --ink: #1d2433; --muted: #555d6b; --rule: #c4c9d1;
  width: 11in; height: 8.5in; transform-origin: top left;
  display: grid; grid-template-columns: 5.5in 5.5in;
  background: var(--paper); color: var(--ink); position: relative;
  box-shadow: 0 1px 2px rgba(20, 30, 50, .18), 0 8px 24px rgba(20, 30, 50, .10);
}
.sheet::after { /* fold line, screen only */
  content: ""; position: absolute; top: 0; bottom: 0; left: 50%;
  border-left: 1px dashed #c4c9d1;
}
.sheet .panel {
  height: 8.5in; padding: .5in .55in; overflow: hidden;
  font-size: 10.5pt; line-height: 1.45; position: relative;
}
.sheet .p-cover { padding-block: .75in .6in; }

/* ---------- The two-page handout ----------
   Same 5.5x8.5in panel as a booklet page, but this sheet is cut rather
   than folded, so the middle gets a cut line on screen and real crop
   marks on paper. The fold line above is screen-only on purpose -- a
   printed fold line is a line down a finished bulletin. A cut mark is
   the opposite: it is an instruction to whoever runs the paper cutter
   and has to survive printing, so it sits in the 1/8in of margin at the
   very top and bottom where no panel content reaches. */
.sheet.cut::after { border-left-style: dotted; }
/* The crop marks themselves: two short ticks on the centre line, in the
   top and bottom eighth-inch. `::after` above is the full dotted guide
   and is hidden when printing; these are not, which is the whole point
   -- they are what somebody lines a paper cutter up against. */
.sheet.cut::before {
  content: ""; position: absolute; left: 50%; top: 0; width: 1px; height: 100%;
  background:
    linear-gradient(#1d2433, #1d2433) top center / 1px .14in no-repeat,
    linear-gradient(#1d2433, #1d2433) bottom center / 1px .14in no-repeat;
}
.sheet .p-half { padding-block: .45in .4in; }
/* The handout opens with the program rather than a cover, so its
   header is a line, not a panel: the ward and the date, then straight
   into who is conducting. */
.half-head { text-align: center; margin: 0 0 .22in; }
.half-head .ward { font-size: 1.45em; line-height: 1.15; margin: 0; }
.half-head .half-date { font-size: .92em; color: var(--muted); margin: .06in 0 0; }
.half-head .meeting { white-space: nowrap; }
/* The leader list sits under the program on page one and the heading has
   to separate them without costing a quarter inch. */
.p-half-1 h3 { margin-top: .22in; }
.p-half-2 h2 { margin-bottom: .5em; }
.sheet .panel.overflow { box-shadow: inset 0 -4px 0 var(--warn); }
.sheet .panel.overflow::after {
  content: "Too long to fit"; position: absolute; right: .3in; bottom: .15in;
  background: #b3261e; color: #fff; font-size: 8pt; padding: .1em .6em; border-radius: 3px;
}

/* ---------- Print ---------- */
@page { size: letter landscape; margin: 0; }
@media print {
  :root { padding: 0; }
  body { background: #fff; }
  .toolbar, .digital, .caption, .no-print { display: none !important; }
  .sheets, .sheets.offscreen { position: static; padding: 0; overflow: visible; }
  .sheet-wrap { width: auto !important; height: auto !important; margin: 0; }
  .sheet { transform: none !important; box-shadow: none; break-after: page; }
  .sheet-wrap:last-child .sheet { break-after: auto; }
  /* Wrapping each conference handout in its own div made `:last-child`
     match the last sheet of every handout, which would have let the next
     handout's first sheet share a page with it. Only the final one ends
     without a break. */
  .handout:not(:last-child) .sheet-wrap:last-child .sheet { break-after: page; }
  /* "Print this one": the other handouts are taken out of the job rather
     than the moderator being asked to type a page range. They were
     measured while visible, so hiding them now costs nothing. */
  .sheets.only .handout { display: none !important; }
  .sheets.only .handout.print-me { display: block !important; }
  .sheets.only .handout.print-me .sheet-wrap:last-child .sheet { break-after: auto; }
  .sheet::after, .sheet .panel.overflow::after { display: none; }
  .sheet .panel.overflow { box-shadow: none; }
}

/* ---------------------------------------------------------------------
   Cover picture, hymn titles, grouped announcements.

   Added to match the ward's real bulletins. The printed sheet follows the
   Word document closely -- same density, same conventions -- while the
   digital view stays roomy, because nobody reads 7pt on a phone.
   --------------------------------------------------------------------- */

/* Cover picture. Sized to whatever the panel has left rather than a fixed
   height, so a long quote and a tall drawing cannot fight each other. */
.cover-art { display: flex; justify-content: center; align-items: center; min-height: 0; flex: 1 1 auto; }
.cover-art img { max-width: 100%; max-height: 100%; object-fit: contain; }
.digital .cover-art { margin: 1rem 0; }
.digital .cover-art img { max-height: 16rem; }
.sheet .cover-art { margin: .12in 0; }
.sheet .cover-art img { max-height: 2.9in; }

/* Hymn title on its own centered line under the dot-leader row. */
.hymn-title { text-align: center; font-style: italic; margin: -.1em 0 .5em; }
.sheet .hymn-title { font-size: 9.5pt; margin: -.02in 0 .05in; }

/* ---------- Stake meeting program ----------
   Not imposed onto folded panels. A ward bulletin is four 5.5in panels
   because that is what a ward hands out; a conference program is four
   meetings read on a phone, and squeezing it into the same shape would
   mean cutting the part people came for. */
/* A paper surface on the desk, the same as `.digital`. Without it the
   program sat straight on the gray page background. */
/* The container a stake program renders into. NOT `.stake` -- that is
   the stake's name on a ward bulletin cover, and this rule used to apply
   to it: one line of italic text, 110px tall, on a paper background.
   `.officers` and `.row` are shared the same way; scope anything new. */
/* `display: block` matters, and is not tidiness. In the editor this class
   is added to `#digital`, which is a grid of cards -- and a sticky grid
   item is confined to its own grid area, so the meeting chooser would
   have had nowhere to stick. The public page is a <main> and was block
   already; this makes the preview agree with it. */
.stake-program { max-width: 40rem; margin: 1.5rem auto; background: var(--paper);
         display: block; padding: 2.2rem 1.6rem 3rem; }
.stake-head { text-align: center; margin: 0 0 1.5rem; }
.stake-head h1 { font-family: var(--serif); font-weight: 500; font-size: 1.9rem;
                 line-height: 1.15; margin: 0; }
.stake-head .dates { margin: .3rem 0 0; color: var(--muted); }
.stake-head .theme { margin: .9rem 0 0; font-style: italic; }
.stake-head .note { margin: .5rem 0 0; color: var(--muted); font-size: .9rem; }

/* The meeting chooser stays at the top of the screen while the program
   scrolls under it, so "which meeting am I looking at" is never more than
   a glance away and changing it is never a scroll back up.

   `--pick-top` is how far down it sticks. Zero in the editor, whose
   preview pane scrolls on its own; the toolbar's height on the public
   page, where the toolbar is sticky above it and sets the property.

   It bleeds to the card's edges by the card's own gutter, so nothing
   scrolls past in the margin beside it. */
.session-pick {
  position: sticky; top: var(--pick-top, 0px); z-index: 4;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .15rem .7rem;
  margin: 0 -1.6rem 1.8rem; padding: .7rem 1.6rem;
  background: var(--paper);
  border-bottom: 1px solid color-mix(in srgb, var(--rule) 60%, transparent);
}
.session-pick .pick {
  font-family: var(--sans); font-size: .95rem; font-weight: 500;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--rule); border-radius: 10px;
  padding: .4rem 2rem .4rem .7rem; cursor: pointer;
  max-width: 100%; flex: 0 1 auto;
  /* The native arrow varies wildly between platforms and sits badly in a
     rounded control; one drawn here looks the same everywhere. */
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%235b6472' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .7rem center;
  background-size: .62rem;
}
.session-pick .pick:hover { border-color: var(--accent); }
.session-pick .pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.session-pick .pick-when {
  font-family: var(--sans); font-size: .8rem; color: var(--muted);
}
.session-pick .pick-when:empty { display: none; }

/* ---------- The weekend at a glance ----------
   The schedule blocks off the stake site, read live. A time column that
   stays a column, because the thing somebody is doing with this page is
   running their eye down it looking for the next hour. */
.overview { margin: 0; }
.ov-day + .ov-day { margin-top: 2rem; }
.ov-title {
  font-family: var(--serif); font-weight: 500; font-size: 1.25rem;
  margin: 0 0 .9rem; line-height: 1.25;
  padding-bottom: .4rem; border-bottom: 1px solid var(--rule);
}
.ov-row {
  display: grid; grid-template-columns: 10.5rem 1fr; gap: .2rem 1rem;
  padding: .55rem 0;
}
.ov-row + .ov-row { border-top: 1px solid color-mix(in srgb, var(--rule) 45%, transparent); }
.ov-time { margin: 0; font-size: .9rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.ov-what { margin: 0; font-weight: 500; }
.ov-who { margin: .2rem 0 0; font-size: .88rem; color: var(--muted); line-height: 1.45; }
.ov-note { margin: 1.6rem 0 0; font-size: .85rem; color: var(--muted); font-style: italic; }
/* On a phone the time goes above what it is, rather than squeezing the
   long "who" column into a third of the screen. */
@media (max-width: 34rem) {
  .ov-row { grid-template-columns: 1fr; gap: .15rem; }
  .ov-time { font-size: .82rem; }
}

/* The prompt that stands in for a program nobody has asked for yet. It
   exists only while the empty option is selected; once a meeting is
   chosen the program itself is the answer. */
.pick-empty { display: none; }
.none-chosen .pick-empty {
  display: block; margin: 2.5rem 0; text-align: center;
  color: var(--muted); font-style: italic;
}

/* See wireSessionNav: with one meeting showing, the pinned bar already
   carries its name and time, so the heading under it does not repeat
   them. Printing does -- a printed page has no bar. */
.one-meeting .session > .session-title,
.one-meeting .session > .session-when { display: none; }

.session { margin: 0 0 2.6rem; }
.session + .session { border-top: 1px solid var(--rule); padding-top: 1.6rem; }
.session-title { font-family: var(--serif); font-weight: 500; font-size: 1.35rem;
                 margin: 0; line-height: 1.2; }
.session-when { margin: .25rem 0 0; font-weight: 600; font-size: .95rem; }
.session-who { margin: .15rem 0 0; color: var(--muted); font-size: .9rem; }
.session-theme { margin: .6rem 0 0; font-style: italic; font-size: .95rem; }

/* A stake session's officers are a <dl> of role/name pairs and want two
   columns. A ward bulletin's are four `.row`s with dot leaders and want
   to stack -- this rule was unscoped and pulled them into a 2x2 grid,
   which on a phone wrapped "Bishop Yeoman" onto two lines. Scope it. */
.session .officers, .p-stake .officers {
            display: grid; grid-template-columns: auto 1fr; gap: .15rem .8rem;
            margin: 1rem 0 1.2rem; font-size: .92rem; }
.session .officers dt, .p-stake .officers dt { color: var(--muted); }
.session .officers dd, .p-stake .officers dd { margin: 0; }

/* The rows reuse the bulletin's own program styling, so a hymn sets the
   same way here as on a ward's back panel. */
.session .rows { font-size: 1rem; }

/* The conducting times, which only an editor is shown. The column holds
   its width even on rows that have no time, so the program does not
   stagger down the page. */
.at-row { display: grid; grid-template-columns: 5.6rem minmax(0, 1fr); align-items: baseline; }
.at { font-family: var(--sans); font-size: .76rem; color: var(--muted);
      padding-top: .35em; white-space: nowrap; }
.at-what { min-width: 0; }

@media print {
  .stake-program { max-width: none; margin: 0; padding: 0; background: #fff; }
  .session-pick { display: none; }
  /* There is no bar on paper, so the heading has to say which meeting
     this is and when, whatever the screen was showing. */
  .one-meeting .session > .session-title,
  .one-meeting .session > .session-when { display: block; }
  /* Sessions are allowed to flow and pack together. Forcing one per page
     made four sheets out of a conference whose shortest session is six
     rows; there are no announcements here, so the pages are mostly
     program and several will fit. What is never allowed is a heading
     stranded at the foot of a page away from its own meeting, or a row
     split down the middle. */
  .session { break-inside: auto; }
  .session-title, .session-when, .session-who, .session-theme,
  .officers { break-after: avoid; }
  .session-title { break-before: auto; }
  .session .rows > * { break-inside: avoid; }
  .session + .session { padding-top: 1.2rem; }
}

/* ---------- Artwork in dark mode ----------
   The drawn artwork has a transparent background, so a reader's dark
   mode shows through instead of a white block, and the printed sheet
   shows white paper. Both were compared on a dark page before choosing.

   The coloring pages need more: they are dark outlines with nothing
   behind them, so on a dark page they all but vanish. Inverting makes
   them white outlines, which is right for line art -- and wrong for the
   tonal covers, where it yields a negative with a black sun. Hence a
   selector that names the one library it applies to.

   Scoped to `.digital`: `.sheet` is always white paper, and a print
   preview that inverted its artwork would misrepresent what comes out
   of the tray. Paintings and a ward's own upload are never touched --
   the first because a painting has a background, the second because it
   is not ours to alter. */
:root[data-theme="dark"] .digital .cover-art img[src*="coloring/"],
:root[data-theme="dark"] .art-opt img[src*="coloring/"] {
  filter: invert(1);
}
/* A hymn title links to the study library on screen. On paper it is just
   a title: an underlined, colored hymn name on a printed program would
   be noise, and the address it points at is unreadable from a pew. */
.hymn-title a { color: inherit; text-decoration-color: var(--rule);
                text-underline-offset: .15em; }
.sheet .hymn-title a { color: inherit; text-decoration: none; }

/* Announcement bodies written as "-" lines become a real list. */
.ann ul { list-style: none; margin: .1em 0 0; padding: 0; }
.ann li { position: relative; padding-left: .8em; margin-bottom: .12em; }
.ann li::before { content: "\2013"; position: absolute; left: 0; }
.sheet .ann { margin-bottom: .5em; }
.sheet .ann li { margin-bottom: .04in; }

/* ---------- Print density, matching the Word document ---------- */
.sheet .p-back { font-size: 7pt; line-height: 1.28; }
.sheet .p-back h2 { font-size: 9.5pt; margin-bottom: .5em; }
.sheet .p-back h3 { font-size: 8pt; margin: .7em 0 .25em; }
.sheet .p-back .leaders li { margin-bottom: .12em; }
.sheet .p-ann { font-size: 8pt; line-height: 1.3; }
.sheet .p-ann h2 { font-size: 10pt; }
.sheet .p-ann .ann strong { font-size: 8.5pt; }
.sheet .p-program { font-size: 10pt; }
.sheet .p-program h2 { font-size: 15pt; text-align: center; }

/* Interludes are the fixed points of the meeting -- business, the
   sacrament, testimonies. The ward sets them larger than the rows around
   them, which is what makes the order of meeting scannable. */
.sheet .interlude { font-size: 11.5pt; font-style: normal; color: var(--ink); margin: .5em 0; }
.digital .interlude { font-size: 1.05em; font-style: normal; color: var(--ink); }

/* ---------------------------------------------------------------------
   One QR code, several links.

   The printed bulletin carries a single code that leads to the online
   version; the links themselves are named beside it. Online they are real
   links. Five codes on a 5.5in panel is a thicket, and nobody types a
   printed URL.
   --------------------------------------------------------------------- */
.online { align-items: flex-start; }
.online-text { min-width: 0; }
.links { list-style: none; margin: .35em 0 0; padding: 0; }
.links li { margin-bottom: .2em; }
.links a { color: var(--accent); }
.link-note { display: block; color: var(--muted); font-size: .85em; }
.link-names { margin: .25em 0 0; }

/* Rich text announcements. Every size is relative, which is what lets the
   shrink-to-fit pass keep working: the panel scales its own font-size and
   everything inside follows. An absolute pt size here would ignore --fit
   and overflow the sheet no matter how far it shrank. */
.rich > *:first-child { margin-top: 0; }
.rich > *:last-child { margin-bottom: 0; }
.rich p { margin: 0 0 .45em; }
.rich ul, .rich ol { margin: .2em 0 .5em; padding-left: 1.15em; }
.rich li { margin-bottom: .1em; }
.rich h4 { font-size: 1.08em; font-weight: 600; margin: .6em 0 .2em; }
.rich blockquote { margin: .3em 0 .5em .8em; font-style: italic; }
.rich .sz-sm { font-size: .85em; }
.rich .sz-lg { font-size: 1.2em; }
.rich .sz-xl { font-size: 1.45em; }

/* The mirror of .print-only, which already exists. */
.sheet .digital-only { display: none; }
.digital .link-names { display: none; }
.sheet .link-names { font-size: 6.8pt; line-height: 1.3; }

/* ---------------------------------------------------------------------
   Cover date on one line.

   It used to be a tower -- month, the day at 9em, then the year -- which
   left the picture a sliver of the panel. The ward's own bulletins set the
   date as a single line, so this is both roomier and more familiar.
   --------------------------------------------------------------------- */
.p-cover .cover-date {
  display: block; text-align: center; line-height: 1.15;
  font-size: 1.7em; font-weight: 400; letter-spacing: .01em;
  margin: .35em 0 .15em; color: var(--ink);
}
.p-cover .meeting { margin: 0 0 .5em; font-size: .95em; color: var(--muted); }
.sheet .p-cover .cover-date { font-size: 19pt; margin: .06in 0 .03in; }
.sheet .p-cover .meeting { font-size: 9.5pt; margin: 0 0 .06in; }
.sheet .p-cover { padding-block: .45in .4in; }

/* The picture now takes what the date gave back. */
.sheet .cover-art img { max-height: 4.5in; }
.digital .cover-art img { max-height: 24rem; }

/* ---------------------------------------------------------------------
   Standing notices: who to call for an appointment, where to send items
   for next week. They belong with the leaders and the QR code -- all the
   "how to reach us" material in one place -- not adrift among the
   announcements that change every week.
   --------------------------------------------------------------------- */
.notices { list-style: none; margin: 1em 0 0; padding: 0; }
.notices li { margin-bottom: .3em; }
.digital .notices { color: var(--muted); font-size: .92em; }
.sheet .notices { font-size: 6.8pt; line-height: 1.3; margin-top: .09in; }
.sheet .notices li { margin-bottom: .04in; }

/* Version marker. On screen only -- a version string has no business on a
   church program, so it is absent from the printed sheets. */
.version { text-align: center; color: var(--muted); font-size: .72rem; padding: 1.2rem 1rem 2rem; }
.version code { font-family: inherit; }
@media print { .version { display: none; } }

/* ---------------------------------------------------------------------
   Leaders in two columns, richer lists, and panels that shrink to fit.
   --------------------------------------------------------------------- */

/* A single column of thirteen callings left half the panel empty while
   each row stretched a name away from its calling. Two columns read
   better and give the announcements room. */
.sheet .leaders { columns: 2; column-gap: .22in; }
.sheet .leaders li {
  display: block; break-inside: avoid; margin-bottom: .05in;
}
/* The calling in semibold with the name indented under it. Printed, the
   calling and the name sat on two lines at the same weight and the same
   left edge, so thirteen rows read as twenty-six lines of undifferentiated
   text and nothing told you where one person ended and the next began.
   Weight separates them and the indent groups them.

   600 rather than `bold`: Spectral is loaded at 300/400/500/600, so this
   is a real cut of the face. `bold` would ask for 700, which is not
   loaded, and the browser would synthesise it -- smeared letterforms that
   a ward copier makes worse. */
/* The leadership heading: centred, semibold, underlined. It is the only
   heading inside a panel that sits over two columns rather than over
   running text, so centring it reads as a caption for the block instead
   of a left-aligned heading that belongs to the first column.

   `border-bottom` rather than `text-decoration: underline`, so the rule
   hugs the text (the element is centred and shrink-wrapped with
   `width: fit-content`) and sits a hair below the baseline instead of
   cutting through the descenders of "Organizations". */
/* `.sheet .panel h3.leaders-head`, not `.sheet .leaders-head`. Both
   `.sheet .p-half h3` and `.sheet .p-back h3` set `margin: .7em 0 .25em`
   and are more specific than a two-class selector, so they were winning
   and resetting the auto side margins -- which is the whole centring
   mechanism for a `fit-content` box. The text-align did apply, and did
   nothing visible, because it was centring the text inside a box that
   was exactly as wide as the text and parked on the left.
   The margin is declared here in full so there is nothing left to override. */
.sheet .panel h3.leaders-head {
  font-weight: 600; text-align: center; font-style: normal;
  width: fit-content; margin: .7em auto .25em;
  border-bottom: 1px solid currentColor; padding-bottom: .02in;
}
/* The name runs on after the calling rather than sitting on its own
   indented line. That is how the ward's own Word bulletin set it, and
   measured against the real thirteen leaders it is 14% shorter; with the
   tighter leading below, 24%. The semibold calling is what separates one
   person from the next, so the line break was paying for something the
   weight already does.

   Three columns was measured too and saves more -- 29% -- but at 1.35in
   a column "Ward Temple & Family History Leader" wraps to three lines
   and two rows come out ragged. Shorter and uglier is not a saving. */
.sheet .leaders .calling { font-weight: 600; }
.sheet .leaders .who { display: inline; text-align: left; padding-left: .3em; }
/* A phone number is one thing and must not be broken over two lines.
   In a half-width column "Dallan Yeoman (605) 555-0100" was wrapping
   after the area code, which reads as two numbers. It now wraps whole,
   onto its own line if it has to. */
.sheet .leaders .phone { display: inline; white-space: nowrap; }
.sheet .leaders .phone::before { content: " "; }
@media (min-width: 30rem) {
  .digital .leaders { columns: 2; column-gap: 1.6rem; }
  .digital .leaders li { display: block; break-inside: avoid; }
  .digital .leaders .calling { font-weight: 600; }
  .digital .leaders .who { display: block; text-align: left; color: var(--muted); padding-left: .65rem; }
}

/* Numbered announcement lists keep the author's own numbering. */
.ann ol { margin: .1em 0 0; padding-left: 1.35em; }
.ann ol li { margin-bottom: .12em; }
.sheet .ann ol { padding-left: 1.2em; }

/* --fit is set by the renderer when a panel has to shrink to stay on the
   page. It scales type only -- the panel itself is a fixed 5.5x8.5in. */
.sheet .panel { font-size: calc(10.5pt * var(--fit, 1)); }
.sheet .p-back { font-size: calc(7pt * var(--fit, 1)); }
.sheet .p-ann { font-size: calc(8pt * var(--fit, 1)); }
.sheet .p-program { font-size: calc(10pt * var(--fit, 1)); }

/* The two-page handout carries two densities on each panel, because it
   is two of the booklet's panels in one: the order of meeting at the
   program's 10pt over the leader list at the back panel's 7pt, and the
   announcements at 8pt over the calendar and the codes at 7pt.
   Reproducing the figures rather than inheriting them keeps the handout
   setting exactly as dense as the booklet a ward is used to.

   Every one of these has to carry --fit. The shrink-before-you-complain
   pass sets that variable on the panel and nothing else; a size written
   as a plain `7pt` ignores it, so the renderer shrinks the panel, the
   type does not move, and it overflows anyway. That is precisely what
   happened the first time this was measured against a real ward's
   content -- --fit reached its 0.82 floor and the panel was still 60px
   over, because not one rule here was reading it. */
.sheet .p-half-1 { font-size: calc(10pt * var(--fit, 1)); }
.sheet .p-half-2 { font-size: calc(8pt * var(--fit, 1)); line-height: 1.3; }
.sheet .p-half-1 .leaders,
.sheet .p-half-2 .cal,
.sheet .p-half-2 .notices,
.sheet .p-half-2 .online { font-size: calc(7pt * var(--fit, 1)); line-height: 1.28; }
.sheet .p-half h2 { font-size: calc(10pt * var(--fit, 1)); margin-bottom: .5em; }
.sheet .p-half h3 { font-size: calc(8pt * var(--fit, 1)); margin: .7em 0 .25em; }
/* Tighter only on page one of the handout, where the leaders share a
   panel with the order of meeting. On the booklet the back panel is
   theirs alone and has no reason to be cramped. */
.sheet .p-half-1 .leaders { line-height: 1.18; }
.sheet .p-half-1 .leaders li { margin-bottom: .03in; }
.sheet .p-half-2 .leaders li { margin-bottom: .12em; }
.sheet .p-half-1 .half-head .ward { font-size: calc(15pt * var(--fit, 1)); }
.sheet .p-half-1 .half-head .half-date { font-size: calc(9pt * var(--fit, 1)); }

/* ---------- Minutes, on a stake program's item cards ----------
   Narrow and to the right: it is a small decision sitting beside the
   large ones, and it should not take a column's worth of width from the
   name of whoever is speaking. */
/* From, To, and the length that falls out of them. Narrow and to the
   right: a small decision sitting beside the large ones, which should not
   take a column's worth of width from the name of whoever is speaking. */
.mins-wrap {
  display: flex; flex-wrap: wrap; justify-content: flex-end;
  align-items: flex-end; gap: 0 .5rem; margin-top: .35rem;
}
.mins-wrap .field { margin: 0; }
.mins-wrap input.mins { width: 5.5rem; text-align: right; }

/* ---------- What a PDF import found ----------
   The warnings are the point of the screen: an agenda carries mistakes,
   and the ones worth a human's attention should not be a grey note. */
/* --warn, not an undefined --bad with a hardcoded fallback: that kept
   the light red (#b3261e) in dark mode, where --warn is #f2b8b5. */
.warnbox { border: 1px solid var(--warn); border-radius: 8px;
           padding: .75rem 1rem; margin: 0 0 1rem; }
.warnbox p { margin: 0 0 .4rem; }
.warnbox ul, #riBody details ul { margin: .2rem 0 0; padding-left: 1.1rem; }
.warnbox li, #riBody details li { font-size: .88rem; margin: .15rem 0; }
#riBody details { margin: 0 0 .9rem; }
#riBody summary { cursor: pointer; font-family: var(--serif); padding: .25rem 0; }
#riBody .blist li { display: flex; justify-content: space-between; gap: 1rem; align-items: baseline; }

/* ---------- A stake program, folded like a bulletin ----------
   The digital sizes are set for a column on a screen; a 5.5in panel is
   half that wide and needs its own scale. Scoped to `.sheet .p-stake` so
   the digital program is untouched. */
.sheet .p-stake { font-size: 10pt; }
.sheet .p-stake .session-title { font-size: 15pt; }
.sheet .p-stake .session-title .cont {
  font-family: var(--sans); font-size: 8pt; font-weight: 400;
  color: var(--muted); letter-spacing: .04em; text-transform: uppercase;
}
.sheet .p-stake .officers {
  font-size: 9pt; margin: .12in 0 .13in; padding-bottom: .1in;
  border-bottom: 1px solid var(--rule);
}
.sheet .p-stake .rows { font-size: 10pt; }

.sheet .p-stake-cover .sc-title {
  font-family: var(--serif); font-size: 21pt; font-weight: 500;
  line-height: 1.15; margin: 0;
}
.sheet .p-stake-cover .sc-dates {
  margin: .08in 0 0; font-size: 11pt; color: var(--muted);
}
/* Which meeting this handout is for. Set below the conference and larger
   than its dates, because somebody picking a sheet off the table is
   looking for the session, not for the weekend they already know about. */
.sheet .p-stake-cover .sc-session {
  font-family: var(--serif); font-size: 15pt; font-weight: 500;
  line-height: 1.2; margin: 0;
}
.sheet .p-stake-cover .sc-when { margin: .05in 0 0; font-size: 10.5pt; }
.sheet .p-stake-cover .sc-who {
  margin: .03in 0 0; font-size: 9.5pt; color: var(--muted);
}
.sheet .p-stake-cover .sc-note {
  margin: 0; font-size: 9pt; color: var(--muted); font-style: italic;
}
/* Names the stack that follows it. Heavier than the sheet captions under
   it, since it separates one handout from the next; hidden in print with
   every other caption. */
.caption.handout-name {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  font-style: normal; font-weight: 600; color: var(--ink);
  font-size: 1rem; margin-top: 2rem; padding-top: 1rem;
  border-top: 1px solid var(--rule);
}
.handout:first-child .caption.handout-name {
  margin-top: 0; padding-top: 0; border-top: 0;
}
.btn.tiny { font-size: .78rem; padding: .25rem .7rem; flex: none; }
/* Padding a booklet to a multiple of four leaves blanks. They are real
   panels -- the fold needs them -- and print as empty paper. */
.sheet .p-blank { }

/* ---------- Conference, on the front page ----------
   Filled rather than outlined, because on the one weekend it appears it
   is the thing people came for and the ward list is not. */
.conf-link { margin: 0 0 1.2rem; }
.conf-link a {
  display: block; text-decoration: none; color: var(--paper, #fff);
  background: var(--accent, #1d2433); border-radius: 10px;
  padding: .9rem 1.1rem; line-height: 1.35;
}
.conf-link a:hover { filter: brightness(1.12); }
.conf-link strong { display: block; font-family: var(--serif); font-weight: 500; font-size: 1.1rem; }
.conf-link .when { display: block; font-size: .85rem; opacity: .85; }

/* ---------- Choosing somebody off the roster ----------
   The field and its button share a row; the input takes what is left,
   which on a phone is most of it. */
.name-row { display: flex; gap: .4rem; align-items: stretch; }
.name-row > input { flex: 1 1 auto; min-width: 0; }
.name-row > .btn { flex: none; }
#personHits .who { display: block; }
#personHits .what { display: block; font-size: .85em; color: var(--muted); font-style: italic; }

/* ---------- The DEV build marker ---------- */
/* Set by the one-line script every page carries in its head, which keys
   off the hostname: dev.* and *-dev.pages.dev, nothing else. Deliberately
   NOT localhost -- this says "you are on the dev deployment", and the
   offline harness already announces itself.

   In normal flow, as body's own ::before, so it pushes the page down
   instead of sitting on top of it. A fixed strip would have covered the
   sticky toolbar, and overlaying the one thing a moderator reaches for
   is how a marker becomes a nuisance somebody asks to remove.

   `--devbar` is 0 everywhere else, so the one layout that is exactly one
   screen tall can subtract it unconditionally. */
:root { --devbar: 0rem; }
html[data-env="dev"] { --devbar: 1.75rem; }
html[data-env="dev"] body::before {
  content: "DEV BUILD — LIVE DATA";
  display: block; height: var(--devbar);
  background: #b3261e; color: #fff;
  font: 700 .72rem/var(--devbar) var(--sans, system-ui, sans-serif);
  letter-spacing: .14em; text-align: center;
  white-space: nowrap; overflow: hidden;
}
/* Red across the top of a printed sacrament program, forever, in every
   ward that ever prints from the dev site. No. */
@media print { html[data-env="dev"] body::before { display: none; } }
