/* The Discord Activity's own layout, on top of app.css's tokens.
 *
 * The Activity is a small panel inside someone else's window, not a page: no
 * site nav, no footer, no scrolling chrome. It also has to survive being shrunk
 * into a picture-in-picture tile a couple of hundred pixels wide, so
 * everything here sizes from the viewport rather than from a fixed column, and
 * the very small sizes are handled outright in activity-sizes.css.
 *
 * The colors are the writer's (Settings, Colors; `web/colors.rs`): by default
 * Paper when their device is set to light and Ink when it is set to dark
 * (owner, 2026-10-27), chosen before the first paint by `theme-boot.js`.
 * Whether Discord's frame reports Discord's own light or dark, or the
 * computer's, is untested; a writer whose Discord is dark on a light computer
 * may see Paper, and Always dark is the way out.
 *
 * Two rules worth keeping.
 *
 * The board redraws itself every three seconds, so nothing here may animate on
 * arrival. A card that faded in would be a card that never stopped fading in.
 * The one exception is a screen *change*, which happens when somebody presses
 * something and never on the redraw: see the press-feedback section.
 *
 * And this file is on its own. The page loads `tokens.css` for the palette and
 * nothing else: `app.css` is a layout for a site the panel is not part of, and
 * its class names are close enough to these that loading it silently reshapes
 * them. So every element the panel uses is styled here, defaults included. */

/* Jost, which is what sprintobot.com is set in, so somebody who read the help
 * page and then opened the panel is looking at the same thing. Served from our
 * own origin because the Activity runs behind Discord's proxy, where a request
 * to a font host does not leave the page. Three weights, latin only.
 *
 * `swap` on purpose: the panel is a countdown, and a blank clock while a font
 * downloads is worse than a clock in the fallback face for one frame. */
@font-face {
  font-family: "Jost";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/_assets/fonts/jost-400.woff2") format("woff2");
}

@font-face {
  font-family: "Jost";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/_assets/fonts/jost-500.woff2") format("woff2");
}

/* The figures wherever a number changes under somebody's eyes, and nothing
 * else in it.
 *
 * Jost has no tabular figures. Not "the subset dropped them": there is no
 * `tnum` feature in the family, so the `font-variant-numeric` this file used to
 * carry was doing nothing at all, and a 1 going past moved the clock sideways
 * by a fifth of a digit. These are Jost's own figures with every advance set to
 * the widest, built by `scripts/jost-digits.py`. About a kilobyte each.
 *
 * One per shipped weight, because this is asked for in more than one place now
 * and they are not all bold: the countdown is 700, the value beside "When?" is
 * 500, and the sentence under the slider is 400. A single 700 file would have
 * set a normal-weight sentence's figures a weight heavier than its words.
 *
 * The unicode-range is what keeps each to a kilobyte: letters, the colon and
 * everything else fall straight through to Jost proper, which is untouched. */
@font-face {
  font-family: "Jost Digits";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0030-0039;
  src: url("/_assets/fonts/jost-digits-400.woff2") format("woff2");
}

@font-face {
  font-family: "Jost Digits";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  unicode-range: U+0030-0039;
  src: url("/_assets/fonts/jost-digits-500.woff2") format("woff2");
}

@font-face {
  font-family: "Jost Digits";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  unicode-range: U+0030-0039;
  src: url("/_assets/fonts/jost-digits-700.woff2") format("woff2");
}

@font-face {
  font-family: "Jost";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/_assets/fonts/jost-700.woff2") format("woff2");
}

body.activity {
  /* The panel's whole face. The fallback is the system stack the rest of the
   * bot's pages use, so nothing is unreadable if the download does not land. */
  --font-ui: "Jost", var(--font-sans);
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  /* Set here rather than inherited: this panel loads the design tokens and
   * nothing else, so nothing upstream has said how big text is. */
  font-size: 15px;
  line-height: 1.5;
  /* Discord's mobile clients report their notch and home-bar insets here, but
   * only after the first layout change, so each one keeps the browser's own
   * value as a fallback. */
  padding-top: var(--discord-safe-area-inset-top, env(safe-area-inset-top, 0px));
  padding-bottom: var(--discord-safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
  padding-left: var(--discord-safe-area-inset-left, env(safe-area-inset-left, 0px));
  padding-right: var(--discord-safe-area-inset-right, env(safe-area-inset-right, 0px));
  min-height: 100vh;
  /* Clipped, not hidden: with the container below, `hidden` made the body a
   * scroll box of its own that never scrolls, and nothing in it could stick
   * to the top of the window (the calendar image held over its options). */
  overflow-x: clip;
  /* What "wide" is measured against. The panel is a window of its own, so the
   * layout below asks how much room the panel has and not how big the screen
   * is: the preview page draws every board at panel width inside one browser
   * window, and a viewport query there answers about the window. */
  container: panel / inline-size;
  -webkit-font-smoothing: antialiased;
}

/* A plain link in the palette's link color, not the browser's blue, and
 * underlined on hover, as the website's pages draw one (`app.css`): the pets
 * manager's help link, a project named in a goal, the server list. `:where`
 * keeps this at an element's weight, so any class that styles its own link
 * (`.tb-ext`, the website's bar) still wins. */
:where(body.activity) a {
  color: var(--link);
  text-decoration: none;
}

:where(body.activity) a:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

#activity-root {
  max-width: 34rem;
  margin: 0 auto;
  padding: clamp(0.75rem, 3vw, 1.25rem);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* The line the panel shows while it has nothing else to show: connecting,
 * signing in, or explaining why it cannot. */
.activity-status {
  color: var(--muted);
  font-size: 0.95rem;
  margin: 0;
  padding: 3rem 1rem;
  text-align: center;
  line-height: 1.5;
}

.activity-status.is-error {
  color: var(--bad);
}

/* --- The strip above the board ------------------------------------------- */
/* One control on it so far, pushed to the end so it never competes with the
 * phase heading a line below. */

.panel-bar {
  display: flex;
  /* Where you are on the left, the way out of here on the right. */
  justify-content: space-between;
  align-items: center;
  gap: 0.4rem;
  min-height: 1.9rem;
  /* Held at the top while the rest of the panel scrolls under it, on every
   * screen. The dial and the two lines beside it are the smallest true clock
   * there is, and scrolling down to the count block, the record, or the
   * bottom of the start form used to take the countdown off the screen
   * entirely, which is the one thing somebody in a sprint is watching. The
   * padding and the pull back up keep the resting layout where it was. */
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--paper);
  padding: 0.35rem 0;
  margin-top: -0.35rem;
  /* Nothing pulled up under it: the times under the bar are its bottom
   * edge, and the board used to be drawn over them. */
  margin-bottom: 0.25rem;
}

/* Scrolled: the line over the bar and the times under it go, and the margin
 * takes their height so the page under the strip stays where it was (see
 * "The strip, once the page is scrolled" in activity-board.js). */
.bar-folded .panel-bar .tl-cap,
.bar-folded .panel-bar .tl-times {
  display: none;
}

.bar-folded .panel-bar {
  margin-bottom: calc(0.25rem + var(--bar-folded-by, 0px));
}

/* --- Where this is, and the hour ------------------------------------------
 *
 * The Activity opens over the conversation, so the channel name at the top of
 * Discord and the server icon down its side are both behind this panel. Two
 * of these open in two servers looked identical.
 *
 * Answered by showing it: the two names, a picture of the hour with the sprint
 * drawn on it, the two moments it draws written beside it, and how many people
 * have the panel open. No sentence anywhere saying what the Focus Lounge is,
 * because the same reader opens it every day. */
.where-am-i {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  flex: 1 1 auto;
}

/* The names give way first. The reading beside the dial never truncates: a
 * clock time cut in half is worse than a channel name cut in half. */
.where-names {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
  line-height: 1.25;
}

/* Quiet: this is not what anybody came to read, it is what they check without
 * meaning to. The channel is the darker of the two because it is the one that
 * names the room the sprint is in. */
.where-server {
  font-size: 0.7rem;
  color: var(--muted);
}

.where-channel {
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--ink);
}

.where-server,
.where-channel {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.where-hash {
  color: var(--muted);
  margin-right: 0.05em;
}

/* How many have this Focus Lounge open. */
.where-people {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  flex: 0 0 auto;
  padding: 0.2rem 0.5rem;
  font-size: 0.78rem;
  font-weight: 500;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--ink);
}

.where-people[hidden] {
  display: none;
}

.where-people svg {
  width: 0.9rem;
  height: 0.9rem;
  opacity: 0.8;
}

.where-n {
  font-family: "Jost Digits", var(--font-ui);
}
/* --- The sprint as a bar of time ------------------------------------------
 *
 * A line of its own under the names, the whole width of the strip. Each phase
 * is a segment as wide as its minutes (flex-grow is the minutes), with a floor
 * so two minutes of joining still has room for its name. The floor is the one
 * place the bar is not to scale, and the times under it are the exact answer.
 *
 * Colour means the phase: amber joining, green focus, terracotta time's up,
 * the same three the big clock wears, and blue for a chain's break, which is
 * not a time anybody is being asked to join in. */
.panel-bar {
  flex-wrap: wrap;
  --ph-join: var(--amber);
  --ph-focus: var(--good);
  --ph-counts: var(--accent);
  --ph-break: var(--break);
}

/* Not a control: pointing at it shows the tip. No selecting, so a double click
 * does not highlight the times, and a sideways drag reads along the bar rather
 * than panning. */
.tl {
  order: 10;
  flex: 1 0 100%;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0.15rem 0 0;
  color: var(--muted);
  user-select: none;
  -webkit-user-select: none;
  touch-action: pan-y;
}

.tl-cap {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.72rem;
}

.tl-cap b {
  font-weight: 500;
  color: var(--ink);
}

.tl-track {
  position: relative;
  display: flex;
  gap: 2px;
  height: 20px;
}

.tl.none .tl-track {
  border: 1px dashed var(--border);
  border-radius: 4px;
  justify-content: center;
  align-items: center;
  font-size: 0.7rem;
}

.seg {
  --c: var(--ph-focus);
  flex: var(--m, 1) 1 0;
  min-width: 3.7rem;
  position: relative;
  display: flex;
  align-items: center;
  padding: 0 0.35rem;
  border-radius: 4px;
  overflow: hidden;
  white-space: nowrap;
  background: color-mix(in srgb, var(--c) 22%, transparent);
  color: color-mix(in srgb, var(--c) 85%, var(--ink));
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}

/* A chain's rounds carry a number, not a word, and its breaks nothing. */
.tl.chain .seg.focus {
  min-width: 1.4rem;
  justify-content: center;
  padding: 0;
}

.seg.tiny {
  min-width: 0.9rem;
  padding: 0;
  justify-content: center;
}

.seg.join { --c: var(--ph-join); }
.seg.focus { --c: var(--ph-focus); }
.seg.counts { --c: var(--ph-counts); }
.seg.brk { --c: var(--ph-break); }

/* How far through the current segment the sprint is, and a mark at the spot. */
.seg .fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--done, 0%);
  background: color-mix(in srgb, var(--c) 55%, transparent);
}

.seg .here {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--done, 0%);
  width: 2px;
  margin-left: -1px;
  background: var(--ink);
  z-index: 0;
  display: none;
}

.seg.now .here {
  display: block;
}

.seg:not(.now) .fill {
  display: none;
}

.seg-name {
  position: relative;
  z-index: 1;
  /* A halo, so the mark for now can pass under a letter and leave it
   * readable. */
  text-shadow: 0 0 3px var(--paper), 0 0 2px var(--paper);
}

.seg.past {
  background: color-mix(in srgb, var(--c) 55%, transparent);
  color: var(--paper);
}

.seg.past .seg-name {
  text-shadow: none;
}

.seg.now {
  outline: 1.5px solid var(--c);
  outline-offset: 1px;
}

/* The start screen's bar is what the button would make, not a sprint anybody
 * is in: dashed, and nothing filled. */
.tl.preview .seg {
  background: transparent;
  border: 1.5px dashed color-mix(in srgb, var(--c) 70%, transparent);
}

/* Under each segment, its clock times: the start on the left, the end on the
 * right. Laid out the same way as the segments so each time sits under the
 * edge it names. */
.tl-times {
  display: flex;
  gap: 2px;
  min-height: 0.95rem;
  font-size: 0.7rem;
  font-family: "Jost Digits", var(--font-ui);
}

.tl-times > span {
  flex: var(--m, 1) 1 0;
  min-width: 3.7rem;
  display: flex;
  justify-content: space-between;
  white-space: nowrap;
}

.tl.chain .tl-times > span {
  min-width: 1.4rem;
}

.tl-times > span.tiny {
  min-width: 0.9rem;
}

/* More than twelve rounds (`timeline::MANY_ROUNDS`): at the widths above,
 * fifty of them would run off the screen. The bar goes to scale, with no
 * numbers inside it (the cap says which round), and the last time sits
 * inside the right edge. */
.tl.many .tl-track,
.tl.many .tl-times {
  gap: 1px;
}

.tl.chain.many .seg,
.tl.chain.many .tl-times > span {
  min-width: 0;
}

.tl.many .seg {
  border-radius: 1px;
}

.tl.many .seg-name {
  display: none;
}

.tl.many .tl-times > span.end {
  justify-content: flex-end;
}

.tl-times .key {
  color: var(--ink);
  font-weight: 500;
}

/* The pointed-at segment's name, times and countdown, under the bar. Outside
 * the board, placed by the script. */
.tl-tip {
  position: fixed;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.55rem;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  box-shadow: var(--shadow);
  color: var(--ink);
  font-size: 0.75rem;
  line-height: 1.3;
  white-space: nowrap;
  pointer-events: none;
}

.tl-tip[hidden],
.tl-tip-left[hidden],
.tl-tip-icon[hidden] {
  display: none;
}

/* The phase's emote, the one the screen-reader theme announces it with. */
.tl-tip-icon {
  flex: none;
  width: 1.7rem;
  height: 1.7rem;
}

.tl-tip-text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.tl-tip-left {
  font-family: "Jost Digits", var(--font-ui);
  color: var(--muted);
}

.cog {
  padding: 0.35rem;
  min-height: 0;
  border-color: transparent;
  background: transparent;
  color: var(--muted);
  line-height: 0;
}

.cog:hover {
  color: var(--ink);
  border-color: var(--border);
}

/* Menu and the cog, together at the end of the strip. */
.bar-end {
  display: flex;
  align-items: center;
  gap: 0.15rem;
}

/* Quiet like the cog beside it: a word, not a filled button. */
.menu-open {
  padding: 0.3rem 0.55rem;
  min-height: 0;
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}

.menu-open:hover {
  color: var(--ink);
  border-color: var(--border);
}

/* An unread notice: a dot on Menu's corner and at the end of the Menu
 * sheet's Notices entry, the accent the website's envelope dot uses. A dot
 * and not a count, because the count is on the line under the entry. */
.menu-open {
  position: relative;
}

.menu-dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--accent);
}

.menu-open .menu-dot {
  position: absolute;
  top: 0.2rem;
  right: 0.15rem;
}

.opt .menu-dot {
  margin-left: auto;
}

/* A press in the Menu sheet that did not land, under the entries. */
.menu-err {
  margin: 0.4rem 0 0;
  font-size: 0.85rem;
  color: var(--bad);
}

.menu-err[hidden] {
  display: none;
}

.cog-icon {
  width: 1.15rem;
  height: 1.15rem;
  display: block;
}

/* --- Settings ------------------------------------------------------------ */

/* The tabs: a strip across the top at panel width, a rail down the left once
 * the panel is wide (see the wide-window block at the end of this file). The
 * same buttons in the same order either way. Named apart from `.tabs`, the
 * joined-sprint tabs in the top bar. */
.set-tabs {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.set-tab-rail {
  display: flex;
  gap: 0.25rem;
  /* More tabs than fit slide rather than wrap: a second row would push the
   * pane down every time a tab was added. */
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: 1px solid var(--border);
}

.set-tab-rail::-webkit-scrollbar {
  display: none;
}

.set-tab {
  flex: 0 0 auto;
  padding: 0.4rem 0.75rem;
  min-height: 2.1rem;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}

.set-tab:hover {
  color: var(--ink);
}

.set-tab[aria-selected='true'] {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* Each tab's section, on a card. */
.set-pane {
  min-width: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: clamp(0.85rem, 3vw, 1.15rem);
}

.set-pane[hidden] {
  display: none;
}

.set-sec {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  min-width: 0;
}

.set-sec + .set-sec {
  border-top: 1px solid var(--border);
  padding-top: 1rem;
}

.set-sec h2,
.set-pane > #pets-manager .card-head h2 {
  margin: 0;
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The Companion tab is the pets manager, which is a card of its own on the
 * website's other pages. The pane is already the card here, so the manager
 * drops its own box and sits in the pane like any other tab's section. */
.set-pane > #pets-manager.card {
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
}

/* What is set, big, with who set it beside it. */
.set-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.6rem;
}

.set-value {
  min-width: 0;
  font-size: 1.1rem;
  font-weight: 500;
  overflow-wrap: anywhere;
}

.set-value.is-none {
  color: var(--muted);
}

/* Settings, Colors: a small page in each palette, its background with a
 * panel standing on it, wider than it is tall so it reads as a picture of a
 * page and not as a checkbox (the squares it replaced did, beside a tick).
 * Copies of `--paper` and `--surface` from that palette's block in
 * tokens.css, since a sample has to show a palette other than the one the
 * page is in; the edge is a lighter shade of the same hue, so a sample shows
 * on whichever palette is around it. */
.sample {
  position: relative;
  display: inline-block;
  flex: none;
  width: 2.6rem;
  height: 1.7rem;
  overflow: hidden;
  border-radius: 5px;
  border: 1px solid var(--sw-border);
  background: var(--sw-paper);
}

.sample::after {
  content: "";
  position: absolute;
  left: 20%;
  right: 20%;
  top: 32%;
  bottom: -1px;
  border: 1px solid var(--sw-border);
  border-bottom: 0;
  border-radius: 3px 3px 0 0;
  background: var(--sw-surface);
}

/* Match your device: Paper and the dark palette as one page split down the
 * middle, the panel straddling the two. */
.sample-pair {
  display: inline-flex;
  flex: none;
}

.sample-pair .sample {
  width: 1.3rem;
}

.sample-pair .sample:first-child {
  border-right: 0;
  border-radius: 5px 0 0 5px;
}

.sample-pair .sample:first-child::after {
  left: 40%;
  right: -1px;
  border-right: 0;
  border-radius: 3px 0 0 0;
}

.sample-pair .sample:last-child {
  border-left: 0;
  border-radius: 0 5px 5px 0;
}

.sample-pair .sample:last-child::after {
  left: -1px;
  right: 40%;
  border-left: 0;
  border-radius: 0 3px 0 0;
}

.sample[data-colors="ink"] { --sw-paper: #11151c; --sw-surface: #202835; --sw-border: #4a5568; }
.sample[data-colors="charcoal"] { --sw-paper: #17181a; --sw-surface: #292a2e; --sw-border: #55575d; }
.sample[data-colors="forest"] { --sw-paper: #121a16; --sw-surface: #212e28; --sw-border: #4a6157; }
.sample[data-colors="plum"] { --sw-paper: #18141d; --sw-surface: #2a2433; --sw-border: #5a4e6b; }
.sample[data-colors="terracotta"] { --sw-paper: #1c1813; --sw-surface: #2f2820; --sw-border: #5a4c3e; }
.sample[data-colors="paper"] { --sw-paper: #f6f0e4; --sw-surface: #fdfaf2; --sw-border: #b9a989; }

/* The two lists on the tab: a small heading over each, the rows as the
 * sheets' option rows are, the one in force on the panel's raised color with
 * its tick at the end of the row. */
.colors .colors-sub {
  margin: 0.6rem 0 0;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--ink);
}

.color-opts .color-opt {
  padding-left: 0.4rem;
  padding-right: 0.4rem;
}

.color-opts .color-opt[aria-current="true"] {
  background: var(--surface-2);
}

.color-opts .color-opt .ic {
  margin-left: auto;
}

/* Which day sprints count for: the settings in force, a little smaller than
 * the zone above them, which is the tab's own answer. How many sprints they
 * hold sits at the end of the line, quiet. */
.set-period-now {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  margin-bottom: 0.3rem;
}

.set-period-now .set-value {
  font-size: 0.98rem;
}

.set-period-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem 0.5rem;
}

.set-period-n,
.set-period small {
  flex: none;
  font-size: 0.8rem;
  color: var(--muted);
  white-space: nowrap;
}

/* The earlier settings: one fold under the button, newest first, to read. */
.set-fold {
  margin-top: 0.7rem;
}

.set-fold > summary {
  cursor: pointer;
  font-size: 0.88rem;
  color: var(--muted);
}

.set-periods {
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0;
}

.set-period {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0;
}

.set-period + .set-period {
  border-top: 1px solid var(--border);
}

.set-period > .l {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}

/* The day settings sheet's timezone: two options, the legend drawn as the
 * "Day ends at" label above it is. */
.days-zone {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.days-zone legend {
  padding: 0;
  margin-bottom: 0.3rem;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--muted);
}

/* "Use a different timezone": its search box only while it is picked
 * (owner, 2026-10-27: the choice reveals more). */
.days-own:not(:has(input[name="zone_mode"]:checked)) .tz-search {
  display: none;
}

.set-form {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

/* Under "Use a different timezone", lined up with its words, as the search
 * box in the Timezone sheet lines up with "Choose a timezone". */
.set-form .days-own .tz-search {
  padding: 0 0.2rem 0.4rem 2.2rem;
}

/* The control the panel's own stylesheet leaves to the browser, drawn the
 * way its text boxes are, as the Schedule's form draws its own. */
.set-form select {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  color-scheme: dark;
  padding: 0.55rem 0.7rem;
  font: inherit;
  min-width: 0;
  width: 100%;
}

:root[data-theme="paper"] .set-form :is(select, input) {
  color-scheme: light;
}

.set-form select:focus-visible,
.set-form input:focus-visible,
label.tz-pick input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* A match to tick, in a form that has more in it than the zone. */
label.tz-pick {
  cursor: pointer;
}

label.tz-pick:hover {
  background: var(--surface-2);
}

label.tz-pick input {
  flex: none;
  margin: 0 0.25rem 0 0.35rem;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.3rem;
}

.tag {
  font-size: 0.72rem;
  padding: 0.02rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  white-space: nowrap;
}

.tag.warn {
  color: var(--amber);
  border-color: color-mix(in srgb, var(--amber) 45%, transparent);
}

.set-sec .say {
  margin: 0;
  font-size: 0.85rem;
}

.set-sec .say a {
  color: var(--link);
}

.set-sec .hint,
.sheet .hint {
  margin: 0;
  font-size: 0.76rem;
  color: var(--muted);
}

.set-sec .btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.15rem;
}

.set-sec .btn-row button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* A press that leaves for another page: Privacy's way to share the server
 * list on the website, which is the website's Discord sign-in. Drawn as the
 * buttons beside it. */
.set-sec .btn-row a.set-link {
  display: inline-flex;
  align-items: center;
  font-weight: 500;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
  padding: 0.5rem 0.9rem;
  min-height: 2.4rem;
  text-decoration: none;
}

.set-sec .btn-row a.set-link:hover {
  border-color: var(--accent);
}

/* Beside a Notifications setting a press changed: Saved, quiet, or Not saved
 * with the status, in the colour of a failure. */
.save-state {
  font-size: 0.8rem;
  color: var(--muted);
  text-align: right;
}

.save-state.is-bad {
  color: var(--bad);
}

.settings-flag {
  margin: 0 0 0.75rem;
}

.settings-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  /* Clear of the strip above. The heading used to sit inside the settings card
   * and take its padding; out here on its own it was landing under the sticky
   * bar's pull-up. */
  margin-top: 0.55rem;
}

.settings-head h1 {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
}

/* The way back. Quiet, but not hidden: it is the only way off this screen, and
 * the board behind it has stopped refreshing until it is pressed. It sits in
 * the strip at the top, where the cog that opened this screen was. */
.panel-bar .back {
  padding: 0.3rem 0.55rem;
  min-height: 0;
  background: transparent;
  border-color: transparent;
  color: var(--muted);
  margin-left: -0.35rem;
}

.panel-bar .back:hover {
  color: var(--ink);
  border-color: var(--border);
}

/* The clock the zone makes, which is the part anybody can check without
 * knowing what an IANA name is. */
.tz-now {
  margin: 0;
  font-size: 0.9rem;
  color: var(--muted);
}

.tz-now .tz-clock {
  color: var(--ink);
  font-weight: 500;
}

/* The figures, in the face that holds still while they change: Jost has no
 * tabular figures. Same family the countdown uses. */
.tz-clock {
  font-family: 'Jost Digits', var(--font-ui);
}

/* The device, when it is on a different clock: an amber dot, the same colour
 * as the "differs from device" tag above it. */
.tz-device {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.tz-device::before {
  content: '';
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--amber);
  transform: translateY(-1px);
}

.tz-device b {
  font-weight: 500;
  color: var(--ink);
}

/* --- Settings: sprint times ---------------------------------------------- */

/* The state line, then one group of days per line. Switched off, the hours
 * stay on screen, dimmed, under a state line that says Off. */
.times-state {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 500;
}

.times-summary {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 1rem;
}

.times-summary[data-off],
.times-grid[data-off] {
  opacity: 0.55;
}

.set-sec .say.is-bad,
.times-err {
  color: var(--bad);
}

.times-err {
  margin: 0;
  font-size: 0.85rem;
}

.times-zone {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.5rem;
  margin: 0;
  font-size: 0.85rem;
  color: var(--muted);
}

.times-zone b {
  font-weight: 500;
  color: var(--ink);
}

/* A press that reads as a link: Change beside the zone. */
button.linkish {
  min-height: 0;
  padding: 0;
  background: none;
  border: 0;
  color: var(--link);
  font-size: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.times-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin: 0;
}

.times-switch {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.times-switch-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 500;
  cursor: pointer;
}

.times-switch-row .switch:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* The week: a day's name in a narrow column, its ranges and Add hours beside
 * it. */
.times-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  transition: opacity 0.15s ease;
}

.times-day {
  display: grid;
  grid-template-columns: 2.6rem minmax(0, 1fr);
  gap: 0.5rem;
  align-items: start;
  padding: 0.45rem 0;
}

.times-day + .times-day {
  border-top: 1px solid var(--border);
}

.times-dayname {
  padding-top: 0.4rem;
  font-weight: 500;
}

.times-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
  min-width: 0;
}

.times-ranges {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  width: 100%;
}

.times-ranges:empty {
  display: none;
}

.times-range {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0.4rem;
}

.times-range select {
  font: inherit;
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.3rem 0.4rem;
  min-height: 2rem;
}

.times-range select:hover {
  border-color: var(--accent);
}

.times-to,
.times-next {
  color: var(--muted);
  font-size: 0.85rem;
}

.times-add {
  min-height: 2rem;
  padding: 0.2rem 0.55rem;
  font-size: 0.85rem;
}

/* The cross that takes a range away, square and at the end of its line. */
.times-range .times-remove {
  width: 2rem;
  min-height: 2rem;
  padding: 0;
  margin-left: auto;
  font-size: 1.1rem;
  line-height: 1;
}

.times-range .times-same {
  flex-basis: 100%;
  color: var(--bad);
}

.times-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.times-need {
  margin: 0;
}

.times-need-row form {
  margin: 0;
}

/* --- Settings: the sheets ------------------------------------------------ */

/* Change and Change companion open one of these. A bottom sheet on a phone, a
 * centred dialog once the window is wide. */
dialog.sheet {
  width: 100%;
  max-width: 100%;
  max-height: 85dvh;
  margin: auto auto 0;
  padding: 0.9rem 1rem 1.1rem;
  overflow-y: auto;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: 14px 14px 0 0;
  box-shadow: var(--shadow);
}

dialog.sheet[open] {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

dialog.sheet::backdrop {
  background: rgba(0, 0, 0, 0.55);
}

@media (min-width: 46rem) {
  dialog.sheet {
    width: 28rem;
    margin: auto;
    border-bottom: 1px solid var(--border);
    border-radius: var(--radius);
  }
}

.sheet-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
}

/* Written out in full: the sheet sits inside a section, whose own small
 * uppercase heading rule would otherwise reach it. */
dialog.sheet .sheet-head h2 {
  margin: 0;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
}

.sheet-close {
  flex: none;
  min-height: 0;
  padding: 0.25rem 0.5rem;
}

/* Back, at the foot of every popup (`sheet.rs`). On a phone, Discord uses the
 * back gesture to shrink the whole panel, so this button is the way to leave
 * a popup. The full width of the popup and tall enough for a thumb, but quiet
 * like the screen's own Back (`.panel-bar .back`): drawn as a filled button it
 * was the largest control on the popup, outweighed Save and the vote buttons,
 * and on a wide window looked like a second text box under the last one.
 * Pushed to the bottom of a start screen panel, which `keepHeight` can hold
 * taller than its contents. */
.sheet-foot {
  margin-top: auto;
}

.sheet-back {
  width: 100%;
  min-height: 2.75rem;
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}

.sheet-back:hover {
  color: var(--ink);
  border-color: var(--border);
}

/* The options in a sheet, the one in force ticked. */
.opts {
  list-style: none;
  margin: 0;
  padding: 0;
}

.opts > li + li {
  border-top: 1px solid var(--border);
}

.opts form {
  margin: 0;
}

.opt,
.opt-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  min-height: 0;
  padding: 0.55rem 0.2rem;
  text-align: left;
  font: inherit;
  font-weight: 400;
  color: var(--ink);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
}

button.opt {
  cursor: pointer;
}

button.opt:hover {
  background: var(--surface-2);
}

.opt .ic,
.opt-head .ic {
  flex: none;
  width: 1.4rem;
  text-align: center;
  color: var(--good);
}

.opt .l,
.opt-head .l {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

.opt small,
.opt-head small {
  font-size: 0.76rem;
  color: var(--muted);
}

.opt > .tz-clock {
  margin-left: auto;
  font-size: 0.85rem;
  color: var(--muted);
  white-space: nowrap;
}

/* More actions: the word in the card's corner that opens it, and its
 * entries. The icons are marks, not ticks, so they are muted; Cancel reads in
 * the warning colour. */
.head-actions {
  min-height: 0;
  padding: 0.15rem 0.1rem;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.85rem;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
  text-underline-offset: 0.2em;
  cursor: pointer;
}

.head-actions:hover,
.head-actions:focus-visible {
  color: var(--ink);
  text-decoration-color: currentColor;
}

/* Cancelling asks in a sheet of its own: what will happen, then the one red
 * press. */
.cancel-sheet .cancel-line {
  margin: 0 0 0.9rem;
  color: var(--ink);
}

/* Before the start, the leave for somebody who only wants out: between what
 * the vote does and the press that starts it, quieter than both. */
.cancel-sheet .cancel-leave {
  margin: -0.4rem 0 0.9rem;
}

.cancel-sheet .cancel-leave p {
  margin: 0;
  color: var(--muted);
}

.cancel-sheet button.danger {
  background: var(--bad);
  border-color: var(--bad);
  color: var(--on-bad);
}

/* In the wait after a vote failed, the sheet says when a new one can start and
 * the press is switched off until then. */
.cancel-sheet button.danger:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.actions-sheet .opt .ic {
  color: var(--muted);
}

.opt.danger .l > span {
  color: var(--bad);
}


.tare-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.tare-form .field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.tare-form .field .label {
  font-size: 0.8rem;
  color: var(--muted);
}

/* The search box sits under "Choose a timezone", lined up with its label. */
.tz-search {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0 0.2rem 0.4rem 2.2rem;
}

.tz-row {
  display: flex;
  gap: 0.4rem;
}

.tz-row input {
  flex: 1 1 auto;
  min-width: 0;
}

/* The verb keeps its whole word and the box gives up the width. */
.tz-row button {
  flex: 0 0 auto;
  white-space: nowrap;
}

.tz-row button[hidden] {
  display: none;
}

.tz-matches:empty {
  display: none;
}

.tz-matches .empty {
  padding: 0.5rem 0.2rem 0.5rem 2.2rem;
  font-size: 0.85rem;
  color: var(--muted);
}

/* A box that takes a number, drawn as one: the width a word count needs, with
 * its unit on the face. Beside a free-text box of the same height and colour,
 * the label above is the only thing keeping them apart, and a label is not a
 * signifier. */
.with-unit {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.with-unit input[type="text"] {
  flex: 0 1 9rem;
  min-width: 0;
  font-variant-numeric: tabular-nums;
}

.with-unit .unit {
  color: var(--muted);
  font-size: 0.85rem;
}

/* --- The card ----------------------------------------------------------- */

.sprint {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: clamp(0.9rem, 4vw, 1.25rem);
  box-shadow: var(--shadow);
  /* The accent this card is drawn in, swapped by the phase below, so one name
   * carries the colour through the clock, the bar and the primary button. */
  --tint: var(--accent);
  --tint-soft: var(--accent-soft);
}

.sprint[data-phase="waiting"] {
  --tint: var(--amber);
  --tint-soft: color-mix(in srgb, var(--amber) 16%, transparent);
}

/* Green while the writing is running, because that is the stretch that is
 * going well and nothing is being asked of anybody. The terracotta moves to
 * the phase that does ask: time is up and a count is wanted. */
.sprint[data-phase="writing"] {
  --tint: var(--good);
  --tint-soft: color-mix(in srgb, var(--good) 16%, transparent);
}

.sprint[data-phase="collecting"] {
  --tint: var(--accent);
  --tint-soft: var(--accent-soft);
}

.sprint-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* Close on the finished board, at the right of its heading, and the way
 * back to the results over the start screen once it is closed. */
.sprint-head .results-close {
  margin-left: auto;
}

.sprint-head .results-close button {
  min-height: 0;
  padding: 0.15rem 0.5rem;
  font-weight: 400;
}

.results-back {
  margin: 0 0 0.5rem;
}

.results-back button.link {
  min-height: 0;
  padding: 0;
}

.phase {
  font-weight: 500;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
  color: var(--ink);
}

.round,
.sprint-span {
  color: var(--muted);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

/* How long the sprint is, beside the phase. The countdown says how much is
 * left; on the faces that draw a proportion rather than spell one out, this is
 * the other half of the reading. */
.sprint-span {
  margin-left: auto;
}

/* --- The countdown, six ways ---------------------------------------------
 *
 * Every face is this one element with a different class on it, and every one of
 * them is drawn from the same four numbers, which the server sets once and the
 * script rewrites every second:
 *
 *   --gone, --left   how much of this phase has passed, and has not (0 to 1)
 *   --hand, --sweep  where a minute and a second hand would point
 *
 * Nothing below runs a script of its own, and a seventh face would need none.
 * The number itself is always there, sized by --num-size, because it is what
 * the script ticks and what a screen reader reads out; where a drawing carries
 * the meaning the number is set small beside it, never removed.
 *
 * Tabular figures throughout, so a ticking clock does not shuffle sideways
 * every time a 1 goes past. */
/* One box for the countdown, whichever of the six is in it.
 *
 * They are drawings of very different shapes: a line of text is 52 pixels tall
 * and a kitchen timer is 182, so changing face used to shove the roster and the
 * count block a third of a screen up or down, and so did reaching zero. The box
 * is as tall as the tallest of them and everything sits in the middle of it.
 * The bar is inside for the same reason: it is on one face only.
 *
 * The cost is air around the two faces made of nothing but numbers. That is the
 * trade: air that stays put beats a panel that jumps under a pointer.
 *
 * Since the writing's dials grew (2026-10-29) there are two boxes in one
 * column: one for the Clock face and the Kitchen timer, which the reader swaps,
 * and a shorter one for the faces of the other phases. See below. */
.clock-slot {
  /* The size of the drawn faces, one number per layout: the clock face and the
   * kitchen timer are this wide (and as tall), the hourglass nine tenths of it,
   * and the time under them and the gap above the time follow from it. The
   * tile and the two-column layout set their own in activity-sizes.css.
   *
   * One column: 56% of the panel, so a phone keeps a fifth of the card free on
   * either side, and never more than 30% of the window's height, so the roster
   * under it stays on screen in a short window. 13rem is where it stops
   * growing: past that the card is wide enough for two columns to be the
   * better answer. */
  --face-w: clamp(8rem, min(56cqi, 30vh), 13rem);
  /* The time under the drawing, sized against it rather than the window so the
   * two stay in proportion at every size. A fifth of the dial: at the medium
   * weight below, a smaller share read as a footnote to the drawing. */
  --face-num: calc(var(--face-w) * 0.2);
  /* At least half the height of the figures between the drawing and the time,
   * so the two read as a picture and its caption, not one block. */
  --face-gap: calc(var(--face-num) * 0.55);
  /* The box for the two faces of the writing, the Clock face and the Kitchen
   * timer. Worked out, not measured: a drawing --face-w square, the gap, one
   * line of the time, the face's padding (0.75rem) and the clock's margins
   * (1.2rem). The two are the same height, so a press that swaps one for the
   * other moves nothing under them. */
  --clock-h-drawn: calc(var(--face-w) + var(--face-gap) + var(--face-num) * 1.05 + 1.95rem);
  --clock-h: var(--clock-h-drawn);
  min-height: var(--clock-h);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Joining, the breaks and Time's up keep the box they had before the writing's
 * dials grew: about 100px shorter on a phone. The taller box pushed the Join
 * button below the fold at 375 by 812, and Time's up's Total another 80px
 * down. Nobody swaps these faces, so nothing moves under a pointer. The cost:
 * the roster now moves about 100px at the turn into the writing and again at
 * the turn out of it, where before it stayed put across every phase.
 *
 * Measured, not guessed, and measured on both sides of the bend: the tallest
 * of these is the hourglass below, at 7.5rem. Re-measure if one changes size,
 * at 304, 375 and 400.
 *
 * `:where` keeps these at the specificity of `.clock-slot`, so the tile and
 * the two-column layout in activity-sizes.css, which load later, still set
 * their own. */
.clock-slot:where(:has(> .clock:is(.face-digital, .face-bold, .face-ring, .face-hourglass))) {
  --clock-h: clamp(9rem, calc(44vw + 2.4rem), 12.8rem);
}

/* The hourglass at the size it had in one column, 7.5rem at most: its drawing
 * is nine tenths of --face-w, so this is that size divided by 0.9. The time
 * and the gap above it follow from it, and the three together fit the box
 * above. */
.clock-slot:where(:has(> .face-hourglass)) {
  --face-w: clamp(5.56rem, 33.33vw, 8.33rem);
}

.clock {
  margin: 0.5rem 0 0.7rem;
  color: var(--tint);
  --num-size: clamp(2.2rem, 12vw, 3.1rem);
}

.clock-num {
  font-size: var(--num-size);
  /* Not `font-variant-numeric`, which has nothing to act on here. The equal
   * advances are built into the figures themselves; see the @font-face above. */
  font-family: "Jost Digits", var(--font-ui);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.05;
  white-space: nowrap;
}

.clock-words {
  font-size: 1rem;
  font-weight: 500;
  color: var(--muted);
}

/* During the writing the drawing is a button that swaps it for the other face,
 * and the time left under it is a button that dims it. Both look as they did
 * as plain elements. The time keeps `.clock-num`'s font. */
.face-swap,
button.clock-num {
  background: none;
  border: 0;
  margin: 0;
  color: inherit;
  min-height: 0;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.face-swap {
  display: block;
  padding: 0;
  font: inherit;
  line-height: 0;
}

button.clock-num {
  padding: 0 0.3rem;
  transition: opacity 0.2s ease-out;
}

.clock-dim[aria-pressed="true"] {
  opacity: 0.3;
}

/* A mouse only: after a tap, a touch screen keeps the hover until the next
 * tap elsewhere, and the time would stay half bright. */
@media (hover: hover) {
  .clock-dim[aria-pressed="true"]:hover {
    opacity: 0.6;
  }
}

/* At zero the time left is the one thing everybody is looking for. */
.clock.is-over .clock-dim {
  opacity: 1;
}

/* The drawing beside the number. One box, so every face lines up with the ones
 * above and below it whatever shape it happens to be. */
.face-svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* Digital: a sentence. The number is what somebody is looking at and the words
 * are there so they know what it is counting to. Set at one size the line is
 * wider than the panel and wraps through the middle of the number, which is
 * the part that must not break. */
.face-digital {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
}

/* Big numbers: the time and nothing else. The phase word above it already says
 * what it is counting to, so the words come off and the space goes into the
 * figures. */
.face-bold {
  --num-size: clamp(3rem, 21vw, 4.6rem);
  display: flex;
  justify-content: center;
  padding: 0.2rem 0 0.5rem;
}

.face-bold .clock-num {
  letter-spacing: -0.045em;
}

/* Ring: the number in the middle of a circle that empties anticlockwise. The
 * two sit in the same grid cell rather than one on top of the other, so the
 * ring's size is what decides the block's height. */
.face-ring {
  --num-size: clamp(1.45rem, 7.5vw, 1.9rem);
  display: grid;
  place-items: center;
  padding: 0.2rem 0 0.5rem;
}

.face-ring .face-svg,
.face-ring > .clock-num {
  grid-area: 1 / 1;
}

.face-ring .face-svg {
  width: clamp(7.5rem, 44vw, 10rem);
}

.face-ring .ring-track {
  fill: none;
  stroke: var(--paper);
  stroke-width: 8;
}

/* The minute dots inside the ring. Quiet enough to read the time over, dark
 * enough to count. */
.face-ring .ring-dot {
  r: 0.9;
  fill: color-mix(in srgb, var(--ink) 34%, transparent);
}

.face-ring .ring-dot.is-major {
  r: 1.9;
  fill: color-mix(in srgb, var(--ink) 62%, transparent);
}

/* No transition. The script writes these every frame from the deadline itself,
 * so there is no gap to ease across; a transition on top of that is a second
 * clock a fifth of a second behind the first, and the two of them together are
 * what made the countdown move in lurches. */
.face-ring .ring-fill {
  /* The circumference of r=43, which is what a dash pattern is measured in. */
  --circ: 270.18;
  fill: none;
  stroke: var(--tint);
  stroke-width: 8;
  stroke-linecap: round;
  stroke-dasharray: calc(var(--left, 1) * var(--circ)) var(--circ);
  transform: rotate(-90deg);
  transform-box: view-box;
  transform-origin: 50% 50%;
}

/* A wall clock read as a countdown: the hand at the time remaining on a sixty
 * minute dial, so a twenty minute sprint starts at twenty past and walks back
 * to the top, and the last minute is the thin hand going round once. */
.face-analog,
.face-wallclock,
.face-hourglass,
.face-timer {
  --num-size: var(--face-num);
  display: grid;
  justify-items: center;
  gap: var(--face-gap);
  /* A little more below than above, so the time is not pressed against the
   * box's lower edge. */
  padding: 0.25rem 0 0.5rem;
}

/* The time under a drawing is its caption, set in the medium weight: at 700
 * the figures outweighed the dial's hands. Jost Digits is tabular and needs no
 * tracking at 500; the tighter spacing it had was a fix for the bold. */
:is(.face-analog, .face-wallclock, .face-hourglass, .face-timer) > .clock-num {
  font-weight: 500;
  letter-spacing: 0;
}

.face-analog .face-svg,
.face-timer .face-svg {
  width: var(--face-w);
}

.face-analog .dial-face {
  fill: var(--paper);
  stroke: var(--border);
  stroke-width: 1.5;
}

.face-analog .dial-ticks line {
  stroke: var(--muted);
  stroke-linecap: round;
}

.face-analog .hand-minute line {
  stroke: var(--tint);
  stroke-width: 4.5;
  stroke-linecap: round;
}

.face-analog .hand-second line {
  stroke: var(--muted);
  stroke-width: 1.6;
  stroke-linecap: round;
}

.face-analog .hand-minute,
.face-analog .hand-second {
  transform-box: view-box;
  transform-origin: 50px 50px;
}

.face-analog .hand-minute {
  transform: rotate(var(--hand, 0deg));
}

.face-analog .hand-second {
  transform: rotate(var(--sweep, 0deg));
}

.face-analog .dial-pin {
  fill: var(--tint);
}

/* Sand from one bulb to the other. Both piles are shapes scaled against the
 * neck and the floor and clipped to the bulb, so the sand stays inside the
 * glass whatever the fraction. */
.face-hourglass .face-svg {
  /* Nine tenths of the dials' box: the glass is narrow, and at this share it
   * stands nearly as tall as a dial does, without crowding the time under it. */
  width: calc(var(--face-w) * 0.9);
}

.face-hourglass .glass-body {
  fill: var(--paper);
}

.face-hourglass .glass {
  fill: none;
  stroke: var(--border);
  stroke-width: 1.6;
  stroke-linejoin: round;
}

.face-hourglass .glass-shine {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.4;
  stroke-linecap: round;
  opacity: 0.18;
}

/* The plates are drawn like the dials' bezel: the second surface colour with
 * a line round it, so the three faces share one finish. */
.face-hourglass .hg-plate {
  fill: var(--surface-2);
  stroke: var(--muted);
  stroke-width: 1.2;
}

.face-hourglass .hg-post {
  fill: var(--muted);
  opacity: 0.55;
}

.face-hourglass .sand-top,
.face-hourglass .sand-bottom,
.face-hourglass .sand-fall {
  fill: var(--tint);
}

.face-hourglass .sand-top,
.face-hourglass .sand-bottom {
  transform-box: view-box;
}

.face-hourglass .sand-top {
  transform-origin: 50px 50px;
  /* A height worked out from the volume, not the volume itself: see
   * `Face::sand_top`. */
  transform: scaleY(var(--sand-top, 1));
}

.face-hourglass .sand-bottom {
  transform-origin: 50px 88px;
  transform: scaleY(var(--sand-bottom, 0));
}

/* The falling stream, which is only there while something is falling: at either
 * end of the sprint one of the two fractions is zero, and this goes with it. */
.face-hourglass .sand-fall {
  opacity: calc(min(var(--gone, 0), var(--left, 1)) * 20);
}

/* A twist-dial kitchen timer. The wedge is a circle stroked at twice its own
 * radius, which fills it in from the middle out: a pie slice out of the same
 * dash pattern an arc needs, and no path to get wrong. */
.face-timer .timer-body {
  fill: var(--paper);
}

.face-timer .timer-knob {
  fill: var(--muted);
}

/* The dial's edge, drawn over the wedge so the wedge stops at a line. A
 * hairline, the same as the clock face's dial edge: the bezel round it is the
 * frame now. The bezel and the marks are styled with the clock face's in
 * activity-faces.css. */
.face-timer .timer-rim {
  fill: none;
  stroke: var(--border);
  stroke-width: 0.75;
}

.face-timer .timer-wedge {
  /* The circumference of r=21. */
  --circ: 131.95;
  fill: none;
  stroke: var(--tint);
  stroke-width: 42;
  /* Minutes of an hour, not the fraction of the phase: ten minutes on a timer
   * whose face is an hour is a sixth of the dial. See `Face::dial_left`. */
  stroke-dasharray: calc(var(--dial-left, 1) * var(--circ)) var(--circ);
  transform: rotate(-90deg);
  transform-box: view-box;
  transform-origin: 50px 52px;
}

/* The countdown has run out and the next phase has not landed. A sentence, not
 * a number, so it is sized as one, and the drawing has gone: there is no part
 * of the phase left for it to show. */
.clock.is-over {
  --num-size: clamp(1.4rem, 7vw, 1.9rem);
  display: flex;
  justify-content: flex-start;
}

.clock.is-over .clock-num {
  color: var(--muted);
  white-space: normal;
}

/* The bar has to read as a bar before it reads as progress, so the unfilled
 * part is drawn dark against the card rather than one shade off it. Filled
 * against nothing looks like the clock has been underlined. */
.track {
  height: 5px;
  border-radius: 999px;
  background: var(--paper);
  border: 1px solid var(--border);
  overflow: hidden;
  margin-bottom: 0.85rem;
}

.track-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--tint);
}

/* Zero, on every face the same.
 *
 * The drawing stands finished above the words rather than leaving the screen:
 * a ring closed, the sand down, the hand at the top. Whatever the face put the
 * number in the middle of, this is a line of text, and a line of text does not
 * go inside a ring. */
.clock.is-over {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.35rem;
}

/* A drawn face stands finished in the middle of its box, where it was
 * counting down. The `flex-start` set above put the grid's one column at the
 * left edge, and the drawing and the line under it with it. The faces made of
 * type keep it: their number already sits at the left before zero, and
 * centring it at zero would make it jump. */
.clock.is-over:where(.face-analog, .face-wallclock, .face-hourglass, .face-timer) {
  justify-content: stretch;
  /* The drawing shrinks to 0.6 of itself, so the gap under it and the time
   * shrink by the same share: the gap the countdown had, and the caption
   * proportion, still hold at zero. A fixed gap left the time 6px under a
   * 173px drawing. `:where` so the tile can set its own (activity-sizes.css). */
  gap: calc(var(--face-gap) * 0.6);
}

.clock.is-over > .clock-num,
.clock.is-over .face-svg {
  grid-area: auto;
}

.clock.is-over > .clock-num {
  font-size: clamp(1rem, 5.5vw, 1.3rem);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--muted);
  animation: clock-over 0.45s ease-out;
}

/* Under a drawing, the time at zero is 0.6 of its countdown size, the share
 * the drawing shrinks by, so the caption stays in proportion to its picture.
 * Never under 1rem, the floor it had before: the hourglass in one column is
 * small enough that 0.6 of its time would be 15px. The faces made of type
 * keep the size above. After the rule above so it wins at the same
 * specificity. */
.clock.is-over:where(.face-analog, .face-wallclock, .face-hourglass, .face-timer) > .clock-num {
  font-size: max(1rem, calc(var(--face-num) * 0.6));
}

.clock.is-over .face-svg {
  /* The same share of the face's box at every size, so the finished drawing
   * still sits in the middle of the box it was counting down in. */
  width: calc(var(--face-w) * 0.6);
  opacity: 0.75;
  transition: opacity 0.45s ease-out, width 0.45s ease-out;
}

@keyframes clock-over {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* The turn of a phase, which is the one board change worth showing.
 *
 * Not every swap: the board refreshes itself every three seconds, and a panel
 * that breathed on each of those would be worse than one that never moved. The
 * script compares the phase it just replaced with the phase that arrived and
 * puts this on only where they differ. */
.sprint.phase-turned {
  animation: phase-turn 0.4s ease-out;
}

@keyframes phase-turn {
  from {
    opacity: 0;
    transform: translateY(-0.35rem);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* --- When it starts -------------------------------------------------------
 *
 * The line that says when a press would start the sprint. The clock times it
 * comes to are in the strip and under Start. */
/* The dial that used to sit beside this is in the panel's corner now, on every
 * screen rather than on this one form. */
.field.when {
  flex-direction: column;
  gap: 0.35rem;
}

/* The stretch of minutes the sprint may begin in. */
.when-span {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--tint);
  min-width: 3px;
  border-radius: 999px;
}

/* What happens if somebody presses it now. Sized to be read, not skimmed
 * past. */
.when-says {
  margin: 0;
  font-size: 0.95rem;
  color: var(--ink);
  /* Fixed-advance figures, for the reason above: this whole sentence is
   * rewritten every second and the numbers in it must not move. */
  font-family: "Jost Digits", var(--font-ui);
  min-height: 1.2em;
}

/* The minute has fallen out of the back of the window and another has taken
 * over. A moment's fade, so the number is seen to change rather than appearing
 * to jump about on its own. */
.when-says.is-new {
  animation: when-swap 0.45s ease-out;
}

@keyframes when-swap {
  from {
    opacity: 0.15;
  }
  to {
    opacity: 1;
  }
}

/* --- What the number in the box would do ---------------------------------
 *
 * Three figures, under the box, while it is being typed in. The one somebody is
 * looking for is set large; the other two are there because without them the
 * first is a number with no units, and a word count with no units is how a
 * writer files a nine hundred word deletion meaning to add a hundred.
 *
 * An empty block takes no room at all: a panel that reserves space for an
 * answer it does not have yet jumps every time one arrives. */
.sum-block {
  margin-top: 0.5rem;
  /* Holding its place whatever is in it, empty included: the answer arrives,
   * changes and leaves on every keystroke, and a block that sizes to its
   * content takes the panel with it each time.
   *
   * Measured, not guessed. At a 300 pixel panel the answers this block draws
   * run from 18px (a refusal: "that is not a number") to 67px (two full
   * sentences about what a press would do), so reserving only the empty case
   * left a 38 pixel drop on the first character typed. This covers everything
   * but the longest answer at the narrowest panel, which still grows a little.
   * Reserving that too would leave a permanent hole under every short one. */
  min-height: 3.5rem;
}

/* What a number would do, as two sentences.
 *
 * One loud thing: the line that says what pressing does. It is set at reading
 * size rather than display size, because it competes with the button beside it
 * and the button is the thing being pressed. The old row put its largest,
 * brightest figure on the number the parser echoed back, and its smallest grey
 * one on the fact that the press was about to lose nine hundred words. */
.sum {
  margin: 0.35rem 0 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding-left: 0.6rem;
  /* A rail, transparent until there is something to say with it, so the block
   * does not shift sideways when it turns red. */
  border-left: 2px solid transparent;
}

.sum-head {
  margin: 0;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* Everything the head does not carry, kept and named. The starting count lives
 * here and is never dropped: it is the figure a reader goes looking for and
 * cannot work out from the others. */
.sum-body {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Going down. The words say so too, so this is confirmation rather than the
 * only channel. */
.sum.is-down {
  border-left-color: var(--bad);
}

.sum.is-down .sum-head {
  color: var(--bad);
  font-weight: 600;
}

.sum-note {
  margin: 0.35rem 0 0;
  font-size: 0.82rem;
  color: var(--muted);
  line-height: 1.4;
}

/* "Did you mean:" on its own line, then the chips stacked under it, each as
 * wide as the widest, so two of them read as two rows of one list rather than
 * wrapping wherever the line ran out. */
.sum-picks {
  display: grid;
  grid-template-columns: minmax(0, max-content);
  gap: 0.35rem;
  margin-top: 0.45rem;
}

.sum-mean {
  font-size: 0.85rem;
  color: var(--muted);
}

/* Two figures on one chip: what it would leave as written, and the total it
 * would leave behind. The same two the block above it shows, so a choice is
 * made by comparing like with like rather than by reading a command. Square
 * corners, like the length chips. */
.sum-picks .pick {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.9rem;
  border-radius: var(--radius-sm);
  text-align: left;
}

.sum-pick-net {
  color: var(--ink);
  font-weight: 500;
}

.sum-pick-net.down {
  color: var(--bad);
}

.sum-pick-total {
  color: var(--muted);
}

/* Finishing on a round time, among the plain lengths and shaped like them.
 *
 * Two lines: the length it comes to, which is what it has in common with
 * everything else in the row, and the time it ends on, which is the reason for
 * choosing it over one of them. */

/* Stated, and stated first: `[hidden]` is `display: none` in the browser's own
 * stylesheet, and the rule below would quietly beat it. A mark that does not
 * make a sprint has to actually go, not just say it has. */
.end-mark[hidden] {
  display: none;
}

.end-mark {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.02rem;
  padding: 0.25rem 0.6rem;
  min-height: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: 0.9rem;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  line-height: 1.25;
}

.end-time {
  color: var(--muted);
  font-size: 0.76em;
}

.end-mark[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--ink);
  font-weight: 600;
}

.end-mark[aria-pressed="true"] .end-time {
  color: inherit;
}


/* Small chips: the parser's alternatives, and the votes to cancel. */
.picks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.pick {
  font-family: inherit;
  font-size: 0.76rem;
  padding: 0.2rem 0.55rem;
  min-height: 0;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.pick:hover {
  color: var(--ink);
}

.pick[aria-pressed="true"] {
  color: var(--ink);
  border-color: var(--tint);
  background: var(--tint-soft);
}

/* The companion sprinting alongside somebody. Quiet, and after their name: it
 * is theirs, and the row is about them. Truncated rather than wrapped, because
 * a roster of one-line rows that sometimes becomes two is a roster that jumps
 * every time somebody joins. */
.pet {
  display: inline-flex;
  align-items: baseline;
  gap: 0.2rem;
  min-width: 0;
  color: var(--muted);
  font-size: 0.82rem;
}

.pet-look {
  flex: 0 0 auto;
  font-size: 0.95rem;
  line-height: 1;
}

.pet-look .md-emoji {
  height: 1em;
  vertical-align: -0.12em;
}

/* The companion's drawing, at the size of the words it sits between. The pets
 * manager's own rule sizes these for a tile, which on a roster row would be a
 * picture three lines tall. Smoothed rather than left to the pixel rule that
 * suits a tile: this one is about twelve pixels across, and stepping a drawing
 * that far down without smoothing loses the animal. */
.pet-look .pet-art-img {
  width: 1em;
  height: 1em;
  vertical-align: -0.12em;
  image-rendering: auto;
}

.pet-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A qualifying line under the clock: when the next time-remaining notice is
 * due, or when a round is reserved to begin. */
.aside-line {
  margin: -0.35rem 0 0.75rem;
  color: var(--muted);
  font-size: 0.85rem;
  /* Fixed-advance figures, like every other number that changes while somebody
   * is looking at it. */
  font-family: "Jost Digits", var(--font-ui);
}
/* --- Who is in it ------------------------------------------------------- */

/* The heading row: who is in it on the left, and what the figures on the right
 * are. The column heading is the cheapest possible fix for a bare number that
 * could be this sprint's words or the whole document. */
.roster-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 0.9rem 0 0.4rem;
}

.roster-label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}

/* At the right even alone, as in the scoreboard popup, which has no
 * heading of its own over the list (`past_board.rs`). */
.roster-column {
  margin-left: auto;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.roster {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.sprinter {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.35rem 0.5rem;
  border-radius: var(--radius-sm);
  min-width: 0;
}

.sprinter.is-you {
  background: var(--tint-soft);
}

.avatar {
  flex: 0 0 auto;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--surface-2);
  background-size: cover;
  background-position: center;
  display: grid;
  place-items: center;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  user-select: none;
  overflow: hidden;
}

.avatar.has-image {
  color: transparent;
}

.who {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.name {
  font-size: 0.95rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.you-tag {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--tint);
}

/* What somebody said they were working on. Quieter than their name, because it
 * is a note to themselves that the room happens to be able to read. */
.task {
  font-size: 0.8rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* The reader's task line in the count block: "Task:", each task's checkbox
 * (which opens the three answers), a counted task's project, Change task. */
.task-line-joined {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.5rem;
  font-size: 0.92rem;
}

.task-line-joined .task-line-lab {
  color: var(--muted);
}

.task-line-joined .task-project {
  color: var(--muted);
}

.task-line-joined .chip-open {
  min-height: 0;
  padding: 0;
  border: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  font-weight: 400;
}

/* A goal reached in this sprint, offered as a done to-do under the task line
 * (press/goal_offer.rs). Dashed and unfilled because it is a suggestion, not
 * yet on the board (owner, 2026-10-28); its two answers are text links like
 * the task line's own, so Log count stays the one filled button. */
.goal-offer {
  margin-top: 0.6rem;
  padding: 0.45rem 0.7rem 0.55rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.92rem;
}

.goal-offer-lead {
  margin: 0 0 0.3rem;
  color: var(--muted);
}

.goal-offer-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.9rem;
}

/* The two answers wrap together, under the to-do's name on a phone, so
 * Dismiss never sits alone on a line beside the next row's to-do. */
.goal-offer-acts {
  display: inline-flex;
  gap: 0.9rem;
  white-space: nowrap;
}

/* Two goals reached by one count: one box, one lead, a row each. */
.goal-offer-row + .goal-offer-row {
  margin-top: 0.55rem;
}

.goal-offer .chip-open {
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  text-decoration: underline;
  text-underline-offset: 3px;
  font-weight: 400;
}

/* One of the writer's to-dos on their task line, as one pill: the box, which
 * ticks Done on and off in one press, then the name, which opens the other
 * answers (docs/design/sprint-todos.md, "States"). */
.task-answer-pill {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
  padding-left: 0.2rem;
}

.task-toggle {
  display: contents;
}

/* The box alone is the button: no chrome of its own, a 24 px target. */
.task-toggle-box {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
  padding: 0.15rem;
  border: 0;
  border-radius: 999px;
  background: none;
  cursor: pointer;
}

.task-toggle-box .task-box {
  margin-right: 0;
}

@media (hover: hover) {
  .task-toggle-box:hover {
    background: var(--accent-soft);
  }
}

.task-answer {
  position: relative;
}

.task-answer > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.1rem;
  padding: 0.25rem 0.5rem 0.25rem 0.15rem;
}

.task-answer > summary::-webkit-details-marker {
  display: none;
}

.task-answer > summary .caret {
  color: var(--muted);
  font-size: 0.75rem;
  margin-left: 0.3rem;
}

/* A to-do's amount, or its progress in this sprint, and its part, after its
 * name (docs/design/parts.md, section 9). On a narrow panel it goes under the
 * name as one piece, and the name stays on its line. */
.task-answer > summary:has(.task-aim) {
  flex-wrap: wrap;
}

.task-answer > summary:has(.task-aim) .task-label {
  white-space: nowrap;
}

.task-answer > summary .task-aim {
  color: var(--muted);
  font-size: 0.82rem;
  margin-left: 0.35rem;
}

.task-answer > summary .task-aim::before {
  content: "· ";
}

/* Its count does not go toward the amount: the reason is the tooltip. */
.task-answer > summary .task-aim[title] {
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

.task-answer-opts {
  position: absolute;
  z-index: 5;
  top: calc(100% + 4px);
  left: 0;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0.3rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}

.task-answer-opts form {
  display: contents;
}

.task-answer-opts button {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  font-weight: 400;
}

.task-answer-opts button:hover,
.task-answer-opts button[aria-pressed="true"] {
  background: var(--accent-soft);
}

/* "{task} is finished too" under the answers (activity-report.css), set off
 * from them by a rule: it is the task's, not another answer. */
.task-answer-opts .task-finished label {
  margin-top: 2px;
  padding: 0.3rem 0.4rem 0.1rem;
  border-top: 1px solid var(--border);
}

/* Each task: its checkbox upright, its name in italics. */
.task-item {
  white-space: nowrap;
}

.task-label {
  font-style: italic;
}

.task-box {
  width: 1.05em;
  height: 1.05em;
  vertical-align: -0.18em;
  margin-right: 0.25em;
  font-style: normal;
}

.marks {
  display: flex;
  gap: 0.25rem;
  font-size: 0.8rem;
}

/* Words, not glyphs. A tick and a cross carried their whole meaning in a
 * `title`, and a title does not exist on a phone. */
.mark {
  font-size: 0.72rem;
  font-style: italic;
  color: var(--muted);
}

.mark.voted {
  color: var(--bad);
}

.mark.final-in {
  color: var(--good);
}

.count {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-size: 0.95rem;
  color: var(--ink);
  min-width: 3ch;
  text-align: right;
}

/* A sprinter who has filed nothing. Said, rather than left as a hole in a
 * column of numbers. */
.no-count {
  font-size: 0.8rem;
  font-style: italic;
  color: var(--muted);
}

/* --- The finished board -------------------------------------------------- */

/* The scoreboard the channel was given, in the roster's own rows: the rank in
 * a narrow column of its own, the name with the companion and task under it,
 * and the count on the right with the speed under it. */
.results .sprinter {
  align-items: flex-start;
}

.results .rank {
  flex: 0 0 auto;
  min-width: 2ch;
  padding-top: 0.3rem;
  text-align: right;
  font-family: "Jost Digits", var(--font-ui);
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  color: var(--muted);
}

.results .who {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.05rem;
  padding-top: 0.1rem;
}

.who-line,
.who-under {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  min-width: 0;
  max-width: 100%;
}

.who-under {
  flex-wrap: wrap;
}

.results .count {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  padding-top: 0.1rem;
}

.figure {
  white-space: nowrap;
}

.figure-aside {
  font-size: 0.75rem;
  color: var(--muted);
  white-space: nowrap;
}

/* A companion's row: its picture where a face would be. */
.avatar.pet-avatar {
  font-size: 1rem;
  text-transform: none;
}

.avatar.pet-avatar .pet-art-img {
  width: 1.1rem;
  height: 1.1rem;
  image-rendering: auto;
}

/* The lines chat puts under the board, and the post-sprint line under them. */
.board-closing {
  margin: 0.5rem 0.5rem 0;
  font-size: 0.85rem;
  color: var(--muted);
}

.board-closing + .board-closing {
  margin-top: 0.15rem;
}

.board-quote {
  margin: 0.75rem 0.5rem 0;
  font-size: 0.85rem;
  font-style: italic;
  color: var(--muted);
}

/* The reader's own count under it, on one line, and the link that opens the
 * count block and the sprint log. A link rather than a fold's triangle: it is
 * the way to change one number, not a section of the screen. */
.late-count {
  margin-top: 1rem;
  gap: 0;
}

.your-count {
  margin: 0;
  font-size: 0.95rem;
  font-family: "Jost Digits", var(--font-ui);
}

/* "Change count": opens the count and the Sprint log in a dialog over the
 * board. A link in the board's own link colour, like the fold it replaced. */
button.link.late-open {
  color: var(--link);
  padding: 0.45rem 0;
  width: fit-content;
  text-decoration: none;
}

button.link.late-open:hover,
button.link.late-open:focus-visible {
  text-decoration: underline;
}

.late-edit-body {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

/* What the board shows for the reader, before anything to change it. */
.late-on-board {
  margin: 0;
  font-family: "Jost Digits", var(--font-ui);
}

/* A refused count's reason, in the dialog over the notice that says it. */
.late-note {
  margin: 0;
  color: var(--bad);
  font-size: 0.9rem;
}

/* Start again, between the count and the Start another fold, with the plan it
 * repeats in small type under its name. */
.start-again {
  margin: 1rem 0 0.6rem;
}

.start-again button {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
}

.start-again-plan {
  font-size: 0.75rem;
  font-weight: 400;
  opacity: 0.85;
}

.standing {
  margin: 0.75rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* An open vote to cancel, between the roster and the controls: who asked, the
 * 🛑 (cancel) and ✍️ (keep writing) buttons with their counts, and when voting
 * closes. A box of its own, edged in the cancel colour, because it is about the
 * whole sprint and is over in one or two minutes.
 *
 * `[hidden]` stated first, for the same reason as `.end-mark[hidden]`: the
 * `display: flex` below would beat the browser's own `display: none`. */
.cancel-poll[hidden] {
  display: none;
}

.cancel-poll {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin: 0.75rem 0 0;
  padding: 0.6rem 0.7rem;
  background: var(--surface-2);
  border: 1px solid color-mix(in srgb, var(--bad) 45%, var(--border));
  border-radius: var(--radius-sm);
}

.cancel-poll p {
  margin: 0;
}

.cancel-poll-asked {
  color: var(--ink);
  font-size: 0.9rem;
}

/* What the two emoji mean. Visible text, not a tooltip: tooltips never show
 * on a touch screen. */
.cancel-poll-legend {
  color: var(--ink);
  font-size: 0.85rem;
}

.cancel-poll-votes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Bigger and brighter than the other chips: here the emoji and the count
 * are the whole label, and the count is the only sign a press went in. */
.cancel-poll-vote {
  min-width: 4.5rem;
  font-family: "Jost Digits", var(--font-ui);
  font-size: 0.95rem;
  color: var(--ink);
}

.cancel-poll-counts {
  font-family: "Jost Digits", var(--font-ui);
  color: var(--ink);
  font-size: 0.9rem;
}

.cancel-poll-closes {
  color: var(--muted);
  font-size: 0.8rem;
}

/* The same vote over the board, for somebody whose vote is still wanted
 * (`cancel_poll_dialog`): a sheet edged like the card, the card's first line
 * as its heading and the rest of the card under it. The dialog takes the
 * focus itself as it opens, and has no ring for it: it is not a control. */
dialog.cancel-poll-sheet {
  border-color: color-mix(in srgb, var(--bad) 45%, var(--border));
}

dialog.cancel-poll-sheet:focus {
  outline: none;
}

.cancel-poll-modal-body {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.cancel-poll-modal-body p {
  margin: 0;
}

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

.actions {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-top: 1rem;
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

/* Each control is its own form so it can carry the room and the instance, but a
 * form is not a layout: a row of buttons has to lay out as a row of buttons,
 * not as a row of forms each holding one. */
.row form {
  display: contents;
}

button {
  font: inherit;
  font-weight: 500;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
  padding: 0.5rem 0.9rem;
  min-height: 2.4rem;
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

button:hover {
  border-color: var(--accent);
}

button:active {
  transform: translateY(1px);
}

button:focus-visible,
input:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- The gap between a press and the screen it fetches --------------------
 *
 * Every screen on this panel is fetched, and the fetch goes out through
 * Discord's proxy from wherever the reader is. On a good connection the answer
 * is there before the finger is off the button. On a bad one there is a gap,
 * and a gap with nothing in it does not read as waiting: it reads as a press
 * that missed. So they press again, or decide the control is broken.
 *
 * Three things fill it, in the order they get noticed. The control stays down,
 * so the press is visibly the one that landed. A line across the top says the
 * panel is fetching, but only once the wait is long enough to be a wait. And
 * the screen that arrives is seen to arrive, so it reads as the answer to the
 * press rather than as the panel having been that way all along. */

/* Held down until the answer comes. Not a spinner inside the control: this is
 * a button somebody's finger is still on, and one that changes size under it
 * moves the target mid-press. */
.menu-item.htmx-request,
.panel-bar .back.htmx-request,
.back-home .back.htmx-request,
.cog.htmx-request {
  transform: translateY(1px);
  border-color: var(--accent);
  color: var(--ink);
}

/* The line, across the top of the panel where a browser puts one.
 *
 * It carries no reading of how far along anything is, because there is none to
 * carry: one fetch either has come back or has not. A bar filling up at a rate
 * nothing measured would be a made-up number drawn as a fact.
 *
 * The delay is the whole trick. Nothing is drawn for the first eighth of a
 * second, so an answer that arrives in that time is a screen that simply
 * changed, and the line only ever appears on a wait somebody noticed. */
#activity-root.is-navigating::after {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    var(--accent) 50%,
    transparent 100%
  );
  background-size: 40% 100%;
  background-repeat: no-repeat;
  opacity: 0;
  z-index: 20;
  pointer-events: none;
  animation: nav-wait 1s 0.125s linear infinite;
}

@keyframes nav-wait {
  0% {
    opacity: 1;
    background-position: -40% 0;
  }
  100% {
    opacity: 1;
    background-position: 140% 0;
  }
}

/* A screen changing, which is the other thing the file's no-animation rule is
 * not about: the three-second redraw brings the same screen back, and this
 * only runs when the panel has gone somewhere else. */
#board.screen-in {
  animation: screen-in 0.16s ease-out;
}

@keyframes screen-in {
  from {
    opacity: 0;
    transform: translateY(3px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* The one accent, always. The phase tint colours the clock, the bar and your
 * own row, where it says what is happening; on a button an amber "Join this
 * sprint" reads as a caution and a green one as a result, and it is neither. */
button.primary {
  background: var(--accent);
  border-color: var(--accent-strong);
  color: var(--on-accent);
  font-weight: 600;
}

button.primary:hover {
  filter: brightness(1.08);
}

button.quiet {
  background: transparent;
  color: var(--muted);
}

button.quiet:hover {
  color: var(--ink);
}

button.danger {
  background: transparent;
  color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 45%, transparent);
}

button.danger:hover {
  background: color-mix(in srgb, var(--bad) 14%, transparent);
  border-color: var(--bad);
}

button.wide {
  width: 100%;
}

/* --- Fields ------------------------------------------------------------- */

.field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.field label,
.field .label {
  font-size: 0.8rem;
  color: var(--muted);
  font-weight: 500;
}

button.info {
  min-height: 0;
  padding: 0 0.2rem;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1;
}

button.info:hover {
  color: var(--ink);
}

input[type="text"] {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  padding: 0.55rem 0.7rem;
  font: inherit;
  min-width: 0;
  width: 100%;
}

input[type="text"]:focus {
  border-color: var(--accent);
}

input[type="text"]::placeholder {
  color: var(--muted);
  opacity: 0.7;
}

.hint {
  margin: 0;
  font-size: 0.75rem;
  color: var(--muted);
}

/* The word count and its button, on one line.
 *
 * A text input carries an intrinsic width of about twenty characters, and a
 * flex item is not allowed below its own minimum unless it is told it may be.
 * Without these the row is wider than the panel at every size Discord gives us,
 * and the button hangs off the edge. */
.count-row {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
  flex-wrap: wrap;
  min-width: 0;
}

.count-row input[type="text"] {
  flex: 1 1 6rem;
  min-width: 0;
  font-variant-numeric: tabular-nums;
}

.count-row button {
  flex: 0 1 auto;
}

/* The count block.
 *
 * Three rows of one sum, right-aligned into a column so the figures can be read
 * down it, and two of them boxes. Only the row being typed in is drawn as a
 * box: two boxes of the same size and colour, one above the other, is a screen
 * that asks which one it wants, and the answer is always "the one you mean",
 * which is not an answer.
 *
 * A grid rather than three flex rows, because the column has to stay one column
 * at every width Discord gives the panel. Flex rows wrapped their number under
 * their label in the narrow one, which put the figures in three places.
 * `display: contents` on a row hands its cells to the grid; the number column
 * shrinks rather than wraps, and the was-line drops to the next row of it. */
.tally {
  display: grid;
  /* A third column for the controls beside each figure. `auto` rather than a
   * fixed width: it is as wide as the widest row's buttons and no wider, so the
   * number column keeps everything else. */
  grid-template-columns: 1fr minmax(4.5rem, 8rem) auto;
  /* Not baseline: in the narrow panel a two-line label sits beside a
   * one-line box, and a shared baseline put the box through the middle
   * of the second line. */
  align-items: center;
  row-gap: 0.05rem;
  column-gap: 0.5rem;
  /* Clear of whatever is above it. On the live board that is the roster, whose
   * own right-hand column is also words, and the two sets of figures ran into
   * each other with nothing between them. */
  margin: 0.75rem 0 0;
}

.tally-row {
  display: contents;
}

/* A row's cells are the grid's, so hiding the row has to hide them. `display:
 * contents` beats the `hidden` attribute's own rule, and a hidden row would
 * otherwise sit there in full. */
.tally-row[hidden] {
  display: none;
}

/* The controls beside a figure. Their column, so the numbers above and below
 * still line up whether or not a row has two of them. */
.tally-tools {
  grid-column: 3;
  display: flex;
  align-items: center;
  gap: 0.15rem;
}

.tally-edit,
.tally-add {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  flex: 0 0 auto;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
}

.tally-add {
  font-size: 1.1rem;
}

.tally-edit:hover,
.tally-add:hover,
.tally-edit:focus-visible,
.tally-add:focus-visible {
  color: var(--ink);
  border-color: var(--border);
  background: var(--paper);
}

/* The row the plus opens: a second number to add, above the row it goes into.
 * Marked as in progress, because it is not part of the sum until it merges. */
.tally-row.row-add .tally-name {
  font-style: italic;
}

.tally-row.row-add input.tally-num {
  background: var(--paper);
  border-color: var(--accent);
}

.tally-name {
  grid-column: 1;
  min-width: 0;
  font-size: 0.85rem;
  color: var(--muted);
}

/* The number column carries the input's own padding and a transparent border,
 * so a figure that is not a box still lines its digits up with one that is. */
.tally-num {
  grid-column: 2;
  box-sizing: border-box;
  width: 100%;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: 0.95rem;
  color: var(--ink);
  padding: 0.32rem 0.55rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
}

/* The line the sum is drawn under, the width of the number column and no more,
 * because it is the figures above it that are being added up. */
.tally-rule {
  grid-column: 2;
  border-top: 1px solid var(--muted);
  margin: 0.6rem 0 0.25rem;
}

/* With no starting count the rows above it are hidden, and a sum line over
 * the Total alone has nothing to add up. */
.tally.no-start .tally-rule {
  display: none;
}

/* The answer. Largest thing in the block, because it is the number the reader
 * came to check and the one the press is about to change. */
.tally .lead .tally-name {
  color: var(--ink);
  font-weight: 600;
}

.tally .lead .tally-num {
  font-size: 1.15rem;
  font-weight: 700;
}

/* A box only where the caret is. Everywhere else these are numbers in a sum,
 * and they say they can be typed in when the pointer is over them. */
.tally input.tally-num {
  background: transparent;
  border-color: transparent;
  cursor: text;
}

.tally input.tally-num:hover {
  border-color: var(--border);
}

.tally[data-live="total"] input[data-field="total"],
.tally[data-live="new"] input[data-field="new"],
.tally[data-live="start"] input[data-field="start"],
.tally input.tally-num:focus {
  background: var(--paper);
  border-color: var(--border);
}

.tally input.tally-num:focus {
  border-color: var(--accent);
}

/* The panel's own colour rather than the browser's blue, which is the one thing
 * on this screen that belongs to no palette. The count arrives highlighted, so
 * this is the first thing anybody sees. */
.tally input.tally-num::selection {
  background: var(--accent);
  color: var(--on-accent);
}

/* Where the count is coming from, under where it is going. Only drawn while
 * the two differ, and red only alongside the line that says so in words. */
.tally-was {
  grid-column: 2;
  text-align: right;
  padding-right: 0.55rem;
  font-size: 0.78rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.tally-was:empty::before {
  /* Keeps the line's height while it has nothing to say, so a count that
   * starts differing from the filed one does not push the rows down. */
  content: "\00a0";
}

.tally-was.is-down {
  color: var(--bad);
}

.tally-go {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.6rem;
}

/* "Mark me done", under Log count while writing and at Time's up. Quiet, and
 * on the same edge as the button it sits under. */
.tally-done {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.4rem;
}

#tally-done:disabled,
#tally-go:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

#tally-go:disabled:hover {
  filter: none;
}

/* Before the start: the same grid as the count block, the starting count a
 * box and the total greyed, since nothing has been written yet (owner,
 * 2026-10-30). */
.tally-before {
  display: grid;
  grid-template-columns: 1fr minmax(4.5rem, 8rem);
  align-items: center;
  row-gap: 0.05rem;
  column-gap: 0.5rem;
  margin: 0.75rem 0 0;
}

.tally-before input.tally-num {
  background: var(--paper);
  border-color: var(--border);
}

.tally-before .tally-off .tally-name,
.tally-before .tally-off .tally-num {
  color: var(--muted);
  opacity: 0.6;
}

/* The way in for somebody who came in on nothing and whose document did not
 * start empty. Under the sum, quiet, and gone the moment it is used. */
.tally-open-start {
  margin-top: 0.5rem;
}

button.link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.85rem;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}

button.link:hover,
button.link:focus-visible {
  color: var(--ink);
}


/* --- Big enough to hit ----------------------------------------------------
 *
 * Most of this panel is opened on a phone, inside Discord, one-handed. The
 * small controls on it are small because the panel is: a pencil beside a figure
 * cannot be a 44 pixel square without pushing the figure off the row.
 *
 * So the drawing stays the size it needs to be and the target does not. Each of
 * these gets an invisible square centred on it, laid over whatever is beside it
 * rather than taking room in the layout, and everything below is checked
 * against 44 pixels, which is the smallest anybody recommends.
 *
 * Where two of them sit next to each other the squares would overlap and the
 * later one would win, so the row that has two is spaced far enough apart that
 * they do not: see `.tally-tools`.
 *
 * The chips and the countdown picker are made taller outright instead. They are
 * in rows of their own with room underneath, and a chip that is hard to hit
 * because it is 22 pixels tall is a chip nobody presses twice. */
.tally-edit,
.tally-add,
.note-close,
button.link {
  position: relative;
}

.tally-edit::after,
.tally-add::after,
.note-close::after,
button.link::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(100%, 44px);
  height: max(100%, 44px);
  transform: translate(-50%, -50%);
}

/* Two buttons in one row: far enough apart that their squares do not sit on
 * each other, because the one drawn later would take both presses. */
.tally-tools {
  gap: 0.5rem;
}

/* Chips: the parser's alternatives, the lengths and the end marks on the
 * start form. All of these are pressed, and
 * several of them are pressed by somebody who has just been told they typed
 * something wrong. */
.pick,
.choice,
.end-mark,
button {
  min-height: 2.75rem;
}

/* The disclosures. A triangle and a few words is the smallest thing on the
 * panel and one of the most pressed: it is what opens the record and what
 * holds the sprint options.
 *
 * Padding rather than a height, and deliberately not `display: flex`: a summary
 * only draws its own triangle while it is a list item, and laying it out any
 * other way takes the triangle away. */
summary {
  padding-top: 0.55rem;
  padding-bottom: 0.55rem;
}

.pick {
  padding: 0.45rem 0.8rem;
}

/* A coarse pointer has no hover to find things with, so the controls that
 * announce themselves on hover have to be visible without one. */
@media (pointer: coarse) {
  .tally input.tally-num {
    border-color: var(--border);
  }

  .tally-edit,
  .tally-add {
    color: var(--ink);
    border-color: var(--border);
  }
}

/* Same reason, one level up: a column of fields must be allowed to be as narrow
 * as the panel, not as wide as the widest thing in it. */
.field,
.actions,
.actions > *,
.more-body,
.more-body > * {
  min-width: 0;
}

.count-form,
.join-card,
.start-form {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

/* A panel's choices, one per row, the chosen one outlined: the Time's up
 * panel's places. */
.join-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.jopt {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem 0.6rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  cursor: pointer;
}

.jopt:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.jopt input[type="radio"] {
  accent-color: var(--accent);
  margin: 0;
}

.jopt .chip-dflt {
  margin-left: auto;
  color: var(--amber);
  font-size: 0.72rem;
  letter-spacing: 0.03em;
}


/* The join card (board/join.rs, activity-join.js): the Writing / Task switch,
 * what the count is for, the count box, the to-dos beside it, and the line
 * saying how the channel will show the writer. */

.join-card [hidden] {
  display: none !important;
}

.join-card {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

/* The start form's copy, while Join my sprint is off. */
.join-card[hidden] {
  display: none;
}

/* How I join: the Join my sprint switch over the card. The switch itself is
 * the start form's (`.switch`, activity-start.css). */
.join-switch {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.2rem 0 0.6rem;
  font-size: 0.95rem;
  cursor: pointer;
}

.join-switch:has(.switch:checked) {
  border-bottom: 1px solid var(--border);
  margin-bottom: 0.7rem;
}

.join-card .lab {
  display: block;
  color: var(--muted);
  font-size: 0.8rem;
  margin: 0 0 0.25rem;
}

/* Writing / Task. */
.join-kind {
  display: inline-flex;
  align-self: flex-start;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px;
  background: var(--paper);
}

.join-kind button {
  background: none;
  border: 0;
  border-radius: 999px;
  min-height: 0;
  padding: 0.25rem 0.9rem;
  font-size: 0.88rem;
  color: var(--muted);
}

.join-kind button[aria-pressed="true"] {
  background: var(--surface-2);
  color: var(--ink);
}

/* What the count is for: Project, then Part, as labelled drop-downs in a
 * label column and a field column (owner, 2026-10-27: the one list opened
 * from a heading-like "Writing sprint ▾" was hidden and far too long). */
.join-count-block {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.join-level {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.join-pick-rows {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.4rem 0.7rem;
}

.join-pick-row {
  display: contents;
}

.join-pick-row label {
  color: var(--muted);
  font-size: 0.88rem;
  margin: 0;
}

.join-pick-row select,
.join-for-pick {
  width: 100%;
  min-width: 0;
  min-height: 2.3rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 0.95rem;
}

.join-pick-row select:hover,
.join-for-pick:hover {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}

/* Details for the level picked, at the right. */
.join-level-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}

.join-level-foot:not(:has(> :not([hidden]))) {
  display: none;
}

.join-level-foot .join-peek-open {
  margin-left: auto;
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  font-size: 0.82rem;
  font-weight: 400;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Details: over the card, under the level's name. */
.join-peek:empty {
  display: none;
}

.join-peek {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 4px);
  z-index: 6;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem 0.75rem;
  box-shadow: var(--shadow);
}

.peek-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
}

.peek-head b {
  font-weight: 500;
}

.peek-head .p,
.peek-gone {
  color: var(--muted);
  font-size: 0.85rem;
  margin: 0;
}

.peek-total {
  font-size: 1.35rem;
  font-variant-numeric: tabular-nums;
}

.peek-total small {
  font-size: 0.82rem;
  color: var(--muted);
  margin-left: 0.4rem;
}

/* The 14 days, drawn by projects.js with Chart.js. */
.peek-chart {
  position: relative;
  height: 76px;
  margin-top: 0.6rem;
}

.peek-chart canvas {
  width: 100% !important;
  height: 100% !important;
}

.peek-cap {
  font-size: 0.8rem;
  color: var(--muted);
  margin: 0.25rem 0 0;
}

/* TrackBear refused the key: nothing is being sent for this project. */
.peek-tb-refused {
  margin: 0.4rem 0 0;
  font-size: 0.85rem;
  color: var(--bad);
}

/* What a press in the panel did (a record added by hand, its Undo). */
.peek-said {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.75rem;
  margin: 0.4rem 0 0;
  font-size: 0.85rem;
}

.peek-said p {
  margin: 0;
}

.peek-said.is-bad p {
  color: var(--bad);
}

/* Add a record, folded at the panel's foot. */
.peek-add {
  margin-top: 0.75rem;
}

.peek-add summary {
  cursor: pointer;
  font-size: 0.85rem;
}

.peek-add-body .field,
.peek-add-body .hand-when,
.peek-add-body .proj-modes {
  margin-top: 0.5rem;
}

.join-peek h4 {
  margin: 0.75rem 0 0;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.peek-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0.35rem;
  font-size: 0.86rem;
}

.peek-table td {
  padding: 0.25rem 0;
  border-top: 1px solid var(--border);
}

.peek-table td.n {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.peek-table td.d {
  color: var(--muted);
  width: 4.5em;
  white-space: nowrap;
}

.peek-table small {
  color: var(--muted);
}

.peek-links {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 0.6rem;
  font-size: 0.86rem;
}

.peek-links button.link {
  min-height: 0;
  padding: 0;
}

/* The count box: the number, its unit, and an x that sets it to 0. */
.join-countbox {
  display: flex;
  align-items: center;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0 0.25rem 0 0.75rem;
}

.join-countbox:focus-within {
  border-color: var(--accent);
}

.join-countbox input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: 0;
  padding: 0.35rem 0;
  font-size: 1.5rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.join-countbox .unit {
  color: var(--muted);
  font-size: 0.9rem;
  margin-right: 0.35rem;
}

.join-card .x {
  background: none;
  border: 0;
  color: var(--muted);
  min-height: 0;
  width: 2.2rem;
  height: 2.2rem;
  padding: 0;
  border-radius: var(--radius-sm);
  flex: none;
}

.join-card .x:hover {
  background: var(--surface-2);
  color: var(--ink);
}

/* What joining does to the task: one line, or a choice. */
.join-says .small,
.join-says .says-line {
  margin: 0;
  font-size: 0.86rem;
  color: var(--muted);
}

.join-says .says-line {
  color: var(--ink);
}

.join-says button.quiet {
  min-height: 0;
  padding: 0;
  border: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  font-weight: 400;
}

.join-choice {
  border: 1px solid color-mix(in srgb, var(--amber) 40%, transparent);
  background: color-mix(in srgb, var(--amber) 8%, transparent);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.65rem;
}

.join-choice > p {
  margin: 0 0 0.35rem;
  font-size: 0.88rem;
}

.join-choice label,
.join-modes label {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.3rem 0;
  font-size: 0.9rem;
  cursor: pointer;
}

.join-choice label input,
.join-modes label input {
  accent-color: var(--accent);
  margin-top: 0.2rem;
  flex: none;
}

/* "Not writing in a project": a third answer, drawn as a link under the two
 * radios because it changes the switch above rather than the count. */
.join-choice .join-to-writing {
  margin: 0.3rem 0 0.1rem;
  font-size: 0.88rem;
}

.join-choice label small,
.join-modes label small {
  display: block;
  color: var(--muted);
  font-size: 0.8rem;
}

.join-modes {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-top: 0.4rem;
}

.join-modes label {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  padding: 0.45rem 0.6rem;
}

.join-modes label:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.join-zero-pref {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.88rem;
}

.join-zero-pref input {
  accent-color: var(--accent);
  width: 1rem;
  height: 1rem;
}

/* The writer's to-dos: those on this sprint above the box to type one, and
 * the writer's other tasks under "Show your tasks" below it (owner,
 * 2026-10-30), each project's under the project's name. */
.join-tasks {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.task-list,
.task-offer,
.task-group {
  display: flex;
  flex-direction: column;
}

/* The join card's to-do is one box (owner, 2026-10-30): what is typed goes
 * here for Join to send, and is never shown as a list. */
.join-tasks .join-todo-sent {
  display: none;
}

.task-group-h {
  margin: 0.4rem 0 0;
  color: var(--muted);
  font-size: 0.8rem;
  font-weight: 500;
}

/* A row: the name with its line under it, then a + (not on this sprint) or
 * a × (on it); under the name, "Add to a project" and Edit, and a chosen
 * task's own count box. Never a checkbox to choose a to-do (owner,
 * 2026-10-30): on a to-do list a checkbox means done. */
.task-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0 0.5rem;
  min-height: 2.4rem;
}

.task-row .task-txt {
  padding: 0.35rem 0;
}

/* A task not on this sprint: one press anywhere on it adds it. */
/* Its hover fill reaches past the name, which stays in line with the
 * chosen rows'. */
.task-row:not([data-on]) {
  cursor: pointer;
  border-radius: var(--radius-sm);
  margin: 0 -0.4rem;
  padding: 0 0 0 0.4rem;
}

.task-row:not([data-on]):not(:has(.task-plus:disabled)):hover {
  background: var(--surface-2);
}

.task-row:not([data-on]) .task-x,
.task-row[data-on] .task-plus,
.task-row:not([data-on]) .task-acts {
  display: none;
}

.task-plus,
.task-x {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
}

.task-plus {
  color: var(--link, var(--accent));
  font-size: 1.35rem;
}

.task-plus:hover,
.task-x:hover {
  background: var(--surface-2);
  color: var(--ink);
}

/* At the limit (five, or four beside a count): the + is switched off, and
 * the row looks it. */
.task-row:has(.task-plus:disabled) {
  cursor: not-allowed;
  opacity: 0.55;
}

/* The name's controls straight under it, then a chosen task's count box. */
.task-acts {
  order: 1;
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.6rem;
  margin: -0.35rem 0 0.25rem;
}

/* One ticked past the limit, after a switch from To-dos to a count: Join
 * waits for an untick, and looks it, as Start does. */
.join-go:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.task-txt {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.task-txt .nm {
  overflow-wrap: anywhere;
}

.task-txt .aim {
  color: var(--muted);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}

.task-countbox {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0 0 0.4rem;
  order: 2;
}

.task-countbox input {
  width: 7.5rem;
  padding: 0.45rem 0.6rem;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
}

.task-countbox input:focus {
  border-color: var(--accent);
  outline: 0;
}

.task-countbox .unit {
  color: var(--muted);
}

/* Show all, Show your tasks, and a chosen to-do's controls. Coloured as
 * links, underlined on hover. */
.join-tasks .task-all,
.join-tasks .task-show,
.task-row .task-edit,
.task-row .task-for {
  min-height: 1.6rem;
  padding: 0.15rem 0;
  border: 0;
  background: none;
  color: var(--link, var(--accent));
  font-size: 0.86rem;
  font-weight: 400;
}

.task-row .task-for {
  white-space: nowrap;
}

.join-tasks .task-all,
.join-tasks .task-show {
  align-self: flex-start;
}

.join-tasks .task-all:hover,
.join-tasks .task-show:hover,
.task-row .task-edit:hover,
.task-row .task-for:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.task-max {
  margin: 0;
  color: var(--muted);
  font-size: 0.82rem;
}

/* The open row's part and amount, under its row. */
.join-aim {
  margin-top: 0.5rem;
  padding: 0.6rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.join-aim[hidden] {
  display: none;
}

.join-aim-name {
  margin: 0;
  font-weight: 500;
  overflow-wrap: anywhere;
}

.join-aim > button {
  align-self: flex-start;
}

/* A typed to-do's "Add to a project" panel (docs/design/sprint-todos.md,
 * section 4), under its row like the part and amount panel: Project as the
 * card's own Project drop-down is, then the project's two choices. */
.join-for {
  margin-top: 0.5rem;
  padding: 0.6rem 0.75rem;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.4rem 0.7rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.join-for-task-row {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.join-for-task-row[hidden] {
  display: none;
}

.join-for-how {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 1.8rem;
  overflow-wrap: anywhere;
}

.join-for-how input {
  flex: none;
  accent-color: var(--accent);
  width: 1rem;
  height: 1rem;
  margin: 0;
}

/* The task drop-down sits under "As part of a task", in line with its
 * words. */
.join-for .join-for-pick {
  margin-left: 1.5rem;
  width: calc(100% - 1.5rem);
}

.join-for > .join-aim-name,
.join-for > button {
  grid-column: 1 / -1;
}

.join-for > button {
  justify-self: start;
}

/* The task a to-do is for, under its name, as its part and amount are. */
.task-txt .for {
  color: var(--muted);
  font-size: 0.82rem;
  overflow-wrap: anywhere;
}

/* The box that adds a to-do to this sprint. */
.task-add {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.task-add .taskin {
  display: flex;
  align-items: center;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0 0.5rem 0 0.6rem;
}

/* At the limit the box is switched off, and looks it, as the +s do. */
.task-add .taskin:has(input:disabled) {
  opacity: 0.55;
  cursor: not-allowed;
}

.task-add .taskin:focus-within {
  border-color: var(--accent);
}

.task-add .taskin input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  outline: 0;
  padding: 0.55rem 0;
  color: var(--ink);
}

.task-add .len {
  color: var(--muted);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
}

.task-add .len.over {
  color: var(--amber);
}

.task-add .task-new-go {
  align-self: flex-start;
  min-height: 1.8rem;
  padding: 0.15rem 0;
  border: 0;
  background: none;
  color: var(--link, var(--accent));
  font-size: 0.9rem;
  font-weight: 400;
  overflow-wrap: anywhere;
  text-align: left;
}

.task-add .task-need {
  margin: 0;
  color: var(--amber);
  font-size: 0.86rem;
}

.task-add .task-need[hidden] {
  display: none;
}

.task-add .cut {
  margin: 0;
  color: var(--muted);
  font-size: 0.84rem;
}

/* Show the task in the channel, beside the line under Join. */
.join-share {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.86rem;
  margin-top: -0.3rem;
}

.join-share input {
  accent-color: var(--accent);
  width: 1rem;
  height: 1rem;
}

/* The line under Join. */
.join-preview {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}

/* Under Join, the join message: drawn as the message, and a tap opens the
 * note after it. Looks like the message rather than a field, so the note is
 * found by touching what will be sent. */
.join-preview .join-message {
  font: inherit;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.05rem 0.4rem;
  text-align: left;
  overflow-wrap: anywhere;
  max-width: 100%;
  cursor: text;
}

.join-preview .join-message:hover {
  border-color: var(--muted);
}

/* The note, bold in its brackets, as chat shows one. */
.join-note {
  display: inline-flex;
  align-items: baseline;
  max-width: 100%;
  margin-left: 0.25rem;
  color: var(--ink);
  font-weight: 700;
}

.join-note[hidden] {
  display: none;
}

.join-note input {
  width: 14rem;
  max-width: 60vw;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  padding: 0.1rem 0.2rem;
}

.join-note input::placeholder {
  font-weight: 400;
  color: var(--muted);
}

.join-note input:focus {
  outline: none;
  border-bottom-color: var(--link);
}

.join-preview code {
  font-family: inherit;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.05rem 0.4rem;
  /* Wraps rather than cutting off: a part's line names its project, and
   * "Chapter 10 / Demo: The Lighthouse Keeper" lost its end at 375 px. */
  white-space: normal;
  overflow-wrap: anywhere;
  display: inline-block;
  max-width: 100%;
  vertical-align: bottom;
}

/* A to-do's amount on the join card (board/join.rs, `sprint_amount_fields`):
 * Starting count, Added and Total, laid out as the count block's rows are,
 * the label in one column and the value in the other. */
.ja-sum {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.4rem 0.8rem;
}

.ja-sum[hidden],
.ja-sum [hidden] {
  display: none;
}

.ja-sum .ja-lab {
  color: var(--muted);
  font-size: 0.88rem;
}

.ja-val {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  font-variant-numeric: tabular-nums;
}

.ja-val .unit {
  color: var(--muted);
}

.ja-val .ja-num {
  flex: none;
  width: 5.5rem;
}

.ja-val .aim-unit-in {
  flex: 1 1 6rem;
  min-width: 0;
  max-width: 12rem;
}

.ja-added {
  color: var(--muted);
  font-size: 0.86rem;
}

.join-aim .aim-more {
  align-self: flex-start;
  min-height: 1.6rem;
  padding: 0.15rem 0;
  border: 0;
  background: none;
  color: var(--link, var(--accent));
  font-size: 0.86rem;
  font-weight: 400;
}

.join-aim .aim-more:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.join-aim .aim-more[hidden] {
  display: none;
}

/* "Add a to-do" under the task line once in (board/tasks.rs, `todo_adder`):
 * a quiet link that opens a box and Add on one line. */
.todo-adder > summary {
  display: inline-block;
  list-style: none;
  cursor: pointer;
  padding: 0.2rem 0.1rem;
  color: var(--muted);
  font-size: 0.85rem;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
  text-underline-offset: 0.2em;
}

.todo-adder > summary:hover {
  color: var(--ink);
}

.todo-adder > summary::-webkit-details-marker {
  display: none;
}

.todo-adder-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.35rem;
}

.todo-adder-in {
  flex: 1;
  min-width: 0;
  padding: 0.45rem 0.6rem;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font: inherit;
}

.todo-adder-in:focus {
  border-color: var(--accent);
  outline: 0;
}
/* The reader's report while in a sprint (`board/report.rs`): a tab for the
 * count, Mark complete on the part it is for, and one for each task. The
 * tabs themselves are the settings screen's (`.set-tab-rail`, `.set-tab` in
 * activity-settings.css), so a row of tabs looks the same wherever it is;
 * the row of joined sprints over the board is a different control with its
 * own look. */

.report {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Wrapped, where the settings tabs slide: up to seven tabs here, and the
 * owner's complaint was things that could not be found, so none is drawn
 * off the edge. */
.report-tabs {
  flex-wrap: wrap;
  overflow-x: visible;
}

/* A row at every width: on a wide panel the settings tabs become a column
 * beside their pane (activity-sizes.css), and the report sits in the
 * board's narrower right-hand column. Specific enough to win over those
 * rules, which come later. */
@container panel (min-width: 46rem) {
  .report .report-tabs.set-tab-rail {
    flex: 0 0 auto;
    flex-direction: row;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }

  .report .report-tab.set-tab {
    border-right: 0;
    border-bottom: 2px solid transparent;
  }

  .report .report-tab.set-tab[aria-selected='true'] {
    border-bottom-color: var(--accent);
  }
}

/* The count tab's name can be a chapter's whole name. */
.report-tab {
  display: inline-flex;
  align-items: center;
  max-width: 14rem;
}

/* A fill as well as the underline: with the tabs on two rows, a tab on the
 * first row has its underline over the second row's tabs, which a fresh
 * review read as a list of links with nothing picked. */
.report .report-tab[aria-selected='true'] {
  background: var(--surface-2);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

.report-tab-name {
  overflow: hidden;
  text-overflow: ellipsis;
}

.report-panel {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding-top: 0.75rem;
}

.report-panel[hidden] {
  display: none;
}

.report-for {
  margin: 0;
  color: var(--muted);
  font-size: 0.88rem;
}

.report-line {
  margin: 0;
}

/* The "+" tab: a plus, bigger than the names beside it so it reads as one
 * glyph and makes a wide enough target. */
.report-tab-add {
  font-size: 1.2rem;
  line-height: 1;
  padding: 0 0.2rem;
}

/* In the "+" tab: a word count for a to-dos-only join, and a to-do box,
 * each under its own heading. */
.report-add-head {
  display: block;
  margin: 0 0 0.3rem;
  font-size: 0.9rem;
  font-weight: 500;
}

/* It shares `.todo-adder-form` for the page's emptying after an add; the
 * heading goes over the box here, not beside it. */
.report .report-add-todo {
  display: block;
  margin-top: 0.8rem;
}

.report .report-add-todo:first-child {
  margin-top: 0;
}

/* "Use a goal" under the to-do box (`board/goal_todo.rs`): a text link,
 * hidden by the page while the box has something typed in it. */
.goal-todo-open {
  display: block;
  width: fit-content;
  margin-top: 0.35rem;
  padding-inline: 0;
}

.goal-todo-open[hidden] {
  display: none;
}

/* The popup it opens: a line about the list, then one row per goal, its
 * icon where a pick sheet ticks the one in force. */
.goal-todo-lead {
  margin: 0 0 0.5rem;
  color: var(--muted);
  font-size: 0.88rem;
}

.goal-todo-sheet .opt .ic .goal-icon {
  width: 1.3em;
  height: 1.3em;
  vertical-align: middle;
}

/* Remove from this sprint, at the foot of a tab: quiet, and clear of the
 * answers above it. */
.report-remove {
  margin-top: 0.8rem;
}

.report-add-count {
  margin-top: 1rem;
}

.report-add-count:first-child {
  margin-top: 0;
}

/* Why a to-do's amount is not counted from this sprint. It was a tooltip,
 * which a phone never shows. */
.report-why {
  margin: 0;
  color: var(--muted);
  font-size: 0.85rem;
}

/* A goal to-do whose goal this sprint does not count toward
 * (`board/goal_todo.rs`): the line saying why, and the sign after its name
 * on its tab, in the warning colour the keypad and the ring use. */
.report-why.report-warn,
.report-tab-warn {
  color: var(--amber);
}

.report-tab-warn {
  flex: none;
  margin-left: 0.3em;
}

/* The warning's Attach to a project / Change project button: on a line of
 * its own, as "Use a goal" is, so its full-height touch target does not
 * push the last line of the warning down. */
.goal-todo-fix {
  display: block;
  width: fit-content;
  margin-top: 0.2rem;
  padding-inline: 0;
}

/* A task's own count: Count so far, the box, its unit or "of 600 lines",
 * and Save, on one line. */
.report-task-count {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.report-count-lab {
  font-size: 0.88rem;
  color: var(--muted);
}

.report-count-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Its own width, where the panel's fields are full width: the unit and Save
 * stay on the box's line. */
.report-task-count .report-count-num {
  flex: 0 0 7rem;
  width: 7rem;
  text-align: right;
  font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
}

.report-count-units {
  color: var(--muted);
}

.report-count-row button {
  margin-left: auto;
}

.report-hint {
  margin: 0;
  color: var(--muted);
  font-size: 0.82rem;
}

/* Done / Got somewhere / Not this time: one choice of three, drawn as the
 * join card's Writing / Task / Project switch is, but one under another: in
 * a row the three do not fit a phone, and wrapped they read as two
 * controls. */
.report-answers {
  display: flex;
  flex-direction: column;
  align-self: flex-start;
  min-width: 12rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2px;
  background: var(--paper);
}

.report-answers form {
  display: contents;
}

.report-answer {
  display: inline-flex;
  align-items: center;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  min-height: 2.25rem;
  padding: 0.25rem 0.75rem;
  font-size: 0.9rem;
  font-weight: 400;
  color: var(--muted);
}

.report-answer[aria-pressed="true"] {
  background: var(--surface-2);
  color: var(--ink);
}

@media (hover: hover) {
  .report-answer:hover {
    color: var(--ink);
  }
}

/* "{task} is finished too": under the answers, after the Done it follows
 * (docs/design/sprint-todos.md, "When a to-do finishes a task"). A tick of
 * its own, not a sixth answer: it changes the task, not the to-do. */
.task-finished label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 2rem;
  font-size: 0.9rem;
  cursor: pointer;
}

/* "✎ Edit" at the top of a to-do's tab, opening its Edit popup (owner,
 * 2026-10-30: the fold that was here read as part of the tab). Quiet, on
 * the right, as an edit control beside what it edits. */
.todo-edit-open {
  align-self: flex-end;
  margin-bottom: 0.3rem;
}

/* The Edit popup's Amount tab: the fields only once "Use an amount" is
 * ticked, so an untouched to-do has no amount, not an amount of 0. */
.todo-use-amount {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.6rem;
  cursor: pointer;
}

.todo-amount-form:has(input[name="useamount"]:not(:checked)) .todo-amount-fields {
  display: none;
}

.report-complete {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
}
/* --- What the press would do -------------------------------------------- */

/* The sentence over the start form: the room's own defaults, or the line as
 * typed, read by the same grammar the press goes to. It is the first thing on
 * the form because it is the answer; the controls under it are how to change
 * the answer. */
.reading-block {
  min-height: 1.4em;
}

/* --- The start form's choices ------------------------------------------- */

.choices {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  /* One height for every chip in the row, so a plain length and one that
   * finishes on the half hour read as two answers to one question rather than
   * as two kinds of thing. */
  align-items: stretch;
}

.choice input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.choice span {
  display: flex;
  align-items: center;
  height: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  padding: 0.4rem 0.7rem;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  user-select: none;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

.choice:hover span {
  border-color: var(--accent);
}

.choice input:checked + span {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--ink);
  font-weight: 600;
}

.choice input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- The disclosure ----------------------------------------------------- */

.more {
  border-top: 1px solid var(--border);
  padding-top: 0.6rem;
}

.more > summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 0.85rem;
  list-style: none;
  padding: 0.55rem 0;
}

.more > summary::-webkit-details-marker {
  display: none;
}

.more > summary::before {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  margin-right: 0.45rem;
  vertical-align: 0.05em;
  border-top: 0.3em solid transparent;
  border-bottom: 0.3em solid transparent;
  border-left: 0.42em solid currentColor;
  transition: transform 0.12s ease;
}

.more[open] > summary::before {
  transform: rotate(90deg);
}

.more-body {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding-top: 0.6rem;
}

/* --- The empty room ----------------------------------------------------- */

.sprint.empty,
.sprint.settled {
  text-align: left;
}

/* The idle room's start card heads itself: what the screen is for, then one
 * muted line saying what a sprint is. The preset drop-down sits under them;
 * on its own at the top it told a newcomer nothing (owner, 2026-10-29). */
.start-title {
  font-size: 1.3rem;
  margin: 0;
  font-weight: 500;
}

.start-about {
  margin: 0.2rem 0 0.75rem;
  color: var(--muted);
  font-size: 0.9rem;
}

.sprint.empty .start-form {
  margin-top: 1rem;
}

/* A wide button to another screen: the Schedule under the card on a room
 * switched to. */
.menu-item {
  width: 100%;
  justify-content: center;
  font-size: 1rem;
  padding: 0.7rem 1rem;
}

.muted {
  color: var(--muted);
  margin: 0;
  font-size: 0.9rem;
}

/* --- What just happened -------------------------------------------------- */

/* Lives outside the board, so the three-second refresh cannot take it away
 * before it has been read. It stands until the next press replaces it or the
 * reader closes it. */
/* What the last press did. The state is carried by a dot rather than by a bar
 * down the left edge, which every notification on every site has had for about
 * a decade and reads as chrome rather than as something to look at. */
.note {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.6rem;
  font-size: 0.9rem;
  line-height: 1.45;
}

.note::before {
  content: "";
  flex: 0 0 auto;
  width: 0.45rem;
  height: 0.45rem;
  /* Down onto the middle of the first line, rather than its top. */
  margin-top: 0.5rem;
  border-radius: 50%;
  background: var(--good);
}

.note.is-bad::before {
  background: var(--amber);
}

/* The same words for a moment near the bottom, where the reader's count and
 * to-dos are, after each press (owner, 2026-10-30). Rises a little as it
 * comes in; just fades where motion is turned down. */
.press-toast {
  position: fixed;
  left: 50%;
  bottom: 5.5rem;
  z-index: 1000;
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  max-width: min(90vw, 30rem);
  padding: 0.6rem 0.8rem;
  background: var(--surface-2);
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 6px 24px rgb(0 0 0 / 35%);
  font-size: 0.95rem;
  line-height: 1.4;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, 0.75rem);
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.press-toast::before {
  content: "";
  flex: 0 0 auto;
  width: 0.45rem;
  height: 0.45rem;
  margin-top: 0.45rem;
  border-radius: 50%;
  background: var(--good);
}

.press-toast.is-bad::before {
  background: var(--amber);
}

.press-toast.is-shown {
  opacity: 1;
  transform: translate(-50%, 0);
}

@media (prefers-reduced-motion: reduce) {
  .press-toast,
  .press-toast.is-shown {
    transform: translate(-50%, 0);
  }
}

/* The board has stopped hearing back. The dot becomes a turning ring, because
 * the panel is still asking every three seconds and a still marker would read
 * as having given up. It stops when the ring stops: the flag is removed the
 * moment a board arrives. */
.note.note-stale::before {
  width: 0.75rem;
  height: 0.75rem;
  margin-top: 0.35rem;
  background: none;
  border: 2px solid var(--amber);
  border-top-color: transparent;
  animation: note-retry 0.9s linear infinite;
}

@keyframes note-retry {
  to {
    transform: rotate(360deg);
  }
}

/* A save from a popup that was refused, said at the popup's top under its
 * title; the popup stays open so what was typed can be put right
 * (`activity-board.js`). */
dialog.sheet .sheet-said {
  margin: 0 0 0.7rem;
}

dialog.sheet .sheet-said[hidden] {
  display: none;
}

.note p {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Undo or Redo, on the notice that reports the press. */
.note-act {
  flex: none;
  margin: -0.2rem 0;
}

.note-act button {
  min-height: 0;
  padding: 0.2rem 0.65rem;
  font-size: 0.85rem;
}

.note-close {
  flex: 0 0 auto;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0 0.25rem;
  min-height: 0;
  cursor: pointer;
}

.note-close:hover {
  color: var(--ink);
  border-color: transparent;
}

/* --- What the bot said, drawn as a page rather than a chat message ------- */

.md-mention {
  color: var(--link);
  font-weight: 500;
}

.md-command {
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--link);
}

.md-emoji {
  height: 1.2em;
  width: auto;
  vertical-align: -0.2em;
}

.md-time {
  font-variant-numeric: tabular-nums;
}

.md-quote {
  display: block;
  border-left: 2px solid var(--border);
  padding-left: 0.5rem;
  color: var(--muted);
}

.note code,
.after code,
.when .hint code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--paper);
  border-radius: 3px;
  padding: 0.05em 0.3em;
}

/* Present for a screen reader, absent for everyone else. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Shrunk to a tile ---------------------------------------------------- */

/* Discord's picture-in-picture leaves a window a couple of hundred pixels
 * across. Everything but the countdown goes: at that size a roster is four
 * unreadable rows, and the one thing somebody keeps a tile open for is how long
 * is left. */
/* Shown only in the tile, below. */
.tile-status {
  display: none;
}

@media (max-height: 260px), (max-width: 300px) {
  #activity-root {
    padding: 0.5rem;
  }

  .sprint {
    padding: 0.6rem;
    border: none;
    box-shadow: none;
    background: transparent;
  }

  .roster,
  .roster-label,
  .roster-column,
  .actions,
  .standing,
  .aside-line,
  .sum-block,
  .note,
  .cancel-poll,
  .board-closing,
  .board-quote,
  .late-count,
  .start-again,
  /* The corner too. It is the smallest clock there is, and a tile is already
   * nothing but a clock: two of them in two hundred pixels left neither of
   * them readable, and the sprint's own countdown is the bigger one. */
  /* The strip keeps the dial and the channel name only: a tile from one of
   * two rooms has to say which. Everything else on it goes. */
  .tl-cap,
  .tl-times,
  .where-people,
  .where-server,
  .cog,
  .menu-open,
  .tally,
  /* A tile this small is looked at, not filled in. The empty room keeps its
   * heading, which is the whole of what there is to know there. */
  .start-form {
    display: none;
  }

  .panel-bar {
    position: static;
    margin: 0;
    padding: 0;
    min-height: 0;
  }

  .tl-track {
    height: 6px;
  }

  /* The idle room's line, in place of the start form the tile hides. */
  .sprint.empty .tile-status {
    display: block;
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
  }

  .results-back {
    display: none;
  }

  /* With no sprint, the empty track's own "No sprint running" is the line
   * the tile is for (owner, 2026-10-30: the minimised tile showed the
   * channel's name and nothing else), set as a status line rather than a
   * dashed 6px bar that cut it off. */
  .tl.none .tl-track {
    height: auto;
    border: 0;
    justify-content: flex-start;
    color: var(--ink);
    font-size: 1rem;
    font-weight: 600;
  }

  /* A finished sprint says Finished itself, so the empty track goes, and so
   * do Close and Start another sprint: a tile is looked at, not used. */
  #activity-root:has(.sprint.settled) .tl.none,
  .sprint-head .results-close,
  .sprint.settled .more.another {
    display: none;
  }

  /* A frame round the tile (owner, 2026-10-30): Discord's minimised window
   * is the same dark as the chat around it, and the Lounge ran into it.
   * Drawn over everything, the chatter bar included, and never in the way
   * of a press. */
  body.activity::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 40;
    border: 1px solid color-mix(in srgb, var(--ink) 28%, transparent);
    border-radius: 8px;
    pointer-events: none;
  }

  .seg-name {
    display: none;
  }

  /* No names to make room for, so every segment is to scale and three of
   * them fit a tile narrower than their floors add up to. */
  .seg,
  .tl.chain .seg.focus,
  .seg.tiny {
    min-width: 0;
  }

  .clock-slot {
    /* The tile hides everything but the countdown, so the drawings keep more of
     * the room than the ratio above would give them. Measured the same way. */
    --clock-h: clamp(6rem, 52vw, 9.5rem);
    /* Every drawing still fits with the time under it in a 300 by 250 tile,
     * and at this size it is doing more work than the number is: a tile this
     * small is looked at, not read. It is the size this rule always asked for;
     * it used to lose to the clock face's own width rule, which loads after
     * this file. The gap stays at its old 0.3rem: there is no room in a tile
     * for a wider one. */
    --face-w: clamp(3.2rem, 30vw, 5rem);
    --face-gap: 0.3rem;
  }

  /* Discord's minimised window is about 480 by 250: wide enough that the
   * width rules above give the clock its full size, too short for it under
   * the heading, the strip and the chatter line. Capped by the height left
   * over, so the time stays above the chatter line. */
  @media (max-height: 260px) {
    .clock-slot {
      --clock-h: clamp(5rem, calc(100vh - 9rem), 9.5rem);
      --face-w: clamp(3rem, calc((100vh - 9rem) * 0.45), 5rem);
    }
  }

  /* At this size the minute dashes are a grey haze round the dial. */
  .dial-minutes {
    display: none;
  }

  /* At zero the tile keeps the fixed sizes it had. The drawn faces' rule in
   * activity-board.css scales the time with --face-num, which is a 1rem
   * figure here, and 0.6 of that would be unreadable. */
  .clock.is-over {
    gap: 0.35rem;
  }

  .clock.is-over > .clock-num {
    font-size: clamp(1rem, 5.5vw, 1.3rem);
  }

  .clock {
    margin: 0.1rem 0 0.4rem;
    --num-size: clamp(1.5rem, 15vw, 2.4rem);
  }

  .face-ring .face-svg {
    width: clamp(3.5rem, 34vw, 5.5rem);
  }

  /* The seconds dial goes: at this size it is three pixels of hand inside a
   * circle, and the face still says the same thing without it. */
  .face-analog .subdial {
    display: none;
  }

  .face-hourglass .face-svg {
    width: clamp(2.4rem, 22vw, 3.6rem);
  }

  .sprint-head {
    gap: 0.4rem;
  }

  .phase {
    font-size: 0.95rem;
  }

  /* Nothing scrolls in a tile. What is left is a heading and a countdown, and
   * they sit in the middle of whatever room Discord gave the window rather
   * than starting at the top of a box that is taller than it is. */
  body.activity {
    min-height: 0;
  }

  #activity-root {
    gap: 0.25rem;
    min-height: 100vh;
    justify-content: center;
  }

  .clock-slot {
    justify-content: center;
  }
}

@media (prefers-reduced-motion: reduce) {
  .clock.is-over > .clock-num,
  .sprint.phase-turned,
  .when-says.is-new {
    animation: none;
  }

  /* Slow, not stopped. A still ring says nothing is happening, which is the
   * one thing this marker exists to deny. */
  .note.note-stale::before {
    animation-duration: 3s;
  }

  .more > summary::before {
    transition: none;
  }

  button:active {
    transform: none;
  }

  /* The screen still arrives, and still not instantly: a hard cut is the one
   * thing this is here to avoid. Opacity only, no rise. */
  #board.screen-in {
    animation: screen-in-still 0.12s ease-out;
  }

  @keyframes screen-in-still {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }

  /* The line stops travelling and simply stands there, after the same wait.
   * Something has to say the panel is fetching. */
  #activity-root.is-navigating::after {
    background-image: none;
    background-color: var(--accent);
    animation: nav-wait-still 0.01s 0.125s linear forwards;
  }

  @keyframes nav-wait-still {
    to {
      opacity: 0.7;
    }
  }
}

/* --- A window wider than the panel ---------------------------------------
 *
 * The Activity opens at whatever size Discord gives it, and on a desktop that
 * is most of a monitor. The panel drew a phone-width column down the middle of
 * it and left the rest empty, with the countdown small in a screen that had
 * room for it to be the size of a real clock.
 *
 * So past this width the board is two columns: the countdown on the left, at
 * the size the room allows, and everything that is words and controls on the
 * right, in the order it is already in. One breakpoint, and the same order
 * either side of it, so nothing moves about as the window is dragged.
 *
 * `:has` decides it rather than a class, because the boards with a countdown
 * on them are exactly the boards this helps: the start form is its own layout
 * already, and the settled board has no clock to put in a column. */
/* The settings screen turns first. It has no countdown to give a column to, so
 * the only question is whether the tabs have room to stand beside the pane,
 * and they do a good deal earlier than the board's two columns do. */
@container panel (min-width: 46rem) {
  /* The same ceiling the wide board gets. The Companion tab needs it: the
   * pets manager puts its detail beside its roster at 40rem of card. */
  #activity-root:has(.settings-head) {
    max-width: 58rem;
  }

  .set-tabs {
    flex-direction: row;
    align-items: flex-start;
    gap: 1.25rem;
  }

  .set-tab-rail {
    flex: 0 0 9.5rem;
    flex-direction: column;
    overflow-x: visible;
    border-bottom: 0;
    border-right: 1px solid var(--border);
  }

  .set-tab {
    text-align: left;
    border-bottom: 0;
    border-right: 2px solid transparent;
  }

  .set-tab[aria-selected='true'] {
    border-right-color: var(--accent);
  }

  .set-pane {
    flex: 1 1 auto;
  }
}

@container panel (min-width: 62rem) {
  #activity-root {
    max-width: 58rem;
  }

  /* The start form stays one column at the phone's width. Its folds open
   * downward, and a length grid a monitor wide is buttons nobody can aim at. */
  #activity-root:has(.start-form) {
    max-width: 36rem;
  }

  .sprint:has(> .clock-slot) {
    display: grid;
    grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
    column-gap: 1.75rem;
    align-items: start;
  }

  /* The phase, its length and the round: one line about the whole sprint, so
   * it stays over both columns. */
  .sprint:has(> .clock-slot) > .sprint-head {
    grid-column: 1 / -1;
  }

  /* Held beside the column rather than above it, and it keeps the top of the
   * card whatever is stacked to its right. */
  .sprint:has(> .clock-slot) > .clock-slot {
    grid-column: 1;
    grid-row: 2 / span 99;
    position: sticky;
    top: 5rem;
  }

  .sprint:has(> .clock-slot) > *:not(.sprint-head):not(.clock-slot) {
    grid-column: 2;
  }

  /* The room the left column has is the room the face gets: 18rem of the
   * 22rem column, which leaves 2rem of card either side of the dial. A short
   * window takes it down so the time under it stays on screen; the column is
   * sticky, so it has to fit in the window on its own. The time, the gap and
   * the box's height all follow from this one number (see .clock-slot). */
  .sprint:has(> .clock-slot) > .clock-slot {
    --face-w: min(18rem, 40vh);
    /* Every face gets the dials' box here, Joining and Time's up included:
     * the column is sticky and has the room, and nothing beside it moves when
     * the box changes. */
    --clock-h: var(--clock-h-drawn);
    /* Clear of the heading above it, the way the roster beside it is. A
     * margin, not padding, so it stays out of the box's --clock-h. */
    margin-top: 0.75rem;
  }

  /* The faces made of type sit at the top of that box, under the heading.
   * Centred, the Digital time stood about 170px below "Focus time" with empty
   * card above it. The drawn faces stay centred, so the dial does not move
   * between the Clock face and the Kitchen timer. */
  .sprint:has(> .clock-slot) > .clock-slot:has(> .clock:is(.face-digital, .face-bold, .face-ring)) {
    justify-content: flex-start;
  }

  /* The digital faces have no drawing to scale with, and keep the size they
   * had here. */
  .sprint:has(> .clock-slot) :is(.face-digital, .face-bold, .face-ring) {
    --num-size: 4rem;
  }

  .sprint:has(> .clock-slot) .face-ring .face-svg {
    width: 14rem;
  }
}

/* A control the typed line has taken over. It still says what it said, because
 * clearing the box puts it back in charge, and a control that vanished while
 * somebody typed would be a worse answer than a quiet one.
 *
 * Keyed off the body, which is the one element a board swap does not replace:
 * see `paintOverrides`. */
body.overruled-length #len-panel :is(.pchips, .kp-keys, .pick),
body.overruled-start #when-panel :is(.pchips, .pick) {
  opacity: 0.4;
}

/* Over a panel the typed line has taken over: what decides it instead. */
.panel-typed {
  margin: 0 0 0.4rem;
  text-align: center;
  color: var(--amber);
  font-size: 0.85rem;
}

.panel-typed[hidden] {
  display: none;
}

/* The hours beyond the dial's own hour, as a ring inside it. Present only while
 * there is more than an hour to hold: at an hour or less the fraction is zero
 * and so is this. */
.face-timer .timer-over {
  /* The circumference of r=12. */
  --circ: 75.4;
  fill: none;
  stroke: var(--tint-soft);
  stroke-width: 7;
  stroke-dasharray: calc(var(--dial-over, 0) * var(--circ)) var(--circ);
  transform: rotate(-90deg);
  transform-box: view-box;
  transform-origin: 50px 52px;
  opacity: calc(min(var(--dial-over, 0), 1) * 20);
}

/* --- The seconds, in a dial of their own ---------------------------------
 *
 * The pocket watch's countdown is the minute hand walking back to the top. A
 * second hand on the same dial made it read as a clock telling the time, which
 * is the one thing it is not doing. */
.face-analog .subdial-face {
  fill: var(--paper);
  stroke: var(--border);
  stroke-width: 1.2;
}

.face-analog .subdial-ticks line {
  stroke: var(--muted);
  stroke-width: 1.2;
  stroke-linecap: round;
}

.face-analog .subdial .hand-second {
  transform-origin: 50px 70px;
}

.face-analog .subdial-pin {
  fill: var(--muted);
}

/* --- The clock face ------------------------------------------------------
 *
 * The actual time, with the sprint's end ghosted behind it. Every angle here is
 * set by the script: which hour it is where the reader sits is not something
 * the server knows. */
.face-wallclock .face-svg {
  width: var(--face-w);
}

/* --- The dial, shared by the clock face and the kitchen timer -------------
 *
 * Drawn like an instrument: a bezel in the card's second surface colour with
 * a hairline outer edge, the dial inside it in the page colour, and the marks
 * set 4 units in from the dial's edge so they have a margin. Both faces put
 * the dial on the same circle (see `DIAL_CY` in clock.rs), so swapping one for
 * the other leaves it where it was. */
.face-wallclock .dial-bezel,
.face-timer .dial-bezel {
  fill: var(--surface-2);
  stroke: var(--border);
  stroke-width: 1;
}

.face-wallclock .dial-face {
  fill: var(--paper);
  stroke: var(--border);
  stroke-width: 0.75;
}

/* One dash a minute: the circle's `pathLength` is 60, so a dash of 0.12 is
 * a mark a little under half a unit wide, and the offset centres each dash on
 * its minute. */
.face-wallclock .dial-minutes,
.face-timer .dial-minutes {
  fill: none;
  stroke: var(--muted);
  stroke-width: 2.5;
  stroke-dasharray: 0.12 0.88;
  stroke-dashoffset: 0.06;
  opacity: 0.7;
}

/* The hours in the muted colour and the quarters in ink, longer and heavier,
 * so 12, 3, 6 and 9 can be found at a glance without numerals. Square ends:
 * round caps made the ticks look soft at this size. */
.face-wallclock .dial-ticks line,
.face-timer .dial-ticks line {
  stroke: var(--muted);
  stroke-width: 1.6;
}

.face-wallclock .dial-ticks .is-quarter,
.face-timer .dial-ticks .is-quarter {
  stroke: var(--ink);
  stroke-width: 2.6;
}

/* On the kitchen timer every mark is in ink, and the copy of them drawn over
 * the wedge is in the dial's colour (`dial-marks-on-wedge` in clock.rs), so
 * each mark is at least 3:1 against what is under it in both themes. The
 * muted grey all but vanished into the green wedge in the dark theme. The
 * quarters are still told apart by length and weight. */
.face-timer .dial-ticks line,
.face-timer .dial-minutes {
  stroke: var(--ink);
}

.face-timer .dial-marks-on-wedge :is(.dial-ticks line, .dial-minutes) {
  stroke: var(--paper);
}

/* The 12 o'clock mark, which the wedge's edge always runs through, drawn once:
 * ink, with an outline in the dial's colour that shows only on the wedge, half
 * a unit wide outside the mark (`paint-order` puts the stroke under the fill).
 * The one mark whose colour does not depend on the wedge. */
.face-timer .timer-twelve {
  fill: var(--ink);
  stroke: var(--paper);
  stroke-width: 1;
  paint-order: stroke;
}

/* A mask shows what is under its white parts: white here, whatever the
 * theme, so the copy above shows exactly where the wedge is. */
.face-timer .timer-wedge-mask {
  stroke: #fff;
}

.face-wallclock .hand-hour path,
.face-wallclock .hand-minute path {
  fill: var(--ink);
}

.face-wallclock .hand-second line {
  stroke: var(--muted);
  stroke-width: 1;
  stroke-linecap: round;
}

/* The end of the sprint, on the same dial and plainly not the time: the tint
 * this card is drawn in, under the hands in front of them. */
.face-wallclock .ghost-hour path,
.face-wallclock .ghost-minute path {
  fill: var(--tint);
  opacity: 0.55;
}

.face-wallclock .ghost-second line {
  stroke: var(--tint);
  stroke-width: 1.6;
  stroke-linecap: round;
  opacity: 0.55;
}

/* The last minute, where a ghosted minute hand would only be hiding under the
 * live one: the two lower ghosts cross over, and the fraction that decides how
 * far through the crossing they are is set frame by frame by the script. With
 * no script running — a face on the picker, or the first paint before the
 * countdown starts — it is nought, which is the minute hand alone, as before. */
.face-wallclock .ghost-minute path {
  opacity: calc(0.55 * (1 - var(--ghost-swap, 0)));
}

.face-wallclock .ghost-second line {
  opacity: calc(0.55 * var(--ghost-swap, 0));
}

/* The dial's centre, `DIAL_CY` in clock.rs. */
.face-wallclock .hand-hour,
.face-wallclock .hand-minute,
.face-wallclock .hand-second,
.face-wallclock .ghost-hour,
.face-wallclock .ghost-minute,
.face-wallclock .ghost-second {
  transform-box: view-box;
  transform-origin: 50px 52px;
}

.face-wallclock .hand-hour {
  transform: rotate(var(--now-hour, 0deg));
}

.face-wallclock .hand-minute {
  transform: rotate(var(--now-minute, 0deg));
}

.face-wallclock .hand-second {
  transform: rotate(var(--now-second, 0deg));
}

.face-wallclock .ghost-hour {
  transform: rotate(var(--end-hour, 0deg));
}

.face-wallclock .ghost-minute {
  transform: rotate(var(--end-minute, 0deg));
}

.face-wallclock .ghost-second {
  transform: rotate(var(--end-second, 0deg));
}

.face-wallclock .dial-pin {
  fill: var(--ink);
  stroke: var(--paper);
  stroke-width: 0.8;
}

/* --- Swapping the Clock face and the Kitchen timer ------------------------
 *
 * A press on the drawing asks the server for the other face, so the swap is
 * two halves either side of a round trip, set by activity-board.js on <html>:
 * `data-face-leaving` from the press until the new face lands, and
 * `data-face-arriving` for a second after. Everything here moves only what
 * differs between the two faces. The bezel, the dial and the marks are the
 * same circle on both, so they stay where they are and only change colour in
 * the cross-fade.
 *
 * All of it is off for somebody who has asked for less movement: the script
 * sets neither attribute, and none of these rules apply. */
@media (prefers-reduced-motion: no-preference) {
  /* Leaving, the answer to the press: the hands fade, or the wedge winds back
   * to nothing, and the part only this face has (the pin, the knob) goes with
   * them. 150ms, fast because it is clearing the way and the reader is waiting
   * for what comes next. The script holds the new face back a little longer
   * than this (`FACE_LEAVE_MS`), so the cross-fade starts from a cleared face.
   * The `transition` is declared here only: the hands are moved every frame,
   * and a standing transition would smear the second hand. */
  :root[data-face-leaving] .face-wallclock :is(.hand-hour, .hand-minute, .hand-second, .ghost-hour, .ghost-minute, .ghost-second, .dial-pin),
  :root[data-face-leaving] .face-timer .timer-knob {
    opacity: 0;
    transition: opacity 150ms ease-out;
  }

  :root[data-face-leaving] .face-timer :is(.timer-wedge, .timer-over) {
    stroke-dasharray: 0 var(--circ);
    transition: stroke-dasharray 150ms ease-in-out;
  }

  /* The drawing is cross-faded as one picture while the rest of the board
   * changes at once, as it does on every refresh. Named only during a swap: a
   * name on every refresh would make the drawing its own layer for nothing. */
  :root:is([data-face-leaving], [data-face-arriving]) #board .clock-slot .face-svg {
    view-transition-name: face-art;
  }

  /* The page around it is not part of the transition at all: it is drawn
   * live, changing at once as on every refresh. Taking a picture of the whole
   * page to show it unchanged cost dropped frames for nothing. */
  :root:is([data-face-leaving], [data-face-arriving]) {
    view-transition-name: none;
  }

  /* Presses go through to the page while the drawing cross-fades. Chromium
   * sends every press to <html> while any view transition runs, even with the
   * root left out of it, so for the 360ms after each swap a press on the face,
   * on the time or on Log count did nothing at all. */
  ::view-transition {
    pointer-events: none;
  }

  /* The old and new drawings fade over the same time on the same curve, so
   * where the two are the same (the bezel and the dial) they add up to one
   * solid picture throughout. 360ms: the marks changing colour and the knob
   * appearing, nothing that needs following. */
  ::view-transition-group(face-art),
  ::view-transition-old(face-art),
  ::view-transition-new(face-art) {
    animation-duration: 360ms;
    animation-timing-function: ease-in-out;
  }

  /* Arriving: the hands sweep out from twelve to the time, and the ends with
   * them, the way the kitchen timer's wedge winds out from twelve below. 700ms
   * on a curve that starts quickly and settles, staggered 60ms a hand so the
   * three read as separate hands and not one bar. They land on the live time:
   * the animation has no end frame of its own. */
  :root[data-face-arriving] .face-wallclock :is(.hand-hour, .ghost-hour, .hand-minute, .ghost-minute, .hand-second, .ghost-second) {
    animation: face-hand-in 700ms cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
  }

  :root[data-face-arriving] .face-wallclock :is(.hand-minute, .ghost-minute) {
    animation-delay: 60ms;
  }

  :root[data-face-arriving] .face-wallclock :is(.hand-second, .ghost-second) {
    animation-delay: 120ms;
  }

  /* The wedge, and the ring of time past the hour inside it, wind out from
   * twelve to the time left, as they do when a real one is set. */
  :root[data-face-arriving] .face-timer :is(.timer-wedge, .timer-over) {
    animation: face-wind-out 700ms cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
  }

  /* The knob drops onto the bezel, a few units, while the drawing fades in. */
  :root[data-face-arriving] .face-timer .timer-knob {
    transform-box: view-box;
    animation: face-knob-in 360ms ease-out backwards;
  }

  /* The pin fades in with the cross-fade. Said again here for a browser with
   * no view transitions, where nothing else would fade it. */
  :root[data-face-arriving] .face-wallclock .dial-pin {
    animation: face-fade-in 360ms ease-out backwards;
  }
}

@keyframes face-fade-in {
  from {
    opacity: 0;
  }
}

@keyframes face-hand-in {
  from {
    transform: rotate(0deg);
    opacity: 0;
  }
}

@keyframes face-wind-out {
  from {
    stroke-dasharray: 0 var(--circ);
  }
}

@keyframes face-knob-in {
  from {
    transform: translateY(-3px);
    opacity: 0;
  }
}

/* --- The start form, in three layers ------------------------------------
 *
 * Layer one is the drop-down, the plan and the button; two is Adjust, folded
 * directly under the plan so a cell's Change and the control it opens sit
 * together; three is the words box, folded under Start. */

/* The drop-down. A native select: it works with a thumb, a keyboard and a
 * screen reader without any of that being written here, and on a phone it
 * opens as the system's own sheet. Each entry carries its words in brackets. */
.preset-field select {
  width: 100%;
  font: inherit;
  font-size: 1rem;
  font-weight: 500;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.55rem 2.2rem 0.55rem 0.75rem;
  appearance: none;
  -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 1.1rem) 50%, calc(100% - 0.75rem) 50%;
  background-size: 0.35rem 0.35rem, 0.35rem 0.35rem;
  background-repeat: no-repeat;
  cursor: pointer;
}

.preset-field select:hover {
  border-color: var(--accent);
}

/* The open list. Discord's window on Windows draws it on white whatever the
 * page's color-scheme, under the select's own light text, so each entry gets
 * the select's colors. */
.preset-field option {
  background: var(--surface-2);
  color: var(--ink);
}

.preset-field option:disabled {
  color: var(--muted);
}

/* The drop-down and Reset on one row. Reset is there only while the form is
 * off the channel's defaults (`start-moved` on the body, set by the script),
 * so the drop-down has the whole row the rest of the time. */
.preset-row {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
}

.preset-row select {
  flex: 1;
  min-width: 0;
}

.start-reset {
  display: none;
  flex: none;
}

body.start-moved .start-reset {
  display: inline-block;
}

/* The plan: three drawers, each headed by its part of the plan. The heading
 * is the whole toggle, drawn as a box so it reads as something to press, with
 * the drawer's controls opening straight under it. */
.more.drawer {
  border-top: 0;
  padding-top: 0;
}

.more.drawer > summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink);
  font-size: inherit;
  transition: border-color 0.12s ease;
}

.more.drawer > summary:hover {
  border-color: var(--accent);
}

/* The arrow on the right, where a row of values ends, rather than before
 * the label. */
.more.drawer > summary::before {
  display: none;
}

.more.drawer > summary::after {
  content: "";
  flex: none;
  margin-left: auto;
  width: 0.45em;
  height: 0.45em;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg);
  transition: transform 0.12s ease;
}

.more.drawer[open] > summary::after {
  transform: rotate(-135deg);
}

.more.drawer[open] > summary {
  border-color: var(--accent);
}

.fact {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.05rem;
  min-width: 0;
  line-height: 1.2;
}

.fact-k {
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
}

.fact-v {
  font-size: 1.15rem;
  font-weight: 500;
  font-family: "Jost Digits", var(--font-ui);
}

.fact-v small {
  font-size: 0.8rem;
  color: var(--muted);
  font-weight: 400;
  font-family: var(--font-ui);
  margin-left: 0.25rem;
  white-space: nowrap;
}

/* A heading with no figure, only what the drawer holds. */
.fact-v small.fact-hint {
  margin-left: 0;
}

/* A drawer the options box has taken over. Its heading says so. */
.more.drawer > summary:has(.fact.is-typed) {
  border-style: dashed;
}

/* Never shown. The drawer headings say what an accepted line does, and a
 * refused line's reason is shown once, under Start (`.after.is-refused`),
 * between the greyed button and the box it is fixed in. The script reads the
 * reason from here. */
.reading {
  display: none;
}

.reading-block {
  min-height: 0;
}

/* The words box under Start: a label and an empty box until it is used. The
 * examples and the help line show while the caret is in it or it has words
 * in it, and the corner repeats the words only once there are some. */
.words {
  position: relative;
  border-top: 1px solid var(--border);
  padding-top: 0.9rem;
}

/* The × that empties the box, at its end, only while there is something in
 * it. */
.words-box #startopts {
  padding-right: 2.4rem;
}

.words-clear {
  position: absolute;
  top: 50%;
  right: 0.35rem;
  transform: translateY(-50%);
  min-height: 0;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 1.15rem;
  line-height: 1;
}

.words-clear:hover {
  color: var(--ink);
  background: var(--surface-2);
}

.words-box:has(#startopts:placeholder-shown) .words-clear {
  display: none;
}

/* The "other…" chip, once the dialog has given it a length: shows that length
 * and is pressed, like a chip. */
.pick.other[data-chosen] .other-default {
  display: none;
}

.pick.other[data-chosen]::after {
  content: attr(data-chosen);
}

.words-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
}

.words-head .more-sum {
  float: none;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.words #startopts:not(:focus)::placeholder {
  color: transparent;
}

.words .hint {
  display: none;
}

.words:focus-within .hint,
.words:has(#startopts:not(:placeholder-shown)) .hint {
  display: block;
}

/* The suggestions: a list over whatever is under the box, as a completion
 * list is everywhere else. */
.words-box {
  position: relative;
}

.picks-pop {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 30;
  padding: 0.25rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2);
  box-shadow: var(--shadow);
}

.picks-pop[hidden],
.picks-note[hidden],
.picks[hidden] {
  display: none;
}

.picks-note {
  margin: 0;
  padding: 0.35rem 0.5rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.picks {
  display: block;
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 15rem;
  overflow-y: auto;
}

.picks [role="option"] {
  padding: 0.35rem 0.5rem;
  border-radius: 5px;
  cursor: pointer;
  color: var(--ink);
  font-family: "Jost Digits", var(--font-ui);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.picks [aria-selected="true"] {
  background: color-mix(in srgb, var(--accent) 24%, transparent);
}

/* A fold's summary can say what is inside it, on the right. */
.more-sum {
  float: right;
  color: var(--ink);
  font-weight: 500;
  font-size: 0.8rem;
  font-family: "Jost Digits", var(--font-ui);
}

/* The length grid: every five minutes to an hour, three across, the two
 * round finishing times in their own column, and "other…" across the bottom.
 * One weight throughout: a chip that went bold when chosen reflowed its
 * label, and brighter "common" lengths said nothing anybody read. */
.lengths {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.5rem;
  align-items: start;
}

.len-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.3rem;
}

.len-grid .choice span {
  justify-content: center;
  padding: 0.4rem 0.2rem;
}

/* Chosen by colour and border only. A weight change reflows the label. */
.len-grid .choice input:checked + span,
.rounds .choice input:checked + span {
  font-weight: inherit;
}

/* Past this room's ceiling: switched off, with the reason as the title. */
.choice.is-off span {
  opacity: 0.35;
  cursor: not-allowed;
}

.choice.is-off:hover span {
  border-color: var(--border);
}

/* "other…" and the column beside the grid are chips like the lengths, not
 * the rounded pills used for picks elsewhere. */
.len-grid .other {
  grid-column: 1 / -1;
  justify-content: center;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink);
  font-size: 0.9rem;
}

/* Rounds, and the break between them. */
.rounds {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.rounds .choices {
  flex: 1 1 auto;
}

.rounds .brk {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--muted);
  font-size: 0.85rem;
}

/* One round has no break to set. */
.rounds:has(input[name="rounds"][value="1"]:checked) .brk {
  display: none;
}

.rounds .brk input {
  width: 3.4rem;
  text-align: center;
  padding: 0.3rem;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* Notify: the state, then the one action that changes it. */
.notify {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.45rem 0;
  border-top: 1px solid var(--border);
}

.notify-txt {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  font-size: 0.9rem;
}

.notify-txt small {
  color: var(--muted);
  font-size: 0.75rem;
}

.notify .notify-off,
.notify[data-quiet="1"] .notify-on {
  display: none;
}

.notify[data-quiet="1"] .notify-off {
  display: flex;
  flex-direction: column;
  color: var(--amber);
}

.notify {
  cursor: pointer;
}

/* An on/off switch, on while the sprint will notify people. Drawn from the
 * checkbox itself so it keeps the checkbox's keyboard and screen-reader
 * behaviour. */
.switch {
  appearance: none;
  flex: 0 0 auto;
  position: relative;
  width: 2.6rem;
  height: 1.5rem;
  margin: 0;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.switch::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0.2rem;
  width: 1.05rem;
  height: 1.05rem;
  border-radius: 50%;
  background: var(--muted);
  transform: translateY(-50%);
  transition: left 0.15s ease, background-color 0.15s ease;
}

.switch:checked {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.switch:checked::after {
  left: calc(100% - 1.25rem);
  background: var(--accent);
}

.switch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Under Start: what the press comes to, and who it reaches. */
.after {
  margin: -0.2rem 0 0;
  text-align: center;
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1.4;
}

.after-times {
  display: block;
  font-family: "Jost Digits", var(--font-ui);
}

.after-times.is-new {
  animation: when-new 0.6s ease;
}

/* The row under the ring that opens Other options, saying what is in it. */
.start-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.start-row:hover {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}

.start-row .row-txt {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

.start-row b {
  font-weight: 500;
}

.start-row .row-sum {
  color: var(--muted);
  font-size: 0.85rem;
}

/* Inside Other options, the section rule above each is the only line. */
.other-panel .notify {
  padding: 0;
  border-top: 0;
}

.other-panel .words {
  padding-top: 0;
  border-top: 0;
}

.start-row .chev {
  color: var(--muted);
  font-size: 1.3rem;
}

.after.is-refused .after-times {
  color: var(--bad);
}

.start-form button[type="submit"]:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.other-actions {
  justify-content: flex-end;
}

/* The start screen's panels: the Length panel's keypad, its chips, its rows
 * of boxes, and the drawing of a wheel. The panels themselves are sheets
 * (`dialog.sheet`, activity-settings.css), opened from the start form. See
 * docs/design/lounge-start-ring.md. */

/* The heading that opens a panel looks like the drawer headings beside it,
 * which it replaces one at a time. */
.start-form button.more.drawer.panel-open {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  margin: 0;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.12s ease;
}

.start-form button.more.drawer.panel-open:hover {
  border-color: var(--accent);
}

.start-form button.more.drawer.panel-open::after {
  content: "";
  flex: none;
  margin-left: auto;
  width: 0.45em;
  height: 0.45em;
  border-right: 2px solid var(--muted);
  border-top: 2px solid var(--muted);
  transform: rotate(45deg);
}

.start-form button.more.drawer.panel-open:has(.fact.is-typed) {
  border-style: dashed;
}

/* --- A panel ------------------------------------------------------------- */

dialog.start-panel .sheet-close {
  font-weight: 500;
}

/* The title and Done stay in view while a long panel scrolls. The sheet's
 * own top padding is taken into the head, so nothing shows above it. */
dialog.start-panel .sheet-head {
  position: sticky;
  top: -0.9rem;
  z-index: 2;
  margin-top: -0.9rem;
  padding: 0.9rem 0 0.4rem;
  background: var(--surface);
}

.panel-body {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

/* A section of a panel: "Or finish at a clock time", "Or a random wheel".
 * Space and a rule between them, so each reads as its own way of choosing. */
.psec {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.psec h3 {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.pchips {
  display: grid;
  gap: 0.45rem;
}

.pchips.four {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.pchips.five {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

.pchips.six {
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 0.3rem;
}

/* A late-edit row with a typed value or the room's own beside the six
 * offered. */
.pchips.seven {
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.25rem;
}

.pchips.eight {
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: 0.2rem;
}

.pchips.six .pchip,
.pchips.seven .pchip,
.pchips.eight .pchip {
  padding-inline: 0.1rem;
}

/* --- The Time's up panel ------------------------------------------------ */

.timesup-panel {
  --ph-focus: var(--good);
  --ph-counts: var(--accent);
  --ph-break: var(--break);
}

.tu-what,
.tu-note {
  margin: 0;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.35;
}

.tu-what {
  text-align: center;
}

.psec[hidden] {
  display: none;
}

/* The first section follows the big figure, with no rule above it. */
.psec.is-first {
  margin-top: 0.4rem;
  padding-top: 0;
  border-top: 0;
}

/* One stretch of time drawn to scale: writing, word counts, late edits. */
.tu-cycle {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.tu-cycle:empty {
  display: none;
}

.tu-bar {
  display: flex;
  gap: 2px;
  height: 0.85rem;
}

.tu-bar span {
  min-width: 3px;
  border-radius: 4px;
}

.tu-bar .f,
.tu-key .f {
  background: var(--ph-focus);
}

.tu-bar .c,
.tu-key .c {
  background: var(--ph-counts);
}

.tu-bar .ghost {
  background: transparent;
}

.tu-bar .b,
.tu-key .b {
  background: var(--ph-break);
}

.tu-bar .late,
.tu-key .late {
  background: repeating-linear-gradient(
    135deg,
    color-mix(in srgb, var(--ph-counts) 55%, transparent) 0 4px,
    transparent 4px 8px
  );
  border: 1.5px solid var(--ph-counts);
}

/* The end of the writing in the late-edit bar, faded in from the left: not
 * to scale, only the time from Time's up on is measured. */
.tu-bar .stub {
  background: linear-gradient(to right, transparent, var(--ph-focus) 75%);
}

/* A late-edit rule under the bar: a bracket from where the rule counts from
 * (Time's up, or when word counts are due) to where it closes, and its
 * label under it starting at the same place. */
.tu-rule {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.tu-rule i {
  height: 6px;
  border: 1.5px solid var(--muted);
  border-top: 0;
  border-radius: 0 0 3px 3px;
}

.tu-rule span {
  color: var(--muted);
  font-size: 0.75rem;
  line-height: 1.2;
}

/* The label over each row of late-edit chips. */
.tu-row {
  margin: 0.2rem 0 0;
  font-size: 0.85rem;
}

.tu-rules {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.tu-rules[hidden] {
  display: none;
}

.tu-brace {
  display: flex;
  font-size: 0.75rem;
  color: var(--muted);
}

.tu-brace span {
  padding-top: 2px;
  border-top: 1.5px solid var(--muted);
  text-align: center;
  white-space: nowrap;
}

.tu-key {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 0.9rem;
  color: var(--muted);
  font-size: 0.78rem;
}

.tu-key span {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.tu-key i {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 3px;
}

/* A choice's line under its label, in the panels' lists. */
.jopt .jsub {
  flex-basis: 100%;
  padding-left: 1.6rem;
  color: var(--muted);
  font-size: 0.8rem;
}

.pchips.three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.pchips.two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.pchip {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 2.6rem;
  padding: 0.4rem 0.3rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--ink);
  font: inherit;
  line-height: 1.15;
  text-align: center;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.pchip small {
  color: var(--muted);
  font-size: 0.74rem;
}

/* The minutes a clock time ends in, on one line: ":00 :15 :30 :45" fits a
 * third of a phone-width panel only a little smaller than the other chips. */
.pchip .mark-mins {
  white-space: nowrap;
  font-size: 0.86rem;
  letter-spacing: -0.01em;
}

.pchip .chip-dflt {
  color: var(--amber);
  font-size: 0.7rem;
  letter-spacing: 0.03em;
}

.pchip[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.pchip:disabled {
  opacity: 0.38;
  cursor: not-allowed;
}

/* A row of boxes, chosen by typing something that makes sense into it, or
 * by pressing on it again. Outlined while it is the choice. */
.pick {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  cursor: pointer;
}

.pick.is-on {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.pick label,
.pick .unit {
  color: var(--muted);
  font-size: 0.9rem;
}

.pick .says {
  flex-basis: 100%;
  color: var(--muted);
  font-size: 0.8rem;
}

.pick .says.is-bad {
  color: var(--bad);
}

.pbox {
  padding: 0.35rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
}

.pbox::placeholder {
  color: var(--muted);
  opacity: 0.6;
}

.pbox.n {
  width: 4.2em;
  text-align: center;
}

.pbox.t {
  width: 10em;
}

.pbox.wide {
  flex: 1;
  min-width: 7em;
}

/* --- The keypad ---------------------------------------------------------- */

/* The digits, as a phone's timer shows them: dim zeros where nothing has
 * been typed, and a caret where the next digit lands. Selected (lit behind)
 * while the next digit will replace the whole length. */
.kp-display {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 0.2rem;
  width: fit-content;
  margin: 0.2rem auto 0;
  padding: 0.3rem 0.6rem 0.1rem;
  border-radius: var(--radius);
  font-family: "Jost Digits", var(--font-ui);
}

.kp-display.is-selected {
  background: color-mix(in srgb, var(--link) 20%, transparent);
}

.kp-grp {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.kp-digs {
  position: relative;
  display: flex;
  font-size: 3.2rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.kp-dig {
  width: 0.6em;
  text-align: center;
}

.kp-dig.is-empty {
  color: color-mix(in srgb, var(--muted) 45%, transparent);
}

.kp-dig.is-slid {
  animation: kp-slide 0.14s ease-out;
}

@keyframes kp-slide {
  from {
    transform: translateX(0.45em);
    opacity: 0.3;
  }
  to {
    transform: none;
    opacity: 1;
  }
}

.kp-caret {
  position: absolute;
  top: 8%;
  bottom: 8%;
  right: -3px;
  width: 3px;
  border-radius: 2px;
  background: var(--accent);
  animation: kp-blink 1.05s steps(1) infinite;
}

@keyframes kp-blink {
  50% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .kp-dig.is-slid,
  .kp-caret {
    animation: none;
  }
}

.kp-lab {
  margin-top: 0.2rem;
  font-size: 0.72rem;
  color: var(--muted);
  font-family: var(--font-ui);
}

.kp-colon {
  padding-bottom: 1.3rem;
  font-size: 2.6rem;
  line-height: 1;
  color: var(--muted);
}

/* The Starts panel's big words: the wait, or a clock time. */
.when-big {
  margin: 0.2rem auto 0;
  text-align: center;
  font-family: "Jost Digits", var(--font-ui);
  font-size: 2.6rem;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

.when-big.is-long {
  font-size: 2rem;
}

.kp-reading {
  min-height: 1.4em;
  margin: 0;
  text-align: center;
  color: var(--muted);
  font-size: 0.92rem;
}

.kp-reading b {
  color: var(--ink);
  font-weight: 500;
}

.kp-reading.is-bad {
  color: var(--bad);
}

.kp-warn {
  display: block;
  color: var(--amber);
  font-size: 0.85rem;
}

.kp-keys {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.3rem;
}

.kp-key {
  padding: 0.55rem 0;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 1.5rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.kp-key:hover,
.kp-key:active {
  background: var(--surface-2);
}

.kp-key svg {
  width: 1.5rem;
  height: 1.5rem;
  vertical-align: -0.25rem;
}

/* --- A wheel, drawn ------------------------------------------------------ */

.wheel[hidden] {
  display: none;
}

.wheel-box {
  position: relative;
  width: min(15rem, 78%);
  aspect-ratio: 1;
  margin: 0.6rem auto 0;
}

.wheel-box canvas {
  position: absolute;
  inset: 0;
  width: 100% !important;
  height: 100% !important;
}

.wheel-mid {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  pointer-events: none;
}

.wheel-mid small {
  color: var(--muted);
  font-size: 0.75rem;
}

.wheel-says {
  min-height: 2.6em;
  margin: 0.4rem 0 0;
  text-align: center;
  color: var(--muted);
  font-size: 0.86rem;
}

.wheel-says b {
  color: var(--ink);
  font-weight: 500;
}
/* The start screen's ring: the sprint the Start button would make, as an arc
 * open at the bottom, with the length and Start in the middle and two labels
 * under its ends. Painted by activity-ring.js. Every size inside it is in
 * --u, one unit of the drawing's 360 by 322 box, so the words scale with the
 * ring. See docs/design/lounge-start-ring.md. */

.ring {
  position: relative;
  width: 100%;
  max-width: 24rem;
  margin: 0.2rem auto 0;
  aspect-ratio: 360 / 322;
  container-type: inline-size;
  --u: calc(100cqw / 360);
  /* The phases' colours, as the bar of time has them (.panel-bar). */
  --ph-join: var(--amber);
  --ph-focus: var(--good);
  --ph-counts: var(--accent);
  --ph-break: var(--break);
}

.ring-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.ring-track {
  fill: none;
  stroke: var(--border);
  stroke-width: 3;
}

.ring-seg {
  fill: none;
  stroke-width: 15;
  cursor: pointer;
  transition: stroke-width 0.12s ease;
}

.ring-seg:hover {
  stroke-width: 19;
}

.ring-seg.is-join {
  stroke: var(--ph-join);
}

.ring-seg.is-focus {
  stroke: var(--ph-focus);
}

.ring-seg.is-counts {
  stroke: var(--ph-counts);
}

.ring-seg.is-brk {
  stroke: var(--ph-break);
}

/* The typed words decide the length: dashed, as the drawer headings were. */
.ring-seg.is-typed {
  stroke-dasharray: 6 5;
}

/* The length and the line under it, in the middle of the circle. */
.ring-centre {
  position: absolute;
  left: 50%;
  top: calc(var(--u) * 150);
  transform: translate(-50%, -55%);
  width: calc(var(--u) * 236);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* The length, in words ("15 minutes", "10m 30s"), or words for a finishing
 * time or a roll. Not timer digits, which read as a time of day. */
.ring-len {
  max-width: 100%;
  padding: calc(var(--u) * 4) calc(var(--u) * 8);
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--ink);
  font-family: "Jost Digits", var(--font-ui);
  font-size: calc(var(--u) * 37);
  font-weight: 600;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.ring-len:hover {
  background: var(--surface-2);
}

.ring-len.is-long {
  font-size: calc(var(--u) * 31);
}

.ring-len.is-wordy {
  padding-block: calc(var(--u) * 12);
  font-family: var(--font-ui);
  font-size: calc(var(--u) * 30);
}

.ring-sub {
  min-height: 1.2em;
  margin: calc(var(--u) * 2) 0 0;
  color: color-mix(in srgb, var(--ph-focus) 80%, var(--ink));
  font-size: calc(var(--u) * 14.5);
  line-height: 1.25;
}

.ring-typed,
.ring-warn {
  color: var(--amber);
}

/* Start, under the ring, and the starter's own join under it. */
.start-go-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  margin: 0.4rem 0 0.2rem;
}

.start-form .start-go {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  max-width: 22rem;
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  font-size: 1.05rem;
  font-weight: 700;
  white-space: nowrap;
}

.start-form .start-go span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.start-go svg {
  flex: none;
  width: 0.75rem;
  height: 0.75rem;
}

.start-form .start-join-link {
  max-width: 100%;
  font-size: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The two labels under the ends of the arc. */
.ring-end {
  position: absolute;
  top: calc(var(--u) * 250);
  width: calc(var(--u) * 150);
  display: flex;
  flex-direction: column;
  padding: calc(var(--u) * 4);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink);
  font: inherit;
  line-height: 1.2;
}

button.ring-end {
  cursor: pointer;
}

button.ring-end:hover {
  background: var(--surface-2);
}

.ring-end b {
  font-weight: 500;
  font-size: calc(var(--u) * 14.5);
}

.ring-end .ring-k {
  font-size: calc(var(--u) * 12);
  letter-spacing: 0.02em;
}

.ring-end .ring-tick {
  color: var(--muted);
  font-size: calc(var(--u) * 12);
  font-variant-numeric: tabular-nums;
}

.ring-end.is-left {
  left: calc(var(--u) * 6);
  align-items: flex-start;
  text-align: left;
}

.ring-end.is-left .ring-k {
  color: color-mix(in srgb, var(--ph-join) 85%, var(--ink));
}

.ring-end.is-right {
  right: calc(var(--u) * 6);
  align-items: flex-end;
  text-align: right;
}

.ring-end.is-right .ring-k {
  color: color-mix(in srgb, var(--ph-counts) 85%, var(--ink));
}

/* The preset drop-down, as the card's heading rather than a boxed field. */
.start-form .preset-field select#preset {
  width: auto;
  max-width: 80%;
  padding: 0.2rem 1.4rem 0.2rem 0;
  border: 0;
  background-color: transparent;
  font-size: 1.05rem;
  font-weight: 500;
}

/* A chain's rounds, under Start: one row each, the times in the same digits
 * as the ring, the column heads in the ring's colours for Focus and for word
 * counts. The link to the Round times panel sits quietly beside the heading. */
.rounds-plan {
  width: 100%;
  max-width: 24rem;
  margin: 0.3rem auto 0.2rem;
}

.rounds-plan[hidden],
.rounds-tidy[hidden] {
  display: none;
}

.rounds-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.2rem;
}

.rounds-head h3 {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.rounds-table {
  width: 100%;
  border-collapse: collapse;
  font-family: "Jost Digits", var(--font-ui);
  font-size: 0.9rem;
}

.rounds-table th,
.rounds-table td {
  padding: 0.28rem 0.35rem;
  text-align: left;
  white-space: nowrap;
}

.rounds-table th:last-child,
.rounds-table td:last-child {
  text-align: right;
}

.rounds-table thead th {
  font-family: var(--font-ui);
  font-size: 0.74rem;
  font-weight: 500;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
}

.rounds-table thead th:nth-child(2) {
  color: color-mix(in srgb, var(--good) 85%, var(--ink));
}

.rounds-table thead th:nth-child(3) {
  color: color-mix(in srgb, var(--accent) 85%, var(--ink));
}

.rounds-table tbody th {
  width: 1%;
  font-weight: 500;
  color: var(--muted);
}

.rounds-table tbody tr + tr > * {
  border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}

/* The Other timetables panel: the times as set, then each tidier timetable. */
.rt-picks {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

.rt-h {
  margin: 0.5rem 0 -0.1rem;
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.rt-h:first-child {
  margin-top: 0;
}

.rt-bit {
  white-space: nowrap;
}

.rounds-panel .rt-pick {
  align-items: flex-start;
  gap: 0.15rem;
  padding: 0.5rem 0.7rem;
  text-align: left;
}

.rt-pick .rt-times {
  font-family: "Jost Digits", var(--font-ui);
  font-size: 0.95rem;
}
/* --- Rooms ---------------------------------------------------------------
 * Other channels with Sprinto that the reader can see (mockup v2/around.html):
 * the names in the top corner open the list; on a wide panel it is a rail
 * beside the board; two joined sprints get tabs. Each room's phase colours its
 * ring, the same colours the timeline uses. */

/* The same four the bar uses (see .panel-bar), for the list, which lives
 * outside it in a sheet or the rail. */
.rooms {
  --ph-join: var(--amber);
  --ph-focus: var(--good);
  --ph-counts: var(--accent);
  --ph-break: var(--break);
}

.rooms [data-phase="waiting"],
.tabs [data-phase="waiting"] { --c: var(--ph-join); }
.rooms [data-phase="writing"],
.tabs [data-phase="writing"] { --c: var(--ph-focus); }
.rooms [data-phase="collecting"],
.tabs [data-phase="collecting"] { --c: var(--ph-counts); }
.rooms [data-phase="break"],
.tabs [data-phase="break"] { --c: var(--ph-break); }

/* The names are a button now. It still reads as the names, with a caret
 * saying it opens something. */
.room-btn {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.room-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.room-btn .where-channel::after {
  content: " \25BE";
  color: var(--muted);
  font-size: 0.7rem;
}

/* A phase ring in miniature: filled as far as the phase has run. A room
 * with no sprint has a dashed ring. */
.mini {
  display: inline-block;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px dashed var(--border);
  box-sizing: border-box;
}

.mini.on {
  border: 0;
  background: conic-gradient(
    var(--c, var(--muted)) calc(var(--p, 0) * 360deg),
    color-mix(in srgb, var(--c, var(--muted)) 20%, transparent) 0
  );
  -webkit-mask: radial-gradient(circle, transparent 50%, #000 52%);
  mask: radial-gradient(circle, transparent 50%, #000 52%);
}

.rooms {
  list-style: none;
  margin: 0;
  padding: 0;
}

.rooms li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.5rem;
  align-items: center;
  border-radius: var(--radius-sm);
}

.rooms li.cur {
  background: var(--surface-2);
}

.rooms .grp {
  display: block;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0.6rem 0.4rem 0.2rem;
}

.room-go {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 0.55rem;
  align-items: center;
  min-width: 0;
  min-height: 44px;
  padding: 0.35rem 0.4rem;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.room-go:hover {
  background: var(--surface-2);
}

.room-go:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.r-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.r-n {
  font-weight: 500;
  font-size: 0.9rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.r-n .tag2 {
  margin-left: 0.35rem;
  vertical-align: 1px;
}

.tag2 {
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  padding: 0.02rem 0.4rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--muted);
  font-weight: 400;
}

.r-s {
  font-size: 0.75rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.r-end {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding-right: 0.2rem;
}

.r-v {
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  color: var(--c, var(--muted));
  text-align: right;
  white-space: nowrap;
}

.r-v small {
  display: block;
  font-size: 0.68rem;
  color: var(--muted);
}

.star {
  background: none;
  border: 0;
  min-width: 32px;
  min-height: 32px;
  cursor: pointer;
  color: var(--muted);
  font-size: 1rem;
  line-height: 1;
  border-radius: var(--radius-sm);
}

.star.on {
  color: var(--amber);
}

.star:focus-visible {
  outline: 2px solid var(--accent);
}

.rooms-empty {
  font-size: 0.76rem;
  color: var(--muted);
  margin: 0.6rem 0.4rem 0;
}

/* Under the list until the server list is shared: the one button that opens
 * the "Find my other servers" popup. */
.share-servers {
  margin: 0.6rem 0.4rem 0;
  border-top: 1px solid var(--border);
  padding-top: 0.6rem;
}

/* The popup: what sharing adds and its button, or the steps for reopening.
 * The script shows one of the two. */
.find-servers-sheet p,
.find-servers-sheet ol {
  margin: 0 0 0.6rem;
  font-size: 0.86rem;
  line-height: 1.45;
}

.find-servers-sheet ol {
  padding-left: 1.3rem;
}

.find-servers-sheet li + li {
  margin-top: 0.25rem;
}

.find-servers-sheet .share-why {
  color: var(--muted);
  font-size: 0.78rem;
}

.find-servers-sheet .share-declined {
  padding: 0.45rem 0.6rem;
  border-radius: var(--radius-sm);
  background: var(--surface-2, var(--bg));
  border: 1px solid var(--border);
}

.find-servers-sheet [hidden] {
  display: none;
}

/* A tab per joined sprint, only when there are two or more. A line of their
 * own in the top bar, under the names and the cog and over the timeline
 * (which the bar orders last). */
.tabs {
  display: flex;
  gap: 0.35rem;
  overflow-x: auto;
}

.panel-bar .tabs {
  order: 5;
  flex: 1 0 100%;
}

.tab {
  display: inline-flex;
  gap: 0.35rem;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.15rem 0.55rem 0.15rem 0.3rem;
  font: inherit;
  font-size: 0.78rem;
  white-space: nowrap;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  min-height: 32px;
}

.tab.cur {
  border-color: var(--accent);
}

.tab .mini {
  width: 14px;
  height: 14px;
}

.tab b {
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--c);
}

/* The minutes' words, as on a Rooms row but on the same line. */
.tab small {
  font-size: 0.7rem;
  color: var(--muted);
}

/* The way back from a room somebody switched to, on a line of its own under
 * the strip. Quiet, like the strip's own Back. */
.back-home {
  margin: 0.25rem 0 0.5rem;
}

.back-home .back {
  padding: 0.3rem 0.55rem;
  min-height: 0;
  background: transparent;
  border-color: transparent;
  color: var(--muted);
  margin-left: -0.35rem;
}

.back-home .back:hover {
  color: var(--ink);
  border-color: var(--border);
}

/* The one line under the ledger when tabs are up. */
.for-room {
  font-size: 0.8rem;
  color: var(--muted);
  margin: 0.3rem 0 0;
}

/* In place of the controls, on a room somebody switched to. */
.away-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 0.85rem;
  margin-top: 0.75rem;
  background: var(--surface);
}

.away-card h3 {
  margin: 0 0 0.35rem;
  font-size: 0.95rem;
}

.away-card p {
  margin: 0 0 0.6rem;
  font-size: 0.85rem;
  color: var(--ink);
}

/* The room's Schedule, under the card: the menu's own item, as wide. */
.away-more {
  display: flex;
  justify-content: center;
  margin-top: 0.75rem;
}

.away-more .menu-item {
  width: min(20rem, 100%);
}

/* The rail: hidden until it has rooms in it, and only on a panel wide enough
 * to hold it beside the board. The joined-sprint tabs go with it: every room
 * is already one click away in the rail. */
.rooms-rail {
  display: none;
}

@container panel (min-width: 46rem) {
  .rooms-rail:not([hidden]) {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 15rem;
    overflow-y: auto;
    padding: 0.75rem 0.5rem;
    border-right: 1px solid var(--border);
    background: var(--paper);
    box-sizing: border-box;
  }

  .rooms-rail h2 {
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 0.2rem 0.4rem 0.3rem;
  }

  body:has(.rooms-rail:not([hidden])) #activity-root {
    margin-left: 15rem;
  }

  body:has(.rooms-rail:not([hidden])) .tabs {
    display: none;
  }
}
/* Notices (`activity/inbox`): one card of rows like the To-do list's, each
 * with its title, its body and its presses. */

.inbox-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.inbox-item {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.7rem 0.8rem;
}

.inbox-item + .inbox-item {
  border-top: 1px solid var(--border);
}

/* Unread is the dot and the heavier title; a read notice keeps the dot's
 * space so every title starts at the same place. */
.inbox-dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 0.45rem;
  border-radius: 50%;
  background: var(--accent);
  visibility: hidden;
}

.inbox-item.is-unread .inbox-dot {
  visibility: visible;
}

.inbox-main {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  flex: 1;
  min-width: 0;
}

.inbox-title {
  color: var(--muted);
  overflow-wrap: anywhere;
}

.inbox-item.is-unread .inbox-title {
  color: var(--ink);
  font-weight: 500;
}

.inbox-body {
  font-size: 0.88rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* Manage pets and Mark read, side by side under the body: presses that read
 * as words, Manage pets in the link colour and Mark read quiet. */
.inbox-acts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 1.1rem;
  margin-top: 0.2rem;
}

.inbox-acts form {
  margin: 0;
}

.inbox-acts button {
  min-height: 24px;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: 0.88rem;
  cursor: pointer;
}

.inbox-acts .inbox-go {
  color: var(--link);
}

.inbox-acts .inbox-mark-read {
  color: var(--muted);
}

.inbox-acts button:hover {
  color: var(--ink);
}

.inbox-all {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.3rem;
  margin-top: 0.7rem;
}

.inbox-all form {
  margin: 0;
}

/* A press that did not land, next to where it was made. */
.inbox-err {
  margin: 0;
  font-size: 0.85rem;
  color: var(--bad);
}

.inbox-err[hidden] {
  display: none;
}

.inbox-empty {
  margin: 0.4rem 0;
  color: var(--muted);
}
/* Pets: the manager, its tiles, the detail pane, the emoji picker and the
 * hatching bar.
 *
 * Its own file because two surfaces draw it. The website loads it as part of
 * `app.css` and the Discord Activity loads it after `activity.css`: they are
 * one screen in two windows, from one set of rules, and a rule added for one of
 * them is a rule the other gets.
 *
 * Everything here is written against the shared tokens, which both surfaces
 * define, so nothing depends on which of the two stylesheets came first. */

/* ---- What the panel has not got --------------------------------------- */
/* The manager is written against the website's component classes, and the
 * panel loads `activity.css` rather than `app.css`, so in the Focus Lounge its
 * badges, buttons and form rows arrived with no rules at all: two badges ran
 * together into "SupporterFounder 2026", and Save and Back were full-width
 * bars. These are the missing few, copied from `app.css` and scoped to the
 * panel so the website keeps drawing them from the original. */
body.activity .card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.15rem;
  box-shadow: var(--shadow);
  margin: .85rem 0;
}
body.activity .card > :first-child { margin-top: 0; }
body.activity .card > :last-child { margin-bottom: 0; }
body.activity .card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: .6rem; margin-bottom: .5rem;
}
body.activity .card-head h2 { margin: 0; font-size: 1.05rem; font-weight: 600; }
body.activity .badge {
  display: inline-block; font-size: .7rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .06em;
  padding: .16em .55em; border-radius: 99px;
  border: 1px solid var(--border); color: var(--muted);
  vertical-align: middle;
}
/* The head pushes its badges apart from the heading; the badges themselves sit
   in a run of their own so a second one cannot land against the first. */
body.activity .card-head > .badge + .badge { margin-left: .35rem; }
/* The manager's badges, together at the end of its head on both surfaces. */
.card-badges { display: inline-flex; flex-wrap: wrap; gap: .35rem; justify-content: flex-end; }
body.activity .badge-active {
  color: var(--good);
  border-color: color-mix(in srgb, var(--good) 45%, transparent);
  background: color-mix(in srgb, var(--good) 12%, transparent);
}
body.activity .badge-founder {
  color: var(--amber);
  border-color: color-mix(in srgb, var(--amber) 50%, transparent);
  background: color-mix(in srgb, var(--amber) 14%, transparent);
}
body.activity .btn {
  display: inline-block; width: auto; font-size: .9rem;
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: .32em .9em; cursor: pointer; text-decoration: none;
}
body.activity .btn:hover { background: var(--border); border-color: var(--muted); }
body.activity .btn-primary {
  display: inline-block; width: auto; font-size: .9rem;
  background: var(--accent); border: 1px solid var(--accent);
  border-radius: var(--radius-sm); padding: .32em .9em;
  color: var(--on-accent); font-weight: 600; cursor: pointer;
}
body.activity .btn-primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
body.activity .setform { margin: .15rem 0 0; }
body.activity .setform .actions { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .55rem; }
body.activity #pets-manager .field {
  display: flex; flex-direction: row; align-items: center;
  gap: .7rem; margin: .45rem 0; flex-wrap: wrap;
}
body.activity #pets-manager .notice { color: var(--muted); font-style: italic; }
body.activity #pets-manager .notice.bad { color: var(--bad); font-style: normal; font-weight: 600; }
body.activity #pets-manager .flash {
  border: 1px solid color-mix(in srgb, var(--good) 45%, transparent);
  background: color-mix(in srgb, var(--good) 12%, transparent);
  color: var(--ink); border-radius: var(--radius-sm); padding: .45rem .7rem;
}

/* ---- Companion (pet) inventory ---------------------------------------- */
/* A square-tile grid, built to also hold badges / other unlocks later. */
.inventory-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
  gap: .7rem;
  margin: .5rem 0 1.2rem;
}
.pet-tile-form { margin: 0; }
/* Each tile is a clickable button that makes the pet active. */
.pet-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  width: 100%;
  aspect-ratio: 1;
  padding: .5rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  text-align: center;
}
.pet-tile:hover { border-color: var(--muted); background: var(--surface); }
.pet-tile.is-active { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.pet-tile.is-new { border-color: var(--good); box-shadow: 0 0 0 2px color-mix(in srgb, var(--good) 35%, transparent); }
/* The centred art slot: an emoji glyph, or a pixel design's <img>. */
.pet-art { font-size: 2.4rem; line-height: 1; }
.pet-art img { width: 2.4rem; height: 2.4rem; image-rendering: pixelated; }
.pet-name { font-size: .82rem; color: var(--ink); overflow-wrap: anywhere; line-height: 1.2; }
/* A tile is a button, and the panel's buttons keep their label on one line.
   A tile's label wraps instead: "companion seat" is wider than a tile. */
.pet-tile .pet-name { white-space: normal; max-width: 100%; }
/* Two lines of label under the cushion, so a smaller cushion and label to
   keep the tile square like the rest. */
.seat-tile .pet-name { font-size: .72rem; line-height: 1.1; }
.seat-tile .pet-art { font-size: 2rem; }
.seat-tile .pet-art .pet-art-img { width: 2rem; height: 2rem; }
/* Pixel-design artwork: crisp scaling, sized for the tile; .pet-art-lg in the editor. */
.pet-art-img { image-rendering: pixelated; width: 2.4rem; height: 2.4rem; object-fit: contain; }
/* Twemoji_R and Noto_R on the website: the set's own drawing, turned to face
   right as the uploaded artwork is (`web::pets::look_art`). */
.pet-art-img.is-mirrored { transform: scaleX(-1); }
.pet-art-lg { width: 4rem; height: 4rem; }
/* A pet beside its name in a card head or a form row: text-sized, not a tile. */
.pet-art-inline { width: 1.5rem; height: 1.5rem; object-fit: contain; vertical-align: -.25rem; image-rendering: pixelated; }
/* Read-only catalog preview tile (owner "view as"): looks like a tile, not clickable. */
.preview-tile { cursor: default; gap: .25rem; }
.preview-tile .hint { font-size: .72rem; }

/* ---- The editor's fields ---------------------------------------------- */
/* The look and the name as they stand, each with its own way in. Neither is an
   open text box on arrival: a pet is renamed once, and typing an emoji by hand
   is not how anybody picks one. */
/* `flex-direction` and `justify-content` are spelled out because the plain
   `.field` these also carry stacks its label above its box, and half an
   inherited layout is worse than either whole one. */
.pet-look-field, .pet-name-field {
  display: flex; flex-direction: row; align-items: center;
  justify-content: flex-start; gap: .5rem; flex-wrap: wrap;
}
/* `:first-child` only to outweigh the website's own `.field > label:first-child`,
   which reserves 15rem for a settings label and would leave the name out past
   the edge of the pane. */
.pet-look-field > label:first-child, .pet-name-field > label:first-child {
  flex: 0 0 3.2rem; max-width: 3.2rem; color: var(--muted); font-size: .8rem;
}
.pet-look-shown { font-size: 1.6rem; line-height: 1; }
.pet-name-shown { font-size: 1rem; color: var(--ink); overflow-wrap: anywhere; }
.pet-rename { padding: .2rem .55rem; font-size: .8rem; }
.pet-name-field input[type="text"] { flex: 1 1 8rem; min-width: 0; }

/* ---- The art style row ------------------------------------------------- */
/* Three drawings of the same creature, side by side. The pictures are the
   choice; the names under them are for saying which one you mean out loud. */
.pet-style-field { align-items: flex-start; }
.style-picks { display: flex; flex-wrap: wrap; gap: .4rem; }
.style-pick {
  display: flex; flex-direction: column; align-items: center; gap: .25rem;
  padding: .35rem .5rem; min-width: 4.4rem;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); cursor: pointer;
}
.style-pick:hover { border-color: var(--muted); }
.style-pick.is-chosen { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.style-pick[disabled] { opacity: .45; cursor: not-allowed; }
.style-art { font-size: 1.6rem; line-height: 1; }
.style-art .pet-art-img { width: 1.8rem; height: 1.8rem; }
.style-name { font-size: .7rem; color: var(--muted); }

/* ---- Searching the grid ------------------------------------------------ */
/* Sits between the fields and the grid, and outside both, so a roll or a style
   change does not take the query with it. */
.grid-search { margin: .5rem 0 .1rem; }
.grid-search input { width: 100%; max-width: 16rem; }
.grid-empty { margin: .5rem 0; font-size: .85rem; }

/* Randomize row under the edit fields. */
.pet-roll { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin: .35rem 0 .1rem; }
.pet-roll .btn { padding: .25rem .6rem; font-size: .82rem; }

/* ---- Pets manager: master-detail ------------------------------------- */
/* Detail beside the roster on desktop; stacked (detail first) on a narrow
   screen, so tapping a tile reveals the detail right above it.
 *
 * Measured against the card, not the window. The Activity draws this card in a
 * column about 32rem wide however big the Discord window is, so a viewport
 * query put the roster and the detail side by side in half a phone's width and
 * squashed both. The card is the thing that has or hasn't got the room, so the
 * card is what gets asked. Unnamed, because the website has no named container
 * to answer. */
#pets-manager { container-type: inline-size; }
.pets-layout { display: flex; flex-direction: column; gap: 1rem; }
.pets-detail { order: -1; }            /* mobile: detail above the roster */
.pets-roster { min-width: 0; }
@container (min-width: 40rem) {
  .pets-layout { flex-direction: row; align-items: flex-start; }
  .pets-roster { flex: 1 1 0; order: 0; }
  .pets-detail { flex: 1.25 1 0; order: 0; position: sticky; top: 1rem; }
}
/* The roster grid: smaller tiles than the standalone inventory grid so the
   roster sits comfortably beside the detail. */
.roster-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr));
  gap: .5rem;
}
/* A roster tile that's the sprint companion (its name already carries a ✓). */
.pet-tile.is-sitting { border-color: color-mix(in srgb, var(--good) 60%, var(--border)); }
.seat-tile .pet-art, .egg-tile .pet-art { opacity: .9; }
/* The hatching tile sits in the roster; its bar sits under the egg glyph. */
.hatch-tile { cursor: default; }

/* The detail pane: a soft inset panel with the focused pet's stats + actions. */
.pets-detail {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .9rem 1rem;
}
.detail-hint { margin: .2rem 0; }
.detail-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .4rem; }
.detail-head h3 { margin: 0; }
.detail-art { font-size: 2.4rem; line-height: 1; }
.detail-art .pet-art-img { width: 3rem; height: 3rem; }
.pet-stats { list-style: none; margin: .3rem 0 .6rem; padding: 0; color: var(--ink); }
.pet-stats li { padding: .1rem 0; font-size: .9rem; }
.detail-actions { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; align-items: center; }
.detail-actions .inline-form { margin: 0; }
.inline-form { display: inline; }

/* The "pet pet" heart button + the little heart it floats on click. */
.pet-heart { font-size: 1rem; line-height: 1; color: var(--accent); }
.floating-heart {
  position: absolute;
  pointer-events: none;
  font-size: 1.4rem;
  animation: heart-float 1s ease-out forwards;
}
@keyframes heart-float {
  from { opacity: 1; transform: translateY(0) scale(.8); }
  to   { opacity: 0; transform: translateY(-2.5rem) scale(1.3); }
}

/* The appearance editor's full category grid (scrollable). */
.appearance-grid {
  max-height: 18rem;
  overflow-y: auto;
  margin: .5rem 0;
  padding: .4rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.cat-head { margin: .5rem 0 .25rem; font-size: .78rem; color: var(--muted); text-transform: capitalize; font-weight: 600; }
.cat-head:first-child { margin-top: 0; }
.cat-row { display: flex; flex-wrap: wrap; gap: .3rem; }
.emoji-cell {
  font-size: 1.3rem;
  line-height: 1;
  padding: .25rem .35rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
/* A drawn style puts a picture in the cell where the character was. Sized to
   the line the plain characters make, so the grid does not change shape when
   the style does. */
.emoji-cell .pet-art-img { width: 1.5rem; height: 1.5rem; display: block; }
.emoji-cell:hover { border-color: var(--accent); background: var(--surface); }
.emoji-cell.is-chosen { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

/* A hatching egg's loading bar: fills 0->100% over a fixed 10-minute-looking
   window (the inline animation-delay starts it in the past so it completes
   exactly when the egg actually hatches). */
.hatch-bar {
  width: 100%;
  height: .5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.hatch-bar > span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  animation-name: hatch-fill;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}
@keyframes hatch-fill { from { width: 0; } to { width: 100%; } }
/* --- The Schedule (docs/design/scheduling.md) ----------------------------
 *
 * Loaded with activity.css (`ACTIVITY_CSS`). The list, the week, the sheets,
 * the calendar link and the two forms, the sprint's and the event's
 * (docs/design/scheduling-events.md). Everything else on these screens is
 * the panel's own: fields, choices, drawers, sheets, notes. */

.sched-head .sched-zone {
  margin: 0.1rem 0 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.sched-screen {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.sched-err[hidden],
.sched-copied[hidden] {
  display: none;
}

/* A press that did not land, next to where it was made. */
.sched-err {
  margin: 0;
  font-size: 0.85rem;
  color: var(--bad);
}

/* This room / All rooms, and List / Week: two pairs, the one on screen
 * pressed. */
.sched-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.8rem;
}

.sched-seg {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
}

.sched-seg button {
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--muted);
  padding: 0.35rem 0.8rem;
  min-height: 2.2rem;
}

.sched-seg button + button {
  border-left: 1px solid var(--border);
}

.sched-seg button[aria-pressed="true"] {
  background: var(--accent-soft);
  color: var(--ink);
  font-weight: 500;
}

/* --- The list ------------------------------------------------------------ */

.sched-day h2 {
  margin: 0.4rem 0 0.3rem;
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
}

.sched-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.sched-item + .sched-item {
  border-top: 1px solid var(--border);
}

/* One row: when writing starts, what it is, where, and a tag. The time is
 * the column a skim runs down. */
.sched-row {
  display: grid;
  grid-template-columns: 5.2rem minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: 0.6rem;
  width: 100%;
  min-height: 2.8rem;
  padding: 0.55rem 0.7rem;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  text-align: left;
  font: inherit;
  cursor: pointer;
}

.sched-row:hover {
  background: var(--surface-2);
}

.sched-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.sched-time {
  font-family: "Jost Digits", var(--font-ui);
  font-weight: 500;
  white-space: nowrap;
}

.sched-what {
  min-width: 0;
  overflow-wrap: anywhere;
}

.sched-room {
  grid-column: 2;
  grid-row: 2;
  font-size: 0.82rem;
  color: var(--muted);
}

.sched-tags {
  grid-column: 3;
  grid-row: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.3rem;
}

.sched-line {
  grid-column: 2 / -1;
  font-size: 0.82rem;
}

.sched-line.is-bad,
.tag.is-bad {
  color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 45%, transparent);
}

.tag.is-on {
  color: var(--good);
  border-color: color-mix(in srgb, var(--good) 45%, transparent);
}

/* Over, and still here to say what came of it. */
.sched-row.is-past .sched-time,
.sched-row.is-past .sched-what {
  color: var(--muted);
}

/* An event is not a sprint: its row is a header with a stripe down its side
 * and its block as a span, and its sprints are rows of their own under it,
 * set in from the stripe. */
.sched-row.is-event {
  grid-template-columns: minmax(6.5rem, auto) minmax(0, 1fr) auto;
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}

.sched-row.is-event .sched-time {
  font-weight: 400;
  color: var(--muted);
}

.sched-row.is-event .sched-what {
  font-weight: 500;
}

.sched-host {
  grid-column: 2 / -1;
  color: var(--muted);
}

.sched-list.sched-in-event {
  margin: 0 0 0.4rem 0.9rem;
  border-radius: var(--radius-sm);
  box-shadow: inset 3px 0 0 color-mix(in srgb, var(--accent) 45%, transparent);
}

.sched-empty p,
p.sched-empty {
  margin: 0.2rem 0;
  color: var(--muted);
}

button.linkish {
  border: 0;
  background: none;
  padding: 0;
  min-height: 0;
  color: var(--link);
  text-decoration: underline;
  font: inherit;
  cursor: pointer;
}

.sched-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.sched-denied {
  margin: 0;
}

/* --- The week ------------------------------------------------------------ */

.sched-weeknav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}

.sched-weeknav button {
  min-height: 2.2rem;
  min-width: 2.4rem;
}

.sched-weeknav button[aria-pressed="true"] {
  border-color: var(--accent);
}

.sched-weekname {
  margin-left: 0.3rem;
  color: var(--muted);
  font-size: 0.9rem;
}

.sched-cal {
  min-height: 2rem;
}

/* Event Calendar themes itself through custom properties set on the `.ec`
 * element it makes, so these go there and not on the host: a value on the
 * host is beaten by the library's own on `.ec`. */
.sched-cal .ec {
  color-scheme: dark;
  --ec-color-50: var(--paper);
  --ec-color-100: var(--surface);
  --ec-color-200: var(--surface-2);
  --ec-color-300: var(--border);
  --ec-color-400: var(--muted);
  --ec-bg-color: var(--surface);
  --ec-text-color: var(--ink);
  --ec-border-color: var(--border);
  --ec-today-bg-color: color-mix(in srgb, var(--accent) 10%, transparent);
  --ec-highlight-color: color-mix(in srgb, var(--accent) 18%, transparent);
  --ec-now-indicator-color: var(--bad);
  --ec-popup-bg-color: var(--paper);
  --ec-event-bg-color: var(--accent);
  --ec-event-text-color: #1b1410;
  font-size: 0.85rem;
}

:root[data-theme="paper"] .sched-cal .ec {
  color-scheme: light;
}

.sched-cal .ec-event {
  cursor: pointer;
}

.sched-cal .ev-sprint.is-past {
  --ec-event-bg-color: color-mix(in srgb, var(--accent) 45%, var(--surface));
}

.sched-cal .ev-sprint.is-bad {
  --ec-event-bg-color: var(--bad);
}

/* An event: a block behind its sprints, in the list's event colour, with a
 * stripe down its side. It can't be pressed there; the buttons above the
 * grid open its sheet. */
.sched-cal .ec-bg-event.ev-block {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
  opacity: 1;
}

/* A sprint inside an event, outlined so it reads as part of the block. */
.sched-cal .ev-sprint.in-event {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink) 35%, transparent);
}

/* The week's events, as buttons above the grid. */
.sched-week-events-h {
  margin: 0.2rem 0 0;
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
}

.sched-week-events {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.sched-event-chip {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.05rem;
  padding: 0.35rem 0.7rem;
  min-height: 2.4rem;
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
  border: 1px solid var(--border);
  box-shadow: inset 3px 0 0 var(--accent);
  text-align: left;
}

.sched-event-chip .chip-title {
  font-weight: 500;
}

.sched-event-chip .chip-when {
  font-size: 0.8rem;
  color: var(--muted);
}

/* The popup a range dragged on the week opens: its two choices. */
dialog.sheet.sched-pick .proj-btns {
  flex-wrap: wrap;
}

/* --- A sprint's sheet ---------------------------------------------------- */

.sched-sheet p {
  margin: 0.25rem 0;
}

.sched-sheet-room {
  color: var(--muted);
}

.sched-sheet-title {
  font-weight: 500;
}

.sched-status {
  list-style: none;
  margin: 0.4rem 0;
  padding: 0;
  font-size: 0.85rem;
  color: var(--muted);
}

.sched-status li + li {
  margin-top: 0.15rem;
}

.sched-status li.is-bad {
  color: var(--bad);
}

/* "add Sprinto again" in a status line, and under Server event on the form
   when Sprinto can't add events in the server. */
.sched-status a,
.sched-warn a {
  color: var(--link);
}

.sched-warn {
  color: var(--ink);
  border-left: 3px solid var(--bad);
  padding-left: 0.6rem;
}

.sched-sheet-ics a.button {
  display: inline-block;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  text-decoration: none;
}

.sched-sheet-ics a.button:hover {
  border-color: var(--accent);
}

.sched-sheet-foot {
  flex-wrap: wrap;
}

/* An event's sheet: its MCs and its sprints under small headings. */
.sched-sheet-h {
  margin: 0.7rem 0 0.25rem;
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
}

.sched-sheet-rule {
  font-size: 0.9rem;
}

.sched-mcs {
  list-style: none;
  margin: 0;
  padding: 0;
}

.sched-mcs li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.4rem;
  padding: 0.15rem 0;
}

.sched-mcs .sched-line {
  flex-basis: 100%;
}

.sched-sheet-sprints .sched-row {
  grid-template-columns: 5.2rem minmax(0, 1fr);
}

.sched-sheet-event {
  margin: 0.25rem 0;
}

.sched-also {
  margin: 0.4rem 0 0.1rem;
}

.sched-also-list {
  margin: 0 0 0.4rem;
  padding-left: 1.2rem;
}

/* --- The calendar link --------------------------------------------------- */

.sched-link {
  margin-top: 0.6rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--border);
}

.sched-link h2 {
  margin: 0 0 0.3rem;
  font-size: 1rem;
}

.sched-link p {
  margin: 0.2rem 0;
}

.sched-link-row {
  display: flex;
  gap: 0.5rem;
  margin: 0.4rem 0;
  min-width: 0;
}

.sched-link-row input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

.sched-link-row button {
  flex: none;
}

.sched-copied {
  font-size: 0.85rem;
  color: var(--good);
}

.sched-copied.is-bad {
  color: var(--amber);
}

/* --- The forms ----------------------------------------------------------- */

.sched-form {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.sched-when {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0.6rem;
}

.sched-when input {
  width: 100%;
}

/* Where the room, date and time put a sprint inside an event: said, and the
 * sprint's own Repeat, Announce and Server event hidden (the event has
 * them). They stay in the form, as they were. */
.sched-in-event:empty {
  display: none;
}

.sched-in-event {
  padding: 0.45rem 0.7rem;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}

.sched-in-event p {
  margin: 0.1rem 0;
}

.sched-form:has(#sched-in-event[data-inside]) .sched-own {
  display: none;
}

/* A value the form shows and doesn't change: an event's room while it has
 * sprints, its start once it has started. */
.sched-fixed {
  margin: 0.2rem 0;
  font-weight: 500;
}

.ev-people .sched-search {
  margin-top: 0.3rem;
}

/* The announcement's own words. */
.sched-form textarea {
  width: 100%;
  min-height: 5.5rem;
  resize: vertical;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  padding: 0.55rem 0.7rem;
  font: inherit;
}

.sched-form textarea:focus {
  border-color: var(--accent);
  outline: none;
}

.sched-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.2rem 0;
  cursor: pointer;
}

.sched-choices {
  flex-wrap: wrap;
}

/* Event emojis: one slot a row, each a button showing its emoji, the box,
 * and × while there is something to clear. */
.sched-emojis {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.5rem;
}

.emoji-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.sched-emojis input {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
}

.emoji-pick {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  min-height: 0;
  padding: 0;
  background: var(--paper);
  font-size: 1.4rem;
  line-height: 1;
}

.emoji-face {
  display: grid;
  place-items: center;
}

.emoji-face .md-emoji {
  height: 1.6rem;
  vertical-align: 0;
}

.emoji-face-empty {
  width: 1.45rem;
  height: 1.45rem;
  color: var(--muted);
}

.emoji-face.is-refused .emoji-face-empty {
  color: var(--bad);
}

.emoji-clear {
  flex: none;
  min-height: 0;
  padding: 0.3rem 0.55rem;
  font-size: 1.15rem;
  line-height: 1;
}

/* The picker's popup, kept outside the board. The picker draws inside its
 * own shadow DOM and takes its colours from these variables. */
dialog.sheet.emoji-sheet {
  gap: 0.5rem;
}

.emoji-sheet emoji-picker {
  width: 100%;
  height: min(24rem, 62dvh);
  --background: var(--surface);
  --border-color: var(--border);
  --border-radius: var(--radius-sm);
  --button-active-background: var(--surface-2);
  --button-hover-background: var(--surface-2);
  --category-font-color: var(--muted);
  --indicator-color: var(--accent);
  --input-border-color: var(--border);
  --input-border-radius: var(--radius-sm);
  --input-font-color: var(--ink);
  --input-placeholder-color: var(--muted);
  --outline-color: var(--accent);
}

.emoji-failed {
  margin: 0;
  color: var(--bad);
}

.sched-says {
  min-height: 1.3em;
}

.sched-says-line.is-refused {
  color: var(--bad);
}

.sched-people {
  list-style: none;
  margin: 0.3rem 0;
  padding: 0;
  max-height: 14rem;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

.sched-people li + li {
  border-top: 1px solid var(--border);
}

.sched-person {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.7rem;
  cursor: pointer;
}

.sched-person:has(input:checked) {
  background: var(--accent-soft);
}

.sched-person-fixed {
  margin: 0.2rem 0;
  font-weight: 500;
}

.sched-search {
  width: 100%;
}

/* A phone: the two dates one above the other, and the emoji slots too. */
@media (max-width: 30rem) {
  .sched-when,
  .sched-emojis {
    grid-template-columns: minmax(0, 1fr);
  }

  .sched-row {
    grid-template-columns: 4.8rem minmax(0, 1fr) auto;
    column-gap: 0.45rem;
  }
}

/* The controls the panel's own stylesheet leaves to the browser, drawn the
 * way its text boxes are. `color-scheme` gives the time picker the dark
 * clock. The date boxes are text boxes, styled with the rest. */
.sched-form select,
.sched-form input[type="time"],
.sched-form input[type="number"],
.sched-form input[type="search"] {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--ink);
  color-scheme: dark;
  padding: 0.55rem 0.7rem;
  font: inherit;
  min-width: 0;
  width: 100%;
}

:root[data-theme="paper"] .sched-form :is(select, input) {
  color-scheme: light;
}

.sched-form select:focus,
.sched-form input:focus {
  border-color: var(--accent);
  outline: none;
}

.sched-form .rounds .brk input {
  width: 4.5rem;
}

/* A closed heading whose value is words rather than a figure. */
.sched-form .fact-v {
  font-size: 1rem;
}

/* --- The date picker ------------------------------------------------------
 *
 * flatpickr's month, on the forms' date boxes (activity-schedule.js). It is
 * put at the end of the page, outside the board, so nothing here is under
 * `.sched-form`; and its own stylesheet arrives after this one, so every rule
 * starts from `.activity` to outrank it. The colours are the panel's tokens,
 * so the paper theme follows. */

.activity .flatpickr-calendar {
  background: var(--paper);
  color: var(--ink);
  color-scheme: dark;
  font-family: inherit;
  box-shadow:
    0 0 0 1px var(--border),
    0 6px 18px rgba(0, 0, 0, 0.35);
}

:root[data-theme="paper"] .activity .flatpickr-calendar {
  color-scheme: light;
  box-shadow:
    0 0 0 1px var(--border),
    0 6px 18px rgba(0, 0, 0, 0.12);
}

.activity .flatpickr-calendar.arrowTop::before {
  border-bottom-color: var(--border);
}

.activity .flatpickr-calendar.arrowTop::after {
  border-bottom-color: var(--paper);
}

.activity .flatpickr-calendar.arrowBottom::before {
  border-top-color: var(--border);
}

.activity .flatpickr-calendar.arrowBottom::after {
  border-top-color: var(--paper);
}

/* The month of a box inside a popup, in the page's top layer over it as a
 * popover (`activity-schedule.js`): placed by the script, with none of a
 * popover's own frame, and no arrow. */
.activity .flatpickr-calendar[popover] {
  position: fixed;
  inset: auto;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: visible;
}

.activity .flatpickr-calendar[popover]::before,
.activity .flatpickr-calendar[popover]::after {
  display: none;
}

.activity .flatpickr-calendar .flatpickr-month,
.activity .flatpickr-calendar .flatpickr-prev-month,
.activity .flatpickr-calendar .flatpickr-next-month {
  color: var(--ink);
  fill: var(--ink);
}

.activity .flatpickr-calendar .flatpickr-prev-month:hover svg,
.activity .flatpickr-calendar .flatpickr-next-month:hover svg {
  fill: var(--accent);
}

.activity .flatpickr-calendar .flatpickr-current-month {
  font-size: 1.1rem;
}

.activity .flatpickr-calendar .flatpickr-monthDropdown-months,
.activity .flatpickr-calendar input.cur-year {
  font-weight: 500;
}

.activity .flatpickr-calendar .flatpickr-monthDropdown-months option {
  background: var(--paper);
  color: var(--ink);
}

.activity .flatpickr-calendar .flatpickr-monthDropdown-months:hover,
.activity .flatpickr-calendar .numInputWrapper:hover {
  background: var(--surface-2);
}

.activity .flatpickr-calendar .numInputWrapper span {
  border-color: var(--border);
}

.activity .flatpickr-calendar .numInputWrapper span.arrowUp::after {
  border-bottom-color: var(--ink);
}

.activity .flatpickr-calendar .numInputWrapper span.arrowDown::after {
  border-top-color: var(--ink);
}

.activity .flatpickr-calendar span.flatpickr-weekday {
  color: var(--muted);
  font-weight: 500;
}

.activity .flatpickr-calendar .flatpickr-day {
  color: var(--ink);
}

.activity .flatpickr-calendar .flatpickr-day:hover,
.activity .flatpickr-calendar .flatpickr-day:focus {
  background: var(--surface-2);
  border-color: var(--surface-2);
}

.activity .flatpickr-calendar .flatpickr-day.today {
  border-color: var(--muted);
}

.activity .flatpickr-calendar .flatpickr-day.today:hover,
.activity .flatpickr-calendar .flatpickr-day.today:focus {
  background: var(--surface-2);
  border-color: var(--muted);
  color: var(--ink);
}

.activity .flatpickr-calendar .flatpickr-day.prevMonthDay,
.activity .flatpickr-calendar .flatpickr-day.nextMonthDay {
  color: color-mix(in srgb, var(--ink) 40%, transparent);
}

.activity .flatpickr-calendar .flatpickr-day.flatpickr-disabled {
  color: color-mix(in srgb, var(--ink) 15%, transparent);
}

/* The chosen day: the accent, with the dark text the week's sprints have. */
.activity .flatpickr-calendar .flatpickr-day.selected,
.activity .flatpickr-calendar .flatpickr-day.selected:hover,
.activity .flatpickr-calendar .flatpickr-day.selected:focus {
  background: var(--accent);
  border-color: var(--accent);
  color: #1b1410;
  font-weight: 600;
}
/* --- Chatter ---------------------------------------------------------------
 *
 * The bar at the foot of the screen with what Sprinto knows happened in the
 * room (`web/activity/chatter.rs`, `activity-chatter.js`). Shut, it is one
 * line: the newest. Open, it is a list, newest at the bottom like a chat,
 * as tall as the reader drags it.
 *
 * Fixed to the bottom of the panel because on a phone the Lounge covers the
 * channel, and this is the channel's stand-in: it has to be there whatever
 * the reader has scrolled to. The page under it gets the shut bar's height
 * as padding, so Log count and Mark me done are never under it. */

:root {
  /* The shut bar: the grip and one line. The page's bottom padding. */
  --chatter-shut: 3.85rem;
}

body.has-chatter #activity-root {
  padding-bottom: calc(var(--chatter-shut) + 0.75rem);
}

/* And whatever is scrolled to (a box taking the caret, the join card's note)
 * stops above the bar, not behind it. */
html:has(body.has-chatter) {
  scroll-padding-bottom: calc(var(--chatter-shut) + 0.75rem);
}

.chatter {
  position: fixed;
  z-index: 20;
  bottom: 0;
  left: 50%;
  translate: -50% 0;
  width: min(100%, 36rem);
  height: var(--chatter-h, var(--chatter-shut));
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -6px 22px rgba(0, 0, 0, 0.28);
  /* Eases when tapped open or shut; follows the finger without delay. */
  transition: height 0.18s ease;
}

.chatter[hidden] {
  display: none;
}

.chatter.is-dragging {
  transition: none;
}

/* The grip: dragged to size the bar, or moved with the arrow keys. Its
 * whole width takes the drag. */
.chatter-grip {
  flex: none;
  /* 24px: the smallest a target should be. The drawn grip is a short bar
   * in the middle of it. */
  height: 1.5rem;
  cursor: ns-resize;
  touch-action: none;
  display: grid;
  place-items: center;
}

.chatter-grip::before {
  content: "";
  width: 2.25rem;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
}

.chatter-grip:hover::before,
.chatter-grip:focus-visible::before {
  background: var(--muted);
}

/* Shut: the newest line, cut to one line with the unread count at the end. */
.chatter-peek {
  flex: none;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 0.5rem;
  align-items: baseline;
  width: 100%;
  min-height: 2.2rem;
  padding: 0 0.85rem 0.5rem;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: none;
}

.chatter[data-open] .chatter-peek {
  display: none;
}

.peek-when {
  font-size: 0.74rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.peek-what {
  min-width: 0;
  font-size: 0.86rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.peek-unread {
  font-size: 0.78rem;
  color: var(--link);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Open: the list. */
.chatter-list {
  display: none;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
  margin: 0;
  padding: 0.1rem 0.85rem 0.6rem;
}

.chatter[data-open] .chatter-list {
  display: block;
}

.chatter-newest {
  position: absolute;
  bottom: 0.6rem;
  left: 50%;
  translate: -50% 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--link);
  font: inherit;
  font-size: 0.8rem;
  padding: 0.25rem 0.7rem;
  cursor: pointer;
}

.chatter-newest[hidden] {
  display: none;
}

/* One line: its time, then the line. Your own line's state sits under it,
 * as Discord puts "Only you can see this" under its own messages. */
.chatter .ln {
  display: grid;
  grid-template-columns: 4.1rem 1fr;
  column-gap: 0.5rem;
  align-items: baseline;
  padding: 0.18rem 0;
  font-size: 0.86rem;
}

/* The day over the first line of each day, written in by the script on the
 * reader's own calendar. */
.chatter .ln[data-day]::before {
  content: attr(data-day);
  grid-column: 1 / -1;
  text-align: center;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding: 0.55rem 0 0.25rem;
}

.chatter .ln .t {
  font-size: 0.74rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.chatter .ln .w {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* A scoreboard's line: a button that opens the scoreboard in a popup
 * (`past_board.rs`). Drawn as the line's own text, as your lines that open on
 * a tap are, underlined on hover like them. A press anywhere on the line
 * opens it (`activity-chatter.js`), so the target is the whole row. Before
 * the .self and .older rules, which colour the same element. */
.chatter .ln:has(> .ln-open) {
  cursor: pointer;
}

.chatter .ln .ln-open {
  justify-self: start;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 2px;
  min-height: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: inherit;
  text-align: left;
  transition: none;
}

.chatter .ln .ln-open:active {
  transform: none;
}

.chatter .ln:hover > .ln-open {
  text-decoration: underline;
  text-decoration-color: var(--border);
  text-underline-offset: 3px;
}

.chatter .ln .nm {
  font-weight: 600;
}

/* A writer's note, as chat shows it: bold, brackets kept. */
.chatter .ln .chatter-note {
  font-weight: 700;
}

.chatter .ln .st {
  grid-column: 2;
  font-size: 0.72rem;
  color: var(--muted);
}

/* Sprinto's own lines carry a square in the colour the bar of time gives
 * that phase, so a line and the bar read as one thing. */
.chatter .ln.bot .w::before {
  content: "";
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 2px;
  margin-right: 0.4rem;
  vertical-align: 0.05em;
  background: var(--muted);
}

.chatter .ln.ph-join .w::before {
  background: var(--amber);
}

.chatter .ln.ph-focus .w::before {
  background: var(--good);
}

.chatter .ln.ph-up .w::before {
  background: var(--accent);
}

/* A vote to cancel and a cancelled sprint keep the plain grey square: the bar
 * of time has no colour for them (its four are joining, focus, counts and
 * break), and red is kept for errors. */

/* After the square, the emoji the line's post opened with in the channel
 * (`status_emoji` in chatter.rs). One, small: the line only has to stand out
 * from the writers' lines, not as much as the post stands out in chat. As
 * tall as the text, sitting on it like an inline emoji, so the line is never
 * taller for it; the width and height are set before the picture loads, so
 * nothing moves when it does. */
.chatter .ln .ln-emo {
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-right: 0.35em;
  vertical-align: -0.125em;
}

/* An emoji with no picture, as text. Drawn from the attribute so it is not
 * part of the line's words, which the shut bar copies, the same as a
 * picture. The overflow puts the box's baseline at its bottom edge, where a
 * picture's is, so the two sit at the same height; without it the box sits
 * on the glyph's own baseline, about a quarter of the text's height lower.
 * The glyph is centred in the 1em box. */
.chatter .ln .ln-emo-text {
  overflow: hidden;
}

.chatter .ln .ln-emo-text::before {
  content: attr(data-emoji);
  display: block;
  font-size: 0.9em;
  /* 1em of the line's text, at the glyph's 0.9em. */
  line-height: 1.1111;
  text-align: center;
}

/* Only you: italic and muted, as Discord draws its own "Only you can see
 * this" messages. Somebody else's lines are plain, as in any chat. */
.chatter .ln.self .w,
.chatter .ln.self .st {
  font-style: italic;
  color: var(--muted);
}

/* Older lines, muted so the newest sprint stands out (owner, 2026-10-29).
 * A line is older when it comes before the first line of the newest sprint
 * in the list (the latest sprint_at; a chain's rounds share it), or when it
 * is over 12 hours old. The server works this out (`older_than` in
 * chatter.rs) on its own clock, so the class is the same on every 3-second
 * refresh and the morph never flips it. Muted, not hidden: the words stay in
 * --muted at full strength, which is at least 5.4:1 on --surface and
 * --surface-2 in Paper, Ink and the other night palettes, so a line you
 * wrote stays readable. Only the square and the emoji are faded. Before the
 * is-editing rule, so a line opened for editing goes back to --ink. */
.chatter .ln.older .w {
  color: var(--muted);
}

.chatter .ln.older .w::before,
.chatter .ln.older .ln-emo {
  opacity: 0.5;
}

/* A rule over the first line after the older ones, where the newest
 * sprint's lines start (`newer-first`, set by the server with `older`). The
 * grey of an older line is the grey of an "Only you" line, so for those the
 * rule is what says which side of the newest sprint a line is on. Drawn as
 * an inset shadow inside the line's own padding: a border would make the
 * line a pixel taller when it gains the class, and move the lines under
 * it. The day label (`[data-day]::before`) sits under the rule. */
.chatter .ln.newer-first {
  box-shadow: inset 0 1px 0 var(--border);
}

/* Your own line, which a tap opens as the message it would be. */
.chatter .ln[data-tap] .w {
  cursor: pointer;
}

.chatter .ln[data-tap]:hover .w {
  text-decoration: underline;
  text-decoration-color: var(--border);
  text-underline-offset: 3px;
}

/* Opened: upright, as the message reads in the channel, with the note in
 * bold brackets after it, as chat shows a note. */
.chatter .ln.is-editing {
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  margin: 0.15rem -0.5rem;
  padding: 0.35rem 0.5rem;
}

.chatter .ln.is-editing .w {
  font-style: normal;
  color: var(--ink);
  text-decoration: none;
}

/* The note being changed is in the box under the line, not twice. */
.chatter .ln.is-editing .st,
.chatter .ln.is-editing .chatter-note {
  display: none;
}

.ln-edit {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.75rem;
  margin-top: 0.3rem;
}

.ln-edit-note {
  display: flex;
  align-items: baseline;
  flex: 1 1 12rem;
  min-width: 0;
  font-weight: 700;
}

.ln-edit-note input {
  flex: 1;
  min-width: 0;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  padding: 0.15rem 0.2rem;
}

.ln-edit-note input::placeholder {
  font-weight: 400;
  color: var(--muted);
}

.ln-edit-note input:focus {
  outline: none;
  border-bottom-color: var(--link);
}

.ln-edit button {
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-size: 0.82rem;
  padding: 0.3rem 0.8rem;
  min-height: 1.75rem;
  cursor: pointer;
}

.ln-edit .ln-edit-cancel {
  background: none;
  color: var(--link);
  padding: 0.3rem 0.2rem;
}

.chatter .ln-empty {
  padding: 0.6rem 0;
  font-size: 0.86rem;
  color: var(--muted);
}

/* Shut, the empty line says the first sentence only. */
.chatter:not([data-open]) .ln-empty-more {
  display: none;
}

/* --- A scoreboard, opened from its line ------------------------------------
 *
 * The popup a "Scoreboard posted" line opens (`past_board.rs`): the same rows
 * the finished board draws (`activity-roster.css`), under the title and the
 * sprint's date and time. */

.past-board-sheet .sheet-under {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.past-board-sheet .roster-head {
  margin-top: 0;
}

.past-board-said,
.past-board-loading {
  margin: 0;
  font-size: 0.9rem;
}

/* Over a chain's rows: Sprinto saved only the last round posted, which
 * every round's line opens. Muted and small, as the line under the title is:
 * it explains the board, it is not part of it. */
.past-board-note {
  margin: 0 0 0.6rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.past-board-loading {
  color: var(--muted);
}

/* A request that got no answer says so in the line above; the loading line
 * goes. */
.past-board-said:not([hidden]) + .past-board-loading {
  display: none;
}

/* --- Clean up the results (`past_board/clean.rs`) ---------------------------
 *
 * A server admin's mode of the same popup. Each row that has a writer's own
 * words on it becomes a button over the whole row, outlined so the rows that
 * can be clicked stand apart from the ones that can't; the rows themselves
 * are laid out as the board lays them out. A hidden row shows its
 * placeholder ("Task", "Emoji pet") in italics, which is the state. */

.clean-bar {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin: 0 0 0.6rem;
}

.clean-help {
  flex: 1 1 auto;
  margin: 0;
  font-size: 0.85rem;
}

.clean-bar button {
  flex: 0 0 auto;
}

.results .sprinter:has(> .clean-row) {
  padding: 0;
}

.clean-row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  width: 100%;
  min-height: 2.75rem;
  margin: 0.1rem 0;
  padding: 0.35rem 0.5rem;
  border: 0;
  border-radius: var(--radius-sm);
  box-shadow: inset 0 0 0 1px var(--border);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.clean-row:hover {
  background: var(--surface-2);
}

.clean-quote {
  display: block;
  margin-top: 0.75rem;
}

.clean-quote .board-quote {
  display: block;
  margin: 0;
}

/* Struck through, not removed: the admin needs to see what they hid to
 * put it back. */
.board-quote.is-hidden {
  text-decoration: line-through;
}

.clean-note {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.clean-foot {
  margin: 0.9rem 0.5rem 0;
}

.past-board-note.is-bad {
  color: var(--bad);
}

/* Sprinto's own words in place of a writer's, italic as chat draws them. The
 * padding keeps the last letter's slant inside a name cut off with
 * `overflow: hidden`. */
.placeholder {
  font-style: italic;
  padding-right: 0.15em;
}

/* In Discord's minimised tile (activity-sizes.css): one plain line along the
 * foot, the newest thing that happened, with no grip and no rounded sheet
 * edge, so it reads as the tile's footer rather than a drawer half over the
 * clock. A tap still opens it. Last in the file, so it wins over the bar's
 * own rules above. */
@media (max-height: 260px), (max-width: 300px) {
  :root {
    --chatter-shut: 2rem;
  }

  body.has-chatter #activity-root {
    padding-bottom: var(--chatter-shut);
  }

  .chatter {
    width: 100%;
    border: 0;
    border-top: 1px solid var(--border);
    border-radius: 0;
    box-shadow: none;
  }

  .chatter-grip {
    display: none;
  }

  .chatter-peek {
    min-height: var(--chatter-shut);
    padding: 0 0.75rem;
    align-items: center;
    font-size: 0.82rem;
  }
}
