/* granite — the design system.
 *
 * An instrument, not an office system. Used under time pressure by people who
 * are not designers: the provider on a laptop with thirteen assistants and a
 * deadline, an assistant on a phone with one thumb between two shifts.
 *
 * Rules that hold everywhere:
 *   - One ground, layered surfaces. Depth comes from tone and a hairline, not
 *     from boxes drawn around things.
 *   - Colour is information, never decoration. Four status colours, no more, and
 *     every one dual-coded with a glyph or a word — some people using this are
 *     older, and the provider prints the whole thing in black and white.
 *   - One family, tabular numerals. Every number here is an hour, a date or a
 *     count, and hour columns that do not line up are unreadable.
 *   - Nothing animates that costs layout. Transitions are on colour, shadow,
 *     opacity and transform only.
 *
 * ⚠️ On translucency. `backdrop-filter` is beautiful and it is the most
 * expensive property in this file: every element carrying one is its own
 * composited layer that the compositor re-blurs on scroll. It is used in exactly
 * three places — the top bar, the phone tab bar, and the unsaved bar — all of
 * which are single, fixed, and small. It is never put on a card, a chip, a
 * calendar day or a board cell. The month board draws upwards of four hundred
 * cells; a blur on each is a slideshow.
 */

/* ---------- tokens ---------- */

:root {
  color-scheme: light;

  /* Ground and the surfaces that sit on it. */
  --bg: #f4f4f6;
  --surface: #ffffff;
  --surface-2: #fafafa;
  --surface-3: #f0f0f3;
  --surface-hover: #f5f5f7;

  /* Hairlines, not borders. A 1px line at 8 % reads as an edge without drawing
     a box; at full strength thirty of them on one screen read as a cage. */
  --hairline: rgba(18, 20, 24, 0.09);
  --hairline-strong: rgba(18, 20, 24, 0.17);

  --ink: #16181c;
  --ink-soft: #5b6068;
  /* ⚠️ 4.6:1 on white, was #8b9098 at 3.2:1 — and it is used exactly where the
     type is smallest: tab labels at 0.66rem, board day headers at 0.67rem. It
     still reads as faint through size and weight (granite#160). */
  --ink-faint: #6f757e;

  /* What sits ON a filled accent or missing surface. Buttons had a dark-mode
     override and nothing else did, so every other "selected" state was white on
     mint at 2.3:1 — including the count of shifts waiting (granite#160). Two
     tokens, so it cannot drift apart again. */
  --on-accent: #ffffff;
  --on-missing: #ffffff;

  /* AxEdge accent — the product sits in the brand's family without carrying the
     brand's name in front of the customer. */
  --accent: #39b57c;
  --accent-deep: #1c7a50;
  --accent-ink: #14603e;
  --accent-wash: #eaf7f0;
  --accent-tint: rgba(28, 122, 80, 0.10);

  /* The day grid behind the sign-in page (granite#189). */
  --door-line: rgba(28, 122, 80, 0.13);
  --door-fill: rgba(57, 181, 124, 0.13);
  --door-edge: rgba(57, 181, 124, 0.20);

  /* The four states. Nothing else gets a colour. */
  --attested: #1c7a50;
  --attested-wash: #e8f5ee;
  --attested-line: #a8d5c0;
  --waiting: #6b7079;
  --waiting-wash: #f1f1f3;
  --waiting-line: #d3d5da;
  --missing: #a85f00;
  --missing-wash: #fdf3e4;
  --leave: #4a5fb0;
  --leave-wash: #eceffa;
  --sick: #8a4aa8;
  --sick-wash: #f4ecf8;
  --missing-line: #e8c391;
  --conflict: #b3261e;
  --conflict-wash: #fdeeed;
  --conflict-line: #eab3ae;

  /* Radii. Bigger than a form library's defaults on purpose — the softness is
     most of what separates this from an admin panel. */
  --r-xs: 6px;
  --r-sm: 9px;
  --r: 13px;
  --r-lg: 18px;
  --r-xl: 24px;
  --r-pill: 999px;

  /* Two shadows stacked: a tight one that grounds the edge, a wide soft one
     that lifts it. A single shadow either floats or looks stuck on. */
  --shadow-1: 0 1px 1px rgba(16, 18, 22, 0.04), 0 2px 8px rgba(16, 18, 22, 0.045);
  --shadow-2: 0 1px 2px rgba(16, 18, 22, 0.06), 0 10px 28px rgba(16, 18, 22, 0.07);
  --shadow-3: 0 2px 6px rgba(16, 18, 22, 0.10), 0 18px 50px rgba(16, 18, 22, 0.16);
  --shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.6);

  /* Translucent chrome. Solid enough to read text through, sheer enough that
     the page moving underneath is visible. */
  --glass: rgba(255, 255, 255, 0.72);
  --glass-line: rgba(18, 20, 24, 0.08);

  /* Apple's curve: fast out of the gate, long settle. A linear ease reads as
     machinery; this reads as something moving because it was pushed. */
  --ease: cubic-bezier(0.32, 0.72, 0, 1);
  --fast: 120ms;
  --base: 200ms;

  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", ui-sans-serif,
          system-ui, "Segoe UI Variable", "Segoe UI", Inter, Roboto,
          "Helvetica Neue", Arial, sans-serif;

  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Dark is the same design at a different weight, not a second one: the tokens
   change and nothing else does. Two rules keep it honest — a surface is always
   lighter than the ground it sits on, and the four status colours keep their
   hue so the glyph and the colour still say the same thing. */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --bg: #0e1013;
    --surface: #17191d;
    --surface-2: #1c1f23;
    --surface-3: #23262b;
    --surface-hover: #202328;

    --hairline: rgba(255, 255, 255, 0.10);
    --hairline-strong: rgba(255, 255, 255, 0.19);

    --ink: #f0f1f4;
    --ink-soft: #a5abb4;
    --ink-faint: #8f959e;
    --on-accent: #06231a;
    --on-missing: #1a1206;

    --accent: #4ecb95;
    --accent-deep: #3fbf87;
    --accent-ink: #7fdcb0;
    --accent-wash: rgba(78, 203, 149, 0.13);
    --accent-tint: rgba(78, 203, 149, 0.16);

    /* The grid has to carry on a dark ground too, and a little brighter: the
       same alpha over black reads as nothing. */
    --door-line: rgba(111, 219, 166, 0.115);
    --door-fill: rgba(76, 201, 141, 0.12);
    --door-edge: rgba(76, 201, 141, 0.19);

    --attested: #4ecb95;
    --attested-wash: rgba(78, 203, 149, 0.13);
    --attested-line: rgba(78, 203, 149, 0.34);
    --waiting: #9aa1aa;
    --waiting-wash: rgba(255, 255, 255, 0.055);
    --waiting-line: rgba(255, 255, 255, 0.16);
    --missing: #e9a54a;
    --missing-wash: rgba(233, 165, 74, 0.14);
    --leave: #93a4ef;
    --leave-wash: rgba(74, 95, 176, 0.25);
    --sick: #c99be0;
    --sick-wash: rgba(138, 74, 168, 0.25);
    --missing-line: rgba(233, 165, 74, 0.36);
    --conflict: #ff6b60;
    --conflict-wash: rgba(255, 107, 96, 0.14);
    --conflict-line: rgba(255, 107, 96, 0.36);

    --shadow-1: 0 1px 1px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
    --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.45), 0 10px 28px rgba(0, 0, 0, 0.4);
    --shadow-3: 0 2px 6px rgba(0, 0, 0, 0.5), 0 18px 50px rgba(0, 0, 0, 0.6);
    --shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.05);

    --glass: rgba(23, 25, 29, 0.72);
    --glass-line: rgba(255, 255, 255, 0.09);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-size: 15px;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3 { font-weight: 600; margin: 0; }
h1 { font-size: 1.4rem; letter-spacing: -0.021em; }
h2 { font-size: 1.05rem; letter-spacing: -0.014em; }
h3 { font-size: 1.02rem; letter-spacing: -0.008em; }

a { color: var(--accent-ink); text-underline-offset: 2px; }

/* One ring, everywhere, and only for a keyboard. A mouse user who sees a focus
   ring on every click learns to ignore rings. */
:focus-visible {
  outline: 2px solid var(--accent-deep);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

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

/* ---------- shell ---------- */

.shell {
  max-width: 1120px;
  margin: 0 auto;
  /* Bottom padding clears the phone tab bar and the unsaved bar, both fixed. */
  padding: 22px 20px calc(96px + env(safe-area-inset-bottom));
}

/* ⚠️ One of three translucent elements in the file. Sticky, so the page slides
   under it — which is the whole reason it is translucent rather than solid. */
.topbar {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: 14px;
  padding: 11px 20px;
  border-bottom: 1px solid var(--glass-line);
  background: var(--glass);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
}

/* Where the blur is not available the bar has to be readable anyway, so it
   falls back to opaque rather than to a wash of text over content. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .topbar { background: var(--surface); }
}

.mark {
  font-weight: 600; letter-spacing: -0.02em; font-size: 0.98rem;
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ink); text-decoration: none;
}
.mark::before {
  content: ""; width: 9px; height: 9px; border-radius: 3px;
  background: var(--accent); flex: 0 0 auto;
  box-shadow: 0 0 0 3px var(--accent-tint);
}

.topbar .account { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.topbar .who { color: var(--ink-faint); font-size: 0.83rem; padding-left: 8px; }

/* How far below the top of the window something sticky has to stop. The top
   bar is there on a phone and before sign-in; once the menu is on the left it
   is not, and a sticky header that still kept 52px clear of it floated in the
   air above the page. */
:root { --chrome-top: 52px; }
@media (min-width: 721px) {
  :root:has(.sidebar) { --chrome-top: 0px; }
}

/* ---------- the menu ---------- */

/* Down the left on anything wider than a phone (granite#75). A column the page
   sits beside, never over: the board and the schedule are the widest things
   here, and a menu that covered them would be closed again every time.

   ⚠️ The width is not animated. Growing a grid column moves every cell of a
   thirty-one-day board, which is the kind of motion this file does not do. */
:root { --nav-w: 232px; }
:root[data-nav="narrow"] { --nav-w: 64px; }

.frame:has(> .sidebar) {
  display: grid; grid-template-columns: var(--nav-w) minmax(0, 1fr);
  min-height: 100vh;
}
.frame-main { min-width: 0; }
.frame:has(> .sidebar) .topbar { display: none; }

/* Beside the menu, not centred in what is left of the window. Centred, a wide
   screen put an empty band between the menu and the page on one side and the
   same band on the other, and the page read as belonging to neither. */
.frame:has(> .sidebar) .shell { margin-inline: 0; }

/* The schedule is a four-week grid of names and times, and every pixel the
   reading width held back was a name broken over two lines. It takes the
   width; pages of sentences keep theirs. */
.frame:has(> .sidebar) .shell:has(.sched-head) { max-width: none; }

.sidebar {
  position: sticky; top: 0; height: 100vh;
  overflow-y: auto; overflow-x: hidden;
  display: flex; flex-direction: column;
  padding: 12px 10px;
  background: var(--surface);
  border-right: 1px solid var(--hairline);
}
.sidebar-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 4px 4px 6px 10px; min-height: 40px;
}
.nav { display: flex; flex-direction: column; gap: 1px; }

.nav-toggle {
  appearance: none; border: 0; background: none; cursor: pointer;
  width: 30px; height: 30px; flex: 0 0 auto;
  display: grid; place-items: center;
  border-radius: var(--r-xs); color: var(--ink-faint);
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease);
}
.nav-toggle:hover { background: var(--surface-hover); color: var(--ink); }
.nav-toggle svg { width: 18px; height: 18px; }

/* Group names are labels, not links: small, spaced, and quiet, so the eye reads
   the items and uses the groups only to find its place. */
.nav-group {
  padding: 14px 10px 5px;
  font-size: 0.67rem; font-weight: 600; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-faint); white-space: nowrap;
}

.nav-item {
  appearance: none; border: 0; background: none; font: inherit; cursor: pointer;
  position: relative; width: 100%;
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; border-radius: var(--r-sm);
  color: var(--ink-soft); text-decoration: none; text-align: left;
  font-size: 0.875rem; font-weight: 500; white-space: nowrap;
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease);
}
.nav-item:hover { background: var(--surface-hover); color: var(--ink); }
.nav-item[aria-current="page"] { background: var(--accent-wash); color: var(--accent-ink); font-weight: 600; }
.nav-item svg { width: 18px; height: 18px; flex: 0 0 auto; }
.nav-label { overflow: hidden; text-overflow: ellipsis; }

/* How many shifts are waiting. The missing colour, because a shift nobody has
   confirmed is exactly what that colour means everywhere else. */
.nav-count {
  margin-left: auto; min-width: 20px; padding: 1px 7px;
  border-radius: var(--r-pill); text-align: center;
  background: var(--missing-wash); color: var(--missing);
  font-size: 0.7rem; font-weight: 700;
}

.sidebar-foot {
  margin-top: auto; padding-top: 10px;
  border-top: 1px solid var(--hairline);
  display: flex; flex-direction: column; gap: 1px;
}
.sidebar-foot form { margin: 0; }
.nav-who {
  padding: 4px 10px 2px; color: var(--ink-faint); font-size: 0.79rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Folded: icons only. Every item keeps its title, so pointing at one still
   says what it is; the count becomes a dot's worth of number on the icon. */
:root[data-nav="narrow"] .sidebar :is(.nav-label, .nav-group, .nav-who, .mark-text) { display: none; }
:root[data-nav="narrow"] .sidebar-head { flex-direction: column; padding: 4px 0 6px; gap: 10px; }
:root[data-nav="narrow"] .nav-group + .nav-item { margin-top: 14px; }
:root[data-nav="narrow"] .nav-item { justify-content: center; padding: 9px 0; }
:root[data-nav="narrow"] .nav-count {
  position: absolute; top: 1px; right: 5px;
  min-width: 16px; padding: 0 4px; font-size: 0.62rem; line-height: 16px;
}

.card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
  padding: 20px;
  margin-bottom: 16px;
}

/* A card that is only a container for one number or one sentence does not need
   the same weight as one holding a month of work. */
.card.flat { box-shadow: none; background: var(--surface-2); }

.muted { color: var(--ink-soft); }
.faint { color: var(--ink-faint); }
.small { font-size: 0.85rem; }

/* A heading and its actions, on one line, wrapping cleanly. Repeated by hand
   with inline styles on six screens before it was named. */
.head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 12px;
}
.head > :last-child.push { margin-left: auto; }
.push { margin-left: auto; }

/* ---------- the countdown: the primary object on the board ---------- */

.deadline { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }

/* Never shrinks: the one thing on this screen that must stay readable from
   across a room. */
.deadline .count {
  flex: 0 0 auto;
  font-size: 3rem; font-weight: 600; letter-spacing: -0.04em; line-height: 0.95;
  font-variant-numeric: tabular-nums;
}

/* Shrinks and wraps inside its own column instead of pushing the controls out.
   Left to grow, the sentence wrapped the whole toolbar onto a second line at a
   three-digit countdown and not at a two-digit one — fine at 59 days, broken at
   120. `min-width: 0` is the half that actually does it: a flex item will not
   shrink below its longest word without it. */
.deadline .what { flex: 1 1 240px; min-width: 0; color: var(--ink-soft); }

.deadline .count.urgent { color: var(--missing); }
.deadline .count.past { color: var(--conflict); }

/* ⚠️ Green means handed in, and nothing more. Not paid, not accepted, and not
   still correct — a figure that moves afterwards turns the row on Blanketter
   red again. The tick is deliberately not a number: there is no countdown left
   to run (granite#172). */
.deadline .count.filed { color: var(--attested); }

/* What of the month actually reached Försäkringskassan, under the countdown
   that cannot say it. */
.forms-filed {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--hairline);
}
.forms-filed .muted { min-width: 0; }

/* ---------- status ---------- */

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: var(--r-pill);
  font-size: 0.79rem; font-weight: 500; white-space: nowrap;
  border: 1px solid transparent;
}
.pill::before { font-size: 0.9em; }

.pill.attested { color: var(--attested); background: var(--attested-wash); border-color: var(--attested-line); }
.pill.attested::before { content: "✓"; }
.pill.waiting  { color: var(--waiting);  background: var(--waiting-wash);  border-color: var(--waiting-line); }
.pill.waiting::before  { content: "○"; }
.pill.missing  { color: var(--missing);  background: var(--missing-wash);  border-color: var(--missing-line); }
.pill.missing::before  { content: "△"; }
.pill.conflict { color: var(--conflict); background: var(--conflict-wash); border-color: var(--conflict-line); }
.pill.conflict::before { content: "✕"; }

/* ---------- month board ---------- */

.board-scroll {
  overflow-x: auto;
  /* Wide content scrolls inside its own box. The page body never does. */
  scrollbar-width: thin;
}

.board { border-collapse: separate; border-spacing: 0; width: 100%; }
.board th, .board td { padding: 0; }

.board th.day {
  font-size: 0.67rem; font-weight: 500; color: var(--ink-faint);
  text-align: center; padding-bottom: 5px; min-width: 24px;
}
.board th.day.weekend { color: var(--ink-soft); font-weight: 600; }

.board th.who {
  text-align: left; font-weight: 500; font-size: 0.85rem;
  padding-right: 14px; white-space: nowrap; position: sticky; left: 0;
  background: var(--surface); z-index: 1;
}

.board tr.user-row th.who { font-weight: 600; padding-top: 16px; }
.board tr.user-row td { padding-top: 16px; }

/* A cell is a day. Shape carries the state as well as colour, so the board
   survives a black-and-white printout and a colour-blind reader.
   ⚠️ No shadow and no transition here on purpose — there are hundreds. */
.cell {
  display: block; width: 22px; height: 22px; margin: 1px auto;
  border-radius: 6px; border: 1px solid var(--hairline);
  background: var(--waiting-wash);
  font-size: 0.7rem; line-height: 20px; text-align: center; color: var(--ink-faint);
}
.cell.none      { border-style: dashed; background: transparent; color: transparent; }
.cell.attested  { background: var(--attested-wash); border-color: var(--attested-line); color: var(--attested); }
.cell.attested::after { content: "✓"; }
.cell.waiting   { background: var(--waiting-wash);  border-color: var(--waiting-line); }
.cell.waiting::after   { content: "·"; }
.cell.missing   { background: var(--missing-wash);  border-color: var(--missing-line); color: var(--missing); }
.cell.missing::after   { content: "△"; }
.cell.conflict  { background: var(--conflict-wash); border-color: var(--conflict-line); color: var(--conflict); }
.cell.conflict::after  { content: "✕"; }
/* granite#81. Not worked: settled, and nothing covered — a heavier edge than
   missing, so the two read apart in black and white too. Confirmed with other
   times: still green, with a dot, because it is confirmed. */
.cell.notworked { background: var(--missing-wash); border: 2px solid var(--missing); color: var(--missing); line-height: 18px; }
.cell.notworked::after { content: "–"; }

/* Recovery codes (granite#88): printed on their own, nothing else on the page. */
.recovery-codes ol.codes {
  columns: 2; margin: 0; padding-left: 22px;
  font-variant-numeric: tabular-nums; line-height: 1.9;
}
.recovery-codes code { font-size: 1rem; letter-spacing: 0.08em; }
@media print {
  body * { visibility: hidden; }
  .recovery-codes, .recovery-codes * { visibility: visible; }
  .recovery-codes { position: absolute; inset: 0 auto auto 0; background: #fff; color: #000; border: 0; }
  .recovery-codes .no-print { display: none; }
}

/* Holiday and leave (granite#83): neither waiting nor missing, so a look of its
   own — stripes, in a colour no other state uses. */
.cell.absent {
  background: repeating-linear-gradient(135deg, var(--leave-wash) 0 4px, transparent 4px 8px);
  border-color: var(--leave); color: var(--leave);
}
/* A day off with no shift on it: the same stripes, lighter, and no border that
   reads as something to act on. */
.cell.absent-day {
  background: repeating-linear-gradient(135deg, var(--leave-wash) 0 3px, transparent 3px 6px);
  border: 1px dashed var(--leave);
}
.cal-entry.absent {
  border-left-color: var(--leave);
  background: repeating-linear-gradient(135deg, var(--leave-wash) 0 6px, transparent 6px 12px);
}
.pill.leave { color: var(--leave); background: var(--leave-wash); border-color: var(--leave); }

/* Long sick leave (granite#84): the same shapes as leave, in its own colour —
   it is paid differently and has a report attached. */
.cell.absent.sick {
  background: repeating-linear-gradient(135deg, var(--sick-wash) 0 4px, transparent 4px 8px);
  border-color: var(--sick); color: var(--sick);
}
.cell.absent-day.sick {
  background: repeating-linear-gradient(135deg, var(--sick-wash) 0 3px, transparent 3px 6px);
  border-color: var(--sick);
}
.cal-entry.absent.sick {
  border-left-color: var(--sick);
  background: repeating-linear-gradient(135deg, var(--sick-wash) 0 6px, transparent 6px 12px);
}
.pill.sick { color: var(--sick); background: var(--sick-wash); border-color: var(--sick); }
.leave-line { color: var(--leave); font-weight: 600; }
.note.leave-note { border-left-color: var(--leave); }
.absences { list-style: none; margin: 0; padding: 0; }
.absences > li {
  display: flex; gap: 12px; align-items: flex-start; justify-content: space-between; flex-wrap: wrap;
  padding: 10px 0;
}
.absences > li + li { border-top: 1px solid var(--hairline); }
.absences a { display: inline-block; margin: 4px 12px 0 0; color: var(--accent-ink); }
.cell.deviates { position: relative; }
.cell.deviates::before {
  content: ""; position: absolute; top: -3px; right: -3px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--missing); box-shadow: 0 0 0 2px var(--surface);
}

/* Whether each day is covered, above the people who cover it. */
.board tr.coverage-row th.who { color: var(--ink-soft); font-weight: 600; }
.cov {
  display: block; width: 22px; margin: 1px auto;
  border-radius: 5px; text-align: center;
  font-size: 0.62rem; font-weight: 700; line-height: 18px;
}
.cov.full  { color: var(--attested); }
.cov.short { background: var(--missing-wash); color: var(--missing); }
.cov.over  { background: var(--conflict-wash); color: var(--conflict); }
.cell.overlaps { position: relative; }
.cell.overlaps::before {
  content: ""; position: absolute; top: -3px; right: -3px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--conflict); box-shadow: 0 0 0 2px var(--surface);
}
.shortfalls.over { background: var(--conflict-wash); }
.shortfalls.over .sentence { color: var(--conflict); }

.shortfalls { background: var(--missing-wash); border-radius: var(--r); padding: 10px 14px; margin: 0 0 12px; }
.shortfalls .sentence { color: var(--missing); font-weight: 600; }
.shortfalls ul { margin: 4px 0 0; padding-left: 18px; }
.cell-compare { display: grid; grid-template-columns: auto 1fr auto; gap: 2px 12px; margin: 0 0 6px; }
.cell-compare .short { color: var(--missing); }

.legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 14px; }

/* ---------- conflicts ---------- */

.conflict-item {
  border-left: 3px solid var(--conflict); background: var(--conflict-wash);
  padding: 11px 14px; border-radius: 0 var(--r-sm) var(--r-sm) 0; margin-bottom: 8px;
}
.conflict-item.behind  { border-left-color: var(--missing); background: var(--missing-wash); }
.conflict-item.schedule { border-left-color: var(--waiting); background: var(--waiting-wash); }
.conflict-item .sentence { font-weight: 500; }

/* A clash that links somewhere should look like it does. */
a.conflict-item {
  display: block; text-decoration: none; color: inherit;
  transition: border-left-width var(--fast) var(--ease), padding-left var(--fast) var(--ease);
}
a.conflict-item:hover { border-left-width: 6px; padding-left: 11px; }
.conflict-go { color: var(--conflict); font-weight: 600; margin-top: 4px; }

/* ---------- an attestation history ---------- */

.history { list-style: none; margin: 0 0 12px; padding: 0; }
.history li {
  display: flex; gap: 10px; flex-wrap: wrap; align-items: baseline;
  padding: 8px 0; border-top: 1px solid var(--hairline);
}
.history li:first-child { border-top: 0; }

/* A signature another one replaced (granite#129). Struck through and dimmed,
   because it is still a named person's statement and is never deleted — but two
   signatures over the same hours must not read as equals. The pill beside it
   carries the word, so the meaning does not rest on the strike alone. */
.history li.superseded { color: var(--ink-faint); }
.history li.superseded > span:not(.pill) { text-decoration: line-through; }

/* ---------- controls ---------- */

.field { margin-bottom: 16px; }
.field > label,
.field > .label,
.label {
  display: block; font-size: 0.79rem; font-weight: 500;
  color: var(--ink-soft); margin-bottom: 5px;
}

/* A form as a grid rather than a row of floats. Fields keep their own width and
   wrap at a sane place instead of at whatever the flex algorithm decides. */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 12px 14px;
  margin-bottom: 14px;
}
.form-grid .field { margin-bottom: 0; }
.form-grid .field.narrow { grid-column: span 1; }
.form-grid .field.wide { grid-column: 1 / -1; }

/* One input look for every kind of input. Left at the browser default they sit
   a size apart from every button beside them, and a date box and a text box on
   the same row look like two different systems. */
/* ⚠️ No width here. These sit in two kinds of place — stacked in a .field, where
   they should fill it, and side by side in a .choices row, where a shift's two
   time boxes and the person beside them each need their own size. A blanket
   `width: 100%` put one control per line and made the shift form four rows tall. */
.input,
input.choice, select.choice, textarea.choice {
  appearance: none; -webkit-appearance: none;
  border: 1px solid var(--hairline-strong);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--r-sm);
  padding: 8px 11px;
  font: inherit; font-size: 0.92rem; font-weight: 500;
  min-height: 38px;
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.input:hover, input.choice:hover, select.choice:hover { border-color: var(--ink-faint); }
.input:focus, input.choice:focus, select.choice:focus {
  outline: none;
  border-color: var(--accent-deep);
  box-shadow: 0 0 0 3px var(--accent-tint);
}
.input::placeholder { color: var(--ink-faint); }

/* Filling its label is what a stacked field means. */
.field > .input, .field > input.choice, .field > select.choice,
.form-grid .input { width: 100%; }

/* The select needs its own arrow once appearance is gone. Drawn rather than
   loaded — an image request for a chevron is a request too many. */
select.input, select.choice {
  padding-right: 30px;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 14px center, right 9px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  color: var(--ink);
}

.choices { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }

/* A button that is a choice. Selected is filled, not merely tinted — "which one
   is on" was the question being asked, and a pale border does not answer it. */
.choice {
  appearance: none; border: 1px solid var(--hairline-strong); background: var(--surface);
  color: var(--ink); border-radius: var(--r-sm); padding: 7px 13px;
  font: inherit; font-size: 0.875rem; font-weight: 500; cursor: pointer; min-height: 38px;
  display: inline-flex; align-items: center; gap: 6px;
  transition: background-color var(--fast) var(--ease), border-color var(--fast) var(--ease),
              color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.choice:hover { border-color: var(--ink-faint); background: var(--surface-hover); }
.choice:active { transform: scale(0.98); }
.choice.selected {
  border-color: var(--accent-deep); background: var(--accent-deep);
  color: var(--on-accent); font-weight: 600;
}
.choice.selected .faint { color: color-mix(in srgb, var(--on-accent) 75%, transparent); }
.choice.selected::before { content: "✓"; font-size: 0.85em; }

/* ---------- segmented control ---------- */

/* Three views of one thing, in one control. Built as three buttons with a
   shared trough rather than three buttons in a row with negative margins, which
   is what it was — and which put a 1px seam through the selected one. */
.segmented {
  display: inline-flex; padding: 3px; gap: 3px;
  background: var(--surface-3); border-radius: var(--r);
  border: 1px solid var(--hairline);
}
.segmented > button {
  appearance: none; border: 0; background: none; color: var(--ink-soft);
  font: inherit; font-size: 0.85rem; font-weight: 500; cursor: pointer;
  padding: 6px 14px; min-height: 32px; border-radius: calc(var(--r) - 4px);
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease),
              box-shadow var(--fast) var(--ease);
}
.segmented > button:hover { color: var(--ink); }
/* Filled, like every other chosen thing here. A white pill on a pale trough
   was Apple's answer and the provider could not see which tab was open. */
.segmented > button[aria-pressed="true"] {
  background: var(--accent-deep); color: var(--on-accent);
  box-shadow: var(--shadow-1);
}

/* ---------- buttons ---------- */

/* ⚠️ `display` is not optional here. A <button> is inline-block by default and
   looks right without it; an <a class="btn"> stays `inline`, where min-height
   does not apply at all and vertical padding overflows the line box instead of
   pushing it apart — so the button renders on top of the line above it. It was
   struck through the sentence it belonged to on the month board's
   previous-month warning, and on every other link-shaped button that was not
   already inside a flex container. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  appearance: none; border: 1px solid transparent;
  background: var(--accent-deep);
  color: var(--on-accent); border-radius: var(--r-sm); padding: 9px 17px;
  font: inherit; font-size: 0.875rem; font-weight: 600; cursor: pointer; min-height: 38px;
  text-align: center; text-decoration: none;
  box-shadow: var(--shadow-1);
  transition: background-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease),
              transform var(--fast) var(--ease), opacity var(--fast) var(--ease);
}
.btn:hover { background: var(--accent-ink); box-shadow: var(--shadow-2); }
.btn:active { transform: scale(0.975); box-shadow: var(--shadow-1); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; transform: none; }

@media (prefers-color-scheme: dark) {
  /* Colour comes from --on-accent now; only the hover tint is dark-specific. */
  .btn:hover { background: var(--accent); }
}

.btn.secondary {
  background: var(--surface); color: var(--ink); border-color: var(--hairline-strong);
}
.btn.secondary:hover { background: var(--surface-hover); border-color: var(--ink-faint); }

/* Quieter than secondary: an action that is available, in a place where a
   second bordered button would compete with the one that matters. */
.btn.quiet {
  background: none; color: var(--ink-soft); border-color: transparent; box-shadow: none;
}
.btn.quiet:hover { background: var(--surface-3); color: var(--ink); box-shadow: none; }

.btn.small { min-height: 30px; padding: 4px 11px; font-size: 0.79rem; }

/* The confirming button carries the weight; cancel stays quiet. A destructive
   action that looks like every other button is how a benefit period disappears
   from one mis-click. */
.btn.danger { background: var(--conflict); border-color: transparent; color: #fff; }
.btn.danger:hover { background: var(--conflict); filter: brightness(0.92); }

/* A control that is only an icon. Needs a hit area a finger can find and a
   label a screen reader can read; it has no text of its own. */
.icon-btn {
  appearance: none; border: 0; background: none; color: var(--ink-faint);
  cursor: pointer; border-radius: var(--r-pill);
  width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.95rem; line-height: 1;
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease);
}
.icon-btn:hover { background: var(--surface-3); color: var(--ink); }

/* ---------- checkbox and switch ---------- */

/* Which one to use is a question about what the control means, not about how it
   looks: a checkbox is part of a form that gets submitted, a switch takes effect
   the moment it is flipped. Getting that backwards is why a settings page can
   feel like it did not save. */
.check {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: 0.875rem; color: var(--ink); cursor: pointer; user-select: none;
  min-height: 38px;
}
.check input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; margin: 0;
  width: 20px; height: 20px; flex: 0 0 auto;
  border: 1.5px solid var(--hairline-strong); border-radius: var(--r-xs);
  background: var(--surface); cursor: pointer; position: relative;
  transition: background-color var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.check input[type="checkbox"]:hover { border-color: var(--ink-faint); }
.check input[type="checkbox"]:checked {
  background: var(--accent-deep); border-color: var(--accent-deep);
}
/* Drawn, not a glyph: a font's checkmark sits differently in every family and
   at every size, and this one has to be centred in a 20px box. */
.check input[type="checkbox"]:checked::after {
  content: ""; position: absolute; left: 6px; top: 2px;
  width: 5px; height: 10px;
  border: solid var(--on-accent); border-width: 0 2px 2px 0;
  transform: rotate(43deg);
}

.switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; min-height: 38px; }
.switch input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; margin: 0;
  width: 44px; height: 26px; flex: 0 0 auto; border-radius: var(--r-pill);
  background: var(--hairline-strong); cursor: pointer; position: relative;
  transition: background-color var(--base) var(--ease);
}
.switch input[type="checkbox"]::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 22px; height: 22px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28);
  transition: transform var(--base) var(--ease);
}
.switch input[type="checkbox"]:checked { background: var(--accent-deep); }
.switch input[type="checkbox"]:checked::after { transform: translateX(18px); }
.switch .switch-text { display: flex; flex-direction: column; }
.switch .switch-text .small { color: var(--ink-soft); }

/* ---------- notes: warn, done, plain ---------- */

/* One shape, three colours, always in the same place on a card. The eye finds
   it without reading it, and the colour says which it is. */
.note, .warn, .done, .restaff {
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--waiting);
  background: var(--surface-2);
  border-radius: var(--r-sm);
  padding: 12px 14px; margin: 14px 0;
}
.note .sentence, .warn .sentence, .done .sentence { font-weight: 600; }
.note p:last-child, .warn p:last-child, .done p:last-child { margin-bottom: 0; }

.warn, .note.warn {
  border-color: var(--conflict-line); border-left-color: var(--conflict);
  background: var(--conflict-wash);
}
.warn .sentence, .note.warn .sentence { color: var(--conflict); }

.done, .note.done {
  border-color: var(--attested-line); border-left-color: var(--attested);
  background: var(--attested-wash);
}
.done .sentence, .note.done .sentence { color: var(--attested); }

.note.caution {
  border-color: var(--missing-line); border-left-color: var(--missing);
  background: var(--missing-wash);
}
.note.caution .sentence { color: var(--missing); }

/* The quiet counterpart: an action that is available rather than one that needs
   attention. Staffing a month again is ordinary maintenance. */
.restaff { display: flex; flex-direction: column; gap: 7px; }

/* ---------- open sign-in ---------- */

/* Authentication is off. Solid, full width, above everything, and with no way
   to dismiss it — this has to still be noticeable on the day the system is
   handed over, not just on the day it was switched on. */
.open-signin-banner {
  position: sticky; top: 0; z-index: 100;
  background: var(--conflict); color: #fff;
  font-weight: 700; font-size: 0.79rem; letter-spacing: 0.05em;
  text-align: center; padding: 7px 14px;
}

/* ---------- figures and meters ---------- */

.meter {
  height: 10px; border-radius: var(--r-pill);
  background: var(--surface-3); overflow: hidden;
  border: 1px solid var(--hairline);
}
.meter > span {
  display: block; height: 100%; background: var(--accent-deep);
  border-radius: var(--r-pill);
  transition: width var(--base) var(--ease);
}
.meter.over > span { background: var(--conflict); }

/* Two marks on one bar: the filled part is what has been attested, the tick is
   where the period is heading. A single bar cannot say both, and the gap
   between them is the whole question. */
.meter.outlook { position: relative; overflow: visible; }
.meter.outlook > span { overflow: hidden; }
.meter.outlook i {
  position: absolute; top: -4px; bottom: -4px; width: 2px; border-radius: 2px;
  background: var(--ink); opacity: 0.7;
}
.meter.outlook.over i { background: var(--conflict); opacity: 1; }

.figures { display: flex; gap: 28px; flex-wrap: wrap; margin: 12px 0 10px; }
.figures div { display: flex; flex-direction: column; }
.figures dt { font-size: 0.79rem; color: var(--ink-soft); }
.figures dd { margin: 0; font-size: 1.2rem; font-weight: 600; letter-spacing: -0.015em; }
.figures .sub { font-size: 0.75rem; color: var(--ink-faint); }

/* ---------- calendar ---------- */

/* The grid lines are the container's background showing through a 1px gap. One
   colour, one border, and no per-cell borders to double up at the seams. */
/* ⚠️ overflow: visible, not hidden. A box opened on a day in the last row
   extends below the grid, and hidden cut it off with the save button in the
   part nobody could reach. The corners are rounded on the head and the last
   row instead. */
.calendar {
  --cal-gap: 1px;
  background: var(--hairline);
  border: 1px solid var(--hairline);
  border-radius: var(--r); overflow: visible;
}
.calendar .cal-head { border-radius: var(--r) var(--r) 0 0; overflow: hidden; }
.calendar .cal-row:last-child { border-radius: 0 0 var(--r) var(--r); overflow: hidden; }
.calendar .cal-row:last-child:has(.pop) { overflow: visible; }

.cal-head, .cal-row {
  display: grid;
  grid-template-columns: 36px repeat(7, minmax(0, 1fr));
  gap: var(--cal-gap);
  background: var(--hairline);
}
.cal-head + .cal-row, .cal-row + .cal-row { margin-top: var(--cal-gap); }

.cal-weekday, .cal-week {
  background: var(--surface); padding: 7px 8px;
  font-size: 0.67rem; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--ink-faint);
}
.cal-head .cal-week { text-align: center; }

.cal-row .cal-week {
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 9px; font-weight: 500;
}

.cal-day {
  background: var(--surface); min-height: 94px; padding: 6px 7px 8px;
  display: flex; flex-direction: column; gap: 3px;
}
.cal-day.weekend { background: var(--surface-2); }
.cal-day.outside { opacity: 0.38; }
.cal-day.today { box-shadow: inset 0 0 0 2px var(--accent-deep); }

.cal-date { font-size: 0.79rem; font-weight: 600; color: var(--ink-soft); }
.cal-day.today .cal-date { color: var(--accent-ink); }

/* One shift. Small, quiet, and readable at a glance — a day with four of them
   still has to fit without the row growing taller than the screen. */
.cal-entry {
  display: block; text-decoration: none; color: inherit;
  border-left: 3px solid var(--waiting-line);
  border-radius: 0 var(--r-xs) var(--r-xs) 0;
  background: var(--surface-3); padding: 3px 6px;
  font-size: 0.75rem; line-height: 1.28;
}
a.cal-entry, .cal-entry.editable {
  transition: background-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
a.cal-entry:hover { background: var(--accent-wash); }
.cal-entry .who { display: block; font-weight: 600; }
.cal-entry .when { display: block; color: var(--ink-soft); }
.cal-entry.attested { border-left-color: var(--attested); }
.cal-entry.waiting  { border-left-color: var(--waiting); }
.cal-entry.missing  { border-left-color: var(--missing); background: var(--missing-wash); }
.cal-entry.conflict { border-left-color: var(--conflict); background: var(--conflict-wash); }
.cal-entry.activity { border-left-color: var(--accent-deep); background: var(--accent-wash); }

/* Coverage, on the schedule builder. Green is around the clock with nobody
   doubled — the bar the provider's own spreadsheet already clears. */
.cal-day.covered   { box-shadow: inset 3px 0 0 var(--attested); }
.cal-day.uncovered { box-shadow: inset 3px 0 0 var(--conflict); background: var(--conflict-wash); }
.cal-day.doubled   { box-shadow: inset 3px 0 0 var(--waiting); background: var(--waiting-wash); }
.cal-day.uncovered.weekend, .cal-day.doubled.weekend { background: var(--conflict-wash); }

.cover-gap    { float: right; color: var(--conflict); font-weight: 700; font-size: 0.7rem; }
.cover-double { float: right; color: var(--waiting); font-weight: 700; font-size: 0.7rem; }

/* A gap is a button, so it should not look like a paragraph. */
button.cover-gap {
  appearance: none; border: 0; background: none; cursor: pointer;
  font: inherit; font-weight: 700; font-size: 0.7rem; color: var(--conflict); padding: 0;
}
button.cover-gap:hover { text-decoration: underline; }

/* A shift placed against the daily rest rule. Allowed, and never quiet. */
.cal-entry.rest-breach { border-left-color: var(--missing); background: var(--missing-wash); }
.rest-flag { color: var(--missing); font-weight: 700; }

/* A locked shift, in the cycle grid. Quiet — most shifts are not locked, and a
   grid where the exceptions shout is a grid nobody reads. */
.cal-entry.pinned { border-left-color: var(--accent-deep); background: var(--accent-wash); }

.cal-entry.editable { cursor: pointer; }
.cal-entry.editable:hover { box-shadow: inset 0 0 0 1px var(--hairline-strong); }
.cal-entry.editing { box-shadow: inset 0 0 0 2px var(--accent-deep); }

.pin {
  appearance: none; border: 0; background: none; cursor: pointer;
  padding: 0 3px; font-size: 0.75rem; line-height: 1; opacity: 0.4;
  transition: opacity var(--fast) var(--ease);
}
.pin:hover { opacity: 0.9; }
.pin.on { opacity: 1; }

/* An activity on a day. Sits above the shifts, because it is why the day is
   staffed the way it is rather than one of the shifts itself.
   Name over time, the same two-line shape a shift uses: a day column is a
   seventh of the width, so "Ridning 12:00–14:00" on one nowrap line was simply
   cut off at the ellipsis — the time was there in the markup and invisible. */
.cal-activity {
  display: block; width: 100%; text-align: left;
  font-size: 0.67rem; line-height: 1.3;
  color: var(--accent-ink); background: var(--accent-wash);
  border: 0; border-radius: var(--r-xs); padding: 2px 5px; margin-bottom: 2px;
  font-family: inherit;
}
.cal-activity .what {
  display: block; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The time never wraps and never gets elided: it is four characters wider than
   the name and it is the half being asked for. */
.cal-activity .when { display: block; font-weight: 500; opacity: 0.85; white-space: nowrap; }

.cal-activity.extra { color: var(--missing); background: var(--missing-wash); }
/* On the name, not on the whole chip — appended to the block it would land
   under the time instead of after what it qualifies. */
.cal-activity.extra .what::after { content: " +1"; }

/* Clickable, because an activity is edited where it is seen. */
button.cal-activity {
  cursor: pointer;
  transition: box-shadow var(--fast) var(--ease);
}
button.cal-activity:hover { box-shadow: inset 0 0 0 1px currentColor; }
button.cal-activity.editing { box-shadow: inset 0 0 0 2px var(--accent-deep); }

.month-picker { display: flex; align-items: center; gap: 8px; }
.month-picker strong { min-width: 11ch; text-align: center; font-size: 0.92rem; }

.by-other { color: var(--missing); font-weight: 700; }

/* Three months across. Below a laptop's width they stack, because three
   seven-column grids on a phone is three unreadable grids. */
.three-up { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.three-month h3 { margin-bottom: 7px; display: flex; gap: 8px; align-items: baseline; }
.three-up .cal-day { min-height: 62px; }
.three-up .cal-entry { font-size: 0.65rem; }
.three-up .cal-head, .three-up .cal-row { grid-template-columns: 22px repeat(7, minmax(0, 1fr)); }

@media (max-width: 1100px) {
  .three-up { grid-template-columns: 1fr; }
  .three-up .cal-day { min-height: 94px; }
  .three-up .cal-entry { font-size: 0.75rem; }
}

@media (max-width: 720px) {
  /* Seven columns of nothing is worse than a list. Below this width the grid
     becomes one day per row, which is what a phone can actually show. */
  .cal-head { display: none; }
  .cal-row { grid-template-columns: 1fr; }
  .cal-row .cal-week {
    justify-content: flex-start; padding: 5px 9px; border-radius: 0;
  }
  .cal-day { min-height: 0; flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 7px; }
  .cal-day.outside { display: none; }
  .cal-date::after { content: " " attr(data-weekday); font-weight: 500; color: var(--ink-faint); }
  .cal-entry { flex: 1 1 100%; font-size: 0.79rem; padding: 6px 9px; }
  .cal-activity { flex: 1 1 100%; font-size: 0.75rem; padding: 5px 8px; }
}

/* ---------- a sheet: one thing, opened over the page it belongs to ---------- */

/* Not a modal. It sits in the flow next to what it is about, so the grid behind
   it is still readable while it is open — which is the point, since the thing
   being edited is on that grid. */
.sheet {
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  padding: 16px 18px;
  margin-bottom: 16px;
}
.sheet-head {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 14px;
}
.sheet-head h3 { flex: 1 1 auto; min-width: 0; }
.sheet-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--hairline);
}

/* Filling a hole, and editing one shift. Same shape as a sheet, quieter frame —
   they open inside a card rather than over one. */
.fill-panel {
  border: 1px solid var(--hairline-strong); border-radius: var(--r);
  padding: 15px; margin-bottom: 14px; background: var(--surface-2);
}
.fill-option {
  border-top: 1px solid var(--hairline); padding-top: 11px; margin-top: 11px;
  display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start;
}
.fill-shifts { display: flex; gap: 6px; flex-wrap: wrap; min-width: 190px; }
.fill-who { list-style: none; margin: 0; padding: 0; flex: 1 1 240px; font-size: 0.85rem; }
.fill-who li { display: flex; gap: 8px; align-items: baseline; }

.edit-shift { margin-bottom: 12px; }

/* ---------- a fenced-off group inside a card ---------- */

/* The cycle's shape decides what every tab under it is about. Unlabelled at the
   top of a page, two input boxes read as page settings rather than as the thing
   being edited. */
.panel {
  border: 1px solid var(--hairline); border-radius: var(--r);
  padding: 14px 16px; margin-top: 16px; background: var(--surface-2);
}
.panel-title {
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--ink-faint); margin: 0 0 10px;
}

/* Each week of the cycle gets its own heading and its own two buttons, because
   a week is a unit a provider builds. Inside the 36px week column there was no
   room for a confirmation that names what it is about to remove. */
.week-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 11px; background: var(--surface-2);
  border-top: 1px solid var(--hairline);
}
.calendar.cycle > .week-bar:first-of-type { border-top: 0; }
.week-bar strong { font-size: 0.79rem; }

/* ---------- chips: things you drag onto a day ---------- */

/* A rail of people or activities, above the grid they go onto. Each carries
   what it is short of — the question while building is "who still needs hours",
   and answering it in a tooltip means hovering thirteen names. */
.rail {
  display: flex; flex-wrap: wrap; gap: 7px; align-items: center;
  padding: 11px 13px; margin-bottom: 11px;
  border: 1px solid var(--hairline); border-radius: var(--r);
  background: var(--surface-2);
}
.rail > .rail-label {
  flex: 1 1 100%; margin-bottom: 2px;
  font-size: 0.79rem; color: var(--ink-soft);
}
.rail.picking { border-color: var(--accent-deep); background: var(--accent-wash); }

.chips { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }

.chip, .staff-chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  /* One chip, one line (granite#118). A name beside an hours badge and a lock
     is wider than the chip's natural width, and without this the contents wrap
     inside it — so one person in the list stood two rows tall and read as two
     people. The row of chips still wraps; the chip itself does not. */
  white-space: nowrap;
  padding: 6px 12px; border-radius: var(--r-pill); cursor: pointer;
  border: 1px solid var(--hairline-strong); background: var(--surface);
  color: var(--ink); font: inherit; font-size: 0.79rem; font-weight: 600;
  user-select: none;
  transition: background-color var(--fast) var(--ease), border-color var(--fast) var(--ease),
              color var(--fast) var(--ease), transform var(--fast) var(--ease),
              box-shadow var(--fast) var(--ease);
}
.chip:hover, .staff-chip:hover { border-color: var(--ink-faint); box-shadow: var(--shadow-1); }
.chip:active, .staff-chip:active { transform: scale(0.97); }
.staff-chip { cursor: grab; }
.staff-chip:active { cursor: grabbing; }

.chip-note, .chip .faint, .staff-chip .faint { font-weight: 500; color: var(--ink-faint); }

/* Short of contracted hours. */
.staff-chip.short { border-color: var(--accent-deep); color: var(--accent-ink); }

.chip.picked, .staff-chip.picked {
  border-color: var(--accent-deep); background: var(--accent-deep); color: var(--on-accent);
  box-shadow: var(--shadow-2);
}
.chip.picked .faint, .staff-chip.picked .faint, .chip.picked .chip-note {
  color: color-mix(in srgb, var(--on-accent) 78%, transparent);
}

/* The chip that starts something new rather than placing something that exists. */
.chip.new { border-style: dashed; color: var(--ink-soft); font-weight: 500; }

/* While something is picked, every day says it will take it. Drag highlights
   itself; a click needs to be told where it can land. */
.cal-day.awaiting-drop { cursor: copy; }
.cal-day.awaiting-drop:hover {
  box-shadow: inset 0 0 0 2px var(--accent-deep); background: var(--accent-wash);
}

/* ---------- unsaved work ---------- */

/* Follows the reader. The button at the top of the page is off screen by the
   time twenty-eight days have been built, and a generated cycle looks exactly
   like a stored one — so a whole cycle was built and lost in silence.
   ⚠️ Fixed, not sticky. Sticky does nothing for the last element on a page: its
   natural position IS the bottom, so it never has anywhere to stick to. */
.unsaved-bar {
  position: fixed; left: 0; right: 0;
  bottom: calc(env(safe-area-inset-bottom));
  z-index: 60;
  display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap;
  padding: 13px 20px calc(13px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--missing-line);
  background: color-mix(in srgb, var(--missing-wash) 88%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.09);
}
.unsaved-bar .sentence { font-weight: 600; color: var(--missing); }

/* Keeps the bar from covering the last row of whatever it is watching. */
.unsaved-space { height: 78px; }

@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .unsaved-bar { background: var(--missing-wash); }
}

/* ---------- saving ---------- */

.save-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* Under the button, where the click just happened — not at the top of a page
   the eye has already left. */
.saved { color: var(--attested); font-size: 0.85rem; font-weight: 600; }
.saved::before { content: "✓ "; }

/* ---------- confirming something destructive ---------- */

.confirm {
  display: inline-flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 8px 11px; border-radius: var(--r-sm);
  background: var(--conflict-wash); border: 1px solid var(--conflict-line);
}
/* The sentence on its own line, above the buttons (granite#117). Inside a menu
   the box is narrower than 42ch, so the text ran outside the red panel it was
   meant to sit in — a warning that overflows reads as a broken screen rather
   than as something to weigh. */
.confirm-what { font-size: 0.85rem; flex: 1 1 100%; max-width: 42ch; text-align: left; }

/* ---------- a list of rows ---------- */

/* A list, not a table. A table row that carries every setting for every person
   has no rows left to line up, and on a phone it stops being a table at all. */
.people { list-style: none; margin: 0; padding: 0; }
.people > li {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 2px;
}
.people > li + li { border-top: 1px solid var(--hairline); }
.people > li > :first-child { flex: 1 1 220px; min-width: 0; }
.people li.left { opacity: 0.55; }
.people li.editing { box-shadow: inset 3px 0 0 var(--accent-deep); background: var(--accent-wash); }

.people .person {
  display: grid; align-items: center; gap: 4px 16px;
  grid-template-columns: minmax(140px, 1.2fr) minmax(160px, 1.3fr) 2fr auto;
  padding: 12px 6px; text-decoration: none; color: inherit;
  border-radius: var(--r-sm);
  transition: background-color var(--fast) var(--ease);
}
.people .person:hover { background: var(--surface-hover); }

.people .person-name { font-weight: 500; display: flex; align-items: center; gap: 8px; }
.people .person-roles { display: flex; gap: 6px; flex-wrap: wrap; }
.people .person-go { white-space: nowrap; font-size: 0.85rem; color: var(--accent-ink); }
/* An email address is longer than the phone number this column was sized for. */
.people .person-number { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A row that is itself a link to somewhere. */
.row-link {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 13px 12px; margin: 0 -12px;
  text-decoration: none; color: inherit; border-radius: var(--r);
  transition: background-color var(--fast) var(--ease);
}
.row-link:hover { background: var(--surface-hover); }
.row-link .row-go { margin-left: auto; color: var(--ink-faint); font-size: 1.1rem; }

@media (max-width: 720px) {
  /* Stacked, because four columns on a phone is four columns of nothing. */
  .people .person {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "name go"
      "number go"
      "roles roles";
  }
  .people .person-name   { grid-area: name; }
  .people .person-number { grid-area: number; }
  .people .person-roles  { grid-area: roles; margin-top: 4px; }
  .people .person-go     { grid-area: go; align-self: center; }
}

/* ---------- links and quiet buttons ---------- */

.back-link {
  display: inline-flex; align-items: center; gap: 5px;
  text-decoration: none; margin-bottom: 6px;
  font-size: 0.85rem; color: var(--ink-soft);
}
.back-link:hover { color: var(--ink); text-decoration: underline; }

/* Text that opens something, without looking like a button in a list of them. */
.linklike {
  appearance: none; border: 0; background: none; padding: 0; margin: 0;
  font: inherit; color: inherit; cursor: pointer; text-align: left;
}
.linklike:hover { text-decoration: underline; }

/* Present for a screen reader, absent for the eye. Used where the page has a
   name it does not need to draw. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---------- the schedule workbench ---------- */

/* The bar that follows the reader down a four-week grid: who this is, which
   version, which tab, and whether what is on screen is what is stored. */
.sched-head {
  position: sticky; top: var(--chrome-top); z-index: 45;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 16px; margin: -22px -20px 16px;
  background: var(--glass);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--glass-line);
}
.sched-head h1 { font-size: 1.15rem; }

/* Whose schedule this is, as a switch (granite#76). The names read as the
   heading they replace — larger and heavier than the tabs beside them, so the
   two segmented controls are not mistaken for one. */
.who-switch > button { font-size: 0.98rem; font-weight: 600; padding-inline: 14px; }

/* Mina · Alla on the assistant's schedule (granite#77). Two links that look like
   one switch, because they are one schedule read two ways. */
.scope {
  display: inline-flex; gap: 2px; padding: 3px; margin-top: 10px;
  border-radius: var(--r-pill); background: var(--surface-3);
}
.scope a {
  padding: 6px 18px; border-radius: var(--r-pill);
  color: var(--ink-soft); text-decoration: none; font-weight: 600; font-size: 0.875rem; text-align: center;
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease);
}
.scope a:hover { color: var(--ink); }
.scope a[aria-current="page"] { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-1); }
@media (max-width: 640px) {
  .scope { display: flex; }
  .scope a { flex: 1 1 0; padding-block: 9px; }
}
.crumb { color: var(--ink-faint); font-size: 0.85rem; text-decoration: none; }
.crumb:hover { color: var(--ink); }

.vchip {
  display: inline-flex; align-items: center; gap: 6px;
  appearance: none; padding: 5px 10px; border-radius: var(--r-sm);
  border: 1px solid var(--hairline-strong); background: var(--surface);
  color: var(--ink); font: inherit; font-size: 0.83rem; font-weight: 500; cursor: pointer;
}
.vchip::after { content: "▾"; color: var(--ink-faint); font-size: 0.75rem; }

.save-state { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--attested); display: inline-block; }
.dot.unsaved { background: var(--missing); }

.workbench { display: grid; grid-template-columns: 230px minmax(0, 1fr); gap: 14px; align-items: start; }

/* ---------- a page catching up with its own address ----------

   ⚠️ The delay is the point. A load that lands in under a fifth of a second
   never dims at all, so changing month or service user looks instant instead of
   blinking; only a wait long enough to wonder about shows anything. Coming back
   has no delay, so the moment the figures are there the page is bright again.

   One rule for every screen that does this. Written per page it became four
   slightly different waits, which is how two screens end up disagreeing about
   what waiting looks like. */
[data-settles] { transition: opacity 120ms ease 0ms; }

[data-settles="waiting"] {
  opacity: .45;
  pointer-events: none;
  transition: opacity 140ms ease 200ms;
}

@media (prefers-reduced-motion: reduce) {
  [data-settles] { transition: none; }
}
.rail-col { position: sticky; top: calc(var(--chrome-top) + 66px); display: flex; flex-direction: column; gap: 12px; }
.rail-col .card { padding: 12px 13px; margin: 0; }
.rail-col.picking .card { border-color: var(--accent-deep); }
.rail-title {
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-faint); margin: 0 0 8px;
}
.hint { font-size: 0.79rem; color: var(--ink-faint); margin: 6px 0 0; }
.work { min-width: 0; }

.toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }

.strip {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 8px 12px; margin-bottom: 10px; font-size: 0.83rem;
  border: 1px solid var(--hairline); border-radius: var(--r-sm); background: var(--surface-2);
}
.strip .ok { color: var(--attested); font-weight: 600; }
.strip .warnt { color: var(--missing); font-weight: 600; }
.strip .badt { color: var(--conflict); font-weight: 600; }

.chip.small { padding: 3px 9px; font-size: 0.75rem; }
.cal-entry .lock { font-size: 0.62rem; opacity: 0.7; margin-left: 3px; }

/* ---------- popover ---------- */

/* Rendered inside the element it belongs to; that element is the anchor. */
.anchor { position: relative; }
.anchor.block { display: block; }

.pop-backdrop {
  position: fixed; inset: 0; z-index: 69;
  appearance: none; border: 0; background: transparent; cursor: default;
}
.pop {
  position: absolute; z-index: 70; top: calc(100% + 6px); left: 0;
  width: 340px; max-width: calc(100vw - 32px);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--hairline-strong); border-radius: var(--r-lg);
  box-shadow: var(--shadow-3); padding: 14px 16px;
  text-align: left; font-size: 0.92rem; font-weight: 400; cursor: default;
  animation: pop var(--base) var(--ease);
}
.pop.wide { width: 440px; }
.pop.menu { width: auto; min-width: 220px; padding: 6px; }
.anchor.flip > .pop, .anchor.flip .anchor > .pop { left: auto; right: 0; }
.pop.below { top: calc(100% + 8px); }
@keyframes pop { from { opacity: 0; transform: translateY(-4px) scale(0.98); } to { opacity: 1; transform: none; } }
.pop-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.pop-head h3 { flex: 1 1 auto; min-width: 0; }
.pop .field { margin-bottom: 10px; }
.pop .form-grid { margin-bottom: 8px; }
.pop-actions {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--hairline);
}
.pop .fill-option { border-top: 1px solid var(--hairline); padding-top: 10px; margin-top: 10px; display: flex; gap: 12px; align-items: flex-start; }
.pop .fill-who { flex: 1 1 auto; }
.pop .fill-who li { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; margin-bottom: 4px; }

.menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  appearance: none; border: 0; background: none; text-align: left;
  font: inherit; font-size: 0.875rem; color: var(--ink);
  padding: 8px 10px; border-radius: var(--r-sm); cursor: pointer; white-space: nowrap;
}
.menu-item:hover { background: var(--surface-hover); }
.menu-item.static { cursor: default; }
.menu-item.static:hover { background: none; }
.menu-item .k { margin-left: auto; color: var(--ink-faint); font-size: 0.75rem; padding-left: 14px; }
.pop.menu hr { border: 0; border-top: 1px solid var(--hairline); margin: 5px 0; }

@media (max-width: 900px) {
  .workbench { grid-template-columns: 1fr; }
  .rail-col { position: static; }
  .sched-head { margin: -16px -14px 14px; }
  .pop, .pop.wide { width: min(440px, calc(100vw - 32px)); }
}

/* A box opened from a control at the right edge of a toolbar. */
.anchor > .pop.flip-self { left: auto; right: 0; }
.cal-entry .lock { font-size: 0.62rem; opacity: 0.75; margin-left: 3px; }

/* ---------- one person, in sections ---------- */

.person-layout { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: 20px; align-items: start; }
.toc { position: sticky; top: calc(var(--chrome-top) + 12px); display: flex; flex-direction: column; gap: 2px; }
.toc button {
  appearance: none; border: 0; background: none; text-align: left; cursor: pointer;
  font: inherit; color: var(--ink-soft); font-size: 0.875rem;
  padding: 6px 10px; border-radius: var(--r-sm);
  transition: background-color var(--fast) var(--ease), color var(--fast) var(--ease);
}
.toc button:hover { background: var(--surface-3); color: var(--ink); }

.sect { padding: 18px 0; border-top: 1px solid var(--hairline); scroll-margin-top: 70px; }
.sect:first-child { border-top: 0; padding-top: 0; }
.sect h2 { margin-bottom: 4px; }
.sect .lead { color: var(--ink-soft); font-size: 0.875rem; margin: 0 0 12px; max-width: 68ch; }

/* Label to the left, control to the right — the shape a settings pane has. */
/* Against contract (granite#91): one block per service user, granted against
   what the schedule lays out. */
.contract-users { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; }
.contract-user {
  background: var(--surface-2); border: 1px solid var(--hairline);
  border-radius: var(--r); padding: 10px 12px;
}
.contract-user h3 { margin: 0 0 6px; font-size: 1.02rem; }
.contract-user dl { display: grid; grid-template-columns: 1fr auto; gap: 3px 12px; margin: 0; font-size: 0.875rem; }
.contract-user dt { color: var(--ink-soft); }
.contract-user dd { margin: 0; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }

.rows { display: grid; grid-template-columns: 160px minmax(0, 1fr); gap: 12px 16px; align-items: center; }
.rows .lbl { font-size: 0.875rem; color: var(--ink-soft); }
.opts { display: flex; flex-wrap: wrap; gap: 4px 20px; align-items: center; }

.radio { display: inline-flex; align-items: center; gap: 8px; font-size: 0.875rem; cursor: pointer; min-height: 36px; user-select: none; }
.radio input[type="radio"] {
  appearance: none; -webkit-appearance: none; margin: 0;
  width: 20px; height: 20px; flex: 0 0 auto; border-radius: 50%;
  border: 1.5px solid var(--hairline-strong); background: var(--surface); cursor: pointer;
  transition: border-color var(--fast) var(--ease), border-width var(--fast) var(--ease);
}
.radio input[type="radio"]:hover { border-color: var(--ink-faint); }
.radio input[type="radio"]:checked { border-color: var(--accent-deep); border-width: 6px; }

/* Follows the window, not the page. */
.savebar {
  position: sticky; bottom: 0; z-index: 30;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 20px; margin: 18px -20px -20px;
  background: var(--glass);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-top: 1px solid var(--hairline);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

@media (max-width: 720px) {
  .person-layout { grid-template-columns: 1fr; }
  .toc { position: static; flex-direction: row; flex-wrap: wrap; }
  .rows { grid-template-columns: 1fr; gap: 6px; }
  .rows .lbl { margin-top: 6px; }
  .savebar { margin: 16px -16px -16px; padding: 12px 16px; bottom: calc(60px + env(safe-area-inset-bottom)); }
}

/* ---------- the month board: reporting ---------- */

.banner {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 9px 14px; margin-bottom: 12px; font-size: 0.875rem;
  border: 1px solid var(--conflict-line); border-left: 3px solid var(--conflict);
  border-radius: var(--r-sm); background: var(--conflict-wash);
}
.banner .sentence { font-weight: 600; color: var(--conflict); flex: 1 1 240px; }

.su { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 12px; margin-bottom: 12px; }
.su .card { margin: 0; padding: 14px 16px; cursor: pointer; transition: box-shadow var(--fast) var(--ease); }
.su .card:hover { box-shadow: var(--shadow-2); }
.su .card.on { box-shadow: inset 0 0 0 2px var(--accent-deep), var(--shadow-1); }
.su .meter { margin: 4px 0 8px; }
.figs { display: flex; gap: 18px; flex-wrap: wrap; font-size: 0.79rem; color: var(--ink-soft); }
.figs b { display: block; color: var(--ink); font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em; }

/* A cell is a button now: it opens on what is behind it. */
button.cell { appearance: none; padding: 0; font-family: inherit; cursor: pointer; transition: transform var(--fast) var(--ease); }
button.cell:hover { transform: scale(1.12); }
.cell.editing { box-shadow: 0 0 0 2px var(--accent-deep); }
.board td.anchor .pop { top: calc(100% + 4px); }
/* ⚠️ Desktop only. Below 720px this switched off the scroll container while a
   popover was open: scrollLeft reset to 0, the 744px table spilled out of the
   card into page-level scroll, and the popover — absolute inside the cell —
   landed off the right edge for any day past about the tenth. On a phone the
   popover becomes a sheet instead, and the board keeps scrolling
   (granite#160). */
@media (min-width: 721px) {
  .board-scroll:has(.pop) { overflow: visible; }
}
.cell-shift + .cell-shift { border-top: 1px solid var(--hairline); padding-top: 8px; margin-top: 8px; }

/* ---------- an assistant's own days ---------- */

.day-card { display: flex; gap: 14px; align-items: flex-start; padding: 12px 16px; }
.day-date { text-align: center; min-width: 40px; }
.day-date .day-num { display: block; font-size: 1.35rem; font-weight: 600; line-height: 1; letter-spacing: -0.02em; }
.day-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.day-shift { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.day-shift strong { font-size: 1.05rem; font-variant-numeric: tabular-nums; }

/* The reader's own shift, on the whole schedule. Quiet — the point of that
   screen is everybody else. */
.cal-entry.mine { box-shadow: inset 0 0 0 1px var(--accent-deep); }

/* ---------- the phone tab bar ---------- */

/* Not shown at all above 720px, where the top bar has room for every
   destination. Below it, the top bar keeps the mark and the account and the
   navigation moves down to where a thumb is.
   ⚠️ The third and last translucent element in this file. */
.tabbar { display: none; }

@media (max-width: 720px) {
  .tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 55;
    display: flex; justify-content: space-around; align-items: stretch;
    padding: 6px 8px calc(6px + env(safe-area-inset-bottom));
    border-top: 1px solid var(--glass-line);
    background: var(--glass);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
  }

  .tabbar a, .tabbar button {
    appearance: none; border: 0; background: none;
    flex: 1 1 0; min-width: 0;
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    padding: 6px 2px; border-radius: var(--r-sm);
    text-decoration: none; color: var(--ink-faint);
    font: inherit; font-size: 0.67rem; font-weight: 600; cursor: pointer;
    transition: color var(--fast) var(--ease);
  }
  .tabbar svg { width: 22px; height: 22px; }
  .tabbar a, .tabbar button { position: relative; }
  .tabbar a[aria-current="page"], .tabbar button.open { color: var(--accent-ink); }

  /* How many shifts are waiting, on the Intyga tab. Solid rather than washed,
     because at this size a tinted number disappears into the bar behind it. */
  .tab-count {
    position: absolute; top: 2px; left: calc(50% + 6px);
    min-width: 16px; padding: 0 5px; border-radius: var(--r-pill);
    background: var(--missing); color: var(--on-missing);
    font-size: 0.62rem; font-weight: 700; line-height: 16px; text-align: center;
  }
  .tabbar a:active, .tabbar button:active { color: var(--ink); }
  .tabbar .tab-label {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* Everything that did not fit, opened from "Mer". A sheet from the bottom,
     which is where it was tapped. */
  .more-sheet {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 56;
    background: var(--surface);
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    box-shadow: var(--shadow-3);
    padding: 10px 14px calc(84px + env(safe-area-inset-bottom));
    animation: sheet-up var(--base) var(--ease);
  }
  /* The desk's menu at thumb size: the same groups and items, taller rows. It
     scrolls on its own if a short phone cannot show all of it. */
  .more-sheet { max-height: calc(100dvh - 24px); overflow-y: auto; }
  .more-sheet .nav-group { padding: 14px 10px 4px; }
  .more-sheet .nav-item { padding: 12px 10px; font-size: 1rem; }
  .more-sheet .nav-item svg { width: 20px; height: 20px; }
  .more-scrim {
    position: fixed; inset: 0; z-index: 54;
    background: rgba(0, 0, 0, 0.28);
    border: 0; padding: 0; width: 100%;
  }

  @keyframes sheet-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
  }

  /* A phone has no room for a column beside the page. The menu goes, the top
     bar comes back with the mark and the account, and the destinations are
     in the tab bar. */
  .frame:has(> .sidebar) { display: block; }
  .sidebar { display: none; }
  .frame:has(> .sidebar) .topbar { display: flex; }
}

/* The file's own words, not ours (granite#111). Quoted and in the reader's
   text colour so the heading reads as a quotation rather than as a second
   language in our own sentence. */
.quoted { color: var(--ink); font-style: normal; }

/* ---------- how far off contract (granite#119) ---------- */

/* Three readings, not one. Somebody scanning a column needs to know at a glance
   whether a figure is fine, worth watching, or worth acting on — and before this
   the only colour was for a shortfall, so every other row read as unremarkable
   whether it was on contract or well over it. */
.who.on-contract { color: var(--attested); }
.who.near-contract { color: var(--missing); }
.who.under-contract { color: var(--conflict); font-weight: 600; }

/* ---------- the board's key (granite#102) ---------- */

/* Below the grid, folded away. What it explains is three rows of colour that
   somebody learns in a week, so it must not sit on top of the thing being
   learnt. */
.key { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--hairline); }

.key-toggle {
  appearance: none; border: 0; background: none; cursor: pointer; font: inherit;
  font-size: 0.79rem; color: var(--ink-faint); padding: 4px 0;
  display: inline-flex; align-items: center; gap: 6px;
}
.key-toggle:hover { color: var(--ink); }
.key-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-xs); }
.key-toggle .chev { display: inline-block; transition: transform var(--fast) var(--ease); }
.key-toggle[aria-expanded="true"] .chev { transform: rotate(90deg); }

.key-group { margin-top: 10px; }
.key-group h4 {
  margin: 0 0 5px; font-size: 0.67rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 600;
}

.key-row { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.key-item { display: inline-flex; align-items: center; gap: 7px; font-size: 0.79rem; color: var(--ink-soft); }

/* The squares in the key are real board squares and must not react to a pointer
   the way the grid's do — there is nothing behind them to open. */
.key-item .cell { cursor: default; flex: none; }

@media (prefers-reduced-motion: reduce) {
  .key-toggle .chev { transition: none; }
}

/* ---------- the manual (granite#98) ---------- */

.manual { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 22px; align-items: start; }

.manual-toc {
  position: sticky; top: calc(var(--chrome-top) + 12px);
  display: flex; flex-direction: column; gap: 1px;
}

.manual-toc-head {
  font-size: 0.67rem; letter-spacing: 0.11em; text-transform: uppercase;
  color: var(--ink-faint); font-weight: 600; padding: 4px 8px 6px;
}

.manual-link {
  appearance: none; border: 0; background: none; text-align: left; cursor: pointer;
  font: inherit; font-size: 0.875rem; color: var(--ink-soft);
  padding: 6px 8px; border-radius: var(--r-sm);
}
.manual-link:hover { background: var(--surface-hover); color: var(--ink); }

.manual-section {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--r); padding: 18px 20px; margin-bottom: 12px;
  /* Cleared by the sticky chrome when the contents list scrolls to it. */
  scroll-margin-top: calc(var(--chrome-top) + 16px);
}

.manual-section h2 { margin: 0 0 8px; font-size: 1.05rem; }
.manual-section p { margin: 0 0 10px; color: var(--ink-soft); max-width: 62ch; }

.manual-note {
  border-left: 3px solid var(--accent); background: var(--accent-wash);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  padding: 9px 12px; margin-top: 12px; font-size: 0.875rem;
}

@media (max-width: 720px) {
  .manual { grid-template-columns: 1fr; gap: 12px; }
  /* Sticky is for a column beside the text. Above it, it would cover the text. */
  .manual-toc { position: static; }
}

/* ---------- the question mark (granite#97) ---------- */

/* Quiet until looked for. It sits beside a heading or a label and must not
   compete with it, so it borrows the faint ink and only takes the accent once
   it is the thing being read. */
.help-mark {
  appearance: none; cursor: pointer; font: inherit;
  width: 17px; height: 17px; padding: 0; margin-left: 6px;
  border-radius: 50%; border: 1px solid var(--hairline-strong);
  background: var(--surface-2); color: var(--ink-soft);
  font-size: 0.67rem; font-weight: 700; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  vertical-align: 1px; flex: none;
}

.help-mark:hover {
  background: var(--accent-wash); color: var(--accent-ink); border-color: var(--accent);
}

.help-mark[aria-expanded="true"] {
  background: var(--accent-deep); border-color: var(--accent-deep); color: var(--on-accent);
}

/* The mark is inline in a heading; the box it opens is not. */
.help-mark + .pop-backdrop + .pop { font-weight: 400; }

/* ---------- the guided tour (granite#96) ---------- */

/* The hole. One enormous shadow dims everything except the thing being
   explained — cheaper and steadier than four rectangles around it, and it
   follows the target with one transition instead of four. Fixed, because the
   measurement it is given is against the viewport. */
.tour-hole {
  position: fixed;
  z-index: 200;
  pointer-events: none;
  border-radius: var(--r);
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 9999px rgba(8, 10, 13, 0.62);
  transition: top var(--base) var(--ease), left var(--base) var(--ease),
              width var(--base) var(--ease), height var(--base) var(--ease);
}

.tour-card {
  position: fixed;
  z-index: 201;
  width: min(320px, calc(100vw - 32px));
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r);
  padding: 15px 16px 13px;
  box-shadow: 0 20px 48px -16px rgba(0, 0, 0, 0.5);
  transition: top var(--base) var(--ease), left var(--base) var(--ease);
}

.tour-card h2 { margin: 0 0 5px; font-size: 1rem; font-weight: 600; letter-spacing: -0.01em; }
.tour-card p { margin: 0 0 12px; font-size: 0.875rem; color: var(--ink-soft); }
/* Two rows, always: where you are on one, where you can go on the other. Four
   things on one line fits in English and breaks in Swedish — "Tillbaka" and
   "Inte nu" beside a counter wrapped to three lines each. The spacer is the
   break rather than a second element, so the markup stays one row. */
.tour-foot { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.tour-foot .spacer { flex: 1 0 100%; height: 0; }
.tour-step { font-size: 0.75rem; color: var(--ink-faint); font-variant-numeric: tabular-nums; }

.tour-dots { display: flex; gap: 4px; }
.tour-dots span { width: 5px; height: 5px; border-radius: 50%; background: var(--hairline-strong); }
.tour-dots span.on { background: var(--accent-deep); }

/* Never in the way of what it is pointing at. On a phone the card sits at the
   bottom, where it cannot cover a menu item being explained. */
@media (max-width: 640px) {
  .tour-card {
    left: 16px !important;
    right: 16px;
    top: auto !important;
    bottom: calc(80px + env(safe-area-inset-bottom));
    width: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tour-hole, .tour-card { transition: none; }
}

/* ---------- phone ---------- */

@media (max-width: 640px) {
  .shell { padding: 16px 14px calc(96px + env(safe-area-inset-bottom)); }
  .card { padding: 16px; border-radius: var(--r); }
  .deadline .count { font-size: 2.4rem; }

  /* A thumb, not a pointer. Worth the extra height only where that is true. */
  .choice, .btn, .input, input.choice, select.choice { min-height: 44px; }
  .btn { padding: 11px 18px; }
  /* Two to a row, and the last of an odd number is the same size as the
     rest. Left to grow it filled the whole row and read as a different kind
     of choice. */
  .choice { flex: 1 1 44%; max-width: calc(50% - 5px); justify-content: center; }
  .btn.small { min-height: 34px; }
  .segmented { width: 100%; }
  .segmented > button { flex: 1 1 0; justify-content: center; min-height: 38px; }
  .form-grid { grid-template-columns: 1fr 1fr; }
  .form-grid .field.wide, .form-grid .field:has(input:not([type])) { grid-column: 1 / -1; }
}

@media print {
  .topbar, .tabbar, .sidebar, .btn, .unsaved-bar, .open-signin-banner { display: none; }
  .frame:has(> .sidebar) { display: block; }
  body { background: #fff; }
  .card, .sheet { box-shadow: none; border-color: #999; break-inside: avoid; }
  .shell { padding: 0; max-width: none; }
}

/* Signing a form (granite#137).
   The preview is the generated PDF itself, tall enough to read a page of rather
   than to glance at — the reading is what gives the signature its meaning. */
/* The form to read before signing, one image per page.
   ⚠️ Not an embedded PDF: iOS Safari draws page one of one in an iframe and
   stops, and the record of hours is on page two (granite#149). A scroll box
   rather than the whole page inline, so the signing pad stays within reach of
   the thumb on a phone. */
.form-pages {
    max-height: 60vh;
    overflow-y: auto;
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r);
    background: var(--surface);
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    -webkit-overflow-scrolling: touch;
}

.form-pages img {
    width: 100%;
    height: auto;
    display: block;
    border: 1px solid var(--hairline-strong);
    border-radius: var(--r-xs);
}

/* Roughly the proportions of the box on the form, so what is drawn here needs
   little stretching to land there. touch-action: none is not optional — without
   it a finger scrolls the page instead of drawing, on every phone. */
.signing-pad {
    display: block;
    width: 100%;
    max-width: 420px;
    aspect-ratio: 4 / 1;
    border: 1px solid var(--hairline-strong);
    border-bottom-width: 2px;
    border-radius: var(--r);
    background: var(--surface);
    color: var(--ink);
    touch-action: none;
    cursor: crosshair;
}

@media (max-width: 620px) {
    .form-pages { max-height: 50vh; }
}

/* Signing in (granite#145).
   ⚠️ min(), not a flat 420px. An iPhone viewport is 390px wide, so a card fixed
   at 420 ran off the right edge and the fields with it — the first thing anybody
   sees of this system, on the device the assistants use. The margin keeps a
   gutter at every width. */
.signin-card {
    max-width: min(420px, 100%);
    margin: 40px auto;
}

@media (max-width: 480px) {
    .signin-card { margin: 20px auto; }
}

/* Two classes pages asked for and the stylesheet never defined (granite#156). */

/* The manual's column. Held to a reading measure rather than the card's width —
   the help text is prose, and prose set to 900px is not read. */
.manual-body {
    max-width: 62ch;
}

/* "Ladda om" in the crash bar. It sits on a yellow strip that is not on the
   token palette, so the colour is inherited rather than set. */
.reload {
    margin-left: 10px;
    font-weight: 600;
    text-decoration: underline;
}


/* ---------- the board on a phone (granite#160) ---------- */
@media (max-width: 720px) {
  /* The cell popover as a bottom sheet, the shape .more-sheet already uses.
     Fixed to the viewport, so where the cell sits in a 744px-wide scrolling
     table stops mattering. */
  .board td.anchor .pop {
    position: fixed;
    left: 0; right: 0; bottom: calc(60px + env(safe-area-inset-bottom)); top: auto;
    width: auto; max-width: none;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.25);
  }
}

@media (max-width: 640px) {
  /* 22px is half a touch target. 31 days at 30px scrolls inside the card,
     which is what the scroll container is for. */
  .cell { width: 28px; height: 28px; line-height: 26px; font-size: 0.75rem; }
}

/* ---------- the signing pad (granite#160) ---------- */
@media (max-width: 640px) {
  /* 4:1 on a 390px screen is about 82px of height — cramped for a finger. */
  .signing-pad { aspect-ratio: 5 / 2; }
}

/* A line to sign on, and a mark where one starts, the way a paper form has
   both. Drawn in the background so the canvas above it stays untouched. */
.signing-pad {
  background-image:
    linear-gradient(to right, var(--hairline-strong) 0 6px, transparent 6px),
    linear-gradient(to right, transparent 0 24px, var(--hairline) 24px);
  background-repeat: no-repeat;
  background-position: 18px 72%, 0 72%;
  background-size: 100% 1px, 100% 1px;
}

/* ---------- the board, reordered (granite#160) ---------- */

/* The service users as a strip to choose with, above the grid. What were cards
   of up to nine lines each, standing between the countdown and the instrument. */
.su-strip { display: flex; gap: 8px; overflow-x: auto; margin-bottom: 12px; padding-bottom: 2px; }
.su-pick {
  flex: 1 0 auto; min-width: 200px; text-align: left; cursor: pointer;
  display: grid; gap: 6px; padding: 10px 13px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--hairline); border-radius: var(--r);
  font: inherit;
}
.su-pick:hover { background: var(--surface-hover); }
.su-pick.on { border-color: var(--accent-deep); box-shadow: inset 3px 0 0 var(--accent-deep); }
.su-pick .nm { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 0.95rem; }
.su-pick .meter { margin: 0; }

/* A disclosure that reads as one, and matches the key's toggle. */
.fold {
  font: inherit; font-size: 0.83rem; font-weight: 600; cursor: pointer;
  background: transparent; border: 0; color: var(--accent-deep); padding: 2px 0;
}
.fold::after { content: " ›"; display: inline-block; transition: transform var(--fast) var(--ease); }
.fold[aria-expanded="true"]::after { transform: rotate(90deg); }

.hidden { display: none; }

/* The problem sentence, named rather than written out with inline colour in
   five places on this screen. */
.line-conflict { color: var(--conflict); font-weight: 600; }
.line-missing { color: var(--missing); font-weight: 600; }

/* --- foldable settings cards (granite#179, restyled granite#181) ---------- */

/* A card that folds is still a card. It keeps .card's radius, border and
   shadow untouched; only the padding moves inward, onto the head and the body,
   so the whole heading row — not a word in the middle of it — is the hit area.
   The first version dropped the radius by pulling the padding off the card and
   letting a square hover fill run to the edges: it read as a list row, not as
   the cards on every other page (Hans 2026-09-24). */
.foldcard { padding: 0; }

.fold-head {
  width: 100%;
  appearance: none;
  background: none;
  border: 0;
  /* Same shape as the card, so the hover fill stays inside the corners and the
     head alone is the card while it is folded. */
  border-radius: var(--r-lg);
  padding: 18px 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: inherit;
  transition: background var(--fast) var(--ease);
}

/* Open, the body carries the bottom corners. */
.foldcard.open .fold-head { border-radius: var(--r-lg) var(--r-lg) 0 0; }

.fold-head:hover { background: var(--surface-2); }
.fold-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* One glyph that turns, in a token of its own, rather than two characters
   swapping: the movement is what says the card opened. */
.fold-mark {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  color: var(--ink-soft);
  font-size: 0.85rem;
  line-height: 1;
  transition: transform var(--fast) var(--ease);
}
.foldcard.open .fold-mark {
  transform: rotate(90deg);
  background: var(--surface);
  border-color: var(--accent);
  color: var(--accent-deep);
}

/* An <h2>, weighted and sized like every other card's heading. */
.fold-name { flex: 0 0 auto; }

/* Takes the slack, so the warning sits hard right whatever the summary says. */
.fold-summary {
  flex: 1 1 auto;
  color: var(--ink-faint);
  font-size: 0.86rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fold-body { padding: 18px 20px 20px; border-top: 1px solid var(--hairline); }

@media (prefers-reduced-motion: reduce) {
  .fold-mark { transition: none; }
}

/* At phone width the summary would squeeze the name to nothing, so it drops to
   its own line and the warning keeps its place beside the name. */
@media (max-width: 620px) {
  .fold-head { flex-wrap: wrap; row-gap: 6px; padding: 16px; }
  .fold-summary { flex-basis: 100%; order: 3; padding-left: 34px; white-space: normal; }
  .fold-body { padding: 16px; }
}

/* --- the front door (granite#189) -----------------------------------------

   ⚠️ This page is static server-rendered, on purpose: signing in writes a
   cookie and a cookie needs a real HTTP response. So the background grid is
   rendered in Razor rather than built by script — no flash of an empty page,
   and it works with scripting off. */

/* ⚠️ One column now. The page carried two — an explanation on the left, the
   card on the right — until the copy moved out to a public site (granite#193).
   Everybody who reaches this page already has an account, so the card is the
   whole page and is centred rather than pushed to one side. */
.door {
  position: relative;
  min-height: calc(100vh - 80px);
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 30px;
  max-width: 1120px;
  margin: 0 auto;
  padding-block: 56px;
}

.door-only-mark .door-mark { margin-bottom: 0; }

/* The day grid from FK 3059: 31 rows, one per day of the month, shifts lit.
   The most characteristic shape in this domain, and the one pattern that could
   not sit on anybody else's sign-in page. */
.door-grid {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: grid;
  grid-auto-rows: 1fr;
  pointer-events: none;
  /* ⚠️ Centred now. The mask pointed at the top right because the copy sat on
     the left; with the card in the middle it left one corner busy and the rest
     bare. It frames the card instead — strongest at the top, fading before the
     edges, and thinned where the card sits so the form never competes with it. */
  -webkit-mask-image:
    radial-gradient(120% 78% at 50% -6%, #000 0%, rgba(0,0,0,.55) 45%, transparent 82%),
    radial-gradient(38% 34% at 50% 52%, transparent 0%, #000 78%);
  -webkit-mask-composite: source-in;
  mask-image:
    radial-gradient(120% 78% at 50% -6%, #000 0%, rgba(0,0,0,.55) 45%, transparent 82%),
    radial-gradient(38% 34% at 50% 52%, transparent 0%, #000 78%);
  mask-composite: intersect;
}
.door-grid i {
  border-right: 1px solid var(--door-line);
  border-bottom: 1px solid var(--door-line);
}
.door-grid i.on { background: var(--door-fill); }
.door-grid i.shift { background: var(--door-fill); box-shadow: inset 0 0 0 1px var(--door-edge); }

/* ⚠️ Direct children only, and the grid is deliberately NOT one of them. It
   lived inside .door at first and broke twice over: it took a grid cell, which
   pushed the card to a second row, and this rule gave it position:relative,
   which cancelled its own position:fixed and shrank it to a strip. */
.door > * { position: relative; z-index: 1; }

.door-mark {
  display: inline-flex; align-items: center; gap: 11px;
  font-weight: 640; font-size: 1.12rem; letter-spacing: -.017em;
  margin-bottom: 40px;
}
.door-mark .dot {
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--accent-deep); box-shadow: 0 0 0 4px var(--accent-wash);
}

.door h1 {
  margin: 0 0 18px;
  font-size: clamp(2.05rem, 4.6vw, 3.1rem);
  line-height: 1.04; letter-spacing: -.033em; font-weight: 660;
  text-wrap: balance; max-width: 13ch;
}
.door-lead { margin: 0 0 40px; font-size: 1.06rem; color: var(--ink-soft); max-width: 44ch; }

.door-does { list-style: none; margin: 0; padding: 0; display: grid; gap: 15px; max-width: 42ch; }
.door-does li { display: flex; align-items: center; gap: 14px; }
.door-does b { font-weight: 600; }
.door-does span { color: var(--ink-soft); }
.door-ic {
  flex: 0 0 auto; width: 38px; height: 38px;
  display: grid; place-items: center;
  border-radius: var(--r);
  background: var(--surface); border: 1px solid var(--hairline);
  color: var(--accent-deep);
}
.door-ic svg { display: block; }

/* The one thing the eye should land on: the only element here with a shadow. */
.door .signin-card { margin: 0; width: 100%; max-width: 430px; }

@media (max-width: 900px) {
  .door { gap: 26px; padding-block: 32px; min-height: 0; }
  .door .signin-card { max-width: none; }
  .door-grid {
    -webkit-mask-image: radial-gradient(160% 46% at 50% 0%, #000 0%, transparent 72%);
    -webkit-mask-composite: source-over;
    mask-image: radial-gradient(160% 46% at 50% 0%, #000 0%, transparent 72%);
    mask-composite: add;
  }
}
