/* Ward bulletin — shared styles for the public page and the /admin editor.
   Aesthetic: a well-set letterpress program. Cream paper, ink-dark serif,
   one deep green accent, small-caps labels with dotted leader lines.
   All sizes use rem so each phone's own text-size setting is respected. */

/* The browser's own `[hidden] { display: none }` is the weakest rule there is,
   so ANY class that sets a display — .tabbar is flex, .field is block — beats
   it and the element stays on screen. The app hides things by setting
   `el.hidden = true`, so this has to win. Keep it. */
[hidden] {
  display: none !important;
}

:root,
.wp-paper:not(.is-dark) {
  color-scheme: light dark;
  --paper: #faf6ee;
  --card: #fffdf8;
  /* A BRIGHTER CARD, FOR THE TWO KINDS OF CARD ON THE WARD'S OWN PAGE
     (Round 309, Derek: "the background cards of the announcement and
     group cards to be more white"). --card itself is #fffdf8 -- barely a
     shade off the page's own texture-compensated ground (#fffbf3,
     Round 231's --paper-tex), which is why the notice and group cards
     read as almost the same paper as the page behind them rather than as
     a card set on it.

     A NEW TOKEN, NOT --card ITSELF: 49 other rules across the whole app
     -- every dialog, form field and table in the leader app -- read
     --card, and Derek pointed at two specific things on the PUBLIC page,
     not at the admin tools. .ann and .groups-panel .gcard take this one;
     everything else, including the bishopric's approval queue and the
     live-groups dialog (which share the .gcard class but not the
     .groups-panel scope, and are leader-app screens Derek did not point
     at), is untouched.

     PURE WHITE ON PURPOSE: measured against candidates from #fffdf8's own
     step up to #ffffff, and only true white read as a deliberate white
     card rather than a slightly-less-cream one. The accent band and the
     hairline border are what keep it reading as a printed card rather
     than a web pop-up, so white does not need softening the way it would
     on a plain rectangle. */
  --card-bright: #ffffff;
  --ink: #26211a;
  --ink-soft: #6b6254;
  --accent: #527a63;
  --on-accent: #ffffff;
  --line: #e3dac8;
  --dots: #b9ae99;
  --badge-bg: #f4e9cf;
  --badge-ink: #6f5716;
  --danger: #8c3b2e;
  --tenure: #c2643f; /* warm terracotta — time-in-calling shading */
  --notify: #b2542f; /* attention: counts and gaps that need someone */
  --music: #4a6b8a;  /* the music leader's own colour: her blanks on the sheet tab (Round 49) */
  --on-notify: #fffdf8;
  --tenure-vet: #8a4b1f; /* a Provo 1st Veteran: deep orange-brown, a badge of honour */
  /* ONE SCALE OF CORNERS AND ONE LIFT (Round 52). Radii had drifted to nine
     values -- 2, 3, 4, 5, 6, 8, 9, 10, 12 -- with the two largest surfaces in
     the app at 10px sitting directly under cards at 12. Three now: a card,
     an item inside a card, and a detail (a tooltip, a swatch, a thumbnail),
     plus 999px for a pill and 50% for a circle. --lift is the one resting
     shadow a paper surface carries; the two floating panels (the modal, the
     row menu) keep their own deeper shadows. */
  --r-card: 12px;
  --r-item: 8px;
  --r-detail: 4px;
  --lift: 0 1px 3px rgba(20, 16, 10, 0.07);
  /* One distance from the baseline for the quiet pressable words (Round 47
     named it; Round 212 declares it -- every use fell through to this same
     fallback, so nothing on screen moves). */
  --ul-offset: 0.2em;
  /* THE "WORTH A LOOK" INK, and it was never actually defined.
     Nine rules used var(--warn-ink, #9a5b2b) and every one of them fell
     through to that fallback — which is fine on cream (4.99:1) and FAILS on
     the dark paper it was never checked against (3.19:1, and 2.94:1 on a
     card). So on a dark phone every "worth asking again", every stalled
     tracker line and the handover warning were all below AA. Defined in both
     themes now, so the fallbacks are dead code rather than the only value. */
  --warn-ink: #9a5b2b;
  /* A true "green light" — brighter than the sage identity colour, used only
     where something is confirmed good. */
  --success-bg: #d9edde;
  --success-ink: #1d6b3d;
  --success-line: #93c9a6;
}

/* The dark palette. There is no switch: the site follows whatever the phone
   or computer is already set to. A manual override was tried and taken out
   again — it cost a duplicated copy of every colour below (CSS cannot combine
   a media query with an attribute selector) and answered a question almost
   nobody was asking, since people set their device once and forget it. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --paper: #1f1b15;
    --card: #272219;
    /* UNCHANGED FROM --card (Round 309). "More white" is a light-mode
       complaint about a card too close to its own paper; the dark card is
       already lighter than dark paper by design, which is the same idea
       already answered. */
    --card-bright: var(--card);
    --ink: #efe8db;
    --ink-soft: #b4a995;
    --accent: #93bf9e;
    --on-accent: #16241a;
    --line: #3b342a;
    --dots: #56503f;
    --badge-bg: #3a3322;
    --badge-ink: #e0c86f;
    --danger: #e08a76;
    --tenure: #e08a63;
    --notify: #e08a63;
    --music: #8fb0cf;
    --on-notify: #2a1a12;
    --tenure-vet: #d0895a;
    /* 5.94:1 on the dark paper, 5.48:1 on a card — comfortably AA, against
       the 3.19:1 the undefined fallback was giving. */
    --warn-ink: #c98a5e;
    --success-bg: #23402e;
    --success-ink: #8ad4a3;
    --success-line: #3d6b4e;
  }
}

/* THE APPEARANCE SWITCH (Round 182, Derek, for the ward's page only -- see
   the .themepick control). A media query and an attribute cannot be
   combined in one selector, so the dark palette lives twice: the block
   above applies when the device is dark AND nothing has been forced light;
   this one applies when dark has been forced. THE TWO MUST STAY IDENTICAL.
   patch182.js generated this copy from the block above at the moment of
   writing; a token changed up there has to change here too. The leader
   app never sets data-theme, so nothing here reaches it. */
:root[data-theme='dark'],
.wp-paper.is-dark {
  color-scheme: dark;
  --paper: #1f1b15;
  --card: #272219;
  /* Kept identical to the block above, as every token here must be
     (Round 182's own rule) -- unchanged from --card; see that block's
     comment. */
  --card-bright: var(--card);
  --ink: #efe8db;
  --ink-soft: #b4a995;
  --accent: #93bf9e;
  --on-accent: #16241a;
  --line: #3b342a;
  --dots: #56503f;
  --badge-bg: #3a3322;
  --badge-ink: #e0c86f;
  --danger: #e08a76;
  --tenure: #e08a63;
  --notify: #e08a63;
  --music: #8fb0cf;
  --on-notify: #2a1a12;
  --tenure-vet: #d0895a;
  /* 5.94:1 on the dark paper, 5.48:1 on a card — comfortably AA, against
     the 3.19:1 the undefined fallback was giving. */
  --warn-ink: #c98a5e;
  --success-bg: #23402e;
  --success-ink: #8ad4a3;
  --success-line: #3d6b4e;
}

/* THE DARK PAPER HAS TO SET ITS OWN INK, and this is the one asymmetry
   between the two places the dark palette is used.

   REDEFINING A CUSTOM PROPERTY ONLY REACHES DECLARATIONS THAT RESOLVE IT
   AT OR BELOW THAT ELEMENT. On the ward's page data-theme sits on <html>,
   above body, so body's own `color: var(--ink)` resolves to the dark value
   and everything inherits it. In the replica the class sits on .wp-paper,
   deep inside the leader app -- whose body computed `color: var(--ink)`
   from the LIGHT palette long before the paper redefined the token. An
   inherited colour is an already-computed rgb value; it does not re-resolve.

   So every element in the replica with no colour rule of its own -- the ward
   name, the panel heading, every officer name, every hymn number and title --
   kept the light palette's near-black ink on the dark paper. Measured at
   1.07:1: invisible, which is exactly how Derek reported it (Round 292).
   The background did not fail with them, because .wp-paper's own
   `background-color: var(--paper-tex)` is a declaration ON the paper, so it
   resolves against the paper's own redefined token.

   One line fixes all of them, and any element added to the replica later. */
.wp-paper.is-dark {
  color: var(--ink);
}

/* Forced light: the device's dark setting is ignored (the media block above
   is gated on :not([data-theme='light'])) and native controls follow. */
:root[data-theme='light'] {
  color-scheme: light;
}

/* ------------------------------------------------------------- foundation */

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  border-top: 4px solid var(--accent);
  font-family: var(--body);
  font-size: 1.0625rem;
  line-height: 1.6;
}

.wrap {
  max-width: 40rem;
  margin-inline: auto;
  padding-inline: 1.25rem;
  box-sizing: border-box;
}

::selection {
  background: var(--accent);
  color: var(--on-accent);
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
}

/* `border-radius: 4px` WAS HERE and is deleted. It was not part of the ring —
   it rewrote the ELEMENT's own radius, and at (0,1,1) it outranked .btn at
   (0,1,0), so tabbing to anything changed its shape: every 999px pill, every
   date chip and the initials disc all squared off to a 4px rectangle under
   the keyboard. The outline follows whatever radius the element already has. */
button:focus-visible,
a:focus-visible,
summary:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* `select` was in neither focus rule, and there is no select:focus-visible
   anywhere else — so the one control the leader app is mostly made of fell
   back to the browser's own ring: a black-and-white double ring on Chrome,
   a blue glow on Safari, on a cream-and-sage page. The same omission this
   file went out of its way to fix for checkboxes. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* THE SMALL-CAPS LABEL, in three ranks (Round 52). It is the design
   system's signature idiom and it had been set forty different ways -- eleven
   sizes, thirteen tracking values, three weights -- most of them a rounding
   apart from each other. Three ranks now, and each label class below is
   listed under one of them rather than carrying its own numbers:
   - the row label, the canonical setting: 0.8125rem / 0.13em / 650;
   - the subordinate label, for a heading over a small group or a table
     header: 0.75rem / 0.12em / 650;
   - the tag, the micro-label inside a pill or chip: 0.6875rem / 0.1em / 700,
     which is the setting the file had already reached for three times.
   Tracking rises as size falls. Left out on purpose, each for its own
   reason: the mastheads (.welcome-greet, .brand-ward), the printed
   sheet (.cs-*, sized in points), the dense whiteboard (.wb-*), the status
   pill (.pstatus-sel, whose width the tracker columns were measured
   against) and .btn.util, which is a control rather than a label. A rule
   that also sets the font shorthand keeps its own three values, at the
   rank's numbers, because the shorthand would otherwise reset them. */
.row-label,
.field-label,
.edit-type,
.bdays-h,
.readiness-label,
.stand-sec,
.pcard h3,
.actroll h3,
.mtg-h,
.gcard-join,
.gfield span {
  text-transform: uppercase;
  letter-spacing: 0.13em;
  font-size: 0.8125rem;
  font-weight: 650;
}
.mtheme-l,
.annscope span,
.bday-when,
.sundaygrid-h,
.sheetbar-label,
.psort,
.thought-again,
.candctx-label,
.bdday-name,
.hcontents-h,
.hchapter-n,
.htime-y,
.namedrop-head,
.changes-day,
.ptable tr.pgroup td {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.75rem;
  font-weight: 650;
}
.datelist-tag,
.pubstate,
.bizrow-kind,
.nowtag,
.lockrow-tag,
.pill-calling,
.pill-replace,
.ann-draftflag,
.youthchip,
.chip,
.notepin,
.hperson-years,
.locdrop-pick {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.6875rem;
  font-weight: 700;
}

/* --------------------------------------------------------------- masthead */

.masthead {
  text-align: center;
  padding-top: 2.25rem;
}

.ward {
  margin: 0 0 0.4rem;
  font-size: 1.9rem;
  line-height: 1.2;
  font-weight: 650;
  letter-spacing: 0.01em;
}

.mdate {
  margin: 0;
  color: var(--accent);
  font-style: italic;
  font-size: 1.1875rem;
}

/* .mlabel -- the masthead's "SACRAMENT MEETING · 9:00 AM" line -- was here
   until Round 317 (Derek: "get rid of this meeting title and time
   completely from the program"). Both emitters (worker.js's masthead and
   the LIVE PROGRAM replica) stopped printing it, so the rule went with them;
   the meeting type still drives the conducting sheet. */

.fleuron {
  margin: 1.1rem 0 0;
  color: var(--accent);
  font-size: 1.3rem;
  opacity: 0.9;
}

/* ------------------------------------------------------------------- tabs */

/* THE FOUR TABS ARE A CONTENTS LINE, NOT FOUR PILLS (Round 307, Derek:
   "these buttons just don't fit the style and feel anymore ... I'd love to
   have all 4 buttons on one row").

   The pills were the one thing left on this page that read as a web
   widget: rounded, filled, outlined, on a page that has since grown a
   Caslon masthead, THE HISTORIC and EST. 1852, an engraving and a
   printer's fleuron. So the row is set the way a period program would set
   an index -- four words in the masthead's own face, no box, no fill, the
   open one marked with the page's established "you are here" mark (the
   accent, and a rule beneath it -- the scope navigator's own mark, Round
   297), the others in the epigraphs' soft ink.

   MIXED CASE, NOT LETTERSPACED CAPITALS, AND THAT WAS MEASURED, NOT
   CHOSEN. Tracked capitals are the more obvious letterpress voice, but at
   390px four of them need 382px in a 350px column even at 12.3px --
   smaller than "THE HISTORIC" and smaller than the scope navigator it is
   meant to outrank. Mixed-case Caslon at the program names' own size
   (1.2rem, scaled by --ds to 16.9px on a phone) needs 247px for the words
   and 327px with the gaps and the hit padding, and reads a size larger.
   It is the ward name's own face and case, so the row is a sibling of the
   masthead rather than a stranger under it. (The first cut budgeted the
   words and the gaps and forgot the padding: 350.2px in a 350px column,
   and the row wrapped by a fifth of a pixel.)

   ONE ROW ON EVERY ORDINARY PHONE. The 2x2 grid Round 299 built for the
   pills is kept only as a fallback for the narrowest phones and for
   enlarged text -- see the media query near the foot of this file.

   The hit box is padded to about 44px tall (the words alone would be a
   16px target, Round 24's concern), with the rule drawn as an underline
   so it hugs the word rather than the padded box. text-decoration rather
   than a border, as Round 47 settled for every underline here. The size is
   written as the calc directly, in ONE place, rather than as a plain rem
   restated in the --ds block: Round 303 paid for that split once. */
/* THE TWO STRIPS SWAPPED CLOTHES (Round 316, Derek, with both circled: "the
   design styling of these two button groups should swap ... the top buttons
   take on the bottom button's styling, and vice versa", and then, pointing
   at the scope strip's ground: "there's even a slightly lighter outlined
   text box behind those buttons. that's exactly what I want for the upper
   buttons"). So this row now wears what the scope navigator wore from
   Round 297 to 315: the page's own label voice -- tracked small caps at
   the subordinate rank's size and weight -- on a strip of the lighter
   paper-tex ground, ruled above and below. And the scope navigator, below
   in this file, wears what THIS row wore from Round 307: Caslon, mixed
   case, no rule. Round 307's argument for mixed case here was WIDTH
   ("four tracked capitals need 382px in a 350px column"), and Derek's own
   answer to that arrived in the same message: the second tab is BULLETIN
   now, five letters shorter than ANNOUNCEMENTS. MEASURED at 390px: the
   four tracked words are 319px on their own; at the scope strip's own
   1.6rem gap that is 396px and the row breaks in two, so THE GAP IS THE
   ONE NUMBER NOT COPIED ACROSS -- 1rem with no side padding lands at
   348px, one row on a 390, a 375 and a 360 alike. The 2x2 fallback grid
   near the foot of this file still catches the narrowest phones and
   enlarged text.

   The strip's rules are border-block, top and bottom, because an OUTLINED
   box is what he described and what he was looking at: the scope strip's
   own bottom rule sat directly under the birthdays' closing rule, so the
   two read as one outlined box. Here the fleuron is above, not a rule, so
   the box draws both of its own edges. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.35rem 1rem;
  margin-top: 1rem;
  background: var(--paper-tex, var(--paper));
  border-block: 1px solid var(--line);
  padding: 0.7rem 0 0.55rem;
}

.tab {
  flex: 0 0 auto;
  font: inherit;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.8125rem;
  font-weight: 650;
  line-height: 1.3;
  white-space: nowrap;
  background: transparent;
  color: var(--ink-soft);
  border: 0;
  border-radius: 0;
  /* Held clear of the letters so the mark on the open one reads as a rule
     under the word rather than a strike through it -- the scope strip's own
     recipe, drawn as a border so it can be transparent on the others. */
  padding: 0.35rem 0 0.2rem;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  text-decoration: none;
}

.tab:hover,
.tab:focus-visible {
  color: var(--accent);
}

/* THE OPEN ONE: the accent, and the rule under the word. */
.tab[aria-selected='true'] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* (.tab-major and .tab-minor WERE HERE until Round 307 -- Round 293's rule
   that Program and Announcements be one size and Groups and History
   another. That rule sized PILLS; a word has no box to size, so it is
   retired with them, and the classes are gone from the markup too.) */

.tab-count {
  display: inline-block;
  margin-left: 0.45rem;
  padding: 0.05rem 0.55rem;
  border-radius: 999px;
  font-size: 0.8125rem;
  background: var(--accent);
  color: var(--on-accent);
}

/* (.tab[aria-selected='true'] .tab-count lived here until Round 291: the
   count on the ward's Announcements tab, inverted while that tab was open.
   Derek took the count off the public page "forever", and .tab is only ever
   emitted by that strip and its two mirrors, so nothing wears it now.
   .tab-count ITSELF stays -- the leader app's tab badges and every queue
   heading are its real users.) */

/* ----------------------------------------------------------------- panels */

main.wrap {
  padding-top: 1.9rem;
  padding-bottom: 3rem;
}

.panel-h {
  /* Visually hidden (the tabs name the sections); shown when JS is off. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  margin: 0;
}

/* `.js .panel-h { display: none }` WAS HERE and is deleted.
   The rule above already hides these headings the correct way — clipped to a
   pixel, which keeps them in the accessibility tree and takes no space. This
   one then removed them outright, and worker.js marks <html> as .js on every
   load, so the live page's heading structure went h1 "Provo 1st Ward" straight
   to the h3 of an announcement title, with nothing in between. Heading
   navigation is how a screen-reader user moves around a page, and the ward's
   page had no landmarks to move between.
   The no-JS rule below still unfolds them into real 1.4rem headings, which is
   what they are for when there are no tabs to name the sections. */

noscript ~ * .panel-h,
html:not(.js) .panel-h {
  position: static;
  width: auto;
  height: auto;
  overflow: visible;
  clip-path: none;
  font-size: 1.4rem;
  margin: 1.5rem 0 1rem;
}

/* Without JavaScript the tabs cannot switch anything, so they come off and
   BOTH sections are shown, one after the other under their own headings.
   These two rules used to live in an inline <noscript><style> block in the
   page — which the site's own Content-Security-Policy (style-src 'self', no
   unsafe-inline) silently refused to apply, so the fallback never actually
   ran and a reader without JavaScript could not reach the announcements at
   all. In the external stylesheet they are allowed, and they work. */
html:not(.js) .tabs {
  display: none;
}

html:not(.js) .panel[hidden] {
  display: block !important;
}

/* --------------------------------------------------- dotted "leader" rows */

/* A LONG ENTRY BREAKS THE WAY A PRINTED PROGRAM BREAKS ONE.
   Without flex-wrap nothing could stack: when the label and the name together
   ran past one line, the name wrapped to two right-aligned lines and the dots
   shrank to their 1rem floor — an 18px stub of leader hanging in the middle of
   the row, pointing at nothing, which reads as a printing fault.
   It was not hypothetical. Measured on a 320px phone with this week's real
   names: two of the four officer rows already broken at the default text size,
   three at one notch up (Conducting 92px tall), all four at two notches.
   Now the leader runs out to the right margin and the name sits beneath it,
   which is what a program does with a long entry. Nothing moves at the sizes
   where it already fitted. */
.row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.6rem;
  min-width: 0;
}

.row-label {
  color: var(--accent);
}

.row.officer .row-label {
  color: var(--ink-soft);
}

.dots {
  flex: 1 1 1rem;
  min-width: 1rem;
  border-bottom: 2px dotted var(--dots);
  transform: translateY(-0.3em);
}

.row-value {
  text-align: right;
  /* 100%, not 62%: once the row can wrap, the cap's only job is to stop the
     value being wider than the column. margin-left:auto keeps it hard right
     on the line it drops to, so it still reads as the answer to the label. */
  max-width: 100%;
  margin-left: auto;
  overflow-wrap: break-word;
}

/* The month's theme, set as an epigraph above the officers. Centred and in
   the accent so it reads as the page's own voice rather than as a program
   row, and italic because it is somebody's chosen words, not a label. */
.mtheme {
  margin: 0 0 1.4rem;
  text-align: center;
  font-style: italic;
  font-size: 1.02rem;
  line-height: 1.5;
  color: var(--accent);
  text-wrap: balance;
}

.mtheme-l {
  display: block;
  font-style: normal;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 0.3rem;
}

/* THE FOUR CREDITS ARE A TWO-COLUMN TABLE (Round 300, Derek: the titles
   "should all be in left alignment with each other within the text box that
   they are in"). Round 231 centred each line on its own, so every label
   started at a different x and the block read as four unrelated lines.

   The GROUP is still centred -- justify-content on content-sized tracks,
   so the pair of columns is centred as one object and the block keeps the
   centred feel that tells the credits from the order of service. Only the
   inside of it lines up.

   THIS DEPENDS ON EVERY ROW CONTRIBUTING EXACTLY TWO ITEMS. .row is
   display: contents, so its label and value become the grid items and .dots
   is display: none (not an item at all). Both renderers drop an officer with
   no name (.filter on the value, worker.js and app.js) -- a row with a label
   and no value would put the NEXT label in the value column and step every
   line after it across. Keep that filter. */
.officers {
  border-block: 1px solid var(--line);
  padding: 1.05rem 0;
  margin-bottom: 1.7rem;
  display: grid;
  grid-template-columns: minmax(0, max-content) minmax(0, max-content);
  justify-content: center;
  align-items: baseline;
  gap: 0.45rem 0.55rem;
}

/* What happens after sacrament meeting. Ruled off above rather than boxed:
   the two tabs of this page read as one printed document and a card here
   would be the only bordered thing on it.

   MARGIN-TOP WIDENED FOUR TIMES NOW (Round 302, Round 306, Round 311, and
   this one folds Round 311's own padding-top into it too). Everything
   after this block -- the missionaries section, and the colophon that
   follows the panel in the markup -- moves down WITH it, since nothing
   between here and the page's foot sets a margin of its own that would eat
   the gap.

   NO HAIRLINE HERE AT ALL NOW (Round 313, Derek: "get rid of the line you
   currently have right above 'after sacrament meeting' entirely" --
   paired with "insert one of those after the benediction" instead, which
   is .program-list's own border-bottom above). Round 311's border and this
   block's padding-top were the same idea split into two declarations --
   space, then a line, then more space before the heading -- and with the
   line gone the two just become one number: the total distance from the
   program is unchanged (9.5rem + 1.6rem), it is only uninterrupted now. */
/* 9rem (Round 350, Derek: "a substantially larger gap between the thin line
   and after sacrament meeting"). It has gone back and forth -- 11.1rem in
   Round 311, down to 5.5 in Round 323 when he wanted the tail brought up --
   and this lands between the two.

   IT IS ALSO THE RIGHT HIERARCHY, not only his preference. This is the break
   between the sacrament program and the two appendices under it; the break
   BETWEEN those two appendices is .missionaries at 5.1rem. At 5.5 the two
   were within half a rem of each other, so the first one did not read as the
   bigger division it is. Keep this clear of .missionaries if either moves.

   This is the one margin that carries the whole tail of the Program tab: the
   missionaries and the colophon follow it, since nothing between them sets a
   margin that would eat the change. The thin line it is measured from is
   .program-list's own border-bottom (Round 313); this block has no rule of
   its own. */
.afterward {
  margin-top: 9rem;
}

/* THE SAME VOICE AS THE MISSIONARY BLOCK BELOW IT (Round 336, Derek, having
   had the missionaries put right: "if there's anything that can be improved
   about the above section, fix that too").

   These are the two subordinate sections at the foot of the Program tab.
   They already share a heading rule (.afterward-h, .miss-h) -- and their
   body text did not match: this was 17px at a 1.55 leading against the
   missionary rows' 16px, a pixel and a leading apart for no reason anybody
   chose. 1.55 was undocumented and is neither the page's own 1.6 nor the
   card prose's 1.5, so it was a third value that had simply accumulated.

   1rem, so the two sections read as one system, and it is the size the
   missionary rows took in Round 335 for the same reason.

   NO LINE-HEIGHT AT ALL: it inherits the page's own 1.6, which is the right
   reading measure for running prose at this column width (~58 characters a
   line at 600px). The card prose is 1.5 because it is in a card and 1.6
   sprawls there (Round 326); this is not in a card. Inheriting means one
   fewer number to keep in step if the page's measure is ever retuned.

   Full ink, deliberately, where the missionary mission line is soft: this is
   the section's own content, the way an announcement's message is, not a
   supporting line under a name. */
.afterward-p {
  margin: 0 0 0.6rem;
  font-size: 1rem;
}

.afterward-p:last-child {
  margin-bottom: 0;
}

/* THE TWO SUBORDINATE-SECTION HEADINGS AT THE FOOT OF THE PROGRAM TAB, OFF
   THE SHARED RANK-2 LIST (Round 302, Derek: "I want these lines to be more
   bold so that it's more obvious they're different parts of the program").
   Rank 2's 650 is right for a label sitting beside its own value (MONTHLY
   THEME, a date) and too light for a heading that has to read as a section
   break on its own -- so it left that list, the way .ceremony left rank 1
   in Round 231, and carries its own COMPLETE rule rather than a restated
   subset (Round 52's own rule for exactly this). Weight is the one thing
   that moved; the size and tracking are the rank's own, so the two still
   look like siblings of every other small-caps label on the page. */
.afterward-h,
.miss-h {
  margin: 0 0 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.75rem;
  font-weight: 800;
  color: var(--ink-soft);
}

/* MISSIONARIES SERVING FROM THE WARD (Round 280), at the foot of the
   Program tab. Stacked blocks rather than three columns -- the page is a
   40rem phone column and an email address is wider than a third of it.
   .miss-h takes rank 2 of the small-caps scale, in the list above.

   NO HAIRLINE HERE EITHER, NOW (Round 314, Derek: "remove the thin line
   that separates these two sections" -- shown a screenshot of exactly this
   rule, recoloured to var(--line) one round earlier). Round 313's border
   and this block's own padding-top were the same idea in two declarations
   -- space, then a line, then more space -- and with the line gone the two
   fold into one margin-top (3.5rem + 1.6rem), same total distance from
   "After sacrament meeting" as before, just uninterrupted. margin-bottom
   stays from Round 306: when this is the LAST thing on the tab, it is what
   pushes the colophon -- and the BOLD hairline before the disclaimer,
   which Derek has twice now asked to keep exactly as it is -- further down
   too. Deliberately NOT done by raising main.wrap's own bottom padding,
   which is shared by every public tab and would have widened the footer
   gap on Announcements, Groups and History as well, none of which Derek
   pointed at. */
/* 3.9rem, from 5.1 (Round 350, Derek: "there should not be this big of a
   gap between the after sacrament meeting and missionaries currently
   serving sections. Pull up the entire bottom to close up this gap. Make
   it symmetrical to the gap between the bottom of the missionaries
   currently serving section and the thin line at the bottom").

   3.9rem IS THAT OTHER GAP, ADDED UP, not a number picked to look right:
   below this section there is nothing but its own margin-bottom (0.9rem)
   and main.wrap padding-bottom (3rem) before the colophon draws its rule,
   so the distance from the last missionary to that line is exactly 3.9rem.
   Measured on the live page before and after: 81.6px against 62.4px, now
   62.4 against 62.4. KEEP THESE THREE IN STEP -- change either of the two
   below and this one has to follow or the symmetry he asked for is gone.

   Everything under this moves up with it, which is the "pull up the entire
   bottom" half of the ask. */
.missionaries {
  margin-top: 3.9rem;
  margin-bottom: 0.9rem;
}

.miss {
  margin: 0 0 1rem;
}

.miss:last-child {
  margin-bottom: 0;
}

/* ONE LADDER, NOT FOUR SETTINGS (Round 335, Derek: "you have 4 different
   fonts here on 4 different rows"). He counted treatments rather than
   families -- there were only ever two faces -- and he was right that they
   formed no hierarchy: heading 12px, name 21.6px Caslon at 400, mission
   16.8px, email 17px. So the two supporting rows were the same size as each
   other AND THE EMAIL WAS THE LARGER OF THE TWO, while the name, set in a
   display cut whose strokes are far lighter than the book face, did not read
   as the headline at all despite its bigger point size.

   Round 280 had aimed at this and missed, and the reason is worth keeping:
   it corrected the POINT SIZE and changed the FACE in the same move, so the
   name went up in numbers and down in colour. Weight is what fixes it.

   The block is now the shape this page already uses for a headline with two
   supporting lines -- the group card and the announcement card:

     name      the display face at 650, the SAME synthesised bold Rounds 324
               and 325 measured and took for .gcard-name and .ann-title (one
               weight in caslon.woff2, so 650 is a smear -- rendered and
               judged, not assumed, exactly as those rounds did)
     mission   the book face at 1rem, soft ink
     email     the book face at 1rem, the accent, underlined

   THE BOTTOM TWO ROWS ARE ONE VOICE, DIFFERING ONLY IN COLOUR, which is the
   .ann-when / .ann-where treatment (Rounds 325, 329) and is what answers the
   complaint: the eye sees a name, then one supporting voice, with the link
   coloured. Neither declares a family -- they inherit the book face from
   body, and a redundant declaration is one more thing to drift.

   Not in the --ds block on purpose: at 1rem they are 16px on a computer and
   16px on a phone, which is the phone body size (Round 299), and 14.08px is
   too small for an address somebody has to read off a screen. */
.miss-name {
  margin: 0;
  font-family: var(--display);
  font-size: 1.35rem;
  font-weight: 650;
  line-height: 1.35;
}

.miss-mission {
  margin: 0.1rem 0 0;
  font-size: 1rem;
  color: var(--ink-soft);
}

/* An address is one long unbreakable token, and this column is 40rem at
   most -- without this it is the one thing on the page that can push it
   sideways. */
.miss-email {
  margin: 0.15rem 0 0;
  font-size: 1rem;
  overflow-wrap: anywhere;
}

/* The editor, at the foot of the conducting column. One ruled block per
   missionary rather than a card each: it is a short list inside a fold
   that is shut most weeks. */
.missnote {
  font-style: italic;
  margin: 0 0 0.7rem;
}

.missrow {
  padding: 0.55rem 0 0.65rem;
  border-bottom: 1px solid var(--line);
}

.missrow:last-child {
  border-bottom: 0;
}

/* A row's own action, not one branching off a heading, so it drops the
   leading glyph (the treatment Rounds 185 and 277 gave the same case). */
.missdel {
  margin-top: 0.3rem;
}

/* TWO classes: .quiet-inline::before is written further down this file, so
   at equal specificity it would win and the row would read "Remove" with a
   branch glyph in front of it (the Round 243 trap). */
.quiet-inline.missdel::before {
  content: none;
}

/* .addrow .btn is flex: 1 1 auto, which is right where a row holds one
   full-width action and wrong here: it stretched a two-word primary across
   the whole column (the same fault Round 264 scoped out of .pubitem).
   Rank 1 is sized to its words. */
.missbox .addrow .btn {
  flex: 0 0 auto;
}

/* CLOSED, IT IS A SHELF RATHER THAN A TENTH SECTION (Round 288, Derek:
   "it shouldn't be quite as loud of a tab as the others, because that
   information will only be updated a couple times per year").

   It was carrying a numbered section's whole weight -- a card's full
   padding and a heading at the size and weight of "1 Meeting" -- for a
   standing list that changes twice a year. Closed it now reads as the
   quiet row it is; OPEN it is an ordinary card again, because then it is
   the thing being worked on and should look like one.

   SCOPED TO .missbox, NEVER .foldcard: the same fold dresses the hymn
   planner's volunteers box and the Clerks tab's recent-changes card, and
   neither was asked about. */
body.leader .sheetform .missbox:not(.is-open) {
  padding-top: 0.45rem;
  padding-bottom: 0.45rem;
}

.missbox:not(.is-open) .foldtitle {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink-soft);
}

/* The chevron shrinks with it, or a 30px button holds the row open to the
   height the padding just gave up. */
.missbox:not(.is-open) .foldbtn {
  min-height: 1.5rem;
}

/* ------------------------------------------------------ birthdays this week */

/* On the ward's page, at the head of the Announcements tab (Round 48, part
   3). Set like everything else on this page: no box, a hairline under it,
   the day in the program's small-caps voice and the names at reading size.
   Emitted by worker.js (bdaysHTML) AND by the replica in app.js — the .ann*
   lesson: grep both before touching these. */
.bdays {
  /* The same rule as under a notice, in the same place (Round 212): 1.9rem
     above it and 1.3rem below, the Program tab's own setting for a rule
     that opens what follows (.afterward). */
  margin: 0 0 1.3rem;
  padding: 0 0 1.9rem;
  border-bottom: 1px solid var(--line);
}

.bdays-h {
  margin: 0 0 0.7rem;
  text-transform: uppercase;
  color: var(--accent);
}

/* THE SECTION'S OWN ROSETTE (Round 295). The ward's diamond -- the same
   mark as the fleuron, as the printed program's ornament and as today's
   name below -- set once on the heading. ONCE, never one between every
   pair of names: a row of diamonds down the block would fight the one
   marker that means something, which is the one saying whose birthday is
   today. currentColor, so it is the accent here and follows the heading
   wherever the palette takes it. */
.bdays-h::before {
  content: '';
  display: inline-block;
  width: 0.3rem;
  height: 0.3rem;
  margin-right: 0.5rem;
  background: currentColor;
  transform: rotate(45deg);
  vertical-align: 0.12em;
}

/* A little more air between days than there was (0.45rem), now that each
   day carries a line of display-face names rather than a line of text. */
.bday-day {
  margin: 0 0 0.75rem;
}

/* Or the last day's own margin sits inside the block's padding and the rule
   under the birthdays lands lower than the rule under a notice. */
.bday-day:last-child {
  margin-bottom: 0;
}

/* THE DAY IN THE PAGE'S LABEL VOICE (Round 212). It was 13px plain grey,
   the footnote voice, under a comment promising the small-caps voice; every
   notice's date beneath it is a label. The subordinate rank (the list
   above) in soft ink, exactly as the leaders' own Birthdays tab sets its
   days (Round 71), today's in the accent; the names at the body's own size
   (the 1.05rem was a rounding off it). */
.bday-when {
  display: block;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.bday-when.is-today {
  color: var(--accent);
}

/* A hair of air under the day, so the names read as their own line rather
   than as the tail of the label above them. */
.bday-names {
  display: block;
  margin-top: 0.15rem;
}

/* A name moves to the next line whole -- a day with three names used to
   break inside one. inline-block, not nowrap, so a name wider than the
   column still wraps within it rather than pushing the page sideways.

   THE NAMES ARE THE CELEBRATION, so they are set in the page's own
   display face (Round 295, Derek: make it more visually engaging, we
   would really like to celebrate these people -- and, in the same
   breath, do not use a font that isn't already used on this page). Ward
   Caslon IS that face: the masthead, the program's values, the officers'
   names and the hymn titles are all set in it, so a week of birthdays
   now reads as a roll of names rather than as a line of data, and not
   one byte more is downloaded to do it.

   ONE WEIGHT, EVER (Round 20): caslon.woff2 is a single 400 face, so any
   bold here would be SYNTHESISED, and a display cut set bold loses the
   modelling that is the whole reason to use it. That is why
   .bday-name.is-today drops the 650 it carried on the book face; the
   accent and the ward's diamond were always the two marks doing the
   work. */
.bday-name {
  display: inline-block;
  font-family: var(--display);
  /* 1.45rem, and it is THREE points above where Round 331 left it, not
     two. Round 332 raised every Caslon text on the public page two points
     at once (1.25 -> 1.4 here), and Derek asked for the birthday names
     "just one more point larger" on top of that in the same breath -- so
     this one element carries the general step plus one of its own.

     Still under the tab's own headline (.ann-title, 1.65rem), which is
     Round 295's stated reason for holding this element below the
     announcements it sits beneath, and the reason it is checked whenever
     either number moves.

     AND THE RULE THAT WINS IS THE OTHER ONE: this size is restated as
     calc(1.45rem * var(--ds)) at the foot of the file, which outranks
     this on every screen. Change both, or nothing moves (Round 303). */
  font-size: 1.45rem;
  line-height: 1.35;
}

/* The bishopric's own Birthdays tab: the tracker's table, three columns and
   a Noted button. A row whose day has come and gone without Noted is picked
   out in the attention colour; one already noted fades. */
/* THE BIRTHDAYS TAB (Round 69): a compact ruled list, grouped by day. The
   day is a small heading with a hairline under it; each person is one line
   -- name, the number to text, "Reached out" at the end. Today's heading in
   the accent, a day still waiting from before today in the warning ink. */
.bdlist {
  max-width: 40rem;
}
.bdday {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 1.1rem 0 0.1rem;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid var(--line);
}
.bdday:first-child {
  margin-top: 0.3rem;
}
/* The day is a small-caps label in the subordinate rank (Round 71, Derek:
   the day and the names read the same); the names are the content, at
   reading weight. */
.bdday-name {
  color: var(--ink-soft);
}
.bdday-date {
  font-size: 0.8125rem;
  color: var(--ink-soft);
}
.bdday.is-today .bdday-name {
  color: var(--accent);
}
.bdday.is-late .bdday-date {
  color: var(--warn-ink, #9a5b2b);
}
.bdrow {
  display: grid;
  grid-template-columns: minmax(9rem, 1fr) auto auto;
  gap: 1rem;
  align-items: center;
  padding: 0.38rem 0.2rem;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
}
.bdrow-name {
  font-weight: 500;
}
.bdrow-sms {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.bdrow-act {
  justify-self: end;
}

/* --------------------------------------------------------- the hymn tool */

/* The Conducting sheet tab's SECOND badge (Round 49): music blanks, in the
   music leader's own colour, beside the usual one for everything else. Same
   shape and sizes as .tab-count; only the colour differs, so the two read as
   two counts of one kind. Specificity (0,3,0) sits above the (0,2,0) rules
   that paint the ordinary badge. */
.tabbtn .tab-count.music {
  background: var(--music);
  color: var(--paper);
}

.tabbtn.active .tab-count.music {
  background: var(--music);
  color: var(--paper);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--music) 18%, transparent);
}

/* Music / Meeting details — the two halves of one sheet, as a neutral
   segmented control above the form (the Round 5 rule: the accent is for the
   one primary action, and a view switch is not an action). */
/* THE TWO HALVES OF THE SHEET (Round 66, Derek: no pill, no dark green,
   bigger, no counts inside, classy). Two small-caps words in the house
   typography, set apart by the printer's diamond the tab bar uses, the
   open one in ink with the same inset rule the main tabs carry, the other
   in soft ink. Centred in the form's column. Bigger than the tab bar's
   words on purpose: this is the one control on the sheet that changes what
   the whole page below it is. */
/* ONE BOX, TWO HALVES (Round 68, Derek). A segmented control in the house
   idiom: the same hairline, corner and card the form's own selects use,
   with a 3px inset so the chosen half's fill sits inside the frame rather
   than butting against it. Type at the tab bar's size and case -- this is
   a control, not a heading. The chosen half is the library tabs' mark a
   touch stronger: the accent on a soft sage tint (about 6.5:1), which says
   "this one" without the solid green Derek retired; the other half is
   soft ink on nothing, and warms on hover. Centred in the form's column by
   the sticky wrap (Rounds 63 and 66). */
.sheettabs {
  display: inline-flex;
  align-items: stretch;
  gap: 3px;
  margin: 0;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: var(--card);
}

.sheettab {
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0.012em;
  text-transform: none;
  padding: 0.42rem 1.05rem;
  border: none;
  border-radius: calc(var(--r-item) - 3px);
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}

.sheettab:hover,
.sheettab:focus-visible {
  color: var(--ink);
  background: color-mix(in srgb, var(--ink-soft) 8%, transparent);
}

.sheettab.on,
.sheettab.on:hover {
  /* A shade deeper than the accent: the accent alone sits at about 4.8:1 on
     the tint, this at about 5.4. */
  color: color-mix(in srgb, var(--accent) 80%, var(--ink));
  font-weight: 650;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

/* A hymn in the running order, seen from Meeting details: not theirs to
   type into, so it is one grey line with only the order controls. */
.lockhymn {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.6rem;
  border: 1px dashed var(--line);
  border-radius: var(--r-item);
  color: var(--ink-soft);
  font-style: italic;
}

.lockhymn-what {
  flex: 1;
}

/* "No new hymn on this Sunday's program" — a notice, not an error. Ruled and
   tinted in the music colour so it is plainly the music tool speaking. */
.notice-nonew {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin: 0 0 1rem;
  padding: 0.6rem 0.9rem;
  border-left: 3px solid var(--music);
  background: color-mix(in srgb, var(--music) 8%, transparent);
  border-radius: var(--r-item);
  font-size: 0.9375rem;
}

.notice-nonew p {
  margin: 0;
  flex: 1;
}

/* "Suggest hymns" and its one-line explanation, above the hymn boxes. */
/* The buttons on one line, and the sentence that explains the first of them
   DIRECTLY BENEATH IT. The hint used to be a flex item in the same row, so it
   wrapped into the space beside "Plan the coming Sundays" and read as that
   button's description (Derek). A column puts it where it belongs and needs no
   rule to say so. */
.suggestrow {
  margin-bottom: 0.9rem;
}

.suggestbtns {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.6rem;
}

/* THE HYMN BOX'S UNDO -- LAST IN THE BUTTON ROW (Round 290b, Derek). It
   hung off the card's top-right corner from Round 171 until Round 289 made
   this box a fold and the chevron claimed that corner; the two overlapped.
   In the flow it is an ordinary item of the row, and a size down from the
   three beside it -- an undo arrow and the one word, a quiet text action
   rather than a pill, fainter still while there is nothing to undo.
   (#sec-hymns { position: relative } went with the absolute placement: it
   was there only to hang this off, and nothing else in the card is
   positioned.) */
.hymnundo {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--ink-soft);
  background: none;
  border: none;
  padding: 0.2rem 0.35rem;
  border-radius: var(--r-detail);
  cursor: pointer;
  opacity: 0.85;
}

.hymnundo:hover,
.hymnundo:focus-visible {
  color: var(--ink);
  background: color-mix(in srgb, var(--ink-soft) 10%, transparent);
  opacity: 1;
}

.hymnundo.is-idle {
  opacity: 0.45;
}

.hymnundo .undoicon {
  width: 1em;
  height: 1em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* .suggestrow .suggesthint is gone with the sentence it held (Round 283,
   Derek). Its emitter was deleted, so the rule went with it rather than
   staying behind describing something nothing wears. */

/* The library: slot tabs, a filter row, and one dense row per hymn. */
.hymnlib-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.7rem;
}

.hymnlib-tab {
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 650;
  padding: 0.25rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}

.hymnlib-tab.on {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

.hymnlib-tab.season {
  border-style: dashed;
}

.hymnlib-tab.is-new {
  border-color: var(--music);
  color: var(--music);
}

.hymnlib-tab.is-new.on {
  background: color-mix(in srgb, var(--music) 10%, transparent);
}

/* The library as a dialog (Derek): always large, whatever the page behind
   it is doing with the preview. Wider than any other dialog and the list
   takes most of the height, so four hundred titles are read in a page, not
   a slot. */
.modalcard.library {
  max-width: 64rem;
}

.modalcard.library .hymnlib {
  max-height: 58vh;
}

/* WHAT A FUTURE SUNDAY STILL NEEDS, on its date chip (Round 50). Much
   smaller than the tab bar's badges — these are a glance ahead, not the
   thing you are working on — in the same two colours: meeting blanks in
   --notify, music blanks in --music. A complete sheet gets a green check in
   a circle instead. */
.datechip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.chipcounts {
  display: inline-flex;
  align-items: center;
  /* Round 178 (Derek): more air between the date and the first icon, less
     between the page's count and the note -- the two pairs read as one
     group standing off the date. */
  gap: 0.28rem;
  margin-left: 0.3rem;
}

/* THE COUNTS ARE ICONS (Derek, Round 159; no colour since Round 58). A small
   muted page for meeting blanks and a small muted eighth note for music
   blanks -- the tab badge's own shapes, shrunk and greyed -- each with its
   count beside it in ordinary ink, so the kind of blank is obvious without
   the chip drawing the eye. The icons are painted in one OPAQUE muted ink
   (a mix toward the paper, not toward transparent): the note is three
   overlapping shapes, and a translucent fill would show the overlaps. */
.chipcount {
  display: inline-flex;
  align-items: center;
  gap: 0.16rem;
  font-size: 0.6rem;
  font-weight: 700;
  line-height: 1;
  color: var(--ink);
}

.chipcount svg {
  display: block;
  flex: none;
}

.chipcount .chip-page {
  /* 0.72 x 0.9rem (Round 188; 0.66 x 0.825 in Round 186): three hollow lines
     2.2 units tall need 0.48px per unit to draw at about a pixel, with 2px
     of page between them. */
  width: 0.72rem;
  height: 0.9rem;
}

.chipcount .chip-note {
  width: 0.9rem;
  height: 0.85rem;
}

.chipcount .chip-page .pg-body,
.chipcount .chip-note path {
  fill: color-mix(in srgb, var(--ink-soft) 55%, var(--paper));
}

.chipcount .chip-page .pg-fold {
  fill: color-mix(in srgb, var(--ink-soft) 28%, var(--paper));
}

/* A little more of an occasion (Round 173, Derek: "not quite as exciting as it
   should be"): a 2px ring, a fuller green fill, a heavier and larger check,
   and a shade larger all round. One mark, shared by the date chips, the tab
   bar and the sheet's tabs. */
.chipdone {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.2rem;
  height: 1.2rem;
  border-radius: 50%;
  font-size: 0.78rem;
  font-weight: 800;
  line-height: 1;
  background: color-mix(in srgb, var(--success-ink, var(--accent)) 18%, var(--success-bg, var(--paper)));
  color: var(--success-ink, var(--accent));
  border: 2px solid var(--success-ink, var(--accent));
  box-sizing: border-box;
}

/* The open chip is filled with the accent, so its badges sit on the paper
   colour instead: the numbers in paper, the icons a muted paper. */
.datechip.active .chipcount {
  color: var(--paper);
}

.datechip.active .chipcount .chip-page .pg-body,
.datechip.active .chipcount .chip-note path {
  fill: color-mix(in srgb, var(--paper) 72%, var(--accent));
}

.datechip.active .chipcount .chip-page .pg-fold {
  fill: color-mix(in srgb, var(--paper) 45%, var(--accent));
}

.datechip.active .chipdone {
  background: var(--paper);
  color: var(--accent);
  border-color: var(--paper);
}

.hymnlib-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  margin-bottom: 0.7rem;
}

.hymnlib-filter input[type='search'] {
  flex: 1;
  min-width: 11rem;
  width: auto;
}

.hymnlib-filter select {
  width: auto;
}

.hymnlib {
  max-height: 24rem;
  overflow-y: auto;
  border-top: 1px solid var(--line);
}

.hymnrow {
  display: grid;
  grid-template-columns: 3.2rem minmax(8rem, 1fr) auto auto;
  gap: 0.4rem 0.6rem;
  align-items: center;
  padding: 0.38rem 0.3rem;
  border-bottom: 1px solid var(--line);
}

.hymnrow-num {
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

.hymnrow-title {
  min-width: 0;
}

.hymnrow-title .chip {
  margin-left: 0.4rem;
}

.hymnrow-sung {
  font-size: 0.75rem;
  color: var(--ink-soft);
  white-space: nowrap;
}

.hymnrow-sung.recent {
  color: var(--tenure, #c2643f);
}

.hymnrow.is-aside {
  opacity: 0.55;
}

.hymnrow.is-new .hymnrow-num {
  color: var(--music);
  font-weight: 650;
}

.hymnrow-acts {
  display: flex;
  gap: 0.35rem;
  white-space: nowrap;
}

/* A new hymn nobody has placed yet asks where it fits. */
.hymnrow-place {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

/* The ward members willing to do a musical number, on the Music tab. */
/* THE VOLUNTEERS BOX FOLDS (Round 197, Derek): the card's own h2 laid out
   as a row -- the title, the counts while it is closed, and a boxed chevron
   at the right that expands it; open, the box reads "Collapse" with the
   chevron turned. The heading keeps its type; only its layout changes.
   The body carries the heading's old margin, so a closed card is just its
   heading row. */
.acard > h2.foldhead {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0;
}

.foldcard > .foldbody {
  margin-top: 0.95rem;
}

/* A NUMBERED SECTION THAT FOLDS (Round 289): Hymns, Announcements, Theme
   and Items of business. The heading keeps its number and its type -- only
   its layout changes -- so a folded section still reads as one of the
   column's numbered boxes rather than as a different kind of object. */
body.leader .sheetform .secfold > h2.foldhead {
  gap: 0.45rem;
}

/* The number sits against its title, not a flex gap away: .secnum's own
   margin is what spaces it in every unfolded heading, and here the gap
   above does that job. */
.secfold > h2.foldhead > .secnum {
  margin-right: 0;
}

/* The thin column's heading stands 0.6rem off its body (Round 220's
   density), not the 0.95rem a card uses elsewhere -- so the fold's body
   carries that instead. (0,4,0) to beat .foldcard > .foldbody above. */
body.leader .sheetform .secfold > .foldbody {
  margin-top: 0.6rem;
}

/* These four carry no word beside the chevron, so the button has nothing
   to make room for and stays square when open. */
.secfold .foldbtn.open {
  padding: 0 0.4rem;
}

/* THE ANNOUNCEMENTS TAB'S COMPOSER FOLDS TOO (Round 296, Derek). Its card
   sits inside .sheetform -- announceView gives its own container that
   class for the density rules -- so `body.leader .sheetform .acard > h2`
   (0,3,1) beats the shared `.acard > h2.foldhead` (0,2,1) and would leave
   0.6rem of dead space under the heading of a CLOSED card. Written at
   (0,3,2) to answer it, the same specificity trap Rounds 125, 243, 251
   and 288 each had to write long.

   The body then carries that 0.6rem instead, so an open card sits exactly
   where it always did and a closed one is its heading row and nothing
   else. Its chevron needs no word beside it, so it stays square. */
body.leader .sheetform .acard > h2.foldhead {
  margin: 0;
}

body.leader .sheetform .composer > .foldbody {
  margin-top: 0.6rem;
}

/* The heading row itself is the control, drawn as the accordion above it
   draws its summary: the words at their own heading size, a bare caret at
   the right, no box. `font: inherit` takes the h2's type, so the button is
   invisible as a button and the row simply reads as the heading it is. */
.annfold {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  background: none;
  border: 0;
  cursor: pointer;
}

/* Down while it is shut, up while it is open (Derek). The glyph is drawn
   pointing down, so closed needs no turn and open is a half turn -- the
   same one rotation details.acc and .pubfold both use. */
.annfold .foldchev {
  flex: none;
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--ink-soft);
  transition: transform 0.15s;
}

.annfold[aria-expanded='true'] .foldchev {
  transform: rotate(180deg);
}

.annfold:hover .foldchev,
.annfold:focus-visible .foldchev {
  color: var(--accent);
}

.foldtitle {
  flex: 1 1 auto;
}

.foldmeta {
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--ink-soft);
  white-space: nowrap;
}

.foldbtn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex: none;
  min-height: 1.9rem;
  padding: 0 0.4rem;
  border: 1px solid var(--line);
  border-radius: var(--r-detail);
  background: var(--paper);
  color: var(--ink-soft);
  font-family: inherit;
  font-size: 0.7rem;
  font-weight: 650;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  cursor: pointer;
}

.foldbtn.open {
  padding: 0 0.5rem 0 0.65rem;
}

.foldbtn:hover,
.foldbtn:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}

.foldbtn .foldchev {
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.15s;
}

.foldbtn.open .foldchev {
  transform: rotate(180deg);
}

/* YOUR RECENT CHANGES (Round 201): the Clerks tab's folded record of the
   signed-in person's own log lines -- a day heading in the subordinate
   small-caps rank, then ruled rows of time · what · "Take me there". */
.changes-day {
  margin: 1rem 0 0.3rem;
  color: var(--ink-soft);
}

.changes-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.changes-row {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.4rem 0;
  border-top: 1px solid var(--line);
  font-size: 0.9375rem;
}

.changes-when {
  flex: none;
  min-width: 4.6rem;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  font-size: 0.875rem;
}

.changes-what {
  flex: 1 1 auto;
  min-width: 0;
}

.changes-go {
  flex: none;
  white-space: nowrap;
}

/* The Sunday a sheet line is about, ahead of its words; and one save's
   several lines, listed under one time. */
.changes-ref {
  display: block;
  font-size: 0.8125rem;
  color: var(--ink-soft);
  font-style: italic;
}

.changes-lines {
  margin: 0.15rem 0 0;
  padding-left: 1.1rem;
}

.changes-lines li {
  margin: 0.1rem 0;
}

/* A table row rings by its cells' background alone: a box-shadow on a <tr>
   is not painted reliably, so the tracker row "Take me there" lands on
   flashes this way. */
.flash-tr > td {
  animation: trflash 2s ease-in-out;
}

@keyframes trflash {
  0% { background-color: transparent; }
  20% { background-color: color-mix(in srgb, var(--accent) 16%, transparent); }
  100% { background-color: transparent; }
}

.volunteers {
  columns: 2;
  column-gap: 1.4rem;
  margin: 0.4rem 0 0;
  padding: 0;
  list-style: none;
}

.volunteers li {
  break-inside: avoid;
  padding: 0.15rem 0;
}

.volunteers .chip {
  margin-left: 0.4rem;
}

/* The "Another Sunday" picker: one row per Sunday, one cell per slot. */
.sundaygrid {
  display: grid;
  grid-template-columns: 6rem repeat(4, 1fr);
  gap: 0.3rem;
  align-items: center;
  font-size: 0.875rem;
}

.sundaygrid-h {
  text-transform: uppercase;
  color: var(--ink-soft);
}

.sundayslot {
  font: inherit;
  font-size: 0.8125rem;
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: none;
  color: var(--accent);
  cursor: pointer;
  text-align: left;
}

.sundayslot:disabled {
  color: var(--ink-soft);
  cursor: default;
  opacity: 0.6;
}

.sundayslot.is-unfit {
  visibility: hidden;
}

.program-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* THE THIN LINE MOVES TO THE END OF THE PROGRAM ITSELF (Round 313, Derek:
   "insert one of those [thin lines] after the benediction ... get rid of
   the line you currently have right above 'after sacrament meeting'
   entirely"). It was living on .afterward's own border-top, which put it
   right above a heading that now sits 9.5rem below the program (Round
   311) -- a line stranded that far from either thing it could be read as
   separating. Here it marks the one boundary that is actually there: the
   sacrament meeting itself has ended. var(--line), not var(--dots) -- this
   is the SAME quiet hairline .officers wears above and below itself, not
   the warmer one Round 306 gave the two after-sections. The last .item's
   own 1.2rem margin-bottom is what holds it clear of Benediction; nothing
   more is added. */
.program-list:not(:empty) {
  border-bottom: 1px solid var(--line);
}

.item {
  margin-bottom: 1.2rem;
}

/* THE SUB-LINE BELONGS TO THE ROW ABOVE IT (Round 295, Derek: move the
   hymn title up closer to the dotted line). A hymn's title is the name of
   the number printed opposite the label -- one fact on two lines, not two
   facts -- so it has to sit under its own row rather than float between
   that row and the next.

   The gap was never really 0.25rem: it was that margin PLUS the half-
   leading under a 1.2rem/1.35 value and above a 1.15rem/1.4 title, about
   11px of air in a list whose rows are 19.2px apart. The margin goes and
   the title's own leading comes in a notch, which is what closes it. The
   1.2rem below every .item still separates one step of the meeting from
   the next, so the pairing reads without a rule or a colour. */
.item-sub {
  margin: 0 0 0;
  font-size: 1.25rem;
  line-height: 1.28;
}

/* THE ACCENT, like every other label in the program list. The reader learns
   "green small caps = a step in the meeting" from the eight dotted rows; the
   two steps that are NOT rows — the sacrament and ward business, plus any
   "other" item — used to arrive in --ink-soft, the officers block's
   subordinate colour, which made the meeting's central act the palest line
   on the page. What now marks it as a pause is SPACE, not paleness: see the
   block's margin below. */
.ceremony {
  text-align: center;
  color: var(--accent);
  margin: 2rem 0;
}

/* An "other" program item — a baby blessing, a presentation. The margin lives
   on the block rather than on the title so the title and its detail read as
   one unit; a margin on the title alone would push the detail away from the
   thing it belongs to.

   2rem, against .item's 1.2rem. Margins collapse, so the old 1.5rem gave only
   4.8px more air than an ordinary row — too little to read as a break and too
   much to be invisible. THIS is the rule that governs on the live page: every
   ceremony ships inside .ceremony-block, whose own reset below zeroes the
   title's margin. */
.ceremony-block {
  text-align: center;
  /* 1.5rem, against the sacrament's 2rem below. Collapses with the
     neighbouring row's own 1.2rem, so the larger of the two wins. */
  margin: 1.5rem 0;
}

.ceremony-block.is-sacrament {
  margin: 2rem 0;
}

.ceremony-block .ceremony {
  margin: 0;
}

/* ------------------------------------------------------------ announcements */

/* The two tabs are one document, so the announcements are set like the program
   — ruled off from each other rather than boxed. A stack of bordered cards
   read as a different website from the page behind the Program tab, which has
   no boxes anywhere: it sets its rows with hairlines and small-caps labels. */
/* EACH NOTICE IS A PRINTER'S TRADE CARD (Round 298, Derek: "apply the
   boxes that you use in the groups to each announcement").

   THIS REVERSES THE RULE THAT USED TO LIVE HERE, deliberately and at his
   word: notices were ruled off from each other rather than boxed, because
   a stack of bordered cards read as a different website from the Program
   tab behind them, which has no boxes anywhere. What makes it work now is
   that the card is not a web card -- it is the SAME object the Groups tab
   already uses (Round 231): square corners, the ward's 5px accent rule
   across the head, and a double edge drawn as two shadow spreads, which
   is the 19th-century trade-card border. So the two tabs that carry
   individual items now carry them the same way, and the Program tab
   stays the unboxed order of service it has always been.

   The values are .gcard's plus .groups-panel .gcard's, restated rather
   than shared: the two are the same treatment, but a notice is not a
   group card and giving them one selector would tie the next change to
   one of them to the other. */
.ann {
  background: var(--card-bright);
  border: 1px solid var(--line);
  border-top: 5px solid var(--accent);
  border-radius: 0;
  box-shadow: 0 0 0 3px var(--card-bright), 0 0 0 4px var(--dots);
  padding: 0.95rem 1.05rem 1.1rem;
  /* THE MESSAGE ITSELF, SET ON THE CARD (Round 326, Derek: the description
     "could be slightly smaller and not quite spaced out so much with the line
     spacing"). 1rem/1.5 against the page body's 1.0625rem/1.6.

     ON THE CARD RATHER THAN ON THE PARAGRAPH, and that is the point: every
     other child here declares its own size -- the title, the date, the place,
     the calendar footnote -- so this reaches EXACTLY the message and nothing
     else, and the lists inside it (Round 210) cannot drift away from the
     paragraphs around them, which a rule on `> p` alone would have let happen.

     THE FACE IS RIGHT AND IS DELIBERATELY UNCHANGED. Derek asked whether it
     was: it is the book face, which is this page's reading face, and the rule
     the whole design now follows is that Caslon is a NAME or a TITLE and the
     book face is everything read in sentences (Rounds 318, 325).

     0.9375rem (Round 328, Derek: "I want the description to be smaller text").
     This is the "C" of the three that were rendered side by side in Round 326
     -- he took B, 1rem, then asked for smaller. It puts the message level with
     the date line, which was the argument against C at the time; with the
     place now at 0.8125rem the card still descends, and he has narrowed every
     line on it in turn, so the flattening he was warned about is the compact
     card he is asking for.

     IT IS NOW BELOW THE PAGE'S OWN BODY SIZE, which Round 326 declined to do:
     a phone reads at 16px by Round 299's deliberate choice, and this is 15px
     everywhere. That is his call twice over. 15px is an ordinary reading size
     and needs no defending; below about 14 it would, on a page read in a
     chapel by people who are not all twenty. */
  font-size: 0.9375rem;
  line-height: 1.5;
  /* The double edge reaches 4px past the border, so the cards need more
     air between them than a hairline rule did: 1.35rem clear, which is
     the gap .groups-panel .gcards puts between two group cards. */
  margin: 0 0 1.35rem;
}

/* A card keeps every one of its four edges, so the last notice no longer
   drops anything -- it only stands a little further off the calendar
   offer's own rule, whose 1.9rem margin then collapses to that 1.9rem.
   (Round 218 wrote last-of-type rather than last-child because the
   calendar offer follows the last notice as a div; still true.) */
.ann:last-of-type {
  margin-bottom: 1.9rem;
}


/* The PUBLIC page's announcement heading. Do not delete this as "unused" —
   it is emitted by worker.js, not by app.js, so a search of the app's
   JavaScript will not find it.

   THE DISPLAY FACE, AT THE GROUP CARD'S OWN SIZE AND WEIGHT (Round 317, the
   font pass Derek asked for on the Bulletin and Groups tabs). It was the
   book face at 650 -- the one headline on the ward's page not set in
   Caslon, sitting in the same trade card the group name wears Caslon in
   (Round 298 made the two cards one object). A speaker, a hymn title, an
   officer, a birthday, a missionary and a group's name are all the display
   face at 400; a bold book-face title beside them was Round 286's "font
   that doesn't match anything on the page" in another room. 1.35rem is
   .gcard-name's own number, so the two card headlines are one rank. ONE
   WEIGHT, EVER (Round 20): caslon.woff2 is a single 400 face and a bold
   here would be synthesised. */
/* 650 to match .groups-panel .gcard-name (Round 325, Derek: "the same font,
   color, size, boldness in the title of each announcement card as you have in
   the title of the group cards"). The same synthesised Caslon bold Round 324
   measured and took deliberately -- the reasoning is written out there. */
.ann-title {
  margin: 0;
  font-family: var(--display);
  /* 1.65rem with .gcard-name (Round 332); the two are one rank, and the --ds
     rule at the foot of this file is the one that wins. */
  font-size: 1.65rem;
  /* 1.2, .gcard-name's own leading: with everything else matched, this was
     the last thing that made a two-line title on one card sit differently
     from a two-line name on the other. */
  line-height: 1.2;
  font-weight: 650;
  text-wrap: balance;
}

/* Set exactly like the program's own labels — small caps, letterspaced, in the
   accent — so the eye reads the two tabs as the same printed thing. */
/* THE SAME THREE LINES AS A GROUP CARD (Round 325, Derek: "the announcement
   cards are so difficult to read because there are so many different fonts
   and stuff ... use the exact same font, color, size, and boldness you use on
   the second line of the group cards").
   Round 298 made the two cards one object; this makes what is printed inside
   them one object too. It LEFT the shared rank-1 small-caps list to do it, the
   way .ceremony did in Round 231: it is no longer a label, it is the card's
   second line.

   IT IS .gcard-when's RULE WITH ONE DELIBERATE DIFFERENCE (Round 329, Derek:
   "just on the announcements page, I want you to change the date and time text
   color to the same darker color used in the title"). Face, size, weight,
   slope and leading still match the group card line for line; the COLOUR does
   not, and that is on purpose rather than drift -- a group's when-and-where is
   the line people scan to decide whether they can come, where a notice's date
   sits under a title that is already carrying the announcement.
   var(--ink) is exactly what .ann-title inherits, so the two match by reading
   the same token rather than by two copies of a value. */
.ann-when {
  margin: 0.45rem 0 0;
  font-size: 0.9375rem;
  font-style: italic;
  line-height: 1.5;
  color: var(--ink);
  /* The parts are bound with no-break spaces (Round 212); this lets a bound
     run wider than the column -- "September 24, 2027 ·" at a large text
     size on a narrow phone -- break rather than overflow, and does nothing
     at ordinary sizes. */
  overflow-wrap: anywhere;
}

/* THE SECOND LINE AGAIN, TWO STEPS QUIETER (Round 325: "the exact same as the
   second line, just faded out"; Round 327: "I also want the location to be
   smaller text"). Same family, weight, slope and leading as .ann-when -- the
   colour and now the size are what step back.

   THE FADE IS THE SOFT INK, NOT A PALER GREEN, and that is a measurement
   rather than a preference: the accent is 4.71:1 on this card, a fifth of a
   step above the 4.5:1 floor, so there is no room to fade it and keep the
   PLACE of an event readable -- and Round 51 had to undo thirteen text
   colours that had been faded toward transparent for exactly this reason.
   The soft ink is the page's own quieter voice, and a warm grey reads as a
   step back from a saturated green while measuring 6.0:1 at any of these
   sizes.

   0.8125rem, TWO steps down from the date (Round 328, Derek: "I want the
   location to be even smaller text"). Round 327 stopped at 0.875rem on the
   argument that this size puts the PLACE of an event level with .ann-cal, the
   "Add to calendar" footnote, and that where something happens is not a
   footnote. He asked again, so the recommendation is overridden and the
   argument is left standing here rather than quietly deleted -- it is the
   reason not to go further, and 0.75rem is the next rank below this.
   The two are still told apart, just not by size: this line is ITALIC and
   sits directly under the date, where the calendar row is upright, sits
   below the message, and carries a control set in the accent and underlined.
   (Its own text measures the same soft ink as this -- checked -- so the
   accent is on the button inside it, not on the row.) */
.ann-where {
  margin: 0.15rem 0 0;
  font-size: 0.8125rem;
  font-style: italic;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* THE BODY PARAGRAPHS ONLY.
   This was `.ann p`, which at (0,1,1) outranked .ann-when (0.45rem),
   .ann-where (0.15rem) and .ann-cal (0.5rem) — all three are <p> inside
   .ann, so all three lost regardless of source order and every gap in a
   notice rendered at the same 8.8px. The 0.15rem that binds the date line to
   the place, so the two read as one unit, never rendered at all; nor did the
   tighter gap under the title. A notice was five evenly-spaced lines with no
   internal grouping.
   `> p:not([class])` is exactly the detail paragraphs: paragraphs() in
   worker.js and splitParagraphs() in app.js both emit classless <p> as direct
   children of .ann. */
.ann > p:not([class]) {
  margin: 0.55rem 0 0;
}

/* LISTS IN A NOTICE (Round 210, Derek: "professionally and cleanly
   formatted … not bulky"): a modest indent, the marker in the soft ink a
   size down, items a hair apart. paragraphs() in worker.js and richText()
   in app.js emit them as direct children of .ann (and of .afterward). */
.ann ul,
.ann ol,
.afterward ul,
.afterward ol {
  margin: 0.45rem 0 0;
  padding-left: 1.35rem;
}

.ann ul,
.afterward ul {
  list-style: disc;
}

.ann ol,
.afterward ol {
  list-style: decimal;
}

.ann li,
.afterward li {
  margin: 0.15rem 0;
  padding-left: 0.1rem;
}

.ann li::marker,
.afterward li::marker {
  color: var(--ink-soft);
  font-size: 0.9em;
}

/* NESTING AND INDENT (Round 230, Derek asked for indent right and left).
   A list line indented under the item above it hangs inside that item, so
   it needs only the gap and a marker of its own; the marker changes with
   the depth the way a word processor's does, which is what tells two
   levels apart at a glance. .ann-in1..3 is the other case -- a plain line,
   or a list with nothing above it to hang from -- and it is a MARGIN, not
   padding, so a list keeps its own marker column. Three steps is as far as
   the buttons will go. */
.ann li > ul,
.ann li > ol,
.afterward li > ul,
.afterward li > ol {
  margin: 0.15rem 0;
  padding-left: 1.15rem;
}

.ann ul ul,
.afterward ul ul {
  list-style: circle;
}

.ann ul ul ul,
.afterward ul ul ul {
  list-style: square;
}

.ann ol ol,
.afterward ol ol {
  list-style: lower-alpha;
}

.ann ol ol ol,
.afterward ol ol ol {
  list-style: lower-roman;
}

.ann .ann-in1,
.afterward .ann-in1 {
  margin-left: 1.35rem;
}

.ann .ann-in2,
.afterward .ann-in2 {
  margin-left: 2.7rem;
}

.ann .ann-in3,
.afterward .ann-in3 {
  margin-left: 4.05rem;
}

/* THE GAPS INSIDE A NOTICE RANK (Round 212). The message's first block
   used to sit 0.55rem under the heading -- the very gap between two of its
   own paragraphs -- so nothing said where the heading (title, when, where)
   stopped and the message began. Now it stands off by 0.9rem, the flyer
   strip's own distance; the paragraphs keep their 0.55rem; the date line
   keeps its 0.15rem hold on the place (Round 43). Scoped under .ann so the
   sibling selectors cannot reach the leader app's request cards, which
   emit .ann-title too. */
.ann > .ann-title + p:not([class]),
.ann > .ann-when + p:not([class]),
.ann > .ann-where + p:not([class]),
.ann > .ann-title + ul,
.ann > .ann-when + ul,
.ann > .ann-where + ul,
.ann > .ann-title + ol,
.ann > .ann-when + ol,
.ann > .ann-where + ol,
.ann > .ann-title + [class^='ann-in'],
.ann > .ann-when + [class^='ann-in'],
.ann > .ann-where + [class^='ann-in'] {
  margin-top: 0.9rem;
}

.empty {
  text-align: center;
  color: var(--ink-soft);
  font-style: italic;
  padding: 2.2rem 0;
}

/* ----------------------------------------------------------------- footer */

/* THE FOOT OF THE PAGE IS PADDED WELL CLEAR OF THE SCREEN'S OWN EDGE
   (Round 304, Derek, with a screenshot: a floating scroll-to-top control
   from the device or browser he was viewing on sat right over "Light /
   Dark" at the very bottom of the page). That control is chrome this page
   does not draw and cannot move -- the only thing this page can do is give
   the Appearance row more room below it, so it lands higher up the screen
   once you have scrolled all the way down. 2.6rem to 4.5rem: the row was
   41.6px clear of the document's own bottom edge, now 72px.

   THE HAIRLINE IS var(--dots), NOT var(--line) (Round 306, Derek, circling
   all three of this area's rules: "it looks like you didn't ever bold these
   lines"). --dots is the same warmer, more visible rule the ceremony
   ornament and every dotted leader already use. It was 2px from Round 306
   to Round 316, kept bold at Derek's word twice (Rounds 313, 314); Round 317
   is his word the other way ("make the bold line at the bottom of the
   screen thin"), so it is 1px in the same colour -- the Round 311 reading
   of the same phrase. This is the ONE hairline shared by every public tab,
   not only Program. */
.colophon {
  border-top: 1px solid var(--dots);
  padding: 1.5rem 1.25rem 4.5rem;
  text-align: center;
}

/* THE DISCLAIMER (Round 215, Derek): lower, smaller, and on one line. The
   26rem cap was what wrapped it -- narrower than the page's own column,
   so the sentence could never sit on one row even on a computer. At
   0.75rem it is about 470px, inside the 40rem column's 600px; on a phone
   (335px) it must wrap, and text-wrap: balance makes that two even lines
   rather than a line and a stub. The wording is fixed by the content
   rules and is not touched here. */
.disclaimer {
  font-size: 0.75rem;
  color: var(--ink-soft);
  max-width: none;
  margin: 2rem auto 0;
  text-wrap: balance;
}

.updated {
  font-size: 0.875rem;
  color: var(--ink-soft);
  margin: 0.6rem 0 0;
}

/* APPEARANCE, in the colophon (Round 182): three quiet words -- Follow my
   device · Light · Dark -- the chosen one in the accent and underlined.
   Three settings, not two, so the first person to touch it can always get
   back to following the device (Round 10's condition for ever having a
   switch). Quiet on purpose: it is a preference, set once. */
/* LAST THING ON THE PAGE, under the disclaimer, and a size down from it
   (Round 292, Derek: "smaller text and further down towards the bottom").
   0.75rem is the disclaimer's own size, so the two closing lines sit level
   rather than one shouting over the other; the buttons take it by
   inheriting the paragraph's font. The footer's 2.6rem of bottom padding
   is what holds it off the edge of the screen, so it carries no bottom
   margin of its own. */
.themepick {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0.2rem 0.55rem;
  margin: 1.6rem 0 0;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.themepick-label {
  margin-right: 0.15rem;
}

/* THE WARD'S CALENDAR TO SUBSCRIBE TO (Round 211) sits at the FOOT OF THE
   ANNOUNCEMENTS PANEL (Round 218, Derek: on that tab only, never Program or
   Groups), ruled off from the last notice with the notices' own hairline:
   the last notice drops its rule (.ann:last-of-type) and this block draws
   the one between them, 1.9rem under the notice's text and 1.3rem above
   its own, the notices' own rhythm (the 1.9rem margin collapses with the
   last notice's 0.5rem, so 1.9rem is what lands). Hidden in print with the
   per-notice links. */
.wardcal {
  text-align: center;
  margin: 1.9rem 0 0;
  padding-top: 1.3rem;
  border-top: 1px solid var(--line);
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

/* "No announcements this week." carries 2.2rem of its own padding; the rule
   needs no more air above it there. */
.empty + .wardcal {
  margin-top: 0;
}

/* A reading measure (Round 214): short lines, not one long one across the
   whole column on a computer; the rule above them spans it. */
.wardcal-ask,
.wardcal-note,
.wardcal-links {
  max-width: 30rem;
  margin: 0 auto;
}

/* THE OFFER (Round 214, cut to one sentence in Round 315, Derek's own
   wording): the page's own ink, because it is the one line that says what
   subscribing does; the links line names the act. Not for everyone, which
   is why it has to be plain. */
.wardcal-ask {
  color: var(--ink);
  font-size: 0.875rem;
  font-weight: 500;
}

/* AN EDITORIAL ASIDE, NOT COPY FOR THE WARD (Round 315, Derek's own words,
   verbatim, parentheses included -- "should we even have this subscribe
   feature? Or should we remove it?"). Italic and a size down from the
   sentence above it says PLAINLY that this is a note rather than an
   instruction, which is the whole point of it sitting on the live page
   where the leaders can see it before he decides. Remove this rule and
   its emitter together the day the question is answered. */
.wardcal-note {
  margin-top: 0.4rem;
  font-size: 0.75rem;
  font-style: italic;
  color: var(--ink-soft);
}

.wardcal-links {
  margin-top: 0.35rem;
}

/* .wardcal a is styled with .ann-cal a, further down: the same two words
   are one control (Round 212). */

.wardcal-label {
  margin-right: 0.15rem;
}

.wardcal-sep {
  margin: 0 0.5rem;
}

.themepick button {
  font: inherit;
  background: none;
  border: none;
  padding: 0.1rem 0.15rem;
  color: var(--ink-soft);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-color: color-mix(in srgb, var(--ink-soft) 30%, transparent);
}

.themepick button:hover,
.themepick button:focus-visible {
  color: var(--ink);
  text-decoration-color: currentColor;
}

.themepick button[aria-pressed='true'] {
  color: var(--accent);
  font-weight: 650;
  text-decoration-color: currentColor;
}

/* ================================================================== admin */

body.admin {
  padding-bottom: 7rem;
}

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1.3rem 0 1.1rem;
}

.topbar h1 {
  margin: 0;
  font-size: 1.35rem;
}

/* Ward stationery: the ward's name set small and letterspaced above the
   title, the pair closed by a hairline — the same manner as the printed
   program's masthead. */
body.leader .topbar {
  align-items: flex-start;
  padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--line);
}

.brand {
  display: block;
  line-height: 1.15;
}

.brand-ward {
  display: block;
  font-size: 0.82rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.19em;
  color: var(--ink-soft);
  margin-bottom: 0.22rem;
}

.brand-name {
  display: block;
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: 0.005em;
}

body.leader .topbar .topbar-side {
  padding-top: 0.15rem;
}

.acard {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 1.15rem;
  margin-bottom: 1.25rem;
}

.acard > h2 {
  margin: 0 0 0.95rem;
  font-size: 1.2rem;
}

.field {
  display: block;
  margin-bottom: 0.95rem;
}

.field-label {
  display: block;
  color: var(--ink-soft);
  margin-bottom: 0.3rem;
}

/* THE FORMATTING BUTTONS (Round 230, Derek). A quiet row above the box:
   soft ink until the pointer is on one, no filled block anywhere in it --
   the accent belongs to the one primary action on a form (Round 5), and
   this is a row of small tools, not the thing you came to press. Two
   hairlines divide the three jobs: the letters, the lists, the indent. */
.fmtbar {
  display: flex;
  align-items: center;
  gap: 0.1rem;
  margin: 0 0 0.35rem;
}

/* (.fmtbtn.on WAS HERE, Round 274 to Round 319 -- the soft accent tint that
   marked whichever list button the caret's line was in. Derek asked for it
   in Round 274 and asked for it back out here: "it gets highlighted. No need
   for it to be highlighted." The marker in the box already says which mode
   the line is in, in the place the eye is looking. paintMode went with it in
   app.js, so nothing wears this now.) */

.fmtsep {
  width: 1px;
  height: 1.05rem;
  margin: 0 0.4rem;
  background: var(--line);
}

.fmtbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.95rem;
  height: 1.75rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--r-detail);
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}

.fmtbtn:hover {
  background: color-mix(in srgb, var(--accent) 8%, var(--card));
  border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
  color: var(--ink);
}

.fmtbtn svg {
  width: 1rem;
  height: 1rem;
}

.fmt-rule {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
}

.fmt-dot {
  fill: currentColor;
  stroke: none;
}

/* The only sans on the page, and deliberately: a serif "1" five pixels
   high reads as a speck. */
.fmt-num {
  fill: currentColor;
  stroke: none;
  font-family: system-ui, 'Segoe UI', sans-serif;
  font-size: 5.6px;
  font-weight: 700;
}

/* B and I are set in the page's own serif — a letter says what it does
   better than any drawing of one could. */
.fmtglyph {
  font-size: 1rem;
  line-height: 1;
}

.fmtglyph.fmt-b {
  font-weight: 800;
}

.fmtglyph.fmt-i {
  font-style: italic;
  padding-right: 0.12em;
}

/* EVERY CHECKBOX, not just the ones somebody remembered to class.
   There was no rule here at all, so 115 checkboxes on the Members tab were
   13x13 at the browser's default — and `accent-color: auto` renders them in
   the system BLUE when ticked, which is the one colour nothing else on this
   cream-and-sage page uses. They were also small enough to be fiddly with a
   mouse, let alone a thumb.
   .remember input (1.25rem) and .wb-sa (1.05rem) are more specific or come
   later, so they keep the sizes they were given deliberately. */
input[type='checkbox'],
input[type='radio'] {
  width: 1.1rem;
  height: 1.1rem;
  /* A flex item shrinks below its own width by default, so a checkbox in a
     tight label row was squashed back to 13px wide while keeping its 17.6px
     height — a visibly squeezed rectangle. Measured on a phone before this. */
  flex: none;
  accent-color: var(--accent);
  cursor: pointer;
}

input[type='text'],
input[type='date'],
input[type='password'],
input[type='search'],
textarea {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: 1.0625rem;
  padding: 0.65rem 0.75rem;
  border: 1.5px solid var(--line);
  border-radius: var(--r-item);
  background: var(--paper);
  color: var(--ink);
}

textarea {
  resize: vertical;
}

/* A DETAIL THAT GROWS (Round 245, Derek): starts one line tall and grows
   downward as the words wrap, so a long detail is readable instead of
   scrolling out of the box. Everything else -- font, padding, border --
   comes from the shared field styling above, so it matches the box beside
   it; only the grip and the scrollbar are taken away, since the height is
   the app's to set. */
textarea.autogrow {
  resize: none;
  overflow: hidden;
}

/* Example text has to look like an example. Left at the browser's default it
   sits at roughly the weight of real text, and in this serif on cream a box
   reading "Pickleball" looks like a box someone has already filled in — people
   either think the form is half-done or type around it. Faded well back, it
   reads as a suggestion at a glance while still being legible enough to copy
   the shape of. The colour comes from the soft-ink token, so it lands correctly
   in dark mode too; `opacity: 1` stops Firefox applying a second fade of its
   own on top. */
::placeholder {
  color: color-mix(in srgb, var(--ink-soft) 65%, transparent);
  opacity: 1;
}

.hint {
  display: block;
  font-size: 0.875rem;
  color: var(--ink-soft);
  margin-top: 0.3rem;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
}

/* TWO-UP FIELD ROWS.
   minmax(0, …) rather than a bare 1fr: a grid track's implicit minimum is
   `auto`, so a <select> whose longest option is a long name ("Laurel Smith
   (Williams)") pushed its track past its share and took the whole page with
   it — the conducting sheet measured 637px inside a 375px phone, and the
   phone answered by zooming the entire page out. Same bug .annsplit had. */
.cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0.7rem;
}

/* And on a phone they stack. Two 160px columns of form fields is not a
   two-column layout, it is one column of clipped labels twice over. */
@media (max-width: 34rem) {
  .cols {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A hymn number and its title stay side by side even on a phone — the number
   box is fixed and narrow, so the pair still reads as one field. */
.cols.hymn {
  grid-template-columns: 6.5rem minmax(0, 1fr);
}

/* ================= THE FOUR THINGS A BUTTON CAN BE ==================

   Written down (Round 277, audit item 3) because an audit counted 38
   distinct button treatments across the app and 13 type sizes, and there
   was nowhere saying what the ranks were meant to be. Most of that spread
   is containers setting their own density -- a .btn.slim is 15px in the
   Callings toolbar and 13px in the conducting sheet's narrow column, which
   is Round 220 doing its job, not drift -- so this is a statement of the
   ranks rather than a rewrite of every rule.

   ADD A BUTTON BY PICKING ONE OF THESE, not by inventing a fifth.

   1. PRIMARY  .btn.btn-primary  -- filled accent.
      The one action that moves the work forward, ONE per toolbar (Round 5).
      A dialog's confirm is a primary; so is "+ Add" on Callings, "+ Add
      person" on Admin, "+ Add a group" on Groups, "Add" in a composer, and
      the first waiting action in a queue (Approve, Entered in LCR, 
      Sustained). Softened to 74% of the accent since Round 204, except
      Print (.printbtn) and the composers, which Derek asked to keep full.

   2. SECONDARY  .btn.slim  -- outlined accent, sized to its words.
      Real actions that are not the main one: Print, Planner, Use Hymn
      Library, Snapshot, Sync with LCR, and every "+ Speaker" style control
      that adds a row to a list. As many as a screen needs.

   3. QUIET  .quiet-inline  -- text, no box.
      Utilities and navigation you already know where to find: Move this
      sheet, Earlier versions, Usage history, Download a backup, and the
      per-row actions on the Admin table. Carries a leading glyph by
      default, which containers drop where the actions are a row of equals.

   4. DESTRUCTIVE  .danger  -- a MODIFIER on any of the three, never a
      rank of its own. .btn.danger, .iconbtn.danger, .quiet-inline.danger.
      Filled only as a dialog's final confirm, where it is the one thing to
      press. Everything that cannot be undone takes it: Clear this sheet,
      Remove permanently, Delete this entry, Clear sensitive notes.

   Not ranks, and not to be confused with them: .togglebtn (a state, not an
   action), .iconbtn (a rank-3 action drawn as a glyph where a word will not
   fit), .fmtbtn (a toolbar of formatting actions -- it carried states too
   until Round 319 took the pressed tint off the list buttons), .datechip
   and .sheettab (navigation). ========================================= */

.btn {
  font: inherit;
  font-size: 1rem;
  font-weight: 650;
  min-height: 2.75rem;
  padding: 0.55rem 1.1rem;
  border-radius: 999px;
  border: 1.5px solid var(--accent);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
}

.btn-primary {
  background: var(--accent);
  color: var(--on-accent);
}

.btn:disabled {
  opacity: 0.5;
  cursor: default;
}

.iconbtn {
  min-width: 2.75rem;
  min-height: 2.75rem;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: var(--card);
  color: var(--ink);
  font-size: 1.05rem;
  cursor: pointer;
}

.iconbtn:disabled {
  opacity: 0.3;
  cursor: default;
}

.iconbtn.danger {
  color: var(--danger);
}

.edit-item {
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  padding: 0.9rem;
  margin-bottom: 0.9rem;
  background: var(--paper);
}

.edit-item-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.7rem;
}

.edit-type {
  color: var(--accent);
}

.edit-controls {
  display: flex;
  gap: 0.4rem;
}

.addrow {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.9rem;
}

.addrow .btn {
  flex: 1 1 auto;
}

.badge {
  display: inline-block;
  background: var(--badge-bg);
  color: var(--badge-ink);
  font-size: 0.8125rem;
  font-weight: 650;
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  margin-bottom: 0.6rem;
}

.savebar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: var(--card);
  border-top: 1px solid var(--line);
  padding: 0.7rem 1rem calc(0.7rem + env(safe-area-inset-bottom));
  z-index: 10;
}

.savebar-inner {
  max-width: 40rem;
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.save-status {
  font-size: 0.9375rem;
  color: var(--accent);
}

.save-status.dirty {
  color: var(--badge-ink);
  font-weight: 650;
}

.unlock {
  margin-top: 2.5rem;
}

.unlock-err {
  color: var(--danger);
  font-weight: 650;
  min-height: 1.5em;
  margin: 0.4rem 0;
}

/* The same line while the code is being checked. It is progress, not a
   refusal, and the first thing a new leader saw should not be red. */
.unlock-err.is-working {
  color: var(--ink-soft);
  font-weight: 400;
}

.remember {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0.6rem 0 1rem;
  cursor: pointer;
}

.remember input {
  width: 1.25rem;
  height: 1.25rem;
  accent-color: var(--accent);
}

/* The line that says when an announcement comes off the page. It belongs to
   the checkbox above it, so it sits close under it rather than floating.
   NOT a negative margin: `.remember.slim` is `margin: 0.4rem 0 0`, so it has
   no bottom margin to pull back against and the hint landed on top of the
   label text. */
.annwhen-hint {
  margin: 0.2rem 0 0;
}

details.settings {
  margin: 0.5rem 0 1.5rem;
}

details.settings summary {
  cursor: pointer;
  font-weight: 650;
  color: var(--ink-soft);
  padding: 0.4rem 0;
}

details.settings .acard {
  margin-top: 0.6rem;
}

.toast {
  position: fixed;
  bottom: 5.6rem;
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--paper);
  padding: 0.6rem 1.1rem;
  border-radius: 999px;
  font-size: 0.9375rem;
  /* Wide enough that a sentence plus an Undo sits on one line on a computer;
     still capped by the viewport, so it wraps on a phone as it should. */
  max-width: min(92vw, 36rem);
  text-align: center;
  opacity: 0;
  transition: opacity 0.25s;
  pointer-events: none;
  /* Above everything: a toast confirms what just happened, so it is useless if
     a dialog (50) or the full-screen stand view (60) can cover it. Keep this
     the highest number in the file. */
  z-index: 100;
}

.toast.show {
  opacity: 1;
}

/* SOMETHING WENT WRONG, told apart from something saved. Same dark pill, so
   it is still the app's own voice and still in the same place — but marked
   down its leading edge, and it waits to be dismissed rather than expiring
   under the reader. */
.toast.is-bad {
  border-left: 4px solid var(--danger);
  padding-left: 0.9rem;
  text-align: left;
}

/* A toast is normally click-through; one carrying an Undo must not be. */
.toast.has-action {
  pointer-events: auto;
}

/* ------------------------------------------- the quiet text-action family */

/* A WORD YOU CAN PRESS, WITH NO BOX. Fourteen classes in this file are the
   same object, and each used to re-declare the whole recipe — `font:
   inherit; background: none; border: none; padding: 0; cursor: pointer` —
   before adding its own type and colour. Written once here, so the classes
   below carry only what actually distinguishes them.
   It sits before every one of them, and all of them match at the same
   specificity, so anything a class sets for itself (a padding, a border on
   .install-link) still wins.
   The genuine ranks are untouched and each is argued for in this file:
   .quiet-action (block, 0.875rem) and .quiet-inline (inline, 0.8125rem).
   What went was the noise between them.
   (Two more have since gone the same way, each with its one emitter:
   .glive-view with the live-groups dialog in Round 244, and .undo-apply with
   the apply-to-the-month Undo in Round 290. Leaving a rank here that nothing
   wears would be worse than no rule, because the next reader would believe
   it.) */
.quiet-inline,
.quiet-action,
.noteact,
.install-no,
.gbtn-quiet,
.install-link,
.toast-undo,
.mnamebtn,
.prop-edit {
  font: inherit;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

/* ONE DISTANCE FROM THE BASELINE. These underlines landed at four: 0.18em,
   0.2em, 0.22em, and the browser default on .install-no, which set none at
   all. Nobody chose a half-pixel difference between two links on adjacent
   tabs. The global `a` keeps its own 0.15em — that is prose on the ward's
   page, set larger and in the serif, and a tighter offset there is a real
   decision rather than a leftover. */
.noteact,
.install-no,
.gbtn-quiet,
.toast-undo {
  text-underline-offset: var(--ul-offset, 0.2em);
}

.toast-undo {
  font-size: 0.9375rem;
  font-weight: 700;
  color: inherit;
  margin-left: 0.9rem;
  text-decoration: underline;
}

.toast-undo:hover {
  opacity: 0.72;
}

@media (prefers-reduced-motion: reduce) {
  .toast {
    transition: none;
  }
}

/* =========================================================== leader app */

.topbar-side {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  /* On a narrow phone the masthead wraps and this drops to its own line; keep
     it against the right edge there rather than under the ward's name. */
  margin-left: auto;
}

/* A long name is allowed to run out of room rather than push the menu button
   off the edge on a narrow phone. */
.acctname {
  max-width: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ONE BLOCK, not four. This selector was declared four times at the top level,
   hundreds of lines apart, and declared `gap` in three of them — 0.45rem, then
   0.15rem, then 0 — so two of the three were dead and the block that explained
   the spacing was not the block that set it. Same for padding-bottom (0.6rem
   then 0) and margin-bottom (0.9rem then 1rem).
   These are the values that were actually rendering, measured before the
   merge. The `overflow: visible` at ≥64rem is a real media query and stays
   where it is: on a computer the row fits and does not scroll. */
.tabbar {
  display: flex;
  align-items: flex-end;
  gap: 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
  position: sticky;
  /* Under the Round 229 banner when one is showing; 0 the rest of the time,
     which is every session but the bishop's borrowed ones. */
  top: var(--actbar-h, 0px);
  z-index: 20;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  padding-top: 0.5rem;
  padding-bottom: 0;
  /* The gutter between the tab row and whatever the tab is showing, and
     the ONLY thing that sets it: every view mounts straight into #app, which
     follows this element, so one number governs all ten tabs. It was 1rem,
     and Derek (Round 240) found the content of every page sitting right up
     against the tab header. 2rem is the distance this design already uses
     where a new region begins, and it is a resting gap only -- the bars that
     stick under the tab row (the preview pane, the preview bar, the
     Snapshot select bar) still come flush to it on scroll, which is what
     Rounds 53, 63 and 199 built. Both columns of the conducting split move
     together, so the form still lines up with the preview paper. */
  margin-bottom: 2rem;
}


.grouph {
  font-size: 1.05rem;
  margin: 1.4rem 0 0.6rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.groupintro {
  margin: 0.2rem 0 1rem;
}

/* The one line that stands in for every empty queue on the Bishopric and
   Clerks tabs (Round 150, Derek: an empty section must not take up "a
   massive chunk of the page"). Quiet hint type; when it is the whole page
   (.alone) it gets a little more air above and nothing else. */
.allclear {
  margin: 1.4rem 0 0.6rem;
  color: var(--ink-soft);
}
.allclear.alone {
  margin-top: 1.8rem;
}

/* The Snapshot's Clear dialog (Round 158): two radio rows, and the
   availability and note for the "still in the ward" choice indented under
   their radio. */
.modalcard .clearopt {
  align-items: flex-start;
  margin: 0.55rem 0 0;
}
.modalcard .clearopt input {
  margin-top: 0.2rem;
}
.modalcard .clearstay {
  margin: 0.35rem 0 0 1.9rem;
}

.edit-item.slim {
  padding: 0.7rem 0.8rem;
}

.btn.slim {
  min-height: 2.25rem;
  padding: 0.3rem 0.8rem;
  font-size: 0.9375rem;
}

.remember.slim {
  margin: 0.4rem 0 0;
  font-size: 0.9375rem;
}

.sheetpick {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  flex-wrap: wrap;
}

/* A slim shelf above the sheet: what week you are on, and the housekeeping
   that goes with it. It should never read as heavily as the sheet itself. */
/* 0.5rem under the picker, from 1 (Round 261): the other half of "move up
   a little". Scoped here rather than on .acard, which every card in the
   app shares. */
.sheetpicker {
  padding: 0.65rem 1.1rem;
  margin-bottom: 0.5rem;
}

.sheetbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem 1.4rem;
  flex-wrap: wrap;
  margin-bottom: 0;
}

/* Label, month, then the Sundays — read left to right, no dead middle. */
.sheetbar-main {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  min-width: 0;
}

.sheetbar-label {
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}

.sheetbar-main select {
  width: auto;
}

/* This month, marked in the open list. The optgroup heading carries it in
   every browser; this tints it as well wherever option colours are honoured. */
.sheetbar-main option.is-current {
  font-weight: 700;
  background: color-mix(in srgb, var(--accent) 16%, var(--card));
}

.sheetbar h2 {
  margin: 0;
}

.sheetchips {
  display: flex;
  flex-wrap: wrap;
  /* The row gap has to clear the active chip’s pointer, which hangs 7px
     below its pill (Round 256). At the old 0.45rem the tip landed 0.2px
     above the chip on the row beneath, measured — so the ROWS give way,
     not the arrow. The columns keep their 0.45rem. */
  gap: 1rem 0.45rem;
  align-items: center;
}

.datechip.ghost {
  border-style: dashed;
}

.datechip {
  font: inherit;
  font-size: 0.9rem;
  font-weight: 650;
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  border: 1.5px solid var(--line);
  background: var(--paper);
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
}

.datechip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.datechip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.sheet-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.8rem;
  flex-wrap: wrap;
}

.readiness {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  align-items: center;
  border-top: 1px solid var(--line);
  padding-top: 0.6rem;
  margin-top: 0.2rem;
}

/* A LITTLE MORE OBVIOUS (Round 167, Derek): the label takes the attention
   colour, and each chip is a step larger on a fuller fill with a solid edge
   -- the dashed 55% edge on an 8% tint read as a faint outline. Still a
   chip in the row, not a banner: the tab bar's badge already shouts. */
.readiness-label {
  color: var(--notify);
  text-transform: uppercase;
  margin-right: 0.2rem;
}

.misschip {
  font: inherit;
  font-size: 0.875rem;
  font-weight: 650;
  padding: 0.2rem 0.7rem;
  border-radius: 999px;
  /* Dotted, at Derek's word (Round 170): the dashed edge Round 167 made
     solid is back as dots, in the same red; hover firms the colour only. */
  /* Round 172 thickened these to 2px at 85%; Round 205 put the 1.5px / 70%
     edge back at Derek's word: he preferred the lighter one. */
  border: 1.5px dotted color-mix(in srgb, var(--notify) 70%, transparent);
  background: color-mix(in srgb, var(--notify) 16%, transparent);
  color: var(--notify);
  cursor: pointer;
  white-space: nowrap;
}

.misschip:hover {
  background: color-mix(in srgb, var(--notify) 26%, transparent);
  border-color: var(--notify);
}

.secnum {
  color: var(--ink-soft);
  font-weight: 400;
  margin-right: 0.45rem;
}

/* THE FLASH (Round 176, Derek: smoother, not so intense, and on the row as
   well as the card). Two rings that swell in over the first fifth and fade
   over the rest -- the card's soft and wide, the row's tighter -- in the
   accent let down toward transparent rather than the solid green that used
   to snap on. Two seconds, ease-in-out. */
.flash {
  animation: secflash 2s ease-in-out;
}

@keyframes secflash {
  0% { box-shadow: 0 0 0 0 transparent; }
  20% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 32%, transparent); }
  100% { box-shadow: 0 0 0 5px transparent; }
}

.flash-row {
  animation: rowflash 2s ease-in-out;
  border-radius: var(--r-item);
}

@keyframes rowflash {
  0% { box-shadow: 0 0 0 0 transparent; background-color: transparent; }
  20% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 55%, transparent); background-color: color-mix(in srgb, var(--accent) 8%, transparent); }
  100% { box-shadow: 0 0 0 3px transparent; background-color: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .flash {
    animation: none;
  }
}

.conducting-col {
  max-width: 100%;
}

/* Form on the left, live preview of the printed sheet on the right. */
.sheetsplit {
  display: grid;
  gap: 1.4rem;
  grid-template-columns: 1fr;
  align-items: start;
}

.sheetform {
  min-width: 0;
  max-width: 48rem;
  margin-inline: auto;
  width: 100%;
}

/* THE TICK IN THE BOX. The conducting sheet has no Save button and never has;
   this is the box saying, in the box, that what is in it is safe. It appears
   as you type and clears when the server has it (app.js, "the field tick"). */
.tickwrap {
  position: relative;
  display: block;
  /* One of these sits inside a flex row (the announcement lines), where the
     wrapper is now the flex child rather than the textarea. Both halves are
     needed: grow to fill the row, and be allowed to shrink inside it. */
  flex: 1 1 auto;
  min-width: 0;
}

/* The room for the tick is held open at all times rather than made when it
   appears. Padding that grew as you typed would shove the words sideways at
   the exact moment somebody is reading them.
   WRITTEN LONG ON PURPOSE. The desktop density block further down this file
   sets `body.leader input[type='text'] { padding: 0.5rem 0.6rem }` — the
   SHORTHAND, at (0,2,2), from a later line. A plain `.tickwrap > input` ties
   or loses to that and the reserved room silently vanished above 64rem, which
   is every screen this tab is used on. These selectors outrank it wherever it
   sits, so a later reshuffle of the file cannot take the space away again. */
body.leader .tickwrap > input[type='text'],
body.leader .tickwrap > textarea {
  padding-right: 2.35rem;
}

.fieldtick {
  position: absolute;
  right: 0.4rem;
  top: 50%;
  transform: translateY(-50%);
  width: 1.65rem;
  height: 1.65rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 50%;
  background: none;
  color: var(--accent);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.16s ease, background-color 0.16s ease, color 0.16s ease;
}

/* A textarea's bottom right corner belongs to the browser's resize grip. */
.tickwrap.tick-area .fieldtick {
  top: 0.4rem;
  transform: none;
}

.tickwrap.is-pending .fieldtick,
.tickwrap.is-saved .fieldtick {
  opacity: 1;
  pointer-events: auto;
}

.tickwrap.is-pending .fieldtick {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.tickwrap.is-pending .fieldtick:hover {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

/* Saved: the success green, which is the token that exists for exactly this —
   a green light, told apart from the sage the whole app is drawn in. It is a
   statement now rather than a button, so it stops taking the pointer. */
.tickwrap.is-saved .fieldtick {
  color: var(--success-ink);
  background: var(--success-bg);
  border-color: var(--success-line);
  pointer-events: none;
  cursor: default;
}

.fieldtick .tickmark {
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (prefers-reduced-motion: reduce) {
  .fieldtick {
    transition: none;
  }
}

.previewpane {
  display: none;
}

/* THE CONDUCTING PREVIEW IS PERMANENT (Round 209, Derek: "you can't close
   it"). Below the split width it stacks under the form rather than
   vanishing -- since Round 209 it is where the program is edited, so it
   has to be somewhere at every width. The announcements pane (.annpane)
   keeps its own arrangement. */
.previewpane:not(.annpane) {
  display: block;
}

@media (min-width: 72rem) {
  /* THE FORM COLUMN: 24rem in Round 220 ("much thinner so the preview is
     wider and more prominent"), 27 in Round 240 ("just slightly too thin"),
     and 34 here -- Derek, Round 261: "significantly wider... I don't want it
     to be half the page." At the 84rem workspace that is 544px of a 1304px
     row, 42%: plainly the narrower column still, and half again the 34% it
     was.

     THE PREVIEW PAYS FOR IT, and there was no way round that. The printed
     column is a fixed 48rem = 768px, so every pixel the form takes comes
     off the pane and the sheet is scaled to fit. MEASURED at a 1440px
     window, form width -> preview scale:

       27rem -> 1.000   30 -> 0.967   32 -> 0.926
       34rem -> 0.884   36 -> 0.842   38 -> 0.801

     Scaling costs no accuracy -- it is a transform, not a re-flow, so every
     line breaks where it prints (Round 155, which shipped at 0.83). It is
     only smaller. Re-measure this table if the printed width or the gap
     ever moves. */
  .sheetsplit {
    /* BOTH COLUMNS ARE BOUNDED, AND THE SLACK GOES BETWEEN THEM (Round 263,
       Derek: skinnier on the left, "a little bit less wide" on the preview,
       "but in doing so, I do not want you to re-center the two panels. Just
       allow that space gap between them to be larger").
       The preview column was 1fr, so it swallowed every pixel the form gave
       up and narrowing one WIDENED the other -- there was no way to shrink
       both. Capping it too and letting justify-content push the pair to the
       outer edges puts all the leftover in the channel between them, which
       is exactly what he asked for. The gap is the MINIMUM now, not the
       distance; on a narrow screen the percentages take over and it closes
       back to 1.2rem.
       The caps are what he wants on a wide monitor, the percentages hold the
       proportions on a laptop (Round 261: fixed rem alone made the form half
       the page at 1152px).
       38 + 58 = 96%, so the channel is the other 4%. Round 263 had the
       preview at 53% and a 9% channel; Derek drew a line on a screenshot for
       where its left edge should sit, which read as 42% of the row -- the
       preview takes back most of that gap. The FORM is unchanged: his line
       moved one edge, not both. */
    grid-template-columns: minmax(0, min(31rem, 38%)) minmax(0, min(47rem, 58%));
    justify-content: space-between;
    gap: 1.2rem;
  }

  .sheetform {
    margin-inline: 0;
    /* --sheet-drop (measureTabbar, Round 199; re-based in Round 220 with
       the sub-tabs gone): the pane's head is taller than the Still-blank
       strip, so the column pads itself down by the difference and the
       first card's top lands on the preview paper's. */
    padding-top: var(--sheet-drop, 0px);
  }

  /* Flush under the sticky tab bar (--tabbar-h, measured in app.js), with
     its own paper and a little padding above the head, so nothing scrolls
     by in a slit between the two. border-box so the padding lives inside
     the max-height; the bottom clearance stays 1.8rem for the status
     strip.
     0.25rem, from 0.75 (Round 261, Derek: both panels "move up a little").
     THIS RAISES BOTH COLUMNS, not just this one: --sheet-drop is measured
     from the pane's top to the paper's, so the form's own top padding
     follows it down and the two stay aligned (Round 199, his own ask). */
  .sheetsplit > .previewpane {
    display: block;
    position: sticky;
    top: calc(var(--actbar-h, 0px) + var(--tabbar-h, 4.2rem));
    max-height: calc(100vh - var(--actbar-h, 0px) - var(--tabbar-h, 4.2rem) - 1.8rem);
    box-sizing: border-box;
    padding-top: 0.25rem;
    background: var(--paper);
    display: flex;
    flex-direction: column;
  }
}

.preview-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}

.preview-head .field-label {
  margin: 0;
}

.preview-paper {
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 1.2rem 1.4rem 1.6rem;
  box-shadow: var(--lift);
}

/* The preview used to set .stand-inner to 0.95rem and no max-width here,
   which is exactly why it wrapped differently from the paper. Gone (Round
   155): the sheet inside .previewpage keeps its printed 40rem and .paperlike
   sizes and is shrunk as a whole. */

.preview-paper .stand-sec {
  margin-top: 1.1rem;
}

/* --------------------------------------------------------------- modals */

.modalcard {
  max-width: 28rem;
  width: 100%;
  margin: 0;
  padding: 1.4rem 1.5rem 1.25rem;
  box-shadow: 0 18px 46px rgba(20, 16, 10, 0.24);
  /* The close X hangs in this corner (Round 227), so the card has to be the
     box it is measured from -- without this it would anchor to the overlay
     and sit in the corner of the whole window. */
  position: relative;
}

.modalcard h2 {
  margin-top: 0;
  margin-bottom: 0.75rem;
}

/* The sign-in form holds this line open so the layout cannot jump. A dialog
   has no such worry, and the reserved line reads as a hole above the buttons. */
.modalcard .unlock-err:empty {
  display: none;
  margin: 0;
}

/* The Sundays an action will touch: one plain row each, ruled like the
   program's own leader rows. */
.datelist {
  list-style: none;
  margin: 0.65rem 0 0;
  padding: 0;
  border-top: 1px solid var(--line);
}

.datelist li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.44rem 0.1rem;
  border-bottom: 1px solid var(--line);
}

.datelist-tag {
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}

.modal-body {
  margin: 0 0 0.4rem;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 1.1rem;
}

/* A QUIET TEXT ACTION THAT DESTROYS SOMETHING (Round 276). The app has
   had .btn.danger and .iconbtn.danger for a long time; the one thing
   missing was the text rank, which is where "Clear this sheet" lives. */
.quiet-inline.danger {
  color: var(--notify);
}

.quiet-inline.danger:hover,
.quiet-inline.danger:focus-visible {
  color: var(--notify);
  text-decoration: underline;
  text-underline-offset: var(--ul-offset);
}

.btn.danger {
  border-color: var(--danger);
  background: var(--danger);
  color: var(--paper);
}

.btn.danger:hover:not(:disabled) {
  background: var(--danger);
  filter: brightness(0.92);
}

.needsyou {
  border-color: color-mix(in srgb, var(--notify) 45%, transparent);
  border-left: 4px solid var(--notify);
}

.bcard-meta.late {
  color: var(--notify);
  font-weight: 650;
}

.records {
  display: block;
  margin-bottom: 0.95rem;
}

/* TWO COLUMNS OF NAMES (Round 290b, Derek): eleven move-ins down one column
   is most of the panel's height, and they read fine two across.
   auto-fit + minmax means the browser decides -- two tracks in the sheet's
   own column, one when it is narrower than two names will bear, at every
   text size, with no breakpoint to keep in step.
   minmax(11rem, 1fr) and never the implicit auto track (Round 29's grid
   lesson): an auto track sizes to its widest content, so one long surname
   would push its column past the card. */
.recordlist {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.3rem;
  margin-bottom: 0.6rem;
}

.recordrow {
  display: flex;
  min-width: 0;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.3rem 0.35rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: var(--paper);
}

.recordrow .iconbtn {
  min-width: 2rem;
  min-height: 2rem;
  font-size: 0.9rem;
}

.recordadd {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
}

.recordadd input[type='text'] {
  flex: 1;
  min-width: 12rem;
  width: auto;
}

/* THE FIRST / LAST PAIR AND THE GREEN CHECK (Round 160), on the add line and
   in a row being edited. The check is the field tick's mark in the success
   green, always showing: here it is the way to submit, not a report that a
   save happened. A box left empty on a press is marked for a moment in the
   attention colour. */
/* flex-basis AUTO, not 0: the pair asks for its boxes' natural width, so on a
   narrow row the Done button wraps beneath it instead of sharing the line
   (with basis 0 the two split the line and the check slid under Done). Then
   min-width: 0 lets the pair shrink to the row, and the boxes shrink inside
   it -- a text box will not go below its own intrinsic width otherwise. */
.namepair {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  gap: 0.4rem;
  align-items: center;
}

.recordadd .namepair input[type='text'],
.recordrow .namepair input[type='text'] {
  flex: 1 1 7rem;
  min-width: 5rem;
  width: auto;
}

.namepair input.is-missing {
  border-color: var(--notify);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--notify) 25%, transparent);
}

.recordtick {
  flex: none;
  width: 1.9rem;
  height: 1.9rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--success-line);
  border-radius: 50%;
  background: var(--success-bg);
  color: var(--success-ink);
  cursor: pointer;
}

.recordtick:hover {
  background: color-mix(in srgb, var(--success-ink) 16%, var(--success-bg));
}

.recordtick .tickmark {
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The name in a row is a button: press it to edit. Quiet until hovered.
   nowrap and hidden are what let fitRecordNames() measure it: what the
   name wants against what it has, stepped down to fit. The ellipsis is
   the floor's honest answer, not the ordinary one. */
.recordname {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: left;
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  padding: 0.1rem 0.3rem;
  margin: -0.1rem -0.3rem;
  border-radius: var(--r-detail);
  cursor: pointer;
}

.recordname:hover,
.recordname:focus-visible {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  text-decoration: underline dotted;
  text-underline-offset: var(--ul-offset, 0.2em);
}

.recordrow.is-editing {
  grid-column: 1 / -1;
  padding-left: 0.3rem;
}

/* ------------------------------------------------- hover affordances */

.btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.btn-primary:hover:not(:disabled) {
  background: var(--accent);
  filter: brightness(0.92);
}

/* A SHADE SOFTER GREEN on the leader app's filled pills and the chosen sheet
   tab (Round 184, Derek: "just a slightly more faded green, not a lot").
   The accent let down 14% toward the paper, then 26% (Round 204, "a little
   bit more"). Print (.printbtn) and the open
   date chip keep the full accent, at his word; the ward's page is untouched
   (scoped to body.leader). */
body.leader .btn-primary:not(.printbtn),
body.leader .btn-primary:not(.printbtn):hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 74%, var(--paper));
}

.iconbtn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  border-color: var(--accent);
}

.togglebtn:hover:not(.on) {
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--ink);
}

.datechip:hover:not(.active) {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}

.misschip:hover {
  filter: brightness(0.95);
}

.stat:hover {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}

.wb-add:hover {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
  border-radius: var(--r-item);
}

details.acc > summary:hover,
.bcard details > summary:hover,
details.settings summary:hover {
  color: var(--accent);
}

.snapline:hover .snaptitle {
  color: var(--accent);
}

.sheetpick select {
  flex: 1;
  min-width: 12rem;
  font: inherit;
  font-size: 1rem;
  padding: 0.55rem 0.6rem;
  border: 1.5px solid var(--line);
  border-radius: var(--r-item);
  background: var(--paper);
  color: var(--ink);
}

select {
  font: inherit;
  font-size: 1rem;
  padding: 0.5rem 0.55rem;
  border: 1.5px solid var(--line);
  border-radius: var(--r-item);
  background: var(--paper);
  color: var(--ink);
  max-width: 100%;
}

.bizrow {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--line);
}

.bizrow:last-of-type {
  border-bottom: none;
}

.biznote {
  margin: 0.2rem 0 0;
  color: var(--badge-ink);
  font-size: 0.9375rem;
  font-weight: 650;
}

.pipehead {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  flex-wrap: wrap;
}

.pipecall {
  color: var(--ink-soft);
  font-size: 0.9375rem;
}

.badge.release {
  background: #f3ddd3;
  color: #7c3a24;
}

@media (prefers-color-scheme: dark) {
  .badge.release {
    background: #472c1f;
    color: #eda98d;
  }
}

.memberrow {
  display: grid;
  gap: 0.5rem;
}

.snapline {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  cursor: pointer;
  flex-wrap: wrap;
}

.snaptitle {
  font-weight: 650;
}

.snapholder {
  color: var(--ink-soft);
  font-size: 0.9375rem;
  text-align: right;
}

.snaprow.vacant {
  border-style: dashed;
}

.snapedit {
  display: none;
  margin-top: 0.7rem;
  border-top: 1px solid var(--line);
  padding-top: 0.7rem;
}

.snaprow.open .snapedit {
  display: block;
}

.codeoverlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 16, 10, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
  z-index: 50;
}

.codecard {
  position: relative;
  max-width: 34rem;
  text-align: center;
}

/* The corner close. Quiet enough not to compete with the two copy buttons,
   large enough to hit — it sits clear of the heading's line. */
.codex {
  position: absolute;
  top: 0.45rem;
  right: 0.55rem;
  width: 2rem;
  height: 2rem;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-size: 1.25rem;
  line-height: 1;
  background: none;
  border: none;
  border-radius: 50%;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.codex:hover {
  background: color-mix(in srgb, var(--ink-soft) 12%, transparent);
  color: var(--ink);
}

@media (prefers-reduced-motion: reduce) {
  .codex { transition: none; }
}

.codebig {
  font-size: 1.4rem;
  font-weight: 650;
  letter-spacing: 0.02em;
  color: var(--accent);
  margin: 0.6rem 0;
  overflow-wrap: anywhere;
}

/* ------------------------------------------- leader app: desktop layout */

/* The public page keeps its narrow printed-program column; the leader app
   gets a real desktop canvas. Phone layout is unchanged. */
/* THE WORKSPACE KEEPS ITS WIDTH WHATEVER THE TEXT SIZE. Everything here is
   sized in rem, so scaling the text scaled the column holding it too: smaller
   text gave a NARROWER workspace and wider empty margins, and you ended up
   seeing the same amount of the callings table, only smaller. That defeats the
   point of sizing down. Dividing by the step's own scale (set beside the font
   sizes, near the foot of this file) cancels that out, so the column stays the
   width it is at normal text and only the contents change size. Smaller text
   now genuinely fits more rows and more columns on the screen.

   Not applied to the public page, which has no text control and stays a 40rem
   phone column; nor to the page replicas inside the app (.wp-paper,
   .annpaper), which are pictures of that phone page and must keep their own
   proportions. */
body.leader .wrap {
  /* 84rem. Round 156 took this 76 -> 88 for room for the tab bar at the
     larger text sizes, a bigger live preview and more of the Callings table;
     Derek, Round 257: "it is too wide."
     THE LIVE PREVIEW SETS THE FLOOR, and 84 IS THAT FLOOR. The printed
     column is 48rem = 768px and the pane is whatever the split leaves after
     the 27rem form, the 1.2rem gap and .wrap's own 20px of padding a side,
     so the sheet is shown at full size only while the pane clears 768px.
     MEASURED at a 1920px window, scale by workspace width: 80rem -> 0.947,
     82 -> 0.988, 84 -> 1, 88 -> 1. So 84 is the narrowest that still shows
     the sheet 1:1; below it the preview is uniformly scaled, which keeps
     every line break (it is a transform, not a re-flow) but reads smaller.
     Re-measure this if the form column or the printed width ever moves.
     The tab bar has wrapped since Round 156, so the narrower workspace
     costs it nothing it cannot absorb. Still divided by the text scale, so the workspace
     keeps its pixel width whatever A+/A- says (Round 10), and never wider
     than the window, which is what a block's max-width means anyway. */
  max-width: calc(84rem / var(--text-scale, 1));
}

/* The same calc as .wrap above, and for the same reason: without it the fixed
   status strip keeps a flat width while the workspace it belongs to divides by
   the text scale, so at the smallest step the strip overhangs the column it is
   reporting on and at the largest it stops short. The comment above .wrap
   already asks the next reader to keep the two halves in step — this was the
   half that was not. */
body.leader .savebar-inner {
  max-width: calc(84rem / var(--text-scale, 1));
}

body.leader .unlock {
  max-width: 26rem;
  margin-inline: auto;
}

/* `body.leader .hero` WAS HERE. Round 43 deleted the .hero block down in the
   badge section and missed this one, which lives in a different part of the
   file — so the class had one surviving rule and still no emitter. */

.twocol {
  display: grid;
  gap: 0 1.4rem;
  grid-template-columns: 1fr;
  align-items: start;
}

.cardgrid {
  display: grid;
  gap: 0.9rem;
  grid-template-columns: 1fr;
  align-items: start;
  margin-bottom: 0.9rem;
}

.cardgrid > .edit-item {
  margin-bottom: 0;
}

@media (min-width: 64rem) {
  .twocol {
    grid-template-columns: 1fr 1fr;
  }

  .cardgrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  body.leader .statgrid {
    grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  }

  body.leader .acard > h2 {
    font-size: 1.3rem;
  }
}

@media (min-width: 88rem) {
  .cardgrid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* ------------------------------------------------ leader app: navigation */

/* One quiet line. Related tabs sit together and the groups are held apart by
   space alone — no headings, no rules. The clustering is felt, not announced. */
.tabgroup {
  display: flex;
  gap: 0.1rem;
}

/* A printer's dinkus between the groups — the same diamond as the ornament on
   the Welcome page and the printed program. It holds the groups apart and
   makes the division deliberate rather than accidental whitespace. */
.tabgroup + .tabgroup::before {
  content: '';
  flex: none;
  align-self: center;
  width: 5px;
  height: 5px;
  margin: 0 1.6rem 0.15rem;
  transform: rotate(45deg);
  background: color-mix(in srgb, var(--accent) 34%, transparent);
}

/* The base the text-size buttons are cut from. It was the whole utility row
   once; everything else in that row now lives in the account menu. */
.utilbtn {
  font: inherit;
  font-size: 0.9375rem;
  background: none;
  border: none;
  padding: 0;
  color: var(--ink-soft);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-color: color-mix(in srgb, var(--ink-soft) 32%, transparent);
}

.utilbtn:hover {
  color: var(--accent);
  text-decoration-color: currentColor;
}

/* Narrow: the headings and rules come off and it returns to one plain row. */
@media (max-width: 63.99rem) {
  .tabgroup + .tabgroup::before { margin: 0 0.7rem 0.15rem; }
}

/* The open tab is marked three ways — accent colour, the rule beneath it, and
   bold text. Bolding normally makes a tab row jitter as the label grows; it
   cannot here, because .tablabel reserves the BOLD width on every tab (see
   below), so only the weight changes and the row stays put. */
.tabbtn {
  position: relative;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.012em;
  white-space: nowrap;
  padding: 0.5rem 0.55rem 0.62rem;
  background: none;
  border: none;
  border-radius: 0;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 0.15s ease;
}

/* The marker is inset from the label rather than spanning its padding, with
   softened ends — it reads as struck under the word, not as a table border. */
.tabbtn::after {
  content: '';
  position: absolute;
  left: 0.55rem;
  right: 0.55rem;
  bottom: 0;
  height: 2px;
  border-radius: 999px;
  background: transparent;
  transition: background-color 0.15s ease;
}

/* No filled block on hover — a painted rectangle in a tab row reads as heavy. */
.tabbtn:hover {
  color: var(--ink);
  background: none;
}

.tabbtn:hover::after {
  background: color-mix(in srgb, var(--ink-soft) 32%, transparent);
}

.tabbtn.active,
.tabbtn.active:hover {
  color: var(--accent);
  background: none;
}

.tabbtn.active .tablabel {
  font-weight: 700;
}

/* Every label silently carries an invisible bold copy of itself at zero height.
   It sets the label's width, so switching tabs changes the ink of a word but
   never its footprint — nothing to the right of it moves. */
.tablabel {
  display: inline-block;
}

.tablabel::before {
  content: attr(data-label);
  display: block;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 700;
  pointer-events: none;
}

.tabbtn.active::after,
.tabbtn.active:hover::after {
  background: var(--accent);
}

.tabbtn .tab-count {
  margin-left: 0.3rem;
  font-size: 0.7rem;
  font-weight: 650;
  padding: 0 0.4rem;
  background: var(--notify);
  color: var(--on-notify);
}

@media (prefers-reduced-motion: reduce) {
  .tabbtn,
  .tabbtn::after { transition: none; }
}

/* The badge keeps one identity in both states — the selected tab earns its
   emphasis from size, weight and a soft halo, not from inverting the colour
   (a white bubble on cream reads as a hole, not a count). */
.tabbtn.active .tab-count {
  /* No halo any more (Round 153, Derek): the open tab's badge only darkens. */
  background: var(--notify);
  color: var(--on-notify);
  box-shadow: none;
}

/* THE OLD HOME TAB'S HERO AND STAT CARDS WERE HERE, and are deleted.
   Round 3 replaced that screen with the Welcome tab and moved the counts onto
   the tab bar as badges: `.hero`, `.hero-label`, `.hero-state`,
   `.hero-actions`, `.statgrid`, `.stat`, `.stat-num` and `.stat-label` have
   had no emitter since. Checked against app.js, index.html, install.js AND
   worker.js — the .ann* lesson — before removing.
   `.chip` below is LIVE (five emitters) and stays. */

/* ------------------------------------------------- leader app: status chips */

.chip {
  display: inline-block;
  text-transform: uppercase;
  border-radius: 999px;
  padding: 0.12rem 0.6rem;
  border: 1px solid var(--line);
  color: var(--ink-soft);
  background: transparent;
  white-space: nowrap;
}

.chip.ready {
  background: var(--success-bg);
  border-color: var(--success-line);
  color: var(--success-ink);
}

.chip.hold {
  background: var(--badge-bg);
  border-color: var(--badge-bg);
  color: var(--badge-ink);
}

.chip.release {
  background: #f3ddd3;
  border-color: #f3ddd3;
  color: #7c3a24;
}

@media (prefers-color-scheme: dark) {
  .chip.release {
    background: #472c1f;
    border-color: #472c1f;
    color: #eda98d;
  }
}

/* ------------------------------------------- leader app: lists & accordions */

.searchbar {
  margin: 0.2rem 0 1rem;
}

.filterrow input[type='search'],
.searchbar input[type='search'] {
  flex: 1;
  min-width: 11rem;
  width: auto;
}

/* AND STOPS THERE. flex: 1 above let it take every pixel the row was not
   using — 908px of a 1368px row on the Callings tab, a search box eleven
   times the width of anything anyone types into it. It still grows to fill a
   narrow row; it just has a sensible ceiling now. (.searchbar is excluded on
   purpose: that one is a full-width bar of its own, not one control in a
   row of them.) */
.filterrow input[type='search'] {
  max-width: 24rem;
}

.searchbar input[type='search'] {
  width: 100%;
}

details.acc {
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--card);
  margin-bottom: 0.8rem;
  overflow: hidden;
}

details.acc > summary {
  cursor: pointer;
  list-style: none;
  padding: 0.85rem 1.05rem;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  font-weight: 650;
}

details.acc > summary::-webkit-details-marker {
  display: none;
}

details.acc > summary::after {
  content: '▾';
  color: var(--ink-soft);
  transition: transform 0.15s;
}

details.acc[open] > summary::after {
  transform: rotate(180deg);
}

details.acc > .acc-body {
  padding: 0 1.05rem 0.9rem;
}

.acc-meta {
  font-weight: 400;
  color: var(--ink-soft);
  font-size: 0.875rem;
  margin-left: auto;
}

.filterrow {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

/* "Saved ✓" after a cell edit on the Callings and Members tables — the two
   saves in the app that used to say nothing. It settles rather than flashes
   (see cellSaved() in app.js), and it holds its place when empty so the
   controls beside it do not shift as it comes and goes. The conducting
   sheet's own .save-status voice, at the far end of the filter row. */
.cellsave {
  margin-left: auto;
  align-self: center;
  min-width: 4.5rem;
  text-align: right;
  font-size: 0.875rem;
  color: var(--accent);
  transition: opacity 0.3s ease;
}

.cellsave:empty {
  opacity: 0;
}

.togglebtn {
  font: inherit;
  font-size: 0.875rem;
  font-weight: 650;
  padding: 0.35rem 0.85rem;
  border-radius: 999px;
  border: 1.5px solid var(--line);
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
}

.togglebtn.on {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}

.rowline {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}

.rowline:last-child {
  border-bottom: none;
}

/* --------------------------------------------------- leader app: stand view */

.standview {
  position: fixed;
  inset: 0;
  z-index: 60;
  overflow-y: auto;
  background: var(--paper);
  padding: 0 1.25rem 4rem;
}

.stand-inner {
  max-width: 40rem;
  margin-inline: auto;
  font-size: 1.15rem;
  line-height: 1.65;
}

.stand-top {
  position: sticky;
  top: 0;
  background: var(--paper);
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 0.75rem 0;
  z-index: 5;
}

.stand-h {
  text-align: center;
  padding-top: 0.6rem;
}

.stand-h .ward {
  font-size: 1.5rem;
  margin-bottom: 0.2rem;
}

.stand-sec {
  text-transform: uppercase;
  color: var(--accent);
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.25rem;
  margin: 1.6rem 0 0.6rem;
}

.stand-script {
  font-style: italic;
  color: var(--ink-soft);
  font-size: 1.05rem;
  margin: 0.4rem 0;
}

.stand-name {
  font-weight: 650;
}

.stand-center {
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  font-size: 0.9rem;
  font-weight: 650;
  color: var(--ink-soft);
  margin: 1.3rem 0;
}

.stand-warn {
  color: var(--badge-ink);
  background: var(--badge-bg);
  border-radius: var(--r-item);
  padding: 0.35rem 0.7rem;
  font-size: 0.95rem;
  font-weight: 650;
  margin: 0.3rem 0;
}

.stand-notes {
  border: 1px dashed var(--line);
  border-radius: var(--r-card);
  padding: 0.7rem 0.9rem;
  color: var(--ink-soft);
  font-size: 1rem;
  margin-top: 1.6rem;
}

.stand-empty {
  /* A step darker than the leader-dot colour it wore (Round 192); on paper
     the print block gives it a grey of its own. */
  color: color-mix(in srgb, var(--dots) 70%, var(--ink-soft));
}

@media print {
  .tabbar, .topbar, .savebar, .toast, .stand-top {
    display: none !important;
  }
  body.standing > .wrap {
    display: none;
  }
  .standview {
    position: static;
    overflow: visible;
    padding: 0;
  }
  body {
    border-top: none;
    background: #fff;
  }
}

/* ---------------------------------------------- leader app: status boards */

.board {
  display: grid;
  gap: 0.8rem;
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 44rem) {
  .board {
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  }
}

/* THE KANBAN'S COLUMNS WERE HERE, and are deleted. Round 15 removed the board
   view; `.bcol`, `.bcol.ready`, `.bcol-h`, `.bcol-empty` and `.bcol-note` have
   had no emitter since.
   `.bcard` and everything under it are LIVE — seventeen emitters — because the
   Bishopric tab's Sunday confirmation cards still use them. Only the COLUMNS
   went; the cards that used to sit in them did not. */

.bcard {
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: var(--paper);
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.5rem;
}

.bcard-h {
  display: flex;
  justify-content: space-between;
  gap: 0.4rem;
  align-items: baseline;
  flex-wrap: wrap;
}

.bcard-sub {
  color: var(--ink-soft);
  font-size: 0.85rem;
}

.bcard-meta {
  color: var(--ink-soft);
  font-size: 0.78rem;
  margin-top: 0.15rem;
}

.bcard .biznote {
  font-size: 0.85rem;
}

.bcard-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.45rem;
  align-items: center;
}

.bcard-actions .btn,
.bcard-actions .togglebtn {
  min-height: 1.9rem;
  padding: 0.15rem 0.6rem;
  font-size: 0.8125rem;
}

.bcard details > summary {
  font-size: 0.8125rem;
  color: var(--ink-soft);
  cursor: pointer;
  margin-top: 0.35rem;
}

.bcard .field {
  margin-bottom: 0.5rem;
}

.bcard select {
  width: 100%;
  font-size: 0.875rem;
  padding: 0.3rem 0.4rem;
}

/* -------------------------------------------- leader app: the whiteboard */

.wb {
  columns: 21rem;
  column-gap: 1rem;
}

.wb-org {
  break-inside: avoid;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 0.7rem 0.8rem 0.55rem;
  margin: 0 0 1rem;
}

.wb-h {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  border-bottom: 2px solid var(--accent);
  padding-bottom: 0.35rem;
  margin-bottom: 0.3rem;
}

.wb-h strong {
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent);
}

.wb-row {
  display: grid;
  grid-template-columns: minmax(6rem, 1fr) minmax(7rem, 1.1fr) auto auto;
  gap: 0.35rem;
  align-items: center;
  padding: 0.32rem 0.3rem;
  margin-inline: -0.3rem;
  border-radius: var(--r-item);
  border-bottom: 1px solid var(--line);
}

.wb-sa {
  width: 1.05rem;
  height: 1.05rem;
  accent-color: var(--accent);
  cursor: pointer;
}

.wb-sa-label {
  display: inline-flex;
  align-items: center;
  gap: 0.28rem;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
}

.tenure-1 { background: color-mix(in srgb, var(--tenure) 8%, transparent); }
.tenure-2 { background: color-mix(in srgb, var(--tenure) 22%, transparent); }
.tenure-3 { background: color-mix(in srgb, var(--tenure) 38%, transparent); }
.tenure-4 { background: color-mix(in srgb, var(--tenure) 56%, transparent); }

/* THE DEEPER THE SHADING, THE HARDER ITS OWN ROW WAS TO READ. The calling
   title is the only thing identifying a whiteboard row, and it is set in the
   soft ink — which composites to 3.68:1 on a tenure-3 row and 2.86:1 on a
   tenure-4 one. So the rows the shading is drawing attention to were the
   least legible on the board. Full ink measures 9.8 and 7.61, and reads as
   the row asserting itself rather than fading, which is the point. */
.tenure-3 .wb-title,
.tenure-4 .wb-title {
  color: var(--ink);
}

.wb-legend {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
  margin: -0.4rem 0 0.9rem;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.wb-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

.wb-swatch {
  width: 1rem;
  height: 1rem;
  border-radius: var(--r-detail);
  border: 1px solid var(--line);
  display: inline-block;
}

.wb-org .wb-row:last-of-type {
  border-bottom: none;
}

.wb-title {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.wb-row select {
  width: 100%;
  padding: 0.28rem 0.35rem;
  font-size: 0.875rem;
  border-radius: var(--r-item);
}

.wb-row input[type='text'] {
  padding: 0.28rem 0.4rem;
  font-size: 0.875rem;
}

.wb-row.vacant select {
  background: var(--badge-bg);
  border-color: var(--badge-bg);
  color: var(--badge-ink);
  font-weight: 650;
}

.wb-holder {
  font-size: 0.9rem;
  text-align: right;
}

.wb-more {
  min-width: 1.9rem;
  min-height: 1.9rem;
  font-size: 0.9rem;
  border-radius: var(--r-item);
}

/* THE OPEN PANEL IS A CARD OF ITS OWN (Round 179, Derek: "it's hard to see
   where the member information ends and the next calling starts"). It sits
   inside the row's tenure tint on the card colour, with a sage-leaning edge
   and rounded corners, and a little air below it before the next row. */
.wb-detail {
  grid-column: 1 / -1;
  margin: 0.4rem 0 0.45rem;
  padding: 0.75rem 0.8rem 0.85rem;
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--accent) 38%, var(--line));
  border-radius: var(--r-item);
  box-shadow: var(--lift);
}

.wb-add {
  width: 100%;
  text-align: left;
  font: inherit;
  font-size: 0.8125rem;
  color: var(--ink-soft);
  background: none;
  border: none;
  border-top: 1px dashed var(--line);
  padding: 0.45rem 0 0.3rem;
  cursor: pointer;
  margin-top: 0.2rem;
}

.wb-add:hover {
  color: var(--accent);
}

/* ------------------------------------------- leader app: desktop density */

/* ADD A MEMBER (Round 98, Derek). The card is as wide as the members box
   above it and stands clear of it; the title and the segmented control
   share one row; the chosen form sits beneath at a reading width. */
body.leader .addcard {
  max-width: 70rem;
  margin-top: 1.75rem;
  /* No global border-box in this file (Round 29): without this the padding
     and border sit outside the cap and the card runs 34px past the box. */
  box-sizing: border-box;
}
body.leader .memtab.no-notes .addcard {
  max-width: 58rem;
}
.addhead {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem 1.5rem;
  flex-wrap: wrap;
}
.addtitle {
  margin: 0;
}
.addtabs .sheettab {
  padding: 0.42rem 0.9rem;
}
.addbody {
  max-width: 40rem;
  margin-top: 1.1rem;
}
.addbody .addrow {
  justify-content: flex-start;
}
.addbody .addrow .btn {
  flex: 0 0 auto;
  min-width: 7rem;
}

.composer .btn-primary {
  margin-top: 0.3rem;
}

/* LEAVE, THEN COMMIT (Round 272, Derek: "the discard button should be first").
   The margin moves off the button and onto the row, so the gap the sheet's
   composer holds open for its "Added to program" line (Round 260) is
   unchanged -- it just belongs to the row now. */
.composer-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-top: 0.3rem;
}

.composer-actions .btn-primary {
  margin-top: 0;
}

/* "A light grayish color" (Derek). A wash of the page's own ink over
   nothing, so it is a pale grey on cream and a pale grey on dark paper from
   one rule -- a literal hex could only have served one of them. */
.btn.btn-quiet {
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  border-color: var(--line);
  color: var(--ink-soft);
}

.btn.btn-quiet:hover:not(:disabled),
.btn.btn-quiet:focus-visible {
  background: color-mix(in srgb, var(--ink) 13%, transparent);
  border-color: var(--ink-soft);
  color: var(--ink);
}

/* THE SAME GREEN AS THE CHOSEN DATE PILL (Derek). Round 204 softened every
   primary button to 74% of the accent except Print; this is the second
   exception, and it is written at (0,4,1) so it wins wherever it sits in the
   file rather than by being further down than the rule it beats. */
body.leader .acard.composer .btn-primary,
body.leader .acard.composer .btn-primary:hover:not(:disabled) {
  background: var(--accent);
  border-color: var(--accent);
}

/* The x sits in the card's own corner, so the card is the positioning box.
   .acard sets no position of its own, so this is where it gets one. */
.acard.composer {
  position: relative;
}

.composer-x {
  position: absolute;
  top: 0.5rem;
  right: 0.6rem;
  font: inherit;
  font-size: 1.35rem;
  line-height: 1;
  padding: 0.15rem 0.4rem;
  background: none;
  border: 0;
  border-radius: var(--r-detail, 4px);
  color: var(--ink-soft);
  cursor: pointer;
}

.composer-x:hover,
.composer-x:focus-visible {
  background: color-mix(in srgb, var(--ink-soft) 12%, transparent);
  color: var(--ink);
}

@media (min-width: 64rem) {
  body.leader {
    font-size: 1rem;
  }

  body.leader .acard {
    padding: 1rem 1.05rem;
  }

  /* `select` was left out of this pass, so a dropdown stayed at the base
     1rem while the text box beside it dropped to 0.9375rem — and 64rem is the
     leader app's home width, so that mismatch is the normal case, not an edge
     one. Every date-and-time and name-and-role pair on the conducting sheet
     had two different type sizes sitting side by side. */
  body.leader input[type='text'],
  body.leader input[type='date'],
  body.leader input[type='password'],
  body.leader input[type='search'],
  body.leader select,
  body.leader textarea {
    padding: 0.5rem 0.6rem;
    font-size: 0.9375rem;
  }

  body.leader .field {
    margin-bottom: 0.65rem;
  }
}

/* ------------------------------------- public page: no meeting + address */

.nomeeting {
  text-align: center;
  padding: 2.5rem 0.5rem;
}

.nomeeting-title {
  font-size: 1.35rem;
  font-weight: 650;
  color: var(--accent);
  margin: 0 0 0.5rem;
}

.nomeeting-sub {
  margin: 0;
  color: var(--ink-soft);
  font-style: italic;
}

/* The passage under a no-meeting notice. Quieter than the Welcome tab's
   thought (this is a phone page read in passing, not a title page), set as
   reading text with the reference in the program's own small-caps voice. */
.nomeeting .fleuron {
  margin: 1.6rem 0 0;
}

.nomeeting-thought {
  margin: 1.25rem auto 0;
  max-width: 30rem;
}

.nomeeting-thought-text {
  margin: 0;
  font-size: 1.1rem;
  line-height: 1.6;
  text-wrap: balance;
}

.nomeeting-thought-ref {
  margin: 0.7rem 0 0;
  color: var(--accent);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* -------------------------------------- leader app: history + youth bits */

.histhint {
  display: block;
  font-size: 0.8125rem;
  color: var(--ink-soft);
  margin-top: 0.25rem;
  min-height: 1em;
}

/* --tenure is #c2643f: 3.75:1 on paper, 3.98:1 on card — both under AA in
   light mode, on the one hint that exists in order to be noticed. --warn-ink
   is the same warm family, is defined in both themes, and measures 5.29:1.
   .histhint.booked already uses it, so this also makes the three agree. */
.histhint.stale {
  color: var(--warn-ink);
  font-weight: 650;
}

.servicelist {
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.servicelist li {
  padding: 0.15rem 0;
  border-top: 1px dotted var(--line);
}

.youthchip {
  text-transform: uppercase;
  border-radius: 999px;
  padding: 0.05rem 0.45rem;
  background: var(--badge-bg);
  color: var(--badge-ink);
}

.histhint.recent {
  color: var(--warn-ink);
  font-weight: 650;
}

/* ---------------------------------------- printing the conducting sheet */

/* The half inch is on `body` in the print block below, NOT here — and that
   is the whole point. A browser draws its own header and footer (the date
   and time, the page title, the URL, the page number) INSIDE the page
   margin box, so a page with no margin has nowhere to put them and they do
   not print. Padding on the content gives the margin back — and it is the
   ONE place the printed margin is set now, so change it there, not here.

   The one thing it costs: padding falls at the start and end of the
   content, not on every sheet of paper, so a conducting sheet long enough
   to run onto a second page starts that page flush at the top edge. The
   sheet is built to fit one page (see the print block's tightening) and
   that is the trade Derek chose: no browser furniture on the page he
   prints every week. Put `margin: 0.3in` back here to undo it. */
@page {
  size: letter portrait;
  margin: 0;
}

/* Print goes straight to the browser's dialog (Round 113), so the stand
   view is mounted only to be printed and is never seen. It has to stay in
   the document — the print rules make IT the page — so it is hidden on
   screen alone. `visibility`, not `display`: a display:none element does
   not print either, which would send a blank sheet to the printer. */
@media screen {
  .standview.printing {
    visibility: hidden;
  }
}

@media print {
  /* Nothing but the sheet itself reaches the paper.
     `.acard.sheet-head` WAS in this list and has no emitter anywhere — Round 9
     merged the picker and the sheet header into one shelf and renamed it
     `.acard.sheetpicker`. So the one card this rule existed to suppress was
     the one card that still printed: pressing Ctrl+P on the Conducting tab
     gave you a page of month dropdowns, date chips and buttons. The preview
     bar and the still-blank chips are screen furniture for the same reason. */
  .tabbar, .topbar, .savebar, .toast, .stand-top, .previewpane, .sheetform,
  .acard.sheetpicker, .previewbar, .readiness, .codeoverlay {
    display: none !important;
  }

  /* The ward's tab strip inside the announcements preview is a PICTURE of the
     strip, not a working one — its own comment says so, and pointer-events
     are already off. On paper three inert pills are just three pills. */
  .annstand .tabs,
  .annpaper .tabs {
    display: none !important;
  }

  body.standing > .wrap {
    display: none;
  }

  /* The page margin is 0 so the browser cannot print its date/URL header —
     see the `@page` rule above. This padding IS the printed margin, and the
     only place it is set. 0.25in (Round 193, Derek: "way, way smaller so
     that the conducting sheet can fill much more of the entire page"; 0.5in
     for Round 190, 0.6in for Round 189, 0.3in from Round 110). This IS the
     floor: a printer cannot lay ink in the last few millimetres of the
     paper, and Chrome's own "Minimum" margin is the hardware's. The column
     is 8in = 48rem, which .paperlike .stand-inner and .previewpage both
     take, so the preview is the paper. Keep body.admin below in step. */
  body {
    border-top: none;
    background: #fff;
    color: #000;
    font-size: 10.5pt;
    line-height: 1.35;
    padding: 0.25in;
  }

  /* `body.admin` holds a 7rem foot open for the sticky status strip, and at
     (0,1,1) it outranks the shorthand above — so on paper the sheet carried
     an inch and a sixth of dead space at the bottom, which is enough to tip
     a full Sunday onto a second page. Measured: 112px before, 29px after. */
  body.admin {
    padding-bottom: 0.25in;
  }

  .standview {
    position: static;
    overflow: visible;
    padding: 0;
    background: #fff;
  }

  .stand-inner {
    max-width: none;
    font-size: 10.5pt;
  }

  /* Printing the announcements from Full screen: the card is a screen device
     (a page floating on a desk), so on paper it becomes the page itself. The
     draft marker goes too — an unpublished notice must not print as though the
     ward can already read it. */
  .annstand .annpaper {
    max-width: none;
    margin: 0;
    padding: 0;
    border: 0;
    background: #fff;
    font-size: 10.5pt;
  }

  .annstand .ann.ann-draft {
    display: none !important;
  }

  .stand-h {
    padding-top: 0;
    margin-bottom: 0.35rem;
  }

  .stand-h .ward {
    font-size: 15pt;
    margin-bottom: 0.1rem;
  }

  .stand-h .mdate {
    font-size: 11.5pt;
  }

  .stand-inner p {
    margin: 0.16rem 0;
    orphans: 2;
    widows: 2;
  }

  .stand-inner ul {
    margin: 0.2rem 0;
    padding-left: 1.1rem;
  }

  /* Section headings stay with the lines that follow them. */
  .stand-sec {
    margin: 0.6rem 0 0.2rem;
    font-size: 8.5pt;
    color: #000;
    border-bottom: 0.5pt solid #999;
    break-after: avoid;
    page-break-after: avoid;
  }

  .stand-center {
    margin: 0.45rem 0;
    font-size: 9.5pt;
  }

  .stand-script {
    font-size: 9.5pt;
    color: #333;
    margin: 0.16rem 0;
  }

  .stand-warn {
    background: none;
    color: #000;
    font-weight: 700;
    padding: 0;
  }

  .stand-empty {
    color: #999;
  }

  .stand-notes {
    border: 0.5pt dashed #999;
    color: #333;
    margin-top: 0.5rem;
    padding: 0.35rem 0.5rem;
    break-inside: avoid;
  }
}

/* ------------------------------------------------- leader app: welcome tab */

/* A title page: the greeting sits optically centred in whatever room the
   window gives, rather than pinned to the top with a void beneath it. */
.welcome {
  max-width: 40rem;
  margin: 0 auto;
  padding: 1.5rem 0 3rem;
  text-align: center;
  min-height: max(21rem, calc(100vh - 17rem));
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.welcome-greet {
  margin: 0;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.8125rem;
  font-weight: 600;
}

.welcome .fleuron {
  margin: 1rem 0 1.6rem;
  color: var(--accent);
}

.thought {
  min-height: 6rem;
}

.thought-text {
  margin: 0 auto;
  max-width: 34rem;
  font-size: 1.35rem;
  line-height: 1.55;
  /* Even the lines out rather than leaving a long one over a stub. */
  text-wrap: balance;
}

.thought-ref {
  margin: 1rem 0 0;
  color: var(--accent);
  font-style: italic;
  font-size: 1.05rem;
}

/* Minimal by design: the small-caps label and its hairline are the whole
   button. The rule underneath is what says "clickable"; the glyph says what
   it does. */
/* 0.62rem is 9.9px, and the 62% mix put it at 2.39:1 — the least readable
   control in the app, and the only thing to press on the page every leader
   opens onto. The Welcome tab is meant to be calm, which the size, the small
   caps and the hairline underneath all still do; being nearly invisible was
   not part of that. 0.7rem at the full accent is 4.78:1. */
.thought-again {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font: inherit;
  font-size: 0.75rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--accent);
  background: none;
  border: none;
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  border-radius: 0;
  cursor: pointer;
  padding: 0.35rem 0.15rem 0.25rem;
  margin-top: 2.25rem;
  align-self: center;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.thought-again::before {
  content: '↻';
  font-size: 0.82rem;
  letter-spacing: 0;
  line-height: 1;
  display: inline-block;
  transition: transform 0.4s ease;
}

.thought-again:hover {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

.thought-again:hover::before {
  transform: rotate(-180deg);
}

.thought-again:active {
  transform: translateY(1px);
}

@media (prefers-reduced-motion: reduce) {
  .thought-again,
  .thought-again::before {
    transition: none;
  }
}

.welcome-sunday {
  margin-top: 3rem;
  padding-top: 1.8rem;
  border-top: 1px solid var(--line);
}

.welcome-sunday .mdate {
  margin: 0.15rem 0 1.1rem;
  font-size: 1.35rem;
}

/* `.tab-count.muted` WAS HERE and is deleted. It was the quiet variant for
   "counts that are just facts", and Round 3 answered that question a
   different way: Members and Snapshot carry no badge at all, because a badge
   means something is waiting. `badge()` kept a `muted` argument no caller
   had passed since. Grepped app.js, index.html, install.js and worker.js —
   the .ann* lesson — before removing. */

.sheet-actions {
  display: flex;
  gap: 0.45rem;
  align-items: center;
  flex-wrap: wrap;
}

.modalcard .field {
  margin-bottom: 0;
  margin-top: 0.9rem;
}

.modalcard input[type='date'] {
  width: 100%;
}

/* ------------------------------------------------------- flyer photos */

/* Flyers sit in a ROW that scrolls sideways and never wraps. A flyer is an
   attachment to a notice, not the notice itself — at full width it was the
   loudest thing on the announcements page and pushed everything else below the
   fold. Small enough to skim, one tap to read.
   `.ann-photo` is kept because a single full-width flyer is still what the
   Live page replica and the composer preview draw for a legacy announcement. */
.ann-photo {
  display: block;
  width: 100%;
  height: auto;
  margin-top: 0.9rem;
  border-radius: var(--r-item);
  border: 1px solid var(--line);
}

.ann-shots {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.9rem;
  overflow-x: auto;
  /* No wrapping, ever: ten flyers is one scrolling row, not three stacked
     ones that push the next announcement off the screen. */
  flex-wrap: nowrap;
  padding-bottom: 0.35rem;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}

.ann-shot {
  flex: 0 0 auto;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: var(--card);
  cursor: zoom-in;
  overflow: hidden;
  line-height: 0;
  scroll-snap-align: start;
}

.ann-shot img {
  display: block;
  width: 6.5rem;
  height: 6.5rem;
  object-fit: cover;
}

/* A LONE FLYER IS SHOWN WHOLE. A flyer is portrait — the live one is
   1147x1400 — and `cover` in a landscape box showed 60 per cent of its height
   with 20 per cent cut off the top and 20 off the bottom: on a real poster,
   the event name and the contact line both gone, leaving a middle slice with
   no title. Letterboxed, it is instantly recognisable as a poster, which is
   also the cue that there is something worth opening.
   The multi-flyer strip keeps `cover`: there the squares are a rhythm, and
   the lightbox one tap away shows each in full. */
.ann-shots:not(.is-many) .ann-shot img {
  object-fit: contain;
  background: var(--card);
}

.ann-shot:hover,
.ann-shot:focus-visible {
  border-color: var(--accent);
}

/* A single flyer has nothing to scroll through, so it is allowed to be bigger —
   the strip only earns its small squares when there is a row of them. */
.ann-shots:not(.is-many) .ann-shot img {
  width: 11rem;
  height: 8rem;
}

/* The scrollbar is the only thing telling you there is more to the right, so on
   the platforms that let us style it, it is made visible rather than hidden. */
.ann-shots::-webkit-scrollbar {
  height: 6px;
}

.ann-shots::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--ink-soft) 35%, transparent);
  border-radius: 999px;
}

/* ---------------------------------------------------------- the lightbox */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(20, 16, 10, 0.88);
  /* SWIPE WALKS THE GALLERY (Round 350), so the browser must not claim a
     horizontal drag for its own overscroll-to-go-back. pan-y keeps the
     vertical gesture and pinch-zoom keeps a photograph zoomable -- plain
     touch-action: none would have taken pinch away, and a portrait is
     worth pinching into. */
  touch-action: pan-y pinch-zoom;
}

body.lb-open {
  overflow: hidden;
}

.lb-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--r-detail);
}

.lb-close,
.lb-step {
  position: absolute;
  font: inherit;
  color: #faf6ee;
  background: rgba(20, 16, 10, 0.55);
  border: 1px solid rgba(250, 246, 238, 0.35);
  border-radius: 50%;
  cursor: pointer;
  line-height: 1;
}

.lb-close {
  top: 1rem;
  right: 1rem;
  width: 2.6rem;
  height: 2.6rem;
  font-size: 1.5rem;
}

/* Big enough for a thumb, and held clear of the picture's edges so they do not
   read as part of the flyer. */
.lb-step {
  top: 50%;
  transform: translateY(-50%);
  width: 3rem;
  height: 3rem;
  font-size: 1.75rem;
}

.lb-prev { left: 0.75rem; }
.lb-next { right: 0.75rem; }

.lb-close:hover,
.lb-step:hover {
  background: rgba(20, 16, 10, 0.8);
}

.lb-count {
  position: absolute;
  bottom: 1rem;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  color: #faf6ee;
  font-size: 0.875rem;
  letter-spacing: 0.06em;
}

/* A flyer is a picture on a page, not a document to print. */
@media print {
  .lightbox { display: none !important; }

  .ann-shots {
    overflow: visible;
    flex-wrap: wrap;
  }
}

.photofield {
  margin-top: 0.6rem;
}

/* A flyer is the exception, not the expectation — most notices never get one,
   and the three buttons here (Add / Replace / Remove) are housekeeping beside
   the announcement's own words. At the standard slim size the outlined pill
   was the heaviest thing in the form and competed with the Save button below
   it. Scoped to .photofield so other slim buttons keep their size. */
/* THE SMALL SIZE, ASKED FOR BY NAME.
   This was `.photofield .btn.slim`, and there was a second near-identical one
   at `.ptable .pactions .btn.slim` (0.78rem / 1.8rem) — two tiny sizes half a
   pixel apart, each an ANCESTOR silently redefining a modifier, so `btn slim`
   in the markup told a reader nothing about what would render. One size now,
   named, and written where the button is made.
   Used by: the flyer controls on an announcement, and "Moved out" on the
   members table. */
.btn.tiny {
  min-height: 1.9rem;
  padding: 0.18rem 0.7rem;
  font-size: 0.8125rem;
  font-weight: 600;
  border-width: 1px;
}

.photo-thumb {
  display: block;
  max-width: 100%;
  max-height: 11rem;
  width: auto;
  border-radius: var(--r-item);
  border: 1px solid var(--line);
  margin-bottom: 0.5rem;
}

.bcard .photo-thumb {
  max-height: 8rem;
  margin-top: 0.5rem;
}

/* -------------------------- conducting: announcements beside the program */

.sheetform {
  container-type: inline-size;
}

/* ALREADY PUBLISHED, UNDER THE LIST (Round 220, Derek: "underneath and
   attached to the announcement box"). Round 112's two-column .annsplit and
   its .pubtoggle are gone. One hairline off the list above; the heading row
   carries the count still waiting and two quiet actions (Hide / Show folds
   the block; "Show published announcements" brings back what was pulled
   across or left off). The items keep the .pubitem look below. */
.pubblock {
  margin-top: 0.9rem;
  padding-top: 0.65rem;
  border-top: 1px solid var(--line);
}

/* ONE CONTROL (Round 254, Derek): a full-width row with a chevron, reading
   "Show published announcements" closed and "Hide published announcements"
   open. No box and no fill at rest -- the row itself is the button, and the
   accent is the only weight it carries. Round 220's heading and its two
   quiet links (.pubblock-h, .pubblock-acts) are gone with it, and so is
   .pubdone: nothing leaves the list any more, so it can never be empty
   while there is anything published at all. */
/* THE PREVIEW IS NOT AN ANCHOR (Round 256). Its page is rebuilt a tick after
   every render, and inserting a thousand pixels of sheet back into the column
   makes the browsers own scroll anchoring adjust the window -- measured, it
   moved the page 38px with no scroll call anywhere in the app, which is what
   made pressing Add feel glitchy long after the press. The form is where the
   reader is; let the anchor be chosen there. */
.previewpane {
  overflow-anchor: none;
}

.pubfold {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  width: 100%;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--accent);
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--r-item);
  padding: 0.4rem 0.5rem;
  margin-left: -0.5rem;
  cursor: pointer;
  transition: background 0.15s ease;
}

.pubfold:hover {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

/* Pointing DOWN when the block is closed and UP when it is open (Round 256,
   Derek). The glyph is drawn pointing down, so closed needs no turn at all
   and open is a half turn -- one glyph rotated, which is how the volunteers
   fold reads (Round 197). */
.pubfold .foldchev {
  flex: none;
  width: 0.9rem;
  height: 0.9rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transform: rotate(0deg);
  transition: transform 0.15s ease;
}

.pubfold[aria-expanded='true'] .foldchev {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
  .pubfold,
  .pubfold .foldchev {
    transition: none;
  }
}

/* The count of notices not on the conducting sheet yet: the sheet tabs' old
   bubble (Round 167), the attention colour pulled toward the paper. */
.pubcount {
  display: inline-block;
  min-width: 1.35em;
  padding: 0.02rem 0.4rem;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  background: color-mix(in srgb, var(--notify) 78%, var(--paper));
  color: var(--on-notify);
}

.publist {
  margin-top: 0.35rem;
}

.pubitem {
  border-top: 1px solid var(--line);
  padding: 0.55rem 0;
}

.pubitem:first-of-type {
  border-top: none;
}

.pubitem .btn.slim {
  margin-top: 0.4rem;
}

/* A notification count stays quiet until you are on that tab, where it grows
   and bolds so the number you are working through is unmistakable. */
.tabbtn .tab-count {
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.05rem 0.45rem;
  opacity: 0.8;
  transition: font-size 0.12s ease, opacity 0.12s ease, box-shadow 0.12s ease;
}

.tabbtn:hover .tab-count {
  opacity: 1;
}

.tabbtn.active .tab-count {
  /* No growth at all (Round 153, Derek: "I don't actually want the
     notification icons or bubbles to get bigger at all anymore. They just
     darken a bit when that tab is selected"). Same size, weight and padding
     as a closed tab; the only change is opacity 0.8 -> 1, which is the
     darkening. Round 149 had cut the growth to 8%; Round 4's grow-and-halo
     is retired. The page and note icons follow, being sized in em. */
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.05rem 0.45rem;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .tabbtn .tab-count { transition: none; }
}

/* A quiet, typographic action — used where a pill would shout. */
/* NO RULE ABOVE THIS. The link applies the fields directly above it, so it has
   to read as attached to them — a hairline in between fenced it off and left
   you guessing which part of the card it meant. The only rule in this card is
   the one under the meeting type, which genuinely is a different thing. */
/* APPLY TO THE REST OF THE MONTH (Round 274, Derek: an arrow rather than the
   return glyph, not italic, and "figure out how an expert web app designer
   would design this button").

   It has ONE emitter, so this class is that button and nothing else.

   What was wrong with it, beyond the two things Derek named: it was
   display:block, width:100%, left-aligned -- a full-width block carrying a
   short italic label, which is the shape of a line of TEXT, not of a control.
   And what it does is not small: it writes the two names onto every remaining
   Sunday of the month, creating sheets that do not exist yet. A footnote was
   under-signalling a four-sheet write.

   So it is a control now: an outlined pill sized to its own words, upright,
   in the accent. That is the rank "Hymn Library" and "Planner" already
   occupy (.btn.slim), so it costs the page no new vocabulary -- and outlined,
   never filled, because Round 5 keeps the filled accent for the one primary
   action in a toolbar and this is not it.

   The arrow LEADS, where the return glyph did, and is upright: it points at
   the Sundays the names are going to. */
.quiet-action {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.875rem;
  font-weight: 600;
  font-style: normal;
  color: var(--accent);
  background: none;
  border: 1.5px solid color-mix(in srgb, var(--accent) 42%, transparent);
  border-radius: 999px;
  margin-top: 0.55rem;
  padding: 0.32rem 0.85rem;
  cursor: pointer;
}

.quiet-action::before {
  content: '→';
  font-style: normal;
  font-weight: 400;
  opacity: 0.9;
}

.quiet-action:hover:not(:disabled),
.quiet-action:focus-visible:not(:disabled) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--accent);
  text-decoration: none;
}

/* ================= the printed conducting sheet (boxed, like the old one) */

/* THE SHEET'S INKS (Round 192, Derek: "whatever shade of text everything
   currently is on that page right now, darken it a little bit more"). The
   sheet is read at a pulpit, so it takes a firmer pair than the app around
   it: its body ink a step past --ink toward black, its soft ink a quarter
   of the way from --ink-soft toward --ink. Two tokens on .cs, so every text
   rule below reads one pair; the borders keep --ink-soft (the ask was the
   text). In dark mode "darker" means "stronger", so the body ink steps
   toward white there instead, and the soft mix already moves toward that
   palette's ink. The print block forces its own greys regardless, one step
   darker than before for the same ask -- and it MUST force .cs's colour,
   or a dark-mode machine would print the light ink (Round 43). */
.cs {
  --cs-ink: color-mix(in srgb, var(--ink) 85%, #000);
  --cs-soft: color-mix(in srgb, var(--ink-soft) 75%, var(--ink));
  color: var(--cs-ink);
  font-size: 1rem;
  line-height: 1.4;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .cs {
    --cs-ink: color-mix(in srgb, var(--ink) 85%, #fff);
  }
}

.cs-block {
  border: 1px solid var(--ink-soft);
  border-radius: 3px;
  margin-bottom: 0.55rem;
  overflow: hidden;
}

.cs-title {
  text-align: center;
  font-size: 1.15rem;
  padding: 0.35rem 0.6rem;
  background: color-mix(in srgb, var(--ink-soft) 10%, transparent);
  border-bottom: 1px solid var(--ink-soft);
}

.cs-row2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--line);
}

.cs-block > .cs-row2:first-child,
.cs-title + .cs-row2 {
  border-top: none;
}

.cs-row2 > .cs-cell + .cs-cell {
  border-left: 1px solid var(--line);
}

.cs-cell {
  padding: 0.3rem 0.6rem;
}

.cs-label {
  font-style: italic;
  color: var(--cs-soft);
}

.cs-value {
  font-weight: 650;
}

/* A HYMN'S NUMBER AND TITLE (Round 191, Derek): the sacrament line's look on
   every hymn the sheet names -- italic, the soft ink, the script size -- so
   the opening, intermediate and closing hymns read like the one inside the
   double frame. The value only; the label beside it keeps its row's size,
   since it shares that row with Invocation or Benediction. Bold stays: it
   is .cs-value's own weight, as on the sacrament line. The print block
   names this class in its fixed-grey list, or a dark-mode machine would
   print the soft ink's dark-theme value (Round 43). */
.cs-value.cs-hymn {
  font-style: italic;
  color: var(--cs-soft);
  font-size: 0.95rem;
}

.cs-center {
  text-align: center;
}

.cs-left {
  text-align: left;
}

.cs-script {
  text-align: center;
  font-style: italic;
  color: var(--cs-soft);
  font-size: 0.95rem;
}

.cs-tight {
  padding-top: 0;
}

.cs-ruled {
  border-top: 1px solid var(--line);
}

.cs-gap {
  display: inline-block;
  width: 2.5rem;
}

/* The stake-business statement sits above the Ward Business heading. */
.cs-block > .cs-cell.cs-script:first-child {
  border-bottom: 1px solid var(--line);
}

.cs-heading {
  text-align: center;
  font-size: 1.05rem;
  font-style: italic;
  padding: 0.35rem 0.6rem;
  border-bottom: 1px solid var(--line);
}

.cs-heading.cs-small {
  font-size: 0.9rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-style: normal;
  color: var(--cs-soft);
}

.cs-list {
  margin: 0.25rem 0 0;
  padding-left: 1.15rem;
}

.cs-list li {
  margin-bottom: 0.15rem;
}

/* A list inside an announcement line (Round 210): nested under it, hollow
   bullets so the two levels read apart, numbers where numbers were typed,
   the marker in the sheet's soft ink. */
.cs-list li ul,
.cs-list li ol {
  margin: 0.1rem 0 0.1rem;
  padding-left: 1.1rem;
}

.cs-list li ul {
  list-style: circle;
}

.cs-list li ol {
  list-style: decimal;
}

.cs-list li li {
  margin-bottom: 0.05rem;
}

.cs-list li li::marker {
  color: var(--cs-soft);
}

/* The new member records are read out one after another, so they run THREE
   ACROSS the box (Derek) instead of down the page a name to a line: six
   move-ins made a short list look like a long one.

   A GRID OF THREE EQUAL COLUMNS, AND IT STAYS THREE (Round 287, Derek:
   "keep the 3 column formatting, even when there are only 2 names on a row.
   That keeps the columns neat"). Two things went with the flex row it
   replaced:

   -- justify-content: center, which centred a row that was not full, so the
      last two names of a list sat in the middle instead of in columns one
      and two. A grid item lands in its track whether the row is full or not,
      which IS the ask;
   -- .cs-name-wide, where a name too long for a column was given two and the
      wrap carried it down to a row with the room. Derek reversed that:
      "just allow that text box for that long name to spill over onto
      another row within that column." It wraps inside its own third now,
      and the measuring pass that decided which names were wide is gone.

   minmax(0, 1fr) is load-bearing rather than tidiness: a grid track's
   implicit minimum is auto, so one long unbroken surname would push its
   track past a third and spill the box (Round 29). The tracks are exact
   thirds, so the flex basis's hair-under-a-third fudge goes with them. */
.cs-names {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 0.6rem;
  row-gap: 0.1rem;
  text-align: center;
}

.cs-names > .cs-value {
  min-width: 0;
  overflow-wrap: break-word;
}

.cs-table {
  width: 100%;
  border-collapse: collapse;
}

.cs-table td {
  padding: 0.2rem 0.4rem;
  border-top: 1px dotted var(--line);
  vertical-align: top;
}

.cs-table tr:first-child td {
  border-top: none;
}

/* THE NAMES SIT AT THE RIGHT EDGE, THE DOTS RUNNING OUT TO EACH ONE (Round
   162, Derek, matching the ward's own page). Round 47 turned the dots
   ninety degrees into a leader, but drew it inside a 42% label column: the
   values sat at the column's edge, centred on the page, and the dots
   stopped short of them (Derek's screenshot). Right-aligning the value
   column alone was tried first and is not enough either -- a column is as
   wide as its widest entry, so the leader still stopped at the column line
   and the shorter names hung out past it. So each row is now ONE cell
   holding a flex line -- label, a growing .cs-dots spacer, value -- which
   is exactly the public page's .row / .dots idiom: the leader is the space
   between the two words, however long either is. The flex box is a div
   inside the cell, never the cell itself (a <td> set to flex leaves the
   table box model, Rounds 19 and 27). No rule between rows. */
.cs-prog td {
  border-top: none;
}

.cs-prog td.cs-leadcell {
  width: auto;
  white-space: normal;
}

.cs-leadrow {
  display: flex;
  align-items: baseline;
  gap: 0.4em;
  /* BOTH ENDS COME IN (Round 195, Derek: "such a huge gap between them that
     it makes the readability more difficult"). Round 193 widened the printed
     column to 8in, which stretched every leader to five inches and more; a
     dotted leader longer than about four stops guiding the eye and becomes a
     gap to cross. The row is capped at 36rem (6in) and centred in its box,
     so the label and the name each step about 0.85in in from the box's edge:
     a long label opposite a long name leaves under three inches of dots, a
     short one opposite a short one about four. The box's own borders stay
     full width; only the rows draw in. */
  max-width: 36rem;
  margin-inline: auto;
}

.cs-leadrow .cs-label {
  flex: none;
  white-space: nowrap;
}

.cs-leadrow .cs-dots {
  flex: 1 1 1rem;
  min-width: 1rem;
  /* --dots, not --line: --line is the hairline that divides blocks and is too
     pale to read as a leader. --dots is the token the public page's own
     leaders use. On paper the print block forces #bbb, the way every other
     rule on this sheet does, so a dark-mode machine cannot print a dark
     leader. */
  border-bottom: 1px dotted var(--dots);
  transform: translateY(-0.28em);
}

.cs-leadrow .cs-value {
  flex: 0 1 auto;
  min-width: 0;
  text-align: right;
}

.cs-sacrament {
  border: 2px double var(--ink-soft);
  margin: 0.5rem;
  border-radius: 2px;
}

.cs-admin {
  text-align: center;
  font-style: italic;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 1.05rem;
  padding: 0.45rem 0.6rem;
  border-top: 1px solid var(--line);
}

.cs-sacrament .cs-cell:first-child {
  border-bottom: none;
}

/* AN "OTHER" ITEM IS A THING THAT HAPPENS, NOT A VALUE THAT LOST ITS LABEL.
   A baby blessing, a presentation, a funeral: it spans both of the program's
   columns as a centred line, with any detail beneath it. Three things had
   gone wrong with that.
   - It was set at .cs-value's own weight (650) — the exact setting of the
     bold names in the column beside it — so it read as a stray value hanging
     under the row above rather than as an item in its own right. It takes the
     sheet's own ceremony voice instead: centred italic, the same as the
     "Ward Business" heading and ADMINISTRATION OF THE SACRAMENT.
   - Round 47 turned the program's dots ninety degrees and took the rules off
     its rows, which is right for a column of label/value lines and left this
     one with nothing at all to say where it began or ended. It carries its
     own hairlines now.
   - It is the row's FIRST child (colspan 2, so it is the only cell), so
     inside .cs-prog it was inheriting the label column's `width: 42%` and
     `white-space: nowrap` — a long title or detail could not wrap and would
     have run off the edge of the sheet. */
/* Scoped through .cs-table because that is where `.cs-table td` sets the
   cell padding this needs to beat — an item of its own wants more air than
   a line in a list of them. */
.cs-table td.cs-otherrow {
  text-align: center;
  padding: 0.45rem 0.4rem;
}

.cs-othertitle {
  font-size: 1.05rem;
  font-style: italic;
}

/* The announcement title, bold on the sheet (Round 267). It inherits the
   sheet ink, so the print block colour rules already cover it. */
.cs-anntitle {
  font-weight: 700;
}

.cs-otherdetail {
  margin-top: 0.05rem;
  font-size: 0.86em;
  font-style: italic;
  color: var(--cs-soft);
}

/* A musical number's details, under the performers on a leader row rather
   than centred under an "other" item's title -- so right-aligned, under the
   name it belongs to. It borrows .cs-otherdetail for everything else, which
   also keeps it in the print block's fixed-grey list and its point size
   without a second rule to remember. */
.cs-subline {
  text-align: right;
}

.cs-prog td.cs-otherrow {
  width: auto;
  white-space: normal;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

/* The closing hymn's row draws a rule above itself, so an "other" that ends
   the program must not draw a second one a few pixels above that. */
.cs-prog tr:last-child td.cs-otherrow {
  border-bottom: none;
}

/* The sheet keeps its boxes on paper, just tighter. */
/* THE SHEET AS IT PRINTS -- ONE SET OF RULES (Round 155, Derek: "I want the
   preview to be an accurate representation of what the printed version will
   look like"). The live preview used to draw the sheet at its own size in
   its own column, so lines broke in different places from the paper. Now
   the printing overlay AND the preview page both carry .paperlike, and the
   type scale, cell padding and block spacing that decide where a line
   breaks live HERE, once. The print block below keeps only what is about
   paper: colours, page breaks, print-color-adjust.
   The sizes are the print block's points written in rem at a 16px root
   (13pt = 1.0833rem, 15pt = 1.25rem, 12pt = 1rem, 11.5pt = 0.9583rem):
   printing ignores the app's text-size setting (it is @media screen), so
   there rem IS 16px and these are the old points exactly; on screen the
   preview page is zoomed by 16 / root in app.js so it, too, lays out as a
   16px root would. THE PRINTED COLUMN IS 48rem -- 8in, a letter page inside
   the 0.25in margins (Round 193) -- set on .paperlike .stand-inner below;
   .previewpage takes the same width. (The print block sets .stand-inner to
   max-width: none, so before Round 193 the paper's column was whatever the
   margins left while the preview stayed at .stand-inner's 40rem: the two
   agreed on type and padding but not on width. Now they agree on all
   three.) */
.paperlike .stand-inner {
  max-width: 48rem;
}
/* THE VERTICAL RHYTHM (Round 189, Derek: "very crowded from a vertical
   standpoint ... I really don't care at all about one page"; Round 190,
   having seen it: "It feels a little airy. Look at it again."). A design
   panel proposed three spacing schemes for this block and two judges scored
   each; Round 189 shipped the top pick, and Round 190 took the trims its
   own judges had named as the dial for exactly this complaint. The base
   unit is 0.5rem = 6pt on paper. Body 13pt at 1.4 is 18.2pt, three units.
   A row is 0.375rem (4.5pt) above and below that line -- a 27pt pitch,
   about 2.1x the type; the pick's 0.5rem made 30pt rows, and that is what
   floated. Hierarchy by space: a cell 0.375rem, a small-caps heading bar
   0.5rem, the date's title bar 0.75rem. Boxes stand 0.75rem (9pt) apart --
   twice the padding inside a cell, so text-to-text across a box edge is
   1.5rem against 0.75rem within, and the eye groups by box first and then
   by row, which is what an agenda wants at a pulpit. The sacrament's double
   frame stands 0.5rem clear of its box. TYPE SIZES ARE UNCHANGED: the air is
   what changed. The old values (line-height 1.28, cells 0.16rem, blocks
   0.28rem) were the one-page squeeze of Rounds 52-114, which Derek has
   retired; an ordinary Sunday now sits at about one page, a busy one runs
   onto a second. */
/* LARGE TEXT (Round 196, Derek: "Some people's eyes aren't very good, and
   they like it bigger"): one factor over the whole scale -- 15pt body from
   13pt, and in proportion the title 17.3pt, the scripts and hymns 13.8pt,
   the small-caps bars 13.3pt -- set by the "Large text" button beside Print
   (app.js sheetTextToggle) and carried by the preview page and the printing
   overlay alike, so what prints is what is shown. The air between rows is
   in rem and does not grow with it, and the column does not either, so
   lines wrap sooner and a sheet runs longer. */
.paperlike {
  --cs-scale: 1;
}
.paperlike.text-large {
  --cs-scale: 1.1538;
}
.paperlike .cs {
  font-size: calc(1.0833rem * var(--cs-scale));
  line-height: 1.4;
}
.paperlike .cs-block {
  margin-bottom: 0.75rem;
}
.paperlike .cs-title {
  font-size: calc(1.25rem * var(--cs-scale));
  letter-spacing: 0.03em;
  padding: 0.75rem 0.75rem;
  border-bottom-width: 2px;
}
.paperlike .cs-cell {
  padding: 0.375rem 0.75rem;
}
/* "Acknowledge Visiting Authorities" sits tight under "General Welcome": the
   two are one pair, not two rows. .cs-tight is (0,1,0) and the cell rule
   above (0,2,0) had silently beaten it since Round 155; with 0.375rem of
   padding either side the pair would otherwise stand 0.75rem apart. */
.paperlike .cs-cell.cs-tight {
  padding-top: 0;
}
/* THE SCRIPTS AND THE SECTION HEADINGS ARE BODY TEXT (Round 298, Derek:
   the ward-business block reads too small). They were 12pt against the
   sheet's 13pt body, which made the lines somebody actually says out loud
   -- General Welcome, No stake business today -- the smallest ordinary
   type on a page read from a pulpit. They are the body's own size now;
   the italic and the centring are what keep them subordinate. */
.paperlike .cs-script,
.paperlike .cs-heading {
  font-size: calc(1.0833rem * var(--cs-scale));
}
/* 12pt at 1.5 is 18pt: the italic scripts sit on the same 18pt line as the
   body (both judges' correction to the scheme's 1.45). */
.paperlike .cs-script {
  line-height: 1.5;
}
.paperlike .cs-value.cs-hymn {
  font-size: calc(1rem * var(--cs-scale));
}
.paperlike .cs-heading {
  padding: 0.5rem 0.75rem;
}
/* The two small-caps bars (Program, Notes) were 11.5pt -- the smallest
   thing left once the scripts moved up. 12pt keeps them a rank below the
   body without being the page's fine print. */
.paperlike .cs-heading.cs-small {
  font-size: calc(1rem * var(--cs-scale));
}
.paperlike .cs-table td {
  padding: 0.375rem 0.75rem;
}
/* An "other" item (a baby blessing, a funeral) is a thing of its own and
   keeps a touch more air than a line in the list around it, as on screen. */
.paperlike .cs-table td.cs-otherrow {
  padding: 0.5rem 0.75rem;
}
.paperlike .cs-list {
  margin-top: 0.375rem;
}
.paperlike .cs-list li {
  margin-bottom: 0.25rem;
}
.paperlike .cs-list li li {
  margin-bottom: 0.1rem;
}
.paperlike .cs-names {
  row-gap: 0.25rem;
}
.paperlike .cs-admin {
  font-size: calc(1.0833rem * var(--cs-scale));
  padding: 0.5rem 0.75rem;
}
/* An item's own name (Baby Blessing) was 12.6pt -- under the body, so a
   thing that happens in the meeting was set smaller than a speaker's
   name. Body size; italic and centred still mark it as a ceremony. */
.paperlike .cs-othertitle {
  font-size: calc(1.0833rem * var(--cs-scale));
}

/* THE PREVIEW HAD BEEN SHOWING THIS SMALLER THAN THE PAPER (Round 298).
   The print block has set .cs-otherdetail to 1em since Round 190 -- the
   baby's name under Baby Blessing is the content, not a footnote -- but
   the PREVIEW kept the 0.86em screen rule, so the two disagreed by a
   point and a half. That is exactly what .paperlike exists to prevent
   (Round 155), so the size lives here and the print block keeps only the
   colour it also has to force. */
.paperlike .cs-otherdetail {
  font-size: 1em;
}
.paperlike .cs-sacrament {
  margin: 0.5rem;
}

/* The preview page: the sheet at its printed width, shrunk bodily with a
   transform to the pane (the Round 111 thumbnail's technique). The fit box
   is given the scaled height in script, since a transform takes no part
   in layout. */
.previewfit {
  overflow: hidden;
}
.previewpage {
  width: 48rem; /* = .paperlike .stand-inner's max-width: the printed column */
  transform-origin: top left;
  margin-inline: auto; /* centred when the pane is wider than the page (Round 220) */
}

/* REORDERING ON THE PREVIEW (Round 207, decorateLivePreview in app.js).
   Positioned, never in the flow -- the preview must stay the printed sheet
   to the pixel (Round 155) -- and only under .previewpage, so the paper and
   the version thumbnail never have them. .pvctls undoes the page's shrink
   (--pv-k, set by refreshPreview) so a control is the same size at any pane
   width. The arrows sit in the inset the 36rem rows leave at the right
   (Round 195); the speaker swap in the inset at the left. */
.previewpage .cs-prog tr[data-pi] > td,
.previewpage .cs-cell[data-swap] {
  position: relative;
}

.pvctls {
  position: absolute;
  top: 50%;
  right: 0.3rem;
  display: inline-flex;
  gap: 0.1rem;
  transform: translateY(-50%) scale(calc(1 / var(--pv-k, 1)));
  transform-origin: right center;
  z-index: 1;
}

.pvctls.at-left {
  right: auto;
  left: 0.3rem;
  transform-origin: left center;
}

/* THE CONTROLS, REDRAWN (Round 220, Derek): no box round any of them; the
   swap and the two arrows in the music blue (--music, the palette's own
   slate blue -- "blue" without a colour foreign to the page), the x in the
   danger red; a soft tint of its own colour under the pointer. The glyphs
   are stroked SVG (app.js, decorateLivePreview) so they keep one line
   weight whatever the badge size. The hover labels are cards on <body>
   (hoverTip, the star's .startip), never pseudo-elements: those clipped at
   the pane's edge and slid under the sticky bar. */
.pvctl {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.55rem;
  height: 1.55rem;
  padding: 0;
  border: none;
  border-radius: var(--r-detail);
  background: none;
  color: var(--music);
  cursor: pointer;
  box-shadow: none;
}

.pvctl .pvglyph {
  width: 0.95rem;
  height: 0.95rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.pvctl.pvswap .pvglyph {
  width: 1.05rem;
  height: 1.05rem;
}

.pvctl:hover:not(:disabled),
.pvctl:focus-visible {
  background: color-mix(in srgb, var(--music) 13%, transparent);
  outline: none;
}

.pvctl.pvx {
  color: var(--danger);
  margin-left: 0.15rem;
}

.pvctl.pvx:hover:not(:disabled),
.pvctl.pvx:focus-visible {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}

.pvctl:disabled {
  opacity: 0.22;
  cursor: default;
}

/* A LINE ON THE PREVIEW OPENS ITS ITEM (Round 209): pointer and a faint
   tint on hover or keyboard focus, nothing in the flow. */
.previewpage .pvedit {
  cursor: pointer;
  border-radius: var(--r-detail);
}

.previewpage .pvedit:hover,
.previewpage .pvedit:focus-visible {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  outline: none;
}

/* The item's box (programItemDialog). */
.modalcard .progdialog .field {
  margin-bottom: 0.7rem;
}

.progremove.quiet-inline {
  color: var(--danger);
  margin-top: 0.3rem;
}

@media print {
  /* Round 210: the nested list's marker is a token (--cs-soft) on screen and
     must print in the sheet's fixed grey, as its labels do (Round 43). */
  .cs-list li li::marker {
    color: #222;
  }
}

@media print {
  /* 13pt (Round 112, Derek's call; 10pt until Round 52, 12pt until now).
     Every name here is read aloud from a pulpit mid-sentence, so this is
     the one place in the project where type size is a legibility question
     rather than a design one. The other pt sizes move with it in
     proportion — title 15, scripts and headings 12, the sacrament line 13.

     MEASURED, so the next person does not have to: a busy Sunday (four
     announcements, two new member records, a release, a sustaining, three
     speakers, an intermediate hymn and a musical number) is 11.03in of a
     10.40in page here, against 10.25in at 12pt. It therefore runs to a
     second page, and Derek was asked and said that is fine — it happens
     often anyway. So DO NOT "fix" a two-page sheet by putting the type
     back; the break rules below are what keep that second page tidy.
     THE SIZES THEMSELVES NOW LIVE IN .paperlike ABOVE (Round 155), shared
     with the live preview; the overlay that prints carries that class. */

  /* WHERE A BUSY SUNDAY BREAKS. Every block refused to split, which is
     right for the fixed boxes (title, meeting, sacrament, notes) and wrong
     for the two whose height is unbounded: on a busy sheet the whole
     Program block jumped to page two, leaving over an inch blank at the
     foot of page one and the program orphaned on its own. The two flowing
     blocks (.cs-flow, set in standInner) may break, but only BETWEEN
     rows: a heading stays with what follows it, a read-aloud script stays
     with the names it introduces, and no table row, two-up row or "other"
     item is ever cut in half. A split block keeps its full border on each
     fragment where the browser supports that. */
  .cs-block {
    border-color: #666;
    break-inside: avoid;
    page-break-inside: avoid;
  }
  .cs-block.cs-flow {
    break-inside: auto;
    page-break-inside: auto;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
  }
  .cs-heading,
  .cs-cell.cs-script.cs-left {
    break-after: avoid;
    page-break-after: avoid;
  }
  .cs-table tr,
  .cs-otherrow,
  .cs-row2,
  .cs-cell.cs-names,
  .cs-cell.cs-script {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  /* The shaded title bar is the one tinted thing on the sheet, and most
     printers are told not to print backgrounds, so the tint usually never
     reaches the paper. print-color-adjust asks for it anyway; where the
     browser still refuses, the title is marked without it -- a heavier rule
     beneath and a little letterspacing, so it reads as the head of the sheet
     rather than as one more line of it. */
  .cs-title {
    background: #eee;
    border-color: #666;
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }

  /* The two section bars — "Program:" (or "Fast and Testimony Meeting")
     and "Notes". `.cs-heading.cs-small` is (0,2,0) and its screen size is
     in rem, so it OUTRANKS the line above and never took part in the point
     scale at all: it printed at 0.9rem = 10.8pt whether the sheet was 10pt,
     12pt or 13pt, and the gap grew every time the sheet did. 11.5pt keeps
     the 0.9 ratio the screen design gives it. (Now in .paperlike, as
     0.9583rem, with the rest of the scale.) */

  .cs-sacrament {
    border-color: #666;
  }

  .cs-row2,
  .cs-row2 > .cs-cell + .cs-cell,
  .cs-table td,
  .cs-heading,
  /* These two were missing from the list, so they kept var(--line) and printed
     at 1.39:1 on white — a hairline that effectively is not there. One is the
     rule inside the double-ruled sacrament box, directly above
     ADMINISTRATION OF THE SACRAMENT; the other divides the stake-business
     line from the block above it. */
  .cs-admin,
  .cs-block > .cs-cell.cs-script:first-child,
  /* The program's dotted leader (.cs-dots, Round 162). It is a span, so it is
     not covered by the .cs-table td line above and would otherwise print at
     var(--dots) — which in dark mode is #56503f. */
  .cs-prog .cs-dots,
  /* The hairlines that bracket an "other" item. Same specificity as the
     screen rule that sets them and later in the file, so this wins — a
     dark-mode machine cannot print a dark rule across the program. */
  .cs-prog td.cs-otherrow,
  .cs-ruled {
    border-color: #bbb;
  }

  /* PAPER DOES NOT HAVE A DARK MODE, and this block never said so.
     It forces body colour and every border to a fixed grey, but left the
     colour TOKENS alone — and prefers-color-scheme is not overridden for
     printing. So a bishop whose laptop is in dark mode printed every label
     ("Conducting:", "Presiding:") and all three sentences read verbatim from
     the pulpit in the dark theme's soft ink, #b4a995, which measures 2.32:1
     on white. In light mode the same text is 6:1.
     Stated as a fixed print grey, the way the borders next to it already are:
     12.63:1 either way, on the one document that is read at arm's length. */
  /* The sheet's own ink tokens (Round 192) would otherwise print the dark
     theme's light ink on a dark-mode machine -- the trap above, again. */
  .cs {
    color: #000;
  }

  .cs-label,
  .cs-script,
  .cs-value.cs-hymn,
  .cs-heading,
  .cs-heading.cs-small,
  .cs-otherdetail,
  .cs-note {
    color: #222;
  }

  .stand-empty {
    color: #555;
  }

  /* Left-aligned because they are SENTENCES, not labels — the records, the
     releases and the callings scripts are read out word for word. .cs-left
     said so but tied with .cs-script on specificity and lost on source order,
     so all three printed centred. */
  .cs-script.cs-left {
    text-align: left;
  }
}

/* =========================== the callings & members tables (spreadsheet-ish) */

.ptable-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--card);
}

/* THE HEADINGS STICK NOW. .ptable th has been position: sticky since Round 7
   and never engaged: overflow-x: auto makes the wrap the scrollport on BOTH
   axes (the spec promotes the other axis from visible to auto), and with no
   height limit the wrap never scrolled vertically, so top: 0 was never
   reached -- a hundred rows of borderless inputs edited with no column
   labels on screen. A bounded height makes the wrap the vertical scrollport
   its own CSS and keepingScroll() already assume, and pins the horizontal
   scrollbar to the bottom of the visible box instead of the bottom of the
   page. Desktop only: a nested scroll region on a phone is the classic
   scroll trap, and 100vh there overstates the visible viewport. */
@media (min-width: 64rem) {
  .ptable-wrap {
    max-height: calc(100vh - 13rem);
    overflow-y: auto;
  }
}

.ptable {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.ptable th {
  text-align: left;
  padding: 0;
  border-bottom: 1.5px solid var(--line);
  background: var(--card);
  position: sticky;
  top: 0;
  z-index: 2;
  white-space: nowrap;
}

.psort {
  font: inherit;
  font-size: 0.75rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-soft);
  background: none;
  border: none;
  padding: 0.55rem 0.6rem;
  width: 100%;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
}

.psort:hover {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}

.psort.on {
  color: var(--accent);
}

.ptable td {
  padding: 0.25rem 0.45rem;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}

.ptable tbody tr:hover td {
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}

/* Borderless cells so the table reads like a spreadsheet, not a form. */
.ptable .pcell {
  width: 100%;
  min-width: 7rem;
  border: 1px solid transparent;
  background: transparent;
  padding: 0.25rem 0.35rem;
  font-size: 0.9rem;
  border-radius: var(--r-item);
}

.ptable .pcell:hover {
  border-color: var(--line);
  background: var(--paper);
}

.ptable .pcell:focus {
  border-color: var(--accent);
  background: var(--paper);
}

.ptable select {
  font-size: 0.82rem;
  padding: 0.2rem 0.3rem;
  border-radius: var(--r-item);
  max-width: 11rem;
}

.ptable .pactions {
  white-space: nowrap;
}

/* `.ptable .pactions .btn.slim` WAS HERE — folded into `.btn.tiny` above.
   Its one button ("Moved out") grows by half a pixel of type and 1.6px of
   height, inside a 69px row, so nothing moves. */

.ptable .bcard-actions {
  gap: 0.25rem;
  margin-top: 0;
}

.ptable .bcard-meta {
  font-size: 0.7rem;
}

/* THE TRACKER'S ROW STRIPES ARE GONE, and so is the rule that was hiding
   them. Seven colours lived here and an eighth further down, all overridden
   into nothing by `.ptable tbody tr[class*='st-'] td:first-child
   { box-shadow: none }` — (0,3,3) against their (0,3,2) — added deliberately
   once the status grouping carried the colour. Eight hard-coded hexes that
   looked like the status palette but rendered nothing, and one of them was
   written AFTER the rule that killed it.
   The stylesheet now says once that these rows are not striped. The status
   colour lives on .pgroup-dot and the pill.
   The MEMBERS table below is deliberately still striped: its rows carry
   `av-*` classes, which that override never matched. */

.pstatus {
  font-weight: 650;
  font-size: 0.82rem;
}

/* Members table */
.mtable .mname {
  font-weight: 650;
  white-space: nowrap;
}

.mtable .mcenter {
  text-align: center;
}

.mtable tr.av-available td:first-child { box-shadow: inset 4px 0 0 #7fbf8a; }
.mtable tr.av-unavailable td:first-child { box-shadow: inset 4px 0 0 #d8b53f; }
.mtable tr.av-dnc td:first-child { box-shadow: inset 4px 0 0 var(--notify); }
.mtable tr.av-unset td:first-child { box-shadow: inset 4px 0 0 var(--line); }

/* Give the tracker real column widths and let it scroll rather than squeeze.
   The per-column minimums below add up to more than this, so they are what
   actually sets the width; this is the floor for any table using the class. */
.ptable {
  min-width: 66rem;
}

/* THE TRACKER RUNS WIDER THAN THE WORKSPACE. body.leader .wrap is 76rem, and
   the tracker's ten columns want more than that, so on a wide screen the
   Notes column sat just past the card's edge with a scrollbar as the only
   hint it existed -- while a hand's width of empty paper stood either side.
   On the Callings tab only, the scroll container steps out of the column:
   as wide as the window less a margin, capped at 100rem, and centred on the
   viewport rather than on .wrap (50% of the containing block is .wrap's own
   centre, which is the viewport's). The toolbar above it keeps the workspace
   width, so the table reads as the one thing on the tab allowed to spread. */
@media (min-width: 80rem) {
  body.leader .pipetab .ptable-wrap {
    width: min(calc(100vw - 3rem), 100rem);
    margin-left: calc(50% - min(calc(100vw - 3rem), 100rem) / 2);
  }
}

.mtable {
  min-width: 40rem;
}

/* Tracker column widths.
   ----------------------
   MEASURED, not guessed: each is the widest thing that column can actually
   hold — for a <select> the widest OPTION, not the current value, since any
   row may be switched to it — plus the cell padding and the control's own
   chrome. An <input> has no intrinsic width, so a table cannot size itself to
   its value; without these the text is simply cut off.
   Re-measure when the longest calling title or a status label changes.

   THE EXCLUSIONS MATTER, and both were found the same way — by a column
   coming out wrong on a table that is not the tracker. `.ptable` is worn by
   three tables; these widths belong to ONE of them, and nth-child cannot
   tell them apart. The members table (Round 15) took them on its Notes and
   action columns; the Admin table (Round 278) took 176px / 148px / 96px on
   two names and a date, and on the organization-leader table a 280px
   minimum meant for "1st Counselor in Primary Presidency" landed on the
   actions cell — so the table wanted 1326px inside 1268 and its card
   scrolled sideways for no reason anybody could see. Measured after
   excluding it: 1268 = 1268, nothing scrolls.
   ADD A `:not()` HERE FOR ANY OTHER TABLE THAT REUSES `.ptable`.
   nth-child does not follow a column when one is added or removed — these
   shifted down by one when Submitted/Submitted by were merged. */
.ptable:not(.mtable):not(.atable) th:nth-child(1) { min-width: 11rem; }    /* Status: "Done — remove from list" */
.ptable:not(.mtable):not(.atable) th:nth-child(2) { min-width: 9.25rem; }  /* Organization: fits every name but one — see .porgcell */
.ptable:not(.mtable):not(.atable) th:nth-child(3) { min-width: 6rem; }     /* Type: "Release" plus a real gutter for the select's arrow */
.ptable:not(.mtable):not(.atable) th:nth-child(4) { min-width: 13.6rem; }  /* Candidate: "Mckinley Gardner (Laws)" */
.ptable:not(.mtable):not(.atable) th:nth-child(5) { min-width: 17.5rem; }  /* Calling: "1st Counselor in Primary Presidency" */
.ptable:not(.mtable):not(.atable) th:nth-child(6) { min-width: 6.75rem; }  /* Extending */
.ptable:not(.mtable):not(.atable) th:nth-child(7) { min-width: 8rem; }     /* Submitted: "Bishopric (shared)" over the date */

/* Organization: the ONE place a select is deliberately allowed to be narrower
   than its longest option.
   ---------------------------------------------------------------------------
   MEASURED: "Temple & Family History" needs 148px of text, the next longest
   ("Bishopric & Clerks") only 109px. Sizing the column to that one outlier —
   which is what a select does on its own — spent 40px on EVERY row to hold a
   name that appears on a handful of them. Letting it shrink took the column
   199px → 159px and the whole table 1698px → 1658px.
   Trimming the min-width alone was worth 2px, so this rule, not the number
   above, is what actually narrows the column: a <select> will not go below its
   widest option without `width` and `min-width: 0`.
   Every organization but that one still shows in full. That one ellipsizes,
   carries the full name in its `title`, and — as with any select — opening the
   list shows every name complete. Nothing is unreachable and nothing is
   ambiguous: no other organization begins with "Temple".
   Classed, not positional: the nth-child widths above have been renumbered
   four times, and this must not move with them. */
.porgcell select {
  width: 100%;
  min-width: 0;
  text-overflow: ellipsis;
}

/* Notes is the ONE column deliberately too narrow for its contents: a note is
   a thread now, read in the panel the cell opens. Held to a fixed width so it
   cannot absorb the leftover space and squeeze the columns that must fit. */
.ptable:not(.mtable):not(.atable) th:nth-child(8) {
  min-width: 12rem;
  width: 12rem;
}

/* The members table's columns are sized below, under "THE MEMBERS TABLE"
   (Round 86); the two 12rem minimums that used to sit here were what made
   the Availability and Notes columns so wide. */

.ptable .pcell {
  min-width: 0;
}

/* The tab strip can still scroll on a phone, but its scrollbar never shows. */
.tabbar::-webkit-scrollbar {
  display: none;
}

/* A profile piece the bishopric left off the checklist, offered back
   (Round 278). Quiet words inside a hint line, so the leading glyph
   .quiet-inline draws -- which marks an action branching off a heading --
   is dropped, as it is on the sheet actions row (Round 185). */
.ignoredrow .quiet-inline::before {
  content: none;
}

.ignoredrow .quiet-inline {
  font-size: inherit;
}

/* THE QR POSTERS (Round 279). One card per size, the preview beside what it
   is and the two downloads -- a column, because three sheets of three
   different shapes do not tile into a grid without one of them looking
   like a mistake. Both downloads are rank 2 (.btn.slim, outlined): there
   are six of them on the page and no one of them is the primary action,
   which is the rule a nineteen-row table taught us (Round 43). */
.qrtab .qrlist {
  display: grid;
  gap: 0.9rem;
  max-width: 46rem;
}

.qritem {
  display: flex;
  align-items: flex-start;
  gap: 1.1rem;
}

/* A fixed box for the preview, so three different shapes still line their
   words up down the page. */
.qrshot {
  flex: 0 0 8.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 8.5rem;
  padding: 0.5rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-item);
}

.qrshot img {
  display: block;
  max-width: 100%;
  max-height: 9.5rem;
  width: auto;
  height: auto;
}

.qrmeta {
  flex: 1 1 auto;
  min-width: 0;
}

.qrmeta h3 {
  margin: 0.1rem 0 0.35rem;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.55rem;
}

/* The trim size is the fact you are choosing on, so it reads as a label
   beside the name rather than as more of the sentence below it. */
.qrsize {
  font-weight: 400;
  color: var(--ink-soft);
}

.qracts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.7rem;
}

.qracts .btn {
  min-width: 4.5rem;
}

@media (max-width: 34rem) {
  .qritem {
    flex-direction: column;
  }

  .qrshot {
    flex: 0 0 auto;
    align-self: flex-start;
  }
}

/* --------------------- published announcements: three obvious states */

.pubpanel-h {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 0.4rem;
}

.pubpanel-h .field-label {
  margin: 0;
}

.pubpanel-h .tab-count {
  background: var(--notify);
  color: var(--on-notify);
  font-size: 0.72rem;
  font-weight: 700;
  padding: 0.05rem 0.45rem;
  margin: 0;
}

.pubstate {
  text-transform: uppercase;
  margin-bottom: 0.15rem;
}

/* The right padding holds a title clear of the hide x in the corner. */
.pubitem {
  position: relative;
  padding: 0.6rem 1.9rem 0.6rem 0.75rem;
  border-top: 1px solid var(--line);
  border-left: 3px solid transparent;
  border-radius: 0 var(--r-item) var(--r-item) 0;
}

/* THE HIDE X (Round 255, Derek): in the corner of each published notice,
   taking it out of this Sunday's list until "Show all" brings it back. It
   sits at 45% until the pointer is on the row, so a column of them does not
   read as a column of delete buttons; solid on touch, where there is no
   hover to reveal it.
   WRITTEN WITH .pubitem IN FRONT so it outranks [data-tip]'s own
   `position: relative`, which is later in this file and would otherwise
   un-position it -- the Round 10 trap, and Round 99's fix for it. */
.pubitem .pubhide {
  position: absolute;
  top: 0.35rem;
  right: 0.35rem;
  font: inherit;
  font-size: 0.85rem;
  line-height: 1;
  color: var(--ink-soft);
  background: none;
  border: none;
  border-radius: var(--r-detail);
  padding: 0.15rem 0.3rem;
  opacity: 0.45;
  cursor: pointer;
  transition: opacity 0.15s ease, color 0.15s ease;
}

.pubitem:hover .pubhide,
.pubitem .pubhide:focus-visible {
  opacity: 1;
}

.pubitem .pubhide:hover {
  color: var(--notify);
}

@media (hover: none) {
  .pubitem .pubhide {
    opacity: 0.8;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pubitem .pubhide {
    transition: none;
  }
}

/* Fairly minimalist (Derek): a quiet line at the foot of the OPEN list, ruled
   off from it. No radius -- a single-sided border with rounded corners reads
   as a box that lost three of its sides. */
.pubshowall {
  display: block;
  width: 100%;
  font: inherit;
  font-size: 0.8125rem;
  color: var(--ink-soft);
  text-align: left;
  background: none;
  border: none;
  border-top: 1px solid var(--line);
  border-radius: 0;
  margin-top: 0.4rem;
  padding: 0.5rem 0 0.1rem;
  cursor: pointer;
  transition: color 0.15s ease;
}

/* IT HAS TO LOOK PRESSABLE (Round 269, Derek: "when the user hovers over it,
   it should change colors so that it's clearly clickable"). The colour alone
   was already here and was too quiet to notice: soft ink to the accent is two
   mid-tones a hair apart at 0.8125rem. So the accent is deepened toward the
   ink -- which darkens it on cream and LIGHTENS it on dark paper, one rule
   for both palettes (the Round 241 formulation) -- and an underline is added,
   which is this app's own mark for a quiet word promoted to "you can press
   this" (.glive-view, .install-link; Round 47). It rides on --ul-offset, so
   it sits where every other underline in the app does.

   NO TOOLTIP, deliberately, per the same message. This control carries no
   data-tip and must not gain one: the words already say what the press does,
   and a card popping up over the list beneath it would cover the very rows
   it is about to bring back. */
.pubshowall:hover,
.pubshowall:focus-visible {
  color: color-mix(in srgb, var(--accent) 82%, var(--ink));
  text-decoration: underline;
  text-underline-offset: var(--ul-offset);
}

@media (prefers-reduced-motion: reduce) {
  .pubshowall {
    transition: none;
  }
}

/* Waiting on a decision — the one state that should catch the eye. */
.pubitem.pending {
  border-left-color: var(--notify);
  background: color-mix(in srgb, var(--notify) 6%, transparent);
  opacity: 1;
}

.pubitem.pending .pubstate {
  color: var(--notify);
}

/* ALREADY ON THE SHEET: faded IN PLACE and labelled, never moved and never
   shrunk (Round 254, Derek: "it doesn't disappear anymore. It just gets
   faded out and has an obvious 'On Conducting Sheet' label on it. But it
   doesn't move"). THE FADE IS ON THE PREVIEW ALONE, so the label and the
   Remove button stay at full strength: two opacities multiply (Round 43),
   and a 0.5 button inside a 0.5 item is a smudge. Round 220's rules that
   shrank a used item to one line are gone -- they hid .bcard-meta and
   .bcard-sub, which annPreview has not emitted for a long while, so they
   were doing nothing either way. .pubitem.skipped went with "Don't
   include". */
.pubitem.used {
  border-left-color: var(--accent);
}

.pubitem.used .annview {
  opacity: 0.5;
}

.pubitem.used .pubstate {
  color: var(--accent);
}

/* THE FADE IS A ONE-SHOT KEYFRAME, NOT A TRANSITION (Round 256, Derek: "a
   classy animation to make it fade, but stay right where it is"). render()
   replaces the row, so a transition has nothing to move from -- the fresh
   node starts at its target opacity and the change is instant. An animation
   on the newly built row plays once and settles. Only the preview moves; the
   label and the button stay at full strength, as Round 254 set them. */
@keyframes pubfadeout {
  from { opacity: 1; }
  to { opacity: 0.5; }
}

@keyframes pubfadein {
  from { opacity: 0.5; }
  to { opacity: 1; }
}

@keyframes publabelin {
  from { opacity: 0; }
  to { opacity: 1; }
}

.pubitem.pubfade-out .annview {
  animation: pubfadeout 0.38s ease both;
}

.pubitem.pubfade-in .annview {
  animation: pubfadein 0.38s ease both;
}

.pubitem.pubfade-out .pubstate {
  animation: publabelin 0.38s ease both;
}

@media (prefers-reduced-motion: reduce) {
  .pubitem.pubfade-out .annview,
  .pubitem.pubfade-in .annview,
  .pubitem.pubfade-out .pubstate {
    animation: none;
  }
}

.pubitem .addrow {
  margin-top: 0.45rem;
  gap: 0.35rem;
}

/* THE BUTTON IS AS WIDE AS ITS WORDS (Round 264, Derek: "way too wide").
   `.addrow .btn` is `flex: 1 1 auto`, which is right where a row holds one
   action and wrong here, where it stretched a five-word button across the
   whole column. (0,3,0) to beat it. */
.pubitem .addrow .btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

/* Bold, and in the button's own ink -- paper on the filled Add, the accent
   on the outlined Remove. Stroked rather than filled: an arrowhead drawn as
   a shape closes up into a blob at this size (Round 126). */
.addsheet .addarrow {
  width: 0.95em;
  height: 0.95em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* THE MOVE ITSELF (Round 266): a node put back where its content was and
   released, so the browser carries it to where it now belongs. Only the
   transform is animated -- nothing here changes layout, which is what lets
   a row, a table cell's value and a list item all use the same rule. */
.flipping {
  transition: transform 0.42s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* A NAME ARRIVING FROM A DROPDOWN (Round 266, Derek): the value drops the
   last few pixels into its cell out of a sage wash that clears behind it.
   Short, and it leaves nothing on the sheet afterwards. */
@keyframes csarrived {
  0% { opacity: 0; transform: translateY(-5px); background: color-mix(in srgb, var(--accent) 30%, transparent); }
  45% { opacity: 1; transform: translateY(0); }
  100% { opacity: 1; transform: translateY(0); background: transparent; }
}

.cs-value.csarrived {
  border-radius: 3px;
  animation: csarrived 0.8s ease both;
}

@media (prefers-reduced-motion: reduce) {
  .flipping {
    transition: none;
  }
  .cs-value.csarrived {
    animation: none;
  }
}

/* THE GHOST THAT TRAVELS (Round 264): a small card carrying the title, from
   the row that was pressed to the line it became. Fixed on <body> so it
   takes part in no layout and cannot move either list; below the dialogs
   (50) and the toast (100), above everything else. */
.flyann {
  position: fixed;
  z-index: 90;
  max-width: 20rem;
  padding: 0.4rem 0.65rem;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--line));
  border-radius: var(--r-item);
  background: var(--card);
  box-shadow: 0 8px 22px rgba(20, 16, 10, 0.16);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
  transition: transform 0.55s cubic-bezier(0.34, 0.06, 0.2, 1), opacity 0.55s ease-in;
}

/* And the line lights up as it arrives, so the eye ends where the work did. */
@keyframes annlanded {
  0% { background: color-mix(in srgb, var(--accent) 26%, transparent); }
  100% { background: transparent; }
}

.standread-item.annlanded {
  border-radius: var(--r-item);
  animation: annlanded 1.2s ease both;
}

@media (prefers-reduced-motion: reduce) {
  .standread-item.annlanded {
    animation: annlanded 1.2s steps(1, end) both;
  }
}

.preview-actions {
  display: inline-flex;
  gap: 0.4rem;
}

/* THE FORM BESIDE THE PREVIEW (Round 29) was 36rem in the split and 56rem
   with the preview hidden; the hidden case is gone (Round 209, the preview
   is permanent), so the form is the split's column at every width. */

/* (Round 29's .sheetsplit.nopreview and .conducting-col.narrow rules --
   the one-column layout for a hidden preview -- are gone: the preview is
   permanent since Round 209.) */

.modal-list {
  margin: 0.5rem 0 0;
  padding-left: 1.25rem;
}

.modal-list li {
  margin-bottom: 0.2rem;
}

/* The composer and the ward-details panel share the tab's own column. Both use
   border-box so the stated width means the same thing for each.
   The cap was 44rem, but Round 29 had already set this tab's column to 56rem
   for exactly the line-length reason — so the cap did that job a second time,
   and the result was a composer card standing 96px narrower on each side than
   the list of live announcements directly beneath it: two different left edges
   on one page. */
body.leader .composer,
body.leader .warddetails {
  max-width: 56rem;
  margin-inline: auto;
  box-sizing: border-box;
}

/* A notice ABOUT YOUR OWN announcement (Round 228) is neither an approval nor
   a refusal -- somebody has simply told you what they did -- so it takes the
   quiet informational treatment rather than the yes/no colours. */
.notice.is-told {
  border-left-color: var(--ink-soft);
  /* Mixed with --card, not transparent, so it sits on the same ground as
     .is-yes and .is-no rather than letting the page show through. */
  background: color-mix(in srgb, var(--ink-soft) 6%, var(--card));
}

/* THE ANNOUNCEMENTS TAB'S OWN SPLIT (Round 227). Round 220 narrowed
   .sheetsplit to 24rem for the CONDUCTING sheet, where the preview is the
   work and the form is a thin column beside it. This tab shares the class and
   was squeezed with it -- Derek: "far too narrow to be helpful". Here the
   composer IS the work, so it takes the wider half. */
.sheetsplit.anntab {
  grid-template-columns: minmax(0, 40rem) minmax(0, 1fr);
}

/* With the preview closed there is nothing to sit beside, so the column
   collapses to one and the composer's own max-width centres it on the screen
   rather than stranding it in a 24rem track at the left. */
.sheetsplit.anntab.nopreview {
  grid-template-columns: minmax(0, 1fr);
}

/* The writing column carries .sheetform, which the wide-screen rule pins to
   the left (margin-inline: 0) so the conducting sheet's form sits against its
   preview. With no preview to sit against there is nothing to line up with,
   so it goes back to centred -- which is what "center this modal on the
   screen" means here. */
.sheetsplit.anntab.nopreview .sheetform {
  margin-inline: auto;
}

/* The close X, for a dialog that says its way out (Round 227). Sits in the
   card's corner; .modalcard is made position: relative above for it. */
.modalcard .modal-x {
  position: absolute;
  top: 0.5rem;
  right: 0.6rem;
  font: inherit;
  font-size: 1.35rem;
  line-height: 1;
  padding: 0.15rem 0.4rem;
  background: none;
  border: 0;
  border-radius: var(--r-detail, 4px);
  color: var(--ink-soft);
  cursor: pointer;
}

.modalcard .modal-x:hover,
.modalcard .modal-x:focus-visible {
  background: color-mix(in srgb, var(--ink-soft) 12%, transparent);
  color: var(--ink);
}

/* The time list is deliberately SHORT -- about six rows -- so it reads as a
   few choices rather than a wall (Round 227). */
.namedrop.timedrop {
  max-height: 12rem;
}

.namedrop.timedrop .namedrop-row.is-chosen {
  font-weight: 650;
  color: var(--accent);
}

/* Move / reset live at the foot of the sheet, quiet and out of the way. */
.sheet-footactions {
  border: 1px dashed var(--line);
  border-radius: var(--r-card);
  padding: 0.9rem 1.15rem 1rem;
  margin-bottom: 1.25rem;
}

.sheet-footactions .field-label {
  margin-bottom: 0.2rem;
}

.sheet-footactions .quiet-action {
  margin-top: 0.55rem;
}

/* The candidate's name is what the eye looks for first in the tracker. */
.ptable .pname {
  font-weight: 650;
  font-size: 0.95rem;
  color: var(--ink);
}

/* The "add" action sits at the end of the toolbar, apart from the filters. */
/* The Snapshot opens from the Callings toolbar now (Derek). It sits at the
   far right, apart from the filters and the one primary action, because it
   is not a filter and it is not the thing you came here to do — it is the
   door to the other view of the same work. */
.snapopen {
  margin-left: auto;
}

/* ONE THING PUSHES RIGHT, AND IT IS THAT ONE. `.filterrow .addbtn` and
   `.cellsave` both carry margin-left: auto of their own, which cost nothing
   while the search box stretched to fill the row and left them no free space
   to claim. Capping the search freed 515px — and three auto margins SHARE
   free space equally, so "+ Add", "Saved ✓" and the Snapshot each drifted
   172px from the control before them and the row read as five things scattered
   across it. The two are zeroed here only (the Admin bar's .addbtn still uses
   its auto), so the filters and the one primary action stay together on the
   left and the Snapshot sits alone at the right. */
.pipetab .filterrow .addbtn,
.pipetab .filterrow .cellsave {
  margin-left: 0;
}

/* A back link points the other way, so it drops the ↳ that .quiet-inline
   carries for actions branching off a heading. TWO CLASSES, not one:
   `.quiet-inline::before` is written further down the file, so at equal
   specificity it wins on source order and the glyph came back — the arrow
   read "↳ ← Back to Callings". */
.quiet-inline.snapback::before {
  content: none;
}

.snapback {
  display: inline-block;
  margin-bottom: 0.5rem;
}

.filterrow .addbtn {
  margin-left: auto;
}

.modalcard.wide {
  max-width: 34rem;
}

.modalcard .cols {
  gap: 0.6rem;
}

.modalcard .field:last-child {
  margin-bottom: 0;
}

/* Fields with a suggestion list still autocomplete as you type, so the
   browser's dropdown arrow is redundant. Date fields keep their calendar. */
input[type='text']::-webkit-calendar-picker-indicator {
  display: none !important;
}

/* ================= callings table: data first, controls on approach ===== */

/* Selects sit flat in the cell until you reach for them, so a row reads as
   information rather than a row of widgets. */
.ptable select {
  appearance: none;
  -webkit-appearance: none;
  background-color: transparent;
  border-color: transparent;
  padding: 0.2rem 1.1rem 0.2rem 0.35rem;
  border-radius: var(--r-item);
  cursor: pointer;
  background-image: none;
}

/* THE CARET NEEDS ITS OWN GUTTER, and the rule above never gave it one.
   `.ptable select` asks for padding-right: 1.1rem — 17.6px, enough to clear
   the caret drawn below at 4–12px from the right edge — but it is (0,1,1),
   and `body.leader … select { padding: 0.5rem 0.6rem }` in the desktop
   density block is (0,2,1). The shorthand won, the gutter silently became
   9.6px, and the caret was drawn ON the last letter: the Type column's
   "Release" measured 49.8px of text in a 50px content box, ending 0.2px past
   where the caret's ink begins.
   Written LONG and scoped past that block. padding-left is deliberately left
   to the --cell-inset rule further down, which is more specific again — the
   same trap the save tick hit in Round 125. */
body.leader .ptable select:not(.pstatus-sel) {
  padding-top: 0.2rem;
  padding-bottom: 0.2rem;
  padding-right: 1.15rem;
}

.ptable td:hover > select,
.ptable select:focus {
  border-color: var(--line);
  background-color: var(--paper);
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-soft) 50%),
    linear-gradient(135deg, var(--ink-soft) 50%, transparent 50%);
  background-position: calc(100% - 12px) 55%, calc(100% - 8px) 55%;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}

/* The status pill: the spreadsheet's colour coding, still one click to change. */
.pstatuscell {
  white-space: nowrap;
}

/* Needs to outrank the generic .ptable select rule above. */
.pstatus-sel,
.ptable select.pstatus-sel {
  display: inline-block;
  font: inherit;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
  max-width: 100%;
}

/* A DESCENDANT, not a child. Round 19 moved the pill inside a .pstatusrow div
   so the ⋯ could sit beside it — which quietly killed this rule, because the
   pill stopped being the cell's direct child. It left the status pill as the
   one control in the tracker that does not answer the cursor, while every
   select and text cell around it does. */
.ptable td:hover .pstatus-sel,
.pstatus-sel:focus {
  background-image: none;
  filter: brightness(0.96);
}

/* THE OPEN LIST NEEDS ITS OWN COLOURS. The pill colours below dress the CLOSED
   control, and four of them are white text on a dark fill. An <option> inherits
   the select's colour but is painted by the browser on its own background — so
   opening the status menu on a green "Ready to sustain" pill gave white text on
   white, and the entire list was invisible except the one row the browser
   happened to be highlighting. Options are set back to ordinary ink on card. */
.pstatus-sel option {
  color: var(--ink);
  background-color: var(--card);
  font-weight: 600;
}

.pstatus-sel.st-proposal { background: #dbe8f2; color: #1f4a68; }
.pstatus-sel.st-need_filled { background: #f6e7bd; color: #6b5311; }
.pstatus-sel.st-approved_extending { background: #d6ecd9; color: #23572f; }
/* A step on from the pale green of "approved, extending" and a step short of
   the solid green of "on the sheet" — which is exactly where it sits. */
.pstatus-sel.st-accepted { background: #b5ddbf; color: #17401f; }
.pstatus-sel.st-to_sustain { background: #2f7d4f; color: #ffffff; }
.pstatus-sel.st-awaiting_lcr { background: var(--notify); color: var(--on-notify); }
.pstatus-sel.st-in_lcr { background: #45699f; color: #ffffff; }
.pstatus-sel.st-hold { background: #6b665f; color: #ffffff; }
.pstatus-sel.st-declined { background: #9a5f4a; color: #ffffff; } /* was #a8705c: white on it is 4.09:1, under AA. 5.11:1 now. */
.pstatus-sel.st-done { background: var(--line); color: var(--ink); } /* soft ink on --line was 4.32:1; full ink is 10.9:1 light, and --line flips in dark so both themes hold. */

/* Grouping headers when the table is sorted by status. */
.ptable tr.pgroup td {
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  padding: 0.55rem 0.6rem 0.3rem;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.ptable tr.pgroup:hover td {
  background: var(--paper);
}

.pgroup-dot {
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  margin-right: 0.45rem;
  vertical-align: baseline;
}

.pgroup-dot.st-proposal { background: #7fa8c9; }
.pgroup-dot.st-need_filled { background: #d8b53f; }
.pgroup-dot.st-approved_extending { background: #7fbf8a; }
.pgroup-dot.st-accepted { background: #57ac6e; }
.pgroup-dot.st-to_sustain { background: #2f7d4f; }
.pgroup-dot.st-awaiting_lcr { background: var(--notify); }
.pgroup-dot.st-in_lcr { background: #45699f; }
.pgroup-dot.st-hold { background: #6b665f; }
.pgroup-dot.st-declined { background: #9a5f4a; }

/* THE STATUS PALETTE IN DARK MODE. The pill fills above are literal hexes,
   picked for cream paper, and nothing redefined them when the page went dark:
   three pastel pills (proposal, need filled, approved) sat as bright blocks
   on dark paper with their deep ink at 2:1 against the fill, and the four
   solid fills sank into the background. The group dots and the row stripes
   follow the same colours. The hover filter darkens the pill on cream, so in
   dark mode it is turned the other way -- the pill should come towards you,
   not fade into the paper. */
@media (prefers-color-scheme: dark) {
  .pstatus-sel.st-proposal { background: #22394b; color: #cfe0ee; }
  .pstatus-sel.st-need_filled { background: #4a3c10; color: #f1dfa2; }
  .pstatus-sel.st-approved_extending { background: #1f4228; color: #cfe6d3; }
  .pstatus-sel.st-accepted { background: #27512f; color: #d6ecd9; }
  .pstatus-sel.st-to_sustain { background: #33804f; }
  .pstatus-sel.st-in_lcr { background: #4f74ab; }
  .pstatus-sel.st-hold { background: #726d64; }
  .pstatus-sel.st-declined { background: #9e6350; }
  .pgroup-dot.st-accepted { background: #5db373; }
  .pgroup-dot.st-to_sustain { background: #33804f; }
  .pgroup-dot.st-in_lcr { background: #4f74ab; }
  .pgroup-dot.st-hold { background: #726d64; }
  .pgroup-dot.st-declined { background: #9e6350; }
  .ptable td:hover .pstatus-sel,
  .pstatus-sel:focus {
    filter: brightness(1.12);
  }
}

.pgroup-count {
  margin-left: 0.5rem;
  color: var(--dots);
  font-weight: 600;
}

/* Calmer density and clearer rows. */
.ptable td {
  padding: 0.15rem 0.45rem;
}

.ptable tbody tr:hover td {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}

.ptable .pcell:hover {
  border-color: var(--line);
  background: var(--paper);
}

/* THE TABLE/BOARD VIEW SWITCHER WAS HERE, and is deleted. Round 15 removed
   the kanban and the segmented control with it; `.segmented`, `.segbtn` and
   its four states have had no emitter since. Its comment still explained, in
   the present tense, why a view switcher stays neutral — for a switcher that
   has not existed in the app for several rounds. */

/* The quiet utility treatment: squared and grey, in keeping with the ruled
   document it sits over. A DISPLAY PREFERENCE, not an action on the sheet.

   It was `.preview-actions .btn` — an ancestor redefining the pill — and that
   made the show/hide toggle change into a different-looking control when you
   pressed it. previewToggle() always returns one class, but when the preview
   is hidden the button is appended to .previewbar and rendered as the app's
   standard sage outlined pill, and when it is open it is appended to
   .preview-actions and rendered as this small grey rectangle. Pressing "Show
   preview" turned the button you had just pressed into a different button in
   a different place. The Announcements pane did the same.
   Now it is asked for by name at both positions, so the control keeps one
   appearance through its own state change. */
.btn.util {
  border-radius: var(--r-detail);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink-soft);
  font-size: 0.7rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  min-height: 0;
  padding: 0.32rem 0.65rem;
}

.btn.util:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}

/* Notes print with the sheet, so they follow the same boxed treatment. */
.cs-notes {
  white-space: pre-wrap;
}

/* Sheet-level actions sit with the date they act on: quiet, inline, and
   plainly subordinate to the one primary button in the header. */
.sheet-quick {
  display: flex;
  flex-wrap: wrap;
  gap: 1.1rem;
}

.sheetbar .sheet-quick {
  align-items: center;
  gap: 0.9rem;
}

/* THE APP'S STANDING SECONDARY ACTION, at 2.99:1. The 75% mix composited to
   well under AA on a pattern used by roughly fifteen controls — Move this
   sheet, Reset, Who extends, Show them, Open that Sunday. It is meant to be
   QUIET, and it still is: the italic, the small size and the ↳ all do that
   work. The fade was the one part of it that made the words hard to read
   rather than merely subordinate. Full accent is 4.78:1. */
.quiet-inline {
  font-size: 0.8125rem;
  font-style: italic;
  color: var(--accent);
}

.quiet-inline::before {
  content: '↳ ';
  font-style: normal;
  opacity: 0.7;
}

/* .glive-view WAS HERE (Round 34) and is gone (Round 244). It was the way in
   to the live groups when they sat behind a dialog off the Bishopric tab;
   they are listed on the Groups tab itself now, so nothing links to them. */

.quiet-inline:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* The status bar names the sheet you are editing, so the answer is on screen
   no matter how far down the form you are. */
.save-sheet {
  font-weight: 650;
  color: var(--ink);
  margin-right: auto;
}

.save-sheet[hidden] {
  display: none;
}

/* The chosen date points at the sheet it opened. */
.datechip.active {
  position: relative;
}

.datechip.active::after {
  content: '';
  position: absolute;
  left: 50%;
  /* Round 256 (Derek): bigger — 16x8 rather than 10x5, the same 2:1
     proportion. The arrow is 8px of border, so its offset is in px too: in
     rem it would drift away from the pill at the larger text sizes and leave
     a gap. -7px tucks its tip 1px under the pill, so the two read as one
     shape. It still clears the hairline below the row by 7.69px. */
  bottom: -7px;
  transform: translateX(-50%);
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-top: 8px solid var(--accent);
}

/* The active chip's pointer reaches below the row, so the note beneath needs
   room to breathe — and it is a footnote, so it sits back a little. */
/* --------------------------------------------------- hover labels on icons */

/* Enough to name a bare icon, and nothing more. Instant, in the page's own
   palette — the browser's own tooltip is slow and looks borrowed. */
[data-tip] {
  position: relative;
}

[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 0.4rem);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: 15rem;
  text-align: center;
  background: var(--ink);
  color: var(--paper);
  font-size: 0.72rem;
  font-style: normal;
  font-weight: 400;
  letter-spacing: 0.01em;
  line-height: 1.35;
  padding: 0.25rem 0.5rem;
  border-radius: var(--r-detail);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
  z-index: 40;
}

[data-tip]:hover::after,
[data-tip]:focus-visible::after {
  opacity: 1;
}

/* Lift the whole control while its label is showing. Without this the label
   is painted over by anything positioned later — a sticky table header, the
   next card — however high its own z-index is. */
[data-tip]:hover,
[data-tip]:focus-visible {
  z-index: 60;
}

/* The tab bar sticks to the top of the window, so a label above it would be
   clipped off-screen once the page is scrolled. These hang below instead. */
.tabbar [data-tip]::after {
  bottom: auto;
  top: calc(100% + 0.45rem);
}

/* …and the bar only needs to scroll when the tabs cannot fit. Wherever they do
   fit it stops being a scroll container, so the labels can escape it — a
   scroll container clips its overflow on both axes, whatever their z-index.
   Hover labels only matter where there is a pointer, and a pointer means a
   screen wide enough that no scrolling was needed anyway. */
@media (min-width: 64rem) {
  .tabbar {
    overflow: visible;
    /* And WRAPS (Round 156, Derek: at the larger text sizes the bar spilled
       off the right of the page). Measured on a 1440px window: the row needs
       1198px at the default size, 1339 at the middle one and 1480 at the
       largest -- more than the window itself, so no width could hold it on a
       laptop. The groups are the flex items, so a whole group drops to the
       next line together and the dinkus before it becomes the line's mark.
       --tabbar-h is measured, so the sticky bars beneath follow the taller
       row on their own. Phones keep the single scrolling line above. */
    flex-wrap: wrap;
    row-gap: 0;
  }
}

/* Point the label DOWNWARD instead. For a control sitting in the first row of
   a table, the space above it belongs to the header — which is opaque and
   sticky — so a label drawn upward is fighting for that space. Below the
   control there is nothing but card, and nothing to lose to. */
[data-tip].tip-under::after {
  bottom: auto;
  top: calc(100% + 0.4rem);
}

@media (prefers-reduced-motion: reduce) {
  [data-tip]::after { transition: none; }
}

/* ------------------------------------- copied to the rest of the month */

/* Once the later Sundays match, the button has nothing left to do. It reads
   as SETTLED rather than broken: 0.3 opacity was right for a line of italic
   text and far too faint for a bordered control, which at that strength
   looks like a rendering fault rather than a button that is done. */
.quiet-action:disabled {
  color: var(--ink-soft);
  border-color: var(--line);
  opacity: 0.65;
  cursor: default;
}

.quiet-action:disabled:hover {
  color: var(--ink-soft);
  background: none;
  border-color: var(--line);
  text-decoration: none;
}


/* Stands in for the publish checkbox when the item is public already. */
.annrow-note {
  font-size: 0.875rem;
  font-style: italic;
  color: var(--ink-soft);
}

.annrow-note::before {
  content: '✓ ';
  font-style: normal;
  color: var(--accent);
}

/* The meeting type belongs in this box but is not part of what gets applied
   to the rest of the month, so it is ruled off and says so. */
.meetingtype {
  padding-bottom: 0.15rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 1rem;
}

/* Who leads the music is a separate question from who conducts and presides:
   it changes week to week, the month button does not copy it, and the music
   tier writes these two fields and nothing else on the sheet. The rule says all
   of that without a word — and it fences the apply-to-month link above it, so
   "these names" clearly means the pair it sits under. */
.musicnames {
  padding-top: 1rem;
  margin-top: 0.9rem;
  border-top: 1px solid var(--line);
}

/* ============================================= one member's profile page ===

   Everything the ward already knows about one person. It is a page, not a
   panel: the whole tab gives way to it, because these are the questions you sit
   and read rather than glance at beside a table. */

/* A name in the members table is a way in, so it has to look like one — but
   quietly, since every row has one and a table full of links reads as noise. */
.mnamebtn {
  font-weight: 650;
  color: var(--ink);
  text-align: left;
}

.mnamebtn:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.profile {
  max-width: 46rem;
}

.profile-top {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1.1rem;
}

.profile-name {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 650;
}

/* The back link leads, so it sits first and full width above the name. */
.profile-top .quiet-inline {
  flex-basis: 100%;
  text-align: left;
}

/* `.pcard { padding: 1rem 1.15rem }` WAS HERE and is deleted: at (0,1,0) it
   never beat `body.leader .acard { padding: 1rem 1.05rem }` at (0,2,1) inside
   the ≥64rem query — the leader app's home width — so it only ever applied on
   a phone, which is not where the member profile is read. A rule that looks
   like a deliberate difference and is a no-op is worse than no rule. */

/* A CARD'S TITLE OUTRANKS A FIELD LABEL INSIDE IT. This was byte-for-byte the
   same specification as .field-label — same size, weight, case, tracking and
   ink — so on the member profile the heading of a card and the labels beneath
   it were indistinguishable, and the card looked like a list of five equal
   labels. It keeps the small-caps idiom, which is right for this app, and
   takes the full ink and a wider tracking so it reads as the rank above. */
.pcard h3 {
  margin: 0 0 0.7rem;
  text-transform: uppercase;
  color: var(--ink);
}

/* Anything happening right now is the one thing on this page that might stop
   you doing what you came to do, so it is the one thing that carries colour. */
.pcard-live {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  background: color-mix(in srgb, var(--accent) 5%, var(--card));
}

.prow {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--line);
}

.prow:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.prow-l {
  color: var(--ink-soft);
  font-size: 0.9rem;
  white-space: nowrap;
}

.prow-v {
  text-align: right;
  overflow-wrap: anywhere;
}

/* Sustained but never set apart: a loop still open. */
.prow.is-open .prow-v {
  color: var(--notify);
  font-weight: 650;
}

.pcalling + .pcalling {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
}

.pcalling-t {
  margin: 0 0 0.35rem;
  font-size: 1.05rem;
  font-weight: 650;
}

.pcard .empty {
  padding: 0.9rem 0;
  text-align: left;
}

/* Editing one of the four pulpit lists: a name per row, EDITABLE and
   removable, with one box to add to it. Short by design — these are the
   handful of people who cycle through the job, not a directory. A row is
   flat until the pointer or the caret is on it (Round 251), which is the
   callings tracker's idiom and the search box's hover: the list reads as a
   list, and a misspelling is corrected where it stands. */
.namelist {
  margin: 0.9rem 0 0.6rem;
  border-top: 1px solid var(--line);
}

.namelist .empty {
  padding: 1.1rem 0;
}

.namerow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.2rem 0.1rem;
  border-bottom: 1px solid var(--line);
}

/* SCOPED PAST THE GENERAL INPUT RULE, and written long. The shared
   `input[type='text'], ...` block near the top of this file is (0,1,1) and
   a bare `.namerow-n` is (0,1,0), so it kept its own paper fill, hairline
   and 8px padding and every row rendered as a filled box at rest -- three
   of them stacked inside a dialog that is supposed to read as a list. Same
   trap as Round 125's save-tick reserve and Round 243's select gutter.
   No left padding of its own: the box is pulled back by its own inset so
   the names line up with the sentence above them, hovered or not. The padding needs body.leader as well: the desktop density
   block sets it at (0,2,2). */
body.leader .namelist input.namerow-n {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 0.95rem;
  color: var(--ink);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-detail);
  padding-top: 0.24rem;
  padding-right: 0.4rem;
  padding-bottom: 0.24rem;
  padding-left: 0.4rem;
  margin-left: -0.4rem;
  transition: background 0.15s ease, border-color 0.15s ease;
}

/* The pointer anywhere on the ROW raises every box, not just the one it is
   over: that is what says the names are editable at all, which is the thing
   Derek could not tell from the old labels. */
body.leader .namerow:hover input.namerow-n,
body.leader .namelist input.namerow-n:focus {
  background: var(--card);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}

@media (prefers-reduced-motion: reduce) {
  body.leader .namelist input.namerow-n {
    transition: none;
  }
}

.nameadd {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.nameadd input {
  flex: 1;
}

/* Why a name was not added, said where the pressing happened. It collapses
   when empty: an error line holding its own blank row reads as a hole above
   the buttons (the trap Round 6 fixed inside .modalcard). */
.namenote {
  margin: 0.5rem 0 0;
  font-size: 0.8125rem;
  color: var(--warn-ink);
}

.namenote:empty {
  display: none;
}


/* ------------------------- the ward's announcements, previewed while editing */

.preview-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: 0.5rem;
}

.preview-head .field-label {
  margin: 0;
}

/* The heading belongs to the previewed PAGE, not to the pane holding it. The
   page is a narrow column centred in a much wider pane, so a heading row that
   spanned the pane put "Live preview" out at the far left with nothing beneath
   it. The head and the page now share one width — declared once, here — so
   the label sits on the page's left edge and the button on its right, and they
   cannot drift apart. Scoped to .annpane: the conducting sheet's preview fills
   its pane, so its heading is right to span it.
   `width: 100%` matters — .previewpane is a flex column, and an auto inline
   margin makes a flex child shrink to its content instead of stretching. */
/* 40rem is the public column's real width, so the preview is the ward's page
   at its own size wherever the pane has room and reflows narrower where it
   does not — which is what a member on a small phone sees anyway. It was
   26rem, sized for a simplified card rather than for the page itself. */
.annpane {
  --preview-page: 40rem;
}

.annpane .preview-head,
.annpane .annpreview {
  width: 100%;
  max-width: var(--preview-page);
  margin-inline: auto;
}

/* The page scrolls INSIDE the pane, the way the conducting sheet's
   .preview-paper does. Without this the paper simply grew past the sticky
   pane's max-height: it could not be scrolled, so there was nothing for
   "follow the announcement I'm editing" to scroll, and keeping your place
   across a redraw was meaningless too. `min-height: 0` is the part that lets
   a flex child shrink below its own content height — without it the rule
   looks right and does nothing. */
.annpane .annpreview {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Set as a narrow page, the way it lands on a phone — the public column is
   40rem and must not be previewed at the width of a desktop pane. border-box
   so that width is the page's real outer edge, which is what the heading above
   is lining up with. */
/* The FRAME only. The page inside brings its own measure and padding
   (.wp-paper), so this supplies nothing but the sheet it is printed on — the
   same device .preview-paper uses beside the conducting sheet. It used to
   carry the page's padding too, which double-inset everything once the real
   masthead moved in. */
.annpaper {
  box-sizing: border-box;
  max-width: var(--preview-page, 40rem);
  margin: 0 auto;
  padding: 0 0 0.6rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: var(--lift);
}

/* The tab strip in the announcements preview is a picture of the ward's, not a
   working one — there is only the announcements panel behind it. This kills
   the click and, with it, the pointing-hand cursor `.tab` sets: with
   pointer-events off the pointer never lands on the pill, so it keeps the
   ordinary arrow it has over the page. Doing it here rather than with
   `cursor: default` on the pill means a stray click cannot focus one either. */
.annpaper .tabs {
  pointer-events: none;
}

/* `.annpaper-ward` and `.annpaper-head` were deleted here: they set the
   invented "PROVO 1ST WARD / Announcements" head of the old simplified card,
   which the real masthead has replaced. Checked against src/worker.js as well
   as the app before removing them — the `.ann*` classes on the public page
   are emitted by the SERVER, so grepping only the app's JavaScript has led to
   deleting a live rule here before. */

/* NO SPACING RULE FOR .ann HERE ANY MORE. This preview is now the public
   page's own markup (.wp-paper + .panel + .ann), so it takes the page's own
   `.ann { padding-bottom; margin-bottom; border-bottom }` and its
   `.ann:last-child` exception. The override that used to live here added a
   SECOND rule on top of each notice, which is exactly the kind of drift a
   replica exists to prevent. */

/* The announcement still being written, shown where publishing will put it.
   A DASHED left edge rather than a tint: the panel's whole job is to show the
   ward's page truthfully, so the unpublished one has to be unmistakable
   without repainting it into something that no longer looks like the page. */
.annpaper .ann.ann-draft {
  border-left: 2px dashed color-mix(in srgb, var(--accent) 55%, transparent);
  padding-left: 0.75rem;
  margin-left: -0.1rem;
}

.ann-draftflag {
  margin: 0 0 0.35rem;
  text-transform: uppercase;
  color: var(--accent);
}

/* Full screen: the same overlay the conducting sheet uses, but the page inside
   it opens out to the 40rem the ward actually reads it at instead of the
   26rem the side panel squeezes it into. */
.annstand .annpaper {
  max-width: 40rem;
  margin: 1rem auto 0;
  font-size: 1.05rem;
}

/* A date box that confirms the weekday it landed on. */
.datefield {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

/* CLEARING A DATE OR A TIME (Round 273, Derek). A faded x inside the box,
   solid on hover. Faded by mixing toward the CARD rather than toward
   transparent, so it is one value in both palettes.

   78%, MEASURED, NOT PICKED. The first cut was 58% and came out at 2.32:1
   against the card -- the exact figure Round 43 had to fix as a real defect,
   and below the 3:1 a control you have to aim at needs. 78% is 3.43:1, still
   plainly faded beside the box's own soft-ink text at 5.57:1 and the value
   at full ink. "Faded" is the brief; invisible is not. */
.fieldclear {
  position: absolute;
  top: 50%;
  right: 0.5rem;
  transform: translateY(-50%);
  font: inherit;
  font-size: 1.05rem;
  line-height: 1;
  padding: 0 0.25rem;
  background: none;
  border: 0;
  border-radius: var(--r-detail, 4px);
  color: color-mix(in srgb, var(--ink-soft) 78%, var(--card));
  cursor: pointer;
}

.fieldclear:hover,
.fieldclear:focus-visible {
  color: var(--ink);
  background: color-mix(in srgb, var(--ink-soft) 13%, transparent);
}

/* The date input is width:100% and box-sizing:border-box, so this wrapper is
   the same box and "right" is measured off the same edge. It exists at all
   because .datefield is a column holding the box AND the weekday line
   beneath it, which is not the box to position against. */
.clearwrap {
  position: relative;
  display: block;
}

/* Clear of the browser's own calendar button, which is drawn by the browser
   inside the input and cannot be moved or restyled. */
.clearwrap .fieldclear {
  right: 2.05rem;
}

.dayhint {
  min-height: 1.1em;
  font-size: 0.8125rem;
  font-style: italic;
  color: var(--accent);
}

.dayhint:empty {
  min-height: 0;
}

/* An announcement shown the way the ward reads it on the program. One set of
   rules for all three places that display one, so they cannot drift. */
.annview-title {
  margin: 0;
  font-weight: 700;
  line-height: 1.3;
}

.annview-when {
  margin: 0.15rem 0 0;
  color: var(--accent);
  font-size: 0.85rem;
}

.annview-text {
  margin: 0.35rem 0 0;
  color: var(--ink-soft);
  white-space: pre-wrap;
}

/* Round 230: it holds paragraphs and lists now rather than one run of
   pre-wrapped text, so that the markers themselves are never shown. */
.annview-text p,
.annview-text ul,
.annview-text ol {
  margin: 0 0 0.3rem;
}

.annview-text ul,
.annview-text ol {
  padding-left: 1.2rem;
}

.annview-text > :last-child {
  margin-bottom: 0;
}

.annview-photo {
  display: block;
  max-width: 100%;
  margin-top: 0.6rem;
  border-radius: var(--r-item);
}

/* ------------------------------------- 3 · Announcements read from the stand */

/* These are list items, not forms. Numbered because they are read in that
   order from the pulpit — which also means the arrows do not have to shout
   to convey that the order matters. */
.standread {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: ann;
}

.standread-item {
  counter-increment: ann;
  position: relative;
  padding: 0.75rem 4.3rem 0.75rem 1.9rem;
  border-top: 1px solid var(--line);
}

.standread-item:first-child {
  border-top: none;
  padding-top: 0.25rem;
}

.standread-item::before {
  content: counter(ann);
  position: absolute;
  left: 0;
  top: 1.05rem;
  width: 1.25rem;
  text-align: right;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--ink-soft);
}

.standread-item:first-child::before {
  top: 0.55rem;
}

/* Set like the rendered announcement it will become, and unboxed until you
   are actually in it — so the list reads the way the published panel does
   while staying directly editable, with no click to "unlock" a row. */
textarea.grow {
  resize: none;
  overflow: hidden;
  min-height: 0;
  padding: 0.35rem 0.5rem;
  line-height: 1.4;
  font-weight: 700;
  border-color: transparent;
  background: transparent;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

textarea.grow:hover {
  background: color-mix(in srgb, var(--ink) 4%, transparent);
}

textarea.grow:focus {
  font-weight: 400;
  border-color: var(--accent);
  background: var(--paper);
}

@media (prefers-reduced-motion: reduce) {
  textarea.grow { transition: none; }
}

/* THE X IN THE BOX, THE ARROWS BESIDE IT (Round 284, Derek). The x acts on
   THIS announcement, so it belongs to its box; the arrows act on its place
   among the others, so they sit in a gutter down the right of the row,
   centred on the announcement they would move. */
.annbox {
  position: relative;
}

/* THE CORNER ALREADY HAD A TENANT. Every box on this sheet carries the
   save tick in its top right (Round 125), and in a textarea the tick sits
   at the TOP right because the bottom right belongs to the resize grip --
   so the x Derek asked for landed exactly on it, measured overlapping by
   20px. Two controls in one place, one of them destructive.

   The x takes the corner, since that is the position asked for, and the
   tick steps left of it. Both keep their meaning and neither can be
   pressed by mistake for the other.

   Written body.leader .annbox ... at (0,4,2) to beat Round 125's own
   `body.leader .tickwrap > textarea` (0,3,2), which is what reserves the
   room in the first place -- a plainer selector loses to it silently and
   the text runs under whichever control is further in. */
body.leader .annbox .tickwrap > textarea {
  padding-right: 3.6rem;
}

.annbox .tickwrap.tick-area .fieldtick {
  right: 1.75rem;
}

/* WRITTEN WITH .annbox IN FRONT so it outranks [data-tip]'s own
   `position: relative`, which is later in this file and would otherwise
   un-position it -- the Round 10 trap, and Round 255's fix for it on the
   published notice's own corner x. */
.annbox .annkill {
  position: absolute;
  top: 0.15rem;
  right: 0.15rem;
  min-width: 1.5rem;
  min-height: 1.5rem;
  padding: 0;
  font-size: 0.8rem;
  line-height: 1;
  border-color: transparent;
  background: none;
  opacity: 0.45;
  transition: opacity 0.15s ease, color 0.15s ease;
}

.standread-item:hover .annkill,
.annbox .annkill:focus-visible {
  opacity: 1;
}

@media (hover: none) {
  .annbox .annkill { opacity: 0.8; }
}

@media (prefers-reduced-motion: reduce) {
  .annbox .annkill { transition: none; }
}

.annfoot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  margin-top: 0.4rem;
}

.annpub {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 0.875rem;
  color: var(--ink-soft);
  cursor: pointer;
}

/* The controls step back until you reach for the row they belong to. */
.anncontrols {
  display: flex;
  gap: 0.2rem;
  opacity: 0.35;
  transition: opacity 0.15s ease;
}

/* OUTSIDE THE BOX, CENTRED ON THE ROW (Round 284, Derek: "just to the
   right, but on the outside of, the announcement boxes ... in horizontal
   center alignment with that particular announcement"). The row carries a
   right padding to hold the gutter open, so nothing overlaps whatever the
   arrows are beside. (0,2,0) to beat the flow rule above. */
.standread-item .anncontrols {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}

.standread-item .anncontrols .iconbtn {
  min-width: 1.7rem;
  min-height: 1.7rem;
}

.standread-item:hover .anncontrols,
.anncontrols:focus-within {
  opacity: 1;
}

/* TWO OPACITIES WERE MULTIPLYING. The row's controls sit at 0.35 until it is
   hovered, and .iconbtn:disabled adds 0.3 — so an arrow that cannot move
   rendered at 0.105 effective opacity in a 32px box: a smudge, not a control.
   With one item on the sheet, which is most Sundays, BOTH arrows are in that
   state, so the row showed two ghosts and a working ✕.
   Round 10 already settled this for the Announcements tab: an out-of-range
   arrow is hidden and holds its space, because a dead button still reads as
   one you pressed wrongly. Same treatment here. */
.anncontrols .iconbtn:disabled {
  visibility: hidden;
  opacity: 1;
}

.anncontrols .iconbtn {
  min-width: 2rem;
  min-height: 2rem;
  font-size: 0.85rem;
  border-color: transparent;
}

.anncontrols .iconbtn:hover:not(:disabled) {
  border-color: var(--accent);
}

/* No hover on a touch screen, so they must simply be there. */
@media (hover: none) {
  .anncontrols { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .anncontrols { transition: none; }
}

/* -------------------------------------------------- 5 · Items of business */

/* Most Sundays every list here is empty, so an empty one must cost a word on
   the heading line, not a heading plus a sentence of its own. */
.bizhead {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
}

.bizhead .field-label {
  margin: 0;
}

.bizhead-side {
  display: flex;
  gap: 1.1rem;
}

.bizempty {
  margin: 0;
  font-size: 0.875rem;
  font-style: italic;
  color: var(--ink-soft);
}

.bizlabel {
  font-size: 0.9375rem;
  font-weight: 650;
}

.bizcount {
  font-size: 0.72rem;
  font-weight: 700;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: var(--notify);
  color: var(--on-notify);
}

.bizstake {
  margin: 0 0 0.9rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--line);
}

/* The two lists that arrive from elsewhere, held together under one heading
   that says where they come from. */
.bizsec {
  margin-top: 1.1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
}

.bizblurb {
  margin: 0.2rem 0 0.15rem;
}

/* (The italic note under 'From the Callings tab' went in Round 289, Derek's
   call -- so .bizsec .bizblurb went with its one emitter. .bizblurb itself
   stays: the proposals card on the Callings tab still uses it.) */

.bizgroup {
  margin-top: 0.55rem;
}

.bizgroup .bizrow {
  margin-top: 0.4rem;
}

/* ------------------------------------------- Admin: who can sign in, and as what */

.adminbar {
  align-items: center;
}

.adminbar h2 {
  margin: 0;
  font-size: 1.2rem;
}

/* One card per tier, so the shape of who-sees-what is legible at a glance
   rather than something you have to hold in your head. */
/* `.tiercard { padding: 1rem 1.15rem 0.9rem }` WAS HERE and is deleted, for
   the same reason as .pcard above: (0,1,0) never beat the leader app's own
   `.acard` padding at (0,2,1) on any screen the Admin page is used on. */

.tierhead {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.tierhead .field-label {
  margin: 0;
}

.tierblurb {
  margin: 0.25rem 0 0.7rem;
  max-width: 46rem;
}

/* Before a level is chosen there is no description to give, and an empty
   paragraph would hold the gap open. Same reason as .unlock-err:empty. */
.tierblurb:empty {
  display: none;
}

/* The Admin table has five columns, not the tracker's ten — it must not
   inherit the wide minimum that forces the tracker to scroll. */
.ptable.atable {
  min-width: 0;
}

.atable td {
  vertical-align: middle;
}

/* The two name columns take the slack so the actions stay beside them. */
.atable th:first-child,
.atable td:first-child,
.atable th:nth-child(2),
.atable td:nth-child(2) {
  width: 21%;
}

.atable th:last-child {
  text-align: right;
}

/* The Admin table is a few rows per tier — it never scrolls, so it has no use
   for a sticky header, and the sticky header is an OPAQUE LAYER sitting exactly
   where a row's hover label wants to be. Ordinary flow instead. */
.atable th {
  position: static;
  z-index: auto;
}

.atable input[type='text'] {
  min-width: 7rem;
}

.atable-when {
  white-space: nowrap;
  color: var(--ink-soft);
  font-size: 0.9rem;
}

.atable-when .wait {
  font-style: italic;
  opacity: 0.7;
}

/* NOT display: flex. A <td> set to flex LEAVES THE TABLE BOX MODEL: it sizes
   itself to its own content instead of stretching to the row, so in a 48px row
   this cell was 42px and its bottom border landed 6px above every other cell's.
   That is the rule that appeared to break halfway along an admin row. Inline
   layout keeps it a real table cell; the three declarations below do what
   justify-content, align-items and gap were doing. */
.atable-acts {
  text-align: right;
  white-space: nowrap;
}

.atable-acts > * {
  vertical-align: middle;
}

.atable-acts > * + * {
  margin-left: 0.35rem;
}

/* The three row actions are words now (Round 277). They are quiet text,
   not buttons: the row already carries editable fields, and three pills
   per row on a table of a dozen people would be the loudest thing on a
   screen you open twice a year. The leading glyph .quiet-inline draws
   is dropped -- it marks an action branching off a heading, which is not
   what a row of three equals is. */
.atable-acts .quiet-inline::before {
  content: none;
}

.atable-acts .quiet-inline {
  font-size: 0.8125rem;
}

.atable-acts .iconbtn {
  min-width: 2.25rem;
  min-height: 2.25rem;
  font-size: 0.95rem;
}

/* Someone released: still listed, plainly inactive — but the DIMMING STOPS AT
   THE ACTIONS CELL, for two reasons. Those buttons are the whole point of a
   turned-off row (you came here to switch it back on), so they must not read as
   disabled. And opacity below 1 makes a cell a stacking context: it trapped the
   hover labels inside it, where no z-index could lift them above the sticky
   header, so "Turn back on" was unreadable. Do not put opacity back on the
   whole row. */
.atable tr.is-off td:not(.atable-acts) {
  opacity: 0.55;
}

/* A readable preview of the letter that is about to be sent — long enough to
   scan, capped so the dialog never runs off the screen. */
.codemsg {
  margin: 0.9rem 0 0;
  padding: 0.8rem 0.95rem;
  max-height: 17rem;
  overflow-y: auto;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.76rem;
  line-height: 1.6;
  white-space: pre-wrap;
  text-align: left;
  color: var(--ink-soft);
}

.codedone {
  display: block;
  margin: 0.9rem auto 0;
}

.signin-note {
  margin-top: 0.9rem;
}

/* Quiet footnote on the Welcome page when the shared password is in use. */
.shared-note {
  margin: 2.75rem auto 0;
  max-width: 30rem;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--ink-soft);
}

.shared-note .quiet-inline {
  font-size: inherit;
}

/* ------------------------------- the ward's own page, seen from the inside */

/* Set as the printed program is: a narrow centred column of leader rows. It is
   deliberately flat — no inputs anywhere — because the Conducting sheet and the
   Announcements tab are the only things allowed to write here. */
/* Wide enough to hold the 40rem replica plus the page's own 1.25rem gutters
   without squeezing them — squeeze the column and the line breaks stop
   matching the ward's, which is most of what "the same page" means. */
.wardpage {
  max-width: 45rem;
  margin: 0 auto;
  padding: 1.25rem 1rem 1.6rem;
}

.wp-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--line);
}

.wp-head .field-label {
  margin: 0;
}

/* text-decoration, not border-bottom — the same swap as .install-link above,
   for the same reason. */
.wp-link {
  font-size: 0.78rem;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--accent) 30%, transparent);
  text-underline-offset: var(--ul-offset, 0.2em);
}

.wp-link:hover {
  color: var(--ink);
  text-decoration-color: var(--ink);
}

/* Full screen, at the other end of the head row. No leading glyph: the
   arrow marks an action branching off a heading above it (Round 185),
   and this one leads its own row opposite a link. */
.wp-head .quiet-inline::before {
  content: none;
}

.wp-head .wp-full {
  font-style: normal;
  font-size: 0.78rem;
}

/* The replica itself. It holds NO styling of its own beyond this column: the
   masthead, tabs, dotted rows, program and announcements inside it are the
   public page's own classes doing their own work, at the 40rem width the ward
   reads them at. That is the whole point — there is nothing here to keep in
   step, so the copy cannot drift from the original.

   Everything that used to live here (.wp-masthead, .wp-row, .wp-ann and the
   rest) was a SECOND rendering of the public page, and it had already drifted:
   flat label/value rows instead of dotted leaders, the address in the masthead
   instead of the footer, no fleuron. It is gone. */
.wp-paper {
  max-width: 40rem;
  margin-inline: auto;
  padding-inline: 1.25rem;
  box-sizing: border-box;
}

/* The public masthead opens with 2.25rem of air above it, which it needs at
   the top of a browser tab and does not need directly under a card's own
   heading rule. */
.wp-paper .masthead {
  padding-top: 1.5rem;
}

/* The public page's <main> carries this air under the tabs (it gets it from
   .wrap, which this column cannot borrow — .wrap is 76rem inside the leader
   app). Without it the first line sits tight under the pills. */
.wp-paper main {
  padding-top: 1.9rem;
  padding-bottom: 1rem;
}

/* The ward's page ends at the bottom of the screen; in here the card ends, so
   the footer keeps its rule but drops the tail of empty space. */
.wp-paper .colophon {
  padding-bottom: 0.4rem;
}

/* The leader app's own shortcut, deliberately outside the paper so it cannot
   be mistaken for something the ward sees. */
/* One child again: Round 238's "Add existing group" button stood here for a
   day and went to the Groups tab in Round 244, taking the wrap and nowrap
   rules it needed with it. */
.wp-editrow {
  display: flex;
  justify-content: flex-end;
  min-height: 1.4rem;
  margin-top: 0.9rem;
}

/* --------------------------------- LIVE PROGRAM, at the size of a phone

   Round 295, Derek: an option on that page to pull it up full screen,
   full top to bottom, with the sides that are not part of the program
   left blank -- so that it shows what it will actually look like for a
   member scrolling the live program on their phone.

   The replica in the card behind is 40rem wide, which is a tablet. This
   is the same replica -- the same wardPageView, so the two cannot drift
   -- standing in one column the width of a phone, on a plain ground, in
   a window it fills top to bottom and scrolls inside.

   THE COLUMN IS 390px WHATEVER THE READER'S TEXT SIZE IS SET TO. The
   leader app scales itself in five steps (Round 10) and the replica is
   deliberately allowed to scale with it, which is right for a picture of
   a phone page sitting in a card and wrong for a claim about what a
   phone shows. So the paper is zoomed by 16/root in script -- Round
   155's technique for the printed sheet, for the same reason -- and its
   width is set in rem, so width x root x (16/root) lands on 390px every
   time. See openWardFullscreen in app.js. */
body.phoneframe {
  overflow: hidden;
}

.phoneview {
  position: fixed;
  inset: 0;
  /* Above the app and its sticky bars, below the flyer lightbox (90),
     which opens FROM this view and has to sit over it. */
  z-index: 55;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  animation: planfade 200ms ease-out;
}

/* Slim on purpose: everything under it is the ward's page, and the point
   of the view is how much of that page a phone holds at once. */
.phoneview-bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.45rem 1rem;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
}

.phoneview-stage {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  /* The blank either side: the app's paper taken a few points down, so
     the column reads as the lit screen and the surround as the desk. */
  background: color-mix(in srgb, var(--ink) 7%, var(--paper));
}

.phoneview-stage .wp-paper {
  flex: none;
  width: 24.375rem;
  max-width: 100%;
  min-height: 100%;
  box-shadow: 0 0 0 1px var(--line), 0 10px 34px rgba(20, 16, 10, 0.14);
}

/* The replica trims two paddings because it sits in a card under a
   heading rule (see .wp-paper .masthead and .wp-paper .colophon above).
   In here there is no card, so the ward's own air comes back -- this
   column is a claim about the real page and has to keep its proportions. */
.phoneview-stage .wp-paper .masthead {
  padding-top: 2.25rem;
}

/* 4.5rem, matching .colophon's own bottom padding (Round 304) -- this
   stage's whole claim is that it shows what a real phone shows, and a
   stale copy here would understate the room Round 304 gave the Appearance
   row against an external overlay control. */
.phoneview-stage .wp-paper .colophon {
  padding-bottom: 4.5rem;
}

@media (prefers-reduced-motion: reduce) {
  .phoneview {
    animation: none;
  }
}

/* It is a preview of a screen, so it has no business on paper: printing
   with it open would put the overlay over whatever was being printed. */
@media print {
  .phoneview {
    display: none;
  }
}




/* A two-column row that has more beneath it needs a line to close it off,
   otherwise the divider between the two cells just runs out. The last row in
   a block is closed by the block's own border. */
.cs-row2:not(:last-child) {
  border-bottom: 1px solid var(--line);
}

@media print {
  .cs-row2:not(:last-child) {
    border-bottom-color: #bbb;
  }
}

/* Confirmation that the later Sundays already carry these details. */
.applied-note {
  margin: 0.5rem 0 0;
  font-size: 0.78rem;
  font-style: italic;
  color: var(--success-ink);
}

.applied-note::before {
  content: '✓ ';
  font-style: normal;
}

/* ============================ announcements: what the ward is reading now ===

   This was a two-column grid of bordered cards, each ending in a row of four
   boxed icon buttons. Three things were wrong with it and all three are fixed
   here by the same move — turning it back into a LIST.

   1. The order is the point. These print on the public page one after another,
      and the only controls were ↑ / ↓ — yet a two-column grid gives the eye no
      reading order at all. Now every row is numbered and full width.
   2. The chrome outweighed the content. Four 2.75rem outlined buttons per card
      were the loudest thing on the screen, louder than the announcements. The
      controls are borderless here and carry their labels on hover.
   3. The cards were ragged: unequal heights, actions floating wherever the
      text happened to stop. Ruled rows sit flush, the way the printed program
      sets its own rows.                                                     */

.annlist {
  list-style: none;
  margin: 0 0 1.6rem;
  padding: 0;
  border-top: 1px solid var(--line);
}

.annrow {
  display: grid;
  grid-template-columns: 1.9rem minmax(0, 1fr) auto;
  gap: 0 0.9rem;
  align-items: start;
  padding: 0.9rem 0.5rem;
  border-bottom: 1px solid var(--line);
}

/* The controls sit flush right so they form one column down the list, which
   leaves a wide gap between an announcement and its own buttons. The row tint
   closes that gap — it says which row you are about to act on. */
.annrow:hover {
  background: color-mix(in srgb, var(--ink-soft) 5%, transparent);
}

/* The row being edited is tinted more strongly, so there is never a question
   about which announcement the open form belongs to. */
.annrow.is-open,
.annrow.is-open:hover {
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}

/* Its position on the ward's page — quiet, but the reason the arrows mean
   anything. Lining figures so the column stays straight past nine. */
.ann-pos {
  font-size: 0.82rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  text-align: right;
  padding-top: 0.22rem;
}

.ann-main {
  min-width: 0;
}


/* The row shows the announcement through the shared `annview` component — the
   same rendering as the Live page tab and the conducting sheet's published
   panel — so it can never drift from what the ward actually reads. Only the
   sizes are tuned here, and ONLY inside a row: the public page uses its own
   .ann-* classes and must not be touched by any of this. */
.annrow .annview-title {
  font-size: 1.05rem;
  font-weight: 650;
  color: var(--ink);
}

.annrow .annview-text {
  max-width: 46rem;
  font-size: 0.92rem;
  line-height: 1.5;
}

.annrow .annview-photo {
  max-height: 6.5rem;
  width: auto;
  border: 1px solid var(--line);
}



/* Housekeeping — when it comes down, where it came from. One line, plainly
   subordinate, so it never sits at the weight of the announcement itself. */
.ann-foot {
  margin: 0.45rem 0 0;
  font-size: 0.75rem;
  font-style: italic;
  color: var(--ink-soft);
}

.ann-acts {
  display: flex;
  align-items: center;
  gap: 0.1rem;
  padding-top: 0.1rem;
}

/* Borderless. A filled, outlined box for every action is what made the old
   cards read as a control panel with an announcement attached. */
.ann-act {
  min-width: 2.1rem;
  min-height: 2.1rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-size: 0.95rem;
  background: none;
  border: none;
  border-radius: var(--r-item);
  color: var(--ink-soft);
  cursor: pointer;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.ann-act:hover {
  background: color-mix(in srgb, var(--ink-soft) 12%, transparent);
  color: var(--ink);
}

.ann-act.danger:hover {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  color: var(--danger);
}

/* The one action worth spelling out. "Edit" cannot be misread; ▾ had to be
   explained in a sentence above the list. */
/* The same rank as .quiet-inline — italic, 0.8125rem, quiet accent — and it
   used to sit at 80% where that one sits at 75%. A 5% alpha step is invisible
   on screen and arbitrary in the file: a fifth variant of one thing, bought
   for nothing. Same value now. */
.ann-act-word {
  min-width: 0;
  padding: 0 0.6rem;
  font-size: 0.8125rem;
  font-style: italic;
  color: var(--accent);
}

.ann-act-word:hover {
  color: var(--accent);
}

/* Top row, bottom row: the arrow keeps its space but shows nothing. A disabled
   button still looks like a button you pressed wrongly. */
.ann-act.is-void {
  visibility: hidden;
}

/* Spans the row, but indented to the text column — the form is about that
   announcement, so it should start where the announcement starts. */
.ann-edit {
  grid-column: 1 / -1;
  margin-top: 0.75rem;
  padding: 0.85rem 0 0 2.8rem;
  border-top: 1px solid var(--line);
}

.ann-edit .field:last-child {
  margin-bottom: 0.2rem;
}

/* The way out of the editor. Right-aligned and full size, sitting on its own
   ruled line under the last field, so it reads as the end of the form rather
   than as one more control in it — the small italic 'Done' it replaces sat up
   in the row's corner and was missed completely. */
.annsave-row {
  display: flex;
  justify-content: flex-end;
  margin-top: 1.1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
}

.annsave-btn {
  min-width: 8rem;
}

/* Finished notices. Muted with COLOUR, never opacity — opacity makes a
   stacking context and would trap the hover labels underneath the page. */
.annlist.is-past .annview-title {
  font-weight: 500;
  color: var(--ink-soft);
}

.annlist.is-past .annview-when {
  color: var(--ink-soft);
}


/* The quiet count beside a group heading: a fact, not a notification, so it is
   never the filled accent bubble the tab bar uses. */
.grouph-count {
  font-size: 0.8rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

/* Where the Announcements tab stops being a form and starts being a record of
   what the ward is reading. Those are two different kinds of thing, and the
   composer's own card border was the only thing dividing them — so the
   heading beneath it read as the card's last row rather than as the start of
   something else. A rule across the column says "a new thing begins here",
   which is the one job a horizontal rule does well and exactly what is meant
   here. (Contrast the apply-to-month links, where a rule was REMOVED because
   it wrongly cut a control off from the fields it belonged to.) */
.grouph.annsection {
  margin-top: 2.5rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--line);
}

@media (prefers-reduced-motion: reduce) {
  .ann-act { transition: none; }
}

/* On a phone the actions drop under the text rather than squeezing it. */
@media (max-width: 40rem) {
  .annrow {
    grid-template-columns: 1.5rem minmax(0, 1fr);
  }

  .ann-acts {
    grid-column: 2;
    margin-top: 0.5rem;
    margin-left: -0.5rem;
  }

  .ann-edit {
    padding-left: 0;
  }
}

/* ==================================== social groups — "Find Your People" */

/* THERE IS NO HANDWRITING FONT, and adding one back needs a decision first.

   The answers on a group card used to be set in whatever hand the device had
   (Bradley Hand / Segoe Print / Ink Free / Chalkboard). No web font is loaded
   here — this page promises no third-party requests, and a Google font would
   send every visitor's address to Google — so the result differed on every
   device and Android, which ships no hand at all, fell through the list to
   Comic Sans or plain text.

   It was dropped rather than fixed with a self-hosted file, because the card
   already reads as an index card without it: the green band, the small-caps
   labels, and above all the RULE under each answer, which is the fill-in-the-
   blank cue. The hand was a second signal for the same idea, and the only one
   that did not survive the trip to a stranger's phone. It was also the one
   place the site left its own voice — everything else here is a printed
   program, not a scrapbook.

   If a consistent hand is ever wanted, ONE self-hosted file is the way; never
   a Google Fonts link. */

/* CENTRED, and standing 4rem off the first card (1.9rem in Round 343, 3rem
   in Round 345, 3.5rem in Round 347, and Derek asked for slightly more
   again in Round 349 -- four asks in the same direction, so the number is
   his rather than a measurement).

   Round 343 set this to 1.9rem to MATCH the gap between the tab row and
   Find Your People, which is what Derek asked for then. He then asked for
   more, and he is right: a group card is a heavy object -- bordered, with
   the double rule of a trade card -- so it wants more air above it than a
   line of type does. Matching a heading gap was the wrong measure for it.
   3rem reads as a clear break without becoming a section break; the big
   ones on this page are 5rem and up.

   text-wrap: balance because centred prose with a ragged last line reads as
   an accident; the paragraph is four short sentences, well inside the line
   count browsers will balance. */
.glead {
  margin: -0.2rem 0 4rem;
  text-align: center;
  text-wrap: balance;
  color: var(--ink-soft);
}

/* THE TRACK CAN NEVER BE WIDER THAN THE COLUMN HOLDING IT (Round 333).
   A bare minmax(16.5rem, 1fr) sets a HARD floor: 16.5rem is 264px at the
   ordinary root and 396px at a 150% browser text size, which is wider than
   any phone column -- so the card ran off the side of the page and took the
   whole document with it (measured 106px of sideways scroll at 320px, 51 at
   375, 36 at 390). min(16.5rem, 100%) keeps the 16.5rem wherever there is
   room for it and gives way to the container where there is not, so the
   desktop grid is untouched and the phone simply gets one card per row.
   The percentage resolves against this grid box, which has a definite
   width, so auto-fill still counts its tracks correctly. */
.gcards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(16.5rem, 100%), 1fr));
  gap: 1rem;
}

/* An index card: green band across the head, a label over each ruled answer. */
.gcard {
  background: var(--card);
  border: 1px solid var(--line);
  border-top: 5px solid var(--accent);
  border-radius: var(--r-detail);
  padding: 0.95rem 1.05rem 1.1rem;
  box-shadow: var(--lift);
}

/* This was a filled-in FORM — a label and a ruled line over each of four
   answers — because the answers were set in a handwriting face and the form
   was what they were written on. With the hand gone the scaffolding had no
   job left: four labels and four rules meant nothing on the card could be the
   headline, and "GROUP NAME" sat above the name saying what the name already
   said. Typography carries the hierarchy now instead. */

.gcard-name {
  margin: 0;
  /* 1.65rem, from 1.5 (Round 332, Derek: every Caslon text on the public
     page two points larger -- the same conversion Round 328 used to take
     this pair 1.35 -> 1.5). .ann-title moved with it: the two card
     headlines have been one rank since Round 325 and must stay one.
     THE RULE THAT ACTUALLY WINS IS THE --ds ONE at the foot of this file.
     Both were changed; changing only this one would have looked right in the
     source and done nothing on the page, which is what Round 303 paid for. */
  font-size: 1.65rem;
  font-weight: 400;
  line-height: 1.2;
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* When and where takes the accent: it is the line people actually scan for,
   and the one that decides whether they can come.

   ITALIC, IN THE MASTHEAD DATE'S OWN VOICE (Round 317): the page already has
   a voice for a date set as prose -- .mdate -- so this line takes it, and the
   card's dateline reads as the masthead's does.

   IT IS NOW THE MODEL FOR THE ANNOUNCEMENT CARD TOO (Round 325). A notice's
   date used to be a small-caps label; Derek asked for the two cards to print
   the same three lines, so .ann-when copies this rule's TYPE -- face, size,
   weight, slope, leading. KEEP THOSE IN STEP; if one changes here, ask whether
   it should there. Their COLOURS deliberately parted in Round 329, when Derek
   took the notice's date to the ink and left a group's in the accent.
   (.ann-where is the same type again in the soft ink and, since Round 327, a
   size down: a variation on this, not a copy of it.) */
.gcard-when {
  margin: 0.45rem 0 0;
  font-size: 0.9375rem;
  font-style: italic;
  line-height: 1.5;
  color: var(--accent);
  overflow-wrap: anywhere;
}

.gcard-foot {
  margin-top: 0.85rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--line);
}

/* An instruction, not a field name — "Text Derek to join" tells you what to do
   with the number underneath, which "Text to join" over a labelled box did not. */
/* THE SMALLEST AND FAINTEST TEXT ON THE PAGE, and it was the instruction —
   the line telling somebody how to join a group. 0.62rem is 9.9px, and the
   85% mix composited to #81796d on the card: 4.23:1, the only text on the
   public page missing AA, on a page read in a chapel by people who are not
   all twenty. Everything else measures 5.6:1 or better.
   Now the design system's own label size and ink — 0.8125rem, the same as
   .row-label, at 5.9:1. Nothing moves: it is one small-caps line with room
   above and below. (.ann-when was the other example named here until Round
   325 took it out of the small-caps ranks altogether.) */
.gcard-join {
  display: block;
  text-transform: uppercase;
  color: var(--ink-soft);
  overflow-wrap: anywhere;
}

.gcard-tel {
  display: block;
  margin-top: 0.1rem;
  font-size: 1.05rem;
  color: var(--accent);
}

.gcard-tel a {
  position: relative;
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
}

/* A bigger thumb target without moving anything. Padding would push the
   underline away from the digits and change how the number looks, so the hit
   area is an invisible box laid over the link instead: ~24px tall becomes
   ~43px, which is a comfortable tap. It reaches further DOWN than up, into the
   card's own padding, so it does not swallow the label above — and where it
   does overlap, tapping "Text Marci Bell to join" texts Marci, which is what
   anyone aiming there wanted anyway. */
.gcard-tel a::after {
  content: '';
  position: absolute;
  left: -0.5rem;
  right: -0.5rem;
  top: -0.4rem;
  bottom: -0.8rem;
}

.gcard-tel a:hover {
  border-bottom-color: currentColor;
}

/* ------------------------------------------------ proposing a group */

.gstart {
  margin-top: 1.8rem;
  padding-top: 1.3rem;
  border-top: 1px solid var(--line);
}

.gstart-open {
  font: inherit;
  font-size: 1rem;
  font-weight: 650;
  padding: 0.6rem 1.2rem;
  border-radius: 999px;
  border: 1.5px solid var(--accent);
  background: none;
  color: var(--accent);
  cursor: pointer;
}

/* A PRESS, NOT A HIGHLIGHT (Round 329, Derek: "that button highlights, but I
   want it to not highlight, I would like it to have a clean animation where it
   depresses and as soon as you let off the button it takes you to that page").

   .gstart-open:hover FILLED THE PILL with the accent, and that is what he was
   seeing: a pointer that has just clicked is still over the button, so the
   fill reads as the press having stuck. On a touch screen it is worse -- :hover
   latches there until something else is tapped.

   So the fill is gone and the feedback is the press itself: 1px down and a hair
   smaller, which is what a physical button does, released the instant the
   pointer is. Nothing new was needed for "as soon as you let off ... it takes
   you to that page" -- a click fires on RELEASE, so that is already when the
   form opens and the first field takes the caret.

   :not(:disabled) so the submit button does not appear to accept a press while
   it is sending, and the keyboard ring is untouched: button:focus-visible near
   the top of this file still draws it, which is the one highlight that has to
   stay. */
.gstart-open,
.gbtn {
  transition: transform 120ms ease;
}

.gstart-open:active,
.gbtn:active:not(:disabled) {
  transform: translateY(1px) scale(0.98);
}

@media (prefers-reduced-motion: reduce) {
  .gstart-open,
  .gbtn {
    transition: none;
  }
}

/* On the Live page this same control is a link to the real page rather than a
   form opener, so it needs the two things an <a> does not get for free. */
.wardpage .gstart-open {
  display: inline-block;
  text-decoration: none;
}

.gform-lead {
  margin: 0 0 1rem;
  color: var(--ink-soft);
}

.gfield {
  display: block;
  margin-bottom: 0.85rem;
}

/* Raised with .gcard-join so the two agree — these label the form somebody
   fills in to start a group, and 9.9px is small for a field label. */
.gfield span {
  display: block;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 0.3rem;
}

.gform-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.85rem;
}

.gform-err {
  margin: 0 0 0.6rem;
  min-height: 1.2em;
  color: var(--danger);
  font-weight: 650;
}

.gform-err:empty {
  min-height: 0;
  margin: 0;
}

.gform-acts {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.9rem;
}

.gbtn {
  font: inherit;
  font-size: 1rem;
  font-weight: 650;
  padding: 0.6rem 1.3rem;
  border-radius: 999px;
  border: 1.5px solid var(--accent);
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
}

.gbtn:disabled {
  opacity: 0.55;
  cursor: default;
}

.gbtn-quiet {
  color: var(--ink-soft);
  text-decoration: underline;
}

.gbtn-quiet:hover {
  color: var(--ink);
}

/* The thank-you. Warm, and it says plainly what happens next. */
.gthanks {
  text-align: center;
  padding: 1.4rem 1rem 0.4rem;
}

.gthanks .fleuron {
  color: var(--accent);
  margin-bottom: 0.9rem;
}

/* The display face (Round 317): it was book-face bold, the other headline on
   this tab that was not Caslon. It stands in for the form it replaces, so it
   takes the same voice as "Find Your People" above it, a size down.

   IT NO LONGER SHARES THE CARD HEADLINE'S RANK, which is what this comment
   used to claim. Round 328 took .gcard-name and .ann-title a step above it
   at Derek's word: he named the two CARD TITLES, and a thank-you after a
   form is not one. Round 332 moved all three together (+2 points on every
   Caslon text on the page), so that gap is exactly preserved -- 1.5rem here
   against the cards' 1.65. Deliberate, not an oversight -- close it only if
   the two are meant to march together again. */
.gthanks-h {
  margin: 0;
  font-family: var(--display);
  font-size: 1.5rem;
  font-weight: 400;
  line-height: 1.25;
}

.gthanks-p {
  margin: 0.5rem 0 0;
  color: var(--ink-soft);
}

@media (max-width: 26rem) {
  /* .gcard-row went with the card's form layout; the proposal form still has
     its own two-up row that has to stack on a narrow phone. */
  .gform-row {
    grid-template-columns: 1fr;
  }
}

/* Reviewing a proposed group: the same card the ward will see, with the
   decision attached — so approving it is the same glance as reading it. */
.gcard.is-pending {
  border-top-color: var(--notify);
}

.gcard-acts {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--line);
}

.gcards-h {
  display: block;
  margin: 1.6rem 0 0.6rem;
}

body.leader .gcards {
  margin-top: 0.6rem;
}

/* These inputs carry no type attribute, so they miss the shared
   input[type='text'] sizing rule. */
.gfield input {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: 1.0625rem;
  padding: 0.65rem 0.75rem;
  border: 1.5px solid var(--line);
  border-radius: var(--r-item);
  background: var(--paper);
  color: var(--ink);
}

.gfield input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ================================================ text size (leader app) ===

   Five steps, set per person and kept on their own device. They are
   PERCENTAGES, never pixels: 100% means "whatever this person has already set
   as their browser's text size", so someone who has already turned their whole
   browser up keeps that and moves relative to it. A pixel value here would
   quietly overrule them, which is the opposite of the point.

   Everything in this stylesheet is sized in rem, so one root value carries the
   whole app — type, spacing and column widths together, in proportion. The
   printed conducting sheet is untouched: its sizes are in points.

   Breakpoints do not move. `rem` inside a media query is measured against the
   browser's initial font size, not this one, so sizing up never collapses the
   split-view preview or the desktop layout.

   SCREEN ONLY. The print blocks set the printed sheet's sizes in points, but
   any element they do not name falls back to its rem size — and rem is measured
   from the root, so without this guard a counselor reading at 125% would send a
   125% conducting sheet to the printer and lose its one-page fit. Printing
   always uses 100%.

   Each step also records its own scale as a number, which the workspace width
   at `body.leader .wrap` divides by so the column does not shrink with the
   text. Keep the two halves of each line below in step. */
@media screen {
  /* 90%, not 85%. The scale is sound at the top end, but at 85% the root is
     13.6px and the app's smallest labels landed under 10px — small enough to
     stop being readable rather than merely dense, which is not what the
     counselor who wants more on screen was asking for. 90% keeps a visible
     step down from 100% and lifts those labels back over the line. */
  :root[data-text='xs'] { font-size: 90%;    --text-scale: 0.9; }
  :root[data-text='s']  { font-size: 92.5%;  --text-scale: 0.925; }
  :root[data-text='m']  { font-size: 100%;   --text-scale: 1; }
  :root[data-text='l']  { font-size: 112.5%; --text-scale: 1.125; }
  :root[data-text='xl'] { font-size: 125%;   --text-scale: 1.25; }
}

/* (The workspace width that divides by this lives with the other leader-app
   layout rules, at `body.leader .wrap`.) */


/* ====================================================== the account menu

   Four unrelated things used to sit loose in the masthead: a setting, a
   place, a name and an action, all styled alike, with the only one that was
   NOT clickable in the middle. They are all rare — the text size is set once,
   Admin a few times a year, Sign out almost never, since sessions do not
   lapse — so they hang off the name instead. One item at rest, and the
   corner survives a phone. */

.account {
  position: relative;
  display: inline-block;
}

.acctbtn {
  font: inherit;
  font-size: 0.9375rem;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.3rem 0.7rem 0.3rem 0.35rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background: none;
  color: var(--ink);
  cursor: pointer;
  line-height: 1;
}

.acctbtn:hover,
.acctbtn[aria-expanded='true'] {
  border-color: var(--line);
  background: var(--card);
}

/* The disc carries the accent, so the cluster reads as one object from across
   the room without the name having to shout. */
/* Outlined, not filled. A solid accent disc in the corner is the strongest
   mark on the page, and it is only saying who is signed in — which the name
   beside it already says. A ring carries the same identity at a fraction of
   the weight. */
.acctinit {
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  background: none;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  transition: border-color 0.15s ease, color 0.15s ease;
}

/* It firms up when you reach for the menu it belongs to. */
.acctbtn:hover .acctinit,
.acctbtn[aria-expanded='true'] .acctinit {
  border-color: var(--accent);
  color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .acctinit { transition: none; }
}

.acctcaret {
  width: 0;
  height: 0;
  border-left: 0.26rem solid transparent;
  border-right: 0.26rem solid transparent;
  border-top: 0.28rem solid var(--ink-soft);
  transition: transform 0.12s ease;
}

.acctbtn[aria-expanded='true'] .acctcaret {
  transform: rotate(180deg);
}

/* Below the toast (100) and the overlays (50, 60) on purpose: a menu must not
   cover the thing that tells you what just happened. */
/* THE TWO POPOVER MENUS ARE ONE OBJECT. .acctmenu (off the account name) and
   .rowmenu (off a tracker row) share the border, the shadow, the z-index and
   the item hover, and differed in every measurement and in none of them
   meaningfully: radius 10 against 9, padding 0.35rem against 0.3rem, item
   type 0.9375rem against 0.9rem, item padding 0.45/0.55 against 0.4/0.5.
   Nobody chose a 1px radius difference between two menus; they were written a
   fortnight apart and each rounded its own numbers. One set now, shared —
   they compose from a base the way every card in this app composes from
   .acard. Each keeps only what is genuinely its own: how it is positioned,
   and how wide it wants to be. */
.acctmenu,
.rowmenu {
  z-index: 45;
  padding: 0.35rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: 0 8px 24px rgba(20, 16, 10, 0.18);
  text-align: left;
}

.acctmenu {
  position: absolute;
  top: calc(100% + 0.35rem);
  right: 0;
  min-width: 12.5rem;
}

.acctsize {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.9rem;
  padding: 0.4rem 0.55rem;
}

.acctsize-label {
  font-size: 0.875rem;
  color: var(--ink-soft);
  /* The row is wider with the slider in it; the label stays on one line and
     the menu (absolute, sized to its content) widens to fit. */
  white-space: nowrap;
}

/* THE TEXT-SIZE SLIDER (Round 166, Derek): a 1px line between A− and A+
   with a thin upright handle, and a mark at the middle for the default
   size, so the person can see how far either way they are. The native
   range input is restyled per engine (WebKit/Blink and Gecko each want their
   own pseudo-elements); the middle mark is the wrapper's ::before, since a
   range input cannot draw one. Sized in rem so the handle stays a comfortable
   target at every step, and the row keeps its height as the text grows. */
.textsize {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.sizetrack {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 6rem;
  height: 1.4rem;
}

.sizetrack::before {
  content: "";
  position: absolute;
  left: calc(50% - 0.5px);
  top: 50%;
  width: 1px;
  height: 0.55rem;
  transform: translateY(-50%);
  background: color-mix(in srgb, var(--ink-soft) 55%, transparent);
  pointer-events: none;
}

.sizeslider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 1.4rem;
  margin: 0;
  padding: 0;
  background: none;
  cursor: pointer;
}

.sizeslider:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

.sizeslider::-webkit-slider-runnable-track {
  height: 1px;
  background: color-mix(in srgb, var(--ink-soft) 45%, transparent);
}

.sizeslider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 2px;
  height: 0.9rem;
  margin-top: calc(-0.45rem + 0.5px);
  border: none;
  border-radius: 1px;
  background: var(--accent);
}

.sizeslider::-moz-range-track {
  height: 1px;
  background: color-mix(in srgb, var(--ink-soft) 45%, transparent);
}

.sizeslider::-moz-range-thumb {
  width: 2px;
  height: 0.9rem;
  border: none;
  border-radius: 1px;
  background: var(--accent);
}

.acctsep {
  height: 1px;
  background: var(--line);
  margin: 0.3rem 0.55rem;
}

/* A note, not a control — the only thing in this menu you cannot press. It
   says why there is no name above, directly over the Admin row that fixes
   it. Kept in the menu because the app now reopens on your last tab, so the
   Welcome page carrying the longer version of this can go unseen. */
.acctnote {
  margin: 0.15rem 0.55rem 0.35rem;
  max-width: 14rem;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

/* One menu row, for both menus. */
.acctitem,
.rowmenu-item {
  font: inherit;
  font-size: 0.9375rem;
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.45rem 0.55rem;
  border: none;
  border-radius: var(--r-item);
  background: none;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}

.acctitem:hover,
.acctitem:focus-visible {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--accent);
}

.acctitem.active {
  color: var(--accent);
  font-weight: 650;
}

/* Sign out is the one thing here you cannot undo with another click, so it is
   set quieter than its neighbour rather than beside it as a peer. */
.acctitem-out {
  color: var(--ink-soft);
}

.textsize {
  display: inline-flex;
  align-items: baseline;
  gap: 0.6rem;
}

/* Not underlined like the other utility buttons — these are a control, not a
   place to go, and two underlined "A"s beside the account read as links. */
.sizebtn {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  text-decoration: none;
  line-height: 1;
}

.sizebtn.big {
  font-size: 1.0625rem;
}

.sizebtn:hover {
  color: var(--accent);
  text-decoration: none;
}

/* At either end there is genuinely nowhere further to go, so the button says
   so. (Unlike a tab with an empty queue, which still works and must not be
   dimmed — see the tab bar notes.) */
.sizebtn:disabled {
  opacity: 0.3;
  cursor: default;
}

.sizebtn:disabled:hover {
  color: var(--ink-soft);
}

/* A to_sustain item on the board or table states when it is due; confirming
   that it happened belongs to the Bishopric tab alone. */
.bizrow-kind {
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}

/* ============================ proposals, and the decisions that come back */

/* A decision addressed to the person reading. Green for yes, terracotta for
   no — the only place in the app that speaks to you personally. */
.notices {
  margin-bottom: 1.1rem;
}

.notice {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  padding: 0.8rem 1rem;
  border-radius: var(--r-card);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line);
  background: var(--card);
  margin-bottom: 0.5rem;
}

.notice.is-yes {
  border-left-color: var(--success-line, var(--accent));
  background: color-mix(in srgb, var(--accent) 5%, var(--card));
}

.notice.is-no {
  border-left-color: var(--notify);
  background: color-mix(in srgb, var(--notify) 5%, var(--card));
}

.notice-h {
  margin: 0;
}

.notice-note {
  margin: 0.35rem 0 0;
  color: var(--ink-soft);
  white-space: pre-wrap;
}

.notice-when {
  margin: 0.3rem 0 0;
  font-size: 0.78rem;
  color: var(--ink-soft);
}

/* The queue itself. */
.propcard {
  padding: 1rem 1.15rem 0.6rem;
  border-left: 4px solid var(--notify);
}

.prophead {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.prophead .field-label {
  margin: 0;
}

.proprow {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.75rem 0;
  border-top: 1px solid var(--line);
}

.prop-name {
  margin: 0;
  font-weight: 650;
  font-size: 1.0625rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.pill-release,
.pill-calling {
  text-transform: uppercase;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
}

.pill-release {
  background: color-mix(in srgb, var(--notify) 16%, transparent);
  color: var(--notify);
}

/* BOTH kinds are labelled. A chip only on releases meant a proposal for a
   calling carried no mark at all, which read as "nobody said which this is"
   rather than "this is a calling". The calling chip is the quieter of the two,
   because it is the ordinary case — but it is present, and that is the point. */
.pill-calling {
  background: color-mix(in srgb, var(--ink-soft) 12%, transparent);
  color: var(--ink-soft);
}

/* Edit leads the row. A pill here would be a third button competing with the
   two that decide the thing, so it is a bare mark instead — quiet until you
   reach for it, and always in the same place, which the old middle position
   never was. */
.prop-edit {
  flex: none;
  font-size: 0.95rem;
  line-height: 1;
  padding: 0.15rem 0.2rem;
  margin-top: 0.1rem;
  color: var(--ink-soft);
  opacity: 0.55;
  transition: opacity 0.12s ease, color 0.12s ease;
}

.prop-edit:hover,
.prop-edit:focus-visible {
  opacity: 1;
  color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .prop-edit { transition: none; }
}

/* The text takes the slack so the decisions stay pinned right and the mark
   stays pinned left, whatever length the name is. */
.prop-main {
  flex: 1;
  min-width: 0;
}

.prop-sub {
  margin: 0.1rem 0 0;
  color: var(--ink-soft);
}

.prop-note {
  margin: 0.3rem 0 0;
  color: var(--ink-soft);
  font-style: italic;
}

/* Who proposed it and when — the thing Derek wanted visible at a glance. */
.prop-meta {
  margin: 0.3rem 0 0;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.prop-acts {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

/* ---- the conducting shelf: navigate above the rule, act below it ---- */

/* The date and what it means, on one line with the actions that apply to it.
   Previously this was a second card repeating the date already shown on the
   chip above, with the ★ said twice. */
.sheetnow {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.7rem 1.5rem;
  flex-wrap: wrap;
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--line);
}

.sheetnow-main {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
  flex-wrap: wrap;
}

/* The date of the sheet you have open. It governs everything below it, so it
   outranks the numbered section headings ("1 Meeting", 1.3rem on a computer) —
   at 1.15rem it was SMALLER than every heading it presides over, which is
   backwards. There is only one emitter and it is always inside .sheetpicker,
   so an unscoped .sheet-date rule stating different numbers was deleted
   rather than left to look like a deliberate difference. */
.sheetpicker .sheet-date {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 650;
  line-height: 1.2;
}

.nowtag {
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* A complete sheet: the date in the success green with the Ready chip before
   it (Round 168), on the date's own row rather than one of its own. */
.sheetpicker .sheet-date.is-ready {
  color: var(--success-ink);
}

.sheetready {
  align-self: center;
}

.nowtag.is-live {
  color: var(--accent);
}

.sheetpicker .sheet-actions {
  display: flex;
  align-items: center;
  /* Closer together and without the ↳ glyph (Round 185, Derek): three
     plain words for this Sunday, not three branches off a heading. */
  gap: 0.6rem;
  flex-wrap: wrap;
}

.sheetpicker .sheet-actions .quiet-inline::before {
  content: none;
}


/* The program, as the music tier sees it: their items editable, everyone
   else's shown in place but plainly not theirs. */
.musicprog .standread-item.is-locked {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

.lockrow {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 0;
  flex-wrap: wrap;
  color: var(--ink-soft);
}

.lockrow-what {
  font-weight: 650;
}

.lockrow-tag {
  margin-left: auto;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}

/* Submitted: a stamped record, not a field. */
.ptable .pwhen {
  white-space: nowrap;
  color: var(--ink-soft);
  font-size: 0.9rem;
}

/* ====================================================== note threads

   The Notes column is one line of a ten-column table and can never be wide
   enough to read a note in. The cell shows the NEWEST note as a reference and
   opens the thread; it is a button styled to sit as quietly as the borderless
   inputs beside it, so the row still reads as a row. */

/* A FIXED width, not 100%. In an auto-layout table a percentage width is
   circular, so the cell still sizes to the full text and the column swells to
   fit a note it was never meant to show — 41rem of it, squeezing everything
   else. A definite width caps what the column can ask for. */
.notecell {
  font: inherit;
  display: block;
  width: 12rem;
  text-align: left;
  padding: 0.3rem 0.35rem;
  border: 1px solid transparent;
  border-radius: var(--r-item);
  background: none;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.notecell:hover,
.notecell:focus-visible {
  border-color: var(--line);
  background: var(--paper);
}

.notecell.is-empty {
  color: var(--ink-soft);
}

.notescard {
  width: min(38rem, 92vw);
  position: relative; /* the flag sits in its corner */
}

.noteswho {
  margin: -0.35rem 0 0.9rem;
}

.notelist {
  max-height: min(26rem, 50vh);
  overflow-y: auto;
}

.noteitem {
  padding: 0.7rem 0;
  border-top: 1px solid var(--line);
}

.noteitem:first-child {
  border-top: none;
  padding-top: 0;
}

.notebody {
  margin: 0;
  white-space: pre-wrap;
}

/* Who and when, under the words rather than beside them — the note is what you
   came to read; its provenance is the footnote. */
.notemeta {
  margin: 0.2rem 0 0;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.notemeta-who {
  font-weight: 650;
}

.noterow-actions {
  display: flex;
  gap: 0.9rem;
  margin-top: 0.35rem;
}

.noteadd {
  border-top: 1px solid var(--line);
  padding-top: 0.8rem;
  margin-top: 0.4rem;
}

.noteedit {
  width: 100%;
  margin-bottom: 0.5rem;
}

/* Not .quiet-inline: that carries a leading ↳, which reads as an action
   branching off a heading. These sit under a note as plain small actions. */
.noteact {
  font-size: 0.8125rem;
  color: var(--ink-soft);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--ink-soft) 32%, transparent);
}

.noteact:hover {
  color: var(--accent);
  text-decoration-color: currentColor;
}

.noteact.danger:hover {
  color: var(--danger);
}

/* ================================================== the activity log ======

   Two questions on one page. The roll-up at the top answers "is anyone using
   this"; the feed below answers "who did that". Bishopric only. */

.adminlinks {
  margin: -0.4rem 0 1.2rem;
}

.actroll {
  padding: 1rem 1.15rem 0.6rem;
  margin-bottom: 1.4rem;
}

.actroll h3 {
  margin: 0 0 0.7rem;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Each person is also a filter — clicking narrows the feed to them, which is
   the natural next move after noticing a name. */
.actperson {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  width: 100%;
  font: inherit;
  text-align: left;
  padding: 0.42rem 0.4rem;
  background: none;
  border: none;
  border-bottom: 1px solid var(--line);
  border-radius: var(--r-item);
  cursor: pointer;
}

.actperson:last-child {
  border-bottom: none;
}

.actperson:hover {
  background: color-mix(in srgb, var(--ink-soft) 6%, transparent);
}

.actperson.on {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.actperson-n {
  font-weight: 650;
  min-width: 11rem;
}

.actperson-c {
  color: var(--accent);
  font-size: 0.9rem;
}

.actperson-w {
  margin-left: auto;
  color: var(--ink-soft);
  font-size: 0.85rem;
  white-space: nowrap;
}

/* The feed. Ruled rows, the same idiom as the announcements list. */
.actlist {
  list-style: none;
  margin: 0.8rem 0 1.5rem;
  padding: 0;
  border-top: 1px solid var(--line);
}

.actrow {
  display: grid;
  grid-template-columns: 9.5rem minmax(0, 1fr) auto;
  gap: 0 1rem;
  align-items: baseline;
  padding: 0.5rem 0.4rem;
  border-bottom: 1px solid var(--line);
}

.actrow-w {
  color: var(--ink-soft);
  font-size: 0.85rem;
  white-space: nowrap;
}

.actrow-p {
  font-weight: 650;
}

.actrow-r {
  margin-left: 0.5rem;
  color: var(--ink-soft);
  font-size: 0.85rem;
}

.actrow-a {
  color: var(--ink-soft);
  font-size: 0.78rem;
  white-space: nowrap;
}

/* On a phone the time and the area move under the sentence rather than
   squeezing it into a column an inch wide. */
@media (max-width: 48rem) {
  .actrow {
    grid-template-columns: 1fr;
    gap: 0.15rem;
  }

  .actrow-a {
    justify-self: start;
  }

  .actperson {
    flex-wrap: wrap;
  }

  .actperson-n {
    min-width: 0;
  }
}

/* Submitted: who filed it over when they filed it. Stacking costs no width —
   the column is as wide as the longer of the two lines, not their sum — and
   puts the name, which is what you scan for, on top. */
.ptable .psubmit {
  line-height: 1.25;
}

.psubmit-who {
  display: block;
}

.psubmit-when {
  display: block;
  font-size: 0.8125rem;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------- notes: who said it, and when */

.notelist {
  margin: 0.4rem 0 0.7rem;
}

.note {
  padding: 0.55rem 0 0.6rem;
  border-top: 1px solid var(--line);
}

.note:first-child {
  border-top: none;
  padding-top: 0.2rem;
}

.note-body {
  margin: 0;
  white-space: pre-wrap;
}

.note-stamp {
  margin: 0.25rem 0 0;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.noteadd {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
}

.noteadd textarea {
  flex: 1;
}

/* What the proposer said, readable in the queue before you open anything. */
.prop-said {
  margin-top: 0.5rem;
  padding-left: 0.7rem;
  border-left: 2px solid var(--line);
}

.prop-said .note {
  padding: 0.3rem 0;
}

/* ====================================================== the row menu

   A "⋯" at the left of a tracker row. The slot is ALWAYS occupied, so nothing
   shifts sideways when a row is hovered — only the opacity changes. It sits
   inside the status cell rather than in a column of its own, because a tenth
   column would shift every positional width rule again. */

/* THE CELL STAYS A TABLE CELL. It used to carry the flex itself, and a td set
   to display:flex is no longer a table cell — it stops stretching to the
   height of its row. In a 48px row the box was 33px and sat at the top, so
   every status pill floated about 8px above the name beside it. The flex moved
   to a div inside, and the cell keeps the middle alignment every other cell
   has. */
.pstatusrow {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

/* ~17x17px, and it is the ONLY route to Delete on a tracker row. The visible
   glyph stays exactly where it is — widening the control would shift the
   status pill beside it, and the whole point of the always-occupied slot is
   that nothing moves when a row is hovered. The hit area grows instead, the
   same trick the group card's number and the calendar link use. */
.rowdots {
  font: inherit;
  flex: none;
  width: 1.1rem;
  line-height: 1;
  padding: 0.1rem 0;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  position: relative;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.ptable tbody tr:hover .rowdots,
.rowdots[aria-expanded='true'] {
  opacity: 0.55;
}

/* focus-visible belongs in the FULL-opacity group, not the 0.55 one. Opacity
   applies to an element's outline as well as its ink, so the 3px accent focus
   ring was being rendered at 55% — 2.14:1 against the card — on the only
   control a keyboard user can reach Delete through. */
.rowdots:hover,
.rowdots:focus-visible,
.rowdots[aria-expanded='true'] {
  opacity: 1;
  color: var(--accent);
}

/* Touch has no hover, so there the dots are simply always visible. */
@media (hover: none) {
  .rowdots { opacity: 0.55; }
}

/* Fixed to the viewport, not to the row: .ptable-wrap scrolls on x, and a
   container that scrolls on one axis clips both. Below .toast (100) and the
   overlays (50, 60) — a menu must never cover what it opens. */
/* Shares the panel above. position: fixed because .ptable-wrap scrolls on
   one axis and therefore clips both — see Round 16. */
.rowmenu {
  position: fixed;
  min-width: 11rem;
}

.rowmenu-item:hover,
.rowmenu-item:focus-visible {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}

.rowmenu-item.danger:hover,
.rowmenu-item.danger:focus-visible {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  color: var(--danger);
}

/* ============================== the tracker's columns line up ==============

   Every heading sat a few pixels left of the words underneath it, by a
   different amount in each column — Candidate name and Calling were out by 8px,
   Next step by 17.

   The cause: a heading's text always started 10px in (`.psort` supplied its own
   padding and the `<th>` had none), while a cell's text started at the td's 7px
   PLUS whatever padding that particular control carried — 0 for bare text, 6
   for a select, 10 for an input, 9 for a button. Four different gutters under
   one fixed heading.

   So the table now has ONE gutter, declared here. Change `--cell-inset` and
   the headings follow, because they are defined from it.                    */
.ptable {
  --cell-pad: 0.45rem;   /* the td's own padding, matching .ptable td */
  --cell-inset: 0.25rem; /* how far a control's text sits inside its cell */
}

/* The heading's text starts exactly where the cell's text does. The padding
   moves to the <th> so that headings WITHOUT a sort button — "Next step" — are
   inset too; that one had no padding at all and was the worst offender. */
.ptable:not(.mtable) th {
  padding-left: calc(var(--cell-pad) + var(--cell-inset));
}

.ptable:not(.mtable) .psort {
  padding-left: 0;
}

/* Controls keep a little breathing room so a hover border never touches the
   glyphs, but they all keep the SAME amount. The status pill is deliberately
   excluded: it is a coloured pill whose own shape sets its padding, and its
   column lines up on the ⋯ that precedes it. */
.ptable:not(.mtable) td > .pcell,
.ptable:not(.mtable) td > select:not(.pstatus-sel),
.ptable:not(.mtable) td > .notecell {
  padding-left: var(--cell-inset);
}

/* A cell holding plain text has no control to inset, so the text takes the
   inset itself. The Submitted column stacks two spans directly in the cell —
   there is no wrapper to hang this on, so both are named. */
.ptable:not(.mtable) td > .wait,
.ptable:not(.mtable) td > .psubmit-who,
.ptable:not(.mtable) td > .psubmit-when {
  padding-left: var(--cell-inset);
}

/* The last column holds a pill button, and the eye lines a pill up by its EDGE,
   not by the letters inside it — so this cell insets the button's box to the
   heading rather than its text. */
.ptable:not(.mtable) td.pactions {
  padding-left: calc(var(--cell-pad) + var(--cell-inset));
}

/* ====================================================== period type + engraving

   Libre Caslon Display, SIL Open Font License (see caslon-OFL.txt beside it).
   SELF-HOSTED on purpose: the site's CSP is `default-src 'self'`, so a font
   from Google would be blocked — and that is the rule working, since this
   project promises no third-party requests. 24KB, one file, one weight.

   It is used ONLY on the two mastheads — the Leader Tools letterhead and,
   since Round 134, the ward's own name at the top of the public page. Caslon
   is the type American printers were setting when this ward was founded; a
   whole page of it would read as costume rather than letterhead. */

@font-face {
  font-family: 'Ward Caslon';
  src: url('/caslon.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  /* Show the fallback immediately and swap when it lands: the masthead must
     never be invisible while a font loads. */
  font-display: swap;
}

.brand-ward,
.brand-name {
  font-family: 'Ward Caslon', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif;
}

/* Caslon's capitals are lighter than Palatino's at the same weight, so the
   ward line needs a touch more letterspacing to hold the line, and the title
   drops from 700 to 400 — a display face set bold loses the modelling that
   makes it worth having. */
.brand-ward {
  font-weight: 400;
  letter-spacing: 0.22em;
}

.brand-name {
  font-weight: 400;
  letter-spacing: 0.01em;
}

/* The meetinghouse as a letterhead engraving rather than a photograph:
   desaturated, lifted, and tinted to the ink and cream the rest of the page is
   printed in, so it sits ON the paper instead of on top of it. */
.engraving {
  width: min(17rem, 62%);
  margin: 0 auto 1.75rem;
  line-height: 0;
}

.engraving img {
  width: 100%;
  height: auto;
  border-radius: 2px;
  /* Brightness ahead of contrast: the sky burns out to the colour of the
     paper so the plate has no sky, only building — which is what an engraved
     letterhead looks like. WARM SEPIA (Round 163, Derek's choice from three
     treatments): the old-photograph brown, no hue-rotate toward the sage
     any more -- it says "old", which is what THE HISTORIC says beside it,
     and stays ink on paper where real colour would make it a photograph. */
  filter: grayscale(1) brightness(1.38) contrast(1.42) sepia(0.62) saturate(0.8);
  opacity: 0.9;
  /* Fades into the paper rather than ending at a hard edge, the way a vignette
     on old stationery does. */
  -webkit-mask-image: radial-gradient(ellipse 56% 60% at 50% 47%, #000 34%, rgba(0, 0, 0, 0.55) 68%, transparent 92%);
  mask-image: radial-gradient(ellipse 56% 60% at 50% 47%, #000 34%, rgba(0, 0, 0, 0.55) 68%, transparent 92%);
}

/* On a dark screen the plate is inverted so it reads as light ink on dark
   paper, and pulled back further — a bright photograph in a dark room is the
   one thing that would break the calm. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .engraving img {
    /* NOT inverted. Inverting turns the burnt-out sky near-black, which
       paints a dark blob on dark paper. `screen` instead drops the dark
       values away into the page and keeps only the light ones, so the plate
       reads as pale ink rather than a negative. */
    filter: grayscale(1) brightness(1.12) contrast(1.42) sepia(0.6) saturate(0.7);
    opacity: 0.66;
    mix-blend-mode: screen;
  }
}

/* The same, when dark is forced (Round 182). Keep identical to the block above. */
:root[data-theme='dark'] .engraving img,
.wp-paper.is-dark .engraving img {
  /* NOT inverted. Inverting turns the burnt-out sky near-black, which
     paints a dark blob on dark paper. `screen` instead drops the dark
     values away into the page and keeps only the light ones, so the plate
     reads as pale ink rather than a negative. */
  filter: grayscale(1) brightness(1.12) contrast(1.42) sepia(0.6) saturate(0.7);
  opacity: 0.66;
  mix-blend-mode: screen;
}

@media (max-width: 40rem) {
  .engraving {
    width: min(13.5rem, 66%);
    margin-bottom: 1.35rem;
  }
}

/* THE WARD'S PAGE WEARS THE SAME LETTERHEAD (Round 134, Derek): the name in
   Caslon, the meetinghouse engraved above it. Both scoped to .masthead —
   the public page and its replica on the What-the-ward-sees tab — so the
   printed conducting sheet's and the meeting page's .ward keep their own
   type. The name only: the date, the meeting line and everything beneath
   stay in the book face, or the page reads as costume (Round 20's rule).
   Set at 400 like the Leader Tools title (a display face set bold loses its
   modelling) and a touch larger than the 1.9rem it replaces, because
   Caslon's colour is lighter than Palatino's at 650. 2.25rem since Round
   332, when the whole Caslon tier on this page went up two points.
   What it costs the page people open on a phone in the chapel: the 24KB
   font, once (Round 23 declined that for the group names; Derek asked for
   it here), and the 30KB engraving, once — both same-origin, both cached. */
.masthead .ward {
  font-family: 'Ward Caslon', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif;
  font-weight: 400;
  font-size: 2.25rem;
  letter-spacing: 0.01em;
}

/* "THE HISTORIC" over the name (Round 163, Derek): the Leader Tools
   letterhead's own small-caps line (.brand-ward -- Caslon at 400, uppercase,
   0.22em tracking, soft ink) at the same scale to the name it sits over:
   0.82rem to 2rem there, so 0.86rem to the page's 2.1rem here, with the
   same 0.22rem beneath. Emitted by worker.js and mirrored in app.js's
   publicMasthead; the error page's masthead deliberately does not carry it.

   1rem SINCE ROUND 332, and the ratio above is a derivation rather than a
   law: Derek asked for every Caslon text on the page two points larger, and
   a uniform ADDITIVE step necessarily moves every ratio on the page. These
   two are 1 to the name's 2.25 now instead of 0.86 to 2.1.

   AND THE RULE THAT WINS IS THE OTHER ONE (Round 334): this size is
   restated as calc(1rem * var(--ds)) at the foot of the file, which
   outranks this on every screen. Change both, or nothing moves.

   Round 332 had left them OUT of that block, so they kept their full two
   points on a phone while the name beside them gave 12% back -- which put
   the lockup at 0.505 of the name on a phone against 0.444 on a computer.
   Derek looked at the two side by side and chose the proportion: they are
   16px on a computer and 14.08px on a phone now, so the masthead reads the
   same on both. The four History headings are still out of the block; that
   is a separate decision and still his to make. */
/* THE NAME SITS BETWEEN ITS TWO EPIGRAPHS: "THE HISTORIC" above (Round 163,
   Derek's line) and "EST. 1852" below (Round 231, his founding year -- the ward
   was founded in August 1852 and he asked for the year alone, which is the
   letterhead convention anyway). ONE RULE SERVES BOTH so they cannot drift
   apart; they differ only in which side their margin falls on. The tracking is
   0.22em because Caslon's capitals are light -- the same reason .brand-ward
   carries it in the leader app. */
.masthead .ward-over,
.masthead .ward-est {
  font-family: 'Ward Caslon', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif;
  font-weight: 400;
  font-size: 1rem;
  line-height: 1.15;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--ink-soft);
  margin: 0;
}

.masthead .ward-over {
  margin-bottom: 0.22rem;
}

/* The name's own bottom margin, brought down to match the epigraph above it, so
   the pair is symmetric. Scoped: .ward is also the printed sheet's heading. */
.masthead .ward {
  margin-bottom: 0.22rem;
}

/* THE DATE SITS CENTRED BETWEEN THE FOUNDING LINE AND THE ORNAMENT (Round
   323, Derek: "move down so that it is centered in between the established in
   1852 and the decorative line below it").

   MEASURED, NOT GUESSED, AND FROM THE INK RATHER THAN THE BOXES. A line box
   carries half its leading above and below the letters, and the fleuron's own
   box holds 14px of air above the rule it draws -- so the boxes were nearly
   even while the INK was 7.2px below EST. 1852 and 34.3px above the ornament.
   Taken to 20.8 and 20.7.

   The two numbers move in opposite directions by the same 13.6px (0.85rem),
   so the whole masthead keeps its height and nothing below the ornament
   shifts: this margin 0.4 -> 1.25rem, and .masthead .fleuron's 1.1 -> 0.25rem.
   CHANGE THEM TOGETHER or the date moves and the page moves with it. */
.masthead .ward-est {
  margin-bottom: 1.25rem;
}

/* The other half of that pair. SCOPED to the masthead: .fleuron is also the
   ornament under a no-meeting notice, on the Welcome tab and in the group
   thank-you, none of which were asked about. */
.masthead .fleuron {
  margin-top: 0.25rem;
}

/* Smaller than the Welcome tab's plate, and closer to the name: this page is
   opened for the program, so the engraving is a letterhead's small cut, not a
   picture the program has to wait behind. The width and height attributes on
   the <img> hold its space before it loads, so the name never jumps. */
.masthead .engraving {
  width: min(11.5rem, 52%);
  margin: 0 auto 0.9rem;
}

/* .groupscard WAS HERE -- the dialog the approved groups used to live in,
   off the Bishopric tab. Round 244 gave them their own tab and this went with
   the dialog.

   .glist stays, because the tab reuses these rows. It has LOST the dialog's
   max-height and overflow-y: those made sense inside a box with its own
   scroll, and on a page they would be a nested scroller -- the scroll trap
   Round 52 kept off phones. The list simply flows now. */
.glist {
  margin-top: 0.4rem;
}

.gitem {
  padding: 0.9rem 0;
  border-top: 1px solid var(--line);
}

.gitem:first-child {
  border-top: none;
  padding-top: 0.2rem;
}

.gitem-acts {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

/* Inside the dialog the card is the thing being edited, not a tile in a grid,
   so it drops the lift and sits flat against the paper. */
.gitem .gcard {
  box-shadow: none;
}

/* The editor's own row of attached flyers. Same shape as the ward sees, with a
   remove button on each — so what you are editing looks like what you shipped. */
.photostrip {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  flex-wrap: nowrap;
  padding-bottom: 0.35rem;
  margin-bottom: 0.55rem;
}

.photoitem {
  position: relative;
  flex: 0 0 auto;
  line-height: 0;
}

.photoitem .photo-thumb {
  display: block;
  width: 6rem;
  height: 6rem;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  margin: 0;
}

/* Sits ON the picture's corner: at 6rem square there is no room beside it, and
   a row of buttons underneath would double the strip's height. */
.photo-x {
  position: absolute;
  top: -0.4rem;
  right: -0.4rem;
  min-width: 1.6rem;
  min-height: 1.6rem;
  padding: 0;
  font-size: 0.75rem;
  background: var(--card);
  border-radius: 50%;
}

/* The compact list on the Announcements tab: small squares in a row rather than
   one full-width flyer under three lines of text. */
.annview-shots {
  display: flex;
  gap: 0.4rem;
  overflow-x: auto;
  flex-wrap: nowrap;
  margin-top: 0.45rem;
}

.annview-shots .annview-photo {
  flex: 0 0 auto;
  width: 4rem;
  height: 4rem;
  object-fit: cover;
  margin: 0;
}

/* "Remove" sits under each row's next step, quiet until you go for it. It is
   deliberately NOT a pill: the pill in this cell is the step you are meant to
   take, and a second one beside it would give a destructive action the same
   weight as the ordinary one. */
.premove-row {
  margin-top: 0.3rem;
}

.premove {
  font-style: normal;
  font-size: 0.78rem;
  color: var(--ink-soft);
}

.premove::before {
  content: none;
}

.premove:hover,
.premove:focus-visible {
  color: var(--danger);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* Held items flagged for the next bishopric meeting, above the tracker with the
   proposals — both are "somebody is waiting on this", not work in progress. */
.discusscard {
  border-left: 4px solid var(--notify);
}

.discussrow {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  padding: 0.7rem 0;
  border-top: 1px solid var(--line);
}

.discussrow:first-of-type {
  border-top: none;
}

/* The reason someone gave, quoted back rather than hidden in the Notes cell. */
.discussrow .prop-said {
  margin: 0.3rem 0 0;
  font-size: 0.875rem;
  color: var(--ink-soft);
}

.holdwhy {
  width: 100%;
}

/* The declined row stripe was here, and went with the other seven — see the
   note beside them. Declined still reads warm rather than alarming through its
   pill and its group dot: somebody saying no is an ordinary answer. */

/* ===================== on the conducting sheet ===========================

   A calling that has been pushed to the sheet is about to be read over the
   pulpit. Two things say so: the whole row is washed, and the status control
   is replaced by a button that says where it is.

   The wash uses the SUCCESS tokens, not the sage accent, and deliberately:
   the accent at 5–6% is already the table's HOVER tint, so an accent wash
   would have made every hovered row look as though it were on the sheet. */
.ptable tbody tr.is-onsheet td {
  background: color-mix(in srgb, var(--success-bg) 55%, transparent);
}

.ptable tbody tr.is-onsheet:hover td {
  background: color-mix(in srgb, var(--success-bg) 80%, transparent);
}

/* The green push button. It sits under the status pill rather than beside it:
   the status cell is 11rem and a second control on the same line would have
   pushed every other column along. Only the rows that can actually be pushed
   draw one, so no row grows a line for a button it does not have. */
/* Sized to its words, not to the column, and inline — so it sits beside the
   status pill instead of claiming a second line and doubling the row height.
   `.btn` (outlined) rather than `.btn-primary` (filled): see the note at the
   call site. */
/* Sized to its words rather than to the column, and only as tall as its own
   label — `.btn.slim` floors at 2.25rem, which is right for a toolbar and far
   too tall for a control repeated down nineteen table rows.
   `.btn` (outlined) rather than `.btn-primary` (filled): see the call site. */
/* `.btn.slim.pushsheet`, not `.pushsheet`: .btn.slim floors the height at
   2.25rem at (0,2,0) and would otherwise win. That floor is right for a
   toolbar button and far too tall for a control repeated down a table. */
.btn.slim.pushsheet {
  display: inline-block;
  margin-top: 0.3rem;
  padding: 0.2rem 0.55rem;
  font-size: 0.78rem;
  min-height: 1.7rem;
  white-space: nowrap;
  border-width: 1px;
}

/* Its opposite: already on the sheet. Outlined in the success ink rather than
   filled, because this is a STATE being reported, not an action being offered
   — the click takes it back off, which the hover label says. */
.onsheet-btn {
  font: inherit;
  font-size: 0.78rem;
  font-weight: 650;
  padding: 0.28rem 0.6rem;
  border-radius: 999px;
  cursor: pointer;
  color: var(--success-ink);
  background: var(--success-bg);
  border: 1px solid var(--success-line);
  white-space: nowrap;
}

.onsheet-btn:hover,
.onsheet-btn:focus-visible {
  background: color-mix(in srgb, var(--success-line) 45%, var(--success-bg));
}

.onsheet-when {
  margin-top: 0.25rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.onsheet-when.late {
  color: var(--warn-ink, #9a5b2b);
  font-weight: 650;
}

/* "Show preview" on its own row, in the gap between the sheet picker and the
   first card — not among Move / Reset / Print, which act on the sheet, while
   this changes how the editor is laid out. Matches the Announcements tab.
   Sized like the form below it so the button sits on the same right edge; the
   card above is border-box for the same reason (see .conducting-col.narrow). */
/* THE PREVIEW BAR IS STICKY, because it carries Print (Round 53): whatever
   part of a long sheet is being edited, the button that prints it sits at
   the top of the window, flush under the tab bar (--tabbar-h, measured in
   app.js; the preview pane sticks to the same line). Paper behind it, or the cards would show through
   as they slide under; z-index 5 puts it above the cards and below every
   menu and dialog. .is-stuck is set by an observer in conductingView -- CSS
   has no signal of its own for "stopped" -- and draws the hairline that
   separates the bar from whatever is passing beneath it. With the pane open
   on a wide screen the bar is hidden (.has-pane): the pane's own head
   carries the same two buttons and is sticky itself. The old rule that
   widened the bar to the full column when the pane was open is gone with
   it -- in that state the bar is not shown. */
.previewbar {
  position: sticky;
  top: calc(var(--actbar-h, 0px) + var(--tabbar-h, 4.2rem));
  z-index: 5;
  max-width: 56rem;
  margin: -0.35rem auto 0.55rem;
  padding: 0.45rem 0;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.5rem;
  background: var(--paper);
  border-bottom: 1px solid transparent;
}
.previewbar.is-stuck {
  border-bottom-color: var(--line);
}
.previewbar-sentinel {
  height: 0;
}

/* THE LARGE-TEXT LINK (Round 198, Derek: "more minimalist ... on the left
   side of the conducting sheet preview", matching Move this sheet / Clear
   this sheet): the sheet actions' own quiet italic without their glyph,
   at the left of the pane's head opposite Hide preview and Print, and at
   the left of the bar that stands in for the pane. */
.sheettext.quiet-inline::before {
  content: none;
}

.previewbar .sheettext {
  margin-right: auto;
}

/* In the pane's head the link stands a little lower and further in
   (Round 200, Derek): dropped to the foot of the button row, its words a
   few pixels under the buttons' own, and inset so it starts on the
   sheet's left edge inside the paper rather than on the pane's. */
.preview-head .sheettext {
  align-self: flex-end;
  margin-left: 1.5rem;
  padding-bottom: 0;
  /* Round 206: "down just a bit more" -- a transform, so the head keeps its
     height and --sheet-drop (Round 199) is unmoved. */
  transform: translateY(0.2rem);
}
@media (min-width: 72rem) {
  .previewbar.has-pane {
    display: none;
  }
}

/* Edit / take-off on a sustaining or release, on the conducting sheet itself.
   Quiet until the row is hovered: this block is read from the pulpit, so the
   controls must not compete with the names in it. */
.bizrow-acts {
  display: inline-flex;
  gap: 0.75rem;
  margin-left: 0.75rem;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.bizrow:hover .bizrow-acts,
.bizrow:focus-within .bizrow-acts {
  opacity: 1;
}

@media (hover: none) {
  .bizrow-acts { opacity: 1; }
}

/* Reordering one organization's callings. A plain numbered list with ↑ ↓,
   not drag and drop: this is used on a laptop and on a phone, and dragging is
   the one interaction that works badly on both. */
.orderlist {
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
  max-height: 22rem;
  overflow-y: auto;
}

.orderrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.4rem 0.1rem;
  border-bottom: 1px solid var(--line);
}

.orderrow:last-child { border-bottom: none; }

.orderrow-name {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.orderrow-who {
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.orderrow-who.vacant { font-style: italic; }

.orderrow-acts {
  display: inline-flex;
  gap: 0.2rem;
  flex: none;
}

/* Someone already down for a later Sunday. The plain hint is the sage accent;
   this one takes the attention colour, because "already on 10/18" is not
   history, it is a clash about to be created. */
.histhint.booked {
  color: var(--warn-ink, #9a5b2b);
  font-weight: 650;
}

/* What the app already knows about a candidate, shown inside the dialogs where
   a calling is decided. Ruled off rather than boxed: it is context for the form
   above it, not a separate thing to read. */
.candctx {
  margin: 0.6rem 0 0.9rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--line);
}

.candctx-line {
  margin: 0.2rem 0;
  font-size: 0.875rem;
  color: var(--ink-soft);
}

.candctx-label {
  display: inline-block;
  min-width: 9.5rem;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* The ones that are a reason to stop and think. */
.candctx-line.is-warn,
.candctx-line.is-warn .candctx-label {
  color: var(--warn-ink, #9a5b2b);
}

.candctx-line.is-warn { font-weight: 600; }

/* What happens to the name and number on a group card. It sits under the two
   fields it is about, quiet but not faded away — somebody is deciding whether
   to publish their mobile number to anyone who opens the page. */
/* (.gform-note WAS HERE: the line under the group form's two contact fields
   saying that the name and number are published on a page anyone can open.
   Derek had it removed in Round 329 and worker.js no longer emits it, so the
   rule went with its only emitter -- a rule nothing wears is worse than none.
   The reasoning for the note, should it ever come back, is kept as a comment
   at the point in worker.js where it stood.) */

/* Shown when the program on the page is last Sunday's, because the coming
   week's sheet has not been saved yet. It sits above the program rather than
   replacing it: somebody who missed Sunday still wants to read it, they just
   must not take it for this week. Set as a notice, not as an error — nothing
   is wrong, the page is simply older than the reader assumes.

   IT IS NOT A CALLOUT BOX. It used to carry a tinted fill, a rounded corner
   and a coloured left bar — the only filled element in the Program tab, on a
   page whose .ann comment above states the rule it broke ("a stack of
   bordered cards read as a different website from the page behind the
   Program tab, which has no boxes anywhere"). And this is the state the page
   is in most of the week, every week, so that box was its commonest
   appearance. It is now set in the page's own aside voice — the centred
   italic --ink-soft that .empty and .mdate already use — ruled off top and
   bottom, which is how everything else on this page divides itself. */
.stale-note {
  margin: 0 0 1.4rem;
  padding: 0.7rem 0;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--ink-soft);
  font-style: italic;
  text-align: center;
  text-wrap: balance;
  border-block: 1px solid var(--line);
}

/* Earlier copies of a conducting sheet. A list of times and a button each —
   no diff and nothing to interpret, because the person reading it has usually
   just lost something and wants it back, not an explanation. */
.verlist {
  margin: 0.8rem 0 0;
  padding: 0;
  list-style: none;
  max-height: 20rem;
  overflow-y: auto;
}

.verrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0.1rem;
  border-bottom: 1px solid var(--line);
}

.verrow:last-child { border-bottom: none; }

.verrow-when {
  display: flex;
  flex-direction: column;
  font-variant-numeric: tabular-nums;
}

.verrow-who {
  font-size: 0.8125rem;
  color: var(--ink-soft);
  font-variant-numeric: normal;
}

/* The picture in the restore box. A page thumbnail: the sheet drawn at its
   printed width and shrunk bodily, so it keeps the proportions of the paper
   instead of re-flowing into a differently-shaped sheet. The height of
   .verpaper-fit is set in script — a transform takes no part in layout, so
   without it this box would hold the full-size sheet's height open. */
/* overflow-y is SCROLL, not auto, and it is load-bearing. With `auto` the
   width the sheet is measured against depends on whether the scrollbar is
   there, and the scrollbar depends on the height the measurement sets: the
   box scaled up to the full width, which brought the scrollbar back, which
   made the sheet too wide — a horizontal scrollbar under a picture that fit
   a moment earlier. A gutter that is always held open cannot oscillate.
   overflow-x hidden is the belt to that braces: sub-pixel rounding must not
   be able to put that bar back. */
.verpaper {
  margin: 0.35rem 0 0.2rem;
  max-height: min(24rem, 42vh);
  overflow-y: scroll;
  overflow-x: hidden;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  padding: 0.7rem;
}

.verpaper-fit {
  overflow: hidden;
}

.verpaper-page {
  width: 34rem;
  transform-origin: top left;
}

.verpaper-page .stand-inner {
  max-width: none;
  margin: 0;
  font-size: 1rem;
}

.verpaper-cap {
  margin: 0.9rem 0 0;
}

/* The "about" chip in the activity log. It was a passive label for the life of
   the log; it is the filter now, so it has to look pressable without becoming
   a button in a list that is mostly reading. */
.actrow-r {
  font: inherit;
  font-size: 0.8125rem;
  padding: 0.05rem 0.45rem;
  margin-left: 0.4rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
}

.actrow-r:hover,
.actrow-r:focus-visible {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--accent);
}

.actrow-r.on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* The one question the group form asks: you left out when and where you meet.
   It replaces the buttons rather than floating over them — this page has no
   modal of its own, and adding one here would be the only dialog on the whole
   public site. */
.gconfirm {
  margin-top: 0.9rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-detail);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}

.gconfirm-q {
  margin: 0 0 0.25rem;
  font-weight: 650;
}

.gconfirm-p {
  margin: 0 0 0.8rem;
  font-size: 0.9375rem;
  color: var(--ink-soft);
}

/* The suggested answer first: adding the details is what makes the card
   useful, and sending without them is the deliberate exception. */
.gconfirm-acts {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
}

/* How long a tracker item has sat where it is. Quiet by default — it is
   context, not an alarm — and takes the attention colour once it has been
   there long enough to mean somebody should look. */
.stage-age {
  margin-top: 0.25rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
}

.stage-age.is-stale {
  color: var(--warn-ink, #9a5b2b);
  font-weight: 650;
}

/* The "Sundays ahead" card's rules (.aheadlist / .aheadrow*) WERE HERE and
   went with the card (Round 131). Grepped app.js, index.html, install.js and
   worker.js first — one emitter, now deleted. */

/* Longest standing: two short lists in one dialog. */
.standlist { margin: 0.5rem 0 0; padding: 0; list-style: none; }
.standgap { height: 1.2rem; }

.standrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--line);
}

.standrow:last-child { border-bottom: none; }

.standrow-what { display: flex; flex-direction: column; min-width: 0; }

.standrow-org {
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.standrow-age {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

/* The first-Sunday note under the meeting type. */
.calnote { margin: 0.35rem 0 0; }

/* The bishopric meeting on one page. Read-only, and printed most of the time —
   so it is set as a document, not as a screen: sections in reading order, a
   count where a count means something, and the owning tab named under each so
   anybody can go and act on it afterwards. */
.meetingpaper .mtg-sec {
  margin-top: 1.4rem;
  break-inside: avoid;
}

.mtg-h {
  margin: 0 0 0.35rem;
  text-transform: uppercase;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.25rem;
}

.mtg-n {
  margin-left: 0.5rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

.mtg-list {
  margin: 0.35rem 0 0;
  padding-left: 1.15rem;
}

.mtg-list li { margin: 0.18rem 0; }

.mtg-empty {
  margin: 0.35rem 0 0;
  font-style: italic;
  color: var(--ink-soft);
}

/* Which tab owns this, so the page can be acted on after the meeting. */
.mtg-owner {
  margin: 0.3rem 0 0;
  font-size: 0.75rem;
  color: var(--ink-soft);
  text-align: right;
}

@media print {
  /* The owning-tab lines are for somebody at a screen; on paper they are noise. */
  .meetingpaper .mtg-owner { display: none; }
}

/* ---------------------------------------------------------- Round 40 -----
   How long somebody has been unavailable; notes attached to a calling; and
   the two-step bishopric handover's danger link. ------------------------ */

/* The date under an availability control. Quiet until it is a year old, at
   which point it is the whole point of the line and takes the warning ink. */
.availage {
  display: block;
  font-size: 0.75rem;
  color: var(--ink-soft);
  margin-top: 0.15rem;
  white-space: nowrap;
}

.availage.is-stale {
  color: var(--warn-ink, #9a5b2b);
}

.mavailcell {
  white-space: nowrap;
}

.availage-row {
  margin: -0.35rem 0 0.7rem;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.availage-row .availage {
  display: inline;
  margin: 0;
}

/* One line above the members table when answers have gone stale. Not a badge:
   a badge belongs on the tab bar and this is a sentence, not a count. */
.stalenote {
  margin: 0 0 0.9rem;
  font-size: 0.875rem;
  color: var(--ink-soft);
}

/* A holder from another ward (Round 48). A small chip in the TITLE cell, not
   beside the holder — the holder cell is a <select> in the editable board and
   the grid's four tracks are fixed, so anything added there would have moved
   every column. Quiet: it is a fact about the row, not a task. */
.wb-ward {
  margin-left: 0.35rem;
  padding: 0.05rem 0.4rem;
  font-size: 0.66rem;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  border: 1px solid var(--line);
  border-radius: 999px;
  vertical-align: 0.1em;
  white-space: nowrap;
}

/* An 11-year-old shown where they are headed next year. Derived from the
   members list, not a calling row: italic and faded so it cannot be read as
   a vacancy or a filled calling, and there is no dropdown, no set-apart box
   and no ▾ — nothing to do to it. */
.wb-row.wb-incoming {
  font-style: italic;
  color: var(--ink-soft);
}

.wb-row.wb-incoming .wb-title {
  color: var(--ink-soft);
}

/* Handover is the only genuinely destructive thing on the Admin tab, so it
   is the only utility link that is not the accent. */
.quiet-inline.is-danger {
  color: var(--warn-ink, #9a5b2b);
}

/* Answered before the clock existed. Fainter than a real age, because it is
   the absence of a fact rather than a fact. */
.availage.is-undated {
  opacity: 0.7;
  font-style: italic;
}

/* ============================== add to home screen ========================

   Shown only on a phone or tablet, only before the icon has been added, and
   only until it is turned down once -- install.js decides, this only dresses
   it. On the ward's page it renders inline in the colophon; in the leader app
   the same words go through ask(). ------------------------------------- */

.install {
  margin: 0 0 1.1rem;
}

/* ONE UNDERLINE MECHANISM. This was built two incompatible ways alongside
   .glive-view (retired in Round 244): one drew a text-decoration hugging the
   letters, the other a border-bottom that sits lower, spans the padding box
   and runs straight through descenders. Side by side they were visibly not
   the same control. text-decoration won, because it is the one that skips
   descenders — keep it that way for anything of this rank. */
.install-link {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent);
  text-underline-offset: var(--ul-offset, 0.2em);
}

.install-why {
  margin: 0.25rem 0 0;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

.install-panel {
  margin-top: 0.7rem;
  text-align: left;
  display: inline-block;
  max-width: 22rem;
}

/* Numbered because they are done in order, and the numbers are the only thing
   telling somebody where they are in a sequence they are following on a
   different screen than the one they are reading. */
.install-steps {
  margin: 0;
  padding-left: 1.3rem;
  font-size: 0.875rem;
  line-height: 1.5;
}

.install-steps li {
  margin-bottom: 0.4rem;
}

.install-caveat {
  margin: 0.5rem 0 0;
  font-size: 0.8125rem;
  font-style: italic;
  color: var(--ink-soft);
}

.install-no {
  font-size: 0.8125rem;
  color: var(--ink-soft);
  padding: 0.4rem 0 0;
  text-decoration: underline;
}

/* Nothing about this belongs on paper. */
@media print {
  .install { display: none !important; }
}

/* Add to calendar. Quiet and last: the announcement is the message, this is
   an afterthought that happens to be the only part of it that outlives the
   page being closed. */
.ann-cal {
  /* 0.9rem above (Round 212): the footnote stands off the message by the
     same distance the message stands off the heading; it sat closer than
     the message's own paragraphs sit to each other. */
  margin: 0.9rem 0 0;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

/* Two calendars (Round 211): "Add to calendar: Apple · Google". The dot
   keeps the two links' enlarged hit boxes (below) from meeting. */
.ann-cal-sep {
  margin: 0 0.5rem;
}

/* 93x18px measured on a phone — smaller than the group card's number, which
   Round 24 judged too fiddly at 95x24 and enlarged. This is the only control
   on the Announcements tab and the only thing on the page that outlives the
   visit, so it should not be the hardest thing on it to hit.
   Same trick as that one: the hit area is an invisible box laid over the link,
   so the 13px line and its underline stay exactly as set. */
.ann-cal a,
.wardcal a {
  color: var(--accent);
  /* The install link's weight (Round 213, Derek): these two words are the
     only things to press on a notice, and at the sentence's own weight the
     accent alone was a thin signal at 13px. The label before them
     ("Add to calendar:", "Subscribe:") stays at 400. */
  font-weight: 600;
  /* The quiet pressable words' own underline (Round 47's --ul-offset), not
     the prose link's 0.15em: these are 13px control words. The footer's
     copy of the same two words had this offset and no hit box; the notice's
     had the hit box and the prose offset. One rule now (Round 212). */
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: var(--ul-offset, 0.2em);
  position: relative;
  display: inline-block;
}

.ann-cal a::after,
.wardcal a::after {
  content: '';
  position: absolute;
  left: -0.5rem;
  right: -0.5rem;
  top: -0.7rem;
  bottom: -0.9rem;
}

/* THE CHOOSER (Round 222). The two links fold into one button and a short
   list; public/calendar.js does the folding and both pages share it. These
   rules only dress what it builds -- without JavaScript none of them match,
   and the plain links above are what shows. */
.calbtn {
  font: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--accent);
  font-weight: 600;
  display: inline-flex;
  align-items: baseline;
  gap: 0.28rem;
}

.calbtn-words {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: var(--ul-offset, 0.2em);
}

.calbtn::after {
  content: '▾';
  font-size: 0.8em;
  transition: transform 120ms ease;
}

.calbtn[aria-expanded='true']::after {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
  .calbtn::after { transition: none; }
}

.calmenu {
  display: block;
  margin-top: 0.5rem;
  padding: 0.25rem;
  max-width: 15rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-item, 8px);
}

/* The rows are block targets in a list, so the inline links' enlarged hit box
   (.ann-cal a::after) is both unnecessary and harmful here -- it would reach
   into the row above and below. */
.calmenu .calmenu-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* There is no global border-box in this stylesheet (Round 29), so width:100%
     plus the padding below would push each row past the menu's own edge. */
  box-sizing: border-box;
  width: 100%;
  padding: 0.42rem 0.5rem;
  font: inherit;
  font-weight: 500;
  text-align: left;
  color: var(--ink);
  text-decoration: none;
  background: none;
  border: 0;
  border-radius: var(--r-detail, 4px);
  cursor: pointer;
}

.calmenu .calmenu-item::after {
  content: none;
}

.calmenu .calmenu-item:hover,
.calmenu .calmenu-item:focus-visible {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.calicon {
  width: 1.15em;
  height: 1.15em;
  flex: none;
}

/* The footer's block is centred (Round 214), so the list is centred under its
   button rather than stranded at the column's left edge. The per-notice row is
   left-aligned prose and keeps its list on the left. The rows INSIDE the list
   stay left-aligned either way — a mark and a name read as a row, not a
   centred caption. */
.wardcal-links .calmenu {
  margin-inline: auto;
  text-align: left;
}

/* The address, for Outlook and everything else. Ruled off: it is not a
   calendar choice, it is the way out for anyone the two do not cover. */
.calmenu-copy {
  display: block;
  margin-top: 0.25rem;
  padding-top: 0.25rem;
  border-top: 1px solid var(--line);
}

.calmenu-address {
  display: block;
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
  word-break: break-all;
}

/* Nothing to tap on paper. */
/* PAPER HAS NO DARK MODE (Round 43's rule, reached the ward's page in
   Round 212). The print blocks force the body's ink and paper, but every
   element that names a colour TOKEN -- the date lines and headings
   (--accent), the places and footnotes (--ink-soft), the hairlines
   (--line) -- still resolved the dark palette on a dark phone, and always
   when the footer's Dark switch was chosen, since :root[data-theme='dark']
   (Round 182) carries no media condition: pale sage and grey on white, at
   about 2:1. THESE ARE THE LIGHT PALETTE'S VALUES from the top of this
   file -- a token changed there has to change here too. Later in the file
   than both dark blocks, which it beats at equal specificity. */
@media print {
  :root,
  :root:not([data-theme='light']),
  :root[data-theme='dark'],
  .wp-paper.is-dark {
    color-scheme: light;
    --paper: #fff;
    --card: #fff;
    /* Matches --card, which is already #fff on paper (Round 309) -- a
       printed card is unchanged by this round. */
    --card-bright: #fff;
    --ink: #26211a;
    --ink-soft: #6b6254;
    --accent: #527a63;
    --on-accent: #ffffff;
    --line: #e3dac8;
    --dots: #b9ae99;
  }
  /* The dark treatment screens a 0.42 plate onto white, which prints
     nothing; the light treatment restated. */
  :root:not([data-theme='light']) .engraving img,
  :root[data-theme='dark'] .engraving img,
  .wp-paper.is-dark .engraving img {
    filter: grayscale(1) brightness(1.38) contrast(1.42) sepia(0.62) saturate(0.8);
    opacity: 0.9;
    mix-blend-mode: normal;
  }
}

@media print {
  /* The scope navigator is a way of moving about a screen; paper has no
     screen to move about, and the heads it points at are all printed. */
  .annnav { display: none !important; }
  .wardcal { display: none !important; }
  .ann-cal { display: none !important; }

  /* PAPER KEEPS THE RULED COLUMN IT ALWAYS HAD (Round 298). The card is
     a screen object: its double edge is two shadow spreads, which no
     printer draws, and a 5px accent bar across the head of every notice
     is a row of solid rules down a printed page. Nobody asked for the
     printout to change, so it does not -- the hairline between notices
     is exactly what it was before the cards. */
  .ann {
    background: none;
    border: 0;
    border-bottom: 1px solid var(--line);
    box-shadow: none;
    padding: 0 0 1.9rem;
    margin: 0 0 1.3rem;
  }

  .ann:last-of-type {
    border-bottom: none;
    margin-bottom: 0.5rem;
    padding-bottom: 0;
  }
}

/* The saved weekly copies, on the Admin tab. */
.copylist {
  margin: 0.6rem 0;
  padding-left: 1.4rem;
}

.copyrow {
  padding: 0.28rem 0;
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
}

.copyrow-when {
  min-width: 5.5rem;
}

.copyrow-size {
  font-size: 0.8125rem;
  color: var(--ink-soft);
  min-width: 4rem;
}

/* Never having saved a copy off the platform is the one state worth marking. */
.copywarn {
  margin: 0.4rem 0 0;
  font-size: 0.8125rem;
  color: var(--warn-ink, #9a5b2b);
}

/* "If something looks wrong" — ordered because the order is the advice. */
.helpsteps {
  margin: 0.5rem 0 0.9rem;
  padding-left: 1.3rem;
  font-size: 0.9375rem;
  line-height: 1.5;
}

.helpsteps li {
  margin-bottom: 0.45rem;
}

/* The Admin utility links: three things that show you something, spaced so
   they read as three. They used to run edge to edge in one paragraph, because
   el() inserts no text between children and this rule set only a margin. */
.adminlinks {
  margin: -0.4rem 0 1.2rem;
  display: flex;
  flex-wrap: wrap;
  gap: 1.1rem;
}

/* The handover, on its own at the foot of the page. It is the only thing on
   this tab that destroys anything, so it does not share a row with links
   that merely open a dialog. */
.handovercard {
  border-color: color-mix(in srgb, var(--danger) 30%, var(--line));
}

.btn.is-danger {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 55%, var(--line));
}

.btn.is-danger:hover {
  background: color-mix(in srgb, var(--danger) 8%, transparent);
}

/* THE DETAIL OF AN "OTHER" ITEM IS THE CONTENT, not a footnote -- it
   carries the thing being announced: the baby's name under "Baby
   Blessing", read out from the pulpit. It was 0.86em in the soft ink,
   the smallest type anywhere on the sheet.
   THE SIZE MOVED TO .paperlike IN ROUND 298, so the preview shows what
   the paper shows; only the ink is forced here, because a colour token
   would print the dark theme's value on a dark-mode machine (Round 43).
   The italic stays, which is what marks it subordinate to its heading. */
@media print {
  .cs-otherdetail {
    color: #333;
  }
}

/* The row menu's hit area, laid over the glyph so the ⋯ does not move and the
   status pill beside it does not shift. ~17px becomes ~34x40. */
.rowdots::after {
  content: '';
  position: absolute;
  left: -0.45rem;
  right: -0.45rem;
  top: -0.6rem;
  bottom: -0.6rem;
}

/* A HINT THAT ENDS A BLOCK NEEDS ROOM UNDER IT.
   .hint has a top margin and no bottom one, which is right inside a .field —
   the field's own spacing does that job — and wrong wherever a hint is the
   last thing in a card or a section, where it ended up touching the field
   below and reading as that field's caption rather than as the closing note
   of the block above. Scoped so it never reaches a hint inside a .field. */
.acard > .hint:last-child,
.acc-body > .hint:last-child,
section > .hint:last-child {
  margin-bottom: 0.75rem;
}

/* HOW LONG THEY HAVE BEEN IN THE WARD (Round 55, restyled in Round 57 --
   Derek: the pills were far too bulky). Small faded text in its own sortable
   column on the Members tab, and beside the name on the profile; a Provo 1st
   Veteran in deep orange-brown, a badge of honour rather than a warning. */
.tenure {
  font-size: 0.72rem;
  color: var(--ink-soft);
  white-space: nowrap;
}
.tenure.is-vet {
  color: var(--tenure-vet);
}
.profile-name + .tenure,
.chip + .tenure {
  margin-left: 0.6rem;
}
.mtenure {
  white-space: nowrap;
}

/* THE NOTES BOX (Round 57). One small square per row: a "+" when the member
   has notes, empty when they have none; either way the box lifts on hover
   and its label says which it is ("Open notes" / "Add note"), and a press
   opens the thread. Only the people who may read sensitive notes see the
   column at all -- showNotes in the members view, perms.notes on the
   server. */
/* THE NOTE BOX'S TWO ANIMATIONS (Round 73, Derek). A fixed rectangle so the
   motion has a stage. With no notes it is blank at rest, and on hover "Add
   note" rises into the middle. With notes a "+" sits in the middle, and on
   hover it slides to the left while "Open notes" slides in from the right
   to stand beside it. One duration and one easing for both, so the two
   read as a pair. Without a hover (touch) the words simply show; with
   reduced motion the transitions are off and the states just switch. */
.notebox {
  font: inherit;
  position: relative;
  overflow: hidden;
  width: 7.2rem;
  height: 1.75rem;
  padding: 0;
  display: inline-block;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  vertical-align: middle;
  transition: border-color 220ms ease-out, background-color 220ms ease-out;
}
.notebox-plus,
.notebox-word {
  position: absolute;
  top: 50%;
  white-space: nowrap;
  line-height: 1;
  pointer-events: none;
  transition: transform 220ms ease-out, opacity 220ms ease-out, left 220ms ease-out;
}
/* THE PLUS IS THE SIGNAL (Round 241, Derek: bolded, and a colour that says
   it can be pressed). Bold, a size up, and the accent taken to full ink
   strength. color-mix toward --ink DEEPENS it on cream and LIGHTENS it on
   dark paper, so one line raises the contrast in BOTH palettes -- measured
   in the app, 4.51:1 to 6.07:1 in light and 8.31 to 9.52 in dark. A literal
   hex could only ever have served one of them. It stays green because that
   is what this app has always meant by "you can press this"; the attention
   terracotta would say "something needs you", which a note already written
   does not (Round 18). */
.notebox-plus {
  display: none;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.35rem;
  font-weight: 700;
  color: color-mix(in srgb, var(--accent) 75%, var(--ink));
}
.notebox.has .notebox-plus {
  display: block;
}
.notebox-word {
  font-size: 0.8125rem;
  font-weight: 600;
  opacity: 0;
}
/* No notes: the word simply fades in where it sits (Round 76, Derek: much
   more subtle than the rise). A slower, gentler curve than the plus's slide,
   so the two still feel like a pair without the blank box ever moving. */
.notebox:not(.has) .notebox-word {
  left: 50%;
  transform: translate(-50%, -50%);
  transition: opacity 320ms ease;
}
.notebox:not(.has):hover .notebox-word,
.notebox:not(.has):focus-visible .notebox-word {
  opacity: 1;
}
/* Notes: the plus slides left, the word slides in beside it. */
.notebox.has .notebox-word {
  left: 1.95rem;
  color: var(--accent);
  transform: translate(0.6rem, -50%);
}
.notebox.has:hover .notebox-plus,
.notebox.has:focus-visible .notebox-plus {
  left: 0.75rem;
  transform: translate(0, -50%);
}
.notebox.has:hover .notebox-word,
.notebox.has:focus-visible .notebox-word {
  opacity: 1;
  transform: translate(0, -50%);
}
.notebox:hover,
.notebox:focus-visible {
  border-color: var(--ink-soft);
  background: color-mix(in srgb, var(--ink-soft) 5%, transparent);
}
.notebox.has {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.notebox.has:hover,
.notebox.has:focus-visible {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
@media (hover: none) {
  .notebox-word { opacity: 1; }
  .notebox.has .notebox-plus { left: 0.75rem; transform: translate(0, -50%); }
  .notebox.has .notebox-word { transform: translate(0, -50%); }
}
@media (prefers-reduced-motion: reduce) {
  .notebox, .notebox-plus, .notebox-word { transition: none; }
}

/* "Moved out" is a quiet word at the end of the row, not a pill on every
   line (Derek: awkwardly placed). Faint until the row is hovered, always
   visible on touch. */
.mmove {
  font: inherit;
  font-size: 0.78rem;
  color: var(--ink-soft);
  background: none;
  border: none;
  padding: 0.2rem 0.4rem;
  cursor: pointer;
  opacity: 0.55;
  white-space: nowrap;
}
.mtable tr:hover .mmove,
.mmove:focus-visible {
  opacity: 1;
  color: var(--accent);
}
@media (hover: none) {
  .mmove { opacity: 1; }
}

/* A pinned note sits at the top of its thread with a small mark; the mark
   is set by the tag rank at the top of this file. */
.noteitem.is-pinned {
  border-left: 3px solid var(--accent);
  padding-left: 0.6rem;
}
.notepin {
  display: inline-block;
  color: var(--accent);
  margin-bottom: 0.2rem;
}
.notectl .notelatest {
  margin: 0 0 0.4rem;
  color: var(--ink-soft);
  font-size: 0.9rem;
}

/* The Members table without the Youth column (Round 59): a small mark on
   the name instead, and a faded dash where nobody has sorted a member yet. */
.mname .youthmark {
  margin-left: 0.5rem;
  vertical-align: middle;
}
.mavailcell select.is-unset,
.mavail-unset {
  color: var(--ink-soft);
  opacity: 0.6;
}
.mavailcell select.is-unset:hover,
.mavailcell select.is-unset:focus {
  opacity: 1;
}

/* THE NOTES DIALOG (Round 61): the member's name is the heading and "Notes"
   the small label above it; a pinned note carries a green pin rather than
   the word; a member can be flagged for attention, which shows as a small
   terracotta flag beside the name wherever the notes' readers see it. */
.notescard .noteslabel {
  margin: 0 0 0.15rem;
  color: var(--ink-soft);
}
.notescard h2.noteswho {
  font-size: 1.6rem;
  margin: 0 0 0.7rem;
}
.notepin svg,
.flagmark svg,
.flagbtn svg {
  width: 0.9rem;
  height: 0.9rem;
  vertical-align: -0.1em;
}

/* The generate mark on the hymn suggest button (Round 285; it replaced the
   re-run arrow, whose rule this was). Sized in em so it follows the button's
   own type through all five text sizes, and FILLED in currentColor so it is
   the button's ink — on the accent-filled primary button that is the paper
   colour, which no fixed value could have known.

   Filled, where the app's other two icons in buttons are stroked: a star
   tapers away from its box where a ring fills it edge to edge, so at the
   arrow's 1.05em it read a size smaller than the words. 1.2em measures the
   same beside them. */
.btn .sparkle {
  width: 1.2em;
  height: 1.2em;
  margin-right: 0.34rem;
  vertical-align: -0.22em;
  fill: currentColor;
  stroke: none;
}
.notepin svg {
  fill: var(--accent);
}
.flagmark {
  display: none;
  margin-left: 0.4rem;
  vertical-align: middle;
  color: var(--notify);
}
.flagmark.on {
  display: inline-block;
}
.flagmark svg {
  width: 1.05rem;
  height: 1.05rem;
  fill: var(--notify);
}
/* Icon only, in the dialog's top-right corner (Round 64): a small, faintly
   red flag that is easy to ignore until it is on, when it is the full
   attention colour. The list's flag (.flagmark.on) is the obvious one. */
.flagbtn {
  position: absolute;
  top: 1.1rem;
  right: 1.2rem;
  width: 1.9rem;
  height: 1.9rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--notify);
  opacity: 0.45;
  cursor: pointer;
}
.flagbtn svg {
  width: 1rem;
  height: 1rem;
  fill: currentColor;
}
.flagbtn:hover,
.flagbtn:focus-visible {
  opacity: 0.85;
  background: color-mix(in srgb, var(--notify) 10%, transparent);
}
.flagbtn.on {
  opacity: 1;
  background: color-mix(in srgb, var(--notify) 14%, transparent);
}
.notescard .notehint {
  margin: 0.3rem 0 0;
}

/* (The sheet's Meeting details / Music switch -- .sheettabs-wrap, its
   sentinel, folder tabs, counts and checks, Rounds 63-169 -- is gone:
   Round 220, Derek, one column. The base .sheettabs recipe above stays for
   the Members tab's add control and the program box's kind chooser.) */

/* THE STILL-BLANK STRIP heads the column (Round 167; on its own since
   Round 220 took the sub-tabs away): a low card holding "Still blank:" and
   its chips, hidden once nothing is blank. */
.sheetstatus {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 0.5rem 0.85rem;
  margin: 0 0 0.7rem;
}

/* A FILLED BOX ON THE SHEET (Round 172, Derek): a shade toward the accent on
   the card colour, so a box still waiting -- on the plain paper tint -- is
   told apart from one that is done. Set by paintFilled() in app.js on every
   input and change. */
.sheetform input.is-filled,
.sheetform select.is-filled,
.sheetform textarea.is-filled {
  background: color-mix(in srgb, var(--accent) 7%, var(--card));
  border-color: color-mix(in srgb, var(--accent) 30%, var(--line));
}

/* A BOX THAT STILL NEEDS FILLING (Round 177): the Still-blank chip's own
   look -- a dotted edge in the attention colour on a warm tint -- on the box
   the chip points at, so the two read as one thing. Set by markBlankBoxes()
   in app.js from the readiness rules, never from emptiness alone: an
   optional box that happens to be empty stays plain. Beside a filled box's
   calm sage, the difference is read at arm's length. The dots were 2px at 62%
   (Round 172); Round 208 made them the chips' own 1.5px at 70% (Round 205),
   at Derek's word, so box and chip match again. */
.sheetform input.is-blank,
.sheetform select.is-blank,
.sheetform textarea.is-blank {
  border: 1.5px dotted color-mix(in srgb, var(--notify) 70%, transparent);
  background: color-mix(in srgb, var(--notify) 7%, var(--paper));
}

.sheetform input.is-blank:focus,
.sheetform select.is-blank:focus,
.sheetform textarea.is-blank:focus {
  border-style: solid;
  border-color: color-mix(in srgb, var(--notify) 45%, transparent);
}

.sheetstatus .readiness {
  border-top: none;
  padding-top: 0;
  margin-top: 0;
}

/* THE HYMN PLANNER (Round 65) IS A MODAL (Round 220, Derek: "a modal
   filling a good portion of the screen, not all of it ... professional,
   clean ... a smooth animation on open"). The dialogs' own dimmed backdrop
   (.codeoverlay, z 50) with a centred card sized to the window: the head
   and the foot stay put and the grid scrolls between them. It comes in
   with a short rise and fade and leaves the same way; neither under
   prefers-reduced-motion. */
body.planning {
  overflow: hidden;
}
.planmodal-overlay {
  animation: planfade 200ms ease-out;
}
.planmodal {
  width: min(96vw, 78rem);
  height: min(88vh, 60rem);
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  padding: 1.1rem 1.4rem 0.9rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: 0 24px 60px rgba(20, 16, 10, 0.32);
  animation: planin 240ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.planmodal-overlay.is-closing {
  animation: planfadeout 160ms ease-in forwards;
}
.planmodal-overlay.is-closing .planmodal {
  animation: planout 160ms ease-in forwards;
}
@keyframes planfade { from { opacity: 0; } to { opacity: 1; } }
@keyframes planfadeout { to { opacity: 0; } }
@keyframes planin { from { opacity: 0; transform: translateY(14px) scale(0.975); } to { opacity: 1; transform: none; } }
@keyframes planout { to { opacity: 0; transform: translateY(8px) scale(0.985); } }
@media (prefers-reduced-motion: reduce) {
  .planmodal-overlay,
  .planmodal,
  .planmodal-overlay.is-closing,
  .planmodal-overlay.is-closing .planmodal {
    animation: none;
  }
}
.plan-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1rem;
  padding: 0 0 0.7rem;
  border-bottom: 1px solid var(--line);
}
.planmodal .plan-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
}
.planmodal .plan th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--paper);
}
.plan-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  min-height: 1.6rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--line);
}
.plan-foot .unlock-err:empty {
  display: none;
}
.plan-title h2 {
  margin: 0.1rem 0 0;
}
.plan-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.plan-status {
  font-size: 0.8rem;
  color: var(--ink-soft);
}
.plan-status.is-bad {
  color: var(--notify);
}
.plan-hint {
  margin: 0.6rem 0 0.8rem;
}
.plan-scroll {
  overflow-x: auto;
}
/* THE VOLUNTEERS LIST INSIDE THE PLANNER (Round 267). It sits after the grid
   in the same scrolling region, so opening it cannot squeeze the Sundays
   above it. Sticky at the left: on a window narrow enough to scroll the grid
   sideways, a plain block would slide out from under the pointer. */
.plan-volunteers {
  position: sticky;
  left: 0;
  margin: 1.2rem 0 0.2rem;
}
.plan {
  width: 100%;
  min-width: 56rem;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.plan th {
  text-align: left;
  padding: 0.35rem 0.4rem 0.45rem;
  border-bottom: 1.5px solid var(--line);
  color: var(--ink-soft);
}
.plan td {
  padding: 0.5rem 0.4rem 0.55rem;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.plandate {
  width: 9rem;
  white-space: nowrap;
}
.plandate strong {
  font-size: 1rem;
}
.plan-meta {
  font-size: 0.75rem;
  color: var(--ink-soft);
  margin-top: 0.15rem;
  white-space: normal;
  max-width: 9rem;
}
.plan-theme {
  font-style: italic;
}
.plancell {
  min-width: 11rem;
}
.planbox {
  position: relative;
}
.planin {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  padding: 0.32rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: var(--card);
  color: var(--ink);
}
.plantitle {
  font-size: 0.8rem;
  color: var(--ink-soft);
  margin-top: 0.22rem;
  min-height: 1.1em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.plantitle.is-unknown {
  color: var(--notify);
}
.planmarks {
  display: flex;
  gap: 0.45rem;
  flex-wrap: wrap;
  font-size: 0.72rem;
  margin-top: 0.1rem;
  min-height: 1em;
}
.planmark.new {
  color: var(--accent);
  font-weight: 650;
}
.planmark.recent,
.planmark.dup {
  color: var(--notify);
}
.plan-off {
  font-size: 0.8rem;
  font-style: italic;
  color: var(--ink-soft);
  opacity: 0.7;
}
tr.plan-none td {
  opacity: 0.6;
}
.plan-newcol {
  width: 6rem;
  white-space: nowrap;
  font-size: 0.75rem;
}
.plandrop {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 0.15rem);
  z-index: 6;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  box-shadow: 0 8px 24px rgba(20, 16, 10, 0.18);
  max-height: 16rem;
  overflow-y: auto;
  min-width: 16rem;
}
.plandrop-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.35rem 0.55rem;
  font-size: 0.85rem;
  cursor: pointer;
}
.plandrop-row.is-active,
.plandrop-row:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.plandrop-num {
  color: var(--ink-soft);
  min-width: 2.6rem;
  font-variant-numeric: tabular-nums;
}
.plandrop-title {
  flex: 1;
  min-width: 0;
}

/* THE PRAYER PICKER'S PANEL (Round 154, Derek: a list that opens straight
   down under the box and scrolls inside itself, never the browser's own
   floor-to-ceiling datalist). Two groups under small-caps headings that
   stay put while the names scroll; a row is the name, and in the second
   group the date they prayed at the right. Same panel recipe as .plandrop. */
.namepick-box {
  position: relative;
  display: block;
}
.namedrop {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 0.15rem);
  z-index: 6;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  box-shadow: 0 8px 24px rgba(20, 16, 10, 0.18);
  max-height: 16rem;
  overflow-y: auto;
  font-weight: 400;
}
.namedrop-head {
  position: sticky;
  top: 0;
  padding: 0.35rem 0.55rem 0.25rem;
  background: var(--card);
  border-bottom: 1px solid var(--line);
  color: var(--ink-soft);
}
.namedrop-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.3rem 0.55rem;
  font-size: 0.9rem;
  cursor: pointer;
}
.namedrop-row.is-active,
.namedrop-row:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.namedrop-when {
  flex: 0 0 auto;
  font-size: 0.78rem;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* THE LOCATION SUGGESTIONS (Round 242, Derek: the browser's own datalist was
   "not nearly as professional as I'd like"). The same panel, so it is flush
   with the box's left edge and set in the page's own serif rather than the
   browser's sans. Two rows only, so no scroll and a little more room in each
   than the prayer picker's long list can afford. "Select" sits at the right
   of the name -- it takes the tag rank above, and the accent under the
   pointer, so the row reads as a choice rather than a caption. */
.namedrop.locdrop {
  max-height: none;
}
.namedrop.locdrop .namedrop-row {
  padding: 0.45rem 0.6rem;
  font-size: 0.9375rem;
}
.locdrop-pick {
  flex: 0 0 auto;
  color: var(--ink-soft);
  opacity: 0.7;
}
.namedrop.locdrop .namedrop-row:hover .locdrop-pick,
.namedrop.locdrop .namedrop-row.is-active .locdrop-pick {
  color: var(--accent);
  opacity: 1;
}

/* THE DROP-DOWN UNDER A MEMBER'S ROW (Round 74): the name is the toggle,
   with a small caret that turns; the row beneath holds the same sections
   as the full profile page, on a faintly tinted ground, two across where
   there is room. */
.mcaret {
  display: inline-block;
  width: 1rem;
  margin-right: 0.15rem;
  color: var(--ink-soft);
  transition: transform 180ms ease-out;
}
tr.is-open .mcaret {
  transform: rotate(180deg);
}
tr.mexpand td {
  padding: 0.5rem 0.9rem 1rem 2.1rem;
  background: color-mix(in srgb, var(--ink-soft) 4%, transparent);
  border-bottom: 1px solid var(--line);
}
.mexpand-inner {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 0.6rem;
  align-items: start;
}
.mexpand-inner .pcard {
  margin: 0;
}
.mexpand-inner > .quiet-inline {
  grid-column: 1 / -1;
  justify-self: end;
}
.agecols {
  margin: 0.3rem 0;
}
@media (prefers-reduced-motion: reduce) {
  .mcaret { transition: none; }
}

/* THE CLERKS' DETAILS QUEUE (Round 75): one ruled row per new member --
   name, phone, birthday, and the quiet way out. */
.detailrow {
  display: grid;
  grid-template-columns: minmax(9rem, 1.2fr) minmax(9rem, 1fr) minmax(9rem, 1fr) auto;
  gap: 0.4rem 1rem;
  align-items: end;
  padding: 0.55rem 0;
  border-top: 1px solid var(--line);
}
.detailrow .field {
  margin: 0;
}
/* The birthday box with its calendar button (Round 152): the text box takes
   LCR's own "13 Mar 1992" and a small line under it reads back M/D/YY; the
   date picker itself is out of sight and opened by the button. The action
   column wraps its three controls. */
.bdayrow {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}
.bdayrow input[type='text'] {
  flex: 1 1 8rem;
  min-width: 0;
}
.bday-cal {
  flex: 0 0 auto;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-detail);
  background: var(--card);
  color: var(--accent);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.bday-cal:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}
.bday-cal .calicon {
  width: 1rem;
  height: 1rem;
  fill: currentColor;
}
.bday-picker {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.bday-picker.shown {
  position: static;
  width: auto;
  height: auto;
  opacity: 1;
  pointer-events: auto;
}
.bday-read {
  flex: 1 0 100%;
  font-size: 0.78rem;
  color: var(--ink-soft);
  min-height: 1em;
}
.bday-read.bad {
  color: var(--warn-ink, #9a5b2b);
}
.detailrow-acts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0.8rem;
  padding-bottom: 0.6rem;
}
.detailrow-name,
.detailrow > .quiet-inline {
  padding-bottom: 0.6rem;
}
@media (max-width: 44rem) {
  .detailrow {
    grid-template-columns: 1fr 1fr;
  }
  .detailrow-name {
    grid-column: 1 / -1;
    padding-bottom: 0;
  }
}

/* "Optional" beside a field label (Round 77): the label rank stays, the word
   is plain, small and faded. And the add-member buttons sit close under the
   last row now that the explaining sentence is gone. */
.lbl-opt {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  font-size: 0.75rem;
  color: var(--ink-soft);
  /* Two shades fainter than it was (Derek, Round 228): this is the least
     important word on the form -- the field's NAME is what is being read --
     and at 0.75 it was competing with the label beside it. Opacity rather
     than a paler colour, so it fades the same way against either palette. */
  opacity: 0.55;
  margin-left: 0.35rem;
}
.addcard .cols .field {
  margin-bottom: 0.45rem;
}
.addcard .addcard-acts {
  margin-top: 0.15rem;
}

/* The add-member card reserved a blank error line under its fields (the
   same Round 6 trap the dialogs fixed for themselves): collapsed when empty. */
.addcard .unlock-err:empty {
  display: none;
}

/* The clerks' queue row's two quiet actions, stacked (Round 81). */
.detailrow-acts {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
  padding-bottom: 0.4rem;
}

/* THE MEMBERS TABLE (Round 86, Derek). Name · in the ward · calling ·
   availability · notes · moved out. The box is capped at the width its
   content needs rather than the whole 76rem workspace, and the name column
   is the only one that takes what is left -- so the time in the ward sits
   right after the longest name, not half-way across the page. Availability
   and Notes each hold one small control and are centred over it with a
   comfortable margin either side; Moved out is flush right. The calling
   (Round 82's note on the name) is a column now, faded, and may wrap. */
.memtab .ptable-wrap {
  max-width: 70rem;
}
.memtab.no-notes .ptable-wrap {
  max-width: 58rem;
}
/* The search bar and filters stop at the box's edge (Round 94, Derek):
   the row above the box is capped to the same width, so nothing sits
   past the container it filters. */
.memtab .filterrow {
  max-width: 70rem;
  box-sizing: border-box;
}
.memtab.no-notes .filterrow {
  max-width: 58rem;
}
/* Time in the ward, inline after the name (Round 96). The name is 650; the
   tenure must not inherit that weight. */
/* THE BIRTHDAY BESIDE THE NAME (Round 246): drawn only while a birthday
   month is being looked at, so it says what the list under it is ordered by.
   The accent, not the soft ink the time in the ward beside it takes -- it is
   the answer to the question that was asked, and two grey marks after a name
   would leave the eye nothing to run down. Same size and weight, so it reads
   as a mark on the name rather than as a second name. */
.mname .bdaymark {
  margin-left: 1rem;
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--accent);
  white-space: nowrap;
}
.mname .tenure {
  /* 1rem, not 0.55 (Round 100, Derek): a little more air after the name,
     still hanging off the end of each name rather than at a column edge. */
  margin-left: 1rem;
  font-weight: 400;
}
.sortnew {
  align-self: center;
  white-space: nowrap;
}
.sortnew::before {
  content: none;
}
.sortnew.on {
  color: var(--accent);
}
.mtable .mcallcell {
  width: 15rem;
  font-size: 0.8125rem;
  color: var(--ink-soft);
}
/* Left-aligned under its heading (Round 97, Derek): the select carries
   0.6rem of its own padding, so with none on the cell its text starts
   exactly where the heading's does. The dash of an unanswered row is still
   centred inside the box. */
.mtable .mavailcell {
  width: 10.5rem;
  text-align: left;
  padding-left: 0;
  padding-right: 0.6rem;
}
.mtable .mavailcell select {
  width: auto;
  display: inline-block;
}
/* The dash of an unanswered row sits in the middle of its box; once an
   answer is chosen the words are left-aligned like any other select. */
.mtable .mavailcell select.is-unset {
  text-align: center;
  text-align-last: center;
}
.mtable .mnotecell {
  width: 10rem;
  text-align: center;
  padding-left: 1.2rem;
  padding-right: 1.2rem;
}
.mtable th.mcenter .psort {
  text-align: center;
}
.mtable td.pactions {
  text-align: right;
  white-space: nowrap;
  padding-right: 0.6rem;
}

/* The search box lights up a little under the pointer (Round 85, Derek). It
   sits on the paper colour in a row of paper-coloured boxes, so at rest it
   reads as part of the row; under the pointer it lifts to the card colour and
   its border warms toward the accent -- the same lift the note box gives
   (.notebox:hover), so the two say "you can press me" in one voice. The
   selects beside it are left alone: a select opens, it does not take typing,
   and the lift is the invitation to type. Focus keeps the outline ring the
   focus rule already draws; the fill stays lifted while the box has focus so
   it does not fall back to paper the moment you start typing. */
.filterrow input[type='search'],
.searchbar input[type='search'] {
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
.filterrow input[type='search']:hover,
.searchbar input[type='search']:hover,
.filterrow input[type='search']:focus,
.searchbar input[type='search']:focus {
  background: var(--card);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}
@media (prefers-reduced-motion: reduce) {
  .filterrow input[type='search'],
  .searchbar input[type='search'] { transition: none; }
}

/* THE "PROFILE INCOMPLETE" TABLE (Round 88, Derek). Name, then each missing
   piece as a quiet dashed chip you can press to add it, with a small x to
   ignore that piece for this member. The heading in this table is text,
   not a sort button. */
.psort.static {
  display: inline-block;
  cursor: default;
}
.mtable .mgaps {
  padding-top: 0.45rem;
  padding-bottom: 0.45rem;
}
.gap {
  display: inline-flex;
  align-items: center;
  margin: 0.15rem 0.6rem 0.15rem 0;
}
.gapadd,
.gapword {
  font: inherit;
  font-size: 0.8125rem;
  padding: 0.2rem 0.6rem;
  border: 1px dashed color-mix(in srgb, var(--accent) 55%, var(--line));
  border-radius: 999px;
  background: none;
  color: var(--accent);
  white-space: nowrap;
}
.gapadd {
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
.gapadd:hover,
.gapadd:focus-visible {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-color: var(--accent);
}
.gapword {
  color: var(--ink-soft);
  border-color: var(--line);
}
.gapignore {
  font: inherit;
  font-size: 0.9rem;
  line-height: 1;
  margin-left: 0.15rem;
  padding: 0.15rem 0.3rem;
  background: none;
  border: none;
  color: var(--ink-soft);
  opacity: 0.45;
  cursor: pointer;
}
.gap:hover .gapignore,
.gapignore:focus-visible {
  opacity: 1;
}
@media (hover: none) {
  .gapignore { opacity: 1; }
}
.mtable .mempty {
  text-align: center;
  padding: 1.2rem;
}

/* A HOVER LABEL THAT WAITS A MOMENT (Round 91, Derek). The "Since <date>" on
   a member's status would otherwise flash on every pass of the pointer down
   the Availability column; it waits half a second to appear and leaves at
   once. .tipwrap is the span around a <select>, which cannot carry a
   pseudo-element of its own; focus-within shows the label for the keyboard,
   since the focus lands on the select inside. */
.tipwrap {
  display: inline-block;
}
[data-tip].tip-slow:hover::after,
[data-tip].tip-slow:focus-within::after {
  opacity: 1;
  transition-delay: 0.5s;
}
[data-tip].tip-slow:focus-within {
  z-index: 60;
}

/* The flag in the drop-down profile's corner (Round 99). The cell is the
   positioning box; the rule outranks [data-tip]'s position: relative. */
.mexpand-cell {
  position: relative;
}
.mexpand-cell .flagbtn {
  position: absolute;
  top: 0.8rem;
  right: 1rem;
}

/* THE CONDUCTING SHEET TAB'S TWO BADGES AS ICONS (Rounds 102-103, Derek's
   samples): the meeting count on a red page with a folded corner, the
   music count in the head of a blue eighth note whose stem and curved
   flag rise off its right. Both icons are inline SVG built in app.js
   (pageIcon, noteTailIcon); the number stays inside; sizes are in em so
   both grow with the badge on the open tab; the colours are the tokens
   the two badges already used. The page carries no text lines -- the
   number takes that space. Every other tab keeps the plain pill. */
/* Both icon badges sit on the same line (Round 104): vertical-align middle
   on each, and the page centres its number in the page body below the
   fold so the two digits share one horizontal line. */
.tabbtn .tab-count.sheet {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The LAYOUT box stays 1.42 x 1.75em (Round 127): it is what the tab
     row's line box sees, so the row does not grow or shift when the badge
     does. The page itself is DRAWN bigger by the SVG below, hung off this
     box with negative offsets; the digit is flex-centred in this box, on
     the pill badges' line. */
  width: 1.42em;
  height: 1.75em;
  padding: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  /* BASELINE, like the pill badges (Round 127): an inline-flex box's
     baseline is its digit's baseline, so the digit sits on the row's
     baseline exactly as a pill's digit does -- at every badge size, in
     both tab states. vertical-align: middle put it 0.4px high on a closed
     tab and 3.6px high on the open one. */
  vertical-align: baseline;
}
/* The drawn page (Round 127, Derek: bigger, so the fold does not spill
   over the number): 1.63 x 2.04em, the 24:30 proportions, placed so the
   centre of its BODY BELOW THE FOLD -- 19.75 of its 30 units from the top
   -- sits on the layout box's centre, where the digit is. So the flap
   (the top 9.5 units) clears the digit by about two units, and the digit
   stays where the pills' digits are. */
.tabbtn .tab-count.sheet .badge-page {
  position: absolute;
  /* Round 129 (Derek): a tad lower and a tad larger, with the page's bottom
     never below the note head's. The note's bottom is 1.74em below this
     box's top (the two digits share a centre at 0.875em; the head's radius
     is 0.865em), so top + height = 1.74em is the limit: 2.16em tall (from
     2.04, the 24:30 proportions kept) hung at -0.42em puts the bottom
     exactly there. The digit does not move; the fold clears it by 1.5
     units. Any further lowering or growth needs the note head to move. */
  left: -0.154em;
  /* -0.45em (Round 133): the head shrank 0.03em in height, so its bottom
     rose 0.03em; the page rises with it and the two bottoms stay equal
     (0.875 + 0.835 = 1.71 = -0.45 + 2.16). The digit does not move. */
  /* -0.51em (Round 142): the head is 1.55em tall now, so its bottom is
     0.875 + 0.775 = 1.65em below the box's top; -0.51 + 2.16 = 1.65. The
     page rises 0.06em with it; the digit does not move, so the fold sits
     0.06em further above the digit than before. */
  /* -0.645em (Round 143): the head is 1.28em tall, so its bottom is
     0.875 + 0.64 = 1.515em below the box's top; -0.645 + 2.16 = 1.515. */
  top: -0.645em;
  width: 1.73em;
  height: 2.16em;
}
/* A little lighter than the pill badges (Round 120, Derek): the badge red
   with a touch of paper mixed in. */
.tabbtn .tab-count.sheet .pg-body {
  /* 93%, from 86 (Round 121): a tad darker, halfway back toward the pills. */
  fill: color-mix(in srgb, var(--notify) 93%, var(--paper));
}
/* The fold is LIGHTER than the body (Round 120, Derek, reversing Round
   119): a clear step paler, still the same warm hue. */
.tabbtn .tab-count.sheet .pg-fold {
  fill: color-mix(in srgb, var(--notify) 58%, var(--paper));
}
/* The digits sit on the pill badges' line (Round 127, Derek): measured, a
   pill's digit centres 0.7px above its box's centre, while these boxes are
   vertical-align: middle. No padding on the page's digit (it was pushing it
   1.7px low) and both digits lifted 0.045em -- 0.5px at the closed size --
   put all four digit centres on one line. */
.tabbtn .tab-count.sheet .tab-num {
  position: relative;
  display: block;
  padding-top: 0;
  text-align: center;
  line-height: 1;
  color: var(--on-notify);
}
.tabbtn .tab-count.music .tab-num {
  position: relative;
}
.tabbtn.active .tab-count.sheet {
  box-shadow: none;
}
/* A smaller head (Round 117, Derek): a true circle of 1.45em, sized to the
   badge's type rather than by the pill's padding, so it is the same shape
   at both badge sizes and the tail's geometry holds at both. */
.tabbtn .tab-count.music {
  position: relative;
  /* 0.93em (Round 136): the head grew 0.1em and the flag moved 0.097em
     left with the stem, so the tab keeps its width and the next tab its
     clearance. */
  margin-right: 0.93em;
  vertical-align: baseline;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 1.6em (Round 123, Derek): only slightly smaller than the pill badges,
     which are about 1.74em tall on a closed tab. */
  /* 1.73em (Round 125, Derek): the whole note a tad bigger -- head and
     tail up 8% together; the number keeps its size. A little oval (Round
     130, Derek): 1.92em wide, 11% wider than tall, no tilt; border-radius
     50% makes an ellipse of it. */
  /* And the slightest bit smaller (Round 133): 1.85 x 1.67em, 3.5% down,
     the oval kept. */
  /* A little more oval (Round 136, Derek): 1.95em wide at the same 1.67em
     height -- 17% wider than tall, from 11%. The HEIGHT is what the page
     badge's bottom is tied to (Round 129), so it stays and the page does
     not move; the width grows leftward of the stem. */
  /* More oval AND a little smaller (Round 142, Derek): 1.90 x 1.55em, 23%
     wider than tall, about 9% less area. The height changed this time, so
     the page badge moved with it (.badge-page top -0.51em) to keep the two
     bottoms level; the digit's size and baseline are untouched.
     margin-right is left alone: the tab narrows 0.05em, the next tab keeps
     its clearance. */
  /* Smaller and MUCH more oval (Round 143, Derek): 1.85 x 1.28em, 45% wider
     than tall, about a fifth less area than 1.90 x 1.55. The box is now a
     touch shorter than the digit's own text box (1.32em), which is fine:
     the digit's ink is 0.7em tall and centred, the baseline still comes
     from the digit, and nothing clips. The page badge moved with the
     height (.badge-page top -0.645em). */
  width: 1.85em;
  height: 1.28em;
  padding: 0;
  line-height: 1;
  border-radius: 50%;
}
/* The tail (Round 105): 1em wide, 3em tall, its bottom on the badge's
   bottom, the head's right edge at x = 3.5 of 20 (the stem's outer edge is
   0.2 inside it since Round 136, tangent to the oval). The flag is a shade
   heavier than the stem, as in the sample. */
.tabbtn .tab-count.music .badge-note {
  position: absolute;
  /* All four scaled by 1.08 with the head (Round 125): the viewBox unit is
     still 1/32 of the head, so the tail's geometry holds unchanged. */
  right: -0.891em;
  bottom: 0;
  width: 1.08em;
  height: 3.24em;
  overflow: visible;
}
.tabbtn .tab-count.music .nt-stem,
.tabbtn .tab-count.music .nt-flag {
  fill: none;
  stroke: var(--music);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.tabbtn .tab-count.music .nt-stem {
  stroke-width: 4.4;
}
.tabbtn .tab-count.music .nt-flag {
  stroke-width: 5.6;
}
/* A FILLED, tapered flag (Round 107): the reference flag thickens at the
   stem and thins into a rounded horizontal stub, which a stroke cannot do. */
.tabbtn .tab-count.music .nt-flagfill {
  fill: var(--music);
  stroke: none;
}
.tabbtn.active .tab-count.music {
  box-shadow: none;
}

/* ============ box 3's "Add announcement" composer, on the conducting sheet

   The same card as the Announcements tab's composer, opened on demand inside
   section 3. It is a card within that section's card, so it steps BACK a
   shade — paper rather than card — and reads as something that opened rather
   than as another section of the form. It spans the whole section because
   sheetAnnComposer() puts it outside .annsplit; nothing here has to fight the
   published queue's column for width. */
.sheetcomposer {
  margin: 1rem 0 0;
  background: var(--paper);
}

/* The button stands clear of the checkbox above it (Round 258, Derek: it was
   "too close to Include on DIGITAL WARD PROGRAM"). .composer .btn-primary
   gives 0.3rem, which is right where the button follows a field but too tight
   under a label somebody has just reached across to tick. */
/* 1.7rem, not 0.9 (Round 260): the "Added to program" confirmation hangs
   in this gap, out of the flow, so the space has to be there whether it is
   showing or not -- which is exactly what makes the row never move. */
.sheetcomposer .composer-actions {
  margin-top: 1.9rem;
}

/* THE ONE QUESTION THE FORM CANNOT ANSWER FOR YOU, at the foot of it and
   directly above the button that acts on it. A row of its own rather than a
   bare checkbox against the card (Round 260, Derek): on the card colour
   inside the composer's paper, so it reads as a switch that has been set
   rather than a line of small print. It LIGHTS when it is on -- a sage tint
   and a sage edge -- because this is the field that decides where the
   announcement ends up, and it should be answerable at a glance. Full ink,
   not the row checkbox's soft grey, for the same reason. */
.sheetpub {
  position: relative;
  gap: 0.6rem;
  margin-top: 1rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: var(--card);
  font-size: 0.9375rem;
  color: var(--ink);
  transition: background 0.2s ease, border-color 0.2s ease;
}

.sheetpub:hover {
  border-color: color-mix(in srgb, var(--accent) 35%, var(--line));
}

.sheetpub.on {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  background: color-mix(in srgb, var(--accent) 7%, var(--card));
}

.sheetpub .annpub-t {
  flex: 1 1 auto;
  min-width: 0;
}

/* CALM (Round 260, Derek): a check and three words fading up, holding, and
   going again -- 0.3s in, 1.7s held, 0.6s out. It confirms and then gets
   out of the way, so nothing is left on the form claiming to be a field.
   pointer-events off: it lives inside the label, and a click on it would
   toggle the box it has just confirmed. */
.pubsaid {
  /* OUT OF THE FLOW, in the gap that already sits between the switch and
     the button. Inside the row it wrapped to a second line -- the label
     needs about 280px of a 345px column and this needs 130 -- so the row
     grew 24px on tick and shrank again on the way out, moving the primary
     button under the pointer that was heading for it. That is the jump
     Round 256 spent three fixes removing; it does not get to come back for
     a decoration. Hung under the row instead, it changes no height at all. */
  position: absolute;
  top: calc(100% + 0.3rem);
  right: 0;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.8125rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--success-ink, var(--accent));
  pointer-events: none;
  animation: pubsaid 2.6s ease both;
}

.pubsaid-tick {
  font-size: 0.9375rem;
  line-height: 1;
}

@keyframes pubsaid {
  0% { opacity: 0; transform: translateY(2px); }
  12% { opacity: 1; transform: translateY(0); }
  77% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-1px); }
}

@media (prefers-reduced-motion: reduce) {
  .sheetpub {
    transition: none;
  }
  /* Still shown and still taken away -- animationend is what removes it, so
     it must still run; only the movement goes. */
  .pubsaid {
    animation: pubsaid 2.6s steps(1, end) both;
  }
}

/* WHERE DID IT GO? The box shuts on success, so without this the unpublished
   case would have no answer at all — the sheet's autosave is silent by
   design. It says which of the two things happened, because that is exactly
   what the checkbox above it leaves open. Removed after five seconds by the
   node itself, never by a re-render on a timer. */
.annadded {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-top: 1rem;
  padding: 0.7rem 0.95rem;
  border: 1px solid var(--success-line);
  border-radius: var(--r-item);
  background: var(--success-bg);
  color: var(--success-ink);
  font-size: 0.9375rem;
  animation: annadded-in 0.22s ease-out;
}

.annadded-tick {
  font-weight: 700;
}

@keyframes annadded-in {
  from { opacity: 0; transform: translateY(-0.35rem); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .annadded { animation: none; }
}

/* ------------------------------------------ consider a replacement (R128) */

/* THE SNAPSHOT'S SELECT MODE: a tick before each held title, a tint on a
   picked row, and a bar that follows the reader down the board. The bar
   sticks under the tab bar (--tabbar-h, the Round 54 token) because the
   board is 194 rows and the count and the button must be on screen wherever
   the ticking is happening. z-index 4, under the tab bar (20) and the sheet
   bars (5), like the sheet switch. */
.selbar {
  position: sticky;
  top: calc(var(--actbar-h, 0px) + var(--tabbar-h, 4.2rem));
  z-index: 4;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  padding: 0.55rem 0.85rem;
  margin: 0 0 0.9rem;
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  border-radius: var(--r-item);
  box-shadow: var(--lift);
}

.selbar strong {
  font-size: 0.9rem;
  min-width: 6rem;
}

.wb-pick {
  width: 1rem;
  height: 1rem;
  margin: 0 0.4rem 0 0;
  vertical-align: -0.15rem;
}

.wb-row.is-picked {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

/* A replacement is already being considered for this row. Tag rank, in the
   attention colour: the same fact the queue's pill states. */
.wb-queued {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--notify) 14%, transparent);
  color: var(--notify);
  letter-spacing: 0.08em;
  font-size: 0.6rem;
  text-transform: uppercase;
  font-weight: 700;
}

/* The queue's third pill, between the release's warm and the calling's
   grey: the accent, faint. Size and tracking come from the tag rank. */
.pill-replace {
  text-transform: uppercase;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}

/* THE REVIEW BOX. Wider than the ordinary dialog and allowed to scroll inside
   the window (box-sizing so the cap is the outer edge — Round 29's rule):
   there are six sections and a note thread, and a box taller than the
   window with no scroll of its own is a box whose Approve button cannot be
   reached. Sections are ruled off; the plan is a plain list. */
.modalcard.review {
  max-width: 46rem;
  max-height: calc(100vh - 2.5rem);
  overflow-y: auto;
  box-sizing: border-box;
}

.modalcard.review .modal-body {
  margin-bottom: 0.4rem;
}

.rvsec {
  margin: 0.9rem 0 0;
  padding-top: 0.7rem;
  border-top: 1px solid var(--line);
}

.rvsec > .field-label {
  display: block;
  margin-bottom: 0.45rem;
}

.rvkeep {
  margin: 0.8rem 0 0;
}

.rvfacts .prow {
  padding: 0.28rem 0;
}

.rvfacts .candctx {
  margin: 0.4rem 0 0;
}

.rvnote {
  margin: 0.45rem 0 0.3rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.rvnote.is-warn {
  color: var(--warn-ink, #9a5b2b);
  font-weight: 600;
}

.rvtarget .remember {
  margin: 0.3rem 0 0;
}

/* Facts about a second person inside a section are set back a step, so the
   eye keeps the person the box is about. */
.rvtarget .rvfacts {
  margin-top: 0.5rem;
  padding-left: 0.75rem;
  border-left: 3px solid var(--line);
}

.rvplan {
  margin: 0.2rem 0 0;
  padding-left: 1.2rem;
}

.rvplan li {
  margin: 0.2rem 0;
}

.rvplan li:last-child {
  margin-top: 0.45rem;
  font-size: 0.875rem;
  color: var(--ink-soft);
}

/* The calling picker: a search box over a scrolling list grouped by
   organization, each heading sticking as its group scrolls past. */
.cpick input[type='search'] {
  width: 100%;
  box-sizing: border-box;
}

.cpick-list {
  max-height: 15rem;
  overflow-y: auto;
  margin-top: 0.35rem;
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  background: var(--paper);
}

.cpick-org {
  position: sticky;
  top: 0;
  padding: 0.3rem 0.7rem 0.2rem;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 650;
  color: var(--accent);
}

.cpick-opt {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  padding: 0.38rem 0.7rem;
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  color: var(--ink);
  background: none;
  border: none;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}

.cpick-opt:hover,
.cpick-opt:focus-visible {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}

.cpick-opt.is-vacant .cpick-meta {
  color: var(--success-ink, var(--accent));
}

.cpick-meta {
  color: var(--ink-soft);
  font-size: 0.8125rem;
  white-space: nowrap;
}

.cpick-new {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-right: 0.5rem;
}

.cpick-new .cpick-opt {
  border-bottom: none;
}

.cpick-new select {
  max-width: 12rem;
}

.cpick-chosen {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding: 0.4rem 0;
}

.cpick-picked {
  font-weight: 650;
}

/* The confirmation before queueing: who and what, one ruled row each. */
.rvlist {
  margin: 0.5rem 0 0;
}

.rvlist-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.3rem 0;
  border-top: 1px solid var(--line);
}

/* One change, several rows: the tracker's marker. */
.chainline {
  display: inline-block;
  margin-top: 0.25rem;
  padding-left: 0.5rem;
  border-left: 3px solid color-mix(in srgb, var(--accent) 55%, transparent);
  font-size: 0.72rem;
  color: var(--ink-soft);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: default;
}

/* MEMBERS: the derived "Has a calling" in the availability column — words,
   not a control, because it is a fact from the Snapshot and not an answer
   anyone gives here — and the hover card behind it and behind the name. */
.mhascalling {
  display: inline-block;
  padding: 0.28rem 0.6rem;
  font-size: 0.875rem;
  color: var(--ink-soft);
  border-radius: var(--r-item);
  border: 1px dashed transparent;
  cursor: default;
}

.mhascalling:hover,
.mhascalling:focus-visible {
  border-color: var(--line);
  color: var(--ink);
}

/* Above the page (the sticky table header is 2, the sheet bars 5) and below
   every dialog (50): a card must never sit on top of a box that is asking a
   question. Fixed, on <body>, so no scroll container can clip it. */
/* THE DATE CHIPS' LABELS (Round 175 for the star; Round 250 for the check
   and the two counts): a small card on the paper with the hairline and the
   modal's lift, and a caret beneath it pointing at the mark. Fixed on
   <body> and above the tab bar (20), the preview bar (5) and the account
   menu (45); below the dialogs (50) and the toast (100).
   NO CURSOR OF ITS OWN (Round 250, Derek: no question mark beside the
   pointer) -- a mark sits inside a button and takes the pill's pointer. */
.chipstar {
  display: inline-block;
}

/* The lead-in on a badge's hover card (Round 274): the page's subordinate
   small-caps voice, and deliberately NOT bold -- the number beside it is the
   only bold thing in the card. */
.tip-lede {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 400;
  opacity: 0.85;
}

.startip {
  position: fixed;
  z-index: 48;
  display: inline-flex;
  align-items: center;
  padding: 0.5rem 0.8rem;
  background: var(--card);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  box-shadow: 0 6px 18px rgba(20, 16, 10, 0.12), 0 1px 3px rgba(20, 16, 10, 0.08);
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.005em;
  white-space: nowrap;
  pointer-events: none;
}

/* THE PREVIEW CONTROLS' LABELS (Round 220): the same card, a size down,
   with the caret on whichever side faces the control -- beneath it for a
   label above the swap, at the left for "Remove" beside the x; flipped
   (below, or to the left) when the window has no room that way. */
.startip.pvtip {
  font-size: 0.8rem;
  padding: 0.38rem 0.65rem;
}

.startip.tip-right::after,
.startip.tip-below::after {
  bottom: auto;
  left: auto;
  border: none;
}

.startip.tip-right::after {
  left: -6px;
  top: calc(50% - 5px);
  border-left: 1px solid var(--line);
  border-top: 1px solid var(--line);
  transform: rotate(-45deg);
}

.startip.tip-below::after {
  left: calc(var(--caret-x, 50%) - 6px);
  top: -6px;
  border-left: 1px solid var(--line);
  border-top: 1px solid var(--line);
  transform: rotate(45deg);
}

.startip::after {
  content: "";
  position: absolute;
  left: calc(var(--caret-x, 50%) - 6px);
  bottom: -6px;
  width: 10px;
  height: 10px;
  background: var(--card);
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  transform: rotate(45deg);
}

/* Round 240 removed the "where the music stood" row from the live preview
   (.cs-removed / .cs-removed-line) -- Derek: the note belongs on the left
   panel, not on the picture of the program. Its rules went with its one
   emitter; the note itself is styled by .hymnslot-gone, further down. */

/* THE INTERMEDIATE SLOT (Round 220): its label shares a row with a small
   chooser -- Intermediate hymn / Musical number -- drawn as a quiet pill;
   a musical number shows as one summary line with its edit link; a piece
   taken off the program leaves one dashed line saying who did it, with
   the way back. */
.hymnslot {
  margin-bottom: 0.6rem;
}

.slothead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.2rem;
}

.slothead .field-label {
  margin: 0;
}

body.leader .sheetform select.slotkind {
  width: auto;
  min-height: 0;
  padding: 0.12rem 1.5rem 0.12rem 0.55rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  border-radius: 999px;
  color: var(--ink-soft);
  background-color: var(--paper);
  border-color: var(--line);
}

/* (Round 220 summarised the musical number as one line with an "Edit details"
   link opening a modal. Round 262 replaced both with two boxes on the form
   itself, so .slotsummary* went with them. Grepped src/ and public/ first:
   no emitter anywhere.) */

.hymnslot .slotoff.quiet-inline {
  display: inline-block;
  margin-top: 0.2rem;
}

.hymnslot-gone {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.7rem;
  margin: 0.15rem 0 0;
  padding: 0.45rem 0.65rem;
  font-size: 0.85rem;
  font-style: italic;
  color: var(--ink-soft);
  border: 1px dashed var(--line);
  border-radius: var(--r-item);
}

.hymnslot-gone-text {
  flex: 1 0 100%;
}

/* Who and when, in the app's own signed-note voice: upright, a size down,
   so the sentence above reads as the note and this as the signature. */
.hymnslot-gone-when {
  font-style: normal;
  font-size: 0.78rem;
}

.hymnslot-gone .quiet-inline {
  font-style: normal;
}

/* THE ITEM BOXES (Round 220): a narrower card than the ordinary dialog,
   a lead line in soft ink where one is wanted, the kind chooser as the
   segmented control the Members tab uses, and the Youth row with its
   one-line reason beneath the word. */
.modalcard.progbox {
  max-width: 26rem;
}

/* body.leader, or the desktop card rule (body.leader .acard > h2, 0,2,2)
   outranks this and the heading stays 1.3rem (the Round 44 trap). */
body.leader .modalcard.progbox h2 {
  font-size: 1.15rem;
  margin-bottom: 0.85rem;
}

.modal-lead {
  margin: -0.35rem 0 0.85rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.modalcard .progkinds {
  margin: 0 0 0.9rem;
}

.modalcard .progkinds .sheettab {
  font-size: 0.8125rem;
  padding: 0.34rem 0.75rem;
}

.modalcard .progfields .field {
  margin-bottom: 0.7rem;
}

.modalcard .progyouth {
  align-items: flex-start;
  margin-top: 0.1rem;
}

.progyouth-text {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
}

.progyouth-hint {
  margin: 0;
  font-size: 0.78rem;
}

.hymnpair .cols.hymn {
  gap: 0.6rem;
}

/* THE THIN COLUMN'S DENSITY (Round 220, Derek: "rows/text in the left
   column were spaced too much vertically and the text was too large").
   Scoped to .sheetform and written with body.leader so it beats the
   desktop density block (0,2,2): the card, its heading, the labels, the
   boxes, the rows and the buttons each come down a step, so the whole
   sheet is read with less scrolling while the boxes stay 14px and the
   labels 11px -- still comfortable at arm's length. Every other tab keeps
   the ordinary density. */
body.leader .sheetform .acard {
  padding: 0.8rem 0.9rem;
  margin-bottom: 0.7rem;
}

/* The no-new-hymn notice sits between two cards now (Round 247), so it keeps
   the column's own rhythm rather than the 1rem it carried at the head of the
   page. */
body.leader .sheetform .notice-nonew {
  margin-bottom: 0.7rem;
}

body.leader .sheetform .acard > h2 {
  font-size: 1.02rem;
  margin: 0 0 0.6rem;
}

.sheetform .secnum {
  margin-right: 0.35rem;
}

.sheetform .field {
  margin-bottom: 0.55rem;
}

.sheetform .field-label {
  font-size: 0.7rem;
  margin-bottom: 0.18rem;
}

body.leader .sheetform input[type='text'],
body.leader .sheetform input[type='date'],
body.leader .sheetform select,
body.leader .sheetform textarea {
  /* Written LONG, with no padding-right: the save tick's reserved room
     (body.leader .tickwrap > input, Round 125) is the same (0,3,2) and
     earlier in the file, so a shorthand here would wipe it. */
  padding-top: 0.34rem;
  padding-bottom: 0.34rem;
  padding-left: 0.5rem;
  font-size: 0.875rem;
}

/* A select in the thin column takes its track's width, never its widest
   option's (Round 30's pair): the pulpit lists' "Someone else, just this
   Sunday" would otherwise push past the card. */
body.leader .sheetform select {
  width: 100%;
  min-width: 0;
  text-overflow: ellipsis;
}

.sheetform .cols {
  gap: 0.5rem;
}

.sheetform .hint {
  font-size: 0.8rem;
  margin-top: 0.2rem;
}

.sheetform .btn {
  min-height: 2.1rem;
  padding: 0.35rem 0.85rem;
  font-size: 0.875rem;
}

.sheetform .btn.slim {
  min-height: 1.95rem;
  padding: 0.28rem 0.75rem;
  font-size: 0.8125rem;
}

.sheetform .addrow {
  gap: 0.4rem;
  margin-top: 0.3rem;
}

.sheetform .quiet-action,
.sheetform .applied-note {
  font-size: 0.8rem;
}

.sheetform .remember {
  font-size: 0.875rem;
}

.sheetform .meetingtype {
  margin-bottom: 0.6rem;
}

.sheetform .musicnames {
  padding-top: 0.6rem;
  margin-top: 0.55rem;
}

.sheetform .standread-item {
  /* The right holds the arrow gutter open (Round 284). */
  padding: 0.45rem 4rem 0.45rem 1.6rem;
}

.sheetform .standread-item::before {
  top: 0.7rem;
}

.sheetform .standread-item:first-child::before {
  top: 0.25rem;
}

.sheetform .annfoot {
  margin-top: 0.2rem;
}

.sheetform .suggestrow {
  margin-bottom: 0.6rem;
}

.sheetform .suggestbtns {
  gap: 0.35rem 0.4rem;
}

.sheetform .hymnundo {
  font-size: 0.72rem;
}

.sheetform .misschip {
  font-size: 0.78rem;
  padding: 0.12rem 0.55rem;
}

.sheetform .readiness-label {
  font-size: 0.7rem;
}

.sheetform .readiness {
  gap: 0.28rem;
}

.hovercard {
  position: fixed;
  z-index: 45;
  max-width: 22rem;
  padding: 0.6rem 0.8rem;
  background: var(--card);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--r-item);
  box-shadow: 0 8px 24px rgba(20, 16, 10, 0.16);
  font-size: 0.875rem;
  line-height: 1.4;
  pointer-events: none;
}

.hovercard p {
  margin: 0;
}

.hovercard .hovercard-name {
  font-weight: 650;
  margin-bottom: 0.25rem;
}

.hovercard .hovercard-line + .hovercard-line {
  margin-top: 0.2rem;
}

.hovercard-sub {
  display: block;
  color: var(--ink-soft);
  font-size: 0.8125rem;
}

.hovercard-l {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.68rem;
  font-weight: 650;
  color: var(--ink-soft);
}

/* The finished mark on the Conducting sheet tab (Derek): the SAME .chipdone
   the later Sundays' date chips carry, so the two places that say "this sheet
   is done" say it with one mark. Only the spacing from the label is the tab's
   own -- 0.3rem, the gap the count badges beside it use.
   It does NOT grow on the open tab the way a count does, and does not dim on a
   closed one: a count is a thing waiting to be done and earns the attention,
   whereas this says there is nothing to do. */
.tabbtn .chipdone.tabdone {
  margin-left: 0.3rem;
  vertical-align: baseline;
}

/* ---------------- working inside somebody else's profile (Round 229)

   The bishop can open any account and use the app exactly as that person
   does -- their tabs, their permissions, their data, and edits saved as
   theirs. Everything below this strip is telling him he is them, so the
   strip has to be the one thing that cannot be mistaken or missed: full
   width, the attention colour, and STICKY, because it carries the only way
   back and a banner that scrolls away takes the exit with it.

   z-index 30 sits above the tab bar (20) and below the dialogs (50) and the
   toast (100) -- a modal should cover it, since a modal is the thing being
   answered. */

.actbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.35rem 0.9rem;
  padding: 0.5rem 1rem;
  background: var(--notify);
  color: var(--on-notify, #fffdf8);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.16);
}

.actbar-who {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.actbar-label {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.6875rem;
  font-weight: 700;
  opacity: 0.82;
}

.actbar-name {
  font-size: 1rem;
  font-weight: 650;
}

.actbar-role {
  font-size: 0.8125rem;
  opacity: 0.82;
}

/* Paper on the terracotta rather than an outline: it is the one action on
   the strip and the only route out of a borrowed profile, so it should read
   as a button at a glance. */
.actbar-back {
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  padding: 0.4rem 0.8rem;
  border: 0;
  border-radius: var(--r-item);
  background: var(--on-notify, #fffdf8);
  color: var(--notify);
  cursor: pointer;
}

.actbar-back:hover {
  filter: brightness(0.94);
}

/* The Admin row's way in. A word rather than another icon: the three beside
   it are routine and this one changes who the whole app thinks you are. */
.actas {
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  font-style: normal;
  line-height: 1;
  padding: 0.3rem 0.55rem;
  margin-right: 0.35rem;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  border-radius: var(--r-item);
  background: none;
  color: var(--accent);
  cursor: pointer;
  vertical-align: middle;
}

.actas:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

@media print {
  .actbar {
    display: none !important;
  }
}

/* ==========================================================================
   Round 231 -- THE WARD'S PAGE. Derek: "These pages are the hub for every
   member's gateway to the ward's most important information and social
   connection ... re-design these pages to be extremely engaging, and bring
   joy to the users every time they arrive."

   THE ONE DECISION EVERYTHING ELSE HANGS OFF: the three tabs share one
   material and change their FORME, not their world. The panels swap in place
   under a masthead that never moves, so if the Program were aged paper and
   Announcements were a modern app, the paper itself would change under the
   reader's thumb on every tap -- which reads as a fault, not as a style. So:
   one paper, one ink, one type case, one ornament, three arrangements. The
   Program is set as an order of service, the Announcements as a notice
   column, the Groups as a printer's trade cards.

   EVERYTHING IS IN ONE APPENDED SECTION on purpose. It is the largest visual
   change the public page has had, and one block is one thing to read, one
   thing to revert, and -- being last in the file -- it overrides earlier
   rules at equal specificity without editing them out from under the rounds
   that argued for them.

   IT COSTS ZERO NEW BYTES OVER THE WIRE. No new font: public/caslon.woff2
   (Libre Caslon Display, OFL, 24KB) is already fetched on every load for two
   lines of masthead, and Caslon is the type American printers were setting
   when this ward was founded, so the period voice was already paid for. The
   paper is drawn, not downloaded -- see --grain below.

   THE PUBLIC PAGE AND ITS REPLICA. Every selector here is either unscoped or
   scoped to body:not(.admin) plus .wp-paper, so the LIVE PROGRAM tab in the
   leader app -- which renders these same classes inside .wp-paper -- gets the
   design for free. .wrap is deliberately never touched: the leader app shares
   it at 84rem.
   ========================================================================== */

:root,
.wp-paper:not(.is-dark) {
  /* The display voice. Caslon Display is a DISPLAY cut with fine hairlines:
     it is used at 1.05rem and above, and never in italic (there is no italic
     file, and a synthesised oblique on a display face looks broken). Running
     text stays in the book stack, which costs nothing and reads better small. */
  --display: 'Ward Caslon', 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, serif;
  /* The reading face, exactly as body declares it. A token because more than
     one rule now has to set the book face back on something that inherited
     the display face from its parent (.mtheme-l, Round 318), and two copies
     of a stack is two places for it to drift. */
  --body: 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, 'Times New Roman', serif;

  /* AGED PAPER IN ABOUT 700 BYTES AND NO NETWORK REQUEST. Two SVG
     fractal-noise tiles as data: URIs -- permitted by the site's own CSP
     (img-src 'self' data:), which is exactly why the paper is drawn rather
     than fetched. --grain is the tooth of the stock at 180px; --mottle is the
     slow foxing of an old sheet at 520px, large enough that the tile does not
     read as a repeat inside a 40rem column. Both are MULTIPLIED INTO the
     cream rather than laid over the page, so no text sits on top of them and
     every contrast ratio moves by a fraction of a point. The alphas are baked
     into the SVG (0.13 / 0.14) because a background layer cannot carry its
     own opacity; they were chosen against six candidates rendered side by
     side -- 0.25 reads as grey and 0.35 as dirt. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23f)' opacity='0.08'/%3E%3C/svg%3E");
  --mottle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='520' height='520'%3E%3Cfilter id='m'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.028' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='520' height='520' filter='url(%23m)' opacity='0.07'/%3E%3C/svg%3E");
  --grain-blend: multiply, multiply;

  /* THE STOCK THE GRAIN IS PRINTED ON, and the reason the page costs nothing
     in contrast. Multiplying the two tiles into the cream darkens it by about
     2%, which is enough to drag the sage accent from 4.51:1 to 4.31:1 -- under
     AA, on the colour every small-caps label, every date line and every link
     is set in. The accent has NO headroom today (4.51 against a 4.5 floor), so
     any visible texture on a near-white ground breaks it: you cannot lighten
     paper that is already almost white, so texture necessarily darkens.
     Rather than restate a brand colour 230 rounds of iteration have settled,
     the stock is lightened by exactly what the grain takes away. Measured by
     compositing the real tiles onto the real colour in a canvas: #FFFBF3
     under this grain averages rgb(250,246,238), which IS #FAF6EE. Accent
     4.50:1, ink 14.81:1, soft ink 5.56:1 -- the page's numbers today.
     Dark mode needs no compensation: the grain is screened there, and the
     accent sits at 7.19:1 against a 4.5 floor, so the small lift reads as
     paper catching lamplight and costs nothing. */
  --paper-tex: #fffbf3;

}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    /* Multiply on a dark ground buries the grain; screen lifts it, so the
       stock still has tooth when the lamp is low. */
    --grain-blend: screen, screen;
    --paper-tex: #1f1b15;
  }
}

:root[data-theme='dark'],
.wp-paper.is-dark {
  --grain-blend: screen, screen;
  --paper-tex: #1f1b15;
}

/* THE PAPER. On the page itself, and on the replica's column in the leader
   app. body:not(.admin) keeps it off the leader app, whose body carries
   the classes admin and leader. */
body:not(.admin),
.wp-paper {
  background-color: var(--paper-tex);
  background-image: var(--mottle), var(--grain);
  background-size: 520px 520px, 180px 180px;
  background-blend-mode: var(--grain-blend);
}

/* -------------------------------------------------------- the letterhead */

/* THE ENGRAVING KEEPS NO FRAME, and the letterpress highlight is gone with
   it. Both were tried and both were measured out:
   - .engraving img carries a radial mask fading all four edges to transparent
     (Round 20: "or the plate ends in a hard rectangle sitting on top of the
     paper"). A hairline box around a vignetted plate gives empty corners and
     a floating blob, and in dark mode it would frame a screened ghost.
   - --press-hi was a 1px rgba(255,255,255,0.6) shadow under the display
     letters. Composited over the grained cream it lifts the ground by about
     (3, 5, 10) of 255 under a one-pixel line; rendered at 64px and at the
     masthead's own 33.6px, side by side with and without, there is no visible
     difference at all. Round 44 names the shape: a rule that looks like a
     deliberate difference and is a no-op is worse than no rule, because the
     next reader believes it. A genuine impression on cream wants a DARK
     hairline below the glyph, not a light one, which is a different
     experiment and not this round's. */

/* ------------------------------------------------------- the program tab */

/* THE DOTTED LEADER MEANS EXACTLY ONE THING.
   It was spent twice: the four officer rows and the eight program rows used
   the identical .row and .dots construction, so the page showed twelve dotted
   rows of which four meant "who is running this" and eight meant "what
   happens next" -- one idiom carrying two meanings, which is why the credits
   and the order of service read as one undifferentiated list. The officers
   are joined by a middot instead. NO MARKUP CHANGED: .dots is repurposed as
   that middot, which keeps every wrapping fix Round 43 measured on a 320px
   phone.

   The row itself is display: contents, so its label and its value are laid
   out by .officers as two columns (Round 300) rather than as a centred line
   of its own. */
.officers .row {
  display: contents;
}

/* The .dots element still holds its place in the markup; it simply draws
   nothing -- and being display: none it is not a grid item either, so the
   two columns above see exactly the label and the value. */
.officers .dots {
  display: none;
}

/* THE MIDDOT IS GONE (Round 310, Derek, circling one: "remove the dots
   before the names in these four initial sections"). It rode on the value
   from Round 300 on -- ".officers .row-value::before" -- after Round 231
   put it there instead of the dotted leader the eight program rows use, to
   stop the officer credits reading as one more row of "what happens next".
   That reasoning was about which idiom the row uses, not about needing a
   character between the two columns: .officers already sets a 0.55rem
   column-gap (Round 300's own grid), which is what now separates a label
   from its name. */

/* A NAME IS SET IN THE DISPLAY FACE WHEREVER IT APPEARS (Round 286, Derek:
   "that font doesn't match anything on the page and looks really weird").
   These four were the only names on the program not in Caslon -- the
   speakers, the hymn numbers and the hymn titles all took it in Round 231
   and the officers were simply not carried across, so the difference was a
   leftover rather than a decision. Above eight names in one face, four in
   another read as an accident.

   THE SIZE MATCHES THE PROGRAM'S NOW (Round 303, Derek, with the four names
   circled on his phone: "make the text size of these names the same size as
   the other names on the program"). Rounds 286 and 290 had kept these a step
   under -- 1rem, then 1.1rem -- arguing the credits should read as lighter
   than what happens in the meeting; Derek reversed that call directly, so
   level is what is here. THE ACTUAL SIZE IS SET BELOW, in the --ds block
   (Round 299) -- that rule is more specific and wins regardless of source
   order, so this declaration is kept in step with it for anyone reading THIS
   comment rather than believing it while the page does something else
   (Round 299's own lesson: the base has to be the rule that actually wins). */
.officers .row-value {
  margin-left: 0;
  text-align: left;
  max-width: none;
  font-family: var(--display);
  font-size: 1.35rem;
}

/* THE NAME IS THE NEWS. The label is the same eight words every week; the
   name is what the reader came for, so it takes the display face at a size
   where a display cut is at its best. */
.program-list .row-value {
  font-family: var(--display);
  font-size: 1.35rem;
  line-height: 1.35;
}

/* The work's title, in the display face and a step UNDER the name that
   introduces it -- it was 1.25rem against the name's 1.15, which put the hymn
   above the speaker in the one list where the person is the news. */
.item-sub {
  font-family: var(--display);
  font-size: 1.3rem;
}

/* THE SUB-LINE IS ONE SLOT, ONE FACE, TWO SLOPES (Rounds 268 and 292).
   Round 268 made the whole slot upright Caslon, because a musical number's
   details used to be italic in the BOOK face -- so a hymn's title and a
   number's details sat one above the other in the same slot in two
   different faces at two different slopes. That half still stands: the two
   never differ by FACE.

   Round 292 is Derek asking for the hymn names italic, which is the case
   Round 268 itself named as defensible: "italic would be defensible if this
   line were always a work's title, as a hymn's is". A hymn title IS a work's
   title; a musical number's line is a free "Details (optional)" box (Round
   262), where "Accompanied by the Primary" set in italic reads as emphasis
   nobody asked for. So .hymn slants and .music does not.

   THE ITALIC IS SYNTHESISED, and that was measured rather than assumed:
   public/caslon.woff2 is one face (normal/400), so the browser shears it --
   the same string measures 550.4px upright and 550.4px italic at 40px, which
   is the give-away, against 592.7px for the book face's real italic.
   Rendered at 18.4px and at 40px beside a real italic it reads cleanly, and
   one face sheared beats two faces in one slot. A real Libre Caslon italic
   would be a second ~25KB download on the page members open in the chapel;
   Round 23 declined that cost for the group names and it is Derek's call.

   THE PRINTED SHEET IS DELIBERATELY NOT CHANGED. There .cs-subline borrows
   .cs-otherdetail, which is italic soft ink -- and so is every hymn value on
   that sheet (.cs-value.cs-hymn, Round 191, Derek's own instruction that they
   match the sacrament line). Italic is the house voice for a sub-line there,
   so making it upright would break the consistency this fixes on the page.

   The music class is kept on the markup in worker.js and in app.js's
   replica: it says what the line is, and it is the hook the two now differ
   on. The hymn class was added in Round 292 for the same reason -- neither
   line can be told from the other by anything but its class. */
.item-sub.hymn {
  font-style: italic;
  /* CENTRED ON ITS OWN ROW (Derek). The dotted row above it puts the label
     hard left and the number hard right; the title centred beneath sits
     between them rather than lining up under the label, which read as a
     second, unlabelled left-hand column. An "other" item's detail is
     already centred, by .ceremony-block, so the two agree. */
  text-align: center;
}

/* THE MEETING STOPS HERE, AND SO DOES THE PAGE.
   Round 47 marked the sacrament with space rather than paleness, which was
   right; space PLUS a pair of rules is what a job printer would actually have
   done to the central act. The rules are drawn on the block rather than the
   title, so the title and any detail stay one unit. */
/* AN ORDINARY CEREMONY SITS CLOSER TO THE ROWS AROUND IT than the
   sacrament does (Round 348, Derek: decrease the space above and below
   Ward Business, "keep the spacing above and below the administration of
   the sacrament the same"). That is the right hierarchy and not only his
   preference: the sacrament is the meeting's central act and the page
   already treats it as the focal point (Round 231), where Ward Business
   is an administrative interlude between two dense rows.

   Told apart by the marker syncBulletin sets, never by the label. */
.ceremony-block {
  padding: 0.75rem 0;
}

.ceremony-block.is-sacrament {
  padding: 1.15rem 0;
}

.ceremony-block::before,
.ceremony-block::after {
  content: '';
  display: block;
  width: 34%;
  margin: 0 auto;
  border-top: 1px solid var(--dots);
}

/* THE RULES SIT CLOSE TO THE WORDS (Round 346, Derek: "bring both of those
   lines in closer to the text; there is just a lot of empty space there").
   1.15rem to 0.6rem, so about 19px of gap becomes about 10px and the three
   read as one object. The block own padding is untouched: that is what
   holds the whole ornament off the program rows, which is a different gap
   and the one Round 231 tuned. */
.ceremony-block::before {
  margin-bottom: 0.6rem;
}

.ceremony-block::after {
  margin-top: 0.6rem;
}

/* THE COMPLETE SETTING, and the only one. It used to be listed in the
   rank-1 small-caps block at the top of this file and then overridden here
   for three of that rank's four values, so the list said one thing and the
   page did another (Round 44's shape). It is off that list now: the sacrament
   line is a display-face inscription, not a label. */
.ceremony {
  font-family: var(--display);
  font-size: 1.15rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  line-height: 1.35;
  text-transform: uppercase;
}

/* The month's theme, set as an inscription rather than as another sentence.
   Roman, not italic -- the display face has no italic, and centred Caslon
   under a small-caps label already reads as a quotation.

   THE NAME RANK, which is 1.35rem since Round 332 (.program-list
   .row-value, .officers .row-value, .miss-name): the theme is that rank,
   one short line of somebody's chosen words.

   ROUND 318 MEANT TO SET THIS AND HALF OF IT NEVER LANDED. It found the
   theme at 1.22rem against the name rank's 1.2 -- 19.52px and 19.2px, a
   third of a pixel apart, which is not a rank, it is a number nobody chose
   -- and corrected this declaration while leaving the --ds restatement at
   1.22rem, so the page went on rendering 19.52px for fourteen rounds while
   the source said otherwise. Round 332 set both to 1.35rem. It is the same
   lesson Round 303 paid for from the other direction. */
.mtheme {
  font-family: var(--display);
  font-style: normal;
  font-size: 1.35rem;
  line-height: 1.4;
}

/* THE LABEL GOES BACK TO THE BOOK FACE (Round 318's census). It is a child
   of .mtheme, so it was inheriting var(--display) -- the ONLY small-caps
   label on the whole site set in Caslon, against a dozen (.row-label,
   .bdays-h, .bday-when, .gcard-join, .hcontents-h ...) set in the book
   face. Nobody chose that; it fell out of the parent. The rule on
   this page is that Caslon is a NAME or a TITLE and the book face is
   everything else, labels included. */
.mtheme-l {
  font-family: var(--body);
}

/* ------------------------------------------------------- the groups tab */

/* "FIND YOUR PEOPLE" WAS INVISIBLE, and that is a bug rather than a taste
   call. .panel-h is clipped to one pixel on every panel, because on the other
   two the tab above already says the same word -- but this tab is labelled
   "Groups" and its heading reads "Find Your People", so the warmest line the
   ward owns was the one line nobody could read. Unclipped here only, and set
   as the page's one real display heading. */
/* TWO OF THE FOUR PANELS SHOW THEIR OWN HEADING, IN ONE RULE. Groups
   (Round 231, "Find Your People") and History (Round 293, "The Oldest Ward
   in Provo"). Written ONCE: the History copy that used to sit in its own
   section was this rule restated line for line, and two copies of one
   heading is how the ranks drift. The Program panel keeps its heading
   clipped -- the masthead above it is that panel's heading -- and so does
   the Bulletin, whose visible heading is .annhead over the announcements
   themselves rather than over the whole tab (Round 318). */
.groups-panel > .panel-h,
.history-panel > .panel-h {
  position: static;
  width: auto;
  height: auto;
  overflow: visible;
  clip-path: none;
  margin: 0 0 0.6rem;
  font-family: var(--display);
  font-size: 2rem;
  font-weight: 400;
  line-height: 1.15;
  text-align: center;
}

/* THE ANNOUNCEMENTS' OWN HEADING (Round 318, Derek: "There should be a
   large 'Announcements' above Ward, Stake, Church-wide"). The two panels
   above put their heading at the top and name the whole tab; this one sits
   UNDER the birthdays and names one section of the Bulletin, so the tab's
   own word and this one never contradict each other. Same rank and face as
   those two, so the four tabs' headings read as one system. */
.annhead {
  margin: 0 0 0.9rem;
  font-family: var(--display);
  font-size: 2rem;
  font-weight: 400;
  line-height: 1.15;
  text-align: center;
}

/* After the birthdays' closing rule it wants real air; at the top of the
   tab (a week with no birthdays) it sits where the other panels' headings
   do and needs none. The 1.3rem under .bdays collapses into this. */
.bdays + .annhead {
  margin-top: 2.2rem;
}

/* --------------------------------------------------------------- on paper */

/* Paper has no need of printed paper. The grain and every pressed highlight
   come off, or a printer lays the noise down as a grey wash over the sheet.
   This block sits LATER in the file than the public page's other print rules,
   so it wins; do not move it above them. */
@media print {
  body:not(.admin),
  .wp-paper {
    background-image: none;
  }

}

/* ------------------------------------------- ward, stake and Church-wide */

/* Derek: "have subtly different sections for classifying if an announcement
   or activity is related to the Ward, Stake, or is Church-wide."

   SUBTLY is the word that decided the treatment. Not a coloured chip on every
   notice -- that would run three competing colours down a column whose whole
   design is one ink on one paper, and it would mark every notice on an
   ordinary week when they are all ward notices anyway. This is the
   compositor's answer instead: the words held between two rules, which is how
   a printer divides a notice column, in the page's own label voice.

   IT APPEARS ONLY WHEN MORE THAN ONE GROUP HAS ANYTHING IN IT (Derek's rule,
   and the right one). So the common week is exactly as it was, and the moment
   a stake or Church-wide notice arrives the divisions appear.

   The head is a p holding one span so the rules can be flex children either
   side of the words. A span with a background sitting on a full-width rule
   was the other way to draw this and is wrong here: the paper carries a
   grain, so an opaque patch behind the words would show as a clean rectangle
   in the middle of the sheet. */
/* THE SCOPE NAVIGATOR (Round 297, Derek). Three quiet words pinned to the
   top of the screen while the notices scroll under them.

   MINIMALIST, UNDER A HEADING THAT OUTRANKS IT (Round 317, Derek: "There
   should be a large 'Announcements' above Ward, Stake, Church-wide. I feel
   like those three buttons should be more minimalist."). Round 316 had
   given this strip the tab row's Caslon at the program names' size, which
   was right while it was the first thing on the tab; under a 1.85rem
   display heading the same words are a second headline. So: the BOOK face
   at 0.9375rem -- the reading face, a size under the body, the way a
   section's contents line is set in an editorial page -- soft ink, mixed
   case, no box, and NO GROUND AT REST. The current one is the accent with
   a 1.5px rule under the word. Three words and one underline; nothing
   else.

   THE GROUND COMES ONLY WHEN IT IS PINNED (.is-stuck, set by the scroll spy
   in worker.js's head script the moment the sticky strip reaches top 0).
   At rest the strip used to be a flat lighter rectangle on the grained
   paper -- the thing Derek pointed at in Round 316 -- and there is nothing
   for it to do there: the words sit on the page like any other line. Once
   the notices scroll under it, it takes the paper ground and a hairline
   beneath, or the words would read straight through the notice below. The
   LIVE PROGRAM replica and the composer preview never scroll the window
   and never get the class, which is right: they are pictures of the page
   at rest. The tab row above keeps its own tracked small caps and outlined
   box (Round 316, .tabs near the top of this file). */
.annnav {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  justify-content: center;
  /* THE ROW GIVES WAY, NOT THE WORDS. At 320px with the browser's text at
     150% the three words want more than the column has, and without this the
     strip pushed the whole page sideways -- measured 10px of overflow before,
     none after. The words hold their own lines (nowrap below) and the row
     wraps to two instead. */
  flex-wrap: wrap;
  gap: 0 1.6rem;
  background: transparent;
  border-bottom: 1px solid transparent;
  margin: 0 0 0.4rem;
  padding: 0.15rem 0 0.15rem;
}

.annnav.is-stuck {
  background: var(--paper-tex, var(--paper));
  border-bottom-color: var(--line);
}

.annnav-item {
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.3;
  color: var(--ink-soft);
  text-decoration: none;
  padding: 0.35rem 0.1rem;
  /* Church-wide carries a hyphen, which is a break opportunity: without this
     it splits across two lines inside its own item. */
  white-space: nowrap;
}

.annnav-item:hover,
.annnav-item:focus-visible {
  color: var(--accent);
}

/* THE CURRENT ONE: the accent, and the rule under the word -- an underline
   that hugs the word, in currentColor so it can never be a different green
   from the word above it. Colour and the rule, never a weight change: a
   bolder current word would widen and shift its two neighbours on every
   scroll. */
.annnav-item.is-here {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 0.28em;
}

.annscope {
  /* A jump lands the head clear of the sticky strip above it. In the markup
     rather than in the script, so the anchors work with no JavaScript. */
  /* Clear of the strip even when it has wrapped to two rows at the largest
     browser text size; in rem, so it grows with the strip it is clearing. */
  scroll-margin-top: 5.2rem;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  /* Its own top margin collapses with the preceding notice's 1.3rem, so the
     rule that closes the group above stands about 2.4rem clear of the head --
     enough that the two do not read as a pair of rules. */
  margin: 2.4rem 0 1.35rem;
}

.annscope::before,
.annscope::after {
  content: '';
  flex: 1 1 auto;
  border-top: 1px solid var(--dots);
}

.annscope span {
  flex: 0 0 auto;
  color: var(--accent);
}

/* The first head, directly under whatever opens the section: the
   announcements' heading, or the navigator when there is one. (.panel-h is
   clipped to a pixel and out of flow, so it was the adjacent sibling before
   Round 318 gave the section a visible heading of its own.) */
.panel-h + .annscope,
.annhead + .annscope,
.annnav + .annscope {
  margin-top: 0.5rem;
}

/* ------------------------------- the chooser, in the leader app's editors */

/* The segmented control from the conducting sheet (.sheettabs), a size down
   so three words fit a 24rem form column, and allowed to wrap rather than
   push the card wide on a phone. */
.scopetabs {
  flex-wrap: wrap;
  max-width: 100%;
}

/* CENTRED IN THE TWO ADD-ANNOUNCEMENT BOXES (Round 259, Derek), label and
   control together -- his mark took in both. Scoped to .composer, so the
   row editor for a notice already published keeps it left, in line with
   every other field it sits among. .sheettabs is inline-flex, so centring
   the field is all it takes. */
.composer .scopefield {
  text-align: center;
}

.scopetabs .sheettab {
  font-size: 0.8125rem;
  padding: 0.34rem 0.75rem;
}


/* THE INVITATION TO START ONE (Round 231, Derek's own wording). It used to be
   the tail of the paragraph at the top of the tab, where it was read by
   everybody scanning the cards and acted on by nobody: the button it refers to
   was a screen away, below every group. It sits on the button's own shelf now,
   which is the moment somebody has looked through the groups and not found
   theirs. The lead above the cards is shorter for it, and says the one thing a
   member needs before reading them: just turn up. */
.gstart-lead {
  margin: 0 0 0.9rem;
  color: var(--ink-soft);
  max-width: 34rem;
}

/* --------------------------------------------------- the Today seal */



/* ------------------------------------------- your name on your birthday */

/* Derek asked for the pages to bring a smile. This is the cheapest joy on the
   site: on the morning of your birthday your own name is picked out on the
   ward's public page, in the ward's own ornament -- the same diamond as the
   fleuron and the printed program's rule. Both renderers already knew which
   day was today; this is one class on the span.
   A diamond and not a filled chip: this tab has no boxes anywhere, and a pill
   here would be the only one on the page. */
/* NO font-weight: it is Ward Caslon since Round 295, which has one cut,
   so a weight here is a synthesised bold on a display face. The colour
   and the diamond are the marks. */
.bday-name.is-today {
  color: var(--accent);
}

.bday-name.is-today::before {
  content: '';
  display: inline-block;
  width: 0.34rem;
  height: 0.34rem;
  margin-right: 0.4rem;
  background: var(--accent);
  transform: rotate(45deg);
  vertical-align: 0.08em;
}

/* -------------------------------------------------------- the groups tab */


/* --------------------------------------------------------------- history */

/* THE WARD'S HISTORY, set as a printed volume (Round 293).

   Derek: the Provo 1st Ward is the oldest in Provo, and he has photographs of
   every bishop since 1852, the programme from the 1939 dedication, a room by
   room tour of the building and a shelf of stories. He asked for the public
   program's own design, and this is it -- the same cream stock, the same one
   display face, the same small-caps labels in sage and the same fleuron. What
   is new is the ARRANGEMENT: a contents list of anchor links over a long
   scrolling document, which is how a bound history is read.

   NO BOXES, as everywhere else on this page. The .ann rule ("a stack of
   bordered cards read as a different website from the page behind the Program
   tab, which has no boxes anywhere") governs here too: the contents are ruled
   off top and bottom, the chapters are separated by space and a centred head,
   and the only rectangle on the tab is a photograph's own plate. */

/* (.history-panel > .panel-h is set with the Groups and Bulletin headings
   in one rule, beside .groups-panel > .panel-h further up -- Round 317
   folded the three identical copies into one.) */

/* The bottom margin is what sets the gap to the contents rule: it collapses
   against .hcontents margin-top (1.5rem), so the larger of the two wins.
   2.5rem gives 40px -- 24px in Round 346, 32px in Round 347, and Derek
   asked for slightly more again in Round 349. It still beats .hcontents
   1.5rem, so this rule is the one that sets the gap.
   Raising THIS rather than .hcontents keeps the number with the paragraph
   it belongs to -- .hcontents 1.5rem also spaces it from a chapter above. */
.hlead {
  margin: -0.2rem 0 2.5rem;
  color: var(--ink-soft);
  text-align: center;
}

.hlead p {
  margin: 0 0 0.6rem;
}

.hlead p:last-child {
  margin-bottom: 0;
}

/* The contents list. Ruled rather than boxed, and the numerals are soft ink
   against accent links -- a column of green numerals beside a column of green
   titles would leave the eye nothing to run down. */
.hcontents {
  margin: 1.5rem 0 2.2rem;
  padding: 1.1rem 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  /* Lands clear of the window edge when Back to contents jumps to it,
     the same 1rem a chapter gets from its own link. */
  scroll-margin-top: 1rem;
}

.hcontents-h {
  margin: 0 0 0.7rem;
  text-align: center;
  color: var(--ink-soft);
}

.hcontents-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.hcontents-list li {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.3rem 0;
}

.hcontents-n {
  flex: 0 0 2.2rem;
  text-align: right;
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}

.hcontents-list a {
  font-family: var(--display);
  /* THE RULE THAT WINS IS THE OTHER ONE: this size is restated as
     calc(1.25rem * var(--ds)) at the foot of the file, which outranks this
     on every screen. Change both, or nothing moves (Rounds 303, 337). */
  font-size: 1.25rem;
  /* A HAIRLINE RATHER THAN A GREEN RULE. Five accent-coloured titles each
     underlined in the same accent is the loudest block on a page whose
     whole manner is quiet, and a printed contents list carries no rule at
     all. The underline stays, because it is the affordance that says these
     are links, but in the leader colour until the pointer is on one. */
  text-decoration-color: var(--dots);
}

.hcontents-list a:hover,
.hcontents-list a:focus-visible {
  text-decoration-color: var(--accent);
}

/* A chapter. The number and the title are centred over the text the way a
   volume sets a chapter head, and the space above is what separates one from
   the next -- there is no rule between them, because the page has already
   spent its rules on the contents. */
.hchapter {
  margin-top: 2.6rem;
  scroll-margin-top: 1rem;
}

.hchapter-head {
  text-align: center;
  margin-bottom: 1.1rem;
}

.hchapter-n {
  margin: 0 0 0.3rem;
  color: var(--accent);
}

.hchapter-h {
  margin: 0;
  font-family: var(--display);
  /* THE RULE THAT WINS IS THE OTHER ONE: this size is restated as
     calc(1.7rem * var(--ds)) at the foot of the file, which outranks this
     on every screen. Change both, or nothing moves (Rounds 303, 337). */
  font-size: 1.7rem;
  font-weight: 400;
  line-height: 1.15;
}

.hblurb {
  color: var(--ink-soft);
}

.hblurb p {
  margin: 0 0 0.7rem;
}

/* A PHOTOGRAPH'S PLATE. The border is the one rectangle on this tab, and it
   earns itself: an old photograph with a pale sky bleeds into cream paper
   without one. */
.hplate {
  display: block;
  /* THIS STYLESHEET HAS NO GLOBAL BORDER-BOX (see .sheetpicker). A button
     gets one from the browser and a span does not, so without this the
     empty plate -- the only one that is a span -- came out 2px wider than
     its grid column and the last one in each row hung over the edge of the
     panel. Measured 139.6px in a 138px track. */
  box-sizing: border-box;
  width: 100%;
  padding: 0;
  border: 1px solid var(--line);
  background: transparent;
  cursor: pointer;
  overflow: hidden;
  line-height: 0;
}

.hplate img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  /* ONE INK FOR THE WHOLE ARCHIVE. These photographs will span 1852 to this
     year, and a sepia plate beside a colour snapshot reads as two documents
     rather than one history -- the same argument that made the meetinghouse
     on the masthead an engraving rather than a photograph (Round 20). This
     is a far gentler treatment than that one: no brightness push, because
     that was tuned to burn a sky out to the colour of the paper and would
     wash a face away. One line to remove if Derek would rather see the
     photographs as they were taken. */
  filter: sepia(0.55) saturate(0.75) contrast(1.04);
}

/* No photograph. Not decoration: without a plate here a bishop with no
   portrait collapses to a line of text in a grid of pictures and the gallery
   reads as broken rather than as incomplete. */
.hplate.is-empty {
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 5;
  color: var(--dots);
  cursor: default;
}

/* The bishops. A grid that fills whatever width the column has, so it is two
   across on a phone and four or five on a computer with no breakpoint to keep
   in step. minmax(0, ...) rather than an implicit auto track: a long name has
   to wrap inside its own column rather than push the row past the page. */
.hpeople {
  list-style: none;
  margin: 1.2rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: 1.5rem 1rem;
}

.hperson {
  min-width: 0;
  text-align: center;
}

.hperson-name {
  margin: 0.55rem 0 0;
  font-family: var(--display);
  /* THE RULE THAT WINS IS THE OTHER ONE: this size is restated as
     calc(1.2rem * var(--ds)) at the foot of the file, which outranks this
     on every screen. Change both, or nothing moves (Rounds 303, 337). */
  font-size: 1.2rem;
  line-height: 1.2;
  overflow-wrap: anywhere;
}

.hperson-years {
  margin: 0.2rem 0 0;
  color: var(--accent);
}

.hperson-note {
  margin: 0.35rem 0 0;
  font-size: 0.875rem;
  color: var(--ink-soft);
}

/* Milestones. The year hangs in its own column against a hairline, which is
   the printed idiom for this and keeps the dotted leader for the program,
   where it means something else. */
.htimeline {
  list-style: none;
  margin: 1.2rem 0 0;
  padding: 0;
}

.htime {
  display: grid;
  grid-template-columns: 4.2rem minmax(0, 1fr);
  gap: 0 1rem;
  padding: 0.7rem 0;
  border-top: 1px solid var(--line);
}

.htime:last-child {
  border-bottom: 1px solid var(--line);
}

.htime-y {
  margin: 0;
  padding-top: 0.2rem;
  color: var(--accent);
}

.htime-t p {
  margin: 0 0 0.5rem;
}

.htime-t p:last-child {
  margin-bottom: 0;
}

/* A prose entry: a story, a room in the building, a document with its pages
   beneath it. */
.hentry {
  margin-top: 1.5rem;
}

.hentry-h {
  margin: 0 0 0.5rem;
  font-family: var(--display);
  /* THE RULE THAT WINS IS THE OTHER ONE: this size is restated as
     calc(1.35rem * var(--ds)) at the foot of the file, which outranks this
     on every screen. Change both, or nothing moves (Rounds 303, 337). */
  font-size: 1.35rem;
  font-weight: 400;
  line-height: 1.25;
}

.hentry p {
  margin: 0 0 0.7rem;
}

.hshots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 0.8rem;
  margin: 1rem 0 1.2rem;
}

/* A document's pages and a story's photographs are whatever shape they were
   taken; only a portrait is cropped to a plate. */
.hshots .hplate img {
  aspect-ratio: auto;
  object-fit: contain;
}

/* BACK TO CONTENTS (Round 300, Derek: it "should only appear at the top of
   the screen once the user has scrolled down beyond the contents ... stuck
   to the top of the screen always when they are below the actual contents
   list").

   THE BASE RULE IS THE ONE IT ALWAYS HAD -- a quiet centred line at the foot
   of the history -- and that is deliberate: it is what a reader with no
   JavaScript gets, and what the LIVE PROGRAM replica shows inside its card.
   Only the public page lifts it to the top of the window, because only there
   is the window the thing being scrolled. */
.htop {
  margin: 1.5rem 0 0;
  text-align: center;
  font-size: 0.8125rem;
}

.htop a {
  color: var(--ink-soft);
}

/* On the ward own page it leaves the flow entirely: fixed, so it cannot
   push the history about as it comes and goes, and invisible until the
   script says the contents list has gone past the top. It sits INSIDE the
   History panel, so [hidden] on that panel takes it off every other tab
   with no rule of its own.

   IT GAINS A GROUND, and only here. At the foot of a page a bare line of
   text is legible; floating over a scrolling column it is not, so it takes
   the card colour and a hairline -- the least that can be added and still
   be read over a photograph of a bishop. The underline goes with it: the
   edge is what says it can be pressed once there is one.

   z-index 6 clears the page own content and stays below the flyer lightbox
   (90), which opens over everything. */
body:not(.admin) .htop {
  position: fixed;
  top: 0.55rem;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  z-index: 6;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}

body:not(.admin) .htop.is-stuck {
  opacity: 1;
  pointer-events: auto;
}

body:not(.admin) .htop a {
  display: inline-block;
  padding: 0.3rem 0.9rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: var(--lift);
  text-decoration: none;
}

body:not(.admin) .htop a:hover,
body:not(.admin) .htop a:focus-visible {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}

/* With no script nothing ever adds is-stuck, so the button would never be
   seen at all. Back to the flow it goes -- the plain centred link this was
   before, which still works: it is an ordinary anchor to the contents. */
html:not(.js) body:not(.admin) .htop {
  position: static;
  transform: none;
  margin: 1.5rem 0 0;
  opacity: 1;
  pointer-events: auto;
}

/* THE PAGE BEFORE THERE IS A HISTORY (Round 294, Derek: "it should be
   visible now. you and I will work to fill it with content by Sunday").
   Round 293 kept the tab off the page until a chapter had content, so the
   ward never met an empty one; he would rather they saw it coming, so the
   line names what is being gathered instead of apologising for its absence.
   Set as the page's own aside voice, like the stale-week notice. */
.hempty {
  font-style: italic;
  max-width: 30rem;
  margin-inline: auto;
}

/* ------------------------------------------- leader app: the history editor

   The tab is the ward's own record, so it is laid out as a book being set:
   the opening words, then a card per chapter that folds, then one button to
   add another. Everything autosaves on the sheet's own debounce -- there is
   no Save button anywhere on it -- and .histsave at the top right is the one
   thing that says so. */
.histbar h2 {
  margin: 0;
}

.histsave {
  margin-left: auto;
  font-size: 0.8125rem;
  color: var(--ink-soft);
  min-height: 1.2em;
}

.histsave.is-ok {
  color: var(--success-ink);
}

.histsave.is-bad {
  color: var(--notify);
}

.histlead {
  max-width: 46rem;
  margin: 0 0 1.2rem;
}

/* A chapter, shut: its numeral, its heading, what kind of chapter it is and
   how much is in it -- which is what you need to find the one you want among
   ten. Open, it is an ordinary card. */
.histchap-n {
  flex: none;
  font-variant-numeric: normal;
  color: var(--accent);
  font-size: 0.9rem;
  letter-spacing: 0.06em;
  min-width: 2.2rem;
}

.histchap-kind {
  flex: none;
  font-weight: 500;
}

/* Quiet until the row is hovered or holds the focus, so a column of chapters
   is not a column of delete buttons -- and solid on touch, where nothing
   reveals them. The remove button is the one destructive control here, so it
   takes the attention colour from .iconbtn.danger rather than a second
   treatment of its own. */
.histchap-acts {
  flex: none;
  display: inline-flex;
  gap: 0.15rem;
  opacity: 0.45;
  transition: opacity 0.15s;
}

.histchap:hover .histchap-acts,
.histchap:focus-within .histchap-acts {
  opacity: 1;
}

@media (hover: none) {
  .histchap-acts {
    opacity: 1;
  }
}

.histlist {
  margin-top: 1rem;
  border-top: 1px solid var(--line);
}

/* One row per person, per date, per section. The field names are
   placeholders rather than a caption over every box (the new-member records
   pair, Round 160): thirty portraits with three headings apiece is ninety
   words of chrome on a list of thirty names. */
.histrow {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line);
}

.histrow .histf {
  display: block;
  min-width: 0;
}

.histrow .histf input,
.histrow .histf textarea {
  width: 100%;
}

.histf-name {
  flex: 1 1 12rem;
}

.histf-years {
  flex: 0 1 8rem;
}

.histf-note {
  flex: 2 1 14rem;
}

.histf-year {
  flex: 0 1 7rem;
}

.histf-what {
  flex: 1 1 18rem;
}

.histprose-in {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

.histprose-h {
  font-weight: 650;
}

.histacts {
  flex: none;
  display: inline-flex;
  gap: 0.15rem;
  opacity: 0.45;
  transition: opacity 0.15s;
}

.histrow:hover .histacts,
.histrow:focus-within .histacts {
  opacity: 1;
}

@media (hover: none) {
  .histacts {
    opacity: 1;
  }
}

/* ONE PHOTOGRAPH IN A ROW. photoField draws a strip with a sentence under it,
   which is right for an announcement's flyers and far too much beside a
   bishop's name. Empty it is a dashed square; filled it is the plate, and
   pressing the plate replaces the picture. */
.histpic {
  position: relative;
  flex: none;
  width: 3.4rem;
  height: 3.4rem;
}

.histpic-add,
.histpic-img,
.histpic-busy {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-detail);
  background: none;
  font-family: inherit;
  padding: 0;
}

.histpic-add {
  /* 2px, not 1.5: a 1.5px dashed edge draws at 1px and stops reading as
     dashes (Round 172 measured the same on the Still-blank chips). */
  border: 2px dashed color-mix(in srgb, var(--ink-soft) 45%, transparent);
  color: var(--ink-soft);
  font-size: 1.1rem;
  cursor: pointer;
}

.histpic-add:hover,
.histpic-add:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}

.histpic-busy {
  border: 1px solid var(--line);
  color: var(--ink-soft);
}

.histpic-img {
  border: 1px solid var(--line);
  overflow: hidden;
  cursor: pointer;
  padding: 0;
}

.histpic-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The clear button sits on the plate's corner rather than beside it: a row
   of thirty bishops has no width to spend on a second control per line. */
.histpic .histpic-x {
  position: absolute;
  top: -0.35rem;
  right: -0.35rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 50%;
  width: 1.3rem;
  height: 1.3rem;
  min-height: 0;
  padding: 0;
  font-size: 0.7rem;
  opacity: 0;
}

.histpic:hover .histpic-x,
.histpic:focus-within .histpic-x {
  opacity: 1;
}

@media (hover: none) {
  .histpic .histpic-x {
    opacity: 1;
  }
}

/* The chapters Derek already has, offered as one-click starts on a ward with
   none. Nothing is stored until one is pressed -- a seed nobody chose is
   five empty chapters somebody then has to delete. */
.histstart-row {
  flex-wrap: wrap;
  gap: 0.5rem;
}

.histadd {
  margin-top: 1.2rem;
}

/* The Historian column on the Admin table. Narrow: it holds one checkbox, and
   the positional widths beside it are the tracker's (Round 278). */
.athist {
  width: 5.5rem;
  text-align: center;
}

/* SCOPED TO .groups-panel, NEVER BARE .gcard. app.js emits .gcard three
   times: the replica of this page (which should look like this), the
   BISHOPRIC'S APPROVAL QUEUE (cards carrying Approve / Not this one) and the
   live-groups dialog, whose .glist scrolls and would clip a protruding ring.
   An unscoped restyle would tilt and re-frame the bishopric's decision cards.

   The card is a printer's trade card here: square corners, a heavier rule
   across the head, and a double edge drawn as two box-shadow spreads -- the
   classic 19th-century card border, and deliberately not outline, which the
   focus system owns. box-shadow does not print; nobody prints this tab, and
   the cards keep their own border on paper. */
.groups-panel .gcards {
  gap: 1.35rem;
}

.groups-panel .gcard {
  /* Round 309: the same brighter card as .ann, on the public Groups tab
     and its replica only -- the bishopric's approval queue and the
     live-groups dialog use the bare .gcard rule above, not this scope,
     and keep the ordinary --card. */
  background: var(--card-bright);
  border-radius: 0;
  border-top-width: 3px;
  box-shadow: 0 0 0 3px var(--card-bright), 0 0 0 4px var(--dots);
}

/* THE GROUP'S NAME IS THE HEADLINE, AND NOW READS LIKE ONE (Round 324, Derek:
   "make the title of the group bold"). It was Caslon at 400 while the caption
   under it -- TEXT CAMERON WHIPPLE -- was 650 uppercase, so the loudest thing
   on the card was the label rather than the name of the group.

   THIS IS A SYNTHESISED BOLD AND THAT IS A DELIBERATE EXCEPTION. caslon.woff2
   carries one weight; measured, the advance widths at 400, 650 and 700 are
   identical to the hundredth of a pixel, so the browser thickens the 400
   outline rather than loading a second face. Rounds 20 and 295 both refused
   that -- "a display cut set bold loses the modelling that is the whole
   reason to use it" -- and it was rendered at 46px and at the card's own
   21.6px beside the alternatives before being taken: at 650 it holds its
   modelling and reads as Caslon, where the book face at 650 reads as a
   different family sitting in a Caslon card. The rule stands everywhere else;
   this is the one place the smear was looked at and judged good enough.
   A real Libre Caslon bold would be a second ~24KB download on the page
   people open in the chapel, which Round 23 declined for this very tab. */
.groups-panel .gcard-name {
  font-family: var(--display);
  font-weight: 650;
}

/* AND THE CAPTION STOPS COMPETING WITH IT (Round 324, Derek: "unbold the name
   of the person to text"; Round 325, "make the name of the person to text ...
   a little smaller"). It sits in the shared rank-1 small-caps list, which a
   dozen other labels read, so both are dropped HERE rather than there.

   500, not 400: this is letterspaced uppercase in the soft ink, and at 400 it
   thins out to the point of straining -- a genuine unbolding from 650.

   0.75rem is the SUBORDINATE rank's own size, not a number invented for this
   line: the card's caption steps from rank 1 to rank 2 of the three-rank
   small-caps system rather than leaving it. It stays clear of the fault Round
   324's own comment records -- this line was 9.9px once and measured 4.23:1,
   the only text on the public page missing AA -- because that was the 85%
   colour mix, not the size, and in the soft ink it measures 5.9:1 at any of
   these sizes. */
.groups-panel .gcard-join {
  font-size: 0.75rem;
  font-weight: 500;
}

/* THE NUMBER IS THE POINT OF THIS TAB, so it is a thumb-sized target rather
   than 24px of underlined digits with an invisible hit box grafted over them
   (Round 24, which was the right fix while the number had to look untouched).
   It is a pill around THE DIGITS ALONE -- not around the whole "Text Marci
   Bell to join" line, which measures wider than the card at 375px.
   IT IS STILL ONE <a href="sms:...">, WITH THE DIGITS AS VISIBLE TEXT.
   Round 24's requirement survives: a long press still offers Copy on the
   number, which a button would have taken away. */
/* A TRIMMER PILL (Round 324, Derek: the digits "just barely smaller", and the
   pill smaller by MORE than that, "so that there's not as much empty space in
   that pill"). Measured: the number 16.8 -> 16px, the pill 44 -> 38.4px, so
   the air above and below the digits goes 13.6 -> 11.2px. The side padding
   comes in with it.

   IT IS STILL A THUMB TARGET, which is this pill's whole reason for existing
   (Round 24). 38.4px is under the 44px a AAA target asks for and well over
   the 24px an AA one does, and the invisible hit box the digits used to carry
   is still switched off below because the pill outgrew it. Say the word and
   2.75rem goes back. */
.groups-panel .gcard-tel a {
  display: inline-flex;
  align-items: center;
  min-height: 2.4rem;
  margin-top: 0.35rem;
  padding: 0 0.72rem;
  font-size: 1rem;
  border: 1.5px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 999px;
  /* UNDERLINED, FAINTLY (Round 324, Derek). The pill already says it can be
     pressed; the underline says it is a LINK, which is what tells somebody
     they can long-press it to copy the number rather than only tap to text
     (Round 24's requirement). Faded so it does not fight the pill's own edge,
     and offset clear of the digits' baseline. */
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--accent) 38%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
}

.groups-panel .gcard-tel a:hover,
.groups-panel .gcard-tel a:focus-visible {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-color: var(--accent);
}

/* The grafted hit box would now overhang the pill it is no longer needed for. */
.groups-panel .gcard-tel a::after {
  content: none;
}

/* THE LIGHTER OPTION sentence that lived here (Round 231) is GONE (Round
   315): the whole three-sentence offer it belonged to was cut to one line
   of Derek's own wording, and the choice it named -- subscribe to
   everything, or add events one at a time -- is no longer spelled out on
   the page at all. .wardcal-or has no emitter left in either renderer. */

/* NOT TO BE ASKED TO PRAY (Round 235, Derek). The mark button in the prayer
   picker, the warning under a typed name, and the note's own label. */
.namedrop-name {
  /* Holds the name at the left so the date and the mark button sit together
     at the right; without it the row's space-between would strand the date
     in the middle once a third child arrives. */
  margin-right: auto;
}
.namedrop-mark {
  flex: none;
  font: inherit;
  font-size: 0.7rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  padding: 0.1rem 0.4rem;
  border: 1px solid color-mix(in srgb, var(--ink-soft) 40%, transparent);
  border-radius: 999px;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  opacity: 0.75;
}
.namedrop-row:hover .namedrop-mark,
.namedrop-mark:hover {
  opacity: 1;
}
.namedrop-mark:hover {
  border-color: color-mix(in srgb, var(--notify) 55%, transparent);
  color: var(--notify);
}
.namedrop-mark.is-on {
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  color: var(--accent);
  opacity: 1;
}
.namedrop-row.is-nopray .namedrop-name {
  color: var(--ink-soft);
}
/* The line under the box. The attention colour, like .recent beside it. */
.histhint.nopray {
  color: var(--notify);
  font-weight: 650;
}
.noteprivate {
  display: inline-block;
  margin-bottom: 0.3rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.6875rem;
  font-weight: 700;
}
/* ================= SYNC WITH LCR (Round 275) =======================
   The window reuses the hymn planner's shell (.planmodal) -- same size,
   same entrance, same backdrop -- because it is the same kind of thing: a
   big working surface opened over the page and closed again. Only what is
   inside it is new. */
.lcrmodal {
  width: min(94vw, 62rem);
  height: min(88vh, 54rem);
}

.lcrbody {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-right: 0.3rem;
}

/* An upload row: what it is on the left, Instructions on the right. */
.lcrrow {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  padding: 0.9rem 0;
  border-top: 1px solid var(--line);
}

.lcrrow-main { flex: 1 1 auto; min-width: 0; }
.lcrrow-main .hint { margin: 0.15rem 0 0.55rem; }

.lcrrow-file {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
}

/* A chosen file reads as settled, not as another thing to do. */
.lcrfile {
  font-size: 0.875rem;
  color: var(--ink-soft);
}

.lcrfile.has {
  color: var(--accent);
  font-weight: 600;
}

.lcrrow.is-done { background: color-mix(in srgb, var(--accent) 4%, transparent); }

.lcrsteps { flex: none; }

.lcrsteps-list {
  margin: 0.2rem 0 0.9rem;
  padding-left: 1.2rem;
  line-height: 1.55;
}

.lcrsteps-list li { margin-bottom: 0.35rem; }

/* --- the review ------------------------------------------------------- */
.lcrgroup { margin-top: 1.6rem; }

.lcrgroup h3 {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: inherit;
  font-size: 1.02rem;
  font-weight: 700;
  margin: 0 0 0.3rem;
  padding-bottom: 0.35rem;
  border-bottom: 1.5px solid var(--ink);
}

.lcrcount {
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  color: var(--ink-soft);
}

.lcrgroup h3 .btn { margin-left: auto; }

.lcrlist { display: flex; flex-direction: column; }

.lcritem {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.94rem;
}

.lcritem input[type='checkbox'] { flex: none; }

.lcrname { font-weight: 650; }

.lcrmeta, .lcrfield {
  color: var(--ink-soft);
  font-size: 0.86rem;
}

.lcrfield { margin-left: auto; }

.lcrpicks {
  display: flex;
  gap: 0.4rem;
  flex: none;
  flex-wrap: wrap;
}

.lcrpicks.lcrstack { flex-direction: column; align-items: flex-end; }

.lcrconflict .lcrfield { margin-left: 0; flex: 1 1 auto; }

/* Two values side by side, the chosen one filled. A radio would have said
   the same thing in less space and shown neither value. */
.lcrpick {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  font: inherit;
  font-size: 0.86rem;
  text-align: left;
  padding: 0.3rem 0.6rem;
  border: 1.5px solid var(--line);
  border-radius: var(--r-item);
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}

.lcrpick.on {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--ink);
}

.lcrpick-l {
  font-size: 0.66rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.lcrsub {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  font-size: 0.86rem;
}

/* Kept and flagged: nothing to do, so nothing that looks pressable. */
.lcrkeep { opacity: 0.75; }

.lcrgood {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--good, #1d6b3d);
  margin: 1.4rem 0 0.3rem;
}

.lcrbad {
  font-size: 1.02rem;
  font-weight: 700;
  color: var(--notify);
  margin: 1.4rem 0 0.3rem;
}

.lcrfoot {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--line);
}

.lcrfoot-acts {
  margin-left: auto;
  display: flex;
  gap: 0.6rem;
}

.lcrstatus { margin: 0; }

@media (max-width: 46rem) {
  .lcritem { flex-wrap: wrap; }
  .lcrfield { margin-left: 0; }
}

/* HOW OFTEN A NOTICE COMES ROUND (Round 282). Its options are sentences --
   "The third Sunday of every month" -- and a <select> sizes itself to its
   WIDEST option, not to the chosen one, so without these two it would push
   its grid track past the card in the conducting sheet's 27rem column.
   This is the Round 30 treatment and it is safe for the same reason: the
   loss is bounded and recoverable. Opening the list shows every option in
   full, the closed box carries the whole label in its title, and the hint
   beneath spells the answer out in real dates. */
.repeatbox { margin-top: 0.55rem; }
.repeatbox select {
  width: 100%;
  min-width: 0;
  text-overflow: ellipsis;
}
.repeathint { margin-top: 0.45rem; }

/* ==========================================================================
   THE PHONE READS THE SAME PAGE IN A COLUMN 42% NARROWER (Round 299)

   Derek reviewed this page for months on the LIVE PROGRAM tab and then
   looked at it on his phone: "the text is too big everywhere ... the
   buttons got formatted in weird ways."

   THE TWO SURFACES RUN THE SAME RULE. .wrap and .wp-paper are both
   max-width: 40rem with 1.25rem of padding, so the replica in the leader
   app is not a different design -- it is this page at its full 640px, with
   600px of text. A phone gives it 350px. Every size is in rem and rem is
   root-relative, so the type is IDENTICAL in pixels on both and only the
   column moved. Measured: the ward name is 33.6px against 600px of column
   on the desktop and 33.6px against 350px on the phone -- 1.7 times as
   large relative to the line it sits on. That ratio is what reads as "too
   big", and no amount of looking at the replica could ever have shown it.

   THE PAGE CANNOT SIMPLY BE SHRUNK TO MATCH. A true proportional scale is
   350/600 = 0.58, which would put body text at 10px. Text has a readable
   floor that a column width does not, so the phone can never be a photo of
   the desktop view reduced -- it can only have its own scale.

   So: --ds (display scale) multiplies the DISPLAY tier -- the masthead and
   everything set above body text -- and the body itself steps 17px -> 16px.
   Two numbers rather than one because of a constraint worth writing down:
   the display tier is only 1.13-1.27x body, so scaling the headings alone
   past about 0.89 would push a speaker's name BELOW the announcement prose
   and invert the hierarchy Rounds 231, 280 and 295 each set deliberately.
   Moving both keeps every one of those relationships exactly as it is.

   --ds IS 1 EVERYWHERE ELSE, so calc(X * var(--ds)) computes to X and the
   desktop page and the replica are provably byte-for-byte unchanged. It is
   the same idiom --cs-scale uses for the printed sheet (Round 196).

   The phone-frame preview (Round 295, a 390px stage inside the leader app)
   takes the scale unconditionally -- it is 390px by construction, and its
   whole claim is that it shows what a phone shows. Without this line it
   would have become the one view that lies about the phone.
   ====================================================================== */
:root {
  --ds: 1;
}

@media (max-width: 30rem) {
  body:not(.admin) {
    --ds: 0.88;
    font-size: 1rem;
  }

  /* THE FOUR TABS ONE POINT SMALLER ON A PHONE (Round 341, Derek). 0.75rem
     is the subordinate rank of the small-caps system, so this is a step
     within it rather than a number picked for the occasion -- and it buys
     the row about 20px, which is slack before it has to wrap. .tab sets its
     own size at (0,1,0) and does not scale with --ds, so this override is
     both more specific and later. */
  body:not(.admin) .tab {
    font-size: 0.75rem;
  }
}

.phoneview-stage .wp-paper {
  --ds: 0.88;
  font-size: 1rem;
}

/* The 390px preview has to shrink them too, or it stops showing what a
   phone shows, which is the only thing it is for (Round 295). */
.phoneview-stage .wp-paper .tab {
  font-size: 0.75rem;
}

/* THE TABS FALL BACK TO A 2x2 GRID ONLY WHERE ONE ROW CANNOT HOLD THEM
   (Round 307). Since the pills became a contents line the four words need
   about 327px at a phone's 16.9px with their gaps and hit padding, so
   every ordinary phone -- 375px and up -- carries them on one row. Below
   that, and for anyone who has enlarged their text, they sit two and two:
   23rem is measured against the browser's OWN default font size (a media
   query's rem ignores the root this page sets, Round 299), so a reader at
   125% moves the breakpoint to 460px and a 390px phone drops to the grid
   at about the size the row would have overflowed. Each word centred in
   its own half, so the pair reads as a small index rather than a ragged
   list.

   THE PHONE-FRAME PREVIEW TAKES THE BASE ROW AND NOTHING ELSE. It is 390px
   by construction and its whole claim is to show what a phone shows; the
   grid it used to force here would now be the one thing it got wrong.
   Its --ds is already 0.88, so the row's size and gap follow on their own. */
@media (max-width: 23rem) {
  body:not(.admin) .tabs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    justify-items: center;
    row-gap: 0;
  }
}

/* The display tier. Every base size here is the one the rule above it in
   this file already sets -- these multiply, they never redefine. When one
   of those changes, change it here too.

   ROUND 332 MOVED THE WHOLE CASLON TIER UP TWO POINTS (Derek: "make all
   the text that is in the font Libre Caslon Display on the public program
   page 2 points larger"), which is +0.15rem taken to a clean rem -- the
   conversion Round 328 set and Round 331 used. Every Caslon size on the
   four public tabs moved, here AND in its documented rule above; .bday-name
   carries one more point on top, which he asked for in the same message.
   .mdate below is NOT Caslon (it is the book face in italic) and did not
   move. */
body:not(.admin) .masthead .ward,
.wp-paper .masthead .ward {
  font-size: calc(2.25rem * var(--ds));
}

/* The two epigraphs shrink WITH the name they bracket (Round 334, Derek:
   "add them to phone scale block"). Out of this block they held 16px on a
   phone while the name dropped to 31.68px; in it they are 14.08px, so the
   0.444 lockup holds on every screen. One rule serves both, as the
   documented rule above does, so the pair cannot drift apart. */
body:not(.admin) .masthead .ward-over,
body:not(.admin) .masthead .ward-est,
.wp-paper .masthead .ward-over,
.wp-paper .masthead .ward-est {
  font-size: calc(1rem * var(--ds));
}

body:not(.admin) .mdate,
.wp-paper .mdate {
  font-size: calc(1.1875rem * var(--ds));
}

body:not(.admin) .mtheme,
.wp-paper .mtheme {
  font-size: calc(1.35rem * var(--ds));
}

/* 1.35rem to match .program-list .row-value below (Round 303, Derek: level
   with the other names on the program; Round 332 moved the pair together).
   Kept as its own rule rather than
   folded into that selector -- the two happen to share a number today, but
   nothing ties them together, and a future change to one should not have to
   remember it is also changing the other. */
body:not(.admin) .officers .row-value,
.wp-paper .officers .row-value {
  font-size: calc(1.35rem * var(--ds));
}

body:not(.admin) .program-list .row-value,
.wp-paper .program-list .row-value {
  font-size: calc(1.35rem * var(--ds));
}

body:not(.admin) .item-sub,
.wp-paper .item-sub {
  font-size: calc(1.3rem * var(--ds));
}

body:not(.admin) .ceremony,
.wp-paper .ceremony {
  font-size: calc(1.15rem * var(--ds));
}

body:not(.admin) .ann-title,
.wp-paper .ann-title {
  font-size: calc(1.65rem * var(--ds));
}

body:not(.admin) .bday-name,
.wp-paper .bday-name {
  font-size: calc(1.45rem * var(--ds));
}

body:not(.admin) .gcard-name,
.wp-paper .gcard-name {
  font-size: calc(1.65rem * var(--ds));
}

body:not(.admin) .miss-name,
.wp-paper .miss-name {
  font-size: calc(1.35rem * var(--ds));
}

body:not(.admin) .gthanks-h,
.wp-paper .gthanks-h {
  font-size: calc(1.5rem * var(--ds));
}

/* The unclipped panel headings (Groups and History; one rule beside
   .groups-panel > .panel-h sets them at 1.85rem) and the Bulletin's own
   .annhead at the same rank. Target THOSE rules: a plain .panel-h here
   would win on order and shrink it on the desktop too, which is exactly
   what --ds exists to make impossible. History was missing from this list
   until Round 318 -- its heading was the one display line on the page that
   did not shrink on a phone. */
body:not(.admin) .groups-panel > .panel-h,
.wp-paper .groups-panel > .panel-h,
body:not(.admin) .history-panel > .panel-h,
.wp-paper .history-panel > .panel-h,
body:not(.admin) .annhead,
.wp-paper .annhead {
  font-size: calc(2rem * var(--ds));
}

/* AND THE FOUR HEADINGS INSIDE THE HISTORY TAB (Round 337, Derek, having
   watched the masthead epigraphs join in Round 334 and asked for these too).
   Round 332 raised the whole Caslon tier two points and deliberately left
   these out, so on a phone they kept the full two points while every other
   display line on the page gave 12% back -- the History tab was the one tab
   whose type did not hold its proportions across the two screens, and its own
   panel heading (above) had been scaling since Round 318 while the headings
   under it did not. All four are var(--display), so this is the Caslon tier
   finally complete. In page order: the contents list, a chapter head, an
   entry head, a person s name. */
body:not(.admin) .hcontents-list a,
.wp-paper .hcontents-list a {
  font-size: calc(1.25rem * var(--ds));
}

body:not(.admin) .hchapter-h,
.wp-paper .hchapter-h {
  font-size: calc(1.7rem * var(--ds));
}

body:not(.admin) .hentry-h,
.wp-paper .hentry-h {
  font-size: calc(1.35rem * var(--ds));
}

body:not(.admin) .hperson-name,
.wp-paper .hperson-name {
  font-size: calc(1.2rem * var(--ds));
}

/* A FORM BOX NEVER GOES BELOW 16px. iOS zooms the whole page in on a field
   whose text is smaller than that, and the group-proposal form is the one
   place on this page somebody types. Body dropped to 16px above; these keep
   their own 17px so there is headroom either way. */
@media (max-width: 30rem) {
  body:not(.admin) .gfield input,
  body:not(.admin) textarea {
    font-size: 1.0625rem;
  }
}

/* A BISHOP OPENS AS THE FRAMED PORTRAIT HE IS ON THE WALL (Round 339).
   Derek asked for this from photographs of the real frames hanging in the
   bishop's office: a frame, and the name on an engraved plate.

   IT IS THE SAME LIGHTBOX, told apart by one class. The button carries a
   data-name only for a portrait, so an announcement flyer opens exactly as
   it did -- and unframed, .lb-frame and .lb-mat are display: contents, so
   they are not merely unstyled but genuinely not in the layout.

   THE COLOURS HERE ARE LITERAL, NOT TOKENS, and that is deliberate. This is
   a depicted OBJECT rather than page chrome: a walnut moulding is walnut in
   both palettes, and it is always seen against the same dark scrim, which
   is dark in light mode too. Tokens would repaint a picture frame when the
   phone changed theme, which is the one thing a frame should not do.

   NO GLASS GLARE. It was the obvious next flourish and it is left out: a
   diagonal sheen across the one thing the reader opened the box to look at
   buys atmosphere by making a face harder to see. */

.lightbox .lb-frame,
.lightbox .lb-mat { display: contents; }
.lightbox .lb-plate { display: none; }

.lightbox.is-framed .lb-frame {
  display: flex;
  box-sizing: border-box;
  max-width: min(92vw, 32rem);
  max-height: 88vh;
  padding: 1.05rem;
  border-radius: 2px;
  /* the moulding, lit from the upper left as a frame on a wall is */
  background: linear-gradient(152deg, #5a4331 0%, #34251a 38%, #241a12 62%, #4a3626 100%);
  box-shadow:
    inset 0 2px 0 rgba(255, 233, 197, 0.20),
    inset 2px 0 0 rgba(255, 233, 197, 0.08),
    inset 0 -2px 0 rgba(0, 0, 0, 0.55),
    inset -2px 0 0 rgba(0, 0, 0, 0.38),
    0 1.5rem 3.2rem rgba(0, 0, 0, 0.58),
    0 0.35rem 0.9rem rgba(0, 0, 0, 0.42);
}

/* NO MAT (Round 342). Derek: "I don't love all of the white background in
   this picture frame. is it possible to have the photo fill the entire
   frame?" -- and his own reference frames have no mat either: the picture
   goes to the moulding and the nameplate is screwed on over it. The
   gallery mat was my addition, and it was costing a portrait a third of
   the box.

   So the mat element becomes the PICTURE'S OWN BOX: no padding, no ground,
   and the positioning parent for the plate. It keeps the brass fillet,
   which now runs directly around the photograph -- that thin gold line is
   most of what separates an antique frame from a black rectangle. */
.lightbox.is-framed .lb-mat {
  display: block;
  position: relative;
  min-height: 0;
  padding: 0;
  background: none;
  box-shadow: 0 0 0 2px #8b7040;
}

.lightbox.is-framed .lb-img {
  display: block;
  /* only the moulding to leave room for now */
  max-height: calc(88vh - 3rem);
  max-width: 100%;
  border-radius: 0;
  box-shadow: none;
}

/* THE PLATE IS SCREWED ONTO THE PICTURE, not stacked under it. Absolutely
   positioned, so it costs the photograph no height at all and the frame is
   exactly the picture plus its moulding. Its ground is opaque, so it reads
   over a light suit and a dark one alike, and it sits low enough to cover
   a portrait's jacket rather than a face. */
.lightbox.is-plated .lb-plate {
  position: absolute;
  /* CENTRED WITH left/right/auto, NOT left:50% + translate. An absolutely
     positioned box shrink-to-fits against the space from its own left edge
     to the container s right, so left:50% capped this plate at HALF the
     picture s width and wrapped a long name onto two lines. The transform
     moves it afterwards and cannot give the width back. */
  left: 0;
  right: 0;
  bottom: 1.1rem;
  width: fit-content;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.16rem;
  max-width: calc(100% - 1.6rem);
  /* margin-BLOCK only: a bare margin:0 here would reset the margin-inline
     auto above it and drop the plate back against the left edge. */
  margin-block: 0;
  padding: 0.44rem 1.15rem 0.5rem;
  text-align: center;
  border-radius: 1px;
  background: linear-gradient(#2c2723, #17130f 55%, #241f1a);
  /* a brass bezel, then the shadow it casts on the picture behind it */
  box-shadow: 0 0 0 1px #7d6539, 0 2px 8px rgba(0, 0, 0, 0.55);
}

/* THE ENGRAVED LOOK IS THE TEXT SHADOW AND NOTHING ELSE: a dark edge above
   the glyph and a pale one below reads as incised, because the light on a
   wall comes from above. Reverse the two and the same letters emboss. */
.lightbox.is-plated .lb-plate-n {
  font-family: var(--display);
  font-size: 0.95rem;
  line-height: 1.25;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #ece0c6;
  text-shadow: 0 1px 0 rgba(255, 242, 214, 0.18), 0 -1px 1px rgba(0, 0, 0, 0.8);
}

.lightbox.is-plated .lb-plate-y {
  font-size: 0.72rem;
  line-height: 1.3;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: #c0aa7e;
  text-shadow: 0 1px 0 rgba(255, 242, 214, 0.12), 0 -1px 1px rgba(0, 0, 0, 0.75);
}

/* On a phone the moulding and the mat give way before the picture does. */
@media (max-width: 30rem) {
  .lightbox.is-framed .lb-frame { padding: 0.7rem; max-width: 94vw; }
  .lightbox.is-framed .lb-img { max-height: calc(88vh - 2rem); }
  .lightbox.is-plated .lb-plate { bottom: 0.8rem; padding: 0.36rem 0.85rem 0.4rem; }
  .lightbox.is-plated .lb-plate-n { font-size: 0.85rem; letter-spacing: 0.1em; }
}
