/* Christmas tree pickup -- Material Design 3, written out by hand.
 *
 * No component library: this app is server-rendered EJS with no build step,
 * and Material Web Components would mean rewriting every template around
 * custom elements and shipping a JS runtime to a signup form. So the system
 * is here as tokens instead -- colour roles, elevation, shape and type scale
 * -- applied to the plain HTML the templates already emit.
 *
 * Two deliberate departures from the spec, both because they would otherwise
 * cost markup or JavaScript for no gain to a volunteer filling in a form:
 * text fields keep their label above the box rather than floating it, and
 * buttons get a state layer and a press without a ripple.
 *
 * The palette is a hand-tuned tonal set around an evergreen source colour.
 */

:root {
  color-scheme: light dark;

  /* --- colour roles: light ------------------------------------------------ */
  --md-primary: #1b6b44;
  --md-on-primary: #ffffff;
  --md-primary-container: #a6f2c3;
  --md-on-primary-container: #00210f;

  --md-secondary: #4e6355;
  --md-on-secondary: #ffffff;
  --md-secondary-container: #d1e8d5;
  --md-on-secondary-container: #0c1f14;

  --md-tertiary: #3b6470;
  --md-on-tertiary: #ffffff;
  --md-tertiary-container: #beeaf8;
  --md-on-tertiary-container: #001f27;

  --md-error: #ba1a1a;
  --md-on-error: #ffffff;
  --md-error-container: #ffdad6;
  --md-on-error-container: #410002;

  /* Material has no warning role. This pair is an extension, toned to sit
     beside the others rather than shout over them. */
  --md-warning-container: #ffdfa0;
  --md-on-warning-container: #271900;

  /* Seasonal accents, outside Material's roles on purpose and used sparingly:
     the evergreen of the app bar, and berry and gold for the two stat tiles a
     treasurer and a follow-up caller look for. */
  --brand-deep: #0d3b25;
  --brand-mid: #17603c;
  /* Where the evergreen gradients end. Not --md-primary: in the dark scheme
     that is a light green, and white nav text on it all but vanished. */
  --brand-end: #1b6b44;
  --accent-berry: #b3261e;
  --accent-gold: #b7791f;
  --glow: rgba(27, 107, 68, 0.18);
  --card-shadow: 0 1px 2px rgba(13, 59, 37, 0.06), 0 6px 20px -8px rgba(13, 59, 37, 0.18);
  --card-shadow-hover: 0 2px 4px rgba(13, 59, 37, 0.08), 0 14px 32px -10px rgba(13, 59, 37, 0.28);

  --md-surface: #f7fbf4;
  --md-on-surface: #191c1a;
  --md-surface-variant: #dce5db;
  --md-on-surface-variant: #414942;

  --md-surface-container-lowest: #ffffff;
  --md-surface-container-low: #f1f5ee;
  --md-surface-container: #ebefe8;
  --md-surface-container-high: #e5eae2;
  --md-surface-container-highest: #dfe4dd;

  --md-outline: #717971;
  --md-outline-variant: #c0c9bf;
  --md-inverse-surface: #2e312e;
  --md-inverse-on-surface: #eff1ec;

  /* State layer opacities, straight from the spec. */
  --md-state-hover: 0.08;
  --md-state-focus: 0.10;
  --md-state-press: 0.10;

  /* --- elevation ---------------------------------------------------------- */
  --md-elevation-1: 0 1px 2px rgba(0, 0, 0, 0.30), 0 1px 3px 1px rgba(0, 0, 0, 0.15);
  --md-elevation-2: 0 1px 2px rgba(0, 0, 0, 0.30), 0 2px 6px 2px rgba(0, 0, 0, 0.15);
  --md-elevation-3: 0 1px 3px rgba(0, 0, 0, 0.30), 0 4px 8px 3px rgba(0, 0, 0, 0.15);

  /* --- shape scale -------------------------------------------------------- */
  --md-shape-xs: 4px;
  --md-shape-sm: 8px;
  --md-shape-md: 12px;
  --md-shape-lg: 16px;
  --md-shape-xl: 28px;
  --md-shape-full: 999px;

  /* --- motion ------------------------------------------------------------- */
  --md-ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --md-ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
  --md-duration-short: 150ms;
  --md-duration-medium: 250ms;
  /* The drive bar's card swap: longer than a plain fade-through, because it is
     the one animation read at arm's length with a tree in the other hand.
     Kept here beside the M3 durations, but its own token -- SLIDE_MS in
     driver-stops.js matches it. */
  --drive-slide: 400ms;
}

/* --- colour roles: dark --------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root {
    --md-primary: #8bd5a7;
    --md-on-primary: #00391f;
    --md-primary-container: #00522f;
    --md-on-primary-container: #a6f2c3;

    --md-secondary: #b5ccb9;
    --md-on-secondary: #223527;
    --md-secondary-container: #384b3c;
    --md-on-secondary-container: #d1e8d5;

    --md-tertiary: #a3cddb;
    --md-on-tertiary: #043541;
    --md-tertiary-container: #224c58;
    --md-on-tertiary-container: #beeaf8;

    --md-error: #ffb4ab;
    --md-on-error: #690005;
    --md-error-container: #93000a;
    --md-on-error-container: #ffdad6;

    --md-warning-container: #5c4200;
    --md-on-warning-container: #ffdfa0;

    --brand-deep: #06170e;
    --brand-mid: #0f3a25;
    --brand-end: #1a4d33;
    --accent-berry: #ffb4ab;
    --accent-gold: #f0c069;
    --glow: rgba(139, 213, 167, 0.22);
    --card-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px -10px rgba(0, 0, 0, 0.6);
    --card-shadow-hover: 0 2px 4px rgba(0, 0, 0, 0.45), 0 16px 36px -12px rgba(0, 0, 0, 0.7);

    --md-surface: #101410;
    --md-on-surface: #dfe4dd;
    --md-surface-variant: #414942;
    --md-on-surface-variant: #c0c9bf;

    --md-surface-container-lowest: #0b0f0b;
    --md-surface-container-low: #191c19;
    --md-surface-container: #1d201d;
    --md-surface-container-high: #272b27;
    --md-surface-container-highest: #323632;

    --md-outline: #8b938a;
    --md-outline-variant: #414942;
    --md-inverse-surface: #dfe4dd;
    --md-inverse-on-surface: #2e312e;
  }
}

/* --- baseline and type scale ---------------------------------------------- */
/* The phone's own typeface -- Roboto on Android, San Francisco on an iPhone.
   There used to be a Google Fonts webfont here, and every page waited on two
   requests to Google before its text could settle; on the phones this site is
   read on it was mostly fetching a font they already had. Named first so a
   machine that has Roboto installed still uses it. Sizes, weights and
   tracking below are the M3 scale. */

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  /* body-large */
  font-size: 1rem; line-height: 1.5; letter-spacing: 0.03125rem;
  color: var(--md-on-surface);
  background: var(--md-surface);
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--md-primary-container); color: var(--md-on-primary-container); }

a { color: var(--md-primary); text-underline-offset: 0.15em; }

/* Material's focus indicator: a 3px ring in primary, clear of the shape. */
:focus-visible {
  outline: 3px solid var(--md-primary);
  outline-offset: 2px;
}

/* headline-small / title-large */
h1 {
  font-size: 1.5rem; line-height: 2rem; font-weight: 400; letter-spacing: 0;
  margin: 0 0 0.5rem;
}
h2 {
  font-size: 1.375rem; line-height: 1.75rem; font-weight: 400; letter-spacing: 0;
  margin: 2rem 0 0.75rem;
}
/* body-medium, for supporting text */
.sub, .hint, .muted, .stat .l {
  font-size: 0.875rem; line-height: 1.25rem; letter-spacing: 0.015625rem;
  color: var(--md-on-surface-variant);
}
.sub { margin: 0 0 1.5rem; }
.hint { font-weight: 400; }
.right { text-align: right; }
.nowrap { white-space: nowrap; }
.inline { display: inline; }

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

.wrap {
  max-width: 62rem; margin: 0 auto; padding: 1.5rem 1rem 5rem;
  animation: md-fade-through var(--md-duration-medium) var(--md-ease-emphasized) both;
}
.wrap-narrow { max-width: 35rem; }

/* Top app bar (small): 64px, primary container, title-large. */
header.site {
  background: var(--md-primary-container);
  color: var(--md-on-primary-container);
  padding: 0 1rem;
  min-height: 64px;
  display: flex; align-items: center;
}
header.site .inner {
  max-width: 62rem; width: 100%; margin: 0 auto;
  display: flex; flex-wrap: wrap; gap: 0.25rem 0.5rem; align-items: center;
  padding: 0.5rem 0;
}
header.site .brand {
  font-size: 1.375rem; line-height: 1.75rem; font-weight: 400;
  margin-right: auto; padding: 0.25rem 0.25rem 0.25rem 0;
  color: inherit; text-decoration: none;
}
/* Signed out, the brand is the only thing in the bar: there is no nav, and the
   `margin-right: auto` above exists to push one rightwards. Left as it is, the
   title sits against the edge of a 62rem bar while the page underneath is a
   35rem column down the middle, which reads as a header that slipped. Centre
   it instead, and even up the padding so it is centred on the text. */
header.site .brand:only-child {
  margin: 0 auto;
  padding-left: 0.25rem;
}
/* Nav items are text buttons: pill state layer, label-large. */
header.site a:not(.brand) {
  position: relative; color: inherit; text-decoration: none;
  font-size: 0.875rem; line-height: 1.25rem; font-weight: 500;
  letter-spacing: 0.00625rem;
  padding: 0.5rem 0.75rem; border-radius: var(--md-shape-full);
  transition: background-color var(--md-duration-short) var(--md-ease-standard);
}
header.site a:not(.brand):hover {
  background: color-mix(in srgb, currentColor var(--md-state-hover), transparent);
}
header.site a.on {
  background: color-mix(in srgb, currentColor var(--md-state-focus), transparent);
  font-weight: 700;
}

/* --- cards ----------------------------------------------------------------- */
/* Filled card: surface-container-low, no outline, no shadow at rest. */

.card {
  background: var(--md-surface-container-low);
  border: none;
  border-radius: var(--md-shape-md);
  padding: 1.25rem 1.25rem 1.5rem;
  margin-bottom: 1rem;
}

/* --- text fields ----------------------------------------------------------- */
/* Outlined text field. The label sits above the box rather than floating into
   the outline, which is the one place this departs from the spec: floating
   labels need either JS or an :placeholder-shown trick that fights autofill. */

label {
  display: block;
  font-size: 0.875rem; line-height: 1.25rem; font-weight: 500;
  letter-spacing: 0.00625rem;
  color: var(--md-on-surface-variant);
  margin: 1.25rem 0 0.375rem;
}
/* Tick boxes and radios.
   The rule below is for boxes you type in, so these were left to the browser's
   own 13px -- a target a third the size of everything else on the page, and on
   a phone genuinely hard to hit. Everything else here is at least 32px, and the
   label beside one is part of the target, but the box itself should not be the
   hard way to do it. */
input[type=checkbox], input[type=radio] {
  width: 1.375rem; height: 1.375rem;
  margin: 0 0.5rem 0 0;
  vertical-align: -0.25rem;
  accent-color: var(--md-primary);
  flex: none;
}
/* Labels wrapping one are the real target, so give them the height too. */
label:has(> input[type=checkbox]), label:has(> input[type=radio]) {
  display: flex; align-items: center;
  min-height: 44px;
}

input[type=text], input[type=tel], input[type=email], input[type=number],
input[type=password], input[type=date], input[type=search], textarea, select {
  width: 100%;
  min-height: 56px;
  padding: 0.9rem 1rem;
  font: inherit; color: var(--md-on-surface);
  background: var(--md-surface-container-lowest);
  border: 1px solid var(--md-outline);
  border-radius: var(--md-shape-xs);
  transition: border-color var(--md-duration-short) var(--md-ease-standard),
              box-shadow var(--md-duration-short) var(--md-ease-standard);
}
textarea { min-height: 7rem; resize: vertical; line-height: 1.5; }
input:hover:not([readonly]):not(:focus), textarea:hover:not(:focus), select:hover:not(:focus) {
  border-color: var(--md-on-surface);
}
/* Focused: the outline thickens to 2px in primary, drawn as an inset ring so
   the box does not shift by a pixel. */
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--md-primary);
  box-shadow: inset 0 0 0 1px var(--md-primary);
}
input[readonly] {
  background: var(--md-surface-container);
  color: var(--md-on-surface-variant);
  border-style: dashed; cursor: default;
}
.row { display: flex; gap: 1rem; flex-wrap: wrap; }
.row > * { flex: 1 1 8rem; }
/* Short fixed values that belong on one line even on a phone: town, state,
   ZIP. At 8rem each the ZIP dropped to a row of its own and read as a
   separate question. */
.row-fixed > * { flex: 1 1 4.5rem; }
.row-fixed > :first-child { flex-grow: 2; }

/* The route map. Tall enough to be worth opening, short enough that the stop
   list is still reachable with a thumb. */
.route-map {
  width: 100%; height: 22rem;
  background: var(--md-surface-container-high);
  border-radius: var(--md-shape-sm);
}
.card .route-map { border-radius: 0; }
@media (max-width: 30rem) { .route-map { height: 16rem; } }

/* A line of buttons that wraps instead of stretching, unlike .row. */
.actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* Apartment or unit. Loud on purpose: on a dark morning it is the difference
   between the right door and a knock at somebody else's. */
.unit, .paid {
  display: inline-block; vertical-align: 0.05em;
  border-radius: var(--md-shape-sm);
  padding: 0.05rem 0.45rem; margin-left: 0.35rem;
  font-size: 0.8125rem; font-weight: 500; white-space: nowrap;
}
.unit { background: var(--md-tertiary-container); color: var(--md-on-tertiary-container); }

/* Money in hand. A different role from the unit chip and from the collected
   card behind it, so all three can be true at once and still be told apart. */
.paid { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }

/* --- buttons --------------------------------------------------------------- */
/* Filled button: 40px tall, fully rounded, label-large. State layers are drawn
   with color-mix over the container colour rather than a ripple -- a ripple
   needs JS on every page, and this form does not otherwise need any. */

button, .btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 40px; padding: 0 1.5rem;
  font-family: inherit;
  font-size: 0.875rem; line-height: 1.25rem; font-weight: 500;
  letter-spacing: 0.00625rem;
  color: var(--md-on-primary);
  background: var(--md-primary);
  border: none; border-radius: var(--md-shape-full);
  text-decoration: none; text-align: center; cursor: pointer;
  transition: background-color var(--md-duration-short) var(--md-ease-standard),
              box-shadow var(--md-duration-short) var(--md-ease-standard),
              color var(--md-duration-short) var(--md-ease-standard),
              border-color var(--md-duration-short) var(--md-ease-standard);
}
button:hover, .btn:hover {
  color: var(--md-on-primary);
  background: color-mix(in srgb, var(--md-on-primary) 8%, var(--md-primary));
  box-shadow: var(--md-elevation-1);
}
button:active, .btn:active {
  background: color-mix(in srgb, var(--md-on-primary) 10%, var(--md-primary));
  box-shadow: none;
}
/* The plain declarations come first as the fallback: color-mix is only in
   browsers from 2023 on, and a disabled button that still looks enabled is
   worse than one tinted slightly wrong. */
button[disabled], button[disabled]:hover {
  background: var(--md-surface-container-highest);
  color: var(--md-on-surface-variant);
  background: color-mix(in srgb, var(--md-on-surface) 12%, transparent);
  color: color-mix(in srgb, var(--md-on-surface) 38%, transparent);
  box-shadow: none; cursor: not-allowed;
}

/* Outlined button. */
.btn-plain {
  background: transparent; color: var(--md-primary);
  border: 1px solid var(--md-outline);
}
.btn-plain:hover {
  color: var(--md-primary);
  background: var(--md-surface-container-high);
  background: color-mix(in srgb, var(--md-primary) 8%, transparent);
  box-shadow: none;
}
.btn-plain:active { background: color-mix(in srgb, var(--md-primary) 10%, transparent); }

/* Outlined button in the error role. */
.btn-danger { background: transparent; color: var(--md-error); border: 1px solid var(--md-outline); }
.btn-danger:hover {
  color: var(--md-error);
  background: color-mix(in srgb, var(--md-error) 8%, transparent);
  border-color: var(--md-error); box-shadow: none;
}
.btn-danger:active { background: color-mix(in srgb, var(--md-error) 10%, transparent); }

/* Small button: still a pill, still a 32px target minimum. */
/* One line, always: "Mark paid" broken over two lines in a table cell looks
   like two buttons, and doubles the height of every row it sits in. */
.btn-sm { min-height: 32px; padding: 0 0.875rem; font-size: 0.8125rem; white-space: nowrap; }

/* --- banners and chips ------------------------------------------------------ */
/* Material has no "alert" component; these are banners built from the tonal
   container roles, so each severity carries its own guaranteed contrast pair. */

.notice {
  border: none; border-radius: var(--md-shape-md);
  padding: 1rem 1.25rem; margin-bottom: 1rem;
  background: var(--md-surface-container-high);
  color: var(--md-on-surface);
  font-size: 0.875rem; line-height: 1.25rem; letter-spacing: 0.015625rem;
  animation: md-fade-through var(--md-duration-medium) var(--md-ease-emphasized) both;
}
.notice-error { background: var(--md-error-container); color: var(--md-on-error-container); }
.notice-warn { background: var(--md-warning-container); color: var(--md-on-warning-container); }
.notice-ok { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.notice a { color: inherit; }

/* Assist chip: 32px, 8px corner, label-large. */
.tag {
  display: inline-flex; align-items: center;
  min-height: 24px; padding: 0 0.5rem;
  font-size: 0.6875rem; line-height: 1rem; font-weight: 500; letter-spacing: 0.03125rem;
  border-radius: var(--md-shape-sm);
  border: 1px solid var(--md-outline-variant);
  background: transparent; color: var(--md-on-surface-variant);
  white-space: nowrap;
}
.tag-ok { background: var(--md-primary-container); color: var(--md-on-primary-container); border-color: transparent; }
.tag-warn { background: var(--md-warning-container); color: var(--md-on-warning-container); border-color: transparent; }
.tag-bad { background: var(--md-error-container); color: var(--md-on-error-container); border-color: transparent; }

/* --- data tables ------------------------------------------------------------ */

table { width: 100%; border-collapse: collapse; }
th, td {
  text-align: left; vertical-align: top;
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--md-outline-variant);
  font-size: 0.875rem; line-height: 1.25rem; letter-spacing: 0.015625rem;
}
/* title-small for column headers */
th {
  font-weight: 500; letter-spacing: 0.00625rem;
  color: var(--md-on-surface-variant);
}
tr:last-child td { border-bottom: none; }
tbody tr, table tr { transition: background-color var(--md-duration-short) var(--md-ease-standard); }
.card table tr:hover td {
  background: color-mix(in srgb, var(--md-on-surface) 4%, transparent);
}
.scroll { overflow-x: auto; border-radius: var(--md-shape-sm); }
/* The cell a row is about gets room to be read. Left to the table, an address
   in a nine-column plan came out as "125 / Meadowbrook / Road" down three
   lines while a notes column beside it did the same. */
@media (min-width: 48.0625rem) {
  table.stack td.stack-title { min-width: 11rem; }
  /* A crew's note is a sentence; one word to a line is not reading it. */
  table.stack td.notes { min-width: 9rem; }
  /* A little less air either side of a cell, so a nine-column plan fits the
     card rather than scrolling its notes out of sight. */
  .card th, .card td { padding-left: 0.625rem; padding-right: 0.625rem; }
  .card th:first-child, .card td:first-child { padding-left: 1rem; }
}

/* Jumping to a heading lands it at the very top edge of the window, where it
   reads as the page having scrolled past it. A little room above puts it where
   a person would have stopped scrolling. */
:target { scroll-margin-top: 1rem; }
h2[id] { scroll-margin-top: 1rem; }

/* The way in to each truck, kept where it can be reached.
   Once the page has scrolled down into the rounds themselves it pins to the
   top, because that is exactly when it is wanted: you are deep in one crew's
   stop list and the next thing you want is a different crew.
   It pins UNDER the site header, which is sticky too and sits above it: at
   top 0 the bar slid in behind the header and was never seen. The header's
   height changes as its nav wraps, so truck-jump.js measures it into
   --header-h; 4rem, the header's height on one row, stands in until then.

   ONE ROW, SCROLLED SIDEWAYS, rather than the wrapping row it was. Six trucks
   with real names wrap to three lines, and three lines pinned to the top of a
   phone is a third of the screen spent on navigation while you are trying to
   read a round. One line is always one line, whatever the fleet. */
.truck-jump {
  position: sticky; top: var(--header-h, 4rem); z-index: 5;
  display: flex; gap: 0.5rem; align-items: center;
  /* Out to the edges of the page's padding, so nothing scrolls up the side of
     it and shows through. */
  margin: 0 -1rem 1rem;
  padding: 0.5rem 1rem;
  background: var(--md-surface);
  border-bottom: 1px solid var(--md-outline-variant);
  overflow-x: auto;
  scrollbar-width: none;
}
.truck-jump::-webkit-scrollbar { display: none; }
/* Buttons keep their size; the row scrolls instead of squeezing them. */
.truck-jump .btn { flex: none; }

/* Room for the bar above the heading it just jumped to, or the name of the
   truck lands underneath it. The header above the bar, then one row of small
   buttons, its padding, and a little air. */
h2.route-head { scroll-margin-top: calc(var(--header-h, 4rem) + 3.75rem); }

/* Finding one household in a long list. */
.find { margin: 0 0 1rem; }
.find input { margin: 0; }
.find .hint { display: block; margin-top: 0.375rem; min-height: 1.25rem; }

/* The tally on a filter button: part of the button, quieter than its name. */
.btn .count {
  margin-left: 0.375rem;
  padding: 0 0.4rem;
  border-radius: var(--md-shape-full);
  background: color-mix(in srgb, currentColor 14%, transparent);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
}

/* There for a screen reader and nothing else: a search box needs a name, and
   the placeholder is not one -- it goes away as soon as anybody types. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* What just happened, when the page did not reload to show it.
   A reload was its own receipt -- the page came back different and you knew the
   thing had landed. Without one, a removal is a row vanishing and a mark is a
   menu that already said that, so the page has to say so itself. */
.live-flash {
  position: fixed; z-index: 30;
  left: 50%; bottom: 1.5rem; transform: translate(-50%, 1rem);
  max-width: calc(100vw - 2rem);
  padding: 0.875rem 1.25rem;
  border-radius: var(--md-shape-sm);
  background: var(--md-inverse-surface); color: var(--md-inverse-on-surface);
  box-shadow: var(--md-elevation-3);
  font-size: 0.875rem;
  opacity: 0; pointer-events: none;
  transition: opacity var(--md-duration-short) var(--md-ease-standard),
              transform var(--md-duration-short) var(--md-ease-standard);
}
.live-flash.on { opacity: 1; transform: translate(-50%, 0); }

/* A form waiting on the server. Quiet on purpose: these come back in
   milliseconds, and a spinner that flashes is worse than nothing. */
form.sending { opacity: 0.55; transition: opacity var(--md-duration-short) var(--md-ease-standard); }

/* A row of buttons: filters, or the actions under a page.
   Flex rather than inline-flex boxes separated by whitespace, so the gaps are
   even and a wrapped line starts where the one above it did.

   The button pushed to the right used to be `float:right`, and a float turned
   out to be a trap right above `.card.scroll`: overflow-x makes that card a
   block formatting context, and a block formatting context is required not to
   overlap a float -- so it shrank by the width of the button and clipped its
   own contents. Only between about 412px and 580px, where the float wraps to
   its own line but still has the card beside it, which is why two tidy test
   widths either side of the band said everything was fine. Flex does nothing
   to what comes after it. */
.toolbar { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.toolbar .push { margin-left: auto; }
@media (max-width: 48rem) {
  /* On its own wrapped line, `margin-left: auto` strands it against the right
     edge, away from everything it belongs with. Let it flow with the rest. */
  .toolbar .push { margin-left: 0; }
}

/* A disclosure is a control, and at the browser's default it is a 20px line of
   text. Everything else that takes a tap on this site is at least 32px. */
summary {
  display: list-item;
  min-height: 32px;
  padding: 0.375rem 0;
  cursor: pointer;
}

/* --- tables on a phone ------------------------------------------------------
   A table is a grid of small facts and a phone is not wide enough for one.
   `.scroll` kept the PAGE from overflowing by putting each table in a sideways
   scroller, which is honest and was still useless: on the signups list you saw
   Address, Who and Trees, and every control -- located, tree, money, remove --
   sat off the right-hand edge, reachable only by swiping inside the card. The
   page measured clean and did not work.

   So below this width a row stops being a row and becomes a card. The cell the
   row is ABOUT is its heading, and every other cell carries its column name
   with it, from data-label. No JavaScript, and nothing changes above the
   breakpoint -- the same markup is still a table on a laptop. */
@media (max-width: 48rem) {
  table.stack, table.stack tbody, table.stack td { display: block; }
  table.stack thead, table.stack tr > th { display: none; }
  /* Hiding the cells is not hiding the row: a header row written without a
     thead stayed a flex box with padding and a rule under it, so every card
     opened with an empty band and a stray divider. */
  table.stack tr:not(:has(td)) { display: none; }
  /* `display: flex` above outranks the browser's own rule for [hidden], so a
     row filtered out of the list would stay on the page. */
  table.stack tr[hidden] { display: none; }
  /* A column rather than a block, so the heading cell can be pulled to the top
     of the card whatever order the table puts it in. */
  table.stack tr {
    display: flex; flex-direction: column;
    padding: 0.875rem 0;
    border-bottom: 1px solid var(--md-outline-variant);
  }
  table.stack tr:last-child { border-bottom: none; }
  table.stack td { border: none; padding: 0.1875rem 0; }
  /* Neither belongs in a stack: a column kept on one line is a column decision,
     and a value pushed right has no column to be pushed away from. */
  table.stack td.nowrap { white-space: normal; }
  table.stack td.right { text-align: left; }

  /* What the row is about: the address, the username, the plan. */
  table.stack td.stack-title {
    order: -1;
    font-size: 1rem; line-height: 1.5rem;
    padding-bottom: 0.375rem;
  }
  /* Everything else: its column name, then its value. */
  table.stack td[data-label] {
    display: grid;
    grid-template-columns: minmax(0, 7.5rem) minmax(0, 1fr);
    /* Columns only. A row gap split every two-line value in half -- a name,
       then a blank line, then its phone number -- which read as two facts. */
    gap: 0 0.75rem; align-items: baseline;
    /* Without this a chip becomes a grid item stretched to the whole column,
       so "located" arrives as a bar the width of the card rather than as a
       pill round a word. It still wraps when the value is genuinely long --
       start-aligned only bounds it by its own content. */
    justify-items: start;
  }
  /* A cell is not one value: "Who" is a name and a phone number, "Truck" is a
     name and the stop's number on it. Every child after the label belongs in
     the value column, or the second one lands back under the label. */
  table.stack td[data-label] > * { grid-column: 2; }
  table.stack td[data-label]::before {
    content: attr(data-label);
    color: var(--md-on-surface-variant);
    font-weight: 500;
  }
  /* A cell holding nothing but controls wants the width more than a name. */
  table.stack td.act { padding-top: 0.5rem; }
  /* An empty cell in a table is a gap in a grid; in a stack it is a blank line. */
  table.stack td:empty { display: none; }
}

/* A pair of status menus inside a table row.
   The form-field rule above is built for a column of labelled inputs standing
   on their own -- full width, 56px tall -- and dropped into a cell that is a
   block big enough to push the row apart. Here they are two small settings and
   a button, read across rather than down. */
.mark-set { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.mark-set select {
  width: auto;
  min-height: 32px;
  padding: 0.25rem 0.5rem;
  font-size: 0.8125rem;
}


/* --- stat tiles -------------------------------------------------------------- */

.stats { display: flex; flex-wrap: wrap; gap: 1rem; }
.stat {
  flex: 1 1 8rem;
  background: var(--md-surface-container);
  border: none; border-radius: var(--md-shape-lg);
  padding: 1rem 1.25rem;
  animation: md-fade-through var(--md-duration-medium) var(--md-ease-emphasized) both;
}
.stat:nth-child(2) { animation-delay: 50ms; }
.stat:nth-child(3) { animation-delay: 100ms; }
.stat:nth-child(4) { animation-delay: 150ms; }
.stat:nth-child(5) { animation-delay: 200ms; }
/* headline-medium, tabular so the row does not jitter between values. */
a.stat { text-decoration: none; color: inherit; display: block; }
a.stat:hover { background: var(--md-surface-container-high); }
.stat .n {
  font-size: 1.75rem; line-height: 2.25rem; font-weight: 400; letter-spacing: 0;
  color: var(--md-primary); font-variant-numeric: tabular-nums;
}
.stat .l { margin-top: 0.25rem; }

/* --- code -------------------------------------------------------------------- */

code, pre {
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8125rem;
}
code {
  background: var(--md-surface-container-high); color: var(--md-on-surface);
  padding: 0.125rem 0.375rem; border-radius: var(--md-shape-xs);
  /* These hold filesystem paths and callback URLs -- one long run of
     characters with nowhere a browser would choose to break. Left alone, the
     OAuth redirect URI on the maintenance page took the whole layout 200px
     sideways on a phone. Anywhere is the right answer because there is no
     better place; `pre` scrolls instead, below, since in a block of commands
     where a line breaks is part of what it says. */
  overflow-wrap: anywhere;
}
pre {
  background: var(--md-surface-container-high);
  border-radius: var(--md-shape-sm);
  padding: 0.875rem 1rem; overflow-x: auto; margin: 0.5rem 0;
  line-height: 1.5;
}
pre code { background: none; padding: 0; overflow-wrap: normal; }

/* --- address autocomplete ---------------------------------------------------- */
/* The suggestion list is a Material menu: surface-container elevation 2, 4px
   corner, 48px rows. It is ours rather than Google's so it can be filtered to
   one town before anyone sees it. */

.combo { position: relative; }
.suggestions {
  position: absolute; z-index: 20; left: 0; right: 0; top: calc(100% + 0.25rem);
  margin: 0; padding: 0.5rem 0; list-style: none;
  background: var(--md-surface-container);
  border-radius: var(--md-shape-xs);
  box-shadow: var(--md-elevation-2);
  max-height: 17rem; overflow-y: auto;
  animation: md-menu-in var(--md-duration-short) var(--md-ease-emphasized) both;
  transform-origin: top center;
}
.suggestions li {
  display: flex; align-items: center; min-height: 48px;
  padding: 0.5rem 0.75rem; cursor: pointer;
  font-size: 0.875rem; line-height: 1.25rem; letter-spacing: 0.015625rem;
  color: var(--md-on-surface);
  transition: background-color var(--md-duration-short) var(--md-ease-standard);
}
.suggestions li.on, .suggestions li:hover {
  background: var(--md-surface-container-high);
  background: color-mix(in srgb, var(--md-on-surface) 8%, transparent);
}
.suggestions li.none, .suggestions li.none:hover {
  color: var(--md-on-surface-variant); cursor: default; background: transparent;
}

/* The address once picked, as a filled tonal container. The [hidden] rule has
   to come after, because a display value set here would otherwise beat the
   browser's own hiding of [hidden]. */
.picked-address {
  display: flex; flex-wrap: wrap; gap: 0.75rem;
  align-items: center; justify-content: space-between;
  background: var(--md-secondary-container); color: var(--md-on-secondary-container);
  border: none; border-radius: var(--md-shape-md);
  padding: 0.875rem 1rem; margin: 1.25rem 0 0.25rem;
  animation: md-fade-through var(--md-duration-medium) var(--md-ease-emphasized) both;
}
.picked-address span { font-weight: 500; }
[hidden] { display: none !important; }

/* --- plan sheets -------------------------------------------------------------- */

tr.dumprow td {
  background: var(--md-surface-container-high);
  color: var(--md-on-surface-variant);
  font-size: 0.8125rem; line-height: 1.25rem;
}
.card table tr.dumprow:hover td { background: var(--md-surface-container-high); }
.dumpband {
  background: var(--md-tertiary-container); color: var(--md-on-tertiary-container);
  border: none; border-radius: var(--md-shape-md);
  padding: 0.875rem 1rem; margin: 0 0 0.75rem;
  font-size: 0.875rem; line-height: 1.25rem;
}
.dumpband .btn { display: flex; margin-top: 0.75rem; }

/* --- watching the round ------------------------------------------------------- */
/* The organiser's live strip. Quiet by design: it is read at a glance every few
   minutes across a whole morning, not stared at. */

.live-strip {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  margin: 1.25rem 0 0.5rem; padding: 0.625rem 0.875rem;
  background: var(--md-surface-container); border-radius: var(--md-shape-md);
  font-size: 0.875rem; line-height: 1.25rem;
}
.live-strip .live-counts { font-variant-numeric: tabular-nums; }
.live-strip .hint { flex: 1 1 8rem; margin: 0; font-size: 0.8125rem; }
.live-strip button { margin-left: auto; }
.live-dot {
  width: 0.5rem; height: 0.5rem; border-radius: var(--md-shape-full);
  background: var(--md-primary);
  animation: live-pulse 2s var(--md-ease-standard) infinite;
}
@keyframes live-pulse { 50% { opacity: 0.25; } }

/* A house nobody has reached yet stays plain; the rest are tinted so a long
   table can be skimmed for what is left rather than read. */
tr[data-status="picked_up"] > td { background: color-mix(in srgb, var(--md-primary) 7%, transparent); }
tr[data-status="no_tree"] > td, tr[data-status="skipped"] > td {
  background: color-mix(in srgb, var(--md-on-surface) 6%, transparent);
}

/* --- driver sheet -------------------------------------------------------------- */
/* Big targets, one hand, cold fingers. Material's 48px minimum is the floor
   here, not the target. The page answers two questions per stop -- is the tree
   on the truck, did the money come -- so those two live as a pair of large
   toggles and everything else is deliberately smaller. */

body.driver { background: var(--md-surface); }
/* Header and map ride together at the top, so opening the map does not shove
   the round off the screen and reading it costs no scrolling back. */
.driver-top {
  position: sticky; top: 0; z-index: 5;
  box-shadow: var(--md-elevation-2);
}
.driver-head {
  background: var(--md-primary-container); color: var(--md-on-primary-container);
  padding: 0.75rem 1rem; min-height: 64px;
  display: flex; flex-direction: column; justify-content: center;
}
.driver-head h1 {
  font-size: 1.375rem; line-height: 1.75rem; font-weight: 400; margin: 0;
}
.driver-head .meta {
  font-size: 0.875rem; line-height: 1.25rem; letter-spacing: 0.015625rem;
  font-variant-numeric: tabular-nums;
}
/* Marks that have not reached the server yet. Sits under the tally because it
   qualifies it: this is what the number does not know about. */
.queue-note {
  margin-top: 0.125rem;
  font-size: 0.8125rem; line-height: 1.25rem; font-weight: 500;
  color: var(--md-on-primary-container);
  opacity: 0.85;
}

.driver-wrap {
  max-width: 40rem; margin: 0 auto;
  /* The bottom pad clears the fixed bar, which is about 8rem with the safe
     area on a phone with a home indicator. */
  padding: 1rem 0.75rem calc(9rem + env(safe-area-inset-bottom, 0px));
  animation: md-fade-through var(--md-duration-medium) var(--md-ease-emphasized) both;
}

/* Navigation, map and drop-off notes, folded away. Read once at the start of
   the round; the stop list is what the day is actually spent in. */
.driver-tools {
  background: var(--md-surface-container-low);
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--md-shape-md);
  margin-bottom: 1rem;
}
.driver-tools > summary {
  min-height: 48px; padding: 0.75rem 1rem;
  display: flex; align-items: center;
  font-size: 0.875rem; font-weight: 500; color: var(--md-primary);
  cursor: pointer; list-style: none;
}
.driver-tools > summary::-webkit-details-marker { display: none; }
.driver-tools > summary::after {
  content: ""; margin-left: auto;
  width: 0.5rem; height: 0.5rem;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-0.1rem, -0.1rem);
  transition: transform var(--md-duration-short) var(--md-ease-standard);
}
.driver-tools[open] > summary::after { transform: rotate(-135deg) translate(-0.15rem, -0.15rem); }
.driver-tools > *:not(summary) { margin: 0 1rem 0.75rem; }
.driver-tools .hint, .driver-tools p { font-size: 0.8125rem; line-height: 1.25rem; }

/* Each stop is an outlined card. Collected stops recede; the ones still to do
   keep the contrast, because those are the ones being looked for. */
.stop {
  position: relative;
  background: var(--md-surface-container-low);
  border: 1px solid var(--md-outline-variant);
  border-left: 4px solid var(--md-outline-variant);
  border-radius: var(--md-shape-md);
  padding: 0.875rem; margin-bottom: 0.75rem;
  transition: background-color var(--md-duration-medium) var(--md-ease-standard),
              border-color var(--md-duration-medium) var(--md-ease-standard),
              opacity var(--md-duration-medium) var(--md-ease-standard);
}
.stop.done {
  background: var(--md-surface-container);
  border-left-color: var(--md-primary);
  opacity: 0.72;
}
.stop.skipped { border-left-color: var(--md-outline); opacity: 0.72; }
/* A tap that has not reached the server yet. Quiet -- the state has already
   been painted, this only says it is not finished travelling. */
.stop.saving { opacity: 0.55; }

.stop-head { display: flex; align-items: flex-start; gap: 0.625rem; }
/* Tap a house to say that is where you are going next. Upgraded by the script,
   so without it this is plain text rather than a button that does nothing. */
.stop-head.pickable {
  cursor: pointer;
  margin: -0.375rem; padding: 0.375rem;
  border-radius: var(--md-shape-sm);
  transition: background-color var(--md-duration-short) var(--md-ease-standard);
}
.stop-head.pickable:hover { background: color-mix(in srgb, var(--md-primary) 8%, transparent); }
.stop-head.pickable:active { background: color-mix(in srgb, var(--md-primary) 12%, transparent); }

/* The house the bar is pointing at. Without this the bar names a street and
   the crew has to go hunting for it in the list. */
.next-chip {
  display: none;
  border-radius: var(--md-shape-sm);
  padding: 0.05rem 0.45rem; margin-left: 0.35rem;
  font-size: 0.75rem; font-weight: 700; white-space: nowrap;
  vertical-align: 0.08em;
  background: var(--md-primary); color: var(--md-on-primary);
}
.stop.is-next .next-chip { display: inline-block; }
.stop.is-next {
  border-color: var(--md-primary); border-left-color: var(--md-primary);
  opacity: 1;
}
.stop.is-next .n { background: var(--md-primary); color: var(--md-on-primary); }
.stop-where { min-width: 0; flex: 1; }
.stop .n {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  min-width: 2.25rem; height: 2.25rem;
  border-radius: var(--md-shape-full);
  background: var(--md-primary); color: var(--md-on-primary);
  font-size: 0.875rem; font-weight: 500;
  font-variant-numeric: tabular-nums;
  transition: background-color var(--md-duration-medium) var(--md-ease-standard);
}
.stop.done .n, .stop.skipped .n {
  background: var(--md-surface-container-highest); color: var(--md-on-surface-variant);
}
/* The end of the round, drawn like a stop and deliberately not one: nothing to
   answer, nothing to post. It wears the drop-off's colour, the same one the
   "probably full about here" bands use, so the two read as the same place. */
.stop.finish { border-left-color: var(--md-tertiary); }
.stop.finish .n,
.stop.finish.is-next .n {
  background: var(--md-tertiary); color: var(--md-on-tertiary);
}
.stop.finish .n .mark-icon { width: 1.25rem; height: 1.25rem; opacity: 1; }
.stop.finish.is-next {
  border-color: var(--md-tertiary); border-left-color: var(--md-tertiary);
}
.stop.finish .next-chip {
  background: var(--md-tertiary); color: var(--md-on-tertiary);
}
.stop.finish.done .n { background: var(--md-surface-container-highest); color: var(--md-on-surface-variant); }

.stop .addr { font-size: 1.0625rem; line-height: 1.5rem; font-weight: 500; }
.stop .who {
  font-size: 0.8125rem; line-height: 1.25rem; letter-spacing: 0.015625rem;
  color: var(--md-on-surface-variant);
}
/* The crew's own note, folded away because most houses need none. */
.note-editor > summary {
  min-height: 40px; display: flex; align-items: center; gap: 0.375rem;
  margin-top: 0.5rem;
  font-size: 0.8125rem; font-weight: 500; color: var(--md-primary);
  cursor: pointer; list-style: none;
}
.note-editor > summary::-webkit-details-marker { display: none; }
.note-form { display: flex; flex-direction: column; gap: 0.5rem; margin: 0 0 0.25rem; }
.note-form textarea { width: 100%; resize: vertical; }
.note-form button { align-self: flex-start; }
/* Written by the crew, so it reads as theirs rather than as a warning. */
.note-crew {
  background: var(--md-secondary-container) !important;
  color: var(--md-on-secondary-container) !important;
}

.note-icon { width: 1rem; height: 1rem; flex: 0 0 auto; }

.stop .note {
  background: var(--md-warning-container); color: var(--md-on-warning-container);
  border-radius: var(--md-shape-sm);
  padding: 0.5rem 0.75rem; margin: 0.625rem 0 0;
  font-size: 0.875rem; line-height: 1.25rem;
  /* Someone will paste a URL, or a registration number, or lean on a key. */
  overflow-wrap: anywhere;
}

/* --- the two marks ------------------------------------------------------------ */
/* Tree and money, side by side, each a toggle that carries its own answer.
   They are the only large things on the card on purpose: at a stop there is
   nothing else to decide. */

.marks { display: flex; gap: 0.5rem; margin-top: 0.75rem; }
.marks .mark { flex: 1 1 0; min-width: 0; margin: 0; }
.mark-btn {
  position: relative;
  width: 100%; min-height: 5rem;
  flex-direction: column; gap: 0.125rem;
  padding: 0.625rem 0.5rem;
  border-radius: var(--md-shape-md);
  background: var(--md-surface-container-high);
  color: var(--md-on-surface-variant);
  border: 2px solid var(--md-outline-variant);
  box-shadow: none;
}
.mark-btn:hover {
  background: var(--md-surface-container-highest);
  color: var(--md-on-surface); box-shadow: none;
}
.mark-btn:active { transform: scale(0.98); }
.mark-icon { width: 1.75rem; height: 1.75rem; opacity: 0.85; }
.mark-label { font-size: 1rem; line-height: 1.25rem; font-weight: 500; }
.mark-state {
  font-size: 0.6875rem; line-height: 1rem; font-weight: 400;
  letter-spacing: 0.03125rem; text-transform: uppercase;
  opacity: 0.75;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The tick only appears once the answer is yes, so a glance down a card reads
   as a column of ticks rather than a column of labels. */
.mark-tick {
  position: absolute; top: 0.4rem; right: 0.4rem;
  width: 1.125rem; height: 1.125rem;
  opacity: 0; transform: scale(0.6);
  transition: opacity var(--md-duration-short) var(--md-ease-standard),
              transform var(--md-duration-short) var(--md-ease-emphasized);
}
.mark-btn.on {
  background: var(--md-primary-container); color: var(--md-on-primary-container);
  border-color: var(--md-primary);
}
.mark-btn.on:hover { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.mark-btn.on .mark-icon { opacity: 1; }
.mark-btn.on .mark-tick { opacity: 1; transform: none; }
/* Nothing was there. A definite answer, so it is drawn -- struck through and
   greyed -- rather than left looking like a stop nobody has reached yet. */
.mark-btn.none {
  background: var(--md-surface-container-highest);
  border-color: var(--md-on-surface-variant);
  color: var(--md-on-surface-variant);
}
.mark-btn.none .mark-icon { opacity: 0.45; }
.mark-btn.none .mark-label { text-decoration: line-through; }

/* Money answers yes in a different hue from the tree, so a card where both are
   done still reads as two answers rather than one green block -- and a card
   where only one is done is obvious without reading either label. */
.marks .mark:last-child .mark-btn.on {
  background: var(--md-tertiary-container); color: var(--md-on-tertiary-container);
  border-color: var(--md-tertiary);
}
.marks .mark:last-child .mark-btn.on:hover {
  background: var(--md-tertiary-container); color: var(--md-on-tertiary-container);
}

/* Directions, call, no-tree: the exceptions, sized like exceptions. */
.stop .acts {
  display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;
  margin-top: 0.625rem;
}
/* Four of them now, so they settle into two rows of two rather than three and
   a widow. */
.stop .acts form { flex: 1 1 7rem; margin: 0; }
.stop .acts > a, .stop .acts button {
  flex: 1 1 7rem; width: 100%; min-height: 40px; padding: 0 0.5rem;
}

/* --- the map panel ------------------------------------------------------------ */
/* Shorter than the organiser's map: this one is glanced at between houses with
   the list still on screen, not studied. */

.map-panel { background: var(--md-surface-container); }
.map-bar {
  display: flex; flex-wrap: wrap; gap: 0.375rem; align-items: center;
  padding: 0.5rem 0.75rem;
}
.map-bar .btn-plain { background: var(--md-surface); }
/* A pressed state for the plain buttons that latch: locating, and following. */
.map-bar .btn-plain.on {
  background: var(--md-primary); color: var(--md-on-primary);
  border-color: var(--md-primary);
}
.map-bar .hint { flex: 1 1 100%; margin: 0; font-size: 0.75rem; line-height: 1rem; }
.map-panel .route-map { height: 13rem; border-radius: 0; }
.map-note { margin: 0; padding: 0 0.75rem 0.5rem; font-size: 0.75rem; }

/* The key for the three states of the road. Swatches rather than words doing
   the work, because it is read at a glance from a moving cab. */
.map-key { display: flex; flex-wrap: wrap; align-items: center; gap: 0.125rem 0.75rem; }
/* Each swatch stays welded to its own words: wrapping used to leave a colour
   at the end of one line and what it means at the start of the next. */
.key-item { display: inline-flex; align-items: center; gap: 0.3rem; white-space: nowrap; }
.key {
  display: inline-block; width: 1.25rem; height: 0.25rem;
  border-radius: var(--md-shape-full);
}
.key-now { background: #1f5f8b; height: 0.375rem; }
.key-ahead { background: var(--md-primary); }
.key-behind { background: #9aa39a; }

/* --- the current stop --------------------------------------------------------- */
/* The house being driven to, pinned where the thumb already is. Its buttons
   press the ones on that house's card, so a mark is still made in one place. */

.drive-bar {
  position: fixed; z-index: 6;
  left: 0; right: 0; bottom: 0;
  /* Holds the outgoing house inside the bar while it slides away. */
  overflow: hidden;
  padding: 0.5rem 0.75rem calc(0.5rem + env(safe-area-inset-bottom, 0px));
  background: var(--md-surface-container-high);
  border-top: 1px solid var(--md-outline-variant);
  box-shadow: var(--md-elevation-3);
}
/* Wide screens: keep it over the list rather than spanning a desk monitor. */
@media (min-width: 42rem) {
  .drive-bar {
    left: 50%; transform: translateX(-50%);
    right: auto; width: 40rem;
    border: 1px solid var(--md-outline-variant); border-bottom: none;
    border-radius: var(--md-shape-md) var(--md-shape-md) 0 0;
  }
}

.drive-where { display: flex; align-items: center; gap: 0.5rem; }
.drive-bar .n {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; min-width: 2rem; height: 2rem;
  border-radius: var(--md-shape-full);
  background: var(--md-primary); color: var(--md-on-primary);
  font-size: 0.875rem; font-weight: 500; font-variant-numeric: tabular-nums;
}
/* Standing at the house rather than driving to it, and -- the same colour, the
   same reason -- the run back to the drop-off at the end of the round. Scoped
   to the live card: a copy sliding away keeps whatever it was wearing rather
   than being repainted in the colours of what is arriving behind it. */
.drive-bar.here .drive-card .n,
.drive-bar.finishing .drive-card .n {
  background: var(--md-tertiary); color: var(--md-on-tertiary);
}
.drive-bar.here .drive-card .drive-label,
.drive-bar.finishing .drive-card .drive-label {
  color: var(--md-tertiary); font-weight: 700;
}
.drive-bar .n .mark-icon { width: 1.125rem; height: 1.125rem; opacity: 1; }

/* The address is a button because tapping it takes you to the full card --
   comments, unit, phone -- without looking like one more thing to press. */
.drive-addr {
  flex: 1 1 auto; min-width: 0;
  /* Block, not flex: the label and the distance are one line of running text,
     and the street is the line under it. */
  display: block; min-height: 0; padding: 0.125rem 0;
  background: none; border: none; box-shadow: none;
  color: inherit; text-align: left;
}
.drive-addr:hover, .drive-addr:active { background: none; box-shadow: none; color: inherit; }
.drive-label, .drive-dist {
  font-size: 0.6875rem; line-height: 1rem; font-weight: 500;
  letter-spacing: 0.03125rem;
  color: var(--md-on-surface-variant);
}
.drive-label { text-transform: uppercase; }
/* Not uppercased: "190 M" is not a unit. */
.drive-dist { font-variant-numeric: tabular-nums; }
.drive-street {
  display: block;
  font-size: 1rem; line-height: 1.375rem; font-weight: 500;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#drive-nav { flex: 0 0 auto; }
/* Leaving a house unanswered is allowed but not encouraged, so it is the
   quietest thing in the bar. */
.drive-skip { flex: 0 0 auto; padding: 0 0.625rem; }

/* The end of the round, where the marks would be. Built from the same parts as
   a mark group -- a label line and a control the size of a segment -- so the
   bar is exactly as tall either way and nothing jumps when the last house is
   answered. */
.drive-finish-act { margin-top: 0.5rem; }
.drive-done {
  width: 100%;
  background: var(--md-tertiary-container); color: var(--md-on-tertiary-container);
  border-color: var(--md-tertiary); font-weight: 700;
}
.drive-done:hover {
  background: var(--md-tertiary-container); color: var(--md-on-tertiary-container);
}

/* Each mark is a pair: got it, none, or -- with neither lit -- nothing said
   about this house yet. Three answers out of two buttons, with the third one
   costing no space and needing no explaining. */
.drive-marks { display: flex; gap: 0.5rem; margin-top: 0.5rem; }
.drive-group { flex: 1 1 0; min-width: 0; }
.drive-group-label {
  display: flex; align-items: center; gap: 0.25rem;
  padding: 0 0.125rem 0.1875rem;
  font-size: 0.6875rem; line-height: 1rem; font-weight: 500;
  letter-spacing: 0.03125rem; text-transform: uppercase;
  color: var(--md-on-surface-variant);
}
.drive-group-label .mark-icon { width: 0.875rem; height: 0.875rem; }
.drive-seg { display: flex; gap: 0.25rem; }

.seg {
  flex: 1 1 0; min-width: 0;
  min-height: 2.75rem; padding: 0 0.375rem;
  border-radius: var(--md-shape-sm);
  background: var(--md-surface-container-low);
  color: var(--md-on-surface-variant);
  border: 2px solid var(--md-outline-variant);
  font-size: 0.875rem; box-shadow: none;
}
.seg:hover {
  background: var(--md-surface-container-low);
  color: var(--md-on-surface); box-shadow: none;
}
.seg:active { transform: scale(0.97); }
/* Got it: the same green the card uses, so the two views agree at a glance. */
.seg.on {
  background: var(--md-primary-container); color: var(--md-on-primary-container);
  border-color: var(--md-primary); font-weight: 700;
}
.drive-group:last-child .seg.on {
  background: var(--md-tertiary-container); color: var(--md-on-tertiary-container);
  border-color: var(--md-tertiary);
}
/* None: an answer, and a definite one, but not a good outcome -- so it is
   filled and unmistakable without borrowing the colour that means collected. */
.seg.on-none, .drive-group:last-child .seg.on-none {
  background: var(--md-surface-container-highest);
  color: var(--md-on-surface);
  border-color: var(--md-on-surface-variant);
  font-weight: 700;
}
.seg.on:hover, .seg.on-none:hover { background: inherit; color: inherit; }

/* Moving on. Answering a house changes the bar to the next one, and done in a
   single frame that swap is easy to miss with a phone in one hand and a tree
   in the other. So the answered house slides out of the way and the next one
   comes in behind it, from the side the round is coming from. The house and
   its marks are one card and travel together, at a pace that can be followed
   without being waited on. The copy that slides out is made in
   driver-stops.js; the card itself never moves house mid-animation, it is
   only animated in. */

.drive-ghost {
  position: absolute;
  pointer-events: none;
}
.drive-ghost.was-here .n, .drive-ghost.was-finish .n {
  background: var(--md-tertiary); color: var(--md-on-tertiary);
}
.drive-ghost.was-here .drive-label, .drive-ghost.was-finish .drive-label {
  color: var(--md-tertiary); font-weight: 700;
}

.drive-bar.moving .drive-card {
  animation: drive-in var(--drive-slide) var(--md-ease-emphasized) both;
}
.drive-bar.moving .drive-ghost {
  animation: drive-out var(--drive-slide) var(--md-ease-emphasized) both;
}
/* Backwards: a house picked out of order, or one waved past and come back to. */
.drive-bar.moving.back .drive-card { animation-name: drive-in-back; }
.drive-bar.moving.back .drive-ghost { animation-name: drive-out-back; }

/* Sideways only: anything that moves down here goes off the bottom of the
   screen. */
@keyframes drive-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateX(-2.5rem); }
}
@keyframes drive-in {
  from { opacity: 0; transform: translateX(2.5rem); }
  to   { opacity: 1; transform: none; }
}
@keyframes drive-out-back {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateX(2.5rem); }
}
@keyframes drive-in-back {
  from { opacity: 0; transform: translateX(-2.5rem); }
  to   { opacity: 1; transform: none; }
}

/* --- motion ------------------------------------------------------------------- */
/* Material's fade-through: content arrives with a short rise and a fade, on
   the emphasized easing curve. Durations are M3 short-4 and medium-2. */

@keyframes md-fade-through {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
@keyframes md-menu-in {
  from { opacity: 0; transform: scaleY(0.9); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --- driver links ---------------------------------------------------------------- */
/* The link and its QR code are the same fact twice over: one to send, one to
   point a camera at. They sit side by side so they read as one thing. */

.driver-link { display: flex; gap: 1rem; align-items: flex-start; }
.driver-link-text { line-height: 2rem; }
.driver-link-text .hint { line-height: 1.25rem; }
/* The caveat about who the code lets in is worth its own line rather than
   trailing off the end of a button. */
.driver-link .hint { display: block; margin-top: 0.35rem; }
.qr { margin: 0; flex: none; width: 8rem; text-align: center; }
.qr svg {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--md-outline-variant); border-radius: var(--md-shape-sm);
}
.qr figcaption { font-size: 0.75rem; color: var(--md-on-surface-variant); margin-top: 0.25rem; }
@media (max-width: 30rem) { .driver-link { flex-direction: column; } }

/* --- look and feel ---------------------------------------------------------------
   The layer that makes it feel like a season rather than a form. Everything
   above is the structure and most of it stays; this restyles the surfaces on
   top of it -- the app bar, cards, tiles, buttons -- and adds the little bit of
   motion that says a page is alive: tiles counting up, a progress bar filling,
   cards settling in. All of it stops for prefers-reduced-motion (the rule in
   the motion section above), and none of it reaches paper, whose rules follow.

   Kept as one block on purpose, so the look can be changed or taken back out
   without touching a rule that decides where anything goes. */

body {
  background:
    radial-gradient(1100px 380px at 50% -140px, var(--glow), transparent 70%),
    var(--md-surface);
  background-attachment: fixed;
}

/* The app bar: deep evergreen, a row of pines along its foot, and sticky, so
   the way to another page is always one tap away on a 300-row list. */
header.site {
  position: sticky; top: 0; z-index: 20;
  color: #fff;
  background:
    linear-gradient(135deg, var(--brand-deep), var(--brand-mid) 60%, var(--brand-end));
  box-shadow: 0 2px 12px -4px rgba(0, 0, 0, 0.35);
  overflow: hidden;
}
header.site::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 14px;
  pointer-events: none; opacity: 0.18;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='14'%3E%3Cpath fill='%23fff' d='M8 0l6 14H2zM30 3l5 11h-10zM48 1l6 13H42z'/%3E%3C/svg%3E") repeat-x bottom;
}
header.site .inner { position: relative; z-index: 1; }
@media (max-width: 30rem) {
  /* The name fits on one line beside its tree on a phone at this size. */
  header.site .brand { font-size: 1.0625rem; line-height: 1.5rem; }
  header.site .brand::before { width: 1.25rem; height: 1.25rem; }
}
header.site .brand {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-weight: 500; letter-spacing: 0.01em;
}
header.site .brand::before {
  content: ""; width: 1.5rem; height: 1.5rem; flex: none;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l4.5 6h-2.2l4.2 5.5h-2.4L21 19h-7.5v3h-3v-3H3l4.9-5.5H5.5L9.7 8H7.5z'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l4.5 6h-2.2l4.2 5.5h-2.4L21 19h-7.5v3h-3v-3H3l4.9-5.5H5.5L9.7 8H7.5z'/%3E%3C/svg%3E") center / contain no-repeat;
}
header.site a:not(.brand) { color: rgba(255, 255, 255, 0.88); }
header.site a:not(.brand):hover { color: #fff; background: rgba(255, 255, 255, 0.12); }
header.site a.on {
  color: var(--brand-deep); background: #fff; font-weight: 600;
  box-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.3);
}
header.site .btn-plain {
  color: #fff; border-color: rgba(255, 255, 255, 0.55);
}
header.site .btn-plain:hover { background: rgba(255, 255, 255, 0.14); color: #fff; }

h1 { font-size: 1.75rem; line-height: 2.25rem; font-weight: 500; letter-spacing: -0.01em; }
h2 { font-weight: 500; letter-spacing: -0.005em; }

/* Cards lift off the page instead of sitting in it, and arrive in a short
   stagger rather than all at once. */
.card {
  background: var(--md-surface-container-lowest);
  border: 1px solid color-mix(in srgb, var(--md-outline-variant) 55%, transparent);
  border-radius: var(--md-shape-lg);
  box-shadow: var(--card-shadow);
  animation: md-fade-through var(--md-duration-medium) var(--md-ease-emphasized) both;
}
.card:nth-of-type(2) { animation-delay: 40ms; }
.card:nth-of-type(3) { animation-delay: 80ms; }
.card:nth-of-type(4) { animation-delay: 120ms; }
.card:nth-of-type(n+5) { animation-delay: 160ms; }

/* Stat tiles: a coloured rule across the top, a number big enough to read
   from across a room, and a lift under the pointer where the tile is a link. */
.stat {
  position: relative; overflow: hidden;
  background: var(--md-surface-container-lowest);
  border: 1px solid color-mix(in srgb, var(--md-outline-variant) 55%, transparent);
  box-shadow: var(--card-shadow);
  padding: 1.125rem 1.25rem 1rem;
  --tile: var(--md-primary);
  transition: transform var(--md-duration-medium) var(--md-ease-emphasized),
              box-shadow var(--md-duration-medium) var(--md-ease-emphasized);
}
.stat::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 4px;
  background: linear-gradient(90deg, var(--tile), color-mix(in srgb, var(--tile) 35%, transparent));
}
.stat .n {
  font-size: 2.25rem; line-height: 2.5rem; font-weight: 600;
  letter-spacing: -0.02em; font-variant-numeric: tabular-nums;
  color: var(--tile);
}
.stat.tile-gold { --tile: var(--accent-gold); }
.stat.tile-berry { --tile: var(--accent-berry); }
.stat.tile-teal { --tile: var(--md-tertiary); }
a.stat:hover {
  background: var(--md-surface-container-lowest);
  transform: translateY(-3px); box-shadow: var(--card-shadow-hover);
}

/* How far through the day the season is: a bar that fills when the page
   arrives, and fills again as marks come in on a live page. */
.progress {
  margin: 1.25rem 0 0.25rem;
  display: grid; gap: 0.875rem;
}
.progress-row { display: grid; gap: 0.375rem; }
.progress-label {
  display: flex; justify-content: space-between; gap: 1rem;
  font-size: 0.875rem; color: var(--md-on-surface-variant);
}
.progress-label strong { color: var(--md-on-surface); font-variant-numeric: tabular-nums; }
.progress-track {
  height: 10px; border-radius: var(--md-shape-full);
  background: var(--md-surface-container-high); overflow: hidden;
}
.progress-fill {
  height: 100%; width: var(--pct, 0%); border-radius: inherit;
  background: linear-gradient(90deg, var(--md-primary), color-mix(in srgb, var(--md-primary) 55%, #9be7b7));
  animation: progress-in 900ms var(--md-ease-emphasized) both 150ms;
  transform-origin: left;
}
.progress-fill.gold {
  background: linear-gradient(90deg, var(--accent-gold), color-mix(in srgb, var(--accent-gold) 50%, #ffe39a));
}
@keyframes progress-in { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Buttons: a touch of depth on the filled ones, a press you can feel, and a
   lift only where there is a pointer to hover with -- on a touch screen a
   stuck hover state is worse than none. */
/* :where() keeps these at the weight of a bare `button` / `.btn`, so any
   component that styles its own buttons -- the drive bar's address, the
   segmented marks -- still wins without having to know this is here. */
button:where(:not(.btn-plain, .btn-danger, [disabled])),
.btn:where(:not(.btn-plain, .btn-danger)) {
  background: linear-gradient(180deg, color-mix(in srgb, var(--md-primary) 88%, #fff), var(--md-primary));
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.14);
}
button, .btn {
  transition: background-color var(--md-duration-short) var(--md-ease-standard),
              box-shadow var(--md-duration-short) var(--md-ease-standard),
              transform var(--md-duration-short) var(--md-ease-standard),
              color var(--md-duration-short) var(--md-ease-standard),
              border-color var(--md-duration-short) var(--md-ease-standard);
}
@media (hover: hover) {
  button:where(:not(.btn-plain, .btn-danger, .seg, .mark-btn, .drive-addr, [disabled])):hover,
  .btn:where(:not(.btn-plain, .btn-danger)):hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 12px -4px color-mix(in srgb, var(--md-primary) 70%, transparent);
  }
  .btn-plain:hover { border-color: var(--md-primary); }
}
button:active, .btn:active { transform: scale(0.97); }

/* Fields glow rather than just changing their border. */
input:focus, textarea:focus, select:focus {
  box-shadow: 0 0 0 4px var(--glow);
}

/* Chips: pills with a dot, so a status reads before its word does. */
.tag { border-radius: var(--md-shape-full); padding: 0 0.625rem; gap: 0.35rem; }
.tag-ok::before, .tag-warn::before, .tag-bad::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor;
}

/* Tables: quieter headings, a row tint under the pointer. Not upper case:
   tried, and the wider headings pushed the plan's Notes column off the card. */
th { font-size: 0.8125rem; }
.card table tr:hover td { background: color-mix(in srgb, var(--md-primary) 5%, transparent); }

.notice { border-radius: var(--md-shape-lg); }

/* The public front door: a banner with the season in it, and a little snow. */
.hero {
  position: relative; overflow: hidden; isolation: isolate;
  margin: 0 0 1.5rem; padding: 1.75rem 1.5rem 3.25rem;
  border-radius: var(--md-shape-xl);
  color: #fff;
  background: linear-gradient(150deg, var(--brand-deep), var(--brand-mid) 55%, var(--brand-end));
  box-shadow: var(--card-shadow);
  animation: md-fade-through var(--md-duration-medium) var(--md-ease-emphasized) both;
}
.hero h1 { font-size: 2rem; line-height: 2.5rem; margin: 0 0 0.5rem; }
.hero .sub { color: rgba(255, 255, 255, 0.9); margin: 0; font-size: 1rem; line-height: 1.5rem; }
.hero .sub strong { color: #fff; }
.hero-trees {
  position: absolute; inset: auto 0 0 0; height: 60px; z-index: -1;
  width: 100%; color: rgba(255, 255, 255, 0.14);
}
.hero::before, .hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    radial-gradient(2px 2px at 20px 30px, #fff 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 80px 70px, #fff 50%, transparent 51%),
    radial-gradient(2.5px 2.5px at 140px 20px, #fff 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 190px 90px, #fff 50%, transparent 51%),
    radial-gradient(2px 2px at 250px 50px, #fff 50%, transparent 51%);
  background-size: 280px 120px;
  opacity: 0.55;
  animation: snow-near 14s linear infinite;
}
/* A second, smaller, slower layer, so the snow has some depth to it. Each
   layer moves exactly one tile, which is what makes the loop seamless. */
.hero::after { background-size: 200px 100px; opacity: 0.35; animation-name: snow-far; animation-duration: 22s; }
@keyframes snow-near { from { background-position: 0 0; } to { background-position: 30px 120px; } }
@keyframes snow-far { from { background-position: 0 0; } to { background-position: -20px 100px; } }

/* The driver sheet's bar, in the same evergreen as the app bar. White on deep
   green is also the pairing that holds up best in low winter sun. */
.driver-head {
  color: #fff;
  background: linear-gradient(135deg, var(--brand-deep), var(--brand-mid) 60%, var(--brand-end));
}
.driver-head .queue-note { color: #fff; }

/* A row that needs nothing says so quietly. */
.located-ok { color: var(--md-primary); font-weight: 600; opacity: 0.7; }

/* The ready-to-build line on the dashboard: one row when everything is set,
   the full checklist a tap away. */
details.ready-check > summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  list-style: none; font-size: 0.9375rem;
}
details.ready-check > summary::-webkit-details-marker { display: none; }
details.ready-check > summary::after {
  content: "Show"; margin-left: auto;
  font-size: 0.8125rem; font-weight: 500; color: var(--md-primary);
}
details.ready-check[open] > summary::after { content: "Hide"; }
details.ready-check[open] > summary { margin-bottom: 0.5rem; }
details.ready-check { padding-bottom: 1.25rem; }

/* The browser's own "Choose File" button, dressed like the rest. Only the
   button part can be styled, which is all that looks out of place. */
input[type=file] { font: inherit; font-size: 0.875rem; color: var(--md-on-surface-variant); max-width: 100%; }
input[type=file]::file-selector-button {
  font: inherit; font-size: 0.8125rem; font-weight: 500;
  margin-right: 0.75rem; padding: 0 0.875rem; min-height: 32px;
  border-radius: var(--md-shape-full);
  border: 1px solid var(--md-outline); background: transparent; color: var(--md-primary);
  cursor: pointer;
  transition: background-color var(--md-duration-short) var(--md-ease-standard);
}
input[type=file]::file-selector-button:hover {
  background: color-mix(in srgb, var(--md-primary) 8%, transparent);
}

/* The word "optional" in a label, set back so the question reads first --
   it was the same weight and colour as the label and read as one phrase. */
label .hint { font-size: 0.8125rem; font-style: italic; opacity: 0.75; margin-left: 0.25rem; }

/* The signups search stays in reach under the app bar on a long list. Wide
   screens only: on a phone the bar is two rows already, and a second sticky
   strip under it would leave a letterbox to read the list through. */
@media (min-width: 48.0625rem) {
#find-pickups {
  position: sticky; top: 64px; z-index: 10;
  padding: 0.5rem 0;
  background: color-mix(in srgb, var(--md-surface) 88%, transparent);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
}

/* One household's page: label and value side by side, the value wrapping
   under itself rather than under the label. */
.back-link { margin: 0 0 0.75rem; font-size: 0.875rem; }
.back-link a { text-decoration: none; font-weight: 500; }
.card-title { margin: 0 0 0.75rem; font-size: 1.0625rem; line-height: 1.5rem; }
.facts {
  display: grid; grid-template-columns: minmax(0, 9rem) minmax(0, 1fr);
  gap: 0.5rem 1rem; margin: 0;
}
.facts dt { color: var(--md-on-surface-variant); font-size: 0.875rem; }
.facts dd { margin: 0; overflow-wrap: anywhere; }
.facts .comment { white-space: pre-line; }
.facts-actions { margin-top: 1rem; }
.facts-actions + .facts-actions { margin-top: 0.5rem; }
@media (max-width: 30rem) {
  .facts { grid-template-columns: 1fr; gap: 0.125rem; }
  .facts dd { margin-bottom: 0.625rem; }
}
.plain-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 0.5rem; }

/* A row in a list that opens its household. The address is the link; the
   whole row answers a tap (row-open.js), except its own buttons. */
.row-link { color: inherit; text-decoration: none; }
.row-link:hover { color: var(--md-primary); text-decoration: underline; }
tr[data-href] { cursor: pointer; }

/* --- paper --------------------------------------------------------------------- */
/* One page per truck, read on a clipboard in a cold cab. Material's surfaces
   and elevation mean nothing here, so this strips back to ink on white. */

@media print {
  header.site, .noprint, .driver-head, .driver-tools, .driver-top,
  .map-panel, .drive-bar, .stop .acts, .mark-tick,
  .live-strip { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .wrap, .driver-wrap { max-width: none; padding: 0; animation: none; }
  .sheet { page-break-after: always; }
  .sheet:last-child { page-break-after: auto; }
  table { font-size: 10pt; }
  th, td { padding: 0.25rem 0.35rem; border-bottom: 1px solid #999; }
  .card {
    background: #fff; border: none; padding: 0; border-radius: 0;
    box-shadow: none; animation: none;
  }
  .stat::before, .progress, .hero::before, .hero::after, .hero-trees { display: none; }
  .hero { background: #fff; color: #000; box-shadow: none; padding: 0; }
  .hero .sub, .hero .sub strong { color: #000; }
  .stat .n { color: #000; }
  .unit, .paid { background: none; border: 1px solid #999; color: #000; }
  .stat, .notice, .picked-address, .stop, .dumpband {
    animation: none; box-shadow: none; background: #fff; color: #000;
    border: 1px solid #999;
  }
  /* On paper the two marks become two boxes to pencil in, which is what a
     printed round sheet is for in the first place. */
  .stop.done, .stop.skipped { opacity: 1; }
  .mark-btn {
    min-height: 0; background: #fff; color: #000; border: 1px solid #999;
    border-radius: 0; padding: 0.35rem;
  }
  .mark-btn .mark-icon { display: none; }
  .mark-btn .mark-state { text-transform: none; }
  a { text-decoration: none; color: inherit; }
}
