/* Roster-specific components. Base controls come from ui.css; tokens from
   ios-tokens.css. Nothing here hardcodes a colour or a type size.

   Accessibility note: this file uses the accessible tint and the 0.75-alpha
   secondary label by default, per the brief. The vivid palette appears twice,
   both times as a non-text fill — the availability-match dot and the switch
   green inherited from ui.css. */

/* `.btn` and `.badge` set an explicit display, which out-ranks the user-agent
   rule for [hidden] — without this, hiding them from JS silently does nothing.
   The selector is doubled to reach specificity (0,2,0) so it beats a single
   class without reaching for !important. */
[hidden][hidden] {
  display: none;
}

/* Held until main.js has decided whether this visitor is signed in — see the
   note in index.html. `visibility` rather than `display` so the layout is
   already settled when it clears, and nothing reflows on the way in. */
.is-booting .app,
.is-booting .tabbar {
  visibility: hidden;
}

/* Compact leaves room for the tab bar; regular has no tab bar to clear. */
body {
  padding-block-end: calc(3.0625rem + env(safe-area-inset-bottom, 0px));
}

@media (min-width: 48rem) {
  body {
    padding-block-end: 0;
  }
}

body.is-modal {
  overflow: hidden;
}

/* Wide layout ---------------------------------------------------------------
   Below 48rem this is a phone; between 48 and 66 it's the split view the brief
   describes, a 17rem sidebar beside a 38rem column. Past that there's real
   estate going spare, and a single column of cards on a desktop monitor is just
   scrolling for its own sake.

   So the groups flow into newspaper columns instead. Multi-column rather than
   grid because it packs vertically — no ragged gaps under a short card — and
   because `column-span: all` lets a segmented control or an empty state break
   out across the whole width, which is what those need.

   This is the one place the app goes past the brief's 38rem cap. Everything
   inside a column is still measured for reading; it's the page that got wider,
   not the line length. */

@media (min-width: 66rem) {
  .detail__content {
    max-width: 92rem;
    padding-inline: var(--margin);
  }

  .screen {
    columns: 2;
    column-gap: 1.5rem;
    /* Compact and regular get their air from the large title. A toolbar has no
       such thing, so the first card would otherwise sit hard against it. Kept
       in step with .pagesplit__side's sticky offset below. */
    padding-block-start: 1.25rem;
  }

  /* A card split across a column break is unreadable, so they stay whole. */
  .screen > .group {
    break-inside: avoid;
    /* Multi-column keeps the top margin of the first item in each column,
       which would stagger the tops. Zeroing it and spacing below instead
       lines every column up. */
    margin-top: 0;
    margin-bottom: var(--margin);
  }

  /* Things that belong to the page rather than to one column. */
  .screen > .group:has(.segmented),
  .screen > .group:has(.empty),
  .screen > .group:has(.buttons),
  .screen > .group:has(.monthnav),
  .screen > .actionbar {
    column-span: all;
    margin-bottom: 1.25rem;
  }

  /* A line about the whole page, not an item in a column. */
  .screen > .pageline {
    column-span: all;
    margin-bottom: 0.75rem;
  }

  /* Requests turn sideways at this width (see .swap below), so they want the
     page rather than half of it. */
  .screen > .group:has(.swap) {
    column-span: all;
  }
}

@media (min-width: 96rem) {
  .screen {
    columns: 3;
  }
}

/* Split page ----------------------------------------------------------------
   Used where a page is one thing read in order with a rail beside it, rather
   than a set of independent cards: Generate (decisions | what they add up to),
   Requests (the queue | start something), Account (the substance | who you are).

   The newspaper columns are the wrong tool for those, so the screen opts out of
   them. Stacked below 66rem, which puts the rail back at the end where a phone
   wants it. */

.pagesplit__main > .group:first-child,
.pagesplit__side > .group:first-child {
  margin-top: 0;
}

@media (min-width: 66rem) {
  .screen:has(.pagesplit) {
    columns: auto;
  }

  .pagesplit {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 21rem;
    gap: 1.5rem;
    align-items: start;
  }

  /* The toolbar's height plus the same gap the screen leaves under it, so the
     rail sits level with the main column at rest and keeps that gap once it
     pins. Any larger and it starts ten pixels low before you've even moved. */
  .pagesplit__side {
    position: sticky;
    top: calc(3.375rem + 1.25rem);
  }

  .pagesplit__main > .group,
  .pagesplit__side > .group {
    margin-bottom: var(--margin);
  }
}

/* A request on a pointer -----------------------------------------------------
   The phone card stacks who, what and the buttons, because it has one column to
   work with. On a desktop that's a tall box you scroll past four of. The same
   markup turns sideways here: who it's from, the trade side by side, and the
   answer on the right, all on one line you can compare down a list. */

@media (min-width: 66rem) {
  .swap {
    grid-template-columns: minmax(9rem, 12rem) minmax(0, 1fr) auto;
    align-items: center;
    gap: 1.25rem;
    padding: 0.875rem var(--margin);
  }

  .swap__trade {
    grid-template-columns: 1fr 1fr;
    gap: 1.25rem;
  }

  .swap__actions {
    display: flex;
    gap: 0.5rem;
  }

  .swap__actions .btn {
    width: auto;
    padding-inline: 1rem;
  }

  /* A request that can't go ahead any more says so where its buttons were. */
  .swap__stale {
    max-width: 16rem;
    text-align: end;
  }
}

/* Two cards that belong together at the head of a page. The newspaper flow
   balances columns, so a tall card beside a short one leaves a hole under the
   short one; this makes them one row of equal height. */

@media (min-width: 66rem) {
  .screen > .strip {
    column-span: all;
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: stretch;
    margin-bottom: var(--margin);
  }

  .strip > .group {
    display: flex;
    flex-direction: column;
    margin: 0;
  }

  .strip > .group > .card {
    flex: 1;
  }
}

/* A segmented control stretched across a desktop window is a row of enormous
   buttons. On a pointer it sizes to its labels and sits at the start, the way
   macOS shows one. */
@media (min-width: 66rem) {
  .segmented {
    width: max-content;
    max-width: 100%;
  }

  .segmented__label {
    padding-inline: 1rem;
    /* Sizing to content means a label is free to wrap onto two lines and make
       the whole control tall. It isn't. */
    white-space: nowrap;
  }
}

/* Next shift ----------------------------------------------------------------
   "When am I next on" is why this screen gets opened, and a column of identical
   rows makes you read to find out. This says it at a glance.

   Tint is the accent, not the ground: white on the dark-mode tint is about
   3.6:1, so the card keeps the normal card background and spends the tint on a
   rule and a caption, where it's 5.4:1 or better. */

.nextup {
  position: relative;
  padding: 0.875rem var(--margin) 1rem calc(var(--margin) + 0.25rem);
  overflow: hidden;
}

.nextup::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 0.25rem;
  background: var(--tint);
}

.nextup__cap {
  margin: 0;
  font-size: var(--text-caption1);
  line-height: 1.333;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--tint);
}

.nextup__time {
  margin: 0.125rem 0 0;
  font-size: var(--text-title1);
  line-height: 1.214;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--label);
}

.nextup__when {
  margin: 0.125rem 0 0;
  font-size: var(--text-callout);
  line-height: 1.3125;
  color: var(--label);
}

.nextup__meta {
  margin: 0.125rem 0 0;
  font-size: var(--text-subhead);
  line-height: 1.333;
  color: var(--label-secondary);
}

.nextup__money {
  font-weight: 600;
  color: var(--label);
  font-variant-numeric: tabular-nums;
}

.nextup__flag {
  margin: 0.5rem 0 0;
  font-size: var(--text-footnote);
  line-height: 1.385;
  color: var(--label-secondary);
}

.nextup__acts {
  margin-block-start: 0.75rem;
}

.nextup__acts .btn {
  width: auto;
  padding-inline: 1rem;
}

/* A rostered shift is the content of its row, so it carries the weight. */
.row__strong {
  font-weight: 600;
}

.dtable__strong {
  font-weight: 600;
  color: var(--label);
}

.row__sub--money {
  display: block;
  font-variant-numeric: tabular-nums;
}

/* Publishing strip ----------------------------------------------------------
   Four one-line facts. As a card they'd be a tall column with half the page
   empty beside them; as a strip they're a glance. */

@media (min-width: 66rem) {
  .screen > .pubstrip {
    column-span: all;
  }

  .pubstrip__items {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-inline: var(--margin);
  }

  .pubstrip__item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border: 0;
    border-radius: var(--radius-control);
    background: var(--bg-grouped-secondary);
    color: var(--label);
    font: inherit;
    font-size: var(--text-subhead);
    cursor: pointer;
  }

  @media (hover: hover) and (pointer: fine) {
    .pubstrip__item:hover:not(:disabled) {
      background: var(--fill-tertiary);
    }
  }

  .pubstrip__item:disabled {
    cursor: default;
    color: var(--label-secondary);
  }
}

/* Action bar ----------------------------------------------------------------
   The decisions you make about the thing you've just read, so they sit under
   it — and ride the bottom of the window, because a fortnight of grid is a
   long scroll back to a button. No tab bar at this width to collide with. */

@media (min-width: 66rem) {
  .actionbar {
    position: sticky;
    inset-block-end: 0;
    z-index: 4;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.75rem var(--margin);
    margin-block-start: var(--margin);
    background: var(--bg-grouped);
    border-block-start: 1px solid var(--separator);
  }

  .actionbar__note {
    margin: 0;
    font-size: var(--text-footnote);
    color: var(--label-secondary);
  }

  .actionbar__acts {
    display: flex;
    gap: 0.5rem;
    flex-shrink: 0;
  }

  .actionbar .btn {
    width: auto;
    padding-inline: 1rem;
  }
}

/* Tables -------------------------------------------------------------------
   The shared desktop table: used by the roster grid, the board, the
   availability grid and the directory.
   Column headings and alternating rows are the macOS table guidance; the
   density is well below the 44pt touch row because this tier is a pointer,
   not a thumb. Spans the full width — a grid in a newspaper column is useless. */

.screen > .group:has(.dtable) {
  column-span: all;
}

.dtable__wrap {
  margin-inline: var(--margin);
  border-radius: var(--radius-card);
  background: var(--bg-grouped-secondary);
  overflow-x: auto;
}

.dtable {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-footnote);
  line-height: 1.385;
}

.dtable th,
.dtable td {
  padding: 0.4375rem 0.625rem;
  text-align: start;
  vertical-align: top;
  border-inline-end: 1px solid var(--separator);
}

.dtable th:last-child,
.dtable td:last-child {
  border-inline-end: 0;
}

.dtable thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg-grouped-secondary);
  border-block-end: 1px solid var(--separator);
  font-size: var(--text-caption1);
  font-weight: 600;
  color: var(--label-secondary);
  white-space: nowrap;
}

.dtable__corner {
  min-width: 4.5rem;
}

/* Column heads carry two lines — the thing itself, and its measure. */
.dtable__colmain {
  display: block;
  color: var(--label);
  font-variant-numeric: tabular-nums;
}

.dtable__colsub {
  display: block;
  font-weight: 400;
  font-size: var(--text-caption2);
  color: var(--label-secondary);
}

/* Row heads are the days, so they stay put while the times scroll. */
.dtable__rowhead {
  position: sticky;
  inset-inline-start: 0;
  z-index: 1;
  background: var(--bg-grouped-secondary);
  white-space: nowrap;
}

.dtable tbody tr:nth-child(even) .dtable__rowhead {
  background: color-mix(in srgb, var(--label) 4%, var(--bg-grouped-secondary));
}

.dtable__rowmain {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: var(--text-caption1);
  color: var(--label-secondary);
}

.dtable__rowsub {
  display: block;
  font-size: var(--text-callout);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--label);
}

.dtable tr.is-today .dtable__rowmain,
.dtable tr.is-today .dtable__rowsub {
  color: var(--tint);
}

/* Alternating rows, as the macOS table guidance suggests for multiple columns. */
.dtable tbody tr:nth-child(even) {
  background: var(--fill-quaternary);
}

.dtable__time {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
}

.dtable__len {
  display: block;
  font-weight: 400;
  font-size: var(--text-caption2);
  color: var(--label-secondary);
}

.dtable__cell {
  min-width: 6.5rem;
}

.dtable__cell.is-unused {
  background: color-mix(in srgb, var(--label) 4%, transparent);
}

.dtable__none {
  color: var(--label-tertiary);
}

/* One entry per position, stacked when a start time wants several people. */
.dtable__who,
.dtable__open {
  display: block;
  width: 100%;
  margin: 0 0 0.125rem;
  padding: 0.0625rem 0.25rem;
  border: 0;
  border-radius: 0.25rem;
  background: none;
  font: inherit;
  color: var(--label);
  /* Centred so a column of names reads as a column, the way the Plus One
     cells already did — left-aligned they drifted against the cell edges. */
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (hover: hover) and (pointer: fine) {
  button.dtable__who:hover,
  button.dtable__open:hover {
    background: var(--fill-tertiary);
    cursor: pointer;
  }
}

.dtable__who.is-me {
  background: color-mix(in srgb, var(--green-vivid) 20%, transparent);
  font-weight: 600;
}

.dtable__open {
  background: var(--yellow-vivid);
  color: var(--on-yellow);
  font-weight: 600;
  text-align: center;
}

.dtable__open.is-blocked {
  background: color-mix(in srgb, var(--yellow-vivid) 18%, transparent);
  color: var(--label-secondary);
  font-weight: 400;
}

.dtable__note {
  color: var(--orange);
}

/* Toolbar, not a large title -------------------------------------------------
   The collapsing large title is an iOS gesture — it exists because a phone has
   no room for a title and a toolbar at once. At this width there's room for
   both, so the title moves inline and stops animating, and the bar reads as a
   toolbar: view name on the left, actions on the right. */

@media (min-width: 66rem) {
  .navbar__bar,
  .navbar__title {
    max-width: 92rem;
  }

  .navbar {
    padding-block-start: 0.5rem;
  }

  .navbar__title {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  /* The inline title is the only one now, so it stops fading and stops being
     centred — a toolbar title sits at the leading edge. */
  .navbar__inline {
    position: static;
    transform: none;
    opacity: 1;
    margin-inline-end: auto;
    font-size: var(--text-title3);
    font-weight: 700;
    letter-spacing: 0.011em;
  }

  .navbar.is-collapsed .navbar__inline {
    opacity: 1;
  }

  /* The toggle no longer needs to push the actions across. */
  .navbar__toggle {
    margin-inline-end: 0.25rem;
  }

  .navbar::after {
    opacity: 1;
  }
}

.skiplink {
  position: absolute;
  left: -9999px;
  z-index: 100;
}

.skiplink:focus {
  left: var(--margin);
  top: var(--margin);
  padding: 0.75rem 1rem;
  border-radius: var(--radius-control);
  background: var(--bg-grouped-secondary);
  color: var(--tint);
  font-weight: 600;
  text-decoration: none;
}

.icon {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
}

.icon--back {
  transform: rotate(180deg);
}

.icon--down {
  width: 0.75rem;
  height: 0.75rem;
  transform: rotate(90deg);
  color: var(--label-secondary);
}

.icon--check {
  color: var(--tint);
}

/* Tab bar ------------------------------------------------------------------
   Compact-width navigation. Every tab stays visible and enabled whatever its
   content, per the HIG — only the badge comes and goes. */

.tabbar {
  position: fixed;
  inset: auto 0 0;
  z-index: 3;
  display: flex;
  background: color-mix(in srgb, var(--bg-grouped) 82%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-top: 1px solid var(--separator);
  padding-block-end: env(safe-area-inset-bottom, 0px);
}

@media (min-width: 48rem) {
  .tabbar {
    display: none;
  }
}

.tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  min-height: 3.0625rem; /* 49pt, Apple's tab bar height */
  padding: 0.3125rem 0.25rem;
  color: var(--label-secondary);
  text-decoration: none;
}

.tab[aria-current="page"] {
  color: var(--tint);
}

.tab__icon {
  position: relative;
  display: grid;
  place-items: center;
}

.tab__icon .icon {
  width: 1.5rem;
  height: 1.5rem;
}

.tab__label {
  font-size: var(--text-caption2);
  font-weight: 500;
  letter-spacing: 0.006em;
}

/* Badges -------------------------------------------------------------------
   Count badges use the tint rather than Apple's red: white on the dark-mode red
   measures 3.41:1, which fails AA at badge size. The tint clears AA in both
   appearances and still reads as a system badge. */

.badge {
  display: inline-grid;
  place-items: center;
  min-width: 1.375rem;
  height: 1.375rem;
  padding-inline: 0.375rem;
  border-radius: var(--radius-capsule);
  font-size: var(--text-caption1);
  font-weight: 600;
  line-height: 1;
}

/* Plus One. Yellow with black on top — systemYellow is light in both
   appearances, so white on it measures about 1.4:1 and black about 14:1. */
.badge--open {
  background: var(--yellow-vivid);
  color: var(--on-yellow);
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.badge--count {
  min-width: 1.25rem;
  height: 1.25rem;
  background: var(--tint);
  color: var(--on-tint);
  margin-inline-start: auto;
}

/* Which roster a shift is on. Neutral on purpose — it labels rather than
   signals, so it can't be mistaken for the tinted +1. */
.badge--stream {
  background: var(--bg-grouped-tertiary);
  color: var(--label-secondary);
  letter-spacing: 0.02em;
}

/* Badges take the same opaque wash as the tinted buttons, and for the same
   reason: these sit in table rows, and a translucent chip on a zebra-striped
   row measures differently from one on a plain row. Mixed into --bg-grouped
   they read the same wherever the row puts them. */
.badge--swap {
  background: color-mix(in srgb, var(--tint) 15%, var(--bg-grouped));
  color: var(--tint);
}

/* Something of yours is mid-flight — a drop with a manager, or a shift on the
   board. Orange clears AA at this size where the vivid tier wouldn't. */
.badge--pending {
  background: color-mix(in srgb, var(--orange) 14%, var(--bg-grouped));
  color: var(--orange);
  font-size: var(--text-caption2);
  letter-spacing: 0.01em;
}

.tab .badge--count {
  position: absolute;
  inset-block-start: -0.1875rem;
  inset-inline-start: 55%;
  min-width: 1rem;
  height: 1rem;
  margin: 0;
  padding-inline: 0.25rem;
  font-size: var(--text-caption2);
  box-shadow: 0 0 0 2px var(--bg-grouped);
}

/* Rows and leading elements ------------------------------------------------- */

.card--lead .row:not(:last-child)::after {
  left: 4.25rem; /* clears the 2.25rem lead plus both gaps */
}

.datechip {
  flex: none;
  display: grid;
  justify-items: center;
  width: 2.25rem;
}

.datechip__dow {
  font-size: var(--text-caption2);
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--label-secondary);
}

.datechip__num {
  font-size: var(--text-title3);
  line-height: 1.2;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: var(--fill-secondary);
  color: var(--label-secondary);
  font-size: var(--text-caption1);
  font-weight: 600;
}

.avatar--open {
  background: color-mix(in srgb, var(--yellow-vivid) 22%, transparent);
  color: var(--label);
  font-size: var(--text-body);
  font-weight: 600;
}

/* Drop requests carry their own mark so they don't read as a person. */
.avatar--drop {
  background: color-mix(in srgb, var(--orange) 15%, transparent);
  color: var(--orange);
  font-size: var(--text-callout);
  font-weight: 700;
}

/* Reading a day of the roster, two rows have to jump out of the list: the ones
   that are yours, and the ones nobody's on. Each gets a full-width wash and a
   leading bar in its own colour — blue for takeable, green for yours — so they
   separate at a glance rather than on a careful read.

   Both washes are fills, not text, which is why the vivid green is allowed
   here. Every label on top stays at full label contrast. */
.row--open,
.row--mine {
  background: color-mix(in srgb, var(--row-accent) 12%, transparent);
}

.row--open {
  --row-accent: var(--yellow-vivid);
}

.row--mine {
  --row-accent: var(--green-vivid);
}

.row--open::before,
.row--mine::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 0.1875rem;
  background: var(--row-accent);
}

.row--open .row__label,
.row--mine .row__label {
  font-weight: 600;
}

.row--open .row__sub {
  color: var(--label-secondary);
  font-weight: 500;
}

.row--mine .row__sub {
  font-weight: 400;
}

.row--mine .row__value {
  font-weight: 700;
}

/* The open slot's avatar sits in the same yellow family as its row. */
.row--open .avatar--open {
  background: var(--yellow-vivid);
  color: var(--on-yellow);
}

/* A row that's both open and pressed still needs to look pressed. */
button.row--open:active,
button.row--mine:active {
  background: color-mix(in srgb, var(--row-accent) 22%, transparent);
}

/* A clashing shift is explained, not dimmed — the reason it can't be taken is
   the most useful thing in the row, so it keeps full contrast. */
.row.is-disabled .row__label {
  color: var(--label-secondary);
}

.row.is-disabled .badge--open {
  background: var(--fill);
  color: var(--label-secondary);
}

.row__sub--warn {
  color: var(--orange);
}

.row[disabled] {
  opacity: 0.4;
  cursor: default;
}

.row--check .icon--check {
  margin-inline-start: auto;
}

.dot {
  display: inline-block;
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  margin-inline-end: 0.375rem;
  vertical-align: 0.0625rem;
}

.dot--match {
  background: var(--green-vivid); /* decoration, not text — vivid is fine here */
}

/* Stepper -------------------------------------------------------------------
   Apple's stepper is 31pt tall, under the 44pt floor, so the hit area is
   extended past the visible pill rather than the pill being grown. */

.stepper {
  flex: none;
  display: flex;
  border-radius: var(--radius-control);
  background: var(--fill-tertiary);
  overflow: hidden;
}

.stepper__btn {
  position: relative;
  width: 2.75rem;
  height: 2rem;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--text-title3);
  line-height: 1;
  color: var(--tint);
  cursor: pointer;
}

.stepper__btn::before {
  content: "";
  position: absolute;
  inset: -0.375rem 0;
}

.stepper__btn + .stepper__btn {
  box-shadow: -1px 0 0 var(--separator);
}

.stepper__btn:active {
  background: var(--fill-secondary);
}

.stepper__btn[disabled] {
  color: var(--label-quaternary);
  cursor: default;
}

/* Month navigation ---------------------------------------------------------- */

/* The steppers belong to the month they step through, so they sit either side
   of it rather than at the far edges of the card. */
.monthnav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  margin-inline: var(--margin);
}

.monthnav__title {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: var(--tap);
  padding-inline: 0.5rem;
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  font: inherit;
  font-size: var(--text-title3);
  font-weight: 700;
  letter-spacing: 0.011em;
  color: var(--label);
  cursor: pointer;
}

.monthnav__title:active {
  background: var(--fill-quaternary);
}

.monthnav__step {
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  color: var(--tint);
  cursor: pointer;
}

.monthnav__step:active {
  background: var(--fill-quaternary);
}

.monthnav__step[disabled] {
  color: var(--label-quaternary);
  cursor: default;
}

/* Pointer feedback for the app's own controls -------------------------------
   The base controls hover in ui.css; these are the ones this file builds. Same
   rule throughout: deepen the surface the control already has, never invent a
   new one, and say nothing at all on a control that can't be used. */

@media (hover: hover) and (pointer: fine) {
  .monthnav__title:hover,
  .monthnav__step:not([disabled]):hover,
  .navbar__bell:hover,
  .actionsheet__btn:hover,
  .group__footer a:hover {
    background: var(--fill-quaternary);
  }

  .stepper__btn:not([disabled]):hover,
  button.cal__cell:hover .cal__num {
    background: var(--fill-secondary);
  }

  .row__hit:hover .row__label {
    opacity: 0.75;
  }

  /* Sits between the 12% resting wash and the 22% pressed one, so the row has
     three distinct states rather than two and a near-miss. */
  button.row--open:hover,
  button.row--mine:hover {
    background: color-mix(in srgb, var(--row-accent) 17%, transparent);
  }

  .tab:not([aria-current="page"]):hover {
    background: var(--fill-quaternary);
  }
}

/* Calendar ------------------------------------------------------------------
   One filled circle means rostered on, a wash means availability offered, and
   the dots count the starts. Nothing else competes for the cell. */

.cal {
  padding: 0.25rem 0.25rem 0.75rem;
}

.cal__weekdays,
.cal__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

.cal__weekday {
  padding-block: 0.5rem;
  font-size: var(--text-caption1);
  font-weight: 600;
  text-align: center;
  color: var(--label-secondary);
}

.cal__cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1875rem;
  min-height: 3.25rem;
  padding: 0.25rem 0 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--label);
  cursor: pointer;
}

button.cal__cell:active .cal__num {
  background: var(--fill-secondary);
}

.cal__num {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  font-size: var(--text-callout);
  font-variant-numeric: tabular-nums;
}

.cal__dots {
  display: flex;
  gap: 0.1875rem;
  min-height: 0.3125rem;
}

.cal__dots > span {
  width: 0.3125rem;
  height: 0.3125rem;
  border-radius: 50%;
  background: var(--tint);
}

.cal__cell.is-offered .cal__num {
  background: color-mix(in srgb, var(--tint) 14%, transparent);
  color: var(--tint);
  font-weight: 600;
}

.cal__cell.is-rostered .cal__num {
  background: var(--tint);
  color: var(--on-tint);
  font-weight: 600;
}

/* A ring rather than a fill, so "today" can coexist with the other two states.
   The inner ring matches the card so the gap reads as a gap. */
.cal__cell.is-today .cal__num {
  box-shadow: 0 0 0 1.5px var(--bg-grouped-secondary), 0 0 0 3px var(--tint);
}

/* Adjacent months stay visible for context but aren't editable from here —
   past days likewise. Both are rendered as spans, not buttons. */
.cal__cell.is-outside {
  opacity: 0.35;
}

.cal__cell.is-past .cal__num {
  color: var(--label-tertiary); /* disabled context, never essential text */
}

/* Agenda -------------------------------------------------------------------
   The availability list. Its whole argument is that it encodes nothing — the
   times are written out — so there is no legend here to learn. */

.chiprow {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.chip {
  padding: 0.1875rem 0.5rem;
  border-radius: 0.4375rem;
  background: color-mix(in srgb, var(--tint) 14%, transparent);
  color: var(--tint); /* 5.95:1 on its own wash */
  font-size: var(--text-footnote);
  line-height: 1.23;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Already rostered beats merely offered, so it reads as settled rather than
   proposed. The wash is a fill, which is why the vivid green is allowed; the
   label on top stays at full contrast. */
.chip--rostered {
  background: color-mix(in srgb, var(--green-vivid) 22%, transparent);
  color: var(--label);
}

.chip--none {
  background: var(--fill-quaternary);
  color: var(--label-secondary);
  font-weight: 400;
}

/* A day with nothing on it is still a real row you can tap, but it shouldn't
   read louder than a day with chips on it — so it drops a step off body size
   while staying well clear of AA. */
.agenda__empty {
  font-size: var(--text-callout);
  color: var(--label-secondary);
}

.chiprow + .row__sub {
  margin-block-start: 0.1875rem;
}

/* "Today" replaces the weekday, so it drops the uppercase tracking the
   three-letter abbreviations need in order to fit the same column. */
.datechip--today .datechip__dow {
  color: var(--tint);
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0;
}

.datechip--today .datechip__num {
  color: var(--tint);
}

.monthnav__summary {
  margin-block-start: 0.375rem;
}

/* Empty state --------------------------------------------------------------- */

.empty {
  display: grid;
  justify-items: center;
  gap: 0.375rem;
  padding: 2.5rem var(--margin);
  text-align: center;
}

.empty__icon {
  width: 2.25rem;
  height: 2.25rem;
  margin-block-end: 0.25rem;
  color: var(--label-tertiary); /* decorative — the title carries the meaning */
}

.empty__title {
  margin: 0;
  font-size: var(--text-headline);
  font-weight: 600;
}

.empty__message {
  margin: 0;
  max-width: 22rem;
  font-size: var(--text-subhead);
  line-height: 1.334;
  color: var(--label-secondary);
}

/* Swap cards ---------------------------------------------------------------- */

.swap {
  display: grid;
  gap: 0.75rem;
  margin-inline: var(--margin);
  padding: var(--margin);
  background: var(--bg-grouped-secondary);
  border-radius: var(--radius-card);
}

.swap + .swap {
  margin-block-start: 0.75rem;
}

.swap__head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.swap__who {
  font-size: var(--text-body);
  font-weight: 600;
}

.swap__meta {
  display: block;
  font-size: var(--text-footnote);
  line-height: 1.385;
  font-weight: 400;
  color: var(--label-secondary);
}

.swap__trade {
  display: grid;
  gap: 0.625rem;
  padding: 0.75rem;
  border-radius: 0.5rem;
  background: var(--fill-quaternary);
}

/* Stacked rather than two columns: "Sat, Aug 15 · 15:15 – 19:00" is too long to
   share a line with its label at compact width without wrapping mid-range. */
.swap__leg {
  display: grid;
  gap: 0.0625rem;
}

.swap__leg-label {
  font-size: var(--text-footnote);
  line-height: 1.385;
  color: var(--label-secondary);
}

.swap__leg-value {
  font-size: var(--text-callout);
}

.swap__leg-sub {
  display: block;
  font-size: var(--text-caption1);
  color: var(--label-secondary);
}

.swap__actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
}

.swap__actions--single {
  grid-template-columns: 1fr;
}

.swap__stale {
  margin: 0;
  font-size: var(--text-footnote);
  line-height: 1.385;
  color: var(--label-secondary);
}

.swap__inline {
  min-height: 0;
  padding: 0;
  font-size: var(--text-footnote);
}

/* Modal layer ---------------------------------------------------------------
   One scrim per modal. Sheets rise from the bottom edge on compact and present
   as a centred form sheet at regular width; alerts fade and scale from 1.15. */

.scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  justify-content: center;
  background: rgba(0, 0, 0, 0.4);
  -webkit-tap-highlight-color: transparent;
}

.scrim--sheet {
  align-items: flex-end;
}

.scrim--action {
  align-items: flex-end;
  padding: var(--margin);
  padding-block-end: calc(var(--margin) + env(safe-area-inset-bottom, 0px));
}

.scrim--alert {
  align-items: center;
  padding: var(--margin);
}

/* Modal panels carry tabindex="-1" and are focused on open so assistive tech
   announces the dialog. That programmatic focus shouldn't paint a ring around
   the whole panel — but keyboard focus still should, so this suppresses the
   outline only where :focus-visible doesn't apply, rather than removing it. */
.sheet:focus:not(:focus-visible),
.actionsheet:focus:not(:focus-visible),
.alert--modal:focus:not(:focus-visible) {
  outline: none;
}

.sheet {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 32rem;
  max-height: 92dvh;
  background: var(--bg-grouped);
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  overflow: hidden;
}

.sheet__handle {
  display: grid;
  place-items: center;
  padding-block: 0.3125rem;
  touch-action: none;
  cursor: grab;
}

.sheet__grabber {
  width: 2.25rem;
  height: 0.3125rem;
  border-radius: var(--radius-capsule);
  background: var(--label-tertiary);
}

.sheet__bar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.5rem;
  min-height: var(--tap);
  padding-inline: var(--margin);
  border-block-end: 1px solid var(--separator);
}

.sheet__bar > :first-child {
  justify-self: start;
}

.sheet__bar > :last-child {
  justify-self: end;
}

.sheet__side {
  min-height: var(--tap);
  padding-inline: 0;
  font-weight: 400;
}

.sheet__side--done {
  font-weight: 600;
}

.sheet__title {
  margin: 0;
  font-size: var(--text-headline);
  font-weight: 600;
  text-align: center;
}

.sheet__body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-block-end: calc(var(--margin) + env(safe-area-inset-bottom, 0px));
}

.sheet__body .group:first-child {
  margin-top: 0.5rem;
}

@media (min-width: 48rem) {
  .scrim--sheet {
    align-items: center;
    padding: var(--margin);
  }

  .sheet {
    max-width: 30rem;
    max-height: 82dvh;
    border-radius: var(--radius-alert);
    /* Presented from the bottom edge, a sheet is separated from the page by
       being somewhere else. Centred, it's a panel sitting on top of a page the
       same colour as it is — in dark mode both are black — so it needs an
       actual edge. A black drop shadow on a black page draws nothing. */
    border: 1px solid var(--separator);
  }

  .sheet__handle {
    display: none;
  }
}

/* Action sheet -------------------------------------------------------------- */

.actionsheet {
  display: grid;
  gap: 0.5rem;
  width: 100%;
  max-width: 24rem;
}

.actionsheet__group {
  border-radius: var(--radius-alert);
  background: var(--bg-grouped-secondary);
  overflow: hidden;
}

.actionsheet__head {
  padding: 1rem var(--margin);
  border-block-end: 1px solid var(--separator);
  text-align: center;
}

.actionsheet__title {
  margin: 0;
  font-size: var(--text-footnote);
  line-height: 1.385;
  font-weight: 600;
  color: var(--label-secondary);
}

.actionsheet__message {
  margin: 0.125rem 0 0;
  font-size: var(--text-footnote);
  line-height: 1.385;
  color: var(--label-secondary);
}

/* Each choice is a row, not a line of text: icon, label, and a chevron where
   the row leads somewhere. The grid keeps the labels on one column whether or
   not a given row carries a chevron. */
.actionsheet__btn {
  display: grid;
  grid-template-columns: 1.25rem 1fr auto;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 3.5rem;
  padding: 0.5rem var(--margin);
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--text-title3);
  color: var(--tint);
  text-align: start;
  cursor: pointer;
}

/* A sheet whose actions carry no icons drops the gutter rather than indenting
   every label past an empty column. Scoped to the group so rows within one
   sheet always share a left edge. */
.actionsheet__group:not(:has(svg.actionsheet__icon)) .actionsheet__btn {
  grid-template-columns: 1fr auto;
}

.actionsheet__group:not(:has(svg.actionsheet__icon)) .actionsheet__icon {
  display: none;
}

.actionsheet__icon {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--tint);
}

.actionsheet__btn--destructive .actionsheet__icon {
  color: var(--red);
}

.actionsheet__label {
  min-width: 0;
}

.actionsheet__btn + .actionsheet__btn {
  box-shadow: 0 -1px 0 var(--separator);
}

.actionsheet__btn:active {
  background: var(--fill-quaternary);
}

.actionsheet__btn--destructive {
  color: var(--red);
}

/* Cancel carries bare text rather than the icon/label/chevron trio, so it opts
   out of the row grid — otherwise its label lands in the 1.25rem icon column. */
.actionsheet__btn--cancel {
  display: block;
  text-align: center;
  font-weight: 600;
}

/* Alert --------------------------------------------------------------------- */

.alert--modal {
  margin: 0;
  width: 100%;
  max-width: 17rem;
}

.alert--modal .alert__title {
  font-size: var(--text-headline);
}

/* Transient confirmation ---------------------------------------------------- */

.toast {
  position: fixed;
  inset-block-end: calc(4.5rem + env(safe-area-inset-bottom, 0px));
  inset-inline: 0;
  z-index: 60;
  width: max-content;
  max-width: calc(100% - 2 * var(--margin));
  margin-inline: auto;
  padding: 0.625rem 1rem;
  border-radius: var(--radius-capsule);
  background: var(--label);
  color: var(--bg);
  font-size: var(--text-subhead);
  line-height: 1.334;
  text-align: center;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}

@media (min-width: 48rem) {
  .toast {
    inset-block-end: 1.5rem;
  }
}

/* Motion — fast, ease-out, interruptible ------------------------------------ */

@media (prefers-reduced-motion: no-preference) {
  .scrim {
    animation: scrim-in 0.2s var(--ease-out);
  }

  .scrim--sheet .sheet,
  .scrim--action .actionsheet {
    animation: sheet-up 0.32s var(--ease-out);
  }

  .scrim--alert .alert {
    animation: alert-in 0.25s var(--ease-out);
  }

  .scrim.is-leaving {
    animation: scrim-in 0.2s var(--ease-out) reverse forwards;
  }

  .scrim.is-leaving .sheet,
  .scrim.is-leaving .actionsheet {
    animation: sheet-up 0.24s var(--ease-out) reverse forwards;
  }

  .toast {
    animation: sheet-up 0.28s var(--ease-out);
  }

  .toast.is-leaving {
    animation: scrim-in 0.28s var(--ease-out) reverse forwards;
  }

  .sheet {
    transition: transform 0.28s var(--ease-out);
  }
}

@keyframes scrim-in {
  from {
    opacity: 0;
  }
}

@keyframes sheet-up {
  from {
    transform: translateY(100%);
  }
}

@keyframes alert-in {
  from {
    opacity: 0;
    transform: scale(1.15);
  }
}

/* Status marks --------------------------------------------------------------
   Everything below only ever renders when something is true of the row, which
   is what keeps the list quiet the rest of the time. */

/* --fill-secondary is translucent too, so a Draft chip picked up the zebra row
   underneath and landed at 4.08:1 on alternate lines. The grouped tertiary
   background is the opaque neutral that already exists for exactly this. */
.badge--draft {
  background: var(--bg-grouped-tertiary);
  color: var(--label-secondary);
}

.badge--ok {
  background: color-mix(in srgb, var(--green-vivid) 22%, var(--bg-grouped));
  color: var(--label);
}

.badge--warn {
  background: color-mix(in srgb, var(--orange) 18%, var(--bg-grouped));
  color: var(--orange);
}

/* Figures and badges alongside a section header. */
.group__marks {
  float: inline-end;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
}

.group__marks .badge {
  height: 1.125rem;
  min-width: 0;
  padding-inline: 0.375rem;
  font-size: var(--text-caption2);
}

.warn {
  color: var(--orange);
}

.row__sub--bad {
  color: var(--red);
}

.row__sub--note {
  color: var(--label-secondary);
  font-style: italic;
}

/* The duration on a roster row is reference, not headline. */
.row__value--quiet {
  font-size: var(--text-footnote);
  color: var(--label-secondary);
  font-variant-numeric: tabular-nums;
}

.row__inline {
  flex: none;
  min-height: 0;
  padding: 0;
  font-size: var(--text-footnote);
}


/* A row with two targets: a tappable label and a control beside it. The hit
   area is a bare button so it inherits the row's own padding rather than
   nesting a second set of insets. */
.row--split {
  padding-inline-end: var(--margin);
}

.row__hit {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: start;
  cursor: pointer;
}

.row__hit:active .row__label {
  opacity: 0.6;
}

/* The headcount figure. Tabular so a column of them lines up, and muted at zero
   because zero means "nobody starts then", not "nothing set". */
.row__value--count {
  flex: none;
  min-width: 1.25rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.row__value--count.is-zero {
  color: var(--label-tertiary);
  font-weight: 400;
}

/* Notifications -------------------------------------------------------------- */

.notif__dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-inline: 0.5rem 0.375rem;
  border-radius: 50%;
  background: var(--tint);
}

.notif__dot.is-read {
  background: transparent;
}

.row--unread .row__label {
  font-weight: 600;
}

/* A notice is a sentence or two, not a value, so its row gives it the whole
   width and lets it grow — the only multi-line field in the app. */
.row--field {
  display: block;
  padding-block: 0.625rem;
}

.field--multi {
  display: block;
  width: 100%;
  resize: vertical;
  min-height: 4.5rem;
  line-height: 1.4;
}

/* A date field keeps the system picker but loses the boxed-input look, so it
   sits in a grouped row like every other value. */
.field--date {
  flex: none;
  margin-inline-start: auto;
  text-align: end;
  color-scheme: inherit;
}

/* Activity log --------------------------------------------------------------
   The summary is the content of the row, so it carries the meaning and can't
   sit in tertiary label. Tone only shifts weight and hue within accessible
   values — never below AA. */

.log__what {
  color: var(--label-secondary);
}

.log__what--tint {
  color: var(--tint);
}

.log__what--warn {
  color: var(--orange);
}

.log__time {
  flex: none;
  font-size: var(--text-footnote);
  font-variant-numeric: tabular-nums;
}

/* Visually hidden, still announced. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* A checkmark that isn't the SVG icon — used where a row is a picker. */
.check-mark {
  flex: none;
  width: 0.5rem;
  height: 0.9rem;
  border-right: 2px solid var(--tint);
  border-bottom: 2px solid var(--tint);
  transform: rotate(45deg);
  margin-inline: auto 0.25rem;
}

/* Notification bell ---------------------------------------------------------
   In the nav bar rather than a sixth tab: five is the ceiling, and an empty
   inbox should cost nothing on screen. */

.navbar__bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  margin-inline-end: calc(var(--margin) * -0.5);
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  color: var(--tint);
  cursor: pointer;
}

.navbar__bell:active {
  background: var(--fill-quaternary);
}

/* Tint, not Apple's red: white on the dark-mode red is 3.41:1 and this app is
   dark. The ring is the grouped background so it reads as lifted off the icon. */
.badge--bell {
  position: absolute;
  inset-block-start: 0.3125rem;
  inset-inline-end: 0.3125rem;
  min-width: 1rem;
  height: 1rem;
  margin: 0;
  padding-inline: 0.1875rem;
  font-size: var(--text-caption2);
  box-shadow: 0 0 0 2px var(--bg-grouped);
}

/* Sidebar additions --------------------------------------------------------- */

.sidebar__head {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.0625rem;
}

.sidebar__who {
  font-size: var(--text-footnote);
  color: var(--label-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.sidenav__text {
  flex: 1;
  min-width: 0;
}

.sidenav[aria-current="page"] .badge--count {
  background: var(--on-tint);
  color: var(--tint);
}

/* Flat tables — board, directory, activity. Same skin, left-aligned rows with
   no row heads, and every row a target. */
.dtable--flat .dtable__cell {
  min-width: 0;
  white-space: nowrap;
}

.dtable--flat tbody tr {
  cursor: default;
}

@media (hover: hover) and (pointer: fine) {
  .dtable--flat tbody tr.is-actionable:hover {
    background: var(--fill-tertiary);
  }
}

.dtable__rowbtn {
  display: contents;
}

/* The column that carries a sentence — a note, a log entry, a notice. Doubled
   with the flat-table selector because `.dtable--flat .dtable__cell` sets
   nowrap at (0,2,0), which would otherwise win and push the table into a
   horizontal scroll rather than wrapping the text. */
.dtable__wide,
.dtable--flat .dtable__wide {
  white-space: normal;
  min-width: 12rem;
}

.dtable__num {
  font-variant-numeric: tabular-nums;
}

.dtable__muted {
  color: var(--label-secondary);
}

/* Availability cells — a click target the size of the cell, so filling a week
   in is a row of clicks rather than a trip through a sheet each time. */
.dtable__cell.avail {
  padding: 0.1875rem;
  min-width: 7rem;
}

.avail__cell {
  display: block;
  width: 100%;
  min-height: 1.75rem;
  border: 1px dashed var(--separator);
  border-radius: 0.3125rem;
  background: none;
  font: inherit;
  font-size: var(--text-caption1);
  color: var(--label-secondary);
  cursor: pointer;
}

/* Only the button form hovers. A locked cell is a span, and lighting it up on
   hover promises an edit that isn't on offer. */
@media (hover: hover) and (pointer: fine) {
  button.avail__cell:hover {
    background: var(--fill-tertiary);
    border-style: solid;
  }

  button.avail__cell.is-on:hover {
    background: color-mix(in srgb, var(--tint) 32%, transparent);
  }
}

.avail__cell.is-on {
  background: color-mix(in srgb, var(--tint) 22%, transparent);
  border: 1px solid transparent;
  color: var(--label);
  font-weight: 600;
}

.avail__on {
  display: block;
  min-height: 1.75rem;
  padding: 0.1875rem 0.25rem;
  border-radius: 0.3125rem;
  background: color-mix(in srgb, var(--green-vivid) 22%, transparent);
  font-size: var(--text-caption1);
  font-weight: 600;
  text-align: center;
}

/* The action button that ends a flat-table row. */
.dtable__act {
  position: relative;
  min-height: 1.75rem;
  padding: 0.125rem 0.625rem;
  font-size: var(--text-caption1);
  white-space: nowrap;
}

/* The button reads at 28px so the table stays dense, but that is well under the
   44px minimum target. Extend the hit region into the row's own padding without
   touching the visual size. 0.4375rem is half the 14px gap between buttons, so
   the target grows to fill the 42px row pitch exactly and never overlaps the
   button above or below — an overlap here would take you to the wrong shift.
   That lands 2px short of 44; closing it would need a taller row. */
.dtable__act::after {
  content: "";
  position: absolute;
  inset: -0.4375rem 0;
}

/* Starts control in the availability grid — how many of the marked times you
   actually want that day. Capped values are tinted so a day where you've asked
   for fewer than you marked stands out from one where you want them all. */
.dtable__starts {
  min-width: 6rem;
}

.startsctl {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  border-radius: var(--radius-control);
  background: var(--fill-tertiary);
}

.startsctl .stepper__btn {
  width: 1.75rem;
  height: 1.5rem;
  font-size: var(--text-callout);
}

.startsctl .stepper__btn::before {
  content: none;
}

.startsctl__n {
  min-width: 1.125rem;
  text-align: center;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.startsctl__n.is-capped {
  color: var(--tint);
}

/* Rank badge in the preferred-order sheet. Filled while the position is inside
   the number of starts you asked for. */
.rank {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--fill-secondary);
  color: var(--label-secondary);
  font-size: var(--text-footnote);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.rank.is-in {
  background: var(--tint);
  color: var(--on-tint);
}

/* Locked availability — visible, legible, not editable. Dimming it would work
   against the reason it's still on screen, so it loses its affordances rather
   than its contrast. */
.avail__cell.is-locked {
  display: block;
  min-height: 1.75rem;
  padding: 0.1875rem 0.25rem;
  border: 1px solid transparent;
  border-radius: 0.3125rem;
  font-size: var(--text-caption1);
  text-align: center;
  cursor: default;
}

.avail__cell.is-locked.is-on {
  background: color-mix(in srgb, var(--tint) 14%, transparent);
  color: var(--label-secondary);
  font-weight: 600;
}

.dtable tr.is-locked .dtable__rowmain,
.dtable tr.is-locked .dtable__rowsub {
  color: var(--label-secondary);
}

.lockmark {
  font-size: var(--text-caption2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.row.is-locked {
  cursor: default;
}

.monthnav__lock {
  margin-block-start: 0.125rem;
  color: var(--label-secondary);
}

/* Dialogs on a desktop -------------------------------------------------------
   A sheet that slides up from the bottom edge with Cancel top-left is an iOS
   gesture — it exists because a thumb reaches the bottom of a phone. On a
   pointer it should be a dialog: a titled panel with its buttons bottom-right,
   the way macOS has always done it.

   The markup doesn't change. `.sheet__bar` becomes `display: contents` so its
   title and buttons drop through and become items of the dialog's own grid —
   the title in the header row, the buttons in the footer row. That's what lets
   one set of controls serve both shapes, and it means the footer is a real
   grid track rather than two absolutely-positioned buttons dodging each other
   with a hard-coded offset. */

@media (min-width: 66rem) {
  .scrim--sheet {
    align-items: center;
    padding: 2rem var(--margin);
  }

  .sheet {
    display: grid;
    grid-template-columns: 1fr auto auto;
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "title  title  title"
      "body   body   body"
      "footer cancel done";
    max-width: 40rem;
    max-height: 80dvh;
    /* Squarer than the iOS alert radius — a desktop dialog is a window, not a
       rounded card sitting on a phone screen. */
    border-radius: var(--radius-control);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
  }

  /* A sheet carrying a table needs a table's width, or every column is behind
     a horizontal scrollbar. */
  .sheet--wide {
    max-width: 60rem;
  }

  .sheet__bar {
    display: contents;
  }

  /* The bar's padding and rule move onto the title, which is what's left of it
     in the header row. */
  .sheet__title {
    grid-area: title;
    margin: 0;
    padding: 0.875rem var(--margin);
    border-block-end: 1px solid var(--separator);
    text-align: start;
    font-size: var(--text-headline);
    font-weight: 600;
  }

  .sheet__body {
    grid-area: body;
    min-height: 0;
    padding-block: 0.5rem;
  }

  /* The strip the buttons sit on, painted across the whole footer row. It's a
     grid item rather than an absolute overlay, so the row is sized by the
     buttons instead of by a height that has to be kept in step with them. */
  .sheet::after {
    content: "";
    grid-row: 3;
    grid-column: 1 / -1;
    background: var(--bg-grouped);
    border-block-start: 1px solid var(--separator);
  }

  .sheet__side {
    position: relative; /* lifts the buttons above the ::after strip */
    grid-area: cancel;
    min-height: 2rem;
    margin-block: 0.75rem;
    margin-inline-start: 0.5rem;
    /* .btn's 0.75rem block padding would push this back to 44px. */
    padding: 0 1rem;
    border-radius: var(--radius-control);
    background: var(--fill-secondary);
    color: var(--label);
    font-size: var(--text-subhead);
    font-weight: 500;
  }

  /* macOS puts the confirming button last, hard against the trailing edge. */
  .sheet__side--done {
    grid-area: done;
    margin-inline-end: var(--margin);
    background: var(--tint);
    color: var(--on-tint);
    font-weight: 600;
  }

  /* With no Done button, Cancel inherits the trailing edge. */
  .sheet:not(:has(.sheet__side--done)) .sheet__side {
    margin-inline-end: var(--margin);
  }

  /* The empty slot that keeps a compact sheet's title centred has no row to
     sit in here, and would otherwise take a footer column with it. */
  span.sheet__side {
    display: none;
  }

  .sheet__side[disabled] {
    opacity: 0.45;
  }

  /* .sheet__side is .btn.btn--plain, and .btn--plain:hover is (0,2,0) against
     this rule's (0,1,0) — without these the generic hover repaints Cancel a
     shade darker and strips Done of its tint altogether. */
  .sheet__side:hover {
    background: var(--fill);
    filter: brightness(1.18);
  }

  .sheet__side--done:hover {
    background: var(--tint);
    filter: brightness(1.12);
  }

  .sheet__side[disabled]:hover {
    background: var(--fill-secondary);
    filter: none;
  }

  /* An action sheet is a dialog on a pointer too ---------------------------
     Stacked full-width bars in two floating groups with a detached Cancel is
     a thumb pattern: it exists so the buttons land under a hand holding a
     phone. With a cursor it should be the same object as every other popup
     here — one panel, a title bar, a menu, and Cancel in a footer.

     Same trick as the dialog: the group holding the actions drops through so
     its rows become the panel's own. The Cancel group stays a real element,
     because the footer strip needs something to paint. */

  .scrim--action {
    align-items: center;
    padding: 2rem var(--margin);
  }

  .actionsheet {
    display: flex;
    flex-direction: column;
    gap: 0;
    max-width: 24rem;
    background: var(--bg-grouped-secondary);
    border: 1px solid var(--separator);
    border-radius: var(--radius-control);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    overflow: hidden;
  }

  .actionsheet__group:not(:has(.actionsheet__btn--cancel)) {
    display: contents;
  }

  .actionsheet__head {
    padding: 0.875rem var(--margin);
    border-block-end: 1px solid var(--separator);
    text-align: start;
  }

  .actionsheet__title {
    font-size: var(--text-headline);
    color: var(--label);
  }

  .actionsheet__btn {
    min-height: 2.75rem;
    padding: 0.5rem var(--margin);
    font-size: var(--text-callout);
    color: var(--label);
  }

  /* A menu doesn't rule between every item the way a stack of iOS bars does —
     but the destructive choice still gets separated from the safe ones, so
     "request a drop" never sits flush against "put it on the board". */
  .actionsheet__btn + .actionsheet__btn {
    box-shadow: none;
  }

  .actionsheet__btn--destructive {
    color: var(--red);
    margin-block-start: 0.375rem;
    padding-block-start: 0.875rem;
    box-shadow: 0 -1px 0 var(--separator);
  }

  /* The chevron is the quiet part of the row; the label is what's read. */
  .actionsheet__btn .chevron {
    color: var(--label-tertiary);
  }

  .actionsheet__group:has(.actionsheet__btn--cancel) {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    padding: 0.75rem var(--margin);
    background: var(--bg-grouped);
    /* The group's own iOS radius is clipped by the panel, but leaving it on
       means the strip corners fight the panel's if overflow ever changes. */
    border-radius: 0;
    border-block-start: 1px solid var(--separator);
  }

  .actionsheet__btn--cancel {
    width: auto;
    min-height: 2rem;
    padding: 0 1rem;
    border-radius: var(--radius-control);
    background: var(--fill-secondary);
    font-size: var(--text-subhead);
    font-weight: 500;
    color: var(--label);
  }

  .actionsheet__btn--cancel:hover {
    background: var(--fill);
    filter: brightness(1.18);
  }

  /* Alerts get room to breathe and buttons that read left-to-right — and the
     same panel, radius and footer strip as the other two, so a dialog, an
     action sheet and an alert are recognisably the same object. */
  .alert--modal {
    max-width: 24rem;
    text-align: start;
    border: 1px solid var(--separator);
    border-radius: var(--radius-control);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    overflow: hidden;
  }

  .alert--modal .alert__body {
    padding: 1.25rem 1.25rem 1rem;
  }

  .alert--modal .alert__actions {
    justify-content: flex-end;
    /* Without this the buttons stretch to the strip and come out 37px against
       the other two dialogs' 32. */
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem var(--margin);
    background: var(--bg-grouped);
    border-block-start: 1px solid var(--separator);
  }

  .alert--modal .alert__action {
    flex: 0 0 auto;
    min-height: 2rem;
    padding: 0 1rem;
    border-radius: var(--radius-control);
    background: var(--fill-secondary);
    /* Tint on a grey fill is the 3-and-change ratio this palette keeps
       producing at small sizes; the label carries it instead. */
    color: var(--label);
    font-size: var(--text-subhead);
    font-weight: 500;
  }

  .alert--modal .alert__action:hover {
    background: var(--fill);
    filter: brightness(1.18);
  }

  .alert--modal .alert__action + .alert__action {
    border-inline-start: 0;
    background: var(--tint);
    color: var(--on-tint);
    font-weight: 600;
  }

  .alert--modal .alert__action--destructive,
  .alert--modal .alert__action + .alert__action--destructive {
    background: var(--red);
    color: var(--on-red);
  }

  .alert--modal .alert__action + .alert__action:hover,
  .alert--modal .alert__action--destructive:hover {
    filter: brightness(1.12);
  }

  /* The three footer buttons read at 32px like macOS, and all three keep a
     44px target underneath — this width is pointer-first, not pointer-only. */
  .alert--modal .alert__action,
  .actionsheet__btn--cancel {
    position: relative;
  }

  .sheet__side::after,
  .alert--modal .alert__action::after,
  .actionsheet__btn--cancel::after {
    content: "";
    position: absolute;
    inset: 50% 0 auto;
    height: var(--tap);
    translate: 0 -50%;
  }

  /* Sheet bodies are denser at this size — the rows aren't thumb targets. */
  .sheet__body .row {
    min-height: 2.5rem;
    padding-block: 0.4375rem;
  }
}

/* Money is always right-aligned and tabular so a column of it reads. */
.dtable__money {
  text-align: end;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.row__value--money {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.row__value--total {
  font-size: var(--text-title3);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Parameters -----------------------------------------------------------------
   The transparency screen: the generator's own weights, and a bar for where
   somebody sits against a cap. */

/* Weights line up in one column. Nothing on that screen is good or bad news, so
   neither the positives nor the negatives take a colour. */
.row__value--weight {
  flex: none;
  min-width: 3.5rem;
  text-align: end;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* Decorative. Every meter sits beside the same figure written out in the row, so
   nothing depends on being able to read one — which is also why the fill is
   allowed the vivid tint. */
.meter {
  display: block;
  height: 0.375rem;
  margin-top: 0.5rem;
  border-radius: var(--radius-capsule);
  background: var(--fill-tertiary);
  overflow: hidden;
}

.meter__fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-capsule);
  background: var(--tint-vivid);
}

/* Over the cap. Orange, not red — over is a warning a manager signs off, not a
   failure. */
.meter__fill--over {
  background: var(--orange);
}

/* A link inside explanatory text. Underlined, because colour alone shouldn't be
   the thing that says "link" — and unlike a row, there's no chevron to say it. */
.group__footer a {
  color: var(--tint);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.group__footer a:active {
  opacity: 0.4;
}
