/* ft-ui-contract: 3
 *
 * A version stamp the agent checks before serving a cached copy of this file.
 *
 * The widget prefers the stylesheet it last fetched from the tracker over the
 * one built into it, which is right -- a machine that has been online should
 * stay current. But nothing stopped a copy fetched from an older server being
 * served against a newer build forever: the widget would draw a new page with
 * an old stylesheet, which is not a failure anybody reports as a stylesheet
 * problem. bar.js has carried this stamp for exactly that reason; the file that
 * decides what everything looks like had none.
 */
:root {
  --bg: #EEF1F5;
  --surface: #FFFFFF;
  --surface-2: #F3F6F9;
  --border: #DCE2E9;
  --text: #141C25;
  --muted: #5C6B7A;
  /* Taken from the product's own mark. icon.png is #4878f8 with #3860d0 in
     its shading, and the accent had been purple since the first screen was
     drawn -- so every button, bar and avatar disagreed with the logo three
     inches away from it. White on this reads 5.7:1, comfortably past AA. */
  --accent: #2C73B8;
  --accent-hover: #255F97;
  --accent-soft: #EAF1F8;
  /* What white text sits on. In light this is the accent; in dark the
     accent is lightened for legibility as text, and white on it read as a
     washed-out button (1.9:1), so a filled control keeps a deeper blue. */
  --accent-fill: #2C73B8;
  --accent-fill-hover: #255F97;
  --good: #0F8A66;
  /* Used by the activity bars and share pills in the Web & app usage dialog.
     They were only ever written as var(--mid, #b9c0cc) fallbacks -- never
     declared -- so in dark mode those bars kept their light-mode colours
     while everything around them changed. The light values here are exactly
     the fallbacks, so light mode does not move. */
  --mid: #b9c0cc;
  --low: #d1534a;
  --warn: #B26412;
  --bad: #C94A4A;
  --unusual: #7A5AF0;
  --idle: #8696A4;
  --radius: 8px;
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 1px 3px rgba(16, 24, 40, 0.1);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  color-scheme: dark;
  /* Slate, with only a trace of blue in it.
     It was navy -- #171c33 behind #1e2442 -- and with a blue accent on top the
     whole screen was one colour at four brightnesses. A primary button had
     nothing to stand out against, which is what "change the buttons in dark
     mode" was pointing at: the buttons were fine, the ground under them was
     competing with them. Neutral ground, one saturated accent. */
  --bg: #0E141B;
  --surface: #141C25;
  --surface-2: #19232F;
  /* Lifted well clear of the surface. At #333b63 on #1e2442 an outlined button
     or an input was a rectangle you had to look for. */
  --border: #22303E;
  --text: #E9EFF5;
  --muted: #75899C;
  --accent: #3E8FD8;
  --accent-hover: #6FB4E8;
  --accent-soft: #16283C;
  --accent-fill: #3578C0;         /* white on this: 4.8:1 */
  --accent-fill-hover: #2F6CB0;   /* 5.4:1 */
  --good: #16A97F;
  --mid: #7c879b;
  --low: #ef6f68;
  --warn: #D07A1A;
  --bad: #E05D5D;
  --unusual: #8E74EC;
  --idle: #56697C;
  --shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 12px 30px -24px rgba(0, 0, 0, .8);
  }
}

/* Chosen dark, whatever the operating system prefers. The media query above
   covers "follow the system"; without this pair, picking Dark on a machine set
   to light does nothing at all, which reads as a broken switch. */
:root[data-theme="dark"] {
  color-scheme: dark;
  /* Slate, with only a trace of blue in it.
     It was navy -- #171c33 behind #1e2442 -- and with a blue accent on top the
     whole screen was one colour at four brightnesses. A primary button had
     nothing to stand out against, which is what "change the buttons in dark
     mode" was pointing at: the buttons were fine, the ground under them was
     competing with them. Neutral ground, one saturated accent. */
  --bg: #0E141B;
  --surface: #141C25;
  --surface-2: #19232F;
  /* Lifted well clear of the surface. At #333b63 on #1e2442 an outlined button
     or an input was a rectangle you had to look for. */
  --border: #22303E;
  --text: #E9EFF5;
  --muted: #75899C;
  --accent: #3E8FD8;
  --accent-hover: #6FB4E8;
  --accent-soft: #16283C;
  --accent-fill: #3578C0;         /* white on this: 4.8:1 */
  --accent-fill-hover: #2F6CB0;   /* 5.4:1 */
  --good: #16A97F;
  --mid: #7c879b;
  --low: #ef6f68;
  --warn: #D07A1A;
  --bad: #E05D5D;
  --unusual: #8E74EC;
  --idle: #56697C;
  --shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 12px 30px -24px rgba(0, 0, 0, .8);
}

* { box-sizing: border-box; }

/* Segoe UI Variable is what Windows 11 draws its own interface in, and it is
   noticeably better at small sizes than the older Segoe. Named first, with the
   usual stack behind it, so a machine that has it looks right and one that
   does not looks unchanged. Numbers are tabular everywhere a figure sits in a
   column or ticks upward -- a clock whose digits change width jitters. */
:root {
  --font-ui: "Inter", "Segoe UI Variable Text", "Segoe UI", -apple-system,
             BlinkMacSystemFont, Roboto, Ubuntu, "Helvetica Neue", sans-serif;
  --font-display: "Inter", "Segoe UI Variable Display", "Segoe UI",
             -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", SFMono-Regular, Consolas, monospace;

  /* Tells the browser to draw its own widgets -- dropdown arrows, spinners,
     scrollbars, the focus ring -- in the matching scheme. Without it a select
     in dark mode is a white slab with pale text on it, which is exactly how
     the settings screen looked. */
  color-scheme: light;
}

/* hidden means hidden.
 *
 * `[hidden]` is a browser default of the lowest possible specificity, so ANY
 * rule in this file that sets `display` on an element beats it -- and most
 * components here set one. The result is a hidden element that is still on
 * screen, which is a quiet and very confusing failure.
 *
 * Five separate patches for it already existed further down (.viewer-strip,
 * .vnav, .rep-inbox-count, .req-bulk, .rail-orglogo), each added after
 * somebody hit it. The sixth was the app bar's Start button: BUG-021 set
 * `toggle.hidden = true` once nothing was running, .appbar-play sets
 * `display: inline-flex`, and so the finished state went on offering Start
 * beside Start tracking for as long as that fix has been in the code.
 *
 * One rule, once, rather than a seventh. The !important is the point of it:
 * it has to outrank whatever a component says about its own display.
 */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 var(--font-ui);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: "cv05" 1;
}

h1, h2, h3, .dash-main, .stat-big, .plan-price {
  font-family: var(--font-display);
  letter-spacing: -.011em;
}

button, input, select, textarea { font: inherit; color: inherit; }

/* Every field, everywhere, follows the theme. These were browser defaults --
   fine on a light page, a white box with washed-out text on a dark one. */
input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="date"], input[type="time"],
input[type="search"], select, textarea {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  transition: border-color .16s ease, box-shadow .16s ease;
}
/* Focus is the border going accent, and -- for the keyboard only -- the 2px
   outline further down this file. There used to be a 3px glow here as well,
   on :focus rather than :focus-visible, so clicking a field with the mouse
   put a pale blue halo around it and clicking a picker put one around the
   picker and its search box at once. Two conventions for one state, both
   painted, which is what that looked like. */
input:focus-visible, select:focus-visible, textarea:focus-visible,
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
}
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }
input:disabled, select:disabled { opacity: .55; }
select option { background: var(--surface); color: var(--text); }
a { color: var(--accent); }

.hidden { display: none !important; }

/* ─── Login ──────────────────────────────────────────────────────── */
#login {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
}
.login-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 32px;
  width: min(380px, 100%);
}
.login-card h1 { margin: 0 0 4px; font-size: 20px; }
.login-card p.sub { margin: 0 0 24px; color: var(--muted); font-size: 14px; }
.field { margin-bottom: 14px; }
.field label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px; }
.field input, .field select {
  width: 100%;
  padding: 9px 11px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 7px;
}
.field input:focus, .field select:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

.btn {
  background: var(--accent-fill, var(--accent));
  color: #fff;
  border: 0;
  border-radius: 7px;
  padding: 9px 16px;
  cursor: pointer;
  font-weight: 500;
}
.btn:hover { background: var(--accent-fill-hover, var(--accent-hover, var(--accent))); }
/* Outlined buttons carry a faint fill on a dark ground. A transparent button on
   a dark surface is a border and nothing else, and the eye reads it as a label
   rather than something to press. */
.btn.ghost:hover { background: var(--surface-2); }
.btn:disabled { opacity: .55; cursor: default; }
.btn.ghost {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
}
.btn.small { padding: 5px 10px; font-size: 13px; }
.btn.danger { background: var(--bad); }

.error {
  color: var(--bad);
  font-size: 13px;
  margin-top: 10px;
  min-height: 18px;
}

/* ─── Shell ──────────────────────────────────────────────────────── */
header.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 20px;
  height: 56px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
}
.brand { font-weight: 650; letter-spacing: -0.01em; }
.brand span { color: var(--muted); font-weight: 400; }

nav.tabs { display: flex; gap: 2px; margin-left: 8px; flex-wrap: wrap; }
nav.tabs button {
  background: none;
  border: 0;
  padding: 7px 12px;
  border-radius: 7px;
  color: var(--muted);
  cursor: pointer;
}
nav.tabs button:hover { background: var(--surface-2); }
nav.tabs button.active { background: var(--accent-soft); color: var(--accent); font-weight: 500; }

.spacer { flex: 1; }
.whoami { font-size: 13px; color: var(--muted); }

main { padding: 20px; max-width: 1240px; margin: 0 auto; }

.toolbar {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.toolbar input[type="date"], .toolbar select {
  padding: 7px 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 7px;
}

/* ─── Cards & stats ──────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 18px;
  margin-bottom: 18px;
}
.card h2 { margin: 0 0 2px; font-size: 15px; }
.card p.hint { margin: 0 0 14px; color: var(--muted); font-size: 13px; }

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin-bottom: 18px;
}
.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  box-shadow: var(--shadow);
}
.stat .label {
  font-size: 12px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.stat .value { font-size: 26px; font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; }
.stat .value.good { color: var(--good); }
.stat .value.warn { color: var(--warn); }
.stat .value.muted { color: var(--muted); }

/* ─── Tables ─────────────────────────────────────────────────────── */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
  text-align: left;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
  font-weight: 500;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
td { padding: 9px 10px; border-bottom: 1px solid var(--border); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr.clickable { cursor: pointer; }
tbody tr.clickable:hover { background: var(--surface-2); }
td.num { text-align: right; font-variant-numeric: tabular-nums; }
td.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }

.pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}
.pill.working { background: rgba(26, 147, 111, .14); color: var(--good); }
.pill.idle    { background: rgba(199, 119, 0, .14);  color: var(--warn); }
.pill.offline { background: var(--surface-2);        color: var(--muted); }
.pill.no-agent{ background: rgba(192, 57, 43, .14);  color: var(--bad); }
.pill.productive   { background: rgba(26, 147, 111, .14); color: var(--good); }
.pill.neutral      { background: var(--surface-2);        color: var(--muted); }
.pill.unproductive { background: rgba(192, 57, 43, .14);  color: var(--bad); }

/* ─── Bars ───────────────────────────────────────────────────────── */
.bar {
  height: 7px;
  background: var(--surface-2);
  border-radius: 4px;
  overflow: hidden;
  min-width: 90px;
}
.bar > i { display: block; height: 100%; background: var(--accent); }
.bar > i.productive { background: var(--good); }
.bar > i.unproductive { background: var(--bad); }
.bar > i.neutral { background: var(--idle); }

/* ─── Timeline ───────────────────────────────────────────────────── */
.timeline {
  display: flex;
  height: 34px;
  border-radius: 6px;
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--border);
}
.timeline > i { display: block; height: 100%; min-width: 1px; }
.timeline > i.active { background: var(--good); }
.timeline > i.idle { background: var(--idle); opacity: .5; }
.timeline > i.gap { background: transparent; }
.axis {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--muted);
  margin-top: 5px;
  font-variant-numeric: tabular-nums;
}

.legend { display: flex; gap: 16px; font-size: 12px; color: var(--muted); margin-top: 10px; flex-wrap: wrap; }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.legend i.active { background: var(--good); }
.legend i.idle { background: var(--idle); opacity: .5; }

.note {
  background: var(--accent-soft);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 7px;
  padding: 11px 14px;
  font-size: 13px;
  color: var(--text);
  margin-bottom: 18px;
}

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 860px) { .two-col { grid-template-columns: 1fr; } }

.empty { color: var(--muted); text-align: center; padding: 26px; font-size: 14px; }

.token-box {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 11px;
  word-break: break-all;
  font-size: 13px;
  margin-top: 10px;
}

.row-form { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 14px; }
.row-form .field { margin-bottom: 0; }
.row-form .field input, .row-form .field select { min-width: 150px; }

.toast {
  position: fixed;
  right: 20px;
  bottom: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  padding: 11px 16px;
  box-shadow: var(--shadow);
  font-size: 14px;
  z-index: 50;
  max-width: 380px;
}
.toast.bad { border-left-color: var(--bad); }

/* ─── Segmented control ──────────────────────────────────────────── */
.seg { display: inline-flex; background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 2px; }
/* Three options reading "All", "Classified", "Not classified" are 79px
   past the edge of a phone, and an inline-flex row neither wraps nor
   shrinks. It scrolls inside its own box instead -- the answer the activity
   timeline already uses -- so the control stays one row and the page does
   not move. max-width rather than width: on a wide screen this changes
   nothing at all. */
@media (max-width: 640px) {
  .seg {
    max-width: 100%; overflow-x: auto;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    /* It WRAPS first, and only scrolls if wrapping still is not enough.
     *
     * Scrolling alone was the original answer here and the scrollbar is
     * hidden two lines above, so a control that did not fit gave no sign
     * that it did not fit. Measured on Web & app usage at 375px: the
     * All / Productive / Neutral / Unproductive / Not classified strip is
     * 426px of content in a 303px box, and "Not classified" was simply
     * absent from the screen. The owner has now photographed two of these
     * -- that one and the Custom range tab -- so the hiding is the part
     * that was wrong, not the width.
     *
     * A wrapped segmented control is still obviously one control; a
     * clipped one is a missing option. */
    flex-wrap: wrap;
  }
  .seg::-webkit-scrollbar { display: none; }
  .seg button { flex: none; white-space: nowrap; }
}
.seg button {
  background: none; border: 0; padding: 6px 14px; border-radius: 5px;
  color: var(--muted); cursor: pointer; font-size: 13px; font-weight: 500;
}
.seg button.active { background: var(--surface); color: var(--text); box-shadow: var(--shadow); }

/* ─── Web & app usage toolbar ────────────────────────────────────── */
/* Find, one verdict at a time, and how many at a time.
 *
 * Placed after .row-form and after .seg on purpose, not for tidiness: every
 * rule here has the same specificity as the one it narrows, so source order
 * is the whole of what makes it win. Moved above either block, the page size
 * select goes back to the 150px every row-form select gets and the five
 * filter buttons go back to Ranking and Timeline's padding. */
.apps-tools { margin-bottom: 12px; }
/* Room for an application name, which is what people type here. */
.apps-tools .field input[type="search"] { min-width: 230px; }
/* 10, 25 and 50 do not need 150px. */
.apps-tools .field select { min-width: 74px; }
/* "6 of 213 applications", on the baseline with the controls rather than
   floating above them, and pushed to the far end so it reads as a result of
   the row instead of another control in it. */
.apps-tools > .hint { margin-left: auto; padding-bottom: 9px; }
/* Five choices, not two: tighter than Ranking and Timeline, or the row wraps
   onto a second line on a laptop. */
.seg-sm button { padding: 6px 10px; font-size: 12.5px; }

/* Narrow: the count goes under the controls rather than squeezing them. */
@media (max-width: 900px) {
  .apps-tools > .hint { margin-left: 0; padding-bottom: 0; flex-basis: 100%; }
}

/* ─── Modals ─────────────────────────────────────────────────────── */
/* The backdrop, for the dialogs in index.html that hold a .modal-card. Not for
   a .modal inside a .modal-back: that is the other rule called .modal (the
   dialog box itself, under Profile below). Unscoped, this one reached every
   such box too -- fixed to the top-left corner, and made a centring grid that
   shrank its fields to their content -- in Add time, View profile, the date
   range and every dialog built the same way. :where() keeps the specificity of
   a plain .modal, so nothing that used to beat this rule stops beating it. */
.modal:not(:where(.modal-back > .modal)) {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(10, 14, 20, .55);
  display: grid; place-items: center; padding: 24px;
  backdrop-filter: blur(2px);
}
.modal-card {
  position: relative;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: 0 18px 48px rgba(0,0,0,.28);
  padding: 26px; width: min(440px, 100%);
  max-height: 88vh; overflow: auto;
}
.modal-card.wide { width: min(880px, 100%); padding: 0; }
.modal-card h2 { margin: 0 0 6px; font-size: 18px; }
.modal-x {
  position: absolute; top: 12px; right: 12px; z-index: 2;
  background: none; border: 0; font-size: 22px; line-height: 1;
  color: var(--muted); cursor: pointer; padding: 4px 8px;
}
.modal-x:hover { color: var(--text); }

/* ─── Date range picker ──────────────────────────────────────────── */
.dp { display: grid; grid-template-columns: 260px 1fr; min-height: 420px; }
@media (max-width: 720px) { .dp { grid-template-columns: 1fr; } }

.dp .cap {
  font-size: 10px; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 10px;
}
.dp-quick { border-right: 1px solid var(--border); padding: 24px 16px; }
@media (max-width: 720px) { .dp-quick { border-right: 0; border-bottom: 1px solid var(--border); } }
.dp-quick button {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; border-radius: 7px;
  padding: 10px 12px; color: var(--text); cursor: pointer; font-size: 13px;
}
.dp-quick button:hover { background: var(--surface-2); }
.dp-quick button.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }

.dp-cal { padding: 24px; display: flex; flex-direction: column; gap: 14px; }
.cal-head { display: flex; align-items: center; justify-content: center; gap: 18px; font-weight: 600; }
.cal-head button { background: none; border: 0; color: var(--muted); cursor: pointer; font-size: 17px; padding: 4px 10px; }
.cal-head button:hover { color: var(--text); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.cal-grid .dow {
  text-align: center; font-size: 11px; font-weight: 600;
  color: var(--muted); padding: 6px 0;
}
.cal-grid button {
  aspect-ratio: 1; border: 0; border-radius: 7px; background: none;
  color: var(--text); cursor: pointer; font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.cal-grid button:hover { background: var(--surface-2); }
.cal-grid button.other { color: var(--muted); opacity: .45; }
.cal-grid button.in-range { background: var(--accent-soft); border-radius: 0; }
.cal-grid button.edge { background: var(--accent-fill, var(--accent)); color: #fff; font-weight: 600; }
.cal-grid button:disabled { opacity: .25; cursor: default; }
.dp-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: auto; }

/* ─── Download modal ─────────────────────────────────────────────── */
.dl-icon { font-size: 34px; text-align: center; margin-bottom: 10px; color: var(--accent); }
.modal-card > h2 + p { margin: 0 0 4px; font-size: 15px; }
.dl-actions { display: flex; gap: 10px; justify-content: center; margin-top: 22px; }
.dl-actions .btn { min-width: 130px; justify-content: center; }

/* The sign-out question. Prefixed so-* on purpose: this stylesheet is one
   long file and generic names in it have collided four times already --
   twice with two different rules both called .modal. */
.so-head { display: flex; gap: 14px; align-items: flex-start; text-align: left; }
.so-head h2 { margin: 2px 0 6px; }
.so-head p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.5; }
.so-icon {
  flex: none; width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: var(--bad); background: color-mix(in srgb, var(--bad) 16%, transparent);
}
/* A named fallback, because color-mix is not everywhere and an icon with no
   background reads as a missing image rather than a quiet one. */
@supports not (background: color-mix(in srgb, red 16%, transparent)) {
  .so-icon { background: rgba(224, 79, 79, .16); }
}
.so-who {
  display: flex; gap: 12px; align-items: center; text-align: left;
  margin: 18px 0 0; padding: 12px 14px; border-radius: 10px;
  background: var(--surface-2, rgba(255,255,255,.04));
  border: 1px solid var(--line, rgba(255,255,255,.08));
}
.so-name { font-weight: 600; }
.so-who .sub { color: var(--muted); font-size: 13px; }
/* Narrow windows: the desktop app can be dragged small. */
@media (max-width: 420px) {
  .so-head { flex-direction: column; }
  .dl-actions { flex-direction: column-reverse; }
  .dl-actions .btn { width: 100%; }
}

/* ─── Member picker ──────────────────────────────────────────────── */
.mp-list { max-height: 340px; overflow: auto; display: flex; flex-direction: column; gap: 2px; }
.mp-list button {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: none; border: 0; border-radius: 8px; padding: 9px 10px;
  color: var(--text); cursor: pointer;
}
.mp-list button:hover { background: var(--surface-2); }
.mp-list .sub { font-size: 12px; color: var(--muted); }

/* ─── Screenshots ────────────────────────────────────────────────── */
.who { display: flex; align-items: center; gap: 14px; }
.avatar {
  width: 46px; height: 46px; border-radius: 50%; flex: none;
  background: var(--accent-soft); color: var(--accent);
  display: grid; place-items: center; font-weight: 600; font-size: 15px;
}

/* .day-head was declared twice, for two different things. This one styled a
   day LABEL -- small, spaced, uppercase -- and nothing carries that meaning any
   more; the only element with the class is the Screenshots header, which wants
   the flex layout further down (and its .who, h3 and .hint children prove it).
   It inherited the uppercase anyway, so a person's own name and the summary
   beneath it rendered as ALEX MORGAN and AVERAGE 50% INPUT (BUG-094).

   Removed rather than renamed: a second rule under a generic name in a file
   this long is how the collision happened. */
.hour-head { display: flex; align-items: baseline; gap: 10px; margin: 16px 0 10px; }
.hour-head .h { font-size: 17px; font-weight: 600; }
.hour-head .n { font-size: 12px; color: var(--muted); }

/* Timeline: one scrolling row per hour, like a filmstrip.
   Grid: everything wrapped, for scanning a whole day at once. */
.shots.timeline { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 10px; }
.shots.timeline .shot { flex: none; width: 268px; }
.shots.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: 12px; }

.shot {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 9px; overflow: hidden; box-shadow: var(--shadow); cursor: pointer;
}
.shot:hover { border-color: var(--accent); }
.shot .thumb { position: relative; aspect-ratio: 16/9; background: var(--surface-2); }
.shot .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shot .pip {
  position: absolute; top: 8px; right: 8px;
  width: 11px; height: 11px; border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(0,0,0,.35);
}
.shot .pip.high { background: var(--good); }
.shot .pip.mid  { background: var(--warn); }
.shot .pip.low  { background: var(--bad); }
.shot .meta {
  display: flex; align-items: center; justify-content: space-between;
  padding: 9px 11px; font-size: 13px;
}
.shot .meta .pct { font-weight: 600; font-variant-numeric: tabular-nums; }
.shot .meta .pct.high { color: var(--good); }
.shot .meta .pct.mid  { color: var(--warn); }
.shot .meta .pct.low  { color: var(--bad); }
.shot .app {
  padding: 0 11px 9px; font-size: 11px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Full-size viewer */
.viewer {
  position: fixed; inset: 0; z-index: 200; background: rgba(8, 11, 16, .94);
  display: flex; flex-direction: column; padding: 62px 22px 16px;
}
.viewer-body { flex: 1; display: flex; gap: 16px; min-height: 0; }
.viewer-main { flex: 1; position: relative; display: grid; place-items: center; min-width: 0; }

/* The stage clips, so zooming pans inside the frame instead of growing the
   overlay and pushing the controls off screen. */
.viewer-stage {
  max-width: 100%; max-height: 100%; overflow: hidden;
  border-radius: 8px; box-shadow: 0 22px 60px rgba(0,0,0,.5);
  display: grid; place-items: center;
}
.viewer-stage.zoomed { cursor: grab; }
.viewer-stage.zoomed:active { cursor: grabbing; }
.viewer img {
  max-width: 100%; max-height: 100%; display: block;
  transform-origin: center center; transition: transform .12s ease-out;
  user-select: none; -webkit-user-drag: none;
}
.viewer img.failed {
  width: min(60vw, 480px); height: 38vh;
  background: rgba(255,255,255,.05);
  border: 1px dashed rgba(255,255,255,.25); border-radius: 8px;
}

/* ── details ─────────────────────────────────────────────────────────── */
.viewer-side {
  width: 300px; flex: none; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 20px 22px;
}
.viewer-side h4 {
  margin: 22px 0 9px; font-size: 11px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--muted);
}
.vs-when { font-size: 16px; font-weight: 600; line-height: 1.35; }
.vs-sub { color: var(--muted); font-size: 12.5px; margin-top: 4px; }
.vs-track {
  height: 8px; border-radius: 5px; background: var(--surface-2);
  overflow: hidden; margin-bottom: 8px;
}
.vs-track > i { display: block; height: 100%; }
.vs-track > i.band-high { background: var(--good); }
.vs-track > i.band-mid  { background: var(--warn); }
.vs-track > i.band-low  { background: var(--bad); }
.vs-read { font-size: 14px; font-weight: 600; }
.vs-read.band-high { color: var(--good); }
.vs-read.band-mid  { color: var(--warn); }
.vs-read.band-low  { color: var(--bad); }
.vs-app { display: flex; gap: 10px; align-items: flex-start; font-size: 14px; }
.vs-dot {
  width: 9px; height: 9px; border-radius: 50%; background: var(--accent);
  flex: none; margin-top: 6px;
}
.vs-title {
  color: var(--muted); font-size: 12.5px; margin-top: 2px;
  overflow-wrap: anywhere;
}

/* ── filmstrip ───────────────────────────────────────────────────────── */
.viewer-strip {
  flex: none; height: 96px; margin-top: 14px;
  display: flex; gap: 8px; overflow-x: auto; overflow-y: hidden;
  padding: 4px 2px 8px;
  scrollbar-width: thin;
}
.viewer-strip[hidden] { display: none; }
.vthumb {
  flex: none; width: 132px; padding: 0; border-radius: 6px; cursor: pointer;
  background: rgba(255,255,255,.06); border: 2px solid transparent;
  display: flex; flex-direction: column; overflow: hidden;
}
.vthumb img {
  width: 100%; height: 62px; object-fit: cover; display: block;
  background: rgba(255,255,255,.04);
}
.vthumb span {
  font-size: 10.5px; color: #aeb6c2; padding: 3px 0 4px;
  font-variant-numeric: tabular-nums;
}
.vthumb.on { border-color: var(--accent); }
.vthumb.on span { color: #fff; }
.vthumb:hover { border-color: rgba(255,255,255,.35); }
.vthumb.failed img { background: rgba(255,80,80,.14); }
.vthumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── tools and arrows ────────────────────────────────────────────────── */
.viewer-tools {
  position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 4px;
  background: rgba(20, 24, 31, .92); border: 1px solid rgba(255,255,255,.14);
  border-radius: 10px; padding: 5px 7px; backdrop-filter: blur(6px); z-index: 2;
}
.vtool {
  background: none; border: 0; color: #e7eaee; cursor: pointer;
  padding: 7px 9px; border-radius: 7px; line-height: 0;
  display: grid; place-items: center;
}
.vtool:hover:not(:disabled) { background: rgba(255,255,255,.14); }
.vtool:disabled { opacity: .35; cursor: default; }
.vtool:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.vzoom {
  color: #aeb6c2; font-size: 12px; font-variant-numeric: tabular-nums;
  min-width: 44px; text-align: center; user-select: none;
}

.vnav {
  position: absolute; top: 50%; transform: translateY(-50%);
  background: rgba(20, 24, 31, .82); border: 1px solid rgba(255,255,255,.14);
  color: #e7eaee; width: 44px; height: 62px; border-radius: 10px;
  cursor: pointer; display: grid; place-items: center; z-index: 2;
}
.vnav.prev { left: 8px; }
.vnav.next { right: 8px; }
.vnav:hover:not(:disabled) { background: rgba(38, 44, 54, .95); }
.vnav:disabled { opacity: .28; cursor: default; }
.vnav[hidden] { display: none; }
.vnav:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.viewer .close {
  position: absolute; top: 16px; right: 22px; background: none; border: 0;
  color: #fff; font-size: 30px; cursor: pointer; opacity: .8; line-height: 1; z-index: 2;
}
.viewer .close:hover { opacity: 1; }

@media (max-width: 900px) {
  .viewer { padding: 56px 10px 10px; }
  .viewer-side { display: none; }
  .vnav { width: 36px; height: 50px; }
  .viewer-strip { height: 82px; }
  .vthumb { width: 104px; }
  .vthumb img { height: 50px; }
}

.thumb-missing {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--muted);
  font-size: 12px;
}

/* ══ Admin console and dashboard ══════════════════════════════════════ */

/* ── Settings: sidebar and panes ─────────────────────────────────────── */
.settings-shell {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: 22px;
  align-items: start;
}
.settings-shell > * { min-width: 0; }

/* Fixed beside the pane, the way the report tabs are.
 *
 * It was already `sticky`, and that did nothing: with fourteen entries
 * the list is about as tall as the window, and a sticky element with no
 * room to travel simply scrolls away with everything else. Capping its
 * height is what makes the pinning real.
 *
 * Yes, that can give it an inner scrollbar -- which is what QA filed
 * against the main rail (BUG-133). It is the right answer here and was
 * the wrong one there: the rail has nine items and fits, so its scrollbar
 * was pure overflow; this list does not fit on a short window, and the
 * alternative is not 'no scrollbar', it is 'the nav disappears'.
 */
.settings-nav {
  /* BUG-178: "Organisation profile is half hidden under the header".
     It pinned at 16px from the viewport while the app bar above it is
     71px tall and sticky, so the first item sat behind the bar. The
     bar went back to sticky on 19 October with the rail, which is what
     made this certain rather than occasional.

     --appbar-h is published by app.js from the real bar rather than
     guessed, the same offset the report tabs use. 16px of its own
     breathing room on top of that. */
  position: sticky; top: calc(var(--appbar-h, 71px) + 16px);
  /* The cap follows the offset: pushed down by the bar's height, the
     list would otherwise run that much further off the bottom. */
  max-height: calc(100vh - var(--appbar-h, 71px) - 150px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px;
}
/* Thin, and only while the pointer is over it, so a list that happens to
   fit shows nothing at all. */
.settings-nav { scrollbar-width: thin; }
.settings-nav::-webkit-scrollbar { width: 8px; }
.settings-nav::-webkit-scrollbar-thumb {
  background: transparent; border-radius: 8px;
}
.settings-nav:hover::-webkit-scrollbar-thumb { background: var(--border); }
.settings-nav .grp {
  font-size: 10px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
  padding: 14px 10px 6px;
}
.settings-nav .grp:first-child { padding-top: 6px; }
.settings-nav button {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; border-radius: 7px;
  padding: 8px 10px; margin: 1px 0;
  font: 500 13px/1.4 inherit; color: var(--text);
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.settings-nav button:hover { background: var(--surface-2); }
/* BUG-179: hover and selected read as the same thing. Measured: selected
   is rgb(234,241,248) and hover rgb(243,246,249) -- nine points apart on
   one channel, which nobody can see. The accent text and the weight were
   already different; what was missing is something that does not depend
   on comparing two pale greys at opposite ends of a list.

   An inset shadow rather than a border-left, so the text does not shift
   three pixels when an item becomes selected. */
.settings-nav button.active {
  box-shadow: inset 3px 0 0 var(--accent);
  background: var(--accent-soft); color: var(--accent); font-weight: 600;
}

.settings-panes { display: flex; flex-direction: column; gap: 18px; }
.pane-head h2 { margin: 0 0 4px; font-size: 20px; letter-spacing: -.01em; }
.pane-head .hint { margin: 0; max-width: min(72ch, 100%); }

/* ── A settings row ──────────────────────────────────────────────────── */
.srow {
  display: flex; align-items: flex-start; gap: 24px;
  padding: 15px 0;
  border-top: 1px solid var(--border);
}
.card > .srow:first-of-type { border-top: 0; padding-top: 4px; }
.srow-label { flex: 1; min-width: 0; }
.srow-title { font-size: 14px; font-weight: 600; }
.srow-help {
  font-size: 12.5px; color: var(--muted); margin-top: 4px;
  line-height: 1.55; max-width: min(64ch, 100%);
}
/* BUG-166: "the Back-date toggle is about 190px to the right of the
   others". It was, and so was "Stop tracking if not connected" -- the
   two rows with no "Configure per User" strip. The strip lives inside
   this box, so a row that has one is that much wider and its switch
   starts that much further left. Measured: 1089 against 1217.

   A fixed minimum with the contents pushed to the end gives the column
   one right edge whether or not a strip is in it, so every control
   lines up and the strip sits to its left. */
.srow-control {
  display: flex; align-items: center; gap: 8px;
  flex: none; padding-top: 1px;
  min-width: 260px; justify-content: flex-end; flex-wrap: wrap;
}
/* The strip goes on its own line rather than beside the control. Inside
   the same flex line it sits to the RIGHT of the switch, so a row that
   has one pushes its switch left by the strip's width -- which is the
   whole of BUG-166. Putting it on line two leaves the switch to
   right-align with every other row's, and keeps the reading order:
   control first, then what overrides it. */
.srow-control .ovr-strip { flex-basis: 100%; justify-content: flex-end; }
.srow-control input[type=number] { width: 88px; }
.srow-control select { min-width: 200px; max-width: 320px; }
.srow-control .unit { font-size: 12.5px; color: var(--muted); }

/* Rows that do not apply under the current settings. Faded rather than
   removed, so the screen does not jump as switches are flipped. */
.srow.dim { opacity: .38; }
.srow.dim .srow-control { pointer-events: none; }

/* ── Switch ──────────────────────────────────────────────────────────── */
.switch {
  position: relative; flex: none;
  width: 42px; height: 24px; padding: 0;
  border: 0; border-radius: 999px;
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--border);
  cursor: pointer;
  transition: background .18s ease, box-shadow .18s ease;
}
.switch i {
  position: absolute; top: 3px; left: 3px;
  width: 18px; height: 18px; border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(16,24,40,.3);
  transition: transform .18s cubic-bezier(.2,.7,.3,1);
}
.switch.on { background: var(--accent); box-shadow: none; }
.switch.on i { transform: translateX(18px); }
.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.switch-state { font-size: 13px; color: var(--muted); min-width: 92px; }

/* ── Weekday picker ──────────────────────────────────────────────────── */
.daypick { display: flex; gap: 4px; flex-wrap: wrap; }
.daypick button {
  border: 1px solid var(--border); background: var(--surface);
  color: var(--muted); border-radius: 6px;
  padding: 6px 9px; font: 600 11px/1 inherit; cursor: pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.daypick button.on {
  background: var(--accent-fill, var(--accent)); border-color: var(--accent-fill, var(--accent)); color: #fff;
}

/* ── Misc form bits ──────────────────────────────────────────────────── */
.linkrow { display: flex; gap: 8px; }
.linkrow input { flex: 1; min-width: 0; }
.mono { font-family: ui-monospace, Consolas, monospace; font-size: 12.5px; }
.actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.right { text-align: right; }
.right button + button { margin-left: 6px; }
button.small { padding: 5px 10px; font-size: 12px; }
.hint.bad { color: var(--bad); }
.card.warn { border-left: 3px solid var(--warn); }

/* ── Pills ───────────────────────────────────────────────────────────── */
.pill {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600;
  background: var(--surface-2); color: var(--muted);
}
/* The colours are declared once, above, in tokens. This pair restated them as
   light-mode literals and -- being later -- won, so a productive pill stayed
   the same green in dark mode while the text around it changed. Only the
   backgrounds, which are deliberately different here, remain. */
.pill.productive  { background: rgba(70,185,141,.16); }
.pill.unproductive{ background: rgba(229,117,101,.16); }
.pill.blacklisted { background: rgba(16,24,40,.10);    color: var(--muted); }
.pill.paid   { background: rgba(70,185,141,.16); color: #2f8e69; }
.pill.open   { background: rgba(224,162,68,.18); color: #a5701c; }
.pill.draft  { background: var(--surface-2);     color: var(--muted); }
.pill.void   { background: rgba(16,24,40,.10);   color: var(--muted); }

/* ── Stats strip ─────────────────────────────────────────────────────── */
.stats { display: flex; flex-wrap: wrap; gap: 28px; margin: 6px 0 4px; }
.stat-label {
  font-size: 10px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.stat-value {
  font: 600 18px/1.2 ui-monospace, Consolas, monospace;
  font-variant-numeric: tabular-nums; margin-top: 4px;
}
/* Words, not figures. The monospace above is for numbers that line up in a
   column -- a name and an email address in it read as a database dump, which
   is what was reported. Text values take the page's own face; the three
   figure strips elsewhere keep theirs. */
.stat-value.text {
  font: 600 15px/1.35 inherit;
  font-family: inherit;
  letter-spacing: 0;
}

/* The line under each figure. Added with the storage panel, which used to walk
   whatever the endpoint returned and String() every value -- putting
   "[object Object]" under the heading DISK, because disk describes a volume
   rather than being a number. Named figures need a place to say what they
   are of.

   This rule was written INSIDE .stat-value's braces, which under CSS nesting
   means `.stat-value .stat-note` -- and the markup makes the note a SIBLING of
   the value (admin.js: `s.append(l, val, sub)`), never a child. So it matched
   nothing and the storage panel's notes have rendered unstyled since the panel
   was built. Nobody logged it; it was found while reading this block for
   something else. */
.stat-note {
  font-size: 12px;
  color: var(--muted);
  margin-top: 2px;
  line-height: 1.35;
}

/* ── Plans ───────────────────────────────────────────────────────────── */
.plans {
  display: grid; gap: 14px; margin-top: 8px;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.plan {
  border: 1px solid var(--border); border-radius: 10px; padding: 18px;
  display: flex; flex-direction: column; gap: 6px;
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.plan:hover { transform: translateY(-2px); box-shadow: 0 8px 22px rgba(16,24,40,.08); }
.plan.current { border-color: var(--accent); background: var(--accent-soft); }
.plan-name { font-weight: 700; font-size: 15px; }
.plan-price { font: 700 22px/1.2 inherit; letter-spacing: -.02em; }
.plan .hint { flex: 1; margin: 0; }
.plan-current {
  font-size: 12px; font-weight: 600; color: var(--accent);
  padding: 8px 0 0;
}

/* ── Dashboard ───────────────────────────────────────────────────────── */
.dash-cards {
  display: grid; gap: 14px; margin-bottom: 18px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.dash-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 18px 20px; border-top: 3px solid var(--border);
  min-width: 0;
}
.dash-card.a { border-top-color: #6c9bff; }
.dash-card.b { border-top-color: #46b98d; }
.dash-card.c { border-top-color: #57bcd6; }
.dash-card.d { border-top-color: #a97fe0; }
.dash-card.warn { border-top-color: var(--warn); }
.dash-title {
  font-size: 10px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
}
/* A name is not a measure.
 *
 * The `font` shorthand below sets the family as well as the size, which is
 * right for the clocks and totals every other card puts here -- and wrong for
 * the organisation's name, which came out in Consolas beside headings in the
 * display face (BUG-034). `.text` is the same modifier the Account page uses
 * on .stat-value for the same reason. */
.dash-main.text { font-family: inherit; }

/* Worked time no application accounts for. The muted surface rather than a
   judgement colour: it is not a verdict on the time, it is the absence of
   one. */
.hm-split i.none { background: var(--surface-2); }

/* The button inside a flash note. Underlined rather than boxed: the note is
   already a panel, and a second border inside it reads as two things. */
.save-flash { display: flex; align-items: center; gap: 12px; }
.flash-go {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
  white-space: nowrap;
}
.flash-go:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.dash-main {
  font: 600 26px/1.15 ui-monospace, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  margin: 10px 0 12px; letter-spacing: -.01em;
  overflow-wrap: anywhere;
}
.dash-row {
  display: flex; justify-content: space-between; gap: 12px;
  font-size: 13px; color: var(--muted); padding: 3px 0;
}
.dash-row b { color: var(--text); font-variant-numeric: tabular-nums; }

.dash-stats {
  display: grid; gap: 0; margin-bottom: 18px;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
}
.dash-stat { padding: 18px 20px; border-left: 1px solid var(--border); min-width: 0; }
/* The sentence under the four tiles saying what their bars are drawn against.
   .dash-stats is an auto-fit grid, so without the full-row span this lands in
   a column and reads as a fifth tile. */
/* A row's explanation, shown rather than hidden behind a hover. */
.dash-rownote {
  font-size: 11.5px; line-height: 1.35; color: var(--muted);
  margin: -2px 0 6px; padding-left: 2px;
}
.dash-scale {
  grid-column: 1 / -1;
  margin: 0; padding: 10px 20px 12px;
  border-top: 1px solid var(--border);
  font-size: 12px; line-height: 1.4; color: var(--muted);
}
.dash-stat:first-child { border-left: 0; }
.stat-big {
  font: 700 24px/1.2 inherit; letter-spacing: -.02em; margin: 6px 0 2px;
}

.dash-charts {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
.chart-card { min-width: 0; }

/* Bars are elements, not a canvas: readable at any zoom, and no drawing
   library for what is two numbers per bucket. */
.chart {
  display: flex; align-items: flex-end; gap: 2px;
  height: 190px; margin-top: 14px;
  padding-bottom: 18px; position: relative;
  overflow-x: auto;
}
.chart-col {
  flex: 1 1 0; min-width: 6px;
  display: flex; flex-direction: column; align-items: center;
  height: 100%;
}
.chart-stack {
  flex: 1; width: 100%;
  display: flex; flex-direction: column; justify-content: flex-end;
  border-radius: 3px 3px 0 0; overflow: hidden;
}
.chart-stack i { display: block; width: 100%; transition: height .4s cubic-bezier(.2,.7,.3,1); }
.chart-stack i.work { background: var(--accent); }
.chart-stack i.brk  { background: var(--warn); }
.chart-col:hover .chart-stack i.work { filter: brightness(1.1); }
.chart-label {
  font-size: 10px; color: var(--muted); margin-top: 6px;
  height: 12px; white-space: nowrap;
}
.chart-legend {
  display: flex; gap: 16px; font-size: 12px; color: var(--muted); margin-top: 4px;
}
.chart-legend .sw {
  display: inline-block; width: 9px; height: 9px;
  border-radius: 2px; margin-right: 6px;
}
.chart-legend .sw.work { background: var(--accent); }
.chart-legend .sw.brk  { background: var(--warn); }

/* ── Save confirmation ───────────────────────────────────────────────── */
.save-flash {
  position: fixed; bottom: 22px; left: 50%;
  transform: translate(-50%, 14px);
  background: var(--text); color: var(--surface);
  padding: 9px 18px; border-radius: 999px;
  font-size: 13px; font-weight: 600;
  box-shadow: 0 8px 26px rgba(16,24,40,.24);
  opacity: 0; pointer-events: none; z-index: 200;
  transition: opacity .2s ease, transform .2s cubic-bezier(.2,.7,.3,1);
}
.save-flash.show { opacity: 1; transform: translate(-50%, 0); }
.save-flash.bad { background: var(--bad); color: #fff; }

/* ── Narrow screens ──────────────────────────────────────────────────── */
@media (max-width: 860px) {
  .settings-shell { grid-template-columns: 1fr; }
  .settings-nav {
    position: static;
    display: flex; gap: 4px; overflow-x: auto;
    padding: 6px;
  }
  /* The group headings are vertical signposts; in a horizontal strip they
     become gaps that read as separators pointing at the wrong thing. */
  .settings-nav .grp { display: none; }
  .settings-nav button { width: auto; white-space: nowrap; }

  .srow { flex-direction: column; gap: 10px; }
  .srow-control { padding-top: 0; }
  .dash-stat { border-left: 0; border-top: 1px solid var(--border); }
  .dash-stat:first-child { border-top: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .switch i, .chart-stack i, .save-flash, .plan { transition: none; }
}

/* A setting that is stored but not yet acted on. Marked rather than hidden:
   an admin who sets one and assumes it took effect is worse off than one who
   knows it is still to come. */
.badge-pending {
  display: inline-block; margin-left: 8px;
  padding: 1px 7px; border-radius: 999px;
  font-size: 10px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; vertical-align: 1px;
  background: var(--surface-2); color: var(--muted);
  border: 1px solid var(--border);
}

/* ══ Vertical rail, themes, and the panes that go with them ═══════════ */

/* The rail replaces a row of tabs. Vertical is not decoration: seven
   destinations in a horizontal strip either wrap or scroll on a laptop, and
   both are worse than a column that always shows all of them. */
/* Two columns, the rail and everything else -- the layout the navigation
   down the left needs. Restored 19 October 2026 with the rail itself; it
   had become a flex column so the rail could sit across the top. */
#app { display: grid; grid-template-columns: 210px minmax(0, 1fr); min-height: 100vh; }
/* The old top-nav layout centred the content with `margin: 0 auto`, and that
   rule still applies here. Inside a grid, auto side margins override the
   default stretch and collapse the item to its *content* width -- which is how
   a 1283px window ended up with a 376px column of cards floating in the middle
   of it. Both have to be overridden explicitly. */
#app > main {
  min-width: 0;
  max-width: none;
  margin: 0;
  /* The grid stretches it; the flex properties that replaced this are
     meaningless in a grid and left main collapsed to its content. */
  justify-self: stretch;
  padding: 24px 28px 56px;
}

.rail {
  /* Back down the left, 19 October 2026, on the owner's instruction: the
     navigation he built. The markup never changed -- the same
     <aside class="rail"> carried both -- so a column here is the whole
     of it, and the horizontal-bar rules that followed are undone
     further down where they were added. */
  position: sticky; top: 0; align-self: start;
  height: 100vh; overflow-y: auto;
  /* Thin and only when it is needed. The rail fits any window over
     about 600px tall; below that it has to scroll, and a full-width
     bar beside a 118px column is most of the gap between the icons
     and the edge. */
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
  background: var(--surface); border-right: 1px solid var(--border);
  padding: 14px 10px; display: flex; flex-direction: column; gap: 2px;
}
/* No background. It was the accent, from when the brand was a letter on a
   coloured square; once the logo became an image, 2px of that purple showed
   round it as a border (removed 22 September). The box keeps its size so
   nothing below it moves. */
/* The mark does not scroll with the list. Asked for on 19 October:
   "dont include the tymake logo in to the scroll bar". On a short
   window the rail scrolls, and the brand was going up with it.

   The box-shadow is not decoration. .rail carries 12px of top padding
   and a sticky child offsets from the scrollport's PADDING box, so
   this pins 12px down and the list would scroll visibly through the
   strip above it. The shadow paints that strip in the rail's own
   colour instead of adding a wrapper element to the markup. */
.rail-brand {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface);
  box-shadow: 0 -12px 0 var(--surface);
  width: 34px; height: 34px; border-radius: 9px; margin: 0 12px 0 0;
  display: grid; place-items: center; flex: none;
  background: none; color: #fff; font-weight: 800; font-size: 16px;
}
.rail-gap { flex: 1 1 auto; min-height: 18px; }

.rail button {
  display: flex; align-items: center; gap: 11px;
  width: auto; text-align: center; flex: none;
  background: none; border: 0; border-radius: 8px;
  padding: 9px 13px; cursor: pointer;
  font: 500 13.5px/1.3 inherit; color: var(--muted);
  transition: background .15s ease, color .15s ease;
}
.rail button i { font-style: normal; font-size: 14px; width: 18px; text-align: center; flex: none; }
.rail button span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Except this one. The rule above exists so a long NAME ellipsises
   rather than breaking the rail; "Sign in to manage" is a sentence, and
   in a 118px column it came out as "Sign in to man...", which reads as
   a broken label rather than a short one. The button was added for the
   top bar, where it had the width; it has no Layout A precedent to
   follow, so it wraps -- the same answer .rail-me already uses for the
   person's name underneath it. */
#rail-manage span { white-space: normal; text-overflow: clip; line-height: 1.2; }
.rail button:hover { background: var(--surface-2); color: var(--text); }
.rail button.active { background: transparent; color: var(--text); font-weight: 650; box-shadow: inset 0 -2px 0 var(--accent); }
.rail button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Report sub-tabs, so one Reports section holds what used to be three. */
.subtabs {
  display: flex; gap: 4px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px; padding-bottom: 10px;
}
.subtabs button {
  background: none; border: 0; border-radius: 7px;
  padding: 7px 12px; cursor: pointer;
  font: 600 13px/1.3 inherit; color: var(--muted);
  transition: background .15s ease, color .15s ease;
}
.subtabs button:hover { background: var(--surface-2); color: var(--text); }
.subtabs button.active { background: var(--accent-soft); color: var(--accent); }

/* ── The report tabs stay put while the report scrolls ───────────────────
   Asked for: "this tabs should not get hidden when scrolled up, should be
   sticky". A team report runs to hundreds of rows, and changing tab meant
   scrolling all the way back to the top to find the strip.

   Two things had to be true for `sticky` to do anything here.

   First, the strip had somewhere to travel. A sticky element is held inside its
   parent's box, and the strip's parent -- the reports section -- contains the
   strip and nothing else: the report panes are its siblings, not its children.
   So the parent is exactly as tall as the strip, the strip has nowhere to go,
   and it scrolls away like anything else. `display: contents` removes that
   grouping box entirely, which leaves the strip a child of <main> and free to
   stay put for the whole length of the report. The section is only ever a
   grouping element, so it has no appearance to lose. Hiding it still works:
   .hidden carries !important, which beats this rule whatever its specificity.
   The second rule below states that outright rather than leaving it resting on
   an !important somewhere else in a five-thousand-line file.

   Second, it sits below the app bar rather than under it: the bar is sticky at
   the top of the same scroller, so the strip's offset is the bar's height,
   published by the page as --appbar-h and measured rather than assumed, because
   the bar wraps to two rows on a narrow window.

   The bleed into main's 20px gutters is so the report does not show through
   beside the strip; the negative top margin cancels the padding that gives it
   an opaque band, leaving the strip exactly where it sat before. */
#view-reports { display: contents; }
#view-reports.hidden { display: none; }

#reportTabs {
  /* In normal flow: tabs and report content live in separate sibling
     sections, so sticky across them broke after the top-nav scroll change. */
  position: static;
  background: var(--bg);
  margin: 0 -20px 20px;
  padding: 0 20px 10px;
  border-bottom: 1px solid var(--border);
}

/* ── Theme picker ────────────────────────────────────────────────────── */
.dash-bar { display: flex; justify-content: flex-end; margin-bottom: 14px; }
.theme-picker { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.theme-label {
  font-size: 10px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.theme-group {
  display: inline-flex; gap: 2px; padding: 3px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 9px;
}
.theme-group button {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: 0; border-radius: 7px;
  padding: 6px 11px; cursor: pointer;
  font: 600 12.5px/1 inherit; color: var(--muted);
  transition: background .15s ease, color .15s ease, box-shadow .15s ease;
}
.theme-group button i { font-style: normal; font-size: 12px; }
.theme-group button:hover { color: var(--text); }
.theme-group button.active {
  background: var(--surface); color: var(--text);
  box-shadow: 0 1px 2px rgba(16,24,40,.10);
}

/* ── Support columns ─────────────────────────────────────────────────── */
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; margin-top: 10px; }
.cols > * { min-width: 0; }
.cols h4 { margin: 0 0 8px; font-size: 13px; }
.cols ul { margin: 0; padding-left: 18px; font-size: 13.5px; line-height: 1.85; color: var(--muted); }
@media (max-width: 700px) { .cols { grid-template-columns: 1fr; } }

/* ── Narrow: the rail collapses to icons, then to a strip ────────────── */
@media (max-width: 1000px) {
  #app { grid-template-columns: 62px minmax(0, 1fr); }
  .rail button span { display: none; }
  .rail button { justify-content: center; padding: 11px 0; }
  .rail button i { width: auto; font-size: 16px; }
  .rail-brand { margin-left: auto; margin-right: auto; }
}
@media (max-width: 640px) {
  #app { grid-template-columns: 1fr; }
  .rail {
    position: static; height: auto; flex-direction: row;
    overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--border);
  }
  .rail-brand, .rail-gap { display: none; }
  /* Row 3, below the header and the rail. See the header block further
     down for why every item in this grid has to name its own row. */
  #app > main { padding: 18px 16px 48px; grid-column: 1; grid-row: 3; }

  /* The words come back.
   *
   * The 1000px rule above hides them because a 62px column has no room for
   * them. This rule is a different shape entirely -- a row that scrolls
   * sideways -- and it inherited the hiding, so a phone got eight unlabelled
   * 16px glyphs and no way to tell Users from Settings. A row that scrolls
   * has as much width as it needs.
   */
  .rail button span { display: inline; }
  .rail button {
    justify-content: flex-start; gap: 7px;
    padding: 11px 13px; white-space: nowrap;
  }
  .rail button i { width: auto; font-size: 15px; }
}

/* Dark mode needs a little more than swapped tokens. A flat card on a flat
   background reads as one surface however the two differ, so cards get a
   top highlight, and the coloured card rules keep their weight instead of
   glowing against a near-black page. */
:root[data-theme="dark"] .card,
:root[data-theme="dark"] .dash-card,
:root[data-theme="dark"] .dash-stats,
:root[data-theme="dark"] .settings-nav,
:root[data-theme="dark"] .rail {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04), 0 1px 2px rgba(0, 0, 0, .45);
}
:root[data-theme="dark"] .dash-card { border-top-width: 2px; }
:root[data-theme="dark"] .switch.on { filter: saturate(.9); }
:root[data-theme="dark"] .chart-stack i.work { filter: saturate(.92); }
:root[data-theme="dark"] .plan.current { background: rgba(91, 141, 239, .10); }
:root[data-theme="dark"] .save-flash { background: var(--surface-2); color: var(--text); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .card,
  :root:not([data-theme="light"]) .dash-card,
  :root:not([data-theme="light"]) .dash-stats,
  :root:not([data-theme="light"]) .settings-nav,
  :root:not([data-theme="light"]) .rail {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04), 0 1px 2px rgba(0, 0, 0, .45);
  }
  :root:not([data-theme="light"]) .dash-card { border-top-width: 2px; }
  :root:not([data-theme="light"]) .switch.on { filter: saturate(.9); }
  :root:not([data-theme="light"]) .plan.current { background: rgba(91, 141, 239, .10); }
  :root:not([data-theme="light"]) .save-flash { background: var(--surface-2); color: var(--text); }
}

/* ══ Application bar ══════════════════════════════════════════════════
   Above every screen, not only the dashboard: "am I on the clock?" is a
   question you have while looking at something else. */
#app { grid-template-rows: auto minmax(0, 1fr); }
.rail { grid-row: 1 / span 2; }

/* Except in a browser, where there is no agent for it to control.
 *
 * The clock, Break, Finish, Select Task and Start tracking all act on the
 * agent running on THIS computer. In a browser there is none -- and these
 * are not harmlessly inert: Start tracking posts to /work/start and opens a
 * work session with nothing recording against it, so somebody appears to be
 * working and produces no activity at all.
 *
 * Hidden here and never started in app.js, so its one-second clock and its
 * poll of /work/status do not run either. On a 390px screen this is about
 * 170 pixels of the first 844 handed back to the thing the page is for.
 *
 * The grid row goes with it. Left in place, the bar's empty row kept its
 * height and the content started a bar's worth down the page.
 */
:root[data-shell="browser"] .appbar { display: none; }
/* And the height everything sticky is offset by.
 *
 * publishAppbarHeight only publishes a height ABOVE zero -- correct for
 * a bar that has not been laid out yet, wrong for one that is gone, so
 * the stylesheet fell back to its 71px default and pinned the tab strip
 * 71 pixels below a bar that was not there. */
:root[data-shell="browser"] { --appbar-h: 0px; }
/* The header takes the row the bar had, so the rail still spans both and
   the content still begins below. */
#webhead {
  grid-column: 2; grid-row: 1;
  display: flex; align-items: center; gap: 16px;
  padding: 13px 28px; min-width: 0;
  background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 41;
}
.webhead-where {
  font-size: 15px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.webhead-where span { color: var(--muted); font-weight: 500; }
.webhead-who {
  margin-left: auto; display: inline-flex; align-items: baseline; gap: 8px;
  padding: 6px 11px; border-radius: 8px; background: none; border: 0;
  cursor: pointer; color: var(--text); font: 500 13px/1.2 inherit;
  max-width: 46%; overflow: hidden;
}
.webhead-who:hover { background: var(--surface-2); }
.webhead-who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.webhead-who i {
  font-style: normal; font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em; flex: none;
}
.webhead-caret { color: var(--muted); align-self: center; flex: none; }

/* Start tracking. Pushed to the right-hand end with the name, leaving the
   trail on the left -- the arrangement WorkComposer's own header uses. */
.webhead-start {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 15px; border: 0; border-radius: 999px;
  background: var(--accent); color: #fff;
  font: 650 13px/1 inherit; cursor: pointer; flex: none;
}
.webhead-start:hover { filter: brightness(1.07); }
.webhead-start:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The name sits next to it rather than being pushed away from it: with both
   carrying margin-left:auto the first one wins and the second follows. */
.webhead-start + .webhead-who { margin-left: 0; }

/* ── The account menu ───────────────────────────────────────────────────
   Anchored to the header, which is position:sticky and therefore already a
   containing block for an absolutely positioned child. */
.webhead-menu {
  position: absolute; top: calc(100% + 6px); right: 18px; z-index: 60;
  min-width: 268px; max-width: calc(100vw - 32px);
  padding: 6px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: 0 14px 38px rgba(16, 24, 40, .16);
}
.whm-row {
  display: flex; flex-direction: column; gap: 3px;
  padding: 9px 12px;
}
.whm-lab {
  font-size: 10px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.whm-val { font-size: 13.5px; color: var(--text); word-break: break-word; }
.whm-theme { margin-top: 2px; }
.whm-out {
  display: block; width: 100%; text-align: left;
  margin-top: 4px; padding: 10px 12px;
  background: none; border: 0; border-top: 1px solid var(--border);
  border-radius: 0 0 8px 8px;
  color: var(--danger, #b42318); font: 600 13.5px/1.2 inherit; cursor: pointer;
}
.whm-out:hover { background: var(--surface-2); }
.whm-foot {
  padding: 8px 12px 4px; border-top: 1px solid var(--border);
  font-size: 11.5px; color: var(--muted);
}

/* ── "Please download the Tymake desktop app" ───────────────────────────
   Built on .modal-back > .modal, which is the dialog pattern this product
   already has. Not a new one: a bare .modal is a full-screen backdrop in
   this stylesheet, the two rules called .modal leaked into each other for
   weeks, and test_dialogs_sit_in_the_middle.py forbids repeating it. */
/* Written as `.modal-back > .getapp-box`, and that is not belt and braces.
   The dialog rule it has to beat is `.modal-back > .modal`, which centres
   its text -- two classes. A lone `.getapp-box` is one, loses, and the card
   came out centred: heading, sentence and all, which reads as a warning
   rather than a choice. The same trap is recorded against every dialog
   added to this file. */
.modal-back > .getapp-box {
  max-width: 420px; text-align: left; padding: 26px 26px 22px;
}
.modal-back > .getapp-box h3 { margin: 0 0 8px; font-size: 17px; text-align: left; }
.modal-back > .getapp-box p {
  margin: 0; color: var(--muted); font-size: 13.5px; line-height: 1.6;
  text-align: left;
}
.getapp-acts {
  display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px;
}
.getapp-go {
  display: inline-flex; align-items: center;
  padding: 9px 18px; border-radius: 9px;
  background: var(--accent); color: #fff;
  font: 650 13.5px/1 inherit; text-decoration: none;
}
.getapp-go:hover { filter: brightness(1.07); }
/* Cancel reads as a button, not as a word somebody forgot to style. The
   dialog offers two courses and only one of them should look preferred. */
.getapp-acts .ghost {
  padding: 9px 18px; border-radius: 9px;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text); font: 600 13.5px/1 inherit; cursor: pointer;
}
.getapp-acts .ghost:hover { background: var(--surface-2); }

@media (max-width: 640px) {
  /* The words go; the arrow still says what it does. A phone header has
     room for the trail, the name and one of these, not both in full. */
  .webhead-start span { display: none; }
  .webhead-start { padding: 8px 11px; }
  .webhead-menu { right: 8px; left: 8px; min-width: 0; }
  .getapp-acts { flex-direction: column-reverse; }
  .getapp-acts > * { justify-content: center; text-align: center; }
}

/* The header is published by app.js and only in a browser, so the desktop
   shell never has one to lay out. --appbar-h keeps its meaning: the height
   of whatever sits in that row, which is now this. */
:root[data-shell="browser"] { --appbar-h: 51px; }

@media (max-width: 640px) {
  /* One column here, so the header, the rail and the report stack.
   *
   * All four placements have to be stated, and the reason is the rail. It
   * carries `grid-row: 1 / span 2` for the wide layout, where it runs down
   * the side of both the bar and the report. Left in place on a phone it
   * holds row 1 of the only column, the header's `grid-column: 1` collides
   * with it, and the rail is placed in an IMPLICIT second column instead.
   * An implicit column is auto-sized, the rail's content is eight nowrap
   * buttons, and it took 358 of a 390px screen -- leaving main a 32-pixel
   * ribbon with one word per line down the whole report. Measured in a
   * browser at 390px, not reasoned about: nothing here reads as wrong.
   *
   * .appbar is given a row for the same reason. It is display:none in a
   * browser so it never reaches the grid there, but the desktop shell has
   * no header and uses the bar in its place, and a narrow desktop window
   * would otherwise collide in exactly the same way. */
  #webhead { grid-column: 1; grid-row: 1; padding: 11px 16px; }
  .appbar  { grid-column: 1; grid-row: 1; }
  .rail    { grid-column: 1; grid-row: 2; }
  .webhead-who i { display: none; }
}

.appbar {
  grid-column: 2; grid-row: 1;
  display: flex; align-items: center; gap: 20px;
  padding: 12px 24px; min-width: 0;
  background: var(--surface); border-bottom: 1px solid var(--border);
  /* Pinned, like the rail beside it. The whole document scrolls here -- #app
     is a grid with min-height:100vh rather than an inner scrolling pane -- so
     without this the bar scrolled away with the cards, and "am I on the clock?"
     became a question you had to scroll back up to answer. That is the one
     thing on the screen that should never need looking for.

     Opaque background and a z-index above the content it now sits over: a
     transparent sticky bar shows the page sliding through it. */
  /* Restored 19 October 2026. It had been made static when #app became a
     flex column and `main` did its own scrolling -- the bar stayed put for
     free then, so sticky was dropped and this comment was left describing
     behaviour the rule no longer had. With the two-column grid back, the
     document scrolls again and the bar needs to be pinned on its own. */
  position: sticky; top: 0; z-index: 60;
}

/* The break button. It carries a word now, not only an icon: it was a round
   button with two vertical bars in it whose meaning lived in a tooltip, while
   the widget's button beside it has said BREAK and RESUME in words all along.
   Auto width so the word fits; the 38px is a floor, not a fixed size. */
.appbar-play {
  min-width: 38px; height: 38px; flex: none; border: 0; border-radius: 10px;
  padding: 0 12px;
  background: var(--surface-2); color: var(--muted);
  font: 700 12px/1 inherit; letter-spacing: .02em;
  cursor: pointer; display: inline-flex; align-items: center; gap: 7px;
  transition: background .16s ease, color .16s ease, transform .16s ease;
}
.appbar-play i { font-style: normal; font-size: 11px; line-height: 1; }
.appbar-play:hover:not(:disabled) { transform: scale(1.05); }
.appbar-play:disabled { opacity: .5; cursor: default; }
.appbar.working .appbar-play { background: rgba(63,191,143,.16); color: var(--good); }
.appbar.onbreak .appbar-play { background: rgba(221,165,72,.18); color: var(--warn); }

/* The task slot at the far left of the bar, as the reference has it: a
   filled "+ Select Task" while nothing is running, and the running task's
   name on a quiet pill with an x beside it once Start is pressed. */
.appbar-task { display: inline-flex; align-items: center; gap: 4px; flex: 0 1 auto; max-width: 320px; min-width: 0; }
.appbar-taskpick, .appbar-taskname {
  height: 38px; border: 0; border-radius: 10px; padding: 0 14px;
  display: inline-flex; align-items: center; gap: 8px; min-width: 0;
  font-weight: 600; font-size: 13px; cursor: pointer;
  transition: background .16s ease, transform .16s ease;
}
.appbar-taskpick { background: var(--accent-fill, var(--accent)); color: #fff; }
.appbar-taskpick i { font-style: normal; font-size: 17px; line-height: 1; }
/* Written with the bar so they out-rank the generic button hover rules,
   which otherwise took the fill off "+ Select Task" under the pointer. */
.appbar .appbar-taskpick:hover { background: var(--accent-fill-hover, var(--accent-hover, var(--accent))); color: #fff; }
.appbar-taskname { background: var(--surface-2); color: var(--text); max-width: 260px; }
.appbar-taskname span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.appbar-taskname i { font-style: normal; font-size: 11px; color: var(--muted); }
.appbar-taskname:hover { background: var(--accent-soft); }
.appbar-taskclear { color: var(--muted); }
.appbar .appbar-taskclear:hover:not(:disabled) { color: var(--bad); }
@media (max-width: 1200px) { .appbar-task { max-width: 220px; } .appbar-taskname { max-width: 160px; } }
@media (max-width: 900px) { .appbar-task { max-width: 200px; } .appbar-taskname { max-width: 150px; } }

.appbar-clock { flex: none; min-width: 132px; }
.appbar-state {
  font-size: 10px; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted);
}
.appbar.working .appbar-state { color: var(--good); }
.appbar.onbreak .appbar-state { color: var(--warn); }
.appbar.offline .appbar-state { color: var(--bad); }
.appbar-time {
  font: 700 22px/1.15 var(--font-mono);
  font-variant-numeric: tabular-nums; letter-spacing: -.01em; margin-top: 2px;
}

.appbar-progress { flex: 1; min-width: 0; }
.appbar-progress .track {
  display: flex; height: 6px; border-radius: 999px;
  background: var(--surface-2); overflow: hidden;
}
.appbar-progress .track i { display: block; height: 100%; transition: width .5s cubic-bezier(.2,.7,.3,1); }
.appbar-progress .track i.work { background: var(--accent); }
.appbar-progress .track i.brk  { background: var(--warn); }
.appbar-totals {
  display: flex; gap: 20px; flex-wrap: wrap;
  margin-top: 7px; font-size: 12.5px; color: var(--muted);
}
.appbar-totals b { color: var(--text); font-variant-numeric: tabular-nums; font-weight: 600; }
.sw { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 6px; }
.sw.work { background: var(--accent); }
.sw.brk  { background: var(--warn); }

.appbar-range {
  flex: none; border: 0; background: none; color: var(--muted);
  font-size: 13px; padding: 6px 4px; cursor: pointer;
}
.appbar-range:focus { box-shadow: none; }

.appbar-finish {
  flex: none; display: inline-flex; align-items: center; gap: 9px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 9px;
  padding: 9px 20px; font-size: 13.5px; font-weight: 600; cursor: pointer;
  transition: border-color .16s ease, background .16s ease;
}
.appbar-finish:hover:not(:disabled) { border-color: var(--accent); }
.appbar-finish:disabled { opacity: .5; cursor: default; }
.appbar-finish .tick {
  width: 15px; height: 15px; border-radius: 4px;
  border: 1.5px solid var(--muted); display: inline-block; position: relative;
}
.appbar-finish .tick.on { background: var(--accent); border-color: var(--accent); }
.appbar-finish .tick.on::after {
  content: "\2713"; position: absolute; inset: 0;
  display: grid; place-items: center; color: #fff; font-size: 10px; font-weight: 700;
}

@media (max-width: 900px) {
  .appbar { flex-wrap: wrap; gap: 12px; padding: 10px 16px; }
  .appbar-progress { order: 3; flex-basis: 100%; }
  .appbar-range { margin-left: auto; }
}

/* ══ Chart tooltip ════════════════════════════════════════════════════
   A `title` attribute waits a second, lands wherever the OS decides, and
   cannot show two coloured figures. */
.charttip {
  position: fixed; z-index: 300; pointer-events: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 11px 14px;
  box-shadow: 0 10px 30px rgba(0,0,0,.22);
  opacity: 0; transform: translateY(4px);
  transition: opacity .14s ease, transform .14s ease;
}
.charttip.show { opacity: 1; transform: none; }
.charttip-head { font-weight: 700; font-size: 13px; margin-bottom: 7px; }
.charttip-row {
  display: flex; align-items: center;
  font-size: 12.5px; color: var(--text); padding: 2px 0; white-space: nowrap;
}
.chart-col { cursor: default; }

/* ══ Admin reporting ══════════════════════════════════════════════════ */

.rep-stats {
  display: grid; gap: 14px; margin-bottom: 18px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.rep-stat {
  background: var(--surface); border: 1px solid var(--border);
  border-top: 3px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 16px 18px; min-width: 0;
}
.rep-stat.a { border-top-color: #4f7dfb; }
.rep-stat.b { border-top-color: #34c98a; }
.rep-stat.c { border-top-color: #57bcd6; }
.rep-stat.d { border-top-color: #a97fe0; }
.rep-stat.warn { border-top-color: var(--warn); }
.rep-stat .muted { font-size: 12.5px; margin-top: 4px; }

.attention-row {
  display: flex; gap: 12px; flex-wrap: wrap;
  padding: 8px 0; border-top: 1px solid var(--border); font-size: 13.5px;
}
.attention-row:first-of-type { border-top: 0; }

/* Forty-eight half-hour cells. Elements, not a drawing: the row stays legible
   when somebody zooms in to argue about a particular hour. */
.daystrip { display: flex; gap: 1px; height: 22px; min-width: 0; }
.daystrip i { flex: 1 1 0; border-radius: 1px; min-width: 2px; }
.daystrip i.off { background: var(--surface-2); }
.daystrip i.low { background: color-mix(in srgb, var(--good) 35%, var(--surface-2)); }
.daystrip i.on  { background: var(--good); }
.daystrip i.brk { background: var(--warn); }

.strip-legend {
  display: flex; gap: 16px; flex-wrap: wrap;
  font-size: 12px; color: var(--muted); margin: 4px 0 12px;
}
.strip-legend span { display: inline-flex; align-items: center; gap: 6px; }
.strip-legend .cell { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.strip-legend .cell.off { background: var(--surface-2); }
.strip-legend .cell.low { background: color-mix(in srgb, var(--good) 35%, var(--surface-2)); }
.strip-legend .cell.on  { background: var(--good); }
.strip-legend .cell.brk { background: var(--warn); }

.strip-hours {
  display: flex; justify-content: space-between;
  font-size: 11px; color: var(--muted);
  margin: 0 0 6px; padding-left: 190px;
}
@media (max-width: 780px) { .strip-hours { display: none; } }

.strip-list { display: flex; flex-direction: column; }
.strip-row {
  display: grid; align-items: center; gap: 14px;
  grid-template-columns: 176px minmax(0, 1fr) 118px 64px;
  padding: 9px 0; border-top: 1px solid var(--border);
}
.strip-row:first-child { border-top: 0; }
.strip-who { min-width: 0; display: flex; flex-direction: column; }
.strip-who b { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.strip-who .muted { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.strip-act { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--muted); }
.strip-total { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; font-size: 13.5px; }
.strip-under td { padding-top: 0 !important; padding-bottom: 12px !important; }

@media (max-width: 780px) {
  .strip-row { grid-template-columns: 1fr auto; grid-template-areas: "who total" "strip strip" "act act"; }
  .strip-who { grid-area: who; }
  .strip-total { grid-area: total; }
  .daystrip { grid-area: strip; }
  .strip-act { grid-area: act; }
}

.meter { flex: 1; height: 6px; border-radius: 999px; background: var(--surface-2); overflow: hidden; min-width: 46px; }
.meter.wide { min-width: 120px; }
.meter i { display: block; height: 100%; transition: width .4s ease; }
.meter i.good { background: var(--good); }
.meter i.mid  { background: var(--warn); }
.meter i.low  { background: var(--bad); }

.prodbar { display: flex; height: 12px; border-radius: 999px; overflow: hidden; background: var(--surface-2); margin-bottom: 16px; }
.prodbar i { display: block; height: 100%; transition: width .5s ease; }
.prodbar i.productive   { background: var(--good); }
.prodbar i.neutral      { background: var(--idle); }
.prodbar i.unproductive { background: var(--bad); }

/* A table with seven columns cannot fit a phone, and the honest answer is to
   let it scroll inside its own card rather than push the whole page sideways.
   Applied at every width so a long app name cannot do it either. */
.card > table,
.card > .grid,
/* One table, so one set of column widths.

   This was display:block with thead and tbody each display:table -- which is
   two tables, each measuring its own columns, lining up only by luck. On
   attendance they were 245px apart: the heading "Clock in" sat above the
   punctuality pill. Scrolling a wide table is the wrapper's job. */
table.grid {
  display: table;
  width: 100%;
  min-width: max-content;
  border-collapse: collapse;
}
/* The wrapper. Wide tables scroll inside it rather than pushing the page. */
.gridwrap {
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
table.grid th, table.grid td { white-space: nowrap; }
/* The person column is the one worth wrapping; everything else is a figure. */
table.grid td:first-child { white-space: normal; min-width: 160px; }

/* Toolbars and filter rows wrap instead of stretching their container. */
.toolbar, .row-form, .subtabs, .stats, .rep-stats { flex-wrap: wrap; min-width: 0; }
.toolbar > *, .row-form > * { min-width: 0; }

@media (max-width: 760px) {
  .strip-row { gap: 8px; }
  .rep-stat { padding: 14px; }
}

/* The activity timeline is one element per slice with a 1px floor, so on a
   narrow screen the slices add up to more than the window and push the page
   sideways. It scrolls inside its own row instead -- the alternative is
   dropping slices, and a timeline that quietly omits minutes is worse than one
   you have to scroll. */
.timeline {
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.timeline > i { flex: 1 1 auto; }

/* Grid and flex items default to min-width:auto, which resolves to min-content
   -- so a card holding a wide table grows past its own track instead of
   shrinking and letting the table scroll. This is the whole of that bug, and
   it is worth stating once for every container rather than chasing it per
   screen. */
.two-col > *, .cols > *, .plans > *, .rep-stats > *, .dash-cards > *,
.dash-charts > *, .dash-stats > *, .settings-shell > *, .strip-row > * {
  min-width: 0;
}
.table-wrap { min-width: 0; max-width: 100%; }

/* ══ The day grid ═════════════════════════════════════════════════════
   Forty-eight half-hour blocks with gaps between them. The gaps carry
   meaning: a missing half hour is visible without measuring it, which a
   continuous bar cannot show. */
.daygrid {
  display: grid;
  grid-template-columns: repeat(48, minmax(0, 1fr));
  gap: 2px;
  height: 20px;
  min-width: 0;
}
.daygrid > i {
  display: block;
  border-radius: 2px;
  min-width: 0;
  transition: transform .12s ease;
}
.daygrid > i:hover { transform: scaleY(1.25); }

/* Four steps of green, not a gradient: the eye reads bands, and a continuous
   scale invites arguing about a shade. */
.daygrid > i.c0 { background: var(--surface-2); }
.daygrid > i.c1 { background: color-mix(in srgb, var(--good) 28%, var(--surface-2)); }
.daygrid > i.c2 { background: color-mix(in srgb, var(--good) 52%, var(--surface-2)); }
.daygrid > i.c3 { background: color-mix(in srgb, var(--good) 76%, var(--surface-2)); }
.daygrid > i.c4 { background: var(--good); }
.daygrid > i.c5 { background: var(--warn); }
.daygrid > i.now { box-shadow: 0 0 0 2px var(--accent); }

/* The ruler shares the grid's track definition, so an hour label sits above
   the block it belongs to instead of being nudged there with padding. */
.dayruler {
  position: relative;
  display: grid;
  grid-template-columns: repeat(48, minmax(0, 1fr));
  gap: 2px;
  min-width: 0;
  height: 16px;
}
.dayruler > span { font-size: 10px; color: var(--muted); white-space: nowrap; }
.dayruler > span.labelled { overflow: visible; }
.dayruler .nowmark {
  position: absolute; top: -2px; transform: translateX(-50%);
  background: var(--accent-fill, var(--accent)); color: #fff;
  font-size: 10px; font-weight: 700; letter-spacing: .02em;
  padding: 2px 6px; border-radius: 5px; white-space: nowrap;
}
.dayruler .nowmark::after {
  content: ""; position: absolute; left: 50%; top: 100%;
  width: 1px; height: 8px; background: var(--accent);
}

.strip-header { border-top: 0 !important; padding-bottom: 2px !important; }

.grid-legend {
  display: flex; gap: 18px; flex-wrap: wrap;
  font-size: 12px; color: var(--muted); margin: 2px 0 14px;
}
.grid-legend span { display: inline-flex; align-items: center; gap: 6px; }
.grid-legend .swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; flex: none; }
.grid-legend .swatch.c0 { background: var(--surface-2); }
.grid-legend .swatch.c4 { background: var(--good); }
.grid-legend .swatch.c5 { background: var(--warn); }
.grid-legend .swatch.bar-on  { background: var(--good); }
.grid-legend .swatch.bar-brk { background: var(--warn); }
.grid-legend .swatch.bar-off { background: var(--surface-2); }

/* ══ The attendance bar ═══════════════════════════════════════════════
   One continuous run rather than blocks. A different question -- when were
   they here, not what was each half hour like -- and a solid stretch reads as
   a shift. */
.daybar {
  display: flex; height: 26px; border-radius: 6px; overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--border); min-width: 0;
}
.daybar > i { display: block; height: 100%; }
.daybar > i.on  { background: var(--good); }
.daybar > i.brk { background: var(--warn); }
.daybar > i.off { background: transparent; }
.daybar-axis {
  display: flex; justify-content: space-between;
  font-size: 10.5px; color: var(--muted); margin-top: 5px;
}
.bar-label {
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px;
}

/* ══ Screenshots ══════════════════════════════════════════════════════ */

.shots-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 4px 0 16px; min-width: 0;
}
.shots-bar-left, .shots-bar-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.shots-bar-right { margin-left: auto; }

.chip {
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text); border-radius: 8px;
  padding: 7px 13px; font-size: 13px; font-weight: 500; cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.chip:hover { border-color: var(--accent); }
.chip.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.chip.icon { padding: 7px 11px; }
.chip.date { cursor: default; font-variant-numeric: tabular-nums; }
.chip.date:hover { border-color: var(--border); }

.pager { display: flex; gap: 4px; justify-content: center; margin: 14px 0 18px; flex-wrap: wrap; }
.pagebtn {
  min-width: 38px; padding: 7px 11px; border-radius: 8px;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text); font-size: 13px; cursor: pointer;
}
.pagebtn:hover:not(:disabled) { border-color: var(--accent); }
/* The page being shown, filled. Page buttons are left out of the "a button in a
   card is an outline" rule further down: it outranks this one by four classes,
   and every pager lives in a card, so the current page looked like the rest
   and nothing said which page was open (reported 22 September). */
.pagebtn.active { background: var(--accent-fill, var(--accent)); border-color: var(--accent-fill, var(--accent)); color: #fff; font-weight: 600; }
.pagebtn:disabled { opacity: .4; cursor: default; }

/* One card per person. Density is a real preference: four across is a sweep,
   two across is actually looking at the images. */
.shotgrid { display: grid; gap: 16px; }
.shotgrid.up2 { grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }
.shotgrid.up3 { grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); }
.shotgrid.up4 { grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); }
.shotgrid > * { min-width: 0; }

.shotcard {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 14px; display: flex; flex-direction: column; gap: 10px; min-width: 0;
  transition: border-color .16s ease, transform .16s ease;
}
.shotcard:hover { border-color: var(--accent); transform: translateY(-2px); }
.shotcard-head { display: flex; align-items: center; gap: 9px; min-width: 0; }
.shotcard-head b { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.avatar {
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent);
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
}
.avatar.sm { width: 22px; height: 22px; font-size: 9px; }

.shotframe {
  position: relative; border-radius: 8px; overflow: hidden;
  background: var(--surface-2); aspect-ratio: 16 / 10; cursor: pointer;
}
.shotframe img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shotframe-empty {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: var(--muted); font-size: 12.5px;
}
.shotframe .stamp {
  position: absolute; left: 8px; bottom: 8px;
  background: rgba(8, 11, 24, .78); color: #fff;
  font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 6px;
  font-variant-numeric: tabular-nums;
}
.shotframe .zoom {
  position: absolute; right: 8px; top: 8px;
  width: 26px; height: 26px; padding: 0; border: 0; border-radius: 6px;
  background: rgba(8, 11, 24, .7); color: #fff; cursor: pointer;
  opacity: 0; transition: opacity .15s ease;
}
.shotframe:hover .zoom { opacity: 1; }

.shotcard-stats { display: flex; align-items: center; gap: 9px; min-width: 0; }
.shotcard-stats b { font-size: 15px; font-variant-numeric: tabular-nums; }
.spark { display: flex; align-items: flex-end; gap: 2px; height: 20px; flex: 1; min-width: 0; }
.spark i { flex: 1 1 0; min-width: 2px; border-radius: 1px; }
/* good/mid/low, from pctBand, like every other percentage in the product.
   .high is the name this one used before it shared that helper; kept so an
   older cached copy of admin.js does not draw a row of colourless bars. */
.spark i.good,
.spark i.high { background: var(--good); }
.spark i.mid  { background: var(--warn); }
.spark i.low  { background: var(--idle); }
.spark i.none { background: var(--border); }

.band {
  font-size: 10px; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; padding: 3px 8px; border-radius: 999px; flex: none;
}
.band.high     { background: rgba(52,201,138,.16); color: var(--good); }
.band.moderate { background: rgba(240,168,60,.18); color: var(--warn); }
.band.low      { background: rgba(240,115,106,.16); color: var(--bad); }

.shotcard-context {
  font-size: 12px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shotcard-foot {
  margin-top: auto; background: none; border: 0; border-top: 1px solid var(--border);
  padding: 10px 0 0; text-align: left; color: var(--accent);
  font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.shotcard-foot:disabled { color: var(--muted); cursor: default; }

/* --- the timeline half --- */
.pick { text-align: center; padding: 40px 24px; }
.pick h3 { margin: 0 0 6px; font-size: 19px; }
.pick .hint { margin: 0 auto 22px; max-width: min(46ch, 100%); }

.chips { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.memberchip {
  display: inline-flex; align-items: center; gap: 9px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 999px; padding: 7px 16px 7px 8px;
  font-size: 13.5px; font-weight: 600; color: var(--text); cursor: pointer;
  transition: border-color .15s ease, transform .15s ease;
}
.memberchip:hover { border-color: var(--accent); transform: translateY(-1px); }

.day-head { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.day-head .who { display: flex; align-items: center; gap: 12px; min-width: 0; }
.day-head h3 { margin: 0; font-size: 17px; }
.day-head .hint { margin: 2px 0 0; }

/* The day heading above a range of captures (19 Oct 2026).
 *
 * Sits OUTSIDE the hour cards rather than inside one, because it separates
 * them: a heading in the first card of a day reads as belonging to that hour.
 *
 * `.shotday` is a new name here and was checked against every .shot* and
 * .hour* class already in this file before being used -- this stylesheet has
 * had four collisions, two of them rules both called .modal.
 *
 * The rule is the <i>: a flexible hairline that takes whatever width is left
 * after the date and the tally, so the heading spans the pane at any size
 * without a fixed width to go stale. */
.shotday {
  display: flex; align-items: center; gap: 12px;
  margin: 26px 2px 10px;
}
.shotday:first-child { margin-top: 6px; }
.shotday b {
  font-size: 13px; letter-spacing: .06em; text-transform: uppercase;
  white-space: nowrap;
}
.shotday i { flex: 1 1 auto; height: 1px; background: var(--border); }
.shotday .muted { font-size: 12px; white-space: nowrap; }

.hourcard { padding-top: 14px; }
.hour-label {
  display: flex; align-items: baseline; gap: 10px;
  margin-bottom: 12px; font-size: 13px;
}
.hour-label b { font-size: 14px; }
.shottile { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.shottile-meta {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 12px; min-width: 0;
}
.shottile-meta b { font-variant-numeric: tabular-nums; }
.shottile-meta .muted { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 620px) {
  .shots-bar-right { margin-left: 0; width: 100%; }
  .shotgrid.up3, .shotgrid.up4 { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  /* A tile wider than the window means scrolling sideways to see one image. */
  .hourstrip.d2 > * { width: 78vw; }
  .hourstrip.d3 > * { width: 62vw; }
  .hourstrip.d4 > * { width: 46vw; }
}

/* The range control says what the range contains, rather than only naming it.
   Hovering the figure you are looking at and being told what it adds up to is
   the question people actually had. */
.appbar-rangewrap { position: relative; flex: none; }
.appbar-range {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: 0; color: var(--muted);
  font-size: 13px; padding: 7px 8px; border-radius: 7px; cursor: pointer;
}
.appbar-range:hover { background: var(--surface-2); color: var(--text); }
.appbar-range i { font-style: normal; font-size: 10px; }

.ab-pop {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
  min-width: 232px; display: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 14px; box-shadow: 0 12px 34px rgba(0,0,0,.28);
}
.ab-pop.show { display: block; }
.ab-pop-title { font-weight: 700; font-size: 13.5px; margin-bottom: 10px; }
.ab-pop-row {
  display: flex; justify-content: space-between; gap: 18px;
  font-size: 13px; color: var(--muted); padding: 4px 0;
}
.ab-pop-row b { color: var(--text); font-variant-numeric: tabular-nums; }
.ab-pop-choose {
  display: flex; gap: 4px; margin-top: 12px;
  padding-top: 10px; border-top: 1px solid var(--border);
  /* The row decides the popover's least width, rather than being squeezed by
     whatever width the figures above it happened to need. */
  min-width: max-content;
}
.ab-pop-choose button {
  /* "This month" wrapped onto two lines inside its own button while "Today"
     and "This week" sat on one, so the row of three read as a layout fault
     rather than three choices (BUG-027). flex:1 gives each a third of the row
     and the padding took what was left, so the longest label had nowhere to
     go. nowrap states the rule; the reduced side padding gives it the width. */
  flex: 1; background: none; border: 0; border-radius: 6px;
  padding: 6px 2px; font-size: 12px; color: var(--muted); cursor: pointer;
  white-space: nowrap;
}
.ab-pop-choose button:hover { background: var(--surface-2); color: var(--text); }
.ab-pop-choose button.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }

.chip.date { cursor: pointer; }
.chip.date:hover { border-color: var(--accent); }
.chip.icon:disabled { opacity: .4; cursor: default; }
.chip.icon:disabled:hover { border-color: var(--border); }

/* An hour of captures is a sequence, so it stays on one line and scrolls
   sideways. Wrapping it into three rows breaks the only thing the order was
   telling you -- what happened next. */
.hourstrip {
  display: flex; gap: 12px;
  overflow-x: auto; overflow-y: hidden;
  padding-bottom: 10px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.hourstrip > * { scroll-snap-align: start; flex: 0 0 auto; }
.hourstrip.d2 > * { width: 320px; }
.hourstrip.d3 > * { width: 250px; }
.hourstrip.d4 > * { width: 200px; }

/* A visible track, because a strip that scrolls and does not say so is a strip
   people think is truncated.

   Both strips are named on every rule rather than giving them a shared class:
   this stylesheet has a flat namespace and a new generic name in it has
   collided with something four times. */
.hourstrip::-webkit-scrollbar, .wd-shots::-webkit-scrollbar { height: 9px; }
.hourstrip::-webkit-scrollbar-track, .wd-shots::-webkit-scrollbar-track { background: var(--surface-2); border-radius: 999px; }
.hourstrip::-webkit-scrollbar-thumb, .wd-shots::-webkit-scrollbar-thumb {
  background: var(--border); border-radius: 999px; border: 2px solid var(--surface-2);
}
.hourstrip::-webkit-scrollbar-thumb:hover, .wd-shots::-webkit-scrollbar-thumb:hover { background: var(--muted); }
.hourstrip, .wd-shots { scrollbar-width: thin; scrollbar-color: var(--border) var(--surface-2); }

.pick-icon { color: var(--muted); line-height: 0; margin-bottom: 14px; }
.pick .pager { margin: 4px 0 20px; }
.pick .chips { max-width: 900px; margin: 0 auto; }

/* Whose captures are on screen, and the way back to everybody. */
.scopewrap { position: relative; }
.chip.scope { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.chip.scope i { font-style: normal; font-size: 10px; opacity: .7; }
.chip.scope:hover { border-color: var(--accent); }

.scopemenu {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 70;
  display: none; min-width: 236px; max-height: 340px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 6px; box-shadow: 0 12px 34px rgba(0, 0, 0, .28);
}
.scopemenu.show { display: block; }
.scopemenu button {
  display: flex; align-items: center; gap: 9px; width: 100%;
  background: none; border: 0; border-radius: 7px; cursor: pointer;
  padding: 7px 9px; font-size: 13px; color: var(--text); text-align: left;
}
.scopemenu button:hover { background: var(--surface-2); }
.scopemenu button.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.scopemenu-note {
  padding: 8px 9px 4px; margin-top: 4px; border-top: 1px solid var(--border);
  font-size: 12px; color: var(--muted);
}

/* The brand is the product's own mark rather than a letter left from the old
   name. */
.rail-brand img { display: block; border-radius: 7px; }

/* The Account button's avatar, with a presence dot over it. Prefixed rail-
   like everything else in this rail: .avatar and .dot both mean something
   else elsewhere in this file. */
/* Written at three classes deep on purpose, and measured rather than assumed.
   The rail already styles the elements this is made of: `.rail button i` sets
   a 22px icon slot (in four separate rules, the last of which wins) and
   `.rail button span` sets overflow: hidden for name ellipsis. At their own
   weight those made the dot the size of the avatar and then clipped it.

   The dot is a <span> and not an <i> for the same reason, the hard way: as an
   <i> it inherited that 22px icon slot, so a window holding a stylesheet even
   a few minutes old drew the Account button with a second icon stacked inside
   it -- tall, with the label shoved across. Reported from a browser with a
   cached stylesheet on 24 September. As a <span> the worst an old stylesheet
   can do is not draw it. */
.rail .rail-face {
  position: relative; display: inline-grid; place-items: center; flex: none;
  overflow: visible;                      /* or the dot is cut off at the edge */
}
/* The size is set by #rail-face further down -- an id, which no selector here
   can outrank. Left there rather than fought with from up here. */
.rail .rail-face .rail-dot {
  position: absolute; right: -2px; bottom: -2px;
  width: 10px; height: 10px; min-width: 0; border-radius: 50%;
  background: var(--muted);
  /* Ringed in the rail's own ground so it reads as sitting on top of the
     avatar rather than punched out of it. */
  border: 2px solid var(--surface);
}
.rail .rail-face .rail-dot.working { background: var(--good); }
.rail .rail-face .rail-dot.break { background: var(--warn); }
.rail .rail-face .rail-dot.off { background: var(--muted); }

/* Who is signed in, at the foot of the rail. Collapsed rails hide it rather
   than wrapping a full name into a 64px column. */
.rail-me {
  display: none;
  font-size: 11.5px; color: var(--muted); text-align: center;
  padding: 8px 6px 2px; margin-top: 4px;
  border-top: 1px solid var(--border);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rail.collapsed .rail-me, .rail.mini .rail-me { display: none; }

/* A chart says when it was drawn and offers to redraw itself. A chart with no
   such affordance goes stale silently, which is how a bar reading 1h 16m ends
   up beside a widget reading 1h 45m. */
.chart-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; margin-bottom: 4px;
}
.chart-head h3 { margin: 0; }
.chart-head .hint { margin: 2px 0 0; }

.iconbtn {
  flex: none; display: grid; place-items: center;
  width: 30px; height: 30px; padding: 0;
  background: none; border: 1px solid var(--border); border-radius: 7px;
  color: var(--muted); cursor: pointer;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.iconbtn:hover { color: var(--accent); border-color: var(--accent); background: var(--surface-2); }
.iconbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.iconbtn.spinning { color: var(--accent); border-color: var(--accent); }
.iconbtn.spinning svg { animation: spin .7s linear infinite; }

@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .iconbtn.spinning svg { animation: none; opacity: .55; }
}

/* ── Reports: shared toolbar ─────────────────────────────────────────── */
.rep-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 20px;
}
/* Row one: the day controls, then the people filter at the far right of it.
   flex: 1 1 auto so this group owns the whole line beside the title, because
   the filter is pushed over by an auto margin and an auto margin can only
   push as far as its own parent's edge. */
.rep-bar-left {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  min-width: 0; flex: 1 1 auto;
}
.rep-bar-left > .scope { margin-left: auto; }

/* Row two, and it is a row by arithmetic rather than by luck.
 *
 * flex: 0 0 100% cannot share a line with anything, so the tools are on
 * their own row at every width -- which is what the owner asked for on 21
 * October after watching one row fold the moment a person with a long name
 * was selected. Wrapping gave two rows too, but only sometimes, and put the
 * exports on the left of the second one. */
.rep-bar-right {
  display: flex; align-items: center; gap: 8px;
  flex: 0 0 100%; justify-content: flex-end; margin-left: 0;
  /* Wrapping, since the report stamp joined this row: it is a sentence, not
     a button, and at 1024px there are reports where it and four controls do
     not both fit. Without this the group cannot give, so the stamp had to be
     clipped. The gap above is the row gap too, so a wrapped line is spaced
     like the rest of the bar. */
  flex-wrap: wrap;
}

.rep-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.rep-head h2 { margin: 0; }

/* Movement against the previous window. Direction is always shown; whether
   that direction is good is only coloured when the caller says so. */
.delta {
  font-size: 12px; font-weight: 600; margin-left: 9px;
  white-space: nowrap; letter-spacing: .01em;
}
.delta.flat { color: var(--muted); font-weight: 500; }
.delta.tone-good { color: var(--good); }
.delta.tone-bad { color: var(--warn); }
.delta.up:not(.tone-good):not(.tone-bad),
.delta.down:not(.tone-good):not(.tone-bad) { color: var(--muted); }

/* ── Team grid ───────────────────────────────────────────────────────── */
/* A scroller inside a grid or flex parent needs telling it may be narrower
   than what it holds. Without min-width, `min-width: auto` sizes it to its
   content -- the 642px attendance table on a 390px phone -- and the overflow
   it was added to contain leaks into the page instead, taking the whole
   document sideways with it. */
.scroll-x { overflow-x: auto; min-width: 0; max-width: 100%; }
.teamgrid { min-width: 900px; }
/* Top, so the first line of every cell in a row sits on one line (BUG-060).
 *
 * These were middle-aligned, and the meter cells are genuinely taller than
 * the rest -- a bar with a change badge under it against a single line of
 * text. Centring each cell in a row whose height is set by the tallest one
 * put Worked, Break, Activity and Productivity at four different heights.
 * Aligning the tops lines up the figures, which is what a reader scans
 * across. The bar keeps its own middle alignment within its line. */
.teamgrid td { vertical-align: top; }
.teamgrid td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }

.person { display: flex; align-items: center; gap: 10px; min-width: 0; }
.person b { display: block; }
.tiny { font-size: 11.5px; }

.pill {
  display: inline-block; font-size: 11px; font-weight: 600;
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
}
.pill.on { background: var(--good-soft, rgba(47,140,90,.14)); color: var(--good); }
.pill.off { background: var(--surface-2); color: var(--muted); }

.meter-cell { min-width: 128px; }
.meter-cell .meter { display: inline-block; width: 74px; vertical-align: middle; }
.meter-cell b { margin-left: 8px; font-variant-numeric: tabular-nums; font-size: 12.5px; }
.meter.wide { width: 100%; margin: 8px 0 6px; }
/* Tighter inside this table only; .meter.wide is used on the stat cards too,
   where the air is the point. */
.teamgrid .meter.wide { margin: 5px 0 0; height: 5px; }
.meter i.accent { background: var(--accent); }

.sparkline {
  display: flex; align-items: flex-end; gap: 2px;
  height: 30px; width: 132px; min-width: 132px;
}
.sparkline i { flex: 1 1 0; min-width: 2px; border-radius: 1px; }
.sparkline i.good { background: var(--good); }
.sparkline i.mid { background: var(--warn); }
.sparkline i.low { background: var(--idle); }
/* A day with no activity at all. Its own colour rather than the "low" amber,
   because nothing recorded and a little recorded are different facts and the
   column is read by colour before it is read by height. */
.sparkline i.none { background: var(--border); }

.row-actions { width: 44px; text-align: right; }
.row-actions .iconbtn svg { transition: transform .16s ease; }
.row-actions .iconbtn.open svg { transform: rotate(180deg); }

/* ── The detail behind a row ─────────────────────────────────────────── */
.teamdetail > td { background: var(--surface-2); padding: 0; }
.detail { padding: 20px 22px; display: flex; flex-direction: column; gap: 16px; }
.detail-tools { display: flex; gap: 10px; flex-wrap: wrap; }

.card-inset {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 14px 16px; min-width: 0;
}

.vs-line {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 18px; padding: 7px 0; font-size: 13.5px;
}
.vs-line + .vs-line { border-top: 1px solid var(--border); }
.vs-move { font-variant-numeric: tabular-nums; }

.applist-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 88px 62px;
  align-items: center; gap: 12px; padding: 6px 0; font-size: 13px;
}
.applist-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.applist-row b { text-align: right; font-variant-numeric: tabular-nums; font-size: 12.5px; }

/* ── Profile ─────────────────────────────────────────────────────────── */
.modal-back {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(8, 11, 24, .55);
  display: grid; place-items: center; padding: 24px;
}
/* The dialog box inside a .modal-back -- and only that. Unscoped, it also made
   the backdrops in index.html (#dp, #dl, #mp) a 440px opaque box in the corner
   instead of a tint across the window. :where() keeps it as weak as a plain
   .modal, so .range-modal, .jobs-modal and .ovr-modal still set their own widths. */
.modal:where(.modal-back > .modal) {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 26px; width: min(440px, 100%);
  max-height: 88vh; overflow-y: auto; text-align: center;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .3);
}
.modal h3 { margin: 12px 0 18px; font-size: 20px; }
.avatar.lg {
  width: 62px; height: 62px; border-radius: 50%; margin: 0 auto;
  display: grid; place-items: center; font-size: 22px; font-weight: 600;
  background: var(--accent-soft); color: var(--accent);
}
.facts {
  display: grid; grid-template-columns: auto 1fr; gap: 9px 18px;
  text-align: left; margin: 0 0 22px; font-size: 13.5px;
}
.facts dt { color: var(--muted); }
.facts dd { margin: 0; font-weight: 600; text-align: right; }

@media (max-width: 640px) {
  /* On a phone the first row has already wrapped into several, and an auto
     margin on the filter strands it alone on the right of the last of them.
     Let it pack left with the chips it wrapped beside. */
  .rep-bar-left > .scope { margin-left: 0; }
}

/* ── Apps and productivity ───────────────────────────────────────────── */
.appmark {
  flex: none; width: 26px; height: 26px; border-radius: 7px;
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--muted);
  font-size: 12px; font-weight: 700;
}
.linkbtn {
  background: none; border: 0; padding: 0 0 0 4px;
  color: var(--accent); font-size: 12.5px; cursor: pointer;
}
.linkbtn:hover { text-decoration: underline; }

.prod-summary {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 26px; align-items: center;
}
.ring {
  width: 132px; height: 132px; border-radius: 50%;
  display: grid; place-items: center; flex: none;
}
.ring-hole {
  width: 96px; height: 96px; border-radius: 50%;
  background: var(--surface);
  display: grid; place-items: center; align-content: center; text-align: center;
}
.ring-hole b { font-size: 24px; font-variant-numeric: tabular-nums; line-height: 1.1; }
.ring-hole span { font-size: 11px; color: var(--muted); }

.prod-figures {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: 18px; min-width: 0;
}
.prod-figure { min-width: 0; }
.prod-figure .stat-big { font-size: 22px; }
.prod-figure.good .stat-big { color: var(--good); }
.prod-figure.low .stat-big { color: var(--warn); }
.prod-figure.off .stat-big { color: var(--muted); }

.prod-cols {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px; margin-top: 16px;
}
.prod-cols > * { min-width: 0; }
.prod-col { border-top: 3px solid var(--border); }
.prod-col.productive { border-top-color: var(--good); }
.prod-col.neutral { border-top-color: var(--idle); }
.prod-col.unproductive { border-top-color: var(--warn); }
.prod-col-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 2px;
}
.prod-col-head b { font-size: 14px; }
.prod-col-head .count {
  font-size: 11.5px; font-weight: 600; color: var(--muted);
  background: var(--surface-2); border-radius: 999px; padding: 2px 9px;
}
.prod-col .applist-row { grid-template-columns: auto minmax(0, 1fr) auto; }

.cta {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; margin-top: 16px; flex-wrap: wrap;
}
.cta p { margin: 0; max-width: 62ch; }

/* ── Report filters, pagers and exports ──────────────────────────────── */
.head-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.filterbox {
  display: flex; align-items: center; gap: 8px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 8px; padding: 0 10px; min-width: 0;
}
.filterbox:focus-within { border-color: var(--accent); }
.filtericon { color: var(--muted); display: grid; place-items: center; flex: none; }
.filterinput {
  border: 0; background: none; color: var(--text);
  font: inherit; font-size: 13px; padding: 8px 0; width: 190px; min-width: 0;
}
.filterinput:focus { outline: none; }
.filterinput::placeholder { color: var(--muted); }
/* The native clear affordance is inconsistent across engines; Escape clears. */
.filterinput::-webkit-search-cancel-button { -webkit-appearance: none; }

.pagefoot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding-top: 14px; margin-top: 6px; border-top: 1px solid var(--border);
}
/* The count on the left, the Rows picker and the pager together on the
   right. It was a space-between row of three, which put the picker alone
   in the middle of the bar with a gap either side; the owner, 22 October:
   "adjust rows per page slightly just beside the pagination pages, move
   beside it". An auto margin on the count is what pushes the other two
   into one group without naming either of them. */
.pagefoot .hint { margin: 0 auto 0 0; }
/* The same bar above the table. Its rule sits under it rather than over it,
   so the two read as brackets around the rows instead of one line floating
   between the cards and the heading. */
.pagefoot-top {
  padding-top: 0; margin-top: 0;
  padding-bottom: 12px; margin-bottom: 4px;
  border-top: 0; border-bottom: 1px solid var(--border);
}

/* ── Web & app usage, by hour ────────────────────────────────────────── */
.hourlist { display: flex; flex-direction: column; }
.hourrow {
  display: grid;
  grid-template-columns: 62px minmax(90px, 1fr) 66px minmax(0, 1.1fr);
  align-items: center; gap: 14px;
  padding: 9px 0; font-size: 13px;
}
.hourrow + .hourrow { border-top: 1px solid var(--line-soft, var(--border)); }
.hourlabel {
  font-variant-numeric: tabular-nums; color: var(--muted);
  font-size: 12.5px; white-space: nowrap;
}
.hourrow b { text-align: right; font-variant-numeric: tabular-nums; font-size: 12.5px; }
.hourtop { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 760px) {
  .hourrow { grid-template-columns: 54px minmax(70px, 1fr) 60px; }
  .hourtop { display: none; }
  .filterinput { width: 100%; }
  .filterbox { flex: 1 1 180px; }
}

/* The expanded row's panel is pinned to the scroller's width and stuck to its
   left edge, so it stays on screen while the table behind it scrolls. */
.detail-shell {
  position: sticky;
  left: 0;
  max-width: 100%;
}

/* ══════════════════════════════════════════════════════════════════════
   Presentation pass
   ══════════════════════════════════════════════════════════════════════

   The information was right and the screen still read as unfinished. Three
   things were doing it, and none of them was the data:

   1. Every number was the same colour and the same weight, so a card with four
      figures gave no clue which one mattered. Numbers now carry a hue that
      means something -- activity, worked, break, coverage -- and they carry it
      in both themes.

   2. Surfaces sat at one elevation. A card on a background one shade away
      reads as a rectangle drawn on a page rather than a thing on it, which is
      most of why the dark theme in particular looked flat.

   3. Spacing was uniform rather than rhythmic: a label, a figure and a note
      were equally far apart, so nothing grouped.

   Colours are defined as tokens on the bare :root and re-stated for both dark
   paths, so a value never exists only inside a media query. */

:root {
  /* Semantic hues for figures. Distinct from --accent, which is for controls:
     a button and a statistic competing for the same colour is how a screen
     stops telling you where to click. */
  --fig-activity: #b45309;
  --fig-worked:   #3352c9;
  --fig-break:    #a4560c;
  --fig-cover:    #1d6fa5;

  --elev-1: 0 1px 2px rgba(16, 24, 40, .05);
  --elev-2: 0 1px 3px rgba(16, 24, 40, .08), 0 8px 24px rgba(16, 24, 40, .06);
  --elev-3: 0 2px 6px rgba(16, 24, 40, .09), 0 18px 44px rgba(16, 24, 40, .1);

  --ring: 0 0 0 3px rgba(47, 111, 237, .18);
  --radius-lg: 14px;

  /* Five hues for a share -- of a ring, a stack, a legend. Ordered by how
     loud they are, so the largest slice is also the one the eye lands on,
     and kept clear of --accent so a chart never looks clickable. */
  --slice-0: #3b6fe0;
  --slice-1: #12967a;
  --slice-2: #d97a1e;
  --slice-3: #7b5cd6;
  --slice-4: #c9457f;
  --slice-5: #2f9fbf;
  --slice-6: #a0782a;
  --slice-7: #5c6f8a;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* The ground, the surfaces and the border are NOT redefined here.
       They were, in navy, and because this block sits hundreds of lines below
       the palette it silently won: the neutral ground at the top of this file
       was never once on screen, and a screenshot of the application still came
       back blue after it had supposedly been changed.

       One definition, at the top. What this block still owns is everything
       that has no light-mode counterpart above -- figures, elevations, the
       focus ring and the chart slices.

       The lightness separation the old comment here was protecting is kept:
       the surface lifts off the ground by the same 1.15:1 it did, and the
       border reads better against it than it used to (1.65:1, was 1.46). */

    --fig-activity: #f2b04a;
    --fig-worked:   #7fa2ff;
    --fig-break:    #f0a05a;
    --fig-cover:    #5cc6f5;

    --elev-1: 0 1px 2px rgba(3, 5, 18, .5);
    --elev-2: 0 1px 3px rgba(3, 5, 18, .6), 0 10px 30px rgba(3, 5, 18, .45);
    --elev-3: 0 3px 8px rgba(3, 5, 18, .6), 0 22px 54px rgba(3, 5, 18, .55);
    --ring: 0 0 0 3px rgba(79, 125, 251, .3);

    --slice-0: #6f95f5;
    --slice-1: #3fbf9c;
    --slice-2: #eda053;
    --slice-3: #a888ee;
    --slice-4: #ef7aa9;
    --slice-5: #5fc3de;
    --slice-6: #d1a95a;
    --slice-7: #9aa9bd;
  }
}

:root[data-theme="dark"] {
  /* Ground and surfaces: defined once, at the top of this file. */

  --fig-activity: #f2b04a;
  --fig-worked:   #7fa2ff;
  --fig-break:    #f0a05a;
  --fig-cover:    #5cc6f5;

  --elev-1: 0 1px 2px rgba(3, 5, 18, .5);
  --elev-2: 0 1px 3px rgba(3, 5, 18, .6), 0 10px 30px rgba(3, 5, 18, .45);
  --elev-3: 0 3px 8px rgba(3, 5, 18, .6), 0 22px 54px rgba(3, 5, 18, .55);
  --ring: 0 0 0 3px rgba(79, 125, 251, .3);

  --slice-0: #6f95f5;
  --slice-1: #3fbf9c;
  --slice-2: #eda053;
  --slice-3: #a888ee;
  --slice-4: #ef7aa9;
  --slice-5: #5fc3de;
  --slice-6: #d1a95a;
  --slice-7: #9aa9bd;
}

/* ── Cards ───────────────────────────────────────────────────────────── */
.card,
.rep-stat,
.chart-card,
.card-inset {
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-2);
}

.rep-stats {
  gap: 16px;
}

/* A stat card is a label, a figure and a note -- in that order of loudness,
   with a hairline of its own colour along the top so four of them side by side
   are four things rather than one long band. */
.rep-stat {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 20px 22px 18px;
  transition: box-shadow .18s ease, transform .18s ease;
}
.rep-stat::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--tint, var(--accent));
}
.rep-stat:nth-child(1) { --tint: var(--fig-activity); }
.rep-stat:nth-child(2) { --tint: var(--fig-worked); }
.rep-stat:nth-child(3) { --tint: var(--fig-break); }
.rep-stat:nth-child(4) { --tint: var(--fig-cover); }
.rep-stat:hover { box-shadow: var(--elev-3); transform: translateY(-1px); }

.rep-stat .stat-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 10px;
}
.rep-stat .stat-big {
  font-family: var(--font-display, inherit);
  font-size: 30px;
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -.02em;
  color: var(--tint, var(--text));
  font-variant-numeric: tabular-nums;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 2px 8px;
}
.rep-stat .muted { margin-top: 8px; font-size: 12.5px; }
.rep-stat .meter.wide { margin: 12px 0 4px; height: 6px; }

/* The movement chip. Direction always shown, colour only where the caller has
   said which direction is the good one. */
.delta {
  font-size: 11.5px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--muted);
  letter-spacing: 0;
  margin-left: 2px;
}
.delta.tone-good { background: rgba(26, 147, 111, .15); color: var(--good); }
.delta.tone-good { background: color-mix(in srgb, var(--good) 16%, transparent); }
.delta.tone-bad  { background: rgba(199, 119, 0, .16); color: var(--warn); }
.delta.tone-bad  { background: color-mix(in srgb, var(--warn) 18%, transparent); }
.delta.flat { background: none; padding-left: 2px; }

/* ── Controls ────────────────────────────────────────────────────────── */
button.primary {
  /* --accent-fill, not --accent. In dark the accent is lightened so it reads
     as TEXT, and white on that is about 2.1:1 -- the washed-out button the
     note beside --accent-fill was written about. Every other filled control
     was moved across; this one was missed, and it is the class button()
     emits, so it was every primary action in the admin UI. (BUG-078) */
  background: var(--accent-fill, var(--accent));
  color: #fff;
  border: 1px solid transparent;
  font-weight: 600;
  box-shadow: var(--elev-1);
}
button.primary:hover { filter: brightness(1.06); box-shadow: var(--elev-2); }
button.primary:active { filter: brightness(.96); }

.head-tools button:not(.primary):not(.switch),
.detail-tools button {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  font-weight: 600;
  border-radius: 9px;
  padding: 8px 14px;
  transition: border-color .15s ease, background .15s ease;
}
.head-tools button:not(.primary):hover,
.detail-tools button:hover { border-color: var(--accent); color: var(--accent); }

/* No glow here either; the outline rule below covers the keyboard.
   Not .iconbtn: this selector outranks the ring given at the top of the
   file (two classes beat an element), so naming it here left every icon
   button -- the way into a project, the row menus, Refresh -- with no
   visible focus at all. */
button:focus-visible,
.filterbox:focus-within { outline: none; }

.seg {
  background: var(--surface-2);
  border-radius: 10px;
  padding: 3px;
  gap: 2px;
}
.seg button {
  border-radius: 7px;
  font-weight: 600;
  font-size: 12.5px;
  padding: 7px 14px;
  color: var(--muted);
  border: 0;
  background: none;
}
.seg button:hover { color: var(--text); }
.seg button.active {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--elev-1);
}

.chip { border-radius: 9px; font-weight: 600; transition: border-color .15s ease; }
.chip.active { box-shadow: var(--elev-1); }

/* ── Tables ──────────────────────────────────────────────────────────── */
.grid thead th {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 14px;
  padding-bottom: 12px;
  background: var(--surface);
  position: sticky;
  top: 0;
  z-index: 1;
}
.teamgrid tbody tr:not(.teamdetail) { transition: background .14s ease; }
.teamgrid tbody tr:not(.teamdetail):hover { background: var(--surface-2); }
/* ── Team Report: one spine per column, and rows that fit ──────────────
   Reported 27 October 2026 with a screenshot: "no proper alignment ...
   utilise the spaces try to pack them closer".

   What made it look untidy was not any one cell. Every column stacked a
   figure, a bar and a change chip, and the three sat on DIFFERENT edges:
   Worked was right-aligned because it carries .num, while Activity and
   Productivity were left-aligned because .meter-cell is not. The headings
   were left-aligned over all of them. So no two things in a column shared a
   vertical edge, going down a table of sixty-seven people.

   Every figure column now shares one right-hand spine: the number on it, the
   bar spanning the cell beneath, the change chip back on it. The heading
   sits on it too. */
.teamgrid th.tg-num { text-align: right; }
.teamgrid td.meter-cell { text-align: right; }
.teamgrid td.meter-cell .meter {
  display: block; width: 100%; margin: 5px 0 0; vertical-align: baseline;
}
.teamgrid td.meter-cell b { margin-left: 0; display: block; }
/* The chip follows the same edge rather than starting a third one. */
.teamgrid td .delta { display: inline-block; margin-top: 5px; }

/* Packed closer.
   14px of padding above and below, plus a three-element stack and an 8/6
   margin on the bar, is what gave rows of about 130 pixels -- five people on
   a laptop screen, out of sixty-seven. This takes roughly a third out
   without the stack touching. */
.teamgrid td { padding-top: 9px; padding-bottom: 9px; }
.teamgrid td.meter-cell .meter { height: 5px; }
.teamgrid .person .muted.tiny { margin-top: 1px; }

.avatar.sm {
  width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent);
  font-size: 11.5px; font-weight: 700; flex: none;
}

.pill { padding: 4px 11px; font-size: 11px; letter-spacing: .01em; }
.pill.on { background: rgba(26, 147, 111, .14); color: var(--good); }
.pill.on { background: color-mix(in srgb, var(--good) 15%, transparent); }

.meter { height: 6px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.meter i { display: block; height: 100%; border-radius: 999px; }

/* ── Panels inside an expanded row ───────────────────────────────────── */
.teamdetail > td { border-top: 1px solid var(--border); }
.detail { padding: 22px 24px 26px; }
.card-inset { box-shadow: none; border-radius: 12px; background: var(--bg); }
.card-inset > .muted.tiny {
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  font-size: 10.5px;
  margin-bottom: 12px;
}

/* ── Productivity ────────────────────────────────────────────────────── */
.prod-summary { padding: 26px; }
.prod-figure .stat-big { font-family: var(--font-display, inherit); font-weight: 700; }
.prod-col { border-radius: var(--radius-lg); }

/* ── Pane headings ───────────────────────────────────────────────────── */
.pane-head h2 {
  font-family: var(--font-display, inherit);
  letter-spacing: -.018em;
}
.rep-head { align-items: flex-start; margin-bottom: 18px; }

/* ── Report scope picker ─────────────────────────────────────────────── */
.scope { position: relative; }
.scope-trigger {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 9px 14px; cursor: pointer; font-weight: 600;
}
.scope-trigger b { font-size: 10px; opacity: .65; font-weight: 700; }
/* The names, capped.
 *
 * Measured on 21 October: the label had white-space: normal, overflow
 * visible and no max-width, so "Venkata Subrahmanya Sastry Bhattiprolu"
 * took the trigger from 173px to 313px and moved every chip beside it.
 * Now it clips at 320 -- comfortably three ordinary names -- and the
 * trigger's title carries the full list for the ones it cannot show. */
.scope-trigger > span {
  max-width: 320px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.scope-icon { display: grid; place-items: center; color: var(--muted); }
.scope-trigger.active { border-color: var(--accent); color: var(--accent); }
.scope-trigger.active .scope-icon { color: var(--accent); }

/* Hung from its RIGHT edge, because the trigger moved.
 *
 * The filter now ends the first row, and a panel anchored left: 0 from there
 * opens into the space past the window: the owner photographed it on 21
 * October with Filter Options, the search box and all three names sliced off
 * down the right-hand side -- "i got hidden inside so bring it left side".
 * right: 0 pins its right edge to the trigger's and it grows leftwards,
 * where the room actually is.
 *
 * Wider with it: 360 was chosen when the panel sat at the left of the bar
 * with the whole report beside it, and a people filter is mostly names.
 *
 * And it scrolls as one box. A flex column with the list as the only
 * shrinking child keeps the heading, the search, Teams and the sort
 * picklists reachable at any window height, instead of running off the
 * bottom of a short one with no way down. The list is flex: 0 1 auto rather
 * than 1 1 auto deliberately -- three people should make a short panel, not
 * a tall one with a stretched gap in it.
 */
.scope-panel {
  position: absolute; right: 0; left: auto; top: calc(100% + 8px); z-index: 120;
  display: none; flex-direction: column;
  width: min(420px, calc(100vw - 40px));
  max-height: min(560px, calc(100vh - 150px));
  /* Clipped, NOT scrolled -- and the difference is the whole of this bug.
   *
   * It was overflow-y: auto, meant as a backstop. What it actually did was
   * make the panel a scroll container WRAPPED AROUND ANOTHER ONE: the
   * people list inside it is 130px tall over 3118px of names. A nested
   * scroller's content still counts towards its ancestor's scroll range in
   * Chrome, so the panel reported scrollHeight 3246 against clientHeight
   * 373 -- nine screens of scroll over four hundred pixels of content.
   *
   * Nothing is down there. One flick of a trackpad over the panel drags
   * every child off the top and leaves a white box with nothing in it,
   * which is what the owner photographed: "why we have blank in filter??
   * alos in mobile". Measured: scrollTop 900 put the last child at -582.
   *
   * overflow: clip clips exactly as hidden does and is not a scroll
   * container at all, so there is no scroll range to flick through. The
   * Teams picker still cannot spill, because the list above it shrinks
   * first (min-height: 0, below) and the fixed parts come to 190px against
   * a 240px floor on the panel's own height.
   *
   * The select's own popup is immune: it is appended to document.body and
   * positioned fixed, so no ancestor's overflow reaches it. */
  overflow: clip;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg, 14px); padding: 18px;
  box-shadow: var(--elev-3);
}
.scope-panel.show { display: flex; }
/* Everything holds its height; only the list gives way. min-height: 0 is
   what lets a flex item shrink below its content at all, and without it the
   panel grows past its own max-height and the scrollbar never appears. */
.scope-panel > * { flex: none; }
.scope-panel > .scope-list { flex: 0 1 auto; min-height: 0; }

/* On a phone it is a sheet at the bottom of the screen.
 *
 * It has to be, and the numbers say why. By the time the first row has
 * wrapped into three on a 390px screen the trigger is 290px down, which
 * leaves 530 of an 820px window for a panel that wants 560 -- so an absolute
 * panel hangs off the bottom, and .rep-bar is sticky, so scrolling the page
 * takes the panel down with it and the bottom is never reachable. Hung from
 * the right edge it was worse: 350px wide from a trigger 189px in put its
 * left edge at -161.
 *
 * Fixed to the viewport, it does not care where the bar wrapped to.
 *
 * MUST stay below the rule it overrides. The first version of this sat in
 * the toolbar section 500 lines above .scope-panel, and a media query adds
 * no specificity -- equal selectors are settled by source order, so it lost
 * every time and the panel stayed off the left of the screen.
 */
@media (max-width: 640px) {
  .scope-panel {
    position: fixed; left: 16px; right: 16px; width: auto;
    top: auto; bottom: 16px; max-height: calc(100vh - 150px);
  }
}

.scope-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 14px;
}
.scope-head b { font-size: 15px; }
.iconbtn.ghost { border: 0; font-size: 18px; line-height: 1; }

.scope-section {
  display: block;
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
  margin: 14px 0 7px;
}
.scope-panel > .scope-section:first-of-type { margin-top: 0; }

.scope-search, .scope-select {
  width: 100%; font: inherit; font-size: 13px;
  padding: 9px 11px; border-radius: 9px;
  /* background-color, not background: the shorthand would reset the chevron
     that the select rule below sets, and a class wins over an element selector
     whatever the order. */
  background-color: var(--bg); color: var(--text);
  border: 1px solid var(--border);
}
.scope-select { padding-right: 34px; }
.scope-search:focus, .scope-select:focus { outline: none; border-color: var(--accent); }
.scope-search::-webkit-search-cancel-button { -webkit-appearance: none; }

.scope-list {
  /* The cap is the panel's now, not a fixed 190px: the list takes whatever
     height is left after the headings and the pickers, down to a floor that
     always shows a few names. */
  max-height: none; min-height: 112px; overflow-y: auto;
  margin-top: 8px; display: flex; flex-direction: column; gap: 2px;
}
.scope-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 9px; cursor: pointer; font-size: 13px;
}
.scope-row:hover { background: var(--surface-2); }
.scope-row .avatar.sm { width: 30px; height: 30px; font-size: 11px; }

/* The checkbox is real and stays real -- the keyboard and the screen reader
   both need it -- but it is drawn as a tick on the right of the row rather
   than a box on its left. Hidden with clip rather than display:none, because
   a display:none control is not focusable and Tab would skip the whole list. */
.scope-box {
  position: absolute; width: 1px; height: 1px;
  clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden;
}
.scope-names { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.scope-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Which team, because two people across sixty can share a name. */
.scope-sub {
  font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.scope-tick { flex: none; color: var(--accent); opacity: 0; line-height: 0; }
.scope-row.scope-chosen { background: var(--accent-soft); }
.scope-row.scope-chosen .scope-tick { opacity: 1; }
.scope-row.scope-chosen .scope-name { font-weight: 600; }
/* The focus ring belongs on the row, since the control itself is not visible. */
.scope-box:focus-visible + .avatar,
.scope-row:focus-within { outline: 2px solid var(--accent); outline-offset: -2px; }

.scope-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.scope-field { display: block; min-width: 0; }

/* Clearing the filter, next to the control it clears rather than inside it.
   Shown only when there is something to clear, so it is never a button that
   does nothing. */
.scope-clear {
  display: inline-flex; align-items: center; gap: 6px;
  /* Away from the picker it undoes. They are appended as siblings with no
     whitespace between them, so without this they touch. */
  margin-left: 10px;
  color: var(--muted);
}
.scope-clear:hover { color: var(--bad); border-color: var(--bad); }
.scope-clear.hidden { display: none; }

/* Who is selected, as chips you can take back out.
 *
 * The checkbox list below does the choosing, but with sixty people the chosen
 * ones scroll out of sight the moment somebody types a name -- so "who have I
 * picked?" needed scrolling and remembering, and removing one meant finding
 * that row again. These sit above the search and each carries its own cross.
 *
 * Named under .scope- like everything else in this panel: the stylesheet is one
 * long file and a class called .chip or .chips would collide with something,
 * which has already happened here three times.
 */
.scope-chips {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 2px 0 10px;
}
.scope-chips.hidden { display: none; }

.scope-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 6px 4px 10px;
  border-radius: 999px;
  background: var(--accent-soft, rgba(99, 102, 241, .12));
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 12.5px; line-height: 1.4;
  max-width: 100%;
}
/* A team reads as a team, not as a person with an unusual name. */
.scope-chip-team {
  background: var(--surface-2);
  border-color: var(--accent);
  font-weight: 600;
}
.scope-chip > span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 190px;
}

/* A real button, not a glyph in a span: it is the only way out of a selection
   for somebody using a keyboard, and it needs a focus ring for the same
   reason. Sized to the 24px minimum a fingertip can reliably hit. */
.scope-chip-x {
  flex: none;
  width: 18px; height: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 999px;
  background: transparent; color: var(--muted);
  font-size: 15px; line-height: 1; cursor: pointer;
  padding: 0;
}
.scope-chip-x:hover { background: var(--danger, #e5484d); color: #fff; }
.scope-chip-x:focus-visible {
  outline: 2px solid var(--accent, #6366f1); outline-offset: 1px;
}

/* One button left in here now, so it sits on its own rather than in a pair. */
.scope-actions:has(> button:only-child) { justify-content: flex-start; }

/* ── Home: the day at a glance ───────────────────────────────────────── */
.home-cards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 12px; margin-bottom: 14px;
}
/* Sized down on 24 September against the product this one is modelled on,
   whose equivalent four sit in about two thirds of the height. Four cards
   carrying one figure each had been taking a third of the screen before the
   table they introduce. */
.home-card { padding: 14px 16px; }
.home-card .stat-big {
  font-family: var(--font-display, inherit);
  font-size: 26px; font-weight: 700; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; margin: 2px 0 8px;
}
.home-card.flagged .stat-big { color: var(--warn); }
.home-card .muted { margin: 0; font-size: 12px; line-height: 1.45; }

.statebar {
  display: flex; height: 8px; border-radius: 999px; overflow: hidden;
  background: var(--surface-2); margin-bottom: 10px;
}
.statebar i { display: block; }
.statebar i.on { background: var(--good); }
.statebar i.brk { background: var(--warn); }
.statebar i.off { background: var(--idle); opacity: .35; }

.home-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 18px; flex-wrap: wrap; margin-bottom: 10px;
}
.home-head h3 { margin: 0 0 2px; font-size: 17px; }
.home-key { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--muted); }
.home-key span { display: inline-flex; align-items: center; gap: 6px; }
.swatch { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }
.swatch.on { background: var(--good); }
.swatch.brk { background: var(--warn); }
.swatch.off { background: var(--surface-2); border: 1px solid var(--border); }

/* ── "This copy is out of date" ─────────────────────────────────────────
   A strip at the top of the content, not a modal over it. Interrupting
   somebody's work to tell them a version number is a poor trade, and a modal
   that must be dismissed before anything can be done gets dismissed without
   being read.

   Coloured as a warning rather than an error, because nothing is broken: the
   day is still being tracked, the figures are still right, and the only thing
   wrong is that this machine will not pick up the next build on its own. */
.update-note {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
  margin: 0 0 18px;
  padding: 14px 18px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--warn) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warn) 42%, transparent);
  color: var(--text);
  font-size: 13.5px; line-height: 1.5;
}
/* Nothing is wrong when the machine updates itself, so it is not painted as a
   warning: this one is telling you what will happen, not asking for anything. */
.update-note-quiet {
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 38%, transparent);
}
.update-note-text { min-width: 0; flex: 1 1 420px; }
.update-note-text b { display: block; margin-bottom: 2px; }
.update-note-actions { display: flex; gap: 10px; flex: none; }
.update-note-actions .btn { text-decoration: none; display: inline-flex;
  align-items: center; }

/* ── Choosing who the day below shows ────────────────────────────────────
   The figures at the top and this picklist are one control in two places: the
   figure is the quick way in ("show me those four"), the picklist reaches the
   states that have no figure of their own, and either one moves the other.

   A selected figure has to look selected, or clicking it and watching rows
   disappear reads as the page losing data rather than answering a question. */
.home-showing { display: flex; align-items: center; gap: 8px; }
.home-show {
  padding: 6px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--surface);
  color: var(--text); font: 600 12.5px/1.2 inherit; cursor: pointer;
}
.home-show:hover { border-color: var(--accent); }

.home-pick { cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease; }
.home-pick:hover { border-color: var(--accent); }
.home-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* home-picked, not picked: .picked is taken further down this file by a muted
   text style, and a card wearing it would come out at 12.5px and grey. */
.home-pick.home-picked {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
/* What the click did, said in words next to the figure rather than left to be
   inferred from the border. */
.home-pick.home-picked .stat-label::after {
  content: " · showing";
  color: var(--accent); font-weight: 700;
}

.home-summary {
  margin: 0 0 16px; font-size: 13px; color: var(--muted);
  padding-bottom: 14px; border-bottom: 1px solid var(--border);
}

/* Fixed layout, with real widths on the three narrow columns. The day column
   then takes whatever is left, which is a number -- and the flex blocks inside
   it can divide something finite.

   Under the default auto layout, `width: 100%` on a cell is circular: the table
   sizes to its content and the content asks for all of the table. That row
   measured 666,667 pixels wide, and each half-hour block came out at 13,878. */
/* Its own table, not a .grid: that class sets display:block so a wide table can
   scroll inside its card, and table-layout has no effect on a block. This one
   needs the fixed layout, because its middle column has to be a known width for
   the forty-eight blocks inside it to divide. */
.daygrid {
  display: table;
  table-layout: fixed;
  width: 100%;
  min-width: 720px;
  border-collapse: collapse;
}
.daygrid th {
  text-align: left;
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
  padding: 0 10px 12px; border-bottom: 1px solid var(--border);
}
.daygrid td {
  padding: 12px 10px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.daygrid tbody tr:last-child td { border-bottom: 0; }
.daygrid th:nth-child(1), .daygrid td:nth-child(1) { width: 210px; }
.daygrid th:nth-child(3), .daygrid td:nth-child(3) { width: 132px; }
.daygrid th:nth-child(4), .daygrid td:nth-child(4) { width: 86px; }
.daygrid td { padding-top: 12px; padding-bottom: 12px; }
.daygrid td:nth-child(1) { overflow: hidden; }
.daygrid .person b,
.daygrid .person .tiny { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Forty-eight half-hour blocks. Darker means more input, which is a claim
   about that half hour rather than about the day. */
.daystrip { display: flex; gap: 2px; min-width: 0; }
.blk {
  flex: 1 1 0; min-width: 3px; height: 26px; border-radius: 2px;
  background: var(--surface-2); position: relative;
}
.blk.l1 { background: color-mix(in srgb, var(--good) 30%, var(--surface-2)); }
.blk.l2 { background: color-mix(in srgb, var(--good) 50%, var(--surface-2)); }
.blk.l3 { background: color-mix(in srgb, var(--good) 72%, var(--surface-2)); }
.blk.l4 { background: var(--good); }
.blk.l5 { background: var(--warn); }
/* Fallbacks first for engines without color-mix, so the shading still reads. */
.blk.l1 { background: rgba(26, 147, 111, .3); }
.blk.l2 { background: rgba(26, 147, 111, .5); }
.blk.l3 { background: rgba(26, 147, 111, .72); }
.blk.l1 { background: color-mix(in srgb, var(--good) 30%, var(--surface-2)); }
.blk.l2 { background: color-mix(in srgb, var(--good) 50%, var(--surface-2)); }
.blk.l3 { background: color-mix(in srgb, var(--good) 72%, var(--surface-2)); }

.blk.now { box-shadow: 0 0 0 2px var(--accent); z-index: 1; }

.home-axis {
  display: flex; justify-content: space-between;
  font-size: 11px; color: var(--muted); margin-top: 8px;
  padding-left: 210px; font-variant-numeric: tabular-nums;
}

.livedot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.livedot.working { background: var(--good); }
.livedot.break { background: var(--warn); }
.livedot.off { background: var(--idle); opacity: .5; }

.daygrid .num { white-space: nowrap; font-variant-numeric: tabular-nums; }
.daygrid .num b { font-size: 14px; }

@media (max-width: 760px) {
  .home-axis { padding-left: 0; }
}

/* ── Date range picker ───────────────────────────────────────────────── */
.range-modal {
  width: min(860px, 100%); max-width: 100%; text-align: left;
  padding: 0; overflow: hidden; max-height: 88vh;
  display: flex; flex-direction: column;
}
.range-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 18px 22px; border-bottom: 1px solid var(--border);
}
.range-head b { font-size: 16px; }
.range-body { display: grid; grid-template-columns: 232px minmax(0, 1fr); min-height: 0; }
.range-quick {
  border-right: 1px solid var(--border); padding: 16px 14px;
  overflow-y: auto; display: flex; flex-direction: column; gap: 2px;
}
.range-quick .scope-section { margin: 0 0 8px 8px; }
.quickitem {
  text-align: left; background: none; border: 0; width: 100%;
  padding: 10px 12px; border-radius: 9px; cursor: pointer;
  font-size: 13.5px; color: var(--text);
}
.quickitem:hover { background: var(--surface-2); }

.range-right { padding: 16px 20px 20px; overflow-y: auto; min-width: 0; }
.range-tabs { display: inline-flex; margin-bottom: 16px; align-self: flex-start; }
.range-pane { min-width: 0; }

.range-list { display: flex; flex-direction: column; gap: 3px; max-height: 340px; overflow-y: auto; }
.range-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }
.rangeopt {
  display: flex; align-items: center; gap: 11px; width: 100%;
  background: none; border: 0; border-radius: 9px; cursor: pointer;
  padding: 10px 12px; font-size: 13.5px; color: var(--text); text-align: left;
}
.rangeopt:hover { background: var(--surface-2); }
.rangeopt:disabled { opacity: .38; cursor: default; }
.rangeopt:disabled:hover { background: none; }
.tick {
  width: 17px; height: 17px; border-radius: 5px; flex: none;
  border: 1.5px solid var(--border); display: inline-block; position: relative;
}
.rangeopt.on .tick { background: var(--accent); border-color: var(--accent); }
.rangeopt.on .tick::after {
  content: ""; position: absolute; left: 5px; top: 1px;
  width: 4px; height: 9px; border: solid #fff;
  border-width: 0 2px 2px 0; transform: rotate(45deg);
}

/* ── Calendar ────────────────────────────────────────────────────────── */
.cal { min-width: 0; }
.cal-nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 10px;
}
.cal-nav b { font-size: 14px; }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.cal-dow {
  text-align: center; font-size: 10.5px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
  padding-bottom: 6px;
}
.cal-day {
  aspect-ratio: 1; border: 0; background: none; border-radius: 9px;
  font-size: 13px; color: var(--text); cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.cal-day:hover:not(:disabled) { background: var(--surface-2); }
.cal-day.outside { color: var(--muted); opacity: .5; }
.cal-day.today { font-weight: 700; box-shadow: inset 0 0 0 1.5px var(--border); }
.cal-day.chosen { background: var(--accent-fill, var(--accent)); color: #fff; font-weight: 700; }
.cal-day:disabled { opacity: .28; cursor: default; }

.range-custom { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
.range-foot {
  grid-column: 1 / -1; display: flex; align-items: center;
  justify-content: space-between; gap: 16px; flex-wrap: wrap;
  padding-top: 16px; border-top: 1px solid var(--border);
}
.range-foot .hint { margin: 0; }

/* ── Team row detail ─────────────────────────────────────────────────── */
.workedcell { min-width: 132px; }
.workedcell b { display: block; font-size: 14px; }
.workedcell .meter { margin: 6px 0 4px; width: 96px; }
.workedcell .delta { margin-left: 0; font-size: 11px; }
.breakcell { color: var(--warn); }
.meter-cell b.good { color: var(--good); }
.meter-cell b.mid { color: var(--warn); }
.meter-cell b.low { color: var(--bad); }
/* The change pill under a figure.
 *
 * display:block made it fill the cell, so its background ran the whole column
 * width and the pills in the three figure columns started at different places
 * and sat at different heights -- which reads as a layout fault rather than as
 * three comparable numbers (BUG-060). width:max-content keeps the block (it is
 * still on its own line) while the background hugs the text.
 */
.meter-cell .delta {
  display: block; width: max-content; margin: 4px 0 0;
}
/* All three figure cells pinned to the same top, so the pills line up across
   a row however tall the cell above them is. */
.meter-cell { vertical-align: top; }

@media (max-width: 820px) {
  .range-body { grid-template-columns: minmax(0, 1fr); }
  .range-quick { border-right: 0; border-bottom: 1px solid var(--border); }
  .range-custom, .range-cols { grid-template-columns: minmax(0, 1fr); }
}

/* The hours, above the blocks they label. Every second hour, because 24 labels
   in this width become unreadable and the reader can count two. */
.hourruler {
  display: flex; justify-content: space-between;
  font-size: 10px; font-weight: 600; letter-spacing: .02em;
  color: var(--muted); font-variant-numeric: tabular-nums;
  text-transform: none;
}
.hourruler span { flex: 1 1 0; text-align: left; }
.hourruler span:last-child { flex: 0 0 auto; }

.daygrid th.daycol { padding-bottom: 8px; }
.pill.idle { background: var(--amber-soft, var(--surface-2)); color: var(--warn); }
.daygrid .num b.good { color: var(--good); }
.daygrid .num b.mid { color: var(--warn); }
.daygrid .num b.low { color: var(--bad); }

/* Avatars take a hue from the name, so a face is findable in a list rather than
   being one more blue rectangle. Both the fill and the text come from the same
   hue so the pair reads as one object; the text is the hue darkened rather than
   black, which would sit on the fill like a sticker. */
.avatar {
  --hue: 212;
  background: hsl(var(--hue) 62% 92%);
  color: hsl(var(--hue) 68% 32%);
  border-radius: 50%;
  display: grid; place-items: center; flex: none;
  font-weight: 700; letter-spacing: .01em;
  width: 36px; height: 36px; font-size: 13px;
}
.avatar.sm { width: 32px; height: 32px; font-size: 11.5px; }
.avatar.lg { width: 62px; height: 62px; font-size: 22px; margin: 0 auto; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .avatar {
    background: hsl(var(--hue) 48% 26%);
    color: hsl(var(--hue) 82% 82%);
  }
}
:root[data-theme="dark"] .avatar {
  background: hsl(var(--hue) 48% 26%);
  color: hsl(var(--hue) 82% 82%);
}

/* ── Home greeting ───────────────────────────────────────────────────── */
/* On the h2 itself rather than under .home-greet: packReportHeader moves
   this element into the toolbar, and a descendant selector cannot follow it.
   14px rather than the 12 that was intended here, because the gap it was
   meant to give never rendered at all and the two read as one word. */
.home-hello { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 0; }
/* The date line. Styled as a hint was, but deliberately not CALLED one --
   see renderRepHome: p.hint in a report head is swept behind the "?". */
.home-when { margin: 6px 0 0; color: var(--muted); font-size: 13px; }
/* On the greeting's own line now, so it takes the flex gap instead of a
   margin. Still wraps under on a narrow screen, because .home-hello wraps. */
.home-hello .home-when { margin: 0; font-weight: 400; }
.livepill { font-size: 11px; padding: 4px 11px; }
.youare { font-size: 13px; white-space: nowrap; }
.youare b { color: var(--text); }
.home-card .muted.tiny { margin-top: 4px; opacity: .85; }
.home-card .muted .delta { margin-left: 8px; }
/* A line that is only the movement chip. Flush under the sentence above, the
   pill reads as that sentence wrapping rather than as a fact of its own. */
.home-card p.home-delta { margin-top: 7px; }
.home-card p.home-delta .delta { margin-left: 0; }

/* Two by two, because the four figures are two pairs: what the day has been,
   and what is happening right now. auto-fit put three across and dropped the
   fourth onto a row of its own, which reads as something having gone wrong. */
.home-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 1500px) { .home-cards { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 700px)  { .home-cards { grid-template-columns: minmax(0, 1fr); } }

/* ── Home row analytics ──────────────────────────────────────────────── */
/* A stripe down the left of anybody who has moved against their own normal, so
   a list of sixty can be skimmed for the rows worth reading. */
.daygrid tbody tr.flagged td:first-child { position: relative; }
.daygrid tbody tr.flagged td:first-child::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 3px; border-radius: 2px;
}
.daygrid tbody tr.flagged.down td:first-child::before { background: var(--warn); }
.daygrid tbody tr.flagged.up td:first-child::before { background: var(--good); }

/* The badge sits against the last worked block, where the eye already is. */
.blk { position: relative; }
.stripbadge {
  position: absolute; left: 100%; top: 50%; transform: translateY(-50%);
  margin-left: 4px; z-index: 2; white-space: nowrap;
  font-size: 10px; font-weight: 700; padding: 3px 7px; border-radius: 5px;
  font-variant-numeric: tabular-nums;
}
/* ...and it must not leave the column it belongs to. Positioned at left:100%
   of the last worked block, a badge on a block at the right-hand edge hung
   over the next column -- over the Activity figure, on the row it describes.
   Clipped rather than repositioned: the badge is an annotation on the strip,
   and an annotation that escapes its strip is worse than one that is cut. */
.daygrid td { overflow-x: clip; }
.stripbadge.down { background: var(--warn); color: #1a1200; }
.stripbadge.up { background: var(--good); color: #04140d; }

/* The current moment, labelled: an unlabelled line is just a line. */
.nowline {
  position: absolute; top: 4px; bottom: 4px; width: 2px;
  background: var(--accent); z-index: 3; pointer-events: none;
}
.nowline b {
  position: absolute; left: 50%; top: -9px; transform: translateX(-50%);
  background: var(--accent-fill, var(--accent)); color: #fff;
  font-size: 9.5px; font-weight: 700; padding: 2px 6px; border-radius: 5px;
  white-space: nowrap; font-variant-numeric: tabular-nums;
}

.banddot { width: 7px; height: 7px; border-radius: 50%; flex: none; display: inline-block; }
.banddot.good { background: var(--good); }
.banddot.mid { background: var(--warn); }
.banddot.low { background: var(--bad); }

/* The flexing happens inside the cell, not on it: a td that becomes a flex
   container leaves the table layout, and Activity ended up stacked above Total
   instead of beside it. */
.cellflex { display: flex; align-items: center; gap: 8px; min-width: 0; }
.cellflex.end { justify-content: flex-end; }
.daygrid td.num .meter { width: 52px; }
.trend { font-style: normal; font-size: 10px; }
.trend.down { color: var(--warn); }
.trend.up { color: var(--good); }

.swatch.spike { background: var(--unusual); }

/* The rail's own face, filled once we know whose it is. */
/* Slightly larger than the other small avatars: this one carries the presence
   dot in its corner, and at 22px the dot was half the face. */
#rail-face { width: 26px; height: 26px; font-size: 10.5px; }

/* ── Retention panel ─────────────────────────────────────────────────── */
.retention { margin-top: 18px; }
.retention h3 { margin: 0 0 4px; font-size: 16px; }
.retention-body { margin-top: 16px; }
.retention-figures {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 18px; margin-bottom: 18px;
}
.retention-figures .stat-big {
  font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums;
  margin: 4px 0 2px; font-family: var(--font-display, inherit);
}
.danger-note {
  border-left: 3px solid var(--bad); padding-left: 12px;
  margin: 0 0 16px; max-width: 62ch;
}

/* A destructive button should look like one, and should not be the same shape
   as Save. */
button.danger {
  background: var(--bad); color: #fff; border: 1px solid transparent;
  font-weight: 600; padding: 10px 18px; border-radius: 9px;
  box-shadow: var(--elev-1); cursor: pointer;
}
button.danger:hover { filter: brightness(1.07); box-shadow: var(--elev-2); }
button.danger:active { filter: brightness(.94); }
button.danger:disabled { opacity: .6; cursor: default; filter: none; }

/* ══════════════════════════════════════════════════════════════════════
   Controls
   ══════════════════════════════════════════════════════════════════════

   Every button looked the same, so the screen never said which one was the
   point. Three weights now: one primary per view, outlines for the rest,
   ghosts for things that only reveal or dismiss. Destructive is its own,
   above.

   Motion is small and fast -- 120ms, a pixel of lift. Anything larger reads
   as decoration on a screen somebody uses all day, and anything slower feels
   like the app is thinking. */
button {
  font-family: inherit;
  transition: background .12s ease, border-color .12s ease,
              color .12s ease, box-shadow .12s ease, transform .12s ease;
}
button:not(:disabled) { cursor: pointer; }
button:disabled { cursor: default; opacity: .5; }

button.primary:hover:not(:disabled) { transform: translateY(-1px); }
button.primary:active:not(:disabled) { transform: translateY(0); }

/* The default: an outline that fills on hover rather than changing shape.

   BUG-164: a switch IS a <button> inside a .card, and this selector --
   eleven :not() clauses deep -- outranks `.switch.on { background:
   var(--accent) }` further up. So every toggle on Settings was painted
   var(--surface) and On and Off came out the SAME colour: measured
   white in light and rgb(31,39,51) in dark, on and off alike. The On
   rule was right the whole time and never reached the screen, which is
   why reading the stylesheet said it was fine.

   Fixed by excluding switches here rather than by adding weight to
   .switch.on: a specificity war in a file this size is how the next
   one of these gets made. */
.card button:not(.primary):not(.danger):not(.iconbtn):not(.linkbtn):not(.seg button):not(.pagebtn):not(.pj-morebtn):not(.pj-clear):not(.pj-taskname):not(.pj-stagestep):not(.appname-open):not(.srow-why):not(.switch),
.head-tools button:not(.primary),
.detail-tools button:not(.switch),
.scope-actions button:not(.primary):not(.switch) {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 9px;
  padding: 9px 15px;
  font-weight: 600;
  font-size: 13px;
}
.card button:not(.primary):not(.danger):not(.iconbtn):not(.linkbtn):not(.pagebtn):not(.pj-morebtn):not(.pj-clear):not(.pj-taskname):not(.pj-stagestep):not(.appname-open):not(.srow-why):hover:not(:disabled),
.head-tools button:not(.primary):hover:not(:disabled),
.detail-tools button:hover:not(:disabled),
.scope-actions button:not(.primary):hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}

.iconbtn:hover:not(:disabled) { transform: translateY(-1px); }
.iconbtn:active:not(:disabled) { transform: translateY(0); }

/* Keyboard focus is visible on everything that takes it, and never only on
   hover -- a control reachable by tab and invisible when reached is a control
   somebody cannot use. */
/* Not input or select. Those already say they have focus by turning their
   border accent (see the rule near the top of this file), and adding a 2px
   outline two pixels outside it drew a second, detached ring around the box --
   a blue bubble round every search field. That is the same "two conventions
   for one state, both painted" the note up there was written about; this rule
   quietly reintroduced it for text fields. A button has no border to turn, so
   it keeps the ring. */
button:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 8px;
}

/* Rows that do something when clicked say so before they are clicked. */
tr.clickable, .memberchip, .quickitem, .rangeopt, .scope-row {
  transition: background .12s ease, border-color .12s ease, transform .12s ease;
}
.memberchip:active { transform: translateY(0); }

/* Inputs match the buttons rather than the browser. */
input[type="text"], input[type="number"], input[type="email"],
input[type="password"], input[type="date"], input[type="search"], select, textarea {
  font-family: inherit; font-size: 13.5px;
  background-color: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 9px;
  padding: 9px 12px;
  transition: border-color .12s ease, box-shadow .12s ease;
}
input:hover:not(:disabled), select:hover:not(:disabled) { border-color: var(--muted); }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent);
}
input:disabled, select:disabled { opacity: .55; cursor: default; }

/* Settings rows: a label, a control, and a line of help under it. The help was
   the same weight as the label, so the pair read as two settings. */
.setting-row { padding: 16px 0; border-bottom: 1px solid var(--border); }
.setting-row:last-child { border-bottom: 0; }
.setting-row:hover { background: transparent; }
.setting-row .help, .setting-row .hint {
  font-size: 12.5px; color: var(--muted); margin: 6px 0 0; max-width: 68ch;
}

@media (prefers-reduced-motion: reduce) {
  button, .memberchip, .iconbtn, tr.clickable { transition: none !important; }
  button:hover, .iconbtn:hover { transform: none !important; }
}

/* ── Screenshot member cards ─────────────────────────────────────────── */
.shotcard-stats { gap: 10px; padding-top: 2px; }
.shotpct { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.shotpct.high { color: var(--good); }
.shotpct.moderate { color: var(--warn); }
.shotpct.low { color: var(--bad); }

/* A verdict, not a label: HIGH beside 83% is one statement. */
.band {
  font-size: 10px; font-weight: 700; letter-spacing: .07em;
  padding: 3px 8px; border-radius: 5px; white-space: nowrap;
}

/* Denser than the row sparkline: this one has a whole day in a card's width. */
.shotcard .spark { height: 22px; gap: 1.5px; }
.shotcard .spark i { border-radius: 1px; min-width: 2px; }

.shotcard-context {
  display: flex; align-items: center; gap: 7px; min-width: 0;
  font-size: 12.5px; color: var(--muted); padding: 2px 0 4px;
}
.shotcard-context i { flex: none; display: grid; place-items: center; line-height: 0; }
.shotcard-context span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.shotcard-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; width: 100%;
  background: none; border: 0; border-top: 1px solid var(--border);
  padding: 11px 0 2px; margin-top: 2px;
  font-size: 12.5px; color: var(--muted); cursor: pointer;
}
.shotcard-foot:hover:not(:disabled) .seeall { text-decoration: underline; }
.shotcard-foot:disabled { cursor: default; opacity: .6; }
.seeall { color: var(--accent); font-weight: 600; white-space: nowrap; }
.shotcount { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.shotcard { transition: border-color .14s ease, box-shadow .14s ease, transform .14s ease; }
.shotcard:hover { border-color: var(--accent); box-shadow: var(--elev-3); transform: translateY(-2px); }

/* ══════════════════════════════════════════════════════════════════════
   Selects
   ══════════════════════════════════════════════════════════════════════

   A native select sits at whatever height the platform picked and carries the
   platform's own arrow, so a row of controls that should read as one row reads
   as three different widgets. The closed state is ours; the open list stays the
   operating system's, which is the right trade -- a hand-built dropdown loses
   keyboard behaviour, type-ahead and screen-reader support that the native one
   has for free.

   color-scheme is already set per theme above, which is what makes that native
   list dark when the app is. */
select:not([multiple]):not([size]) {
  appearance: none;
  -webkit-appearance: none;
  background-color: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 9px;
  font: inherit;
  font-size: 13.5px;
  line-height: 1.35;
  padding: 9px 34px 9px 12px;
  cursor: pointer;
  max-width: 100%;

  /* The chevron, drawn rather than shipped: an inline SVG needs no request and
     takes currentColor, so it matches the text in both themes. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%236b7480' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 11px;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) select:not([multiple]):not([size]) {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%239aa3c4' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
}
:root[data-theme="dark"] select:not([multiple]):not([size]) {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%239aa3c4' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

select:hover:not(:disabled) { border-color: var(--muted); }
select:focus {
  outline: none;
  border-color: var(--accent);
}
select:disabled {
  opacity: .55; cursor: default;
  background-color: var(--surface-2);
}
/* Firefox draws a dotted focus ring inside the control on top of ours. */
select:-moz-focusring { color: transparent; text-shadow: 0 0 0 var(--text); }
select option { background: var(--surface); color: var(--text); }

/* Where a select sits beside a button they should be the same height, or the
   row looks assembled rather than designed. */
.row-form select, .head-tools select, .toolbar select { min-height: 38px; }

/* ── Toggles ─────────────────────────────────────────────────────────── */
/* The switch is the one control people press without reading the label, so it
   has to say its state by shape as well as by colour -- colour alone fails for
   the eight percent of men who cannot separate red from green. */
.switch {
  position: relative; width: 46px; height: 26px; flex: none;
  border-radius: 999px; border: 1px solid var(--border);
  background: var(--surface-2); cursor: pointer; padding: 0;
  transition: background .16s ease, border-color .16s ease;
}
.switch::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--surface); box-shadow: var(--elev-1);
  transition: transform .16s ease;
}
.switch.on { background: var(--accent); border-color: var(--accent); }
.switch.on::after { transform: translateX(20px); background: #fff; }
.switch:hover:not(:disabled) { border-color: var(--accent); }
.switch:disabled { opacity: .5; cursor: default; }

/* ── Number and text fields ──────────────────────────────────────────── */
input[type="number"] { text-align: right; font-variant-numeric: tabular-nums; }
/* The spinners are a different width in every engine and are never the fastest
   way to change a number that has a keyboard beside it. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type="number"] { -moz-appearance: textfield; }

.field-suffix {
  color: var(--muted); font-size: 12.5px; margin-left: 8px; white-space: nowrap;
}


/* ── List boxes ──────────────────────────────────────────────────────── */
/* A select with several rows showing is a list, not a dropdown: it has no
   closed state, so a chevron on it is a lie -- and painted as a background it
   tiles down every row, which is what the People picker was doing. */
select[multiple], select[size] {
  appearance: auto;
  -webkit-appearance: listbox;
  background-image: none;
  padding: 6px;
  border-radius: 10px;
  min-height: 108px;
  line-height: 1.9;
}
select[multiple] option, select[size] option {
  padding: 6px 10px;
  border-radius: 6px;
}
select[multiple] option:checked, select[size] option:checked {
  /* Chrome ignores background on a checked option unless a gradient is used. */
  background: linear-gradient(var(--accent), var(--accent));
  color: #fff;
}


/* ══ The expanded row ═════════════════════════════════════════════════════
   Opened from a team row, so it answers one question: is this figure right,
   and if not, why. Four panels in the order somebody reads them -- what
   changed, what they were looking at, how it classified, when in the day it
   happened -- then the days themselves.

   The captures are three tiles and a way through to the rest. A panel that
   opens onto a wall of somebody's screen is one people are right to resent,
   and the count is usually the answer; you go to the images when something
   else has already made you want to.
   ═══════════════════════════════════════════════════════════════════════ */

.detail { padding: 22px 24px 26px; gap: 18px; }

.detail-tools { gap: 8px; }

/* A micro-label rather than a heading: the panel's subject, set quietly, so
   the figure inside it is the loudest thing in the box. */
.panel-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}

.panel {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 16px 18px 18px;
  min-width: 0; display: flex; flex-direction: column; gap: 12px;
  box-shadow: var(--elev-1);
}
.panel-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; min-height: 18px;
}
.panel-head .linkbtn { flex: none; padding: 0; }
.panel-note { margin: 0; font-size: 11.5px; color: var(--muted); }
.panel .hint { margin: 0; font-size: 12.5px; }

/* --- what changed ------------------------------------------------------- */
.vs-prior {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 12px 18px 6px;
}
.vs-prior .panel-label { display: block; margin-bottom: 2px; }
.vs-line {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 18px; padding: 8px 0; font-size: 13.5px;
}
.vs-key { color: var(--muted); }
.vs-move {
  display: inline-flex; align-items: baseline; gap: 7px;
  font-variant-numeric: tabular-nums;
}
.vs-arrow { color: var(--muted); font-size: 12px; }
.vs-move b { font-size: 14.5px; }

/* --- the four panels ---------------------------------------------------- */
.detail-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
}
.detail-grid > * { min-width: 0; }

/* --- captures ----------------------------------------------------------- */
.shotmini { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.shotmini-tile {
  position: relative; padding: 0; border: 1px solid var(--border);
  border-radius: 9px; overflow: hidden; background: var(--surface-2);
  aspect-ratio: 16 / 10; cursor: pointer; display: block;
  transition: border-color .15s ease, transform .15s ease;
}
.shotmini-tile:hover { border-color: var(--accent); transform: translateY(-1px); }
.shotmini-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.shotmini-tile img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.shotmini-time {
  position: absolute; left: 5px; bottom: 5px;
  padding: 2px 6px; border-radius: 5px;
  background: rgba(8, 11, 24, .68); color: #fff;
  font-size: 10px; font-weight: 600; letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}

/* --- the ring ----------------------------------------------------------- */
.donut-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 16px; align-items: center;
}
.donut {
  width: 104px; height: 104px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
}
.donut-hole {
  width: 66px; height: 66px; border-radius: 50%;
  background: var(--surface);
  display: grid; place-items: center; align-content: center; gap: 1px;
  text-align: center;
}
.donut-hole b { font-size: 14px; font-variant-numeric: tabular-nums; }
.donut-hole span {
  font-size: 9.5px; color: var(--muted);
  letter-spacing: .06em; text-transform: uppercase;
}

.donut-legend, .classlines { display: flex; flex-direction: column; gap: 6px; }
.legend-row {
  display: grid; grid-template-columns: 9px minmax(0, 1fr) auto;
  align-items: center; gap: 9px; font-size: 12.5px;
}
.classlines .legend-row {
  grid-template-columns: 9px minmax(0, 1fr) 34px auto;
}
.legend-dot {
  width: 9px; height: 9px; border-radius: 3px; flex: none;
  background: var(--muted);
}
.legend-dot.good { background: var(--good); }
.legend-dot.mid  { background: var(--warn); }
.legend-dot.low  { background: var(--bad); }
.legend-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.legend-pct {
  color: var(--muted); font-size: 11.5px; text-align: right;
  font-variant-numeric: tabular-nums;
}
.legend-row b { font-variant-numeric: tabular-nums; font-size: 12.5px; }

/* --- how it classified --------------------------------------------------- */
.classbar {
  display: flex; height: 9px; border-radius: 999px; overflow: hidden;
  background: var(--surface-2);
}
.classbar i { display: block; height: 100%; transition: width .4s ease; }
.classbar i.good { background: var(--good); }
.classbar i.mid  { background: var(--warn); }
.classbar i.low  { background: var(--bad); }

/* --- when in the day ----------------------------------------------------- */
.hourchart {
  display: grid; grid-template-columns: repeat(24, 1fr);
  align-items: end; gap: 2px; height: 78px;
  padding: 0 1px; border-bottom: 1px solid var(--border);
}
.hourbar {
  display: block; height: var(--h); border-radius: 2px 2px 0 0;
  background: var(--border); transition: height .4s ease;
}
.hourbar.good { background: var(--good); }
.hourbar.mid  { background: var(--warn); }
.hourbar.low  { background: var(--bad); opacity: .75; }
.hourbar.none { background: var(--border); }
.hourchart-axis {
  display: flex; justify-content: space-between;
  font-size: 10px; color: var(--muted); margin-top: -4px;
}

/* --- day by day ---------------------------------------------------------- */
.daylines-panel { padding-bottom: 8px; }
.daylines { width: 100%; border-collapse: collapse; table-layout: fixed; }
.daylines th {
  text-align: left; font-size: 10.5px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
  padding: 0 10px 8px 0; border-bottom: 1px solid var(--border);
}
.daylines th.num, .daylines td.num { text-align: right; }
.daylines td {
  padding: 9px 10px 9px 0; font-size: 13px;
  border-bottom: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}
.daylines tr:last-child td { border-bottom: 0; }
.daylines th:first-child, .daylines td:first-child { width: 34%; }
.daylines th.num, .daylines td.num { width: 17%; }
.daylines .barcol { width: 32%; padding-right: 0; }
.daylines .barcol .meter { min-width: 0; }

/* --- adding time --------------------------------------------------------- */
.modal-actions {
  display: flex; gap: 10px; margin-top: 20px;
}
.modal-actions button { flex: 1; }
.timepair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.modal .field { text-align: left; }
.modal .hint.bad { color: var(--bad); margin: 10px 0 0; text-align: left; }
.range-head + .field { margin-top: 4px; }

@media (max-width: 720px) {
  .detail { padding: 16px 14px 20px; }
  .detail-grid { grid-template-columns: minmax(0, 1fr); }
  .donut-row { grid-template-columns: minmax(0, 1fr); justify-items: center; }
  .timepair { grid-template-columns: minmax(0, 1fr); }
}


/* ── Punctuality ──────────────────────────────────────────────────────────
   Three tones, mixed from the semantic tokens rather than written as literal
   rgba: the same rule then reads correctly on both grounds, and a dark theme
   does not end up with a fifteen-percent tint of a colour it never uses.
   ──────────────────────────────────────────────────────────────────────── */
.pill.good, .pill.on-time {
  background: color-mix(in srgb, var(--good) 15%, transparent);
  color: var(--good);
}
.pill.warn {
  background: color-mix(in srgb, var(--warn) 16%, transparent);
  color: var(--warn);
}
.pill.bad {
  background: color-mix(in srgb, var(--bad) 15%, transparent);
  color: var(--bad);
}

/* A tone the caller asked for beats the position-based tint above it. Same
   specificity as the :nth-child rules, so this block has to come after them. */
.rep-stat.good { --tint: var(--good); }
.rep-stat.warn { --tint: var(--warn); }
.rep-stat.bad  { --tint: var(--bad); }
/* The same three under the names the percentage bands use, so one vocabulary
   -- good / mid / low -- serves the meters, the rows and these cards. */
.rep-stat.mid  { --tint: var(--warn); }
.rep-stat.low  { --tint: var(--bad); }

.grid td .pill + .tiny { margin-top: 3px; }


/* ══ Dashboard ════════════════════════════════════════════════════════════
   One hue per card, because the four cards are four different subjects and a
   hue you recognise is faster than a heading you read. The hue is carried by
   a rule, a wash and a mark -- never by the figure, which stays the loudest
   thing in the box regardless of which card it sits in.

   Every colour is mixed from a token, so the dark theme is this design under
   different light rather than a second design maintained alongside it.
   ═══════════════════════════════════════════════════════════════════════ */

.dash-cards { gap: 16px; margin-bottom: 20px; }

.dash-card {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  padding: 18px 20px 16px;
  transition: box-shadow .18s ease, transform .18s ease, border-color .18s ease;
  --tint: var(--accent);
}
/* The wash sits behind everything and fades out well before the figures, so
   it never costs the numbers any contrast. */
.dash-card::after {
  content: "";
  position: absolute; inset: 0;
  background:
    radial-gradient(120% 90% at 100% 0%,
      color-mix(in srgb, var(--tint) 13%, transparent) 0%,
      transparent 62%);
  pointer-events: none;
}
.dash-card::before {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--tint),
              color-mix(in srgb, var(--tint) 35%, transparent));
}
.dash-card > * { position: relative; z-index: 1; }
.dash-card:hover { box-shadow: var(--elev-3); transform: translateY(-1px); }

.dash-card.a { --tint: var(--slice-0); border-top-color: var(--border); }
.dash-card.b { --tint: var(--slice-1); border-top-color: var(--border); }
.dash-card.c { --tint: var(--fig-cover); border-top-color: var(--border); }
.dash-card.d { --tint: var(--slice-3); border-top-color: var(--border); }
.dash-card.warn { --tint: var(--warn); border-top-color: var(--border); }

.dash-title {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.dash-mark {
  flex: none;
  width: 28px; height: 28px; border-radius: 9px;
  display: grid; place-items: center;
  color: var(--tint);
  background: color-mix(in srgb, var(--tint) 14%, transparent);
}
/* The figure takes the card's hue, like every other card in the product.
   It was pinned to --text, so the dashboard was the one screen where the
   numbers stayed grey while everything around them had a colour. */
.dash-main { color: var(--tint, var(--text)); }
.dash-card.warn .dash-main { color: var(--warn); }
/* The organisation name is a name, not a measure. A hue on it says "this is
   a figure worth reading", which it is not. */
.dash-card.a .dash-main { color: var(--text); }

/* Waiting to sync is a state worth noticing, so it gets a shape as well as a
   colour -- amber alone is not a signal everybody can read. */
.dash-card.live .dash-mark::after {
  content: "";
  position: absolute; margin: -18px 0 0 18px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn) 60%, transparent);
  animation: dash-ping 2s ease-out infinite;
}
.dash-card.live .dash-mark { position: relative; }
@keyframes dash-ping {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn) 55%, transparent); }
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .dash-card.live .dash-mark::after { animation: none; }
}

/* --- the stat strip ------------------------------------------------------ */
.dash-stats {
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  margin-bottom: 20px;
}
.dash-stat {
  position: relative;
  padding: 18px 20px 16px;
  --tint: var(--accent);
}
.dash-stat .stat-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
}
.dash-stat .stat-big {
  font-family: var(--font-display, inherit);
  color: var(--tint);
  font-variant-numeric: tabular-nums;
  margin: 8px 0 0;
}
.dash-spark {
  height: 5px; border-radius: 999px; margin: 10px 0 8px;
  background: color-mix(in srgb, var(--tint) 13%, var(--surface-2));
  overflow: hidden;
}
.dash-spark i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--tint) 65%, transparent), var(--tint));
  transition: width .5s cubic-bezier(.2, .7, .3, 1);
}
.dash-stat .muted { font-size: 12px; }

.dash-bar { margin-bottom: 16px; }

@media (max-width: 720px) {
  .dash-card { padding: 16px 16px 14px; }
  .dash-stat { padding: 15px 16px 14px; }
}


/* ══ Buttons, from the floor up ═══════════════════════════════════════════
   Every button style so far was written as "a button inside a card", "a button
   inside the head tools", "a button inside the detail tools". A button
   anywhere else -- in a modal, in a panel head, in a screen written next week
   -- inherited the browser's own grey box, which is why some of them looked
   unfinished no matter how the cards were styled.

   So: a base weight on the element itself, and the variants as classes over
   it. The base sits at element specificity and the opt-outs are wrapped in
   :where(), which contributes nothing -- so every existing rule still wins,
   and the only buttons this changes are the ones that had no rule at all.

   Three weights, and they mean different things:
     .primary   the one thing this view is for. One per view.
     (default)  an outline. Everything else you can do here.
     .ghost     dismisses or reveals; carries no weight of its own.
   Destructive keeps its own colour, because undoing it is somebody's evening.
   ═══════════════════════════════════════════════════════════════════════ */

button:where(:not(.iconbtn, .linkbtn, .chip, .switch, .quickitem, .rangeopt,
                  .memberchip, .zoom, .close, .shotcard-foot, .seg *, .seg > *,
                  .pj-stagestep)) {
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 9px;
  padding: 9px 15px;
  font-weight: 600;
  font-size: 13px;
  line-height: 1.2;
}
button:where(:not(.iconbtn, .linkbtn, .chip, .switch, .quickitem, .rangeopt,
                  .memberchip, .zoom, .close, .shotcard-foot, .seg *, .seg > *,
                  .pj-stagestep)):hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}

/* The segmented control is a set of tabs that happen to be buttons; the base
   above would give each one its own outline and undo the whole idea. */
.seg button {
  background: none;
  border: 0;
  padding: 7px 14px;
  color: var(--muted);
}
.seg button:hover:not(:disabled) { background: none; color: var(--text); }
.seg button.active {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--elev-1);
}

/* Ghost: present, and quiet until you reach for it. */
button.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}
button.ghost:hover:not(:disabled) {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text);
}

button.sm { padding: 6px 12px; font-size: 12.5px; border-radius: 8px; }
button.wide { width: 100%; justify-content: center; }

/* Primary, restated so it beats the base regardless of where it sits. */
button.primary {
  background: var(--accent-fill, var(--accent));
  border-color: transparent;
  color: #fff;
  box-shadow: var(--elev-1);
}
button.primary:hover:not(:disabled) {
  background: var(--accent-fill-hover, var(--accent-fill, var(--accent)));
  border-color: transparent;
  color: #fff;
  filter: brightness(1.06);
  box-shadow: var(--elev-2);
  transform: translateY(-1px);
}
button.primary:active:not(:disabled) { filter: brightness(.96); transform: none; }

button.danger {
  background: var(--bad);
  border-color: transparent;
  color: #fff;
}
button.danger:hover:not(:disabled) {
  background: var(--bad);
  border-color: transparent;
  color: #fff;
  filter: brightness(1.07);
}

button:disabled { opacity: .5; cursor: default; filter: none; transform: none; }
button:disabled:hover {
  border-color: var(--border);
  color: var(--text);
  background: var(--surface);
}
button.ghost:disabled:hover { background: transparent; border-color: transparent; }

/* ── Fields ───────────────────────────────────────────────────────────────
   The same gap as the buttons: inputs were styled per screen, so a field in a
   modal looked like a different product from a field in settings. */
.modal input[type="text"], .modal input[type="date"], .modal input[type="time"],
.modal input[type="number"], .modal select, .modal textarea {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 9px 12px;
  color: var(--text);
  font-size: 13.5px;
}
.modal input:focus-visible, .modal select:focus-visible, .modal textarea:focus-visible {
  outline: none;
  border-color: var(--accent);
}
.modal .field { margin-bottom: 14px; }
.modal .field label {
  display: block;
  margin-bottom: 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
}
.modal .srow-help { margin-top: 6px; font-size: 12px; color: var(--muted); }

/* A modal that carries a form is read top to bottom, not centred. */
.modal:has(.field) { text-align: left; }
.modal .range-head { margin-bottom: 18px; }

/* A site, told apart from an application without spending a column on it. */
.appmark.web {
  background: color-mix(in srgb, var(--fig-cover) 15%, transparent);
  color: var(--fig-cover);
}

/* A figure somebody entered rather than one the tracker observed. Quiet on
   purpose: it is a caveat on a number, not an accusation about a person. */
.addedchip {
  display: block;
  margin-top: 3px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--warn);
  white-space: nowrap;
}
.vs-move .addedchip { display: inline-block; margin: 0 0 0 6px; }
.grid td .addedchip { text-align: right; }


/* ══ Pickers, brought forward ═════════════════════════════════════════════
   A soft ground of their own, a chevron with weight, and states that move.
   Tokens first so both themes are one design rather than two.
   ═══════════════════════════════════════════════════════════════════════ */
:root {
  /* A field sits slightly below the card it is on rather than level with it.
     Mixed from the surface so it tracks the theme instead of being a literal
     that only works on one ground. */
  --field: color-mix(in srgb, var(--surface-2) 62%, var(--surface));
  --field-hover: color-mix(in srgb, var(--surface-2) 88%, var(--surface));
  --field-border: color-mix(in srgb, var(--border) 88%, var(--text) 6%);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --field: color-mix(in srgb, var(--surface-2) 72%, var(--surface));
    --field-hover: color-mix(in srgb, var(--surface-2) 100%, var(--surface));
    --field-border: color-mix(in srgb, var(--border) 100%, transparent);
  }
}
:root[data-theme="dark"] {
  --field: color-mix(in srgb, var(--surface-2) 72%, var(--surface));
  --field-hover: color-mix(in srgb, var(--surface-2) 100%, var(--surface));
  --field-border: color-mix(in srgb, var(--border) 100%, transparent);
}

select:not([multiple]):not([size]) {
  background-color: var(--field);
  border: 1px solid var(--field-border);
  border-radius: 10px;
  padding: 10px 38px 10px 13px;
  font-size: 13.5px;
  font-weight: 500;
  min-height: 40px;
  box-shadow: none;
  transition: background-color .15s ease, border-color .15s ease,
              box-shadow .15s ease, transform .15s ease;

  /* A heavier stroke than before and set slightly larger: at 1.6px and 11px
     the chevron read as a hairline artefact rather than a control. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.75 6 6.5l5-4.75' fill='none' stroke='%236b7480' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  background-size: 12px;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) select:not([multiple]):not([size]) {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.75 6 6.5l5-4.75' fill='none' stroke='%23aab3d4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
}
:root[data-theme="dark"] select:not([multiple]):not([size]) {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.75 6 6.5l5-4.75' fill='none' stroke='%23aab3d4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

select:not([multiple]):not([size]):hover:not(:disabled) {
  background-color: var(--field-hover);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--field-border));
}
select:not([multiple]):not([size]):focus {
  background-color: var(--surface);
  border-color: var(--accent);
  outline: none;
}
select:not([multiple]):not([size]):disabled {
  background-color: var(--surface-2);
  opacity: .5;
  cursor: default;
}

/* Text fields, matched to the pickers -- a row where the select and the input
   beside it are different heights and different grounds is the thing that
   actually reads as dated. */
input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="search"], input[type="date"],
input[type="time"], input[type="url"], textarea {
  background-color: var(--field);
  border: 1px solid var(--field-border);
  border-radius: 10px;
  padding: 10px 13px;
  font-size: 13.5px;
  transition: background-color .15s ease, border-color .15s ease,
              box-shadow .15s ease;
}
input[type="text"]:hover:not(:disabled),
input[type="email"]:hover:not(:disabled),
input[type="number"]:hover:not(:disabled),
input[type="search"]:hover:not(:disabled),
input[type="date"]:hover:not(:disabled),
input[type="time"]:hover:not(:disabled),
textarea:hover:not(:disabled) {
  background-color: var(--field-hover);
}
input:focus, textarea:focus {
  background-color: var(--surface);
  border-color: var(--accent);
  outline: none;
}
input::placeholder, textarea::placeholder { color: var(--muted); opacity: .8; }

/* A list box is a list, so it is drawn as one: rounded rows, a real hover, and
   a selected state that is the accent rather than the platform's blue. */
select[multiple], select[size] {
  background-color: var(--field);
  border: 1px solid var(--field-border);
  border-radius: 10px;
  padding: 6px;
  background-image: none;
}
select[multiple]:focus, select[size]:focus {
  border-color: var(--accent);
  outline: none;
}
select[multiple] option, select[size] option {
  padding: 7px 10px;
  border-radius: 7px;
  margin-bottom: 2px;
}

/* Keep every control on a row the same height, or the row looks assembled. */
.row-form select, .head-tools select, .toolbar select,
.srow-control select, .field select { min-height: 40px; }

/* The untracked swatch, for the tooltip that names what a block was. */
.sw.off { background: var(--border); }

/* ══ Background tasks ═════════════════════════════════════════════════════
   Wider than a confirm dialog because it holds a table, and read top to bottom
   rather than centred: the headline says what is happening now, the list says
   what happened before.
   ═══════════════════════════════════════════════════════════════════════ */
.jobs-modal { width: min(720px, 100%); text-align: left; }

.jobs-hero {
  display: grid;
  place-items: center;
  gap: 10px;
  text-align: center;
  padding: 26px 20px 24px;
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  margin-bottom: 18px;
}
.jobs-hero h3 { margin: 0; font-size: 18px; }
.jobs-hero p {
  margin: 0;
  max-width: 46ch;
  font-size: 13px;
  color: var(--muted);
  line-height: 1.5;
}
.jobs-hero.failed h3 { color: var(--bad); }
.jobs-hero.done h3 { color: var(--good); }

/* A loading state that says what it is waiting for.
 *
 * "Loading people…" was a bare line of hint text beside an empty box, which
 * reads as a page that has finished and found nothing. Reported 27 October
 * 2026 with a screenshot of exactly that.
 *
 * Its own keyframes rather than borrowing jobs-spin: a shared name is how a
 * rule gets deleted with the feature that introduced it and quietly stops
 * another screen working. */
.loadwrap {
  display: flex; align-items: center; gap: 12px;
  padding: 18px 2px; color: var(--muted); font-size: 14px;
}
.loadspin {
  width: 18px; height: 18px; border-radius: 50%; flex: none;
  border: 2px solid color-mix(in srgb, var(--accent) 22%, transparent);
  border-top-color: var(--accent);
  animation: loadspin-turn .8s linear infinite;
}
@keyframes loadspin-turn { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* Still turning, because a still ring reads as stuck -- just slowly. */
  .loadspin { animation-duration: 2.4s; }
}

.jobs-spinner {
  width: 34px; height: 34px; border-radius: 50%;
  border: 3px solid color-mix(in srgb, var(--accent) 22%, transparent);
  border-top-color: var(--accent);
  animation: jobs-spin .8s linear infinite;
}
@keyframes jobs-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .jobs-spinner { animation-duration: 2.4s; }
}

.jobs-download {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 20px;
}

.jobs-list { max-height: 46vh; overflow-y: auto; }
.jobs-empty { text-align: center; padding: 22px 0; margin: 0; }
.jobs-table { width: 100%; }
.jobs-table td { vertical-align: middle; }
.jobs-table .right { text-align: right; }
.jobs-actions { white-space: nowrap; }
.jobs-actions button + button { margin-left: 8px; }
.jobs-table .tiny { font-size: 11.5px; margin-top: 2px; }

/* The screenshots bar is the report bar now, with two extra segmented
   controls in its right half. It keeps its own class only so those controls
   have room to wrap on a narrow window rather than pushing the row sideways. */
.rep-bar.shots-bar .rep-bar-right { flex-wrap: wrap; gap: 8px; }
.dev-bar { margin-bottom: 14px; }

/* ══ Saved data ═══════════════════════════════════════════════════════════
   The screen still works when the server does not, so it has to say so --
   loudly enough to be read before the figures under it, quietly enough that
   it is not mistaken for a failure. Amber, not red: nothing is broken, the
   numbers are just older than they look.
   ═══════════════════════════════════════════════════════════════════════ */
.stalebar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  margin-bottom: 14px;
  border-radius: var(--radius-lg);
  border: 1px solid color-mix(in srgb, var(--warn) 34%, var(--border));
  background: color-mix(in srgb, var(--warn) 10%, var(--surface));
  color: var(--text);
  font-size: 13px;
  line-height: 1.45;
}
.stalebar b { font-weight: 700; }
.stalebar span:nth-of-type(2) { color: var(--muted); }
.stalebar button { flex: none; margin-left: auto; }

/* A shape as well as a colour: the dot pulses, so the banner reads as "still
   trying" rather than as a static warning nobody looks at twice. */
.stalebar-dot {
  flex: none;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn) 55%, transparent);
  animation: stale-pulse 2.4s ease-out infinite;
}
@keyframes stale-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn) 50%, transparent); }
  70%  { box-shadow: 0 0 0 8px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .stalebar-dot { animation: none; }
}

@media (max-width: 640px) {
  .stalebar { flex-wrap: wrap; }
  .stalebar button { margin-left: 0; }
}

/* ══ Chart scale ══════════════════════════════════════════════════════════
   A bar twice the height of its neighbour said "twice as much" and nothing
   else. The axis gives every bar a value you can read without hovering, at
   times a person would say out loud -- 9h 43m, not 9.72.

   Axis and bars share one row, so the labels sit exactly beside the
   gridlines they name rather than approximately near them.
   ═══════════════════════════════════════════════════════════════════════ */
.chart-plot {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  margin-top: 14px;
}

.chart-axis {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /* The bars stop where the labels do; the extra room is the x-axis labels
     underneath, which the scale has nothing to say about. */
  padding-bottom: 18px;
  font-size: 10px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: right;
}
/* Each label names the line it sits on, so it is centred on that line rather
   than resting above it. */
.chart-axis span { transform: translateY(-50%); }
.chart-axis span:first-child { transform: none; }
.chart-axis span:last-child { transform: translateY(-100%); }

.chart { margin-top: 0; }

.chart-grid {
  position: absolute;
  inset: 0 0 18px 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  pointer-events: none;
}
.chart-grid i {
  display: block;
  height: 1px;
  background: var(--border);
  opacity: .55;
}
/* The baseline is the axis itself, not a gridline, so it carries its weight. */
.chart-grid i.base { opacity: 1; height: 1px; }

/* The columns sit above the gridlines rather than behind them. */
.chart-col { position: relative; z-index: 1; }

@media (max-width: 640px) {
  .chart-plot { gap: 6px; }
  .chart-axis { font-size: 9px; }
}

/* ══ Productivity, per person ═════════════════════════════════════════════
   One card each: who, the split, and the applications that made it that
   shape. The three columns are the working part -- every row in them can be
   reclassified where it sits, because the reason a report reads 100% neutral
   is that nobody has classified anything, and a trip to settings per name is
   how it stays that way.
   ═══════════════════════════════════════════════════════════════════════ */
.prodcard { padding: 0; overflow: hidden; }

.prodcard-head {
  display: flex; align-items: center; gap: 14px;
  padding: 18px 22px 14px;
}
.prodcard-head h3 { margin: 0; font-size: 17px; }
.prodcard-head p { margin: 3px 0 0; font-size: 12.5px; }

.prod-figures {
  display: grid;
  grid-template-columns: auto repeat(4, minmax(0, 1fr));
  align-items: center;
  gap: 20px;
  padding: 0 22px 18px;
}

/* The ring is the headline, so it is the only round thing on the card. */
.prodring {
  width: 92px; height: 92px; border-radius: 50%;
  display: grid; place-items: center; flex: none;
}
.prodring-hole {
  width: 68px; height: 68px; border-radius: 50%;
  background: var(--surface);
  display: grid; place-items: center;
  font-family: var(--font-display, inherit);
  font-size: 19px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.prod-figure .stat-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.prod-pct {
  font-family: var(--font-display, inherit);
  font-size: 22px; font-weight: 700; line-height: 1.15;
  margin: 6px 0 2px;
  font-variant-numeric: tabular-nums;
}
.prod-pct.good { color: var(--good); }
.prod-pct.mid  { color: var(--muted); }
.prod-pct.low  { color: var(--bad); }
.prod-figure .muted { font-size: 12px; }

/* --- the three columns --------------------------------------------------- */
.prodcols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--border);
}
.prodcol { min-width: 0; border-left: 1px solid var(--border); }
.prodcol:first-child { border-left: 0; }

.prodcol-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 11px 16px;
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--border);
}
.prodcol-head b {
  font-size: 11.5px; padding: 1px 8px; border-radius: 999px;
  background: var(--surface-2); color: var(--muted);
}
.prodcol.good .prodcol-head {
  background: color-mix(in srgb, var(--good) 9%, transparent);
  color: var(--good);
}
.prodcol.mid .prodcol-head { background: var(--surface-2); color: var(--muted); }
.prodcol.low .prodcol-head {
  background: color-mix(in srgb, var(--bad) 9%, transparent);
  color: var(--bad);
}

/* Capped, with its own scroll: twelve names is a list, forty is a wall. */
.prodcol-list { max-height: 232px; overflow-y: auto; padding: 6px; }
.prodcol-list .hint { padding: 24px 8px; text-align: center; margin: 0; }

.prodrow {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto 26px;
  align-items: center; gap: 9px;
  padding: 7px 8px; border-radius: 8px;
  font-size: 13px;
}
.prodrow:hover { background: var(--surface-2); }
.prodrow-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prodrow b { font-variant-numeric: tabular-nums; font-size: 12.5px; }

/* Nothing has said anything about this one. Marked, because that is the
   actionable state on this screen -- not a fault, a gap. */
.prodrow.unruled .prodrow-name {
  border-bottom: 1px dashed color-mix(in srgb, var(--muted) 55%, transparent);
}

.prodrow-menu { opacity: 0; transition: opacity .12s ease; }
.prodrow:hover .prodrow-menu,
.prodrow-menu:focus-visible { opacity: 1; }
/* Touch has no hover, so the menu has to be there without one. */
@media (hover: none) { .prodrow-menu { opacity: 1; } }

/* --- the classify menu --------------------------------------------------- */
.classpop {
  position: fixed; z-index: 300;
  min-width: 190px; padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 11px;
  box-shadow: var(--elev-3);
}
.classpop-head {
  padding: 7px 10px 8px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
  border-bottom: 1px solid var(--border);
  margin-bottom: 5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.classpop .classpop-item {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; border-radius: 7px;
  padding: 8px 10px; font-size: 13px; font-weight: 500;
  color: var(--text);
}
.classpop .classpop-item:hover:not(:disabled) {
  background: var(--accent-soft); color: var(--accent); border-color: transparent;
}

@media (max-width: 900px) {
  .prod-figures { grid-template-columns: auto repeat(2, minmax(0, 1fr)); }
  .prodcols { grid-template-columns: minmax(0, 1fr); }
  .prodcol { border-left: 0; border-top: 1px solid var(--border); }
  .prodcol:first-child { border-top: 0; }
}

/* Nothing tracked: a line rather than a card of empty scaffolding. */
.prodcard-quiet .prodcard-head { padding: 14px 22px; }
.prodcard-quiet h3 { font-size: 15px; }
.prodcard-quiet p { font-size: 12.5px; }

/* ══ Start / Finish ═══════════════════════════════════════════════════════
   One button doing two jobs, so it has to look like whichever it is. It read
   "Finish" with a checkbox even before anything had started -- offering to
   end a session that did not exist.

   Green to begin, red to end: the two states people already read without
   thinking. Both carry a shape as well as a colour, because a control whose
   only difference is hue is one that fails for anybody who cannot separate
   red from green.
   ═══════════════════════════════════════════════════════════════════════ */
.appbar-finish { gap: 8px; font-weight: 600; }

.appbar-finish.go {
  background: var(--good);
  border-color: transparent;
  color: #fff;
}
.appbar-finish.go:hover:not(:disabled) {
  border-color: transparent;
  filter: brightness(1.06);
  box-shadow: var(--elev-2);
}

.appbar-finish.stop {
  background: color-mix(in srgb, var(--bad) 12%, var(--surface));
  border-color: color-mix(in srgb, var(--bad) 45%, var(--border));
  color: var(--bad);
}
.appbar-finish.stop:hover:not(:disabled) {
  background: var(--bad);
  border-color: transparent;
  color: #fff;
}

.appbar-finish svg { flex: none; }
.appbar-finish:active:not(:disabled) { transform: translateY(1px); }

/* ══ Figures carry their subject's colour ═════════════════════════════════
   Every prominent number takes the hue of the thing it measures, so the eye
   finds "worked" by its colour before reading the label. The rules below are
   the places that were still grey.

   Two limits, or it stops meaning anything: a name is never tinted (it is not
   a measure), and a table cell is only tinted where the colour is a verdict --
   good, warning, bad -- rather than decoration on every row.
   ═══════════════════════════════════════════════════════════════════════ */

/* A tone the caller asked for beats the position-based tint. Same specificity
   as the :nth-child rules, so this has to come after them -- which, being at
   the end of the file, it does. */
.rep-stat.good { --tint: var(--good); }
.rep-stat.warn { --tint: var(--warn); }
.rep-stat.bad  { --tint: var(--bad); }
/* The same three under the names the percentage bands use, so one vocabulary
   -- good / mid / low -- serves the meters, the rows and these cards. */
.rep-stat.mid  { --tint: var(--warn); }
.rep-stat.low  { --tint: var(--bad); }

/* The ring in the middle of a donut is the total it is a ring of. */
.donut-hole b { color: var(--accent); }

/* The productivity score, coloured by what it says: green when most of the
   time was productive, amber in the middle, red when little of it was. */
.prodring-hole { color: var(--text); }
.prodring-hole.good { color: var(--good); }
.prodring-hole.mid  { color: var(--warn); }
.prodring-hole.low  { color: var(--bad); }

/* Figures inside the expanded row, which had the card's chrome but none of
   its colour. */
.vs-move b { color: var(--fig-worked); }
.panel .legend-row b { color: var(--text); }

/* Table columns where the colour is a verdict rather than decoration. */
.teamgrid td.workedcell b { color: var(--fig-worked); }
.teamgrid td.breakcell { color: var(--fig-break); }
.daylines td.num { color: var(--fig-worked); }
.daylines td.num.muted { color: var(--muted); }

/* ══ Home: the four figures, and the tab row ══════════════════════════════
   Home was the one report screen where the numbers stayed the same colour as
   the labels around them. Every other card in the product -- the Dashboard's
   four, the Team report's four -- carries a hue on its figure and a hairline
   of that hue along the top, so a row of cards reads as four subjects rather
   than one long band. Home had the cards and not the colour.
   ═══════════════════════════════════════════════════════════════════════ */

.home-card {
  position: relative;
  overflow: hidden;
  --tint: var(--accent);
}
.home-card::before {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--tint),
              color-mix(in srgb, var(--tint) 35%, transparent));
}
/* The same wash the Dashboard cards carry, fading out well above the figure
   so it never costs the number any contrast. */
.home-card::after {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(120% 90% at 100% 0%,
    color-mix(in srgb, var(--tint) 12%, transparent) 0%, transparent 62%);
  pointer-events: none;
}
.home-card > * { position: relative; z-index: 1; }

/* Order is fixed -- activity, worked, on now, attention -- so position is a
   safe key, the same way the Team report's stat cards are keyed. */
.home-cards .home-card:nth-child(1) { --tint: var(--fig-activity); }
.home-cards .home-card:nth-child(2) { --tint: var(--fig-worked); }
.home-cards .home-card:nth-child(3) { --tint: var(--slice-3); }
/* ...except the activity card, whose tint says how high the figure is. Same
   weight as the positional rules above, so it has to come after them. */
.home-cards .home-card.act-good { --tint: var(--good); }
.home-cards .home-card.act-mid  { --tint: var(--warn); }
.home-cards .home-card.act-low  { --tint: var(--bad); }
/* Nothing needing attention is good news, and green says so faster than the
   zero does. Amber only once there is actually something in the list. */
.home-cards .home-card:nth-child(4) { --tint: var(--good); }
.home-cards .home-card.flagged { --tint: var(--warn); }

.home-card .stat-big { color: var(--tint); }
.home-card.flagged .stat-big { color: var(--warn); }

/* The total column, coloured the same as the Team report's worked column --
   it is the same measure, and it was the only figure in the table left grey
   while the activity percentage beside it was banded. */
.daygrid td.totalcell b { color: var(--fig-worked); }
.daygrid td.totalcell .trend.up { color: var(--good); }
.daygrid td.totalcell .trend.down { color: var(--warn); }

/* These are not free choices. The selected state washes the surface with 15%
   of the hue and then draws the label in that same hue, so each one is read
   against a ground it has tinted towards itself -- and checking the resting
   colour says nothing about it. Solved for instead: every hue clears 5:1
   against its own pill, in its own theme. See scratchpad/check_rail.py.
*/

/* ── Report tabs: one hue each ─────────────────────────────────────────
   Seven tabs all drawn in the same grey, with the selected one in the same
   blue every other control in the product uses, meant the row read as one
   undifferentiated strip and the selection read as "a button". A hue per tab
   makes each one a place you can learn rather than a word you have to read,
   and it makes the selected tab unmistakable because it is the only one at
   full strength.

   Eight distinct hues, spaced around the wheel and given separately for each
   theme -- a hue dark enough to read on white is not light enough to read on a
   dark ground, so neither set is derived from the other.

   The eighth is Project tracking's rust, and it was missing for as long as
   that tab existed: the tab was added after this block was written and no hue
   came with it. Nothing looked broken, because --tab was simply undefined --
   which makes every color-mix() referring to it invalid, so the browser drops
   the whole declaration and the tab quietly has no hover at all. Reported from
   the screen as "the hover colour is not coming for Project tracking".

   Measured on the same footing as the rest: 6.73:1 on white and 6.87:1 on the
   dark surface, inside the 6.68-6.84 and 6.46-8.51 the others occupy. Its
   nearest neighbour either way is amber, at a distance of 23.3 and 28.8, where
   the closest existing pair already sits at 23.2 and 16.9.

   The light set is unchanged and always was right. The dark set was retuned
   when the dark ground moved from a navy (#1e2442) to a near-neutral
   (#161b23): the same hues sat brighter against it and read as neon, so each
   one keeps its exact hue and gives up some saturation and a little value.
   Measured rather than eyeballed -- every one lands between 6.7:1 and 8.8:1
   against the surface, where 4.5:1 is the readable floor, and the most
   saturated fell from 0.66 to 0.48.
   ─────────────────────────────────────────────────────────────────────── */
:root {
  --tab-home:    #2454bf;  /* blue    */
  --tab-team:    #0b6855;  /* teal    */
  --tab-shots:   #6841c3;  /* violet  */
  --tab-attend:  #884e09;  /* amber   */
  --tab-apps:    #0a6280;  /* cyan    */
  --tab-prod:    #a12c6a;  /* magenta */
  --tab-proj:    #a1391f;  /* rust    */
  --tab-devices: #426615;  /* moss    */
  --tab-matrix:  #1c7034;  /* forest  */
  --tab-self:    #52607a;  /* slate   */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --tab-home:    #8ba6ed;
    --tab-team:    #4bb296;
    --tab-shots:   #b398e0;
    --tab-attend:  #e1b167;
    --tab-apps:    #75c2e4;
    --tab-prod:    #e287b0;
    --tab-proj:    #df937f;
    --tab-devices: #99c163;
    --tab-matrix:  #63b97d;
    --tab-self:    #a0abbe;
  }
}
:root[data-theme="dark"] {
  --tab-home:    #8ba6ed;
  --tab-team:    #4bb296;
  --tab-shots:   #b398e0;
  --tab-attend:  #e1b167;
  --tab-apps:    #75c2e4;
  --tab-prod:    #e287b0;
  --tab-proj:    #df937f;
  --tab-devices: #99c163;
  --tab-matrix:  #63b97d;
  --tab-self:    #a0abbe;
}

.subtabs button[data-report="rep-home"]         { --tab: var(--tab-home); }
.subtabs button[data-report="rep-team"]         { --tab: var(--tab-team); }
.subtabs button[data-report="screenshots"]      { --tab: var(--tab-shots); }
.subtabs button[data-report="rep-attendance"]   { --tab: var(--tab-attend); }
.subtabs button[data-report="rep-matrix"]       { --tab: var(--tab-matrix); }
.subtabs button[data-report="rep-apps"]         { --tab: var(--tab-apps); }
.subtabs button[data-report="rep-productivity"] { --tab: var(--tab-prod); }
.subtabs button[data-report="rep-projects"]     { --tab: var(--tab-proj); }
.subtabs button[data-report="devices"]          { --tab: var(--tab-devices); }

/* Resting: the hue pulled well back towards muted, so the row is a spectrum
   you notice rather than a paint chart you have to squint past. */
.subtabs button[data-report] {
  color: color-mix(in srgb, var(--tab) 58%, var(--muted));
}
.subtabs button[data-report]:hover {
  background: color-mix(in srgb, var(--tab) 10%, transparent);
  color: var(--tab);
}
/* Selected: the only tab at full strength, on its own tint, with a ring so
   the selection survives for anyone who cannot separate the hues. */
.subtabs button[data-report].active {
  background: color-mix(in srgb, var(--tab) 15%, transparent);
  color: var(--tab);
  font-weight: 700;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tab) 38%, transparent);
}
.subtabs button[data-report]:focus-visible {
  outline: 2px solid var(--tab);
  outline-offset: 2px;
}

/* ── The rail: one hue each, drawn from the same six ───────────────────
   The report tabs got a hue apiece and the rail above them did not, which
   left the loudest navigation in the product as the only grey thing in it.

   These are the same six values the tabs use rather than six new ones. The
   product has one palette; a second set invented for the rail would have to
   stay distinct from the first forever, and there is no reading in which the
   rail's Reports and a report tab's Screenshots sharing a violet means the
   two are related -- they are never on screen as a pair.
   ─────────────────────────────────────────────────────────────────────── */
.rail button[data-view="dashboard"] { --nav: var(--tab-home);    } /* blue    */
.rail button[data-view="reports"]   { --nav: var(--tab-shots);   } /* violet  */
.rail button[data-view="projects"]  { --nav: var(--tab-team);    } /* teal    */
.rail button[data-view="users"]     { --nav: var(--tab-prod);    } /* magenta */
.rail button[data-view="settings"]  { --nav: var(--tab-attend);  } /* amber   */
.rail button[data-view="support"]   { --nav: var(--tab-apps);    } /* cyan    */
.rail button[data-view="account"]   { --nav: var(--tab-self);    } /* slate   */

/* Resting, hovering and selected read exactly as they do on the tabs -- the
   same mix, the same tint, the same ring -- because they are the same idea one
   level up, and a second visual language for it would be one to learn. */
.rail button[data-view] {
  color: color-mix(in srgb, var(--nav) 58%, var(--muted));
}
.rail button[data-view]:hover {
  background: color-mix(in srgb, var(--nav) 10%, transparent);
  color: var(--nav);
}
.rail button[data-view].active {
  background: color-mix(in srgb, var(--nav) 15%, transparent);
  color: var(--nav);
  font-weight: 650;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--nav) 38%, transparent);
}
.rail button[data-view]:focus-visible {
  outline: 2px solid var(--nav);
  outline-offset: -2px;
}

/* ── And the same, for the reports when they ARE the rail ──────────────
   In a browser there is no Reports section to open: the reports are the
   navigation, built into this rail by buildWebRail. They carry data-report
   rather than data-view, so none of the rules above reach them, and
   without this they would be the only grey entries in a coloured rail.

   The hues are the tabs' own six, not a new set. A report is the same
   colour wherever it is drawn, which is what makes Attendance amber
   whether you met it as a tab in the desktop app or as a rail entry on the
   website. */
.rail button.rail-report[data-report="rep-home"]         { --nav: var(--tab-home);    }
.rail button.rail-report[data-report="rep-team"]         { --nav: var(--tab-team);    }
.rail button.rail-report[data-report="screenshots"]      { --nav: var(--tab-shots);   }
.rail button.rail-report[data-report="rep-attendance"]   { --nav: var(--tab-attend);  }
.rail button.rail-report[data-report="rep-matrix"]       { --nav: var(--tab-matrix);  }
.rail button.rail-report[data-report="rep-apps"]         { --nav: var(--tab-apps);    }
.rail button.rail-report[data-report="rep-productivity"] { --nav: var(--tab-prod);    }
.rail button.rail-report[data-report="rep-projects"]     { --nav: var(--tab-proj);    }
.rail button.rail-report[data-report="devices"]          { --nav: var(--tab-devices); }

.rail button.rail-report {
  color: color-mix(in srgb, var(--nav) 58%, var(--muted));
}
/* "Web & App Usage" does not fit the rail's column on one line, and the
   rail's default is to cut it: it came out as "Web & App Us..." , which
   reads as a broken label rather than a short one. Wrapping is the answer
   #rail-manage already uses for "Sign in to manage". */
.rail button.rail-report span {
  white-space: normal; text-overflow: clip; line-height: 1.2;
}
.rail button.rail-report:hover {
  background: color-mix(in srgb, var(--nav) 10%, transparent);
  color: var(--nav);
}
.rail button.rail-report.active {
  background: color-mix(in srgb, var(--nav) 15%, transparent);
  color: var(--nav);
  font-weight: 650;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--nav) 38%, transparent);
}
.rail button.rail-report:focus-visible {
  outline: 2px solid var(--nav);
  outline-offset: -2px;
}

/* ── The product switcher, and the admin block at the foot ─────────────
   Website only: buildWebRail returns before any of this in the desktop
   shell, which keeps Dashboard, Reports and the tab strip.

   `.rail-switch` is written as its own class and NOT left to inherit the
   rail's button rules, because at >=1001px `.rail button` is laid out as a
   centred icon ABOVE its label -- correct for a 118px column of
   destinations, wrong for a pill that has to read as one control. Every
   property the pill needs is therefore stated. */
.rail button.rail-switch {
  display: flex; flex-direction: row; align-items: center; gap: 9px;
  width: 100%; min-width: 0; margin: 2px 0 0;
  padding: 9px 10px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2); color: var(--text);
  font: 650 12.5px/1.2 inherit; text-align: left; cursor: pointer;
}
.rail button.rail-switch:hover { border-color: var(--muted); }
.rail button.rail-switch:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
.rail-switch-mark { flex: none; color: var(--accent); }
/* Wraps rather than truncating. Two labels in this rail have already come
   back as "Web & App Us..." and "Sign in to man..."; the rail's default is
   nowrap with an ellipsis and it reaches every button. */
.rail button.rail-switch .rail-switch-name {
  flex: 1 1 auto; min-width: 0;
  white-space: normal; text-overflow: clip; line-height: 1.2;
}
.rail-switch-caret { flex: none; color: var(--muted); }

/* A hairline under the pill, separating the switcher from what it switches. */
.rail-rule {
  flex: none; height: 1px; margin: 10px 2px 8px;
  background: var(--border);
}

/* ── Three bands: a head that stays, a list that scrolls, a foot ───────
   The rail used to be one scrolling box, so on a short window the Tymake
   mark travelled up with the list and landed on top of Home. .rail-brand
   has position:sticky and an opaque background against exactly that, and
   in the dark theme the mark is a transparent PNG -- the words read
   straight through it.

   Only the destinations scroll now. The mark, the customer's logo and the
   product switcher sit above the scrollport rather than inside it, which
   means nothing can pass under them: the overlap is gone by construction
   rather than covered over. The admin block is pinned at the foot, where
   WorkComposer keeps Settings & Admin and User Management too.

   min-height: 0 on the scrolling band is the whole trick. A flex item's
   automatic minimum is its content, so without it the box grows to fit all
   nine entries and pushes the foot off the bottom instead of scrolling.

   Browser only, and only where the rail is a column: the desktop shell
   keeps Layout A, and below 641px the rail is a sideways strip with no
   column to divide. */
@media (min-width: 641px) {
  :root[data-shell="browser"] .rail { overflow: hidden; }
  :root[data-shell="browser"] .rail-brand,
  :root[data-shell="browser"] .replogo,
  :root[data-shell="browser"] .rail-switch,
  :root[data-shell="browser"] .rail-rule { flex: none; }
  :root[data-shell="browser"] .rail-scroll {
    flex: 1 1 auto; min-height: 0;
    overflow-y: auto; overflow-x: hidden;
    display: flex; flex-direction: column; gap: 2px;
    /* Thin, and only when it is needed. A full-width bar beside a 206px
       column is most of the gap between a label and the edge. */
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
    /* And standing off the entries rather than against them. The bar sits
       at this box's right edge, so without the padding it touches the
       rounded corner of every button it passes -- asked for on 21 October,
       "add some gap between scroll bar and nav items". The negative margin
       claws most of the width back out of the rail's own padding, so the
       entries lose two pixels rather than eight. */
    padding-right: 8px;
    margin-right: -6px;
  }
  /* The spacer that used to push the admin block down has nothing left to
     push: the scrolling band takes the slack. It becomes the hairline that
     separates the two, which is what it was there to say. */
  :root[data-shell="browser"] .rail .rail-gap-web {
    flex: none; height: 1px; min-height: 0;
    margin: 8px 2px; background: var(--border);
  }
  /* And the OTHER one -- the spacer the markup leaves after Account --
     takes no slack either.
     
     It is `flex: 1 1 auto` like its twin, so it and the scrolling band
     split the free space between them: 103 pixels of it sat below Account
     and held the admin block that far off the bottom. On Task Management,
     where the list is a single entry, there is far more slack to halve and
     the block floated near the middle of the rail -- which is what the
     owner saw and sent. Measured, not guessed: the last child reported
     flex-grow 1 and a height of 103.
     
     With nothing after it to push and nothing below it to separate, it has
     no job left on the website at all. The desktop shell still has both,
     and this rule does not reach it. */
  :root[data-shell="browser"] .rail > .rail-gap:not(.rail-gap-web) {
    flex: none; height: 0; min-height: 0; margin: 0;
  }
}

/* ── The brand block on the website: a mark, a name, a company ─────────
   .rail-brand is a 34x34 grid cell sized for the mark alone, and
   .brand-tx -- the word Tymake that is already in the markup beside it --
   is display:none for every layout but a dormant one. Both are undone
   here, for the browser only, so the block reads as a brand rather than
   an icon:

       [T] Tymake
           Fingertip Plus

   The company name keeps its line while it is empty. org_name arrives
   with /auth/me, a moment after the first paint, and a line that appears
   out of nothing pushes the whole rail down as you are looking at it. */
:root[data-shell="browser"] .rail-brand {
  width: auto; height: auto; border-radius: 0;
  margin: 2px 0 2px; padding: 2px;
  display: flex; align-items: center; gap: 10px;
  place-items: initial;
}
:root[data-shell="browser"] .rail-brand .brand-mark {
  width: 30px; height: 30px; flex: none;
}
:root[data-shell="browser"] .rail-brand .brand-tx {
  display: block; min-width: 0;
}
:root[data-shell="browser"] .rail-brand .brand-tx b {
  font-size: 15px; font-weight: 800; letter-spacing: -.01em;
  color: var(--text);
}
/* The organisation's name was drawn here, under the mark, and is gone --
   "why fingertip is drowned ? no need to show anywhere remove it", 22
   October. See paintWebBrand in app.js for why it existed at all. */

/* 62 pixels of rail has room for a mark and nothing else. */
@media (max-width: 1000px) {
  :root[data-shell="browser"] .rail-brand .brand-tx { display: none; }
  :root[data-shell="browser"] .rail-brand { justify-content: center; }
}

/* ── Settings borrows the band ─────────────────────────────────────────
   Opening Settings & Admin on the website moves #settingsNav into the
   rail, where WorkComposer's web app keeps it. The node is moved rather
   than copied -- fourteen buttons with handlers already bound -- so these
   rules undo the card it wears on its own screen rather than restyling a
   new thing.

   Everything the standalone list needs is wrong here: it is a bordered
   panel because it floats beside the panes; it is position:sticky with a
   max-height because on its own screen it has to pin itself against a
   scrolling page. In the rail it is simply the contents of a band that
   already scrolls, so all of that comes off. */
.settings-nav.in-rail {
  position: static; max-height: none; overflow: visible;
  background: none; border: 0; border-radius: 0; padding: 0;
  /* A COLUMN, at every width, with its headings.
   *
   * @media (max-width: 860px) turns .settings-nav into a horizontal strip
   * -- display:flex, overflow-x:auto, .grp display:none -- which is right
   * for the settings PAGE on a narrow screen and followed the node into
   * the rail when it moved. Measured in the drawer at 375px: the nav was
   * 36 pixels tall, thirteen 34px entries laid out in a row off the right,
   * and every group heading collapsed to zero height. One entry visible.
   * The owner photographed it: "why only org profile???".
   *
   * (0,2,0) against that rule's (0,1,0), so this wins on specificity and
   * not merely on sitting further down the file. */
  display: flex; flex-direction: column; gap: 2px;
}
.settings-nav.in-rail .grp { display: block; padding: 14px 11px 6px; }
.settings-nav.in-rail .grp:first-child { padding-top: 2px; }
.settings-nav.in-rail button {
  padding: 8px 11px; margin: 1px 0; border-radius: 8px;
  font: 500 13px/1.3 inherit;
}

/* With the list gone from the screen, the panes take the whole width. */
:root[data-shell="browser"] #view-settings .settings-shell {
  grid-template-columns: minmax(0, 1fr);
}

/* The project panel in the rail, on the same terms as the settings list.
 *
 * It is a card on its own screen -- surface, border, radius, shadow, and a
 * min-height of min(70vh, 640px) so it reads as a panel rather than a box
 * round a list. Inside the rail it is none of those: the rail is already
 * the surface, and a 70vh floor inside a band that is 354px tall would
 * push + New Project and the search off the top of its own scroll. */
.pj-side.in-rail {
  background: none; border: 0; border-radius: 0; box-shadow: none;
  padding: 0; margin: 0; gap: 8px; min-height: 0;
}
.pj-side.in-rail .pj-list { margin-top: 0; }

/* With the panel in the rail, the tasks take the whole width -- which is
   the point of moving it: "projects list should be in hamsbuger only". */
:root[data-shell="browser"] #view-projects .pj-shell:not(:has(.pj-side)) {
  grid-template-columns: minmax(0, 1fr);
}

/* On a phone the strip runs sideways and there are no bands. The wrapper
   steps out of the way so its children are flex items of the strip again,
   keeping their spacing and the hairlines between them.

   The settings list IS moved into the rail at this width now. It was not,
   while a phone turned the rail into a sideways strip -- fourteen settings
   entries pushed into a strip already carrying nine destinations was worse
   than leaving the list on the page. The strip is gone; below 640 the rail
   is a drawer, and a drawer is a column with a band that scrolls, which is
   where WorkComposer keeps its own settings list too.

   The two-column rule above is still undone here: with the list in the
   drawer the settings screen is one column of panes. */
@media (max-width: 640px) {
  .rail-scroll { display: contents; }
  :root[data-shell="browser"] #view-settings .settings-shell {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Settings & Admin and User Management. Longer than the words they replace,
   so they take the same wrapping exception the report entries have. */
.rail button.rail-admin span {
  white-space: normal; text-overflow: clip; line-height: 1.2;
}

/* The panel. On <body>, fixed, because `.rail` clips on both axes at every
   width (overflow-y: auto makes the unset overflow-x compute to auto) and
   is a stacking context besides, being position:sticky. Nothing inside it
   can escape either. z-index sits above the header's 41 and below the modal
   backdrop's 100, so a dialog still covers it. */
.rail-switch-panel {
  position: fixed; z-index: 70;
  min-width: 232px; max-width: calc(100vw - 16px);
  padding: 6px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: 0 14px 38px rgba(16, 24, 40, .18);
  animation: pickin .13s ease-out;
}
.rail-switch-head {
  padding: 7px 10px 6px;
  font-size: 10px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.rail-switch-panel .switch-product {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 10px; border: 0; border-radius: 9px;
  background: none; color: var(--text);
  font: 600 13.5px/1.2 inherit; text-align: left; cursor: pointer;
}
.rail-switch-panel .switch-product:hover { background: var(--surface-2); }
.rail-switch-panel .switch-product.on {
  background: var(--accent-soft); color: var(--accent);
}
.rail-switch-panel .switch-product span { flex: 1 1 auto; min-width: 0; }
.switch-tick { flex: none; opacity: 0; }
.rail-switch-panel .switch-product.on .switch-tick { opacity: 1; }

/* ── 641-1000px: the rail is 62 pixels and every label is hidden ───────
   There is no room for "Time Tracking" here, and inventing a fourth rail
   width for it would be a fourth shape to keep in step. The pill keeps its
   icon and loses its words, exactly as every other entry in this band
   does; the product's name is in the panel, one press away. */
@media (max-width: 1000px) {
  .rail button.rail-switch {
    justify-content: center; gap: 0; padding: 9px 0;
  }
  .rail button.rail-switch .rail-switch-name,
  .rail-switch-caret { display: none; }
  .rail-rule { margin: 8px 2px; }
}

/* ── <=640px: the rail is a sideways-scrolling row ────────────────────
   `.rail-gap` is the flexible spacer that pushes the admin block to the
   foot of a COLUMN. There is no foot in a row, and the 640px block hides
   it -- so the two admin entries would simply run on after Devices with
   nothing saying they are a different thing. It becomes a hairline instead
   of disappearing, which is the same separation turned on its side.

   overflow-x on the rail is load-bearing at this width and is not touched:
   it is what holds the rail's minimum contribution at zero in a one-column
   grid. Without it the rail took 358 of a 390px screen. */
@media (max-width: 640px) {
  .rail .rail-gap-web {
    display: block; flex: 0 0 1px; min-height: 0;
    align-self: stretch; margin: 6px 7px;
    background: var(--border);
  }
  .rail-rule {
    flex: 0 0 1px; height: auto; align-self: stretch; margin: 6px 7px;
  }
  .rail button.rail-switch {
    width: auto; flex: none; margin: 0;
    justify-content: flex-start; gap: 8px; padding: 9px 12px;
  }
  .rail button.rail-switch .rail-switch-name { display: inline; }
  .rail-switch-caret { display: inline; }
}

/* ══ Roles & Privileges ═══════════════════════════════════════════════════
   Two panels: the roles on the left, the selected role's privileges on the
   right. The shape matters more than it looks -- every control on the right
   belongs to exactly one card on the left, and a screen that lets you forget
   which role you are editing is a screen that grants the wrong one.
   ═══════════════════════════════════════════════════════════════════════ */
.rolesplit {
  display: grid;
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}
@media (max-width: 900px) { .rolesplit { grid-template-columns: minmax(0, 1fr); } }

.rolelist > h3 { margin: 0 0 2px; }
.rolelist > p.muted { margin: 0 0 14px; font-size: 12.5px; }

.rolecard {
  display: block; width: 100%; text-align: left;
  background: none; border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px 16px; margin-bottom: 10px;
  cursor: pointer; color: var(--text);
  transition: border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
.rolecard:hover:not(:disabled) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, transparent);
  color: var(--text);
}
.rolecard.active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);
}
.rolecard b { display: block; font-size: 16px; margin-bottom: 6px; }
.rolecard-meta {
  display: flex; gap: 14px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* The count of what is switched on is the number somebody scans for, so it
   gets the hue and the rest stays quiet. */
.roleactive { color: var(--good); font-weight: 600; }
.rolelock {
  display: block; margin-top: 8px;
  font-size: 11.5px; color: var(--muted); font-style: italic;
}
.rolecard.fixed { opacity: .85; }

.roledetail-head { margin-bottom: 16px; }
.roledetail-head h3 { margin: 0 0 4px; font-size: 20px; }
.rolefacts {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 13px; color: var(--muted);
}
.rolefact-dot { color: var(--border); }

.roletabs {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding-bottom: 14px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.roletabs-label {
  font-size: 12px; font-weight: 700; letter-spacing: .04em;
  color: var(--muted); margin-right: 6px;
}
.roletab {
  background: none; border: 0; border-radius: 8px;
  padding: 7px 13px; cursor: pointer;
  font: 600 12.5px/1.3 inherit; color: var(--muted);
  transition: background .15s ease, color .15s ease;
}
.roletab:hover:not(:disabled) {
  background: var(--surface-2); color: var(--text); border-color: transparent;
}
.roletab.active {
  background: var(--accent-fill, var(--accent)); color: #fff;
}

.rolegroup { margin-top: 20px; }
.rolegroup h4 {
  margin: 0 0 12px; font-size: 15px; font-weight: 700;
}

/* A privilege is a sentence and a control. The control is pinned right and
   never shrinks, so a long description cannot squeeze a select into
   illegibility -- which is how somebody ends up granting All Users by
   mis-reading a truncated option. */
.privrow {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px;
  padding: 16px; margin-bottom: 10px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-2);
}
.privwords { min-width: 0; }
.privwords b { display: block; font-size: 14.5px; margin-bottom: 4px; }
.privwords p.muted { margin: 0; font-size: 12.5px; max-width: 46ch; }
.privrow select { flex: none; min-width: 210px; }
.privrow .switch { flex: none; margin-top: 2px; }
.privrow select:disabled, .privrow .switch:disabled { opacity: .5; cursor: default; }

/* ══ Switch, rebuilt ══════════════════════════════════════════════════════
   Two problems with the old one, and the second is the one that showed.

   A disabled switch was the live switch at opacity .5, which on a white
   ground turns accent blue into a pale wash -- indistinguishable from a
   control that has failed to paint. Disabled now has colours of its own: an
   "on" switch that cannot be changed still reads as on, muted rather than
   faded.

   And the knob carries a real shadow and a hairline, so the state is legible
   by shape as well as by hue -- colour alone fails for the eight percent of
   men who cannot separate red from green, and this control is pressed without
   reading the label beside it.
   ═══════════════════════════════════════════════════════════════════════ */
.switch {
  position: relative; flex: none;
  width: 46px; height: 26px; padding: 0;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  box-shadow: inset 0 1px 2px rgba(16, 24, 40, .07);
  cursor: pointer;
  transition: background .18s ease, border-color .18s ease, box-shadow .18s ease;
}
/* Both markups: the roles pane draws the knob with ::after, older panes with
   an <i>. One set of geometry so they cannot drift apart. */
.switch::after,
.switch i {
  content: "";
  position: absolute; top: 2px; left: 2px;
  width: 20px; height: 20px; border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .28), 0 0 0 1px rgba(16, 24, 40, .06);
  transition: transform .18s cubic-bezier(.2, .7, .3, 1);
}
.switch i { top: 2px; left: 2px; }

.switch.on {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: inset 0 1px 2px rgba(16, 24, 40, .14);
}
.switch.on::after,
.switch.on i { transform: translateX(20px); }

.switch:hover:not(:disabled) {
  border-color: var(--accent);
}
.switch.on:hover:not(:disabled) {
  filter: brightness(1.06);
}
.switch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Not a faded copy of the live control. Fading the accent is what made a
   locked switch look like a rendering fault. */
.switch:disabled { cursor: default; opacity: 1; box-shadow: none; }
.switch:disabled:not(.on) {
  background: var(--surface-2);
  border-color: var(--border);
}
.switch.on:disabled {
  background: color-mix(in srgb, var(--accent) 40%, var(--surface-2));
  border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
}
.switch:disabled::after,
.switch:disabled i {
  background: color-mix(in srgb, #fff 78%, var(--surface-2));
  box-shadow: 0 1px 1px rgba(16, 24, 40, .12);
}

/* The dark theme washed the accent out to keep it from glowing; a switch is
   small enough that it needs the saturation to read at all. */
:root[data-theme="dark"] .switch.on,
:root:not([data-theme="light"]) .switch.on { filter: none; }
:root[data-theme="dark"] .switch.on:hover:not(:disabled),
:root:not([data-theme="light"]) .switch.on:hover:not(:disabled) {
  filter: brightness(1.1);
}

/* The off state, corrected.
   A white knob on #f0f2f5 measured 1.1:1 -- the knob was there and could not
   be seen, which is most of what "the toggle looks broken" meant. Every major
   toggle does put a light knob on a light track, and they all get away with it
   the same way: the knob is separated by its edge, not by its fill. So the
   edge has to be real. The track darkens a little too, which costs nothing and
   means the pill still reads as a control when the knob is at rest. */
.switch:not(.on) {
  background: color-mix(in srgb, var(--border) 70%, var(--surface-2));
}
.switch::after,
.switch i {
  box-shadow:
    0 1px 3px rgba(16, 24, 40, .32),
    0 0 0 1px rgba(16, 24, 40, .16);
}
/* On an accent track the ring would read as a dark halo, so there it is the
   drop shadow alone doing the work. */
.switch.on::after,
.switch.on i {
  box-shadow: 0 1px 3px rgba(16, 24, 40, .38);
}
.switch:disabled::after,
.switch:disabled i {
  box-shadow:
    0 1px 2px rgba(16, 24, 40, .18),
    0 0 0 1px rgba(16, 24, 40, .12);
}

/* ══ Switch: knob and track become theme tokens ═══════════════════════════
   The knob was #fff in both themes. On a light page that is right; on a dark
   navy track it is a stark white dot that belongs to no palette on the page,
   and it is what "still white only" meant.

   So both parts are tokens. The dark theme gets a slate knob at rest -- one of
   its own greys rather than borrowed white -- and keeps a near-white knob when
   the switch is on, because there it sits on saturated accent and has to read
   against it. The off track is lifted off the card surface in both themes so
   the control is visible before anybody touches it.
   ═══════════════════════════════════════════════════════════════════════ */
:root {
  --switch-track-off: #dbe0e7;
  --switch-track-on:  var(--accent);
  --switch-knob-off:  #ffffff;
  --switch-knob-on:   #ffffff;
  --switch-ring:      rgba(16, 24, 40, .18);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --switch-track-off: #333c6e;
    --switch-knob-off:  #aab5dd;
    --switch-knob-on:   #f2f5ff;
    --switch-ring:      rgba(0, 0, 0, .38);
  }
}
:root[data-theme="dark"] {
  --switch-track-off: #333c6e;
  --switch-knob-off:  #aab5dd;
  --switch-knob-on:   #f2f5ff;
  --switch-ring:      rgba(0, 0, 0, .38);
}

.switch:not(.on) {
  background: var(--switch-track-off);
  border-color: color-mix(in srgb, var(--switch-track-off) 70%, var(--text));
}
.switch.on {
  background: var(--switch-track-on);
  border-color: var(--switch-track-on);
}
.switch::after,
.switch i {
  background: var(--switch-knob-off);
  box-shadow: 0 1px 3px var(--switch-ring), 0 0 0 1px var(--switch-ring);
}
.switch.on::after,
.switch.on i {
  background: var(--switch-knob-on);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
}

/* Disabled keeps the same hues, drained rather than faded -- the state still
   reads, it just stops inviting a press. */
.switch:disabled:not(.on) {
  background: color-mix(in srgb, var(--switch-track-off) 55%, var(--surface));
}
.switch.on:disabled {
  background: color-mix(in srgb, var(--switch-track-on) 42%, var(--surface));
  border-color: color-mix(in srgb, var(--switch-track-on) 52%, var(--surface));
}
.switch:disabled::after,
.switch:disabled i {
  background: color-mix(in srgb, var(--switch-knob-off) 82%, var(--surface));
  box-shadow: 0 1px 2px var(--switch-ring), 0 0 0 1px var(--switch-ring);
}

/* ══ Sign in ══════════════════════════════════════════════════════════════
   Two halves on a wide screen, one card on a narrow one. The left half is not
   decoration: this is a monitoring product, and the sign-in screen is the
   first chance to say plainly what it records and what it does not. Somebody
   reading it there is better than somebody finding out later.

   Everything is a token, so the dark theme is the same design under different
   light rather than a second design kept in step by hand.
   ═══════════════════════════════════════════════════════════════════════ */
#login {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(120% 80% at 15% 0%,
      color-mix(in srgb, var(--accent) 10%, transparent) 0%, transparent 60%),
    var(--bg);
}

.signin {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  width: min(940px, 100%);
  border-radius: 18px;
  overflow: hidden;
  border: 1px solid var(--border);
  box-shadow: var(--elev-3, 0 18px 50px rgba(16, 24, 40, .18));
  background: var(--surface);
}

/* --- the half that explains itself ---------------------------------- */
.signin-brand {
  padding: 40px 38px;
  display: flex;
  flex-direction: column;
  gap: 22px;
  color: #fff;
  background:
    radial-gradient(100% 90% at 100% 0%,
      color-mix(in srgb, #fff 16%, transparent) 0%, transparent 55%),
    linear-gradient(155deg,
      var(--accent) 0%,
      color-mix(in srgb, var(--accent) 62%, #101637) 100%);
}
.signin-mark {
  display: flex; align-items: center; gap: 11px;
  font-family: var(--font-display, inherit);
  font-weight: 700; font-size: 19px; letter-spacing: -.01em;
}
.signin-mark img { border-radius: 8px; display: block; }
.signin-brand h2 {
  margin: 0;
  font-family: var(--font-display, inherit);
  font-size: 27px; line-height: 1.2; font-weight: 700;
  letter-spacing: -.02em;
  text-wrap: balance;
}
.signin-points {
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-direction: column; gap: 11px;
  font-size: 13.5px; line-height: 1.45;
  color: color-mix(in srgb, #fff 88%, transparent);
}
.signin-points li {
  padding-left: 24px; position: relative;
}
.signin-points li::before {
  content: "";
  position: absolute; left: 0; top: 6px;
  width: 13px; height: 8px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  opacity: .85;
}
.signin-foot {
  margin: auto 0 0;
  font-size: 12px; letter-spacing: .06em; text-transform: uppercase;
  color: color-mix(in srgb, #fff 62%, transparent);
}

/* --- the form ------------------------------------------------------- */
#login .login-card {
  border: 0; border-radius: 0; box-shadow: none;
  width: auto;
  padding: 44px 40px;
  display: flex; flex-direction: column;
  justify-content: center;
  background: var(--surface);
}
#login .login-card h1 {
  margin: 0 0 6px;
  font-family: var(--font-display, inherit);
  font-size: 24px; letter-spacing: -.02em;
}
#login .login-card p.sub { margin: 0 0 26px; font-size: 13.5px; }

/* The "Sign in to manage" note. Shown only in that mode (see enterManageSignIn)
   as a quiet accent-tinted callout, so it reads as context for the re-sign-in
   rather than as a plain subtitle. */
#login .login-card p.signin-manage-note {
  margin: 0 0 22px;
  padding: 12px 14px;
  border-radius: 10px;
  color: var(--text);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  line-height: 1.5;
}

/* "Continue in your browser", under the manage note it belongs with. Same
   quiet weight: it is an alternative, not the action the card is for. */
.signin-browser { margin-top: 10px; }
.signin-browser a { font-weight: 600; }

/* The "Not now" button sits under the primary, with a little air above it. */
#login .login-card #login-cancel { margin-top: 10px; }

#login .field { margin-bottom: 16px; }
#login .field label {
  font-size: 12px; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 7px;
}
#login .field input {
  padding: 11px 13px;
  font-size: 14px;
  border-radius: 9px;
  background: var(--bg);
  transition: border-color .15s ease, box-shadow .15s ease;
}
#login .field input::placeholder { color: color-mix(in srgb, var(--muted) 70%, transparent); }
#login .field input:focus {
  outline: none;
  border-color: var(--accent);
}

.pw-wrap { position: relative; display: block; }
.pw-wrap input { padding-right: 66px; }
.pw-peek {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; padding: 6px 9px;
  font: 600 12px/1 inherit; letter-spacing: .03em;
  color: var(--accent); cursor: pointer; border-radius: 7px;
}
.pw-peek:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.pw-peek:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

#login .btn {
  width: 100%;
  margin-top: 6px;
  padding: 12px 16px;
  font-size: 14px; font-weight: 650;
  border-radius: 9px;
}
#login .btn:disabled { opacity: .6; cursor: default; }

/* An error here is the only thing standing between somebody and their work,
   so it is stated rather than whispered in grey. */
#login .error:not(:empty) {
  margin-top: 14px;
  padding: 10px 12px;
  border-radius: 9px;
  font-size: 13px;
  color: var(--bad);
  background: color-mix(in srgb, var(--bad) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--bad) 30%, transparent);
}

.signin-help {
  margin: 22px 0 0;
  font-size: 12.5px; color: var(--muted);
}

/* One column below the split: the brand half goes above the form rather than
   away, because on a phone it is the only thing saying what this is. */
@media (max-width: 820px) {
  .signin { grid-template-columns: minmax(0, 1fr); }
  .signin-brand { padding: 30px 26px; gap: 16px; }
  .signin-brand h2 { font-size: 21px; }
  .signin-points { font-size: 13px; }
  .signin-foot { display: none; }
  #login .login-card { padding: 30px 26px; }
}

/* A caveat under an alternate download. Small, but it has to be readable --
   somebody who misses it installs a tracker that records nothing. */
.altnote {
  margin: 10px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}
.altnote code {
  font-size: 11.5px;
  padding: 1px 5px;
  border-radius: 5px;
  background: var(--surface-2);
  color: var(--text);
}

/* Links to the privacy and terms pages. Quiet, but never hidden -- somebody
   deciding whether to install a tracker should be able to find them without
   asking anybody. */
.legallinks { margin-left: auto; display: inline-flex; gap: 10px; }
.legallinks a { color: var(--muted); text-decoration: none; }
.legallinks a:hover { color: var(--accent); text-decoration: underline; }
.legalnote { margin: 0 0 18px; font-size: 13.5px; color: var(--muted); }

/* ─── Users ───────────────────────────────────────────────────────────
 *
 * A teams rail beside a table. The rail is a filter, not navigation: the
 * table is always the answer to "who", and the rail narrows it.
 */
.users-shell { display: grid; grid-template-columns: 224px 1fr; gap: 16px; align-items: start; }
@media (max-width: 900px) { .users-shell { grid-template-columns: 1fr; } }

.users-teams {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 10px;
  position: sticky; top: 12px;
}
/* ...but only while it IS a rail beside the list.
 *
 * .users-shell goes to one column at 900px, which puts this card ABOVE
 * the people rather than beside them -- and sticky then pinned a
 * full-width card to the top of the phone while the people scrolled
 * underneath it. The owner photographed the result: "Engineering  2  :"
 * with a user's "Active" pill and role picker drawn straight through it.
 *
 * It paints through because of the cards, not the card: this rule has
 * z-index auto, and .users-table tr was given position: relative so the
 * actions button could be pinned to its corner. Among positioned
 * siblings at auto, the later one in the DOM wins -- and the list comes
 * after the teams card. Taking the stickiness away at the width where it
 * stopped meaning anything fixes the cause rather than bidding z-indexes
 * against each other.
 *
 * Below the rule it overrides, which it ties with at (0,1,0). */
@media (max-width: 900px) {
  .users-teams { position: static; }
}
/* Close under the button. A divider between the two was tried and taken out
   again: the rule plus the air it needs on both sides came to more room than
   the plain gap it replaced, and the ask was for less. The button has a border
   of its own, which separates it from the flat rows well enough. */
.teamnew { width: 100%; justify-content: center; margin: 0 0 8px; }
.teamlist { display: flex; flex-direction: column; gap: 1px; }
/* The teams rail on the Users screen. Named apart from the Team report's
   table rows on purpose: that screen builds its rows as <tr>, and a flex rule
   reaching those takes every row out of the table layout, so each row's cells
   size to their own content and no two rows line up. */
.teamnav {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 7px 9px; border: 0; border-radius: 8px; cursor: pointer;
  background: none; color: var(--text); font: inherit; font-size: 14px; text-align: left;
}
.teamnav > span:first-of-type { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.teamnav:hover { background: var(--surface-2); }
.teamnav.on { background: var(--accent-soft); color: var(--accent); font-weight: 650; }
.teamicon { display: grid; place-items: center; flex: none; color: var(--muted); }
.teamnav.on .teamicon { color: var(--accent); }
/* The numbers line up down the card. They are tabular already, but a row
   with a ⋮ after it pushed its count left while "All teams", which has no ⋮,
   kept its own hard against the edge -- so 62 and 2 sat in different columns.
   Every row now carries the same two slots, and the rows without a menu carry
   an empty one. */
.teamcount {
  color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums;
  min-width: 2.1em; text-align: right;
}
.teammore {
  color: var(--muted); padding: 0 2px; border-radius: 5px; line-height: 1;
  flex: none; width: 15px; text-align: center;
}
.teammore.ghost { visibility: hidden; pointer-events: none; }
.teammore:hover { background: var(--surface-2); color: var(--text); }
.teamhint { color: var(--muted); font-size: 12.5px; line-height: 1.5; margin: 10px 4px 2px; }

.users-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.users-head h2 { margin: 0; font-size: 22px; letter-spacing: -.01em; }
.users-acts { margin-left: auto; display: flex; gap: 8px; }

.users-filters { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.users-search { flex: 1; min-width: 220px; }
.users-role { min-width: 150px; }
.users-toggleinactive {
  display: flex; align-items: center; gap: 7px;
  color: var(--muted); font-size: 13px; white-space: nowrap; cursor: pointer;
}

/* The list scrolls inside itself, not by taking the page with it.
 *
 * Asked for on 14 October: sixty-seven people made the whole screen scroll, so
 * the heading, the search box, the role filter and "Show archived" all left
 * the top of the window -- and the thing somebody scrolls a list of sixty-seven
 * people for is usually the search box they have just scrolled away from.
 *
 * The header row stays put inside the scroller, on its own background, so the
 * columns are still named when you are forty rows down. It needs an opaque
 * background: a transparent sticky header has the rows sliding visibly
 * underneath it.
 */
.users-tablewrap {
  overflow: auto;
  max-height: calc(100vh - 330px);
  min-height: 220px;
  overscroll-behavior: contain;
}
.users-tablewrap .users-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface);
}
.users-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.users-table th {
  text-align: left; font-size: 11px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); font-weight: 700; padding: 0 14px 9px; white-space: nowrap;
}
.users-table td { padding: 12px 14px; border-top: 1px solid var(--border); vertical-align: middle; }
.users-table tbody tr:hover { background: var(--surface-2); }
.users-table tr.off .users-name, .users-table tr.off .users-mail { opacity: .55; }
.users-who { display: flex; align-items: center; gap: 11px; }
.users-name { font-weight: 600; }
.users-mail { color: var(--muted); font-size: 12.5px; }
.users-rolesel { min-width: 122px; padding: 5px 8px; font-size: 13px; }
.users-count { color: var(--muted); font-size: 12.5px; margin: 12px 2px 0; }

/* ─── Dialogs ─────────────────────────────────────────────────────────
 * The backdrop deliberately does not close these: a half-filled form of
 * six new people is easy to lose to a stray click, and there is no undo.
 */
.sheet-back {
  position: fixed; inset: 0; z-index: 240; background: rgba(8, 11, 16, .55);
  display: grid; place-items: center; padding: 24px;
}
.sheet {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  width: min(880px, 100%); max-height: 88vh; overflow-y: auto;
  box-shadow: 0 26px 70px rgba(0,0,0,.32); padding: 22px 24px 20px;
}
.sheet-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.sheet-head h3 { margin: 0; font-size: 18px; letter-spacing: -.01em; }
.sheet-x { margin-left: auto; font-size: 22px; line-height: 1; padding: 2px 10px; }
.sheet-intro { color: var(--muted); font-size: 13.5px; line-height: 1.6; margin: 0 0 16px; max-width: 62ch; }
.sheet-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 16px; }
@media (max-width: 620px) { .sheet-grid { grid-template-columns: 1fr; } }
.sheet-foot { display: flex; align-items: center; gap: 10px; margin-top: 20px; }
.sheet-foot > :nth-last-child(3) { margin-right: auto; }
.sheet-status { color: var(--bad); font-size: 13px; line-height: 1.45; }

.field { display: flex; flex-direction: column; gap: 5px; }
.field-label { font-size: 12px; font-weight: 650; color: var(--muted); }
.field-static {
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface-2); color: var(--muted); font-size: 14px;
  overflow: hidden; text-overflow: ellipsis;
}

.addrows { display: flex; flex-direction: column; gap: 12px; margin-bottom: 12px; }
.addrow {
  display: grid; gap: 10px; align-items: end;
  grid-template-columns: 1.2fr 1.4fr 1fr .9fr .8fr auto;
  padding: 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2);
}
@media (max-width: 900px) { .addrow { grid-template-columns: 1fr 1fr; } }
.addrow-x { align-self: end; }

.popmenu {
  position: fixed; z-index: 260; min-width: 176px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 14px 40px rgba(0,0,0,.22); padding: 5px;
  display: flex; flex-direction: column;
}
.popmenu button {
  background: none; border: 0; text-align: left; cursor: pointer;
  padding: 9px 11px; border-radius: 7px; font: inherit; font-size: 14px; color: var(--text);
}
.popmenu button:hover { background: var(--surface-2); }
.popmenu button.danger { color: var(--bad); }
.popmenu button.danger:hover { background: rgba(192, 57, 43, .12); }

/* The sync card's mark is a control, not decoration. */
.dash-mark.act { cursor: pointer; border: 0; padding: 0; font: inherit; background: inherit; }
.dash-mark.act:hover { filter: brightness(1.08); }
.dash-mark.act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dash-mark.act.spinning svg { animation: dash-spin .8s linear infinite; }
@keyframes dash-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .dash-mark.act.spinning svg { animation: none; opacity: .5; }
}

/* ─── Users, richer rows ─────────────────────────────────────────────── */
.users-who { display: flex; align-items: center; gap: 12px; }
.users-whotext { min-width: 0; }
.users-table td { padding: 14px; }

.teamchip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 11px; border-radius: 999px;
  background: var(--surface-2); color: var(--text); font-size: 13px;
}
.teamchip .teamicon { color: var(--muted); }

.rolecell { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }
.rolepill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 12px; border-radius: 999px; font-size: 13px; font-weight: 600;
}
.rolepill.owner { background: var(--accent-soft); color: var(--accent); }
.roleicon { display: grid; place-items: center; line-height: 0; }

.users-rolesel {
  border-radius: 999px; padding: 5px 10px; font-size: 13px; font-weight: 600;
  min-width: 118px;
}
.users-rolesel.admin { color: var(--accent); border-color: var(--accent); }
.users-rolesel.manager { color: var(--warn); border-color: var(--warn); }

.rolesub { font-size: 12px; }
.rolesub-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--muted); font: inherit; font-size: 12px; text-decoration: underline;
  text-underline-offset: 2px;
}
.rolesub-link:hover { color: var(--accent); }
.rolesub-link.empty { color: var(--warn); }

.statuspill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 12px; border-radius: 999px; font-size: 13px; font-weight: 600;
}
.statuspill.on { background: rgba(26, 147, 111, .14); color: var(--good); }
.statuspill.off { background: var(--surface-2); color: var(--muted); }

.rowacts { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }
.rowmore { line-height: 0; padding: 7px 9px; }

/* ─── Pickers and bulk entry ─────────────────────────────────────────── */
.picked { color: var(--muted); font-size: 12.5px; margin: 12px 2px 8px; }
.picklist {
  max-height: 46vh; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 10px;
}
.pickrow {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; border-bottom: 1px solid var(--border); cursor: pointer;
}
.pickrow:last-child { border-bottom: 0; }
.pickrow:hover { background: var(--surface-2); }
.pickrow.off { opacity: .6; }

.bulkarea {
  width: 100%; font-family: inherit; font-size: 14px; line-height: 1.6;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--text); resize: vertical;
}
.bulkfile { font-size: 14px; }
.bulkpreview { margin: 14px 0; font-size: 13.5px; color: var(--muted); }
.bulkrow { color: var(--text); padding: 2px 0; }
.bulkbad { color: var(--warn); margin-top: 6px; }

/* A grid child defaults to min-width:auto, which is "as wide as my content".
   The table's own scroller then never engages and the whole page scrolls
   sideways instead -- 25px of it, once the rows grew pills and avatars. */
.users-main { min-width: 0; }
.users-tablewrap { overflow-x: auto; max-width: 100%; }

/* ══ Icons, pickers and switches ══════════════════════════════════════════
   Three controls that were doing the job in plain HTML. A native select
   draws its list with the operating system, so it could never be themed --
   in dark mode it opened as a white slab, and no amount of styling the
   closed control could reach the open one. The list is drawn here now; the
   select itself stays in the document as the value.
   ─────────────────────────────────────────────────────────────────────── */

/* Stroked in currentColor, so an icon in a red row is red and one in a
   disabled row is muted, with no second colour to keep in step. */
.ic { flex: none; vertical-align: -.18em; }

/* Fills its slot by default, like a select in a form field. "hug" is set
   from the script when the select it replaced sized itself to its text --
   a filter in a toolbar rather than a field in a form. */
.ftsel { position: relative; display: block; width: 100%; min-width: 0; }
.ftsel.hug { display: inline-block; width: auto; }

/* Out of sight, still the control: it keeps the value, the name, the form
   and its label, and only stops being the thing that gets drawn. */
/* Left at its natural width rather than stretched, so it can still be
   measured after the fact on a screen that was hidden at upgrade time. */
.ftsel-native {
  position: absolute; left: 0; top: 0;
  opacity: 0; pointer-events: none;
  margin: 0;
}

.ftsel-btn {
  display: flex; align-items: center; gap: 8px;
  width: 100%; min-height: 38px;
  padding: 7px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
.ftsel-btn:hover:not(:disabled) { border-color: var(--muted); }
.ftsel-btn.open, .ftsel-btn:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.ftsel-btn:disabled { opacity: .55; cursor: default; }

.ftsel-label {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ftsel-label.empty { color: var(--muted); }

/* Drawn rather than shipped as a glyph: an arrow character picks up whatever
   font happens to resolve, and half of them centre it differently. */
.ftsel-caret {
  flex: none; width: 9px; height: 9px;
  border-right: 1.6px solid var(--muted);
  border-bottom: 1.6px solid var(--muted);
  transform: translateY(-2px) rotate(45deg);
  transition: transform .18s ease;
}
.ftsel-btn.open .ftsel-caret { transform: translateY(1px) rotate(225deg); }
.ftsel-btn:hover:not(:disabled) .ftsel-caret,
.ftsel-btn.open .ftsel-caret { border-color: var(--accent); }

.ftsel-menu {
  position: fixed; z-index: 1400;
  display: flex; flex-direction: column;
  max-width: calc(100vw - 16px);
  padding: 5px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  box-shadow: 0 10px 30px rgba(16, 24, 40, .16), 0 2px 6px rgba(16, 24, 40, .08);
  overflow: hidden;
  animation: pickin .13s ease-out;
}
@keyframes pickin { from { opacity: 0; transform: translateY(-4px); } }

.ftsel-search {
  display: flex; align-items: center; gap: 7px;
  margin: 1px 1px 5px;
  padding: 0 9px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-2);
  color: var(--muted);
}
.ftsel-search input {
  flex: 1; min-width: 0;
  padding: 8px 0;
  border: 0; background: none; outline: none;
  color: var(--text); font-size: 13px;
}
.ftsel-search input::-webkit-search-cancel-button { display: none; }

.ftsel-list { overflow-y: auto; min-height: 0; }

.ftsel-opt {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 8px 10px;
  border: 0; border-radius: 8px;
  background: none; color: var(--text);
  font-size: 13.5px; text-align: left;
  cursor: pointer;
}
.ftsel-opt-tick { flex: none; width: 16px; height: 16px; color: var(--accent); }
.ftsel-opt-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ftsel-opt.on { font-weight: 600; }
/* One highlight driven by the keyboard and the mouse alike, so arrowing down
   and moving the pointer cannot both claim to be the current row. */
.ftsel-opt.cursor { background: var(--accent-soft); color: var(--accent); }
.ftsel-opt:focus-visible { outline: none; background: var(--accent-soft); }
.ftsel-opt:disabled { opacity: .45; cursor: default; }
.ftsel-empty { padding: 14px 10px; color: var(--muted); font-size: 13px; text-align: center; }

/* Menu rows carry an icon now, and a delete tints its own. */
.popmenu button { display: flex; align-items: center; gap: 10px; }
.popmenu button .ic { color: var(--muted); }
.popmenu button:hover .ic { color: var(--text); }
.popmenu button.danger .ic, .popmenu button.danger:hover .ic { color: var(--bad); }

/* The knob squashes under the press and the track lights up behind it --
   the little bit of weight that separates a switch from a coloured pill. */
.switch:active:not(:disabled)::after,
.switch:active:not(:disabled) i { width: 24px; }
.switch.on { box-shadow: inset 0 1px 2px rgba(16, 24, 40, .14),
                         0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent); }
.switch:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}

/* Dark: the same shapes over a dark ground. Shadows that read as depth on
   white read as smudges here, so they give way to a lighter edge. */
:root[data-theme="dark"] .ftsel-menu,
:root[data-theme="dark"] .popmenu {
  box-shadow: 0 12px 34px rgba(0, 0, 0, .55);
  border-color: color-mix(in srgb, var(--border) 70%, #fff 8%);
}
:root[data-theme="dark"] .switch::after,
:root[data-theme="dark"] .switch i { background: #f2f4f7; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ftsel-menu,
  :root:not([data-theme="light"]) .popmenu {
    box-shadow: 0 12px 34px rgba(0, 0, 0, .55);
    border-color: color-mix(in srgb, var(--border) 70%, #fff 8%);
  }
  :root:not([data-theme="light"]) .switch::after,
  :root:not([data-theme="light"]) .switch i { background: #f2f4f7; }
}

@media (prefers-reduced-motion: reduce) {
  .ftsel-menu { animation: none; }
  .ftsel-caret, .ftsel-btn { transition: none; }
}

/* A drop button is a button with a menu, so it wears the same caret the
   picker does rather than a ▾ from whichever font resolved. */
.dropbtn { display: inline-flex; align-items: center; gap: 8px; }
.dropbtn .ftsel-caret { border-color: currentColor; opacity: .75; }
.dropbtn:hover .ftsel-caret { opacity: 1; }

/* The rail carries drawn icons now, not font glyphs -- a glyph resolved to
   whatever face the machine had, and the one for Users rendered as a colour
   emoji that ignored the rail's colour entirely. */
.rail button i { display: inline-flex; align-items: center; justify-content: center;
                 width: 21px; height: 21px; }
.rail button i svg { width: 21px; height: 21px; flex: none; }
@media (max-width: 1100px) {
  .rail button i, .rail button i svg { width: 22px; height: 22px; }
}

/* ══ The manager sheet ════════════════════════════════════════════════════
   Two ways into one set of people. Teams is a bulk selector, not a second
   thing to save -- reporting lines are held per person -- so a team here is
   ticked when everyone in it reports to this manager and half-ticked when
   only some do. Without that middle state, a team with one person already
   assigned looks exactly like a team with nobody assigned.
   ─────────────────────────────────────────────────────────────────────── */
.sheet-tabs {
  display: flex; gap: 4px;
  margin: 2px 0 14px;
  border-bottom: 1px solid var(--border);
}
.sheet-tab {
  padding: 9px 16px;
  border: 0; border-bottom: 2px solid transparent;
  border-radius: 0;
  background: none; color: var(--muted);
  font-size: 14px; font-weight: 600;
  cursor: pointer;
}
.sheet-tab:hover { color: var(--text); }
.sheet-tab.on { color: var(--accent); border-bottom-color: var(--accent); }
.sheet-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.sheet-meta {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin: 12px 2px 8px;
}
.sheet-total { color: var(--muted); font-size: 13px; }

.pickall {
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2);
  margin-bottom: 8px;
}
.pickall-text { font-weight: 600; font-size: 13.5px; }

/* A checkbox is drawn by the platform too, and the platform's is a small grey
   square that ignores the accent and has no half-state worth looking at. */
.picklist input[type="checkbox"], .pickall input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  position: relative; flex: none;
  width: 19px; height: 19px; margin: 0;
  border: 1.5px solid var(--border); border-radius: 5px;
  background: var(--surface);
  cursor: pointer;
  transition: background .14s ease, border-color .14s ease;
}
.picklist input[type="checkbox"]:hover:not(:disabled),
.pickall input[type="checkbox"]:hover:not(:disabled) { border-color: var(--accent); }
.picklist input[type="checkbox"]:checked, .pickall input[type="checkbox"]:checked,
.picklist input[type="checkbox"]:indeterminate, .pickall input[type="checkbox"]:indeterminate {
  background: var(--accent); border-color: var(--accent);
}
.picklist input[type="checkbox"]::after, .pickall input[type="checkbox"]::after {
  content: ""; position: absolute; opacity: 0;
  left: 5.5px; top: 2px; width: 5px; height: 9.5px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}
.picklist input[type="checkbox"]:checked::after,
.pickall input[type="checkbox"]:checked::after { opacity: 1; }
/* Half-ticked reads as a bar, not a tick -- it is "some", not "yes". */
.picklist input[type="checkbox"]:indeterminate::after,
.pickall input[type="checkbox"]:indeterminate::after {
  opacity: 1;
  left: 3.5px; top: 8px; width: 9px; height: 0;
  border-width: 0 0 2px 0; transform: none;
}
.picklist input[type="checkbox"]:focus-visible,
.pickall input[type="checkbox"]:focus-visible {
  outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}
.picklist input[type="checkbox"]:disabled { opacity: .45; cursor: default; }

.pickrow .teamicon {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 32px; height: 32px;
  border-radius: 9px;
  background: var(--surface-2); color: var(--muted);
}

/* The row's menu button is a button, with an edge, the way the one it sits
   beside is -- a bare glyph at the end of a row reads as decoration. */
.rowmore {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 32px; padding: 0;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); color: var(--muted);
}
.rowmore:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* Tasks and reports is not a screen, so it is not a [data-view] and does not
   pick up the rail's colour rules -- it needs its own, or the same undefined
   --nav that left Users painted in body text. */
.rail button.railjob { --nav: var(--tab-devices); color: color-mix(in srgb, var(--nav) 58%, var(--muted)); }
.rail button.railjob:hover {
  background: color-mix(in srgb, var(--nav) 10%, transparent);
  color: var(--nav);
}
.rail button.railjob:focus-visible { outline: 2px solid var(--nav); outline-offset: -2px; }

/* The only control in the column, so it should look like one you can hit. */
.rowmore {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 34px; padding: 0;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); color: var(--muted);
}
.rowmore:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.rowmore svg { width: 20px; height: 20px; }

/* The other two ways to add people, where somebody adding people is looking. */
.sheet-alt { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }

/* .ghost.sm is two classes and .rowmore is one, so the small-button padding
   won the box and squeezed the icon to two thirds of its width. Naming the
   row as well outranks it, and flex:none stops the icon being the thing that
   gives way when the box is tight. */
.rowacts button.rowmore { padding: 0; width: 38px; height: 34px; }
.rowacts button.rowmore svg { width: 20px; height: 20px; flex: none; }

/* A label and its value both shrank, so a long value wrapped and pulled the
   label apart with it -- "Last sync" came out as "Last" over "sync". The
   label holds its width and the value keeps to one line. */
.dash-row { align-items: center; }
.dash-key {
  display: inline-flex; align-items: center; gap: 7px;
  flex: none; white-space: nowrap;
}
.dash-key svg { color: var(--muted); flex: none; }
.dash-row b { text-align: right; white-space: nowrap; }

/* A card that is all rows has no 26px figure to space it, so the rows take
   the room the figure would have had rather than huddling under the title. */
.dash-rowsonly .dash-row { padding: 7px 0; font-size: 13.5px; }
.dash-rowsonly .dash-title { margin-bottom: 10px; }
.dash-card.c .dash-key svg { color: var(--good); }

/* ══ Per-person exceptions ════════════════════════════════════════════════
   A setting on this screen is the organisation's value, and some people
   depart from it. The screen could not say so before: the control showed one
   value while an unknown number of machines did something else. The wording
   is blunt on purpose -- a settings screen that quietly misdescribes the
   fleet is worse than one that admits it.
   ─────────────────────────────────────────────────────────────────────── */
.srow-wrap { display: block; }

.ovr-strip { display: inline-flex; align-items: center; gap: 10px; margin-left: 12px; }
.ovr-mixed {
  display: inline-flex; align-items: center;
  padding: 3px 9px; border-radius: 999px;
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  color: var(--warn); font-size: 11.5px; font-weight: 700;
  letter-spacing: .03em; text-transform: uppercase;
}
.ovr-open { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
.ovr-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px;
  border-radius: 999px;
  background: var(--accent-soft); color: var(--accent);
  font-size: 11.5px; font-weight: 700;
}

.ovr-note {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0 0 14px; padding: 11px 14px;
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  font-size: 13px;
}
.ovr-note p { margin: 0; flex: 1; color: var(--text); }
.ovr-note-mark { color: var(--accent); line-height: 0; margin-top: 1px; flex: none; }
/* A setting switched off by something above it takes its notice with it. */
.srow-wrap:has(.srow.dim) .ovr-note { opacity: .5; }

/* Left, not centred.
 *
 * .modal:where(.modal-back > .modal) sets text-align: center, which is
 * right for a confirmation and wrong for a list of people: the owner's
 * screenshot shows the heading, its paragraph, "Everyone is on the
 * default" and every name stacked down the middle of the dialog. The
 * same trap a new dialog fell into on 22 September, when 27 geometry
 * checks passed while the thing looked wrong.
 *
 * :where() contributes nothing, so that rule is (0,1,0) and so is this;
 * it wins by sitting four thousand lines further down. */
.ovr-modal { width: min(760px, calc(100vw - 32px)); text-align: left; }
.ovr-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; padding: 4px 2px 14px;
  border-bottom: 1px solid var(--border);
}
.ovr-head b { font-size: 17px; }
.ovr-head p { margin: 4px 0 0; max-width: 48ch; }
.ovr-tally {
  flex: none; padding: 6px 12px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent);
  font-size: 12.5px; font-weight: 650;
}

.ovr-tools { display: flex; align-items: center; gap: 12px; margin: 14px 0 10px; }
.ovr-tools .users-search { flex: 1; min-width: 0; }
.ovr-filters { display: flex; gap: 6px; flex: none; }

.ovr-list { max-height: 46vh; overflow-y: auto; margin: 0 -4px; padding: 0 4px; }
.ovr-row {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: center; gap: 14px;
  padding: 11px 4px; border-bottom: 1px solid var(--border);
}
.ovr-row:last-child { border-bottom: 0; }
.ovr-who { display: flex; align-items: center; gap: 11px; min-width: 0; }
.ovr-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ovr-mail { font-size: 12.5px; color: var(--muted); overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }
.ovr-pill {
  display: inline-block; margin-top: 4px;
  padding: 2px 9px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent);
  font-size: 11.5px; font-weight: 650;
}
.ovr-control { min-width: 190px; display: flex; justify-content: flex-end; align-items: center; }
.ovr-num { width: 110px; }
.ovr-foot { display: flex; align-items: center; justify-content: space-between; }

@media (max-width: 720px) {
  .ovr-row { grid-template-columns: 1fr; }
  .ovr-control { justify-content: flex-start; }
  .ovr-tools { flex-wrap: wrap; }
}

/* ══ Team report: the columns are read across rows ═════════════════════════
   Every figure in this table is compared with the one above it, so a column
   that shifts because somebody has a longer name makes the whole table
   harder to read than a plain list would be. The person column is capped,
   the name steps down a size before it truncates, and nothing to the right
   of it can be pushed.
   ─────────────────────────────────────────────────────────────────────── */
.teamgrid th:first-child { width: 250px; }
.teamgrid td:first-child, .teamgrid th:first-child { max-width: 270px; }
.teamgrid .person { min-width: 0; }
/* The text block is capped in pixels, not just told it may shrink: a max-width
   on the cell is advisory in an auto-layout table, so the column still grew to
   fit a long name. Capping the block is what actually makes it ellipsise. */
.teamgrid .person > div { min-width: 0; max-width: 190px; }
.teamgrid .person b {
  display: block; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.teamgrid .person .nm-sm { font-size: 13.5px; }
.teamgrid .person .nm-xs { font-size: 12.5px; letter-spacing: -.005em; }
.teamgrid .person .muted.tiny {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Figures line up on the digit, not on the word. */
.teamgrid td.num { font-variant-numeric: tabular-nums; }

/* ── Reports > Home: the personal page and the cards under the team's day ──
   Prefixed hm- throughout: this file's generic names have collided three
   times already. */
.hm-two { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 16px; }
@media (max-width: 900px) { .hm-two { grid-template-columns: 1fr; } }
.hm-two > .card, .hm-two + .card { margin-top: 0; }
.hm-viewall { text-align: right; margin: 8px 0 0; }
.hm-viewall a, .hm-link { color: var(--accent); text-decoration: none; font-weight: 600; font-size: 13px; }
.hm-viewall a:hover, .hm-link:hover { text-decoration: underline; }
.hm-lead { font-weight: 600; margin: 10px 0 6px; }
.hm-unclassified { background: var(--surface-2); border-radius: 8px; padding: 10px 12px; margin: 6px 0; font-size: 13px; }
.hm-row { display: flex; align-items: center; gap: 12px; padding: 7px 0; border-top: 1px solid var(--border); font-size: 13px; }
.hm-row:first-of-type { border-top: 0; }
.hm-name { flex: 1 1 40%; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hm-bar { flex: 1 1 40%; height: 6px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
.hm-bar i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.hm-val { flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.hm-val.good { color: var(--good); } .hm-val.low { color: var(--warn); }
.hm-split { display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: var(--surface-2); margin: 10px 0 6px; }
.hm-split i { display: block; }
.hm-split i.good { background: var(--good); } .hm-split i.mid { background: var(--muted); } .hm-split i.low { background: var(--warn); }
.hm-toggle { margin: 8px 0; }
.hm-three { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 14px; }
@media (max-width: 900px) { .hm-three { grid-template-columns: 1fr; } }
.hm-weekbig { text-align: right; }
.hm-weekbig b { display: block; font-size: 24px; line-height: 1.1; }
.hm-chart { width: 100%; height: auto; display: block; }
.hm-grid { stroke: var(--border); stroke-width: 1; }
.hm-axis { fill: var(--muted); font-size: 11px; }
.hm-line { stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.hm-line.hm-line-a { stroke: var(--accent); }
.hm-line.hm-line-b { stroke: var(--muted); stroke-dasharray: 5 5; stroke-width: 2; }
.hm-dot.hm-line-a { fill: var(--accent); } .hm-dot.hm-line-b { fill: var(--muted); }
.hm-notstarted { background: rgba(224,162,68,.16); color: var(--warn); font-size: 11px; margin-left: 8px; }
.pill.brk { background: rgba(224,162,68,.16); color: var(--warn); }
/* Its own colour, not the accent: the accent is also the "now" line across
   every row, and a flag drawn in the same violet read as everybody flagged at
   the current time. The old background was a hardcoded blue the rebrand missed. */
.pill.hm-spike {
  background: color-mix(in srgb, var(--unusual) 14%, transparent);
  color: var(--unusual); font-size: 11px;
  /* Never shrinks and never wraps. It sits in a fixed-width column beside a
     name of unknown length, and a flag reading "4" when it means 40m is a
     wrong number rather than a missing one. The name truncates instead. */
  flex: none; white-space: nowrap;
}

/* The name and its flag on one line: the name takes what is left and ellipses,
   the flag keeps its full width. min-width:0 on both the line and the name,
   because a flex item will not shrink below its content without it -- which is
   what pushed the badge out of the column in the first place. */
.person .namecell { min-width: 0; }
.person .nameline { display: flex; align-items: center; gap: 8px; min-width: 0; }
.person .nameline b {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* And the line under it, which was being cut mid-domain. */
.person .muted.tiny { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The application window's footer: reachability, last sync, version. */
.appfoot {
  display: flex; align-items: center; gap: 16px;
  padding: 7px 18px; font-size: 12px; color: var(--muted);
  border-top: 1px solid var(--border); background: var(--surface);
}
.appfoot .foot-conn { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; }
.appfoot .foot-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.appfoot.online .foot-dot { background: var(--good); }
.appfoot.online #foot-state { color: var(--good); }
.appfoot.offline .foot-dot { background: var(--warn); }
.appfoot.offline #foot-state { color: var(--warn); }
/* An answer old enough to be worth doubting. The time alone reads as current
   -- somebody has to subtract it from their own clock to notice it is from
   yesterday evening -- so when it goes stale it is coloured as well as aged. */
.appfoot.stale #foot-sync { color: var(--warn); font-weight: 600; }
.appfoot .foot-ver { margin-left: auto; }
/* Pinned to the bottom edge of the window, beside the sidebar, the way the
   other tracker's is -- the one place the connection and the last sync are
   always in view. It used to sit in the page's normal flow after <main>, and
   because the whole document scrolls here (#app is a grid, not a scrolling
   pane) the grid put it in a new row at the very end of the page, in the 210px
   sidebar column: below every chart, and only a sliver even there. Reported on
   22 September as missing. Fixed rather than a grid item, so it cannot shift
   the rail or the content; <main> already keeps 56px at its foot, more than
   this bar is tall, so nothing is left underneath it. */
/* The footer clears the rail. All four of these were set to 0 when the
   rail became a bar across the top and there was nothing on the left to
   clear; with the rail back down the left they are the widths it has at
   each breakpoint. Restored 19 October 2026. */
.appfoot { position: fixed; left: 210px; right: 0; bottom: 0; z-index: 10; }
@media (max-width: 1000px) { .appfoot { left: 62px; } }
@media (max-width: 640px) { .appfoot { left: 0; } }
/* A toast rises above the bar instead of landing on it. */
body:has(#app:not(.hidden)) .toast { bottom: 48px; }
body:has(#app:not(.hidden)) .save-flash { bottom: 50px; }

/* ─────────────────────────────────────────────────────────────────────────
   Skeleton loaders. Grey placeholders in the rough shape of what is coming,
   shown on a cold load when there is nothing cached to draw yet, so the screen
   reads as "arriving" rather than empty or broken. The instant real figures
   land they replace the skeleton. No new theme tokens: the base is --surface-2
   (defined for both themes) and the sheen is a translucent white sweep, which
   reads as a highlight over the pale block in light and the dark block in dark. */
.sk {
  position: relative; overflow: hidden;
  background: var(--surface-2);
  border-radius: 8px;
  flex: none;
}
.sk::after {
  content: ""; position: absolute; inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .35), transparent);
  animation: sk-shimmer 1.3s ease-in-out infinite;
}
@keyframes sk-shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .sk::after { animation: none; } }

.sk-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius, 12px); padding: 18px 20px;
  display: flex; flex-direction: column; gap: 13px; min-width: 0;
}
.sk-row { display: flex; align-items: center; gap: 14px; min-width: 0; }
.sk-grow { flex: 1 1 auto; min-width: 0; }
.sk-avatar { width: 34px; height: 34px; border-radius: 50%; }
/* Skeleton dashboard cards/stats stack their bars vertically. */
.dash-card.sk-stack, .dash-stat.sk-stack { display: flex; flex-direction: column; gap: 11px; justify-content: center; }
/* A grid of image tiles for the screenshots wall while it loads. */
.sk-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 14px; }
.sk-tile { aspect-ratio: 16 / 10; border-radius: 10px; }

/* ── Manual time: what replacing tracked time costs ────────────────────────
   Shown inside the Add time dialog when the window asked for already has
   tracked time in it. The three figures are the same three the Delete screen
   shows, because it is the same loss: hours worked, hours on break, and the
   screenshots taken then. Names are prefixed because this file is one long
   stylesheet and "warning" and "figures" were never going to stay unique. */
/* The Requested and Decided columns on the manual-time queue.

   Both timestamps were on the row in the database and on neither screen,
   so "how long has this been waiting" meant reading the audit log. */
/* What the activity figure actually covers, under it, where an hour was
   added by hand. Quiet: it explains the number, it is not the number. */
.actcover { display: block; font-size: 11px; line-height: 1.3; }

.req-stamp { white-space: nowrap; font-variant-numeric: tabular-nums; }
.req-stamp > span { display: block; }
/* Two days is the point at which a pending request stops being "not got
   to it yet". Coloured rather than bolded: the row is already busy, and
   this has to be findable by scanning the column, not by reading it. */
.req-stale { color: var(--warn); font-weight: 600; }

.overwrite-warning {
  margin: 12px 0 4px;
  padding: 12px 14px;
  border: 1px solid var(--bad);
  border-left-width: 3px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--bad) 7%, transparent);
}
.overwrite-warning b { display: block; font-size: 13.5px; }
.overwrite-warning .hint { margin: 4px 0 0; }
.overwrite-figures {
  display: grid;
  /* Four now, not three: the application and website records an overwrite
     deletes were never shown. auto-fit so the fourth wraps under the
     others in a narrow dialog rather than squeezing all four to nothing. */
  grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: 8px;
  margin-top: 10px;
}
.overwrite-figures > div {
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  min-width: 0;
}
.overwrite-figures b {
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.overwrite-label {
  display: block;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ── The three figures under "On now", each a filter ─────────────────────────
   "39 tracking · 2 break · 18 off", where each number narrows the list below
   to the people it counts. They sit inside a card, so the card's own .home-pick
   look (a border that lights up) has nothing to light up here: these carry a
   tint instead, and the pressed one says so in the accent. Prefixed, because
   this file is one long stylesheet and a bare "fig" would not stay unique. */
.home-fig { padding: 1px 5px; margin: 0 -2px; border-radius: 6px; }
.home-fig.home-pick:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--text);
}
.home-fig.home-pick.home-picked {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent); font-weight: 700; box-shadow: none;
}

/* "Flagged only ×" in the table's own heading. Prefixed home-chip- because
   .chip and .tag are both taken in this file, and a bare one would inherit
   whichever came last. */
.home-chip {
  display: inline-flex; align-items: center; gap: 6px;
  margin-left: 10px; padding: 2px 7px 2px 9px;
  border-radius: 999px; cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 700; line-height: 1.7;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
}
/* color-mix is not everywhere, and a chip with no ground reads as broken
   rather than quiet. */
@supports not (background: color-mix(in srgb, red 14%, transparent)) {
  .home-chip { background: var(--accent-soft); border-color: var(--accent); }
}
.home-chip:hover { background: color-mix(in srgb, var(--accent) 24%, transparent); }
.home-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.home-chip-x { font-size: 14px; line-height: 1; opacity: .8; }
/* Unpinned: the same chip, offering the thing back rather than disappearing.
   A control that vanishes when you use it leaves no way to undo it. */
.home-chip-off {
  color: var(--muted); background: none; border-color: var(--border);
  font-weight: 600;
}
.home-chip-off:hover { color: var(--accent); border-color: var(--accent); }
.home-chip:hover .home-chip-x { opacity: 1; }


/* ── Manual time requests ────────────────────────────────────────────────────
   The envelope in the top bar and the screen it opens. Every class here is
   prefixed (req-, rep-inbox, remove-): this is one long stylesheet, and a
   bare "bulk", "note" or "empty" already means something somewhere else. */
/* At the right-hand end of the Reports strip. Written as .subtabs .rep-inbox
   because it is a button inside .subtabs, whose own button rule (padding, no
   border, no background) would otherwise outrank a single class. */
/* The envelope is the envelope: no box around it.
 *
 * 17 October: "remove that border and bg color, just only the envelope is
 * enough". It had a bordered, filled 36px tile because it used to live among
 * the tabs and needed telling apart from them; it sits in report headers now,
 * beside Export, where a second outlined box is one box too many.
 */
.rep-inbox {
  position: relative; flex: none;
  width: 34px; height: 34px; padding: 0; border-radius: 9px;
  display: inline-grid; place-items: center; cursor: pointer;
  background: none; border: 0; color: var(--muted);
}
.rep-inbox:hover:not(:disabled), .rep-inbox.on { color: var(--accent); }
/* Centred on whatever row it lands in, rather than pinned to the top of it:
   the header rows it sits in are aligned to their first line, and a 34px
   icon beside a two-line control reads as having slipped upwards. */
.rep-inbox { align-self: center; }
/* `hidden` loses to the display above, as it did for the count beside it. */
.rep-inbox[hidden] { display: none; }
/* Never in the rail. index.html starts it there, because the button has to
   live somewhere before the first report has drawn and placeInbox has moved
   it into that report's header; while it is there it is not shown. Asked for
   on 17 October: "dont bring that envelope below the devices". */
.subtabs .rep-inbox { display: none; }
.rep-inbox svg { display: block; }
.rep-inbox-count {
  position: absolute; top: -7px; right: -7px;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
  background: var(--bad); color: #fff;
  font-size: 10.5px; font-weight: 700; line-height: 18px; text-align: center;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--surface);
}
/* An element given display above loses the hidden attribute's own rule. */
.rep-inbox-count[hidden] { display: none; }

#view-rep-requests .card + .card { margin-top: 14px; }
#view-rep-requests .card > h2 + .hint { margin: 0; }
.req-status { display: flex; gap: 8px; }
/* Labels line up across the top; the picker and the two buttons differ by a few px. */
.req-filters { display: flex; flex-wrap: wrap; gap: 4px 16px; align-items: flex-start; margin-top: 10px; }
.req-filters .field { margin-bottom: 0; min-width: min(220px, 100%); }


/* ---- Manual Time Requests: one line for the heading and the filters ----
 *
 * 17 October: "that heading filters and filters bring side by side, and hide
 * that description under more info dropdown". The page used to open with a
 * three-line paragraph and a card headed "Filter & Search" carrying a second
 * line about itself, so the first actual request began below the fold.
 *
 * All five class names checked against this stylesheet first.
 */
.req-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; max-width: none;
  /* Air between the filters and the table they filter. Without it the card
     begins against the bottom of the dropdown and the two read as one
     block. Asked for on 17 October. */
  margin-bottom: 18px;
}
.req-head-words { min-width: 0; flex: 1 1 320px; }
.req-head-line { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.req-head-line h2 { margin: 0; }
/* Its own line above the heading: a way back belongs before the place you
   have arrived at, not after it. */
.req-back { order: -1; flex-basis: 100%; text-align: left; font-size: 13px; }
/* Reads as the quiet aside it is, beside a heading rather than under it. */
.req-more { font-size: 13px; }
.req-about { margin: 8px 0 0; max-width: 78ch; }
.req-about[hidden] { display: none; }
/* The filters at the other end of the same line, and no top margin now that
   there is no card heading above them to clear.
   
   Side by side, not stacked: `nowrap` plus fields that may shrink. They were
   wrapping because each field carried `min-width: 220px` from the old card,
   where they had a whole row to themselves -- two 220px minimums beside a
   heading will not fit, so the second dropped under the first. */
.req-head .req-filters {
  margin-top: 0; flex: 0 1 auto; align-items: flex-end;
  flex-wrap: nowrap; gap: 14px;
}
.req-head .req-filters .field { min-width: 0; }
.req-head .req-filters select { min-width: 190px; }
/* Below a laptop the heading takes the width and the filters go under it --
   still side by side with each other. */
@media (max-width: 1100px) {
  .req-head .req-filters { width: 100%; justify-content: flex-start; }
}

.req-bulk {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 9px 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.req-bulk[hidden] { display: none; }
.req-bulk b { margin-right: auto; font-size: 13.5px; font-variant-numeric: tabular-nums; }

/* Sized to fit a laptop screen, Actions column included: every column that can
   wrap does, so only a genuinely narrow window scrolls -- and when one does, the
   scrolling happens inside the card, not the page. */
table.req-table { min-width: 760px; }
table.req-table th, table.req-table td { padding-left: 8px; padding-right: 8px; }
table.req-table td { vertical-align: top; white-space: normal; }
table.req-table th.req-pick,
table.req-table td.req-pick { width: 28px; padding-right: 0; }
table.req-table input[type="checkbox"] { width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--accent); }
.req-member { display: flex; gap: 10px; align-items: flex-start; min-width: 140px; }
.req-member b { display: block; font-weight: 600; }
.req-sub { display: block; margin-top: 2px; color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
/* "Sep-12, 11:30 AM" and "to 12:15 PM" each hold together; the break falls between. */
.req-when { display: block; font-variant-numeric: tabular-nums; }
.req-when span { white-space: nowrap; }
.req-why { min-width: 120px; max-width: 280px; overflow-wrap: anywhere; }
.req-note { min-width: 120px; }

/* ---- a smaller table, 17 October ----
 *
 * "reduce the size of the table also font size". Eight columns of 14px type
 * with 14px of padding on every row made four requests fill a 1080px screen.
 * The numbers below are the smallest that still read at arm's length: 12.5px
 * body, 11px for the uppercase headings, and padding halved.
 *
 * Nothing here is scoped loosely. Every rule names table.req-table, because
 * .req-* prefixes aside, this stylesheet has shipped four collisions and two
 * rules both called .modal.
 */
table.req-table { font-size: 12.5px; min-width: 700px; }
table.req-table th {
  font-size: 11px; letter-spacing: .04em;
  padding-top: 8px; padding-bottom: 8px;
}
table.req-table td { padding-top: 8px; padding-bottom: 8px; line-height: 1.45; }
/* The avatar and the two lines beside it set the height of every row, so it
   is the one thing worth shrinking twice. */
table.req-table .avatar { width: 26px; height: 26px; font-size: 10.5px; }
table.req-table .pill { font-size: 10.5px; padding: 2px 8px; }
table.req-table .req-sub, table.req-table .req-done { font-size: 11px; }
/* The description column was the widest thing on the row and wrapped to three
   lines; the comment column no longer needs to be as wide as the reason. */
table.req-table .req-why { max-width: 220px; }
table.req-table .req-note { min-width: 150px; max-width: 220px; }
table.req-table .req-actions { gap: 6px; margin-top: 6px; }
table.req-table .req-actions button { font-size: 11.5px; padding: 3px 9px; }
.req-note input { width: 100%; padding: 6px 9px; font-size: 13px; }
.req-actions { display: flex; flex-direction: column; align-items: stretch; gap: 6px; }
.req-done { color: var(--muted); font-size: 12.5px; white-space: nowrap; }
.pill.req-work {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}
.pill.req-replaces { display: inline-block; margin-top: 6px; font-size: 11px; white-space: nowrap; }

.req-pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px 16px; flex-wrap: wrap;
  margin-top: 14px; color: var(--muted); font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.req-pager > div { display: flex; align-items: center; gap: 10px; }

.req-empty { padding: 34px 16px; text-align: center; color: var(--muted); font-size: 13.5px; }
.req-empty b { display: block; margin-bottom: 4px; color: var(--text); font-size: 15px; }
.req-empty span { display: block; max-width: 52ch; margin: 0 auto; }

/* Remove time: the found figures sit between the window and the red button. */
.remove-time .overwrite-warning { margin-top: 4px; margin-bottom: 14px; text-align: left; }
.remove-found { margin: 4px 0 12px; }
.remove-found .overwrite-figures { margin-top: 0; }

/* ── Focus, said once and said last ──────────────────────────────────────
 *
 * One state, one indicator: a 2px accent outline, and only for the keyboard.
 *
 * It has to sit at the end of the file to work. There are several `:focus`
 * rules scattered above -- the same idea written out five times as the
 * stylesheet grew -- and each sets `outline: none` at the same specificity as
 * an `:focus-visible` rule, so whichever comes last in the file wins. With the
 * outline declared in the middle, tabbing into a text field left nothing but a
 * border going slightly blue.
 *
 * What was removed to get here: a 3px glow, painted on `:focus` rather than
 * `:focus-visible`, so it appeared on every mouse click. Clicking the employee
 * picker put a pale blue halo around the picker and its search box at once.
 */
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible,
[tabindex]:focus-visible,
/* Two above carry heavier selectors and set `outline: none` -- being last is
   not enough to beat them, so they are named here at their own weight. */
select:not([multiple]):not([size]):focus-visible,
.modal input:focus-visible,
.modal select:focus-visible,
.modal textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ─── Settings › Projects: the stage list ───────────────────────────
   A picklist, not a form. One line per stage: order, the name, how many
   projects sit on it, and the dot-dot-dot. The name is an input so a click
   edits it in place, but it is drawn as text until then. */
.pj-stagelist { display: flex; flex-direction: column; }
.pj-stagerow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: 8px;
  align-items: center;
  padding: 3px 4px 3px 0;
}
.pj-stagerow + .pj-stagerow { border-top: 1px solid var(--border); }
.pj-stagerow.archived .pj-stagename { color: var(--muted); text-decoration: line-through; }
.pj-stagemove { display: flex; gap: 0; }
.pj-stagemove .iconbtn { width: 24px; height: 28px; font-size: 13px; color: var(--muted); }
.pj-stagemove .iconbtn:disabled { opacity: .25; }
.pj-stagename {
  width: 100%;
  padding: 7px 8px;
  border-color: transparent;
  background: transparent;
  font-size: 13.5px;
  font-weight: 600;
}
.pj-stagename:hover { border-color: var(--border); background: var(--surface); }
.pj-stagename:focus { border-color: var(--accent); background: var(--surface); font-weight: 500; }
.pj-stageuse { white-space: nowrap; font-size: 12px; color: var(--muted); }
.pj-stageadd { display: flex; gap: 8px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.pj-stageadd input { flex: 1 1 auto; }

/* ─── Projects and tasks ──────────────────────────────────────────────
   The shape of the reference: projects down the left, a filter bar and the
   tasks on the right. Every class is prefixed pj-. Words go in .linkbtn,
   .btn or a class of their own; glyphs go in .iconbtn -- see
   test_a_word_never_goes_in_an_icon_button. */
.pj-shell {
  display: grid;
  grid-template-columns: minmax(230px, 290px) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

/* ── Left: the projects ── */
.pj-side {
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 12px;
  /* Tall enough to feel like the panel it is in the reference, and it grows
     with the list rather than boxing it: no inner scrollbar at eight rows. */
  min-height: min(70vh, 640px);
}
.pj-newproject { width: 100%; }

/* The search box: a filled, rounded field whose glyph lights on focus and
   which grows a clear button once there is text. The wrapper carries the
   border and the ring; the input inside is bare, so the same box reads the
   same in the side panel, the filter bar and the dialog. */
.pj-search {
  position: relative;
  display: flex;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-2);
  transition: border-color .12s ease, box-shadow .12s ease, background .12s ease;
}
.pj-search:hover { border-color: var(--muted); }
.pj-search:focus-within {
  border-color: var(--accent);
  background: var(--surface);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.pj-search .pj-ic, .pj-search > svg { position: absolute; left: 11px; color: var(--muted); pointer-events: none; display: grid; }
.pj-search:focus-within .pj-ic, .pj-search:focus-within > svg { color: var(--accent); }
.pj-search input, .pj-modal .pj-search input {
  width: 100%;
  border: 0;
  border-radius: 10px;
  background: transparent;
  box-shadow: none;
  padding: 9px 34px 9px 36px;
  font-size: 13.5px;
  color: var(--text);
}
.pj-search input:focus-visible, .pj-modal .pj-search input:focus-visible { outline: none; box-shadow: none; border: 0; }
.pj-search input::placeholder { color: var(--muted); }
.pj-search .pj-clear {
  position: absolute;
  right: 6px;
  display: none;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}
.pj-search.has-text .pj-clear { display: grid; place-items: center; }
.pj-search .pj-clear:hover { background: var(--surface-2); color: var(--text); }

.pj-list { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
.pj-empty { padding: 10px 2px; }

.pj-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  min-height: 42px;
  padding: 6px 4px 6px 12px;
  border-radius: 8px;
  cursor: pointer;
  color: inherit;
  position: relative;
}
.pj-item:hover { background: var(--surface-2); }
.pj-item.selected { background: var(--accent-soft); }
/* The dot the reference puts before the folder of the open project. */
.pj-item.selected::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 50%;
  width: 5px; height: 5px;
  margin-top: -2.5px;
  border-radius: 50%;
  background: var(--accent);
}
.pj-item.selected .pj-name, .pj-item.selected .pj-folder { color: var(--accent); }
.pj-item.archived .pj-name { color: var(--muted); }
.pj-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pj-folder { display: grid; place-items: center; color: var(--muted); }
.pj-name {
  font-weight: 600;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Always on a project or a stage row, as the reference draws it; on a task
   row, where the reference has no menu, only on hover. */
.pj-more { opacity: .55; transition: opacity .12s ease; }
.pj-item:hover .pj-more, .pj-item.selected .pj-more, .pj-stagerow:hover .pj-more { opacity: 1; }
tr .pj-more { opacity: 0; }
tr:hover .pj-more, tr:focus-within .pj-more { opacity: 1; }
@media (hover: none) { .pj-more, tr .pj-more { opacity: 1; } }

/* ── The overflow menu ── */
.pj-menu {
  position: fixed;
  z-index: 60;
  min-width: 170px;
  padding: 5px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 28px rgba(16, 24, 40, .16);
  display: flex;
  flex-direction: column;
}
/* Written with the element so they out-rank button.danger (0,1,1), which
   otherwise fills the Delete item solid red and paints the word in the same
   red: a block nobody could read until they hovered it. */
.pj-menu button.pj-menuitem {
  text-align: left;
  padding: 9px 12px;
  border: 0;
  border-radius: 7px;
  background: none;
  box-shadow: none;
  color: var(--text);
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
}
.pj-menu button.pj-menuitem:hover:not(:disabled) { background: var(--surface-2); filter: none; box-shadow: none; }
.pj-menu button.pj-menuitem.danger { color: var(--bad); background: none; }
.pj-menu button.pj-menuitem.danger:hover:not(:disabled) {
  color: var(--bad);
  background: color-mix(in srgb, var(--bad) 12%, transparent);
}

/* ── Right: the filter bar ── */
.pj-main { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.pj-filtercard { padding: 12px 14px; }
.pj-filters { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.pj-filters .pj-search { flex: 1 1 220px; min-width: 180px; }
/* The picker wraps each select and copies the select's flex and width onto
   the wrapper, so the compact size is declared on the select and repeated on
   the wrapper. Left to the default every picker took a whole row. */
.pj-filters select,
.pj-filters > .ftsel { flex: 0 1 auto; width: auto; min-width: 150px; max-width: 220px; }
.pj-filters .ftsel .ftsel-btn { width: auto; min-width: 150px; }
.pj-morebtn {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-size: 13.5px;
  cursor: pointer;
}
.pj-morebtn:hover { border-color: var(--muted); }
.pj-morebtn i { font-style: normal; font-size: 11px; color: var(--muted); }

/* ── Right: the tasks ── */
.pj-head { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; }
.pj-head h2 { margin: 0; }
.pj-selectall {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 16px 0 2px;
  padding: 0 0 0 8px;
  font-size: 14px;
  font-weight: 600;
}
.pj-tasks td { vertical-align: middle; padding-top: 14px; padding-bottom: 14px; }
.pj-checkcol { width: 28px; padding-right: 0; }
/* The name is a button that opens Task Details; drawn as the name. */
button.pj-taskname {
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  font-weight: 600;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
button.pj-taskname:hover { color: var(--accent); text-decoration: underline; }
button.pj-taskname:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.pj-status { color: var(--text); }
.pj-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.pj-who span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A finished task's row is drawn like any other. It was faded to 68%,
   Start included, and read as a row that could not be used again; done is
   not permanent, and the status column already says Done. */
/* The row the clock is on, as the reference tints it. */
tr.pj-running > td { background: color-mix(in srgb, var(--accent) 9%, transparent); }
tr.pj-running button.pj-taskname { color: var(--accent); }
.linkbtn.pj-act.pj-finish { color: var(--good); }
.linkbtn.pj-act.pj-finish:hover { background: color-mix(in srgb, var(--good) 12%, transparent); }
.pj-rowtools { display: flex; gap: 6px; align-items: center; justify-content: flex-end; white-space: nowrap; }

/* The row's one action, drawn as the reference draws it: a word in the
   accent with a play mark, and no box. It is a .linkbtn so the card-button
   rule -- which boxes every other button in a card -- leaves it alone. */
.linkbtn.pj-act {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  border-radius: 7px;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
}
.linkbtn.pj-act:hover { background: var(--accent-soft); text-decoration: none; }
/* Task Details */
.pj-modal.pj-taskmodal { width: min(720px, 100%); }
.pj-modal.pj-confirmmodal { width: min(460px, 100%); }
.pj-confirm-text { margin: 4px 0 8px; font-size: 15px; }
.pj-two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; }
.pj-readonly {
  padding: 10px 12px;
  border-radius: 9px;
  background: var(--surface-2);
  color: var(--text);
  font-size: 13.5px;
}
.pj-savecenter { display: flex; justify-content: center; margin-top: 4px; }
.pj-taskfoot {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 22px;
  align-items: center;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 13.5px;
}
.pj-taskfoot .pj-fact { white-space: nowrap; }
.pj-taskfoot .linkbtn.pj-danger { margin-left: auto; padding: 0; font-size: 13.5px; text-decoration: underline; }
.pj-taskdetails select:disabled, .pj-taskdetails input:disabled, .pj-taskdetails textarea:disabled { opacity: .7; cursor: default; }

.pj-pager { display: flex; gap: 10px; align-items: center; justify-content: center; margin-top: 14px; }

/* ── Dialogs ──
   The base .modal rule centres its text and scrolls the whole box. A form is
   read left to right, and the one thing that scrolls is the body: the title
   stays put and so does the button at the bottom, whichever pane is open and
   however many people are in the list. */
.pj-modal {
  width: min(620px, 100%);
  text-align: left;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.pj-modal.pj-setupmodal { width: min(1040px, 100%); }
.pj-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 2px; flex: none; }
.pj-modal-head b { font-size: 22px; }
.pj-subtitle { margin: 0 0 14px; color: var(--muted); font-size: 14px; flex: none; }
.pj-modal-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px 2px; }
.pj-modal-foot { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border); flex: none; }
/* The modal's own input rule sets the padding and the double focus ring; the
   search box needs room for its glyph, and a focused field needs one soft
   ring, not an outline drawn around a coloured border. */
.pj-modal .pj-search input { padding-left: 36px; }
.pj-modal input:not([type="checkbox"]):focus-visible, .pj-modal select:focus-visible, .pj-modal textarea:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
/* A checkbox has no border to colour; tabbing down the list needs the ring. */
.pj-modal input[type="checkbox"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pj-form { display: flex; flex-direction: column; gap: 14px; }
.pj-field { display: flex; flex-direction: column; gap: 6px; }
.pj-field > span { font-size: 14px; font-weight: 600; }
.pj-field > small { color: var(--muted); font-size: 12.5px; }
.pj-field input, .pj-field select, .pj-field textarea { width: 100%; }
.pj-req { color: var(--bad); font-style: normal; }
.pj-tracked { color: var(--muted); font-size: 12.5px; }
.pj-ic { display: inline-grid; place-items: center; color: inherit; }
.btn.pj-create { display: inline-flex; align-items: center; gap: 8px; }

/* ── New Project Setup: two panes ── */
.pj-setup {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  gap: 22px;
  align-items: start;
  /* Tall enough that the box does not jump when the access pane opens, and
     never taller than the body it sits in: the box is 88vh at most and the
     title, subtitle, footer and padding take about 260px of that. A fixed
     420px was 2px too many on a 700px window, which drew a scrollbar for
     nothing. */
  min-height: min(420px, calc(88vh - 260px));
}
.pj-setup-nav {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px;
  background: var(--surface-2);
  border-radius: var(--radius);
  align-self: stretch;
}
.pj-setup-tab {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.pj-setup-tab:hover { background: var(--surface); }
.pj-setup-tab.on { background: var(--accent-soft); color: var(--accent); }
.pj-setup-pane { min-width: 0; }
.pj-modal .pj-pane-title { margin: 0 0 12px; font-size: 16px; }   /* out-ranks .modal h3 */

.pj-tabs { display: flex; gap: 28px; border-bottom: 1px solid var(--border); margin-bottom: 14px; }
.pj-tab {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 2px 10px;
  border: 0;
  border-radius: 0;   /* the button base rounds corners, which tapers the underline */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  background: none;
  color: var(--text);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}
.pj-tab.on { color: var(--accent); border-bottom-color: var(--accent); }
.pj-note {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 0 0 16px;
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.45;
}
.pj-note .pj-ic { color: var(--accent); flex: none; margin-top: 1px; }
.pj-accesscontrols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 18px; }
.pj-accesscontrols .pj-field:only-child { grid-column: 1 / -1; }
.pj-tally { display: flex; justify-content: space-between; margin: 14px 0 10px; color: var(--muted); font-size: 14px; }
.pj-people {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.pj-personrow {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  gap: 14px;
  align-items: center;
  padding: 10px 14px;
  cursor: pointer;
}
/* A team row has no face, so it has one column fewer; on the four-column grid
   the member count landed in the middle of the row. */
.pj-personrow.pj-teamrow { grid-template-columns: auto minmax(0, 1fr) auto; }
.pj-personrow + .pj-personrow { border-top: 1px solid var(--border); }
.pj-personrow:hover { background: var(--surface-2); }
.pj-personrow.pj-selectrow { grid-template-columns: auto minmax(0, 1fr); background: var(--surface-2); font-size: 14px; }
.pj-personrow .avatar { width: 36px; height: 36px; font-size: 12.5px; }
.pj-persontext { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pj-persontext b { font-size: 14.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pj-persontext small { color: var(--muted); font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pj-personteam { color: var(--muted); font-size: 13.5px; white-space: nowrap; }

/* ── Pills: stage on a project, priority on a task ── */
.pill.pj-stage    { background: var(--accent-soft);       color: var(--accent); }
.pill.pj-p-high   { background: rgba(192, 57, 43, .14);   color: var(--bad); }
.pill.pj-p-medium { background: var(--accent-soft);       color: var(--accent); }
.pill.pj-p-low    { background: var(--surface-2);         color: var(--muted); }

@media (max-width: 900px) {
  .pj-shell { grid-template-columns: minmax(0, 1fr); }
  .pj-side { min-height: 0; }
  .pj-morebtn { margin-left: 0; }
  .pj-setup { grid-template-columns: minmax(0, 1fr); }
  .pj-setup-nav { flex-direction: row; }
  .pj-accesscontrols { grid-template-columns: minmax(0, 1fr); }
}

/* A destructive action written as a word, not squeezed into an icon box. */
.linkbtn.pj-danger { color: var(--bad); }

/* ─── The picker's search box ──────────────────────────────────────────
   Photographed with a hard blue ring around it, twice. The ring is the
   input:focus-visible rule further up, which is placed last on purpose so
   that tabbing into an ordinary form field shows something -- but a text
   input matches :focus-visible on *every* focus, mouse included, and this
   box focuses itself the moment the picker opens. So every click on the
   picker rang its search box. The trigger button beside it already uses a
   soft ring (see .ftsel-btn.open); the box now uses the same one, on the
   wrapper, so the pair look like one control instead of two conventions. */
.ftsel-search {
  background: var(--surface);
  transition: border-color .15s ease, box-shadow .15s ease, color .15s ease;
}
.ftsel-search:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
}
/* Heavier than the late rule, and after it: being last was its whole claim. */
.ftsel-search input:focus-visible { outline: none; }

/* ─── The rail, narrowed to the reference ────────────────────────────
   WorkComposer's rail is a third the width of the one this replaces: the
   icon sits over the word, both centred, and the column takes 118px, not
   210. Nothing about the buttons' colours changes -- those rules are keyed
   on data-view and stand -- only their shape. All of it above 1000px only:
   below that the rail still collapses to icons alone, and below 640px it
   still turns into a strip, and both of those rules come earlier. */
@media (min-width: 1001px) {
  #app { grid-template-columns: 118px minmax(0, 1fr); }
  .appfoot { left: 118px; }
  .rail { padding: 12px 8px; gap: 4px; }
  .rail-brand { margin: 4px auto 14px; }
  .rail button {
    flex-direction: column;
    justify-content: center;
    gap: 6px;
    padding: 11px 4px 10px;
    text-align: center;
    font-weight: 600;
    font-size: 12.5px;
    line-height: 1.2;
    border-radius: 10px;
  }
  .rail button i { width: auto; height: 22px; display: grid; place-items: center; }
  .rail button span { max-width: 100%; }
  .rail button.active, .rail button[data-view].active { font-weight: 650; }
  .rail .rail-face { margin-bottom: 2px; }
  #rail-face { width: 30px; height: 30px; font-size: 11.5px; }
  .rail-me { white-space: normal; line-height: 1.25; padding: 8px 2px 2px; font-size: 11px; }
}

/* ── The website's rail is wider, and reads along rather than down ─────
   118 pixels is Layout A: the rail the owner built for the desktop app,
   where every entry is an icon above a centred word. It is the right
   answer for eight short destinations in a window, and the wrong one for
   the website, where the same column has to hold "Web & App Usage",
   "Settings & Admin", "User Management" and a product switcher. At 118px
   the switcher came out as "Time Track" over a clipped chevron.

   WorkComposer's own sidebar is about this width with the icon beside the
   label, which is what the owner has been pointing at. So the website gets
   that and the desktop app keeps Layout A untouched -- every rule here is
   behind :root[data-shell="browser"], which app.js stamps only when the
   page was not served by the shell's loopback server.

   .appfoot moves with the column. The two are a pair: the footer is
   position:fixed with an explicit left, so a rail widened without it
   leaves an empty strip down the side, and no test covers the pair. */
@media (min-width: 1001px) {
  :root[data-shell="browser"] #app { grid-template-columns: 206px minmax(0, 1fr); }
  :root[data-shell="browser"] .appfoot { left: 206px; }
  :root[data-shell="browser"] .rail { padding: 12px 12px; gap: 2px; }
  :root[data-shell="browser"] .rail button {
    flex-direction: row;
    justify-content: flex-start;
    gap: 11px;
    padding: 9px 11px;
    text-align: left;
    font-size: 13.5px;
  }
  :root[data-shell="browser"] .rail button i {
    width: 21px; height: 21px; flex: none;
  }
  :root[data-shell="browser"] .rail button span { max-width: none; }
  /* The boot skeleton draws the same column, so it must not step. */
  :root[data-shell="browser"] .boot { grid-template-columns: 206px minmax(0, 1fr); }
}

/* ─── The boot skeleton ──────────────────────────────────────────────
   Painted over the page while app.js decides who is signed in -- a shell
   handover and a round trip, up to a few seconds on a cold machine -- so
   the window opens on the shape of the screen rather than a blank. Drawn
   from the same tokens as the page, so it is the right colour in both
   themes; removed by hideBoot() the moment the app or the form is shown. */
.boot {
  position: fixed; inset: 0; z-index: 50;
  display: grid; grid-template-columns: 118px minmax(0, 1fr);
  background: var(--bg);
}
.boot i { display: block; border-radius: 8px; background: var(--surface-2); }
.boot-rail {
  display: flex; flex-direction: column; align-items: center; gap: 26px;
  padding: 22px 0; background: var(--surface); border-right: 1px solid var(--border);
}
.boot-rail i { width: 40px; height: 40px; border-radius: 12px; }
.boot-rail .boot-brand { width: 34px; height: 34px; border-radius: 9px; margin-bottom: 8px; }
.boot-main { display: flex; flex-direction: column; min-width: 0; }
.boot-bar {
  display: flex; align-items: center; gap: 20px; padding: 16px 24px;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.boot-bar i { height: 38px; }
.boot-bar .w1 { width: 140px; }
.boot-bar .w2 { width: 150px; }
.boot-bar .w4 { flex: 1 1 auto; height: 10px; border-radius: 5px; }
.boot-bar .w3 { width: 150px; }
.boot-body { padding: 24px 28px; display: flex; flex-direction: column; gap: 16px; }
.boot-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.boot-cards i, .boot-card {
  height: 120px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--border);
}
.boot-card { display: block; height: 320px; }
/* A slow sheen across the blocks says "loading" without a spinner. */
.boot i, .boot-card { position: relative; overflow: hidden; }
.boot i::after, .boot-card::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--text) 5%, transparent) 50%, transparent 70%);
  transform: translateX(-100%);
  animation: boot-sheen 1.6s ease-in-out infinite;
}
@keyframes boot-sheen { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .boot i::after, .boot-card::after { animation: none; } }
@media (max-width: 1000px) { .boot { grid-template-columns: 62px minmax(0, 1fr); } .boot-rail i { width: 32px; height: 32px; } }
@media (max-width: 640px) { .boot { grid-template-columns: minmax(0, 1fr); } .boot-rail { display: none; } }

/* ─── Waiting for a report ───────────────────────────────────────────
   A month of Web & app usage is a real wait -- measured on the live box at
   25.1 seconds against 0.18 for a single day -- and the pane used to sit
   empty for all of it, which reads as a screen that has failed rather than
   one that is working.

   Two states, because they answer different questions. Opening a report for
   the first time there is nothing to keep, so the shape of the screen is
   drawn and filled in: the person learns the layout while they wait. Changing
   the date on a report already on screen, the old figures stay readable and
   dim, with a bar above them, because replacing a page of real numbers with
   grey blocks loses information the person was still reading.

   Same sheen as the boot skeleton rather than a second idea about what
   loading looks like. ────────────────────────────────────────────────── */
.skel-pane { display: flex; flex-direction: column; gap: 16px; padding: 4px 0 0; }
.skel-pane i { display: block; border-radius: 8px; background: var(--surface-2); }
.skel-head { width: 210px; height: 22px; }
.skel-sub { width: 320px; height: 12px; margin-top: -8px; }
.skel-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
.skel-row i { height: 76px; }
.skel-card {
  display: block; height: 300px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--border);
}
.skel-pane i, .skel-card { position: relative; overflow: hidden; }
.skel-pane i::after, .skel-card::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 30%,
              color-mix(in srgb, var(--text) 5%, transparent) 50%, transparent 70%);
  transform: translateX(-100%);
  animation: boot-sheen 1.6s ease-in-out infinite;
}
@media (max-width: 900px) { .skel-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Refreshing something already drawn. The figures stay; they are just no
   longer current, and they say so. */
.pane-wait { position: relative; }
.pane-wait > * { opacity: .45; transition: opacity .12s ease; }
.pane-wait::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  border-radius: 3px; z-index: 4;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  background-size: 40% 100%; background-repeat: no-repeat;
  animation: pane-wait-slide 1.1s ease-in-out infinite;
}
@keyframes pane-wait-slide {
  0%   { background-position: -45% 0; }
  100% { background-position: 145% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skel-pane i::after, .skel-card::after { animation: none; }
  .pane-wait::before { animation: none; background: var(--accent); background-size: 100% 100%; }
}

/* ─── Downloads has something new ────────────────────────────────────
   An export was just added; the rail's Downloads item wears a dot until
   the list is opened, so a file asked for is never quietly waiting. */
.rail button.railjob { position: relative; }
.rail button.railjob.has-new::after {
  content: ""; position: absolute; top: 8px; right: 14px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent-fill, var(--accent)); border: 2px solid var(--surface);
}
@media (max-width: 1000px) { .rail button.railjob.has-new::after { right: 8px; } }

/* ─── The range picker, fitted ──────────────────────────────────────
   Its day cells were sized by the other picker's ".cal-grid button" rule
   (aspect-ratio 1 on a 1fr column) and grew to 52px: two months side by
   side no longer fit the box, the box scrolled sideways, and Apply sat
   below the fold. Scoped to this picker so the two stop sharing. */
.range-modal .cal-grid { gap: 2px; }
.range-modal .cal-day,
.range-modal .cal-grid button {
  aspect-ratio: auto; height: 32px; min-width: 0; padding: 0;
  border-radius: 7px; font-size: 12.5px;
}
.range-modal .range-right { overflow-x: hidden; display: flex; flex-direction: column; padding-bottom: 0; }
.range-modal .range-pane { display: flex; flex-direction: column; min-height: 0; }
/* The day view is one month on its own: kept to a compact calendar rather
   than seven 84px squares across the whole pane. */
.range-modal .range-pane > .cal { max-width: 336px; }
/* Apply stays on screen however far the months scroll. */
.range-modal .range-foot {
  position: sticky; bottom: 0; z-index: 1;
  margin-top: auto; padding-bottom: 16px;
  background: var(--surface);
}

/* ─── Reports › Project tracking ────────────────────────────────────── */
.pt-crumb { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; font-size: 14px; }
.pt-crumb .linkbtn.pt-back { font-size: 14px; padding: 0; }
.pt-caption { margin: 18px 0 8px; color: var(--muted); font-size: 14px; }
.pt-table td { vertical-align: middle; padding-top: 12px; padding-bottom: 12px; }
.pt-name { min-width: 180px; }
.pt-name > b { font-size: 14px; }
.pt-folder { display: inline-grid; place-items: center; margin-right: 10px; color: var(--accent); vertical-align: middle; }
.pt-title { display: flex; align-items: center; gap: 8px; }
.pt-title b { font-size: 14px; }
.pt-assignee { display: flex; align-items: center; gap: 6px; margin-top: 4px; font-size: 12.5px; color: var(--muted); }
.pt-assignee .avatar { width: 18px; height: 18px; font-size: 8px; }
.pt-people { display: flex; align-items: center; }
.pt-people .avatar { width: 30px; height: 30px; font-size: 11px; border: 2px solid var(--surface); margin-left: -8px; }
.pt-people .avatar:first-child { margin-left: 0; }
.pt-more { margin-left: 6px; font-size: 12px; color: var(--muted); }
.pt-open { font-size: 15px; }
.pt-meter { min-width: 150px; }
.pt-meter b { display: block; text-align: right; font-size: 14px; font-variant-numeric: tabular-nums; }
.pt-meter b.good { color: var(--good); }
.pt-meter b.mid { color: var(--warn); }
.pt-meter b.low { color: var(--bad); }
.pt-meter .pt-bar { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.pt-meter .pt-bar .meter { flex: 1; }
.pt-meter .meter i.share { background: var(--accent-fill, var(--accent)); }
/* The provenance line above the toolbar. */
.pt-generated { margin: 0 0 8px; text-align: right; font-size: 12.5px; color: var(--muted); }

/* On the toolbar row, which is where it goes on every report that has one.
   margin-right:auto holds the exports, the refresh and the envelope exactly
   where they were -- the row is justified to the end, so the auto margin
   takes the slack and nothing else shifts.

   It is NOT clipped when it will not fit. The first try here ended in an
   ellipsis, and that was measured cutting Attendance and Project tracking
   short at 1024px: the row is still spent and the text cannot be read, which
   is strictly worse than the line it replaced. The group wraps instead (see
   .rep-bar-right), so a stamp with no room takes a line of its own and stays
   whole -- back to where it started, at the widths where there was never
   room to save anything. */
.rep-bar-right .pt-generated {
  margin: 0 auto 0 0; text-align: left; line-height: 1.3;
  white-space: nowrap;
}
/* The cards: two by two, a glyph at the right, the qualifier beside a count. */
.pt-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
@media (max-width: 640px) { .pt-cards { grid-template-columns: minmax(0, 1fr); } }
.pt-card { padding: 20px 22px; }
.pt-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.pt-card-ic {
  display: inline-grid; place-items: center; width: 34px; height: 34px; flex: none;
  border-radius: 10px; background: var(--accent-soft, var(--surface-2)); color: var(--accent);
}
.pt-card .stat-big small { font-size: 15px; font-weight: 500; color: var(--muted); letter-spacing: 0; }
.pt-card.good .stat-big { color: var(--good); }
.pt-card.mid .stat-big { color: var(--warn); }
.pt-card.low .stat-big { color: var(--bad); }
/* The ring is the panel's subject: drawn large, no heading, the legend
   spread beside it in columns. */
.pt-donut { padding: 26px 28px; }
.pt-donut .donut-row { grid-template-columns: auto minmax(0, 1fr); gap: 40px; }
/* Same width as the rule that stacks .donut-row, and after it, because this
   one is two classes and would otherwise keep the ring and the legend side
   by side until the legend ran off the screen. */
@media (max-width: 720px) {
  .pt-donut .donut-row { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 18px; }
  .pt-donut .pt-legend { grid-template-columns: minmax(0, 1fr); }
}
.pt-donut .donut { width: 220px; height: 220px; }
.pt-donut .donut-hole { width: 140px; height: 140px; }
.pt-donut .donut-hole b { font-size: 24px; }
.pt-donut .donut-hole span { font-size: 11px; }
.pt-legend { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px 24px; align-content: center; }
.pt-legend .legend-row { font-size: 14px; }
.pt-legend .legend-row b { font-size: 14px; }
/* A ring with a question mark on a column heading; the tip is its title. */
.pt-help {
  display: inline-grid; place-items: center; width: 14px; height: 14px; margin-left: 6px;
  padding: 0; background: none; min-height: 0;
  border: 1px solid var(--muted); border-radius: 50%; font-size: 9px; line-height: 1;
  color: var(--muted); cursor: help; vertical-align: 1px; text-transform: none; letter-spacing: 0;
}
.pt-help:hover { color: var(--accent); border-color: var(--accent); }
.pt-help::before { content: "?"; }
/* Who worked on a task, opened under its row. */
.pt-sub > td { padding: 0 0 8px 48px; background: var(--surface-2); }
.pt-subtable { width: 100%; }
.pt-subtable td { padding: 8px 12px; border: 0; font-size: 13px; }
.pt-who { display: flex; align-items: center; gap: 8px; }
.pt-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pt-num.good { color: var(--good); }
.pt-num.mid { color: var(--warn); }
.pt-num.low { color: var(--bad); }
button.with-ic { display: inline-flex; align-items: center; gap: 8px; }
.pt-go { width: 40px; text-align: right; }
.pt-legend .legend-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; gap: 10px; align-items: center; }
.pt-legend-pct { font-variant-numeric: tabular-nums; }
.pill.pj-s-todo { background: var(--surface-2); color: var(--muted); }
.pill.pj-s-in_progress { background: var(--accent-soft); color: var(--accent); }
.pill.pj-s-done { background: color-mix(in srgb, var(--good) 14%, transparent); color: var(--good); }

/* ── The project stage bar ──────────────────────────────────────────────
 *
 * A row of chevrons above a project's tasks saying where it has got to. The
 * stages are the organisation's own, so this styles a shape rather than any
 * particular set of words, and it has to survive a list of two and a list of
 * twelve. Long lists scroll sideways instead of wrapping: a wrapped chevron
 * row reads as two sequences rather than one.
 *
 * "stagestep" and not "stage": .pill.pj-stage is already taken further up
 * this file, and a generic name added here collides with something
 * eventually.
 */
.pj-stagecard {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  margin-bottom: 14px;
}
.pj-stagebar {
  display: flex;
  align-items: stretch;
  gap: 4px;
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  padding: 2px 0;
  scrollbar-width: thin;
}
/* Three states, not two.
 *
 * It drew one: the chosen stage, and everything else identical. A row of
 * arrows that says only "this one" wastes the shape it is drawn in -- the
 * point of a chevron track is that you can see how far along something is
 * without reading a word of it. So the stages behind the current one are
 * filled in, the current one is solid, and the ones still to come are empty.
 *
 * Every colour comes from the theme's own tokens, so this follows dark mode
 * without a second set of rules. Earlier it named --surface-2 with a hex
 * fallback as though the token might not exist; it does, and the fallback was
 * hiding that the real problem was elsewhere. */
.pj-stagestep {
  position: relative;
  flex: 0 0 auto;
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  /* Explicit, because this no longer inherits the generic button rule that
     used to set it -- and a line box shorter than the text slices the
     descenders off "Planning" and "In progress", which is exactly how this
     was first reported. */
  line-height: 1.35;
  color: var(--muted);
  background: var(--surface-2);
  padding: 10px 22px 10px 26px;
  white-space: nowrap;
  transition: background .12s ease, color .12s ease;
  /* The chevron. A clip-path rather than borders, so the notch follows the
     box at any height and the label stays centred in it. */
  clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 50%, calc(100% - 12px) 100%, 0 100%, 12px 50%);
}
/* Already passed: filled in, and readable as "done" rather than "chosen". */
.pj-stagestep-done {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}
.pj-stagestep:first-child {
  padding-left: 18px;
  clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 50%, calc(100% - 12px) 100%, 0 100%);
  border-radius: 6px 0 0 6px;
}
.pj-stagestep:last-child {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 12px 50%);
  border-radius: 0 6px 6px 0;
}
/* Hover deliberately changes nothing.
 *
 * On this row a colour means "the project is here". A hover tint is a colour
 * arriving under the pointer for a reason that has nothing to do with the
 * project, and on a row whose whole job is to report a saved state that reads
 * as the state changing. The cursor already says it can be pressed. */
/* Where it is now. --accent-fill rather than --accent: in dark mode the accent
   is lightened so it works as text, and white on that reads 1.9:1 -- the same
   trap the buttons were pulled out of. --accent-fill is the one that stays a
   ground white can sit on. */
.pj-stagestep-on,
.pj-stagestep-on:hover { background: var(--accent-fill, var(--accent)); color: #fff; font-weight: 600; }
/* What Update would set, when that is not already where the project is.
 *
 * A dot before the label, drawn INSIDE the chevron. Inside on purpose: the
 * shape is a clip-path, and anything outside it -- a border, an outline, a rim
 * built from drop-shadows -- is either cut away or mangled. The first attempt
 * ringed the chip with four offset drop-shadows and the notch filled in while
 * the point blobbed out; it looked broken, because it was.
 *
 * The fill is untouched. A fill on this row means "the project is here", and
 * a stage nobody has saved has no business saying that.
 *
 * Every step carries the dot's space whether or not it is showing one, so a
 * click cannot widen one chevron and shove the rest of the row sideways. The
 * bar scrolls when it must; a row that jumps under the pointer is worse than
 * a row that is a little wider. */
.pj-stagestep::before {
  content: "";
  display: inline-block;
  width: 7px; height: 7px; border-radius: 50%;
  margin-right: 8px;
  vertical-align: 1px;
  background: transparent;
}
.pj-stagestep-pick::before { background: var(--accent); }
/* On the saved stage the ground is already the accent, so the dot has to be
   the thing sitting on it rather than the thing it is made of. Only reachable
   when the saved stage is also the pick, which is the moment after a save. */
.pj-stagestep-on.pj-stagestep-pick::before { background: #fff; }

/* Somebody who may not move the project still gets to read it. Only the
   pointer says it is not theirs to press -- dimming the row would make the
   progress harder to see for the people most likely to be just looking. */
.pj-stagestep:disabled { cursor: default; }
.pj-stagestep:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
.pj-stagecard .btn { flex: 0 0 auto; }

@media (max-width: 720px) {
  .pj-stagecard { flex-wrap: wrap; }
  .pj-stagecard .btn { width: 100%; }
}


/* -- Work details: the panel behind a timeline segment --------------------
 *
 * Names are wd- prefixed. styles.css is nearly seven thousand lines with two
 * different rules already called .modal, and a generic name added here
 * collides with something eventually -- four times so far.
 *
 * The box itself reuses .modal inside .modal-back, so it inherits the
 * centring the dialogs were fixed to have rather than restating it.
 */
/* Left, because .modal centres its text and the escape from that is
   `.modal:has(.field)` -- a modal holding a form input. This one holds no
   input, so it was centred: the figures, their labels, the applications and
   the section headings all down the middle of a 760px panel. It reads as a
   question being asked rather than a day being reported, and it is the same
   trap a dialog here fell into once before. */
/* padding: 0 because .modal-card carries 26px for the dialogs that have
   no head/body of their own, and this one has both -- so it was paying
   twice and its header divider stopped 26px short of the edge. The two
   sibling dialogs that got this right, .range-modal and .pj-modal, say
   the same thing for the same reason. */
.wd-modal {
  width: min(760px, 94vw); max-height: 86vh;
  display: flex; flex-direction: column; text-align: left;
  padding: 0; overflow: hidden;
}
.wd-head { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--border); }
.wd-head b { font-size: 15px; }
.wd-when { color: var(--muted); font-size: 13px; margin-left: 2px; flex: 1 1 auto; }
.wd-body { padding: 14px 16px 18px; overflow-y: auto; }
.wd-who { font-size: 14px; margin-bottom: 10px; }
/* Two figures, not three: the time the stretch was, and how active it was.
   The second is a bar with a label at each end, so it gets twice the room --
   which is also how the card it is copied from is laid out. */
.wd-figures { display: grid; grid-template-columns: minmax(130px, 1fr) 2fr; gap: 10px; margin-bottom: 16px; }
.wd-fig { border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; }
.wd-fig b { font-size: 18px; font-variant-numeric: tabular-nums; }
.wd-figlabel { font-size: 12px; color: var(--muted); }
.wd-fignote { font-size: 11px; color: var(--muted); }
/* Activity levels: one bar across the stretch, named at both ends. The figure
   box it sits in is wider than a number needs, so it takes two tracks. */
.wd-levels { gap: 6px; justify-content: center; }
.wd-levelbar {
  height: 8px; border-radius: 4px; overflow: hidden;
  background: var(--surface-2, #eef0f4);
}
.wd-levelbar i { display: block; height: 100%; }
.wd-levelbar .good { background: var(--good, #2e9e6b); }
.wd-levelbar .mid { background: var(--mid, #b9c0cc); }
.wd-levelbar .low { background: var(--low, #d1534a); }
.wd-levelends { display: flex; justify-content: space-between; font-size: 12px; }
.wd-levelends b { font-size: 12px; font-variant-numeric: tabular-nums; }
.wd-levelends span { color: var(--muted); }

/* The activity chart. A fixed height rather than an aspect ratio: it is read
   against the 0/50/100 scale beside it, and a plot that changes height with
   the panel width changes what a bar looks like without changing what it is. */
.wd-chart { display: flex; gap: 8px; height: 132px; }
.wd-chart-scale {
  display: flex; flex-direction: column; justify-content: space-between;
  font-size: 11px; color: var(--muted); text-align: right;
  font-variant-numeric: tabular-nums; flex: none; min-width: 30px;
}
.wd-chart-plot {
  flex: 1 1 auto; display: flex; align-items: flex-end; gap: 1px;
  border-bottom: 1px solid var(--border);
  /* The halfway line, so a bar can be read against 50% without counting. */
  background: linear-gradient(to top, transparent 50%, var(--border) 50%,
                              var(--border) calc(50% + 1px), transparent calc(50% + 1px));
}
.wd-chart-plot i { flex: 1 1 0; min-width: 1px; border-radius: 1px 1px 0 0; }
/* Many bars: the gaps would take more room than the bars. */
.wd-chart-plot.dense { gap: 0; }
.wd-chart-plot.dense i { border-radius: 0; }
.wd-chart-plot .good { background: var(--good, #2e9e6b); }
.wd-chart-plot .mid { background: var(--mid, #b9c0cc); }
.wd-chart-plot .low { background: var(--low, #d1534a); }
/* On the clock, with no capture in this slot. A full-height wash rather than a
   bar: there is no height to draw, and the slot is not empty either -- the
   person was working, nothing sampled them. */
.wd-chart-plot .unsampled {
  height: 100%; background: var(--surface-2, #eef0f4); opacity: .75;
}
/* Outside the stretch. Hatched, and deliberately unlike the above: these two
   were one state once, and nine bars of somebody's working hour said she was
   not there. */
.wd-chart-plot .gap { background: none; height: 100%; opacity: .35;
  background-image: repeating-linear-gradient(45deg, var(--border) 0 2px, transparent 2px 5px); }
.wd-chart-axis {
  display: flex; justify-content: space-between; margin-top: 4px;
  font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;
}

/* The three shares above the application list, as the other tracker has them. */
.wd-shares { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.wd-share {
  font-size: 12px; font-weight: 600; padding: 5px 10px; border-radius: 999px;
  border: 1px solid var(--border); font-variant-numeric: tabular-nums;
}
.wd-share.good { color: var(--good, #2e9e6b); border-color: var(--good, #2e9e6b); }
.wd-share.mid { color: var(--muted); }
.wd-share.low { color: var(--low, #d1534a); border-color: var(--low, #d1534a); }

.wd-section { margin-top: 16px; }
.wd-sechead { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-bottom: 8px; }
.wd-split { display: flex; height: 8px; border-radius: 4px; overflow: hidden; margin-bottom: 10px; background: var(--surface-2, #eef0f4); }
.wd-split i { display: block; }
.wd-split .good { background: var(--good, #2e9e6b); }
.wd-split .mid { background: var(--mid, #b9c0cc); }
.wd-split .low { background: var(--low, #d1534a); }
.wd-apps { display: flex; flex-direction: column; }
/* minmax(0, 1fr), not 1fr. A grid track's automatic minimum is its content's
   min-content width, and the name beside it is white-space: nowrap -- so the
   minimum was the whole string and the track could never shrink. The ellipsis
   on .wd-appname was therefore decoration: a long title pushed the figure
   beside it out of the row instead of being cut. These rows carry window
   titles and page headings, which run to any length at all; the one that
   showed it was a Gmail subject, "Re: Welcome Email-Atmos JR Eastfields Villa
   No 25 - somebody@gmail.com - Gmail". The same pattern is already used at
   .settings-shell and in seven places with min-width: 0. */
.wd-app { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 10px; align-items: center; padding: 7px 0; border-bottom: 1px solid var(--border); }
.wd-app:last-child { border-bottom: 0; }
/* min-width: 0 as well as the track, because these rows are a grid in some
   panels and a flex row in others, and a flex item has the same automatic
   minimum for the same reason. */
.wd-appname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.wd-app b { font-variant-numeric: tabular-nums; }
/* Sideways, not wrapped -- four at a time, the way the hour strips on the
 * screenshots screen already read.
 *
 * This was a wrapping grid, and the panel is 760px wide with its own vertical
 * scroller, so every row of a long stretch sat inside observeShot's 600px
 * vertical lead: pressing a four-hour block fetched all twenty-four captures
 * before anybody had looked at the second one. A strip clips what is off to
 * the right, so four are fetched and the rest arrive two ahead of the scroll
 * (SHOT_LEAD in app.js).
 *
 * The basis is computed rather than a fixed width so exactly four fit whatever
 * the panel is: four tiles and the three gaps between them. */
.wd-shots {
  display: flex; gap: 10px;
  overflow-x: auto; overflow-y: hidden;
  padding-bottom: 10px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.wd-shots > * { flex: 0 0 calc((100% - 30px) / 4); scroll-snap-align: start; }
.wd-shot { position: relative; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; padding: 0; background: none; cursor: pointer; }
/* The same framing the other thumbnails use. Left at height:auto the tiles in
   a flex row took their own heights, so a strip that crossed two monitors of
   different shapes stepped up and down along its bottom edge. */
.wd-shot img { display: block; width: 100%; height: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.wd-stamp { position: absolute; left: 6px; bottom: 6px; font-size: 11px; padding: 2px 6px; border-radius: 4px; background: rgba(0,0,0,.6); color: #fff; }

/* ── Updates ─────────────────────────────────────────────────────────────
   Two figures side by side, then the action, then the lists. Everything
   stacks at phone width: this panel is read on a laptop and on the desktop
   app's own narrow window, and the second is where a two-column grid with a
   long version string in it starts overflowing. */
.up-now { grid-template-columns: 1fr 1fr; }
.up-behind { color: var(--bad, #d1534a); font-weight: 600; }
.up-act {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: -6px 0 16px;
}
.up-act .hint { margin: 0; flex: 1 1 240px; }
.up-get { flex: none; text-decoration: none; }
.up-yours { font-weight: 700; }
.up-when { font-weight: 500; color: var(--muted); font-variant-numeric: tabular-nums; }
/* The history is NOT given its own scroller. The panel body already scrolls,
   and a box that scrolls inside a box that scrolls is one of the things a
   dialog here got wrong before -- you reach the bottom of the inner list and
   the page underneath has not moved, so the rest of the panel reads as
   missing. Twenty releases is a long list and a long list is fine. */
.up-history { }

@media (max-width: 560px) {
  .up-now { grid-template-columns: 1fr; }
  .up-act { align-items: stretch; }
  .up-get { text-align: center; }
}

.wd-figures.ad-figures { grid-template-columns: 1fr 1fr; }

/* A name in the breakdown is pressable: it scopes the panel to that person.
   Styled as the row it already was, not as a link -- seven blue underlined
   rows read as navigation away from here, which is the opposite of what
   pressing one does. */
.ad-pick {
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; text-align: left; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.ad-pick:hover { text-decoration: underline; }
.ad-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
/* The open one stays obvious while its detail is underneath it. */
.ad-person.picked { background: var(--accent-soft); border-radius: 6px 6px 0 0; }
.ad-person.picked .ad-pick { font-weight: 700; color: var(--accent); }

/* A row that opens has to look like one. Rotated rather than swapped for a
   second glyph, so the two states cannot drift apart. */
.ad-pick { display: flex; align-items: center; gap: 7px; }
.ad-caret {
  flex: none; width: 0; height: 0; border-style: solid;
  border-width: 4.5px 0 4.5px 6px;
  border-color: transparent transparent transparent currentColor;
  transition: transform .14s ease; transform-origin: 25% 50%;
}
.ad-person.picked .ad-caret { transform: rotate(90deg); }

/* The name row carries the same inset as the detail that opens under it.
   .wd-app is padding: 7px 0 -- no sides at all -- which is right for a row on
   the panel's own ground and wrong for one sitting on a tinted block: the
   caret and the figure ended up against the edge of the tint while every row
   beneath them was 12px in, so the open row and its contents did not line up.
   Written as .wd-app.ad-person rather than .ad-person so it cannot be undone
   by anything later that touches .wd-app, and scoped to the breakdown's own
   rows so the dozen other panels using .wd-app are untouched. */
.wd-app.ad-person { padding-left: 12px; padding-right: 12px; }
/* Closed rows keep the inset so the list does not jump sideways when one
   opens, but only the open one is tinted. */

/* What one person was doing, directly beneath their name. */
.ad-detail {
  background: var(--accent-soft); border-radius: 0 0 6px 6px;
  padding: 2px 12px 12px; margin-bottom: 8px;
}

.ad-subhead {
  font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); margin: 12px 0 4px;
}
/* Scrolls rather than grows: eight titles and their stretches would push the
   rest of the names off the panel, and the list of names is what somebody
   opened this to read. */
.ad-scroll {
  max-height: 164px; overflow-y: auto;
  /* Room for the scrollbar. Without it the figure on the right of every row
     sat against the bar itself, which is what "add some margin beside 25m"
     was about -- the number looked clipped even though it was whole. */
  padding-right: 8px;
}
.ad-scroll::-webkit-scrollbar { width: 9px; }
.ad-scroll::-webkit-scrollbar-track { background: transparent; }
.ad-scroll::-webkit-scrollbar-thumb {
  background: var(--border); border-radius: 999px; border: 2px solid transparent;
  background-clip: content-box;
}
.ad-scroll { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
/* The rows inside sit on the tinted ground, so their dividers would stack
   with the panel's own. */
.ad-detail .wd-app { border-bottom-color: color-mix(in srgb, var(--border) 60%, transparent); }
.ad-detail .wd-shots { padding-bottom: 6px; }

/* ─── Who used one application: the screen ───────────────────────── */
/* It was a dialog. By the time it held a people list, each person's window
 * titles, every stretch of every title and the captures taken during them, it
 * had three scrollbars inside one another. A page has one: the page.
 *
 * Everything below is placed after .row-form, .seg and .ad-scroll on purpose,
 * not for tidiness -- several of these rules narrow one of those and carry the
 * same specificity, so source order is the whole of what makes them win. */
.ad-page-head { margin-bottom: 14px; }
.ad-back { padding: 0; font-size: 13px; }
.ad-page-title { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-top: 6px; }
.ad-page-title h2 { margin: 0; }
.ad-page-body { display: block; }

/* The search, and the control that opens everything it found. */
.ad-find-row { align-items: flex-end; }
.ad-find-row .field input[type="search"] { min-width: 260px; }
.ad-find-row > .hint { margin-left: auto; padding-bottom: 9px; }
.ad-allbtn { padding-bottom: 9px; }

/* ─── A window title, and the stretches inside it ─────────────────── */
/* Collapsed to begin with. A day can hold forty titles, and forty titles with
 * their stretches and their thumbnails all open at once is the wall this
 * replaced -- "dont show all together; when click particular title it will
 * show all breakups inside". */
.ad-titles { display: flex; flex-direction: column; }
.ad-title {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) 84px auto;
  gap: 10px; align-items: center;
  width: 100%; text-align: left;
  background: none; border: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  padding: 10px 2px; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--text);
  border-radius: 6px;
}

/* Share of the longest row, in place of the stretch count that used to sit
   here. A column of durations in mixed magnitudes -- 32m, 8m, 1h 4m -- does
   not sort itself out by eye; the bar does it without being read.

   Its own track, fixed width, so it cannot squeeze the title or the
   duration. Hidden when narrow (see the media query below) rather than
   shrunk to a stub that says nothing. */
.ad-share {
  height: 5px; border-radius: 3px;
  background: color-mix(in srgb, var(--border) 55%, transparent);
  overflow: hidden;
}
.ad-share > i {
  display: block; height: 100%; border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 60%, transparent);
}
.ad-title.open > .ad-share > i { background: var(--accent); }
.ad-title:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.ad-title:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ad-title.open { border-bottom-color: transparent; }
/* Wraps rather than clipping. An ellipsis on
   "...- Timake - Visual Studio Code" hides the end, and the end is the part
   that says which file. */
.ad-title-name { min-width: 0; overflow-wrap: anywhere; font-weight: 600; }
.ad-title-count { font-size: 11.5px; color: var(--muted); white-space: nowrap; }
/* A duration never wraps. "6h 7m" over two lines is not a duration.
 *
 * The owner photographed it at 375px: "6h" on one line and "7m" on the
 * next, under the window title. Measured there, the <b> was 14 pixels
 * wide in a grid track that had computed to 0px. The narrow rule below
 * gives .ad-title three tracks and spans .ad-title-count across 2/-1 onto
 * its own row, which leaves the duration in an `auto` track competing
 * with a minmax(0, 1fr) beside it -- and an auto track can be squeezed to
 * its MIN-content, which for wrappable text is the width of "6h".
 *
 * nowrap puts a floor under that track: min-content becomes the whole
 * string, so the track cannot collapse below it. */
.ad-title b { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The stretches, indented under the title they belong to. */
.ad-stretches {
  padding: 2px 0 10px 24px;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
/* Two tracks, not three. The third held the captures, which came off this
   panel on 28 October 2026 -- left in place it was an empty column pushing
   the interval and the length apart across the width of the screen. */
.ad-stretch {
  display: grid;
  grid-template-columns: 150px auto;
  justify-content: start;
  gap: 6px 18px; align-items: baseline;
  padding: 6px 0;
  font-size: 12.5px;
}
.ad-st-when { color: var(--muted); font-variant-numeric: tabular-nums; }
/* "across 12 visits", under the interval it qualifies. Its own line, because
   "2:00 PM - 3:40 PM across 12 visits" runs together into one phrase. */
.ad-st-when i {
  display: block; font-style: normal; font-size: 11px;
  color: var(--accent); margin-top: 2px;
}
.ad-st-for { font-weight: 700; font-variant-numeric: tabular-nums; }
.ad-st-shots { min-width: 0; display: flex; align-items: center; gap: 10px; }
/* Four tiles, then a count -- never a sideways scrollbar. This sits in a list
   that already scrolls down the page, and a box scrolling sideways inside a
   box scrolling downwards is what the dialog version collected complaints
   for. The shared strip sizes its tiles to a quarter of its container, which
   here would be whatever is left of the row. */
.ad-st-shots .wd-shots { padding-bottom: 0; gap: 6px; flex: 0 0 auto; overflow: visible; }
.ad-st-shots .wd-shots > * { flex: 0 0 96px; }
.ad-st-shots .wd-stamp { left: 4px; bottom: 4px; font-size: 10px; padding: 1px 4px; }
.ad-st-none { font-style: normal; color: var(--muted); font-size: 11.5px; }
.ad-more-shots { white-space: nowrap; font-size: 12px; }

/* The time the rows do not account for. Quiet, but present: without it the
   stretches add up to less than the figure beside the title. */
.ad-rest {
  margin-top: 10px; font-size: 11.5px; color: var(--muted); font-style: italic;
}

/* Narrow: the share bar goes rather than shrinking to a stub that says
   nothing. The interval and the length still share a line. */
@media (max-width: 820px) {
  .ad-stretch { grid-template-columns: auto auto; gap: 2px 12px; }
  .ad-st-shots { grid-column: 1 / -1; margin-top: 6px; }
  .ad-title { grid-template-columns: 14px minmax(0, 1fr) auto; }
  .ad-share { display: none; }
  /* The count drops to its own line; the LENGTH stays on the title's.
   *
   * It used to span 2 / -1, which left the duration auto-placing into the
   * third track of the first row -- a track with nothing to size it, next
   * to a minmax(0, 1fr) that took the room. The result was the thing the
   * owner photographed: "6h" over "7m" in a 14px column. Naming both
   * placements puts the duration where it is on a wide screen, at the
   * right of the title, and leaves the count underneath. */
  /* The count is placed in BOTH axes, which is what frees row one.
   *
   * Auto-placement is sparse and its cursor never moves backwards. With
   * only a column named, the order of events is: caret to row1/col1; name
   * to row1/col2, cursor now at row1/col3; the count has a definite
   * column-start of 2, which is behind the cursor, so the cursor drops to
   * row 2 and the count takes row2/col2; the duration then finds col2 and
   * col3 of row 2 taken, col4 past the end of a three-track grid, and
   * opens a THIRD row at column 1 -- the 14px caret track. "6h 7m" in
   * 14 pixels breaks at its space, which is exactly what the owner
   * photographed.
   *
   * A definite grid-row puts the count in the first step of the
   * algorithm, before the cursor exists, so caret, name and duration then
   * fill row one in document order and the length sits at the right of
   * the title as it does on a wide screen.
   *
   * Measured rather than reasoned: with a column alone the duration came
   * out at top 59 against the title's 9 -- the second row, beside the
   * count. With the row named it is on the title's line. */
  .ad-title-count { grid-row: 2; grid-column: 2 / -1; }
  .ad-find-row > .hint { margin-left: 0; padding-bottom: 0; flex-basis: 100%; }
}

/* A site's own address, in the breakdown's header. */
.ad-site {
  font-size: 12.5px; font-weight: 600; color: var(--accent);
  text-decoration: none; padding: 4px 9px; border-radius: 999px;
  border: 1px solid var(--border); white-space: nowrap;
}
.ad-site:hover { border-color: var(--accent); }
.ad-site:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* An application name in Web & app usage opens who used it. Styled as the
   bold text it already was rather than as a link: the column is a list of
   names, and twenty blue underlined rows read as navigation. */
.appname-open {
  background: none; border: 0; padding: 0; font: inherit; font-weight: 700;
  color: inherit; cursor: pointer; text-align: left; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.appname-open:hover { text-decoration: underline; }
.appname-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* A segment you can press says so, and keeps a visible focus ring. */
.daybar i.pickable { cursor: pointer; }
.daybar i.pickable:hover { filter: brightness(1.08); }
.daybar i.pickable:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

@media (max-width: 640px) {
  .wd-figures { grid-template-columns: 1fr; }
  /* Four across on a phone is four unreadable pictures. Two, and the gap
     between them. */
  .wd-shots > * { flex: 0 0 calc((100% - 10px) / 2); }
}

/* ─── The week matrix ────────────────────────────────────────────────────
   A grid of seven days against people and projects. Built on table.grid so
   it inherits the borders, the header weight and the horizontal scroller
   every other report table uses; everything below is only what a matrix
   needs on top of that.

   The mx- prefix is deliberate. This stylesheet is one file of five thousand
   lines with a flat namespace, and generic names have collided in it four
   times -- twice on two different rules both called .modal. ───────────── */

.mxgrid { table-layout: auto; }

/* The seven day columns share what is left after the labels, so a week of
   equal days draws as equal columns rather than sizing to whichever cell
   happens to hold the longest number. */
.mxgrid th, .mxgrid td.mx-cell { width: 11%; text-align: center; }
.mxgrid td:first-child, .mxgrid th.mx-corner { width: 23%; text-align: left; }

.mx-corner { text-align: left; }

/* Weekday over date, so the column reads as a day and not as a number. */
.mx-dow {
  display: block; font-size: 11px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; opacity: .72;
}
.mxgrid th b { font-size: 15px; font-weight: 700; }

.mx-cell {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* Four states, matching the bands the rest of the product already uses so a
   green cell here means what a green bar means on Home. The scale is the
   week's own busiest cell, set in matrixBand -- there is no configured
   working day anywhere in this product to measure against. */
.mx-cell.good { background: color-mix(in srgb, var(--good) 22%, transparent); color: var(--good); }
.mx-cell.mid  { background: color-mix(in srgb, var(--warn) 20%, transparent); color: var(--warn); }
.mx-cell.low  { background: color-mix(in srgb, var(--bad) 16%, transparent);  color: var(--bad); }
/* A day nobody tracked. Quiet rather than alarming: it is the most common
   cell in the grid -- weekends alone are two sevenths of it -- and painting
   those red would make the whole screen look like a problem. */
.mx-cell.none { background: var(--surface-2); color: var(--muted); font-weight: 500; }

/* The colour key under the grid (BUG-103).
 *
 * The bands are each cell against the week's busiest cell, which is not
 * something the grid can convey on its own -- QA inferred three absolute
 * thresholds ("full day", "today", "under 1 hour") and none of them exists.
 * The swatches take their fills from the same custom properties as the cells
 * above, so a key cannot drift away from what it is describing; the border
 * makes the pale "nothing tracked" chip visible on its own surface. */
.mx-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  padding: 10px 12px 2px;
  font-size: 12px;
  color: var(--muted);
}
.mx-legend-intro { font-weight: 600; }
.mx-key { display: inline-flex; align-items: center; gap: 6px; }
.mx-swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  display: inline-block;
  flex: none;
}
.mx-swatch.good { background: color-mix(in srgb, var(--good) 22%, transparent); border: 1px solid var(--good); }
.mx-swatch.mid  { background: color-mix(in srgb, var(--warn) 20%, transparent); border: 1px solid var(--warn); }
.mx-swatch.low  { background: color-mix(in srgb, var(--bad) 16%, transparent);  border: 1px solid var(--bad); }
.mx-swatch.none { background: var(--surface-2); border: 1px solid var(--muted); }

.mx-person  { font-weight: 700; }
.mx-project { font-weight: 600; }
/* A project under its person: indented, lighter, and never mistaken for a
   person's own row when the two are interleaved down the page. */
.mx-sub { padding-left: 26px; font-weight: 500; color: var(--muted); }

.mx-tools { display: flex; align-items: center; gap: 10px; }
.mx-week {
  min-width: 170px; text-align: center; font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.mx-views { display: flex; gap: 8px; margin: 0 0 14px; flex-wrap: wrap; }

@media (max-width: 900px) {
  /* The labels stop taking a quarter of a narrow screen, and the week jumps
     under its buttons rather than squeezing them off the edge. */
  .mxgrid td:first-child, .mxgrid th.mx-corner { width: auto; min-width: 130px; }
  .mx-tools { flex-wrap: wrap; justify-content: flex-start; }
  .mx-week { min-width: 0; text-align: left; }
}

/* ─── The loading line ───────────────────────────────────────────────────
   Grey blocks on their own were read as a broken screen rather than a busy
   one -- reported in those words. The blocks stay, because they hold the
   shape of what is coming and nothing jumps when it lands; the spinner and
   the sentence above them are what say it is working.

   It sits at the top of the skeleton rather than centred over it. Centred, it
   moves as the pane grows, and on a tall report it lands below the fold where
   the one thing somebody is looking for cannot be seen.

   Named skel-spin and not spin: this stylesheet has one flat namespace and
   generic names have collided in it five times now. It borrows the existing
   @keyframes spin that .iconbtn.spinning already uses rather than declaring a
   second rotation that would drift from it. ─────────────────────────────── */

.skel-say {
  display: flex; align-items: center; gap: 10px;
  margin: 2px 0 4px; color: var(--muted);
  font-size: 14px; font-weight: 600;
}

.skel-spin {
  width: 16px; height: 16px; flex: 0 0 16px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--accent) 28%, transparent);
  border-top-color: var(--accent);
  animation: spin .7s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  /* Still a mark that something is happening, without the movement. The
     sentence beside it is doing the real work anyway. */
  .skel-spin { animation: none; opacity: .85; }
}

/* ============================================================
   Prototype dashboard — hero band + KPI sparkline cards
   (markup built in admin.js loadDashboard; tokens are the app's own)
   ============================================================ */
.dhero{position:relative;overflow:hidden;display:flex;align-items:center;gap:18px;row-gap:14px;flex-wrap:wrap;
  padding:16px 22px;border-radius:14px;margin-bottom:14px;background:var(--surface);border:1px solid var(--border)}
.dh-glow{position:absolute;width:420px;height:240px;left:-70px;top:-120px;pointer-events:none;
  background:radial-gradient(circle,color-mix(in srgb,var(--good) 26%,transparent),transparent 68%);filter:blur(24px)}
.dh-id{display:flex;align-items:center;gap:15px;position:relative;flex:none}
.dh-ring{width:40px;height:40px;border-radius:50%;border:2px solid var(--good);display:grid;place-items:center;
  box-shadow:0 0 22px -4px var(--good) inset;flex:none}
.dh-dot{width:12px;height:12px;border-radius:50%;background:var(--good);
  box-shadow:0 0 14px 2px color-mix(in srgb,var(--good) 75%,transparent)}
.dh-t{font-size:17px;font-weight:700;letter-spacing:-.02em;font-family:var(--font-display)}
.dh-s{font-size:12px;color:var(--muted);margin-top:3px}
.dh-clock{position:relative;flex:none;padding-left:20px;border-left:1px solid var(--border)}
.dh-c{font-family:var(--font-mono);font-size:32px;font-weight:700;letter-spacing:-.035em;line-height:1}
.dh-cs{font-size:12px;color:var(--muted);margin-top:7px;display:flex;align-items:center;gap:9px}
.dh-pulse{width:8px;height:8px;border-radius:50%;background:var(--good);animation:dhpulse 2s infinite}
@keyframes dhpulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--good) 55%,transparent)}70%{box-shadow:0 0 0 7px transparent}100%{box-shadow:0 0 0 0 transparent}}
.dh-pills{position:relative;display:flex;gap:8px;margin-left:auto;flex-wrap:wrap;flex:1;justify-content:flex-end}
/* --pc is the pill's tone. It was only ever set inline by the hero pills
   of the dashboard that was replaced on 19 October, so with that code gone
   the token had no declaration anywhere and every var(--pc) silently took
   its fallback. Declared here so the rule stands on its own. */
.dpill{--pc:var(--accent);display:flex;align-items:center;gap:9px;padding:8px 12px;border-radius:10px;min-width:104px;flex:1;max-width:190px;
  background:color-mix(in srgb,var(--pc) 11%,transparent);border:1px solid color-mix(in srgb,var(--pc) 34%,transparent)}
.dp-i{width:28px;height:28px;border-radius:8px;display:grid;place-items:center;flex:none;
  background:color-mix(in srgb,var(--pc) 22%,transparent);color:var(--pc)}
.dp-v{font-size:16px;font-weight:700;letter-spacing:-.02em;line-height:1.1;font-variant-numeric:tabular-nums;white-space:nowrap}
.dp-k{font-size:11px;font-weight:600;color:var(--muted);margin-top:2px}
.dp-s{font-size:10.5px;color:var(--idle)}
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:16px}
@media (max-width:1180px){.kpis{grid-template-columns:repeat(2,1fr)}}
@media (max-width:760px){.kpis{grid-template-columns:1fr}}
.kpi{display:flex;align-items:center;gap:10px;padding:15px;border-radius:14px;background:var(--surface);
  border:1px solid var(--border);box-shadow:var(--shadow)}
.kpi-i{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;flex:none}
.kpi-b{min-width:0;flex:1}
.kpi-k{font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;font-weight:700;color:var(--idle);white-space:nowrap}
.kpi-v{font-size:28px;font-weight:700;letter-spacing:-.03em;line-height:1.1;margin-top:5px;font-variant-numeric:tabular-nums;font-family:var(--font-display)}
.kpi-v i{font-style:normal;font-size:16px;color:var(--muted);font-weight:600;margin-left:2px}
.kpi-s{display:flex;align-items:center;gap:8px;margin-top:6px;font-size:11px;color:var(--idle);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.kpi-c{flex:none;margin-left:auto}
@media (max-width:620px){.kpi-c{display:none}}
.mini{display:block}

/* ---- Dashboard lower panels: agent/sync + apps (2x2 with the charts) ---- */
.dash-panels{display:grid;grid-template-columns:1.6fr 1fr;gap:14px;margin-bottom:16px;align-items:stretch}
@media (max-width:980px){.dash-panels{grid-template-columns:1fr}}
.dpanel{display:flex;flex-direction:column}
.dlive{display:flex;flex-direction:column}
.dlive-r{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:11px 2px;border-bottom:1px solid var(--border);font-size:13px}
.dlive-r:last-child{border-bottom:0}
.dlive-r span{color:var(--muted)}
.dlive-r b{font-weight:600;text-align:right}
.dchip{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;font-size:12px;font-weight:600}
.dchip.ok{color:var(--good);background:color-mix(in srgb,var(--good) 15%,transparent)}
.dchip.warn{color:var(--warn);background:color-mix(in srgb,var(--warn) 15%,transparent)}
.dist{display:flex;flex-direction:column;gap:12px;padding-top:4px}
.dist-r{display:flex;align-items:center;gap:11px}
.dist-i{width:30px;height:30px;border-radius:8px;display:grid;place-items:center;flex:none;font-size:13px;font-weight:700}
.dist-b{flex:1;min-width:0}
.dist-n{font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:5px}
.dist-t{display:block;height:6px;border-radius:99px;background:var(--surface-2);overflow:hidden}
.dist-t i{display:block;height:100%;border-radius:99px}
.dist-v{flex:none;text-align:right;font-size:12.5px;font-weight:600;font-variant-numeric:tabular-nums}
.dist-v i{font-style:normal;color:var(--muted);font-size:11px;margin-left:5px}
.dmuted{color:var(--muted);font-size:13px;padding:6px 2px}

/* ---- Dashboard: last-30 calendar + apps-count + sync dot ---- */
#dash-apps-count{margin-left:auto;align-self:center}
.dist-v{display:flex;flex-direction:column;align-items:flex-end;line-height:1.25}
.dist-v i{margin-left:0;margin-top:1px}
.dchip.ok::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--good);box-shadow:0 0 0 0 color-mix(in srgb,var(--good) 60%,transparent);animation:dhpulse 2s infinite}
.mcal{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;margin-top:8px}
.mc-h{font-size:9.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--idle);padding:0 2px 2px}
.mc-c{border:1px solid var(--border);border-radius:8px;padding:5px 6px;min-height:52px;display:flex;flex-direction:column;gap:2px;background:var(--surface-2)}
.mc-c.mc-off{opacity:.45}
.mc-c.mc-now{border-color:var(--good);box-shadow:inset 0 0 0 1px var(--good)}
.mc-d{font-size:10.5px;color:var(--muted);font-weight:600}
.mc-d b{color:var(--text)}
.mc-v{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.mc-v i{font-style:normal;font-size:9px;color:var(--muted);margin-left:1px}
.mc-b{display:block;height:4px;border-radius:99px;background:var(--surface);overflow:hidden;margin-top:auto}
.mc-b i{display:block;height:100%;background:var(--good);border-radius:99px}
.mc-n{color:var(--idle);font-size:12px;margin-top:auto}
.mc-f{display:flex;justify-content:space-between;gap:10px;margin-top:12px;font-size:11px;color:var(--idle)}

/* ---- Top nav: brand lockup + right-side user box (match prototype) ---- */
/* The brand: a mark on its own in the rail, a lockup in the bar.

   The <span class="brand-tx">Tymake</span> beside the mark came in with the
   top bar, which had the width for it. In a 118px column the lockup is wider
   than the rail and the word was clipped at the edge -- reported on
   19 October. Before 9 October the brand here was the image alone, which is
   what the base .rail-brand above still describes, so the word is hidden
   rather than shrunk: there is no size at which a second element beside a
   34px mark fits a column this narrow and still reads. */
.brand-tx{display:none}
.topbar-nav .rail-brand{display:flex;align-items:center;gap:9px;width:auto;height:auto;border-radius:0;margin:0 18px 0 0;padding:0;background:none}
.topbar-nav .rail-brand img{width:30px;height:30px;border-radius:8px}
.topbar-nav .brand-tx{display:flex;flex-direction:column;line-height:1.12}
.topbar-nav .brand-tx b{font-size:15px;font-weight:800;letter-spacing:-.01em;color:var(--text)}
.topbar-nav .brand-tx i{font-style:normal;font-size:10.5px;color:var(--muted);font-weight:600}
.rail button.railuser{gap:9px;padding:5px 8px}
.rail button.railuser.active{box-shadow:none}
.railuser .ub-tx{display:flex;flex-direction:column;line-height:1.18;text-align:left;min-width:0}
.railuser .ub-tx b{font-size:13px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.railuser .ub-tx i{font-style:normal;font-size:11px;color:var(--muted);white-space:nowrap}

/* ---- Top nav: override the colored-nav system to match the prototype ---- */
.rail button[data-view]{color:var(--muted)}
.rail button[data-view]:hover{background:var(--surface-2);color:var(--text)}
.rail button[data-view].active{background:transparent;color:var(--text);font-weight:650;box-shadow:inset 0 -2px 0 var(--accent);border-radius:0}
.rail button[data-view]:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
/* user box on the right: no underline, grey square avatar, no status dot */
.rail button.railuser,.rail button.railuser.active{background:transparent;color:var(--text);box-shadow:none;border-radius:8px;font-weight:500}
.rail button.railuser:hover{background:var(--surface-2)}
.railuser #rail-face{background:var(--surface-2);color:var(--text);border-radius:9px;width:34px;height:34px;font-size:12px;font-weight:700}
.railuser .rail-dot{display:none}
/* The prototype's work bar had no task picker. The owner's does -- it is
   the “+ Select Task” at the far left of the bar in 0.3.52 and 0.3.58,
   and the rule that hid it did not exist in the 8 October stylesheet at
   all. Missed when Layout A was restored on 19 October: seventeen
   top-bar rules were scoped to .topbar-nav and this one was not, so the
   picker stayed hidden. Reported the same day. */
.topbar-nav .appbar-task{display:none}

/* ---- Top nav: spacing, monochrome Downloads, horizontal user box ---- */
/* was .rail{gap:8px;padding:8px 22px} -- horizontal-bar spacing */
.topbar-nav .rail button[data-view]{padding:9px 14px}
.topbar-nav .rail button.railjob{color:var(--muted);padding:9px 14px}
.rail button.railjob:hover{background:var(--surface-2);color:var(--text)}
/* user box: avatar sits LEFT of the name/org, with a divider cap like the prototype */
.topbar-nav .rail button.railuser{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:center;gap:10px;
  padding:4px 2px 4px 16px;margin-left:8px;border-left:1px solid var(--border);border-radius:0}
.rail button.railuser:hover{background:transparent}
.railuser .rail-face{flex:none;margin:0;display:inline-grid;place-items:center}
.railuser .ub-tx{display:flex;flex-direction:column;align-items:flex-start;text-align:left;line-height:1.15;min-width:0}

/* ================================================================
   EXACT prototype top-nav + work bar + report sub-nav.
   Values copied verbatim from timake-ui-redesign/ui/live.html;
   the prototype's token names are aliased here so they resolve,
   and #tabs / #reportTabs win over the app's colored-nav system.
   ================================================================ */
:root{
  --surface-1:var(--surface); --hairline:var(--border); --ink:var(--text);
  --ink-3:var(--muted); --ink-4:var(--idle);
  --s1:var(--good); --s2:var(--warn); --s3:var(--accent); --s4:var(--bad);
  --r:var(--radius); --r-s:6px; --mono:var(--font-mono);
  --surface-3:#E7ECF1; --hair-2:#C7D0DA; --ink-2:#3A4754;
  --s1-l:#0F8A66; --s2-l:#B26412; --s3-l:#2C73B8; --s4-l:#C94A4A;
}
@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
  --surface-3:#1F2B39; --hair-2:#2B3B4C; --ink-2:#A5B5C5;
  --s1-l:#31C99A; --s2-l:#E5992F; --s3-l:#6FB4E8; --s4-l:#F08080;
}}
:root[data-theme="dark"]{
  --surface-3:#1F2B39; --hair-2:#2B3B4C; --ink-2:#A5B5C5;
  --s1-l:#31C99A; --s2-l:#E5992F; --s3-l:#6FB4E8; --s4-l:#F08080;
}
@keyframes pl{0%{transform:scale(.55);opacity:.7}100%{transform:scale(1.7);opacity:0}}

/* ---- top nav (prototype .topnav) ---- */
/* ── The top-bar navigation, switched off ───────────────────────────────
   The rail went back down the left on 19 October 2026 (the owner's call:
   the navigation he built, and keeping it means the screens around it do
   not have to be retested). These are the rules that had flattened it into
   a 54px bar across the top. They are scoped to .topbar-nav, a class
   nothing sets, rather than deleted: inert, but still here to read and to
   put back if the decision changes. The vertical rail itself is defined in
   two places that never went away -- the base .rail rule, and the
   @media (min-width: 1001px) block that narrows it to 118px. */
.topbar-nav #tabs{gap:6px;padding:0 20px;height:54px;background:var(--surface-1);border-bottom:1px solid var(--hairline)}
.topbar-nav #tabs .rail-brand{display:flex;align-items:center;gap:10px;height:40px;width:auto;padding:0 20px 0 0;margin:0 8px 0 0;border-right:1px solid var(--hairline);border-radius:0;background:none}
.topbar-nav #tabs .rail-brand img{width:40px;height:40px;border-radius:8px}
.topbar-nav #tabs .brand-tx b{font-size:14.5px;font-weight:700;letter-spacing:-.01em;line-height:1.15;color:var(--ink)}
.topbar-nav #tabs .brand-tx i{font-size:10.5px;color:var(--ink-3);font-weight:400}
.topbar-nav #tabs button{display:flex;align-items:center;gap:9px;height:100%;padding:0 15px;width:auto;
  font-size:13.5px;font-weight:500;color:var(--ink-2);border-bottom:2px solid transparent;border-radius:0;background:none;box-shadow:none}
.topbar-nav #tabs button:hover{background:var(--surface-2);color:var(--ink)}
.topbar-nav #tabs button.active{color:var(--ink);font-weight:600;background:none;border-bottom:2px solid var(--s3);box-shadow:none}
.topbar-nav #tabs button svg{flex:none}
.topbar-nav #tabs button.railuser{gap:10px;padding:5px 8px;border-radius:var(--r);border:1px solid transparent;margin-left:auto;background:none;box-shadow:none}
.topbar-nav #tabs button.railuser:hover{background:var(--surface-2);border-color:var(--hairline)}
.topbar-nav #tabs button.railuser.active{border-bottom:2px solid transparent}
.railuser #rail-face{width:30px;height:30px;border-radius:8px;background:var(--surface-3);color:var(--ink-2);font-size:11px;font-weight:700}
.railuser .ub-tx b{font-size:12.5px;font-weight:600;line-height:1.25;max-width:150px;color:var(--ink)}
.railuser .ub-tx i{font-size:10.5px;color:var(--ink-3)}

/* ---- work bar (prototype .topbar + .tb-*) ---- */
.appbar{gap:20px;padding:12px 22px;background:var(--surface-1);border-bottom:1px solid var(--hairline)}
.appbar-state{font-size:10px;letter-spacing:.18em;text-transform:uppercase;font-weight:700;color:var(--ink-3)}
.appbar.working .appbar-state{color:var(--s1-l)}
.appbar-time{font-family:var(--mono);font-size:25px;font-weight:700;letter-spacing:-.025em;line-height:1.12}
.appbar.working .appbar-clock{position:relative;padding-left:24px}
.appbar.working .appbar-clock::before{content:"";position:absolute;left:0;top:50%;margin-top:-5.5px;width:11px;height:11px;border-radius:50%;background:var(--s1);box-shadow:0 0 12px 1px color-mix(in srgb,var(--s1) 70%,transparent)}
.appbar.working .appbar-clock::after{content:"";position:absolute;left:-1px;top:50%;margin-top:-6.5px;width:13px;height:13px;border-radius:50%;border:1.5px solid var(--s1);opacity:.5;animation:pl 2.4s ease-out infinite}
.appbar-progress .track{height:9px;border-radius:99px;background:var(--surface-3)}
.appbar-progress .track i.work{background:linear-gradient(90deg,var(--s1),#1FC793)}
.appbar-progress .track i.brk{background:var(--s2)}

/* ---- report sub-nav (prototype .subnav) ---- */
#reportTabs{gap:2px;background:var(--surface-1)}
#reportTabs button{height:44px;padding:0 14px;font-size:13px;color:var(--ink-3);white-space:nowrap;font-weight:500;border-bottom:2px solid transparent;border-radius:0;background:none;box-shadow:none}
#reportTabs button:hover{color:var(--ink);background:none}
#reportTabs button.active{color:var(--ink);font-weight:600;border-bottom:2px solid var(--s3);background:none;box-shadow:none}

/* ---- fixes: nav layout, nav line, pulse ring ---- */
.topbar-nav #tabs button{flex-direction:row}
.topbar-nav #tabs{box-shadow:none;border-top:0}
.appbar.working .appbar-clock::after{left:-6px;margin-top:-11.5px;width:23px;height:23px}

/* ---- report chrome pinned like the second app ---- */
#app[data-view="reports"] > main{ padding-top:0; }
#reportTabs{
  /* Below the app bar, which is sticky again. --appbar-h is published by
     app.js from the real bar rather than guessed; the fallback is only for
     the moment before it is measured. The -1px laps the border so no
     hairline of content shows between the two. */
  position:sticky; top:calc(var(--appbar-h, 71px) - 1px); z-index:40;
  margin:0 -28px; padding:0 28px;
  background:var(--surface-1);
  border-bottom:1px solid var(--hairline);
}
.rep-bar{
  /* The bar, then the 45px tab strip, then this. Stacked rather than
     overlapping: a filter strip that lands ON the tabs is BUG-065 again. */
  position:sticky; top:calc(var(--appbar-h, 71px) + 44px); z-index:39;
  /* The band under the controls measured 33px: fourteen of this bar's own
     padding, the hairline, and eighteen of margin. The owner ringed it on
     27 October 2026 -- "remove it maintain 5px that's enough ... we have
     same space in other tabs also". Now 14: eight, the hairline, and the
     five he asked for.
     The bottom padding is 8 rather than 14 because the rows above it are
     already spaced by the 12px flex gap, so the bar does not need as much
     under the last of them as it needs above the first. Taking it to nought
     puts the hairline against the buttons, which is why the asymmetry. */
  margin:0 -28px 5px; padding:14px 28px 8px;
  background:var(--bg);
  border-bottom:1px solid var(--hairline);
}
/* ── On the website there is no tab strip to clear ─────────────────────
   The 44 pixels above are the report tabs, and the website has none: its
   navigation moved into the rail and #reportTabs is hidden. So the filter
   strip pinned 44px lower than the only thing above it -- the header --
   and the cards scrolled through that band in full view. The owner sent a
   screenshot of exactly that on 21 October: "filters make them sticky look
   how things are showing in background".

   paintHeader publishes the header's measured height into --appbar-h now,
   so this follows a real number rather than the stylesheet's 51px
   fallback, which could never correct itself: publishAppbarHeight measures
   .appbar, .appbar is display:none in a browser, and its `h > 0` guard
   skipped the write every time. */
:root[data-shell="browser"] .rep-bar { top: var(--appbar-h, 51px); }

/* The bleed has to match whatever padding main actually has.
 *
 * The -28px above reaches the edges of a main padded 28px. Below 640px main
 * is padded 16, so the bar ran 12px past each edge and took the whole
 * document sideways with it -- a 390px phone scrolling to 402.
 *
 * Written HERE rather than beside the other narrow rules nine hundred lines
 * up, because a media query adds no specificity: two equal selectors are
 * settled by source order, and the rule above would have won wherever the
 * query was. */
@media (max-width: 640px) {
  .rep-bar { margin: 0 -16px 5px; padding: 14px 16px 8px; }
  /* The tab strip bleeds the same way and needed the same correction;
     with both at -28 against a 16px main the page ran 12px wide, and
     the fixed footer spanned that wider page rather than the screen.
     The footer was a symptom, not a second fault. */
  #reportTabs { margin: 0 -16px; padding: 0 16px; }
}


/* ---- corporate logo top-right (replaces the user box) ---- */
/* The corporate logo was put at the top-RIGHT of the bar, which a column
   has no equivalent of: margin-left:auto drops it to the foot of the rail,
   where it overlapped the footer. Off with the rest of the top bar. The
   organisation's logo the owner asked for is the one above Home on the
   reports rail (#replogo) and is untouched. */
.rail-corp{display:none}
.topbar-nav .rail-corp{margin-left:auto;display:flex;align-items:center;flex:none}
.rail-corp img{height:40px;width:auto;display:block;background:#fff;border:1px solid var(--hairline);border-radius:7px;padding:4px 8px}
/* .rail-me was display:none for the top bar, which had nowhere to put
   a name. The rail shows it under Account, as the owner's screen does. */
.topbar-nav .rail-me{display:none}

/* ---- greeting in the dashboard theme-picker row ---- */
.dash-bar{align-items:center}
.dash-hello{margin-right:auto;font-size:36px;font-weight:700;letter-spacing:-.02em;color:var(--text);font-family:var(--font-display,inherit)}

/* ---- Account page polish ---- */
.acct-profile .acct-id{display:flex;align-items:center;gap:16px;margin-top:2px}
.acct-av{width:56px;height:56px;border-radius:15px;flex:none;display:grid;place-items:center;font-size:21px;font-weight:700;color:#fff;letter-spacing:.02em;background:linear-gradient(135deg,var(--accent),var(--good));box-shadow:var(--shadow)}
.acct-meta{flex:1;min-width:0}
.acct-name{font-size:19px;font-weight:700;letter-spacing:-.01em;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.acct-email{font-size:13.5px;color:var(--muted);margin-top:2px;overflow:hidden;text-overflow:ellipsis}
.acct-role{flex:none;align-self:flex-start;font-size:12px;font-weight:600;padding:5px 12px;border-radius:999px;color:var(--accent);background:var(--accent-soft)}
/* password requirements checklist */
.pw-reqs{list-style:none;margin:10px 0 4px;padding:0;display:grid;grid-template-columns:1fr 1fr;gap:7px 20px}
@media (max-width:620px){.pw-reqs{grid-template-columns:1fr}}
.pw-reqs li{display:flex;align-items:center;gap:9px;font-size:12.5px;color:var(--muted);transition:color .15s}
.pw-ck{width:17px;height:17px;border-radius:50%;flex:none;border:1.5px solid var(--border);position:relative;transition:background .15s,border-color .15s}
.pw-reqs li.ok{color:var(--text)}
.pw-reqs li.ok .pw-ck{background:var(--good);border-color:var(--good)}
.pw-reqs li.ok .pw-ck::after{content:"";position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg)}

/* ---- Account page: focused column + tidy password form ---- */
#view-account .card{max-width:560px}
#view-account .pane-head{max-width:560px}
.acct-pw .field{max-width:none}
.acct-pw .field .input,.acct-pw input{max-width:none}
.acct-pw .pw-reqs{max-width:none}
.acct-pw button{margin-top:4px}
#view-account .card h3{display:flex;align-items:center;gap:9px}
#view-account .card h3 svg{flex:none;color:var(--accent)}

/* ===== Account page (redesign to mockup) ===== */
.acct-hero{position:relative;overflow:hidden;display:flex;align-items:center;gap:20px;
  padding:26px 32px;border-radius:18px;margin-bottom:18px;max-width:none;
  background:linear-gradient(120deg,#6D5CE7 0%,#8A6FF0 55%,#A98BF5 100%);color:#fff}
.acct-hero-tx{position:relative;z-index:1;flex:1;min-width:0}
.acct-hero-k{font-size:11px;font-weight:700;letter-spacing:.16em;opacity:.85}
.acct-hero h2{margin:7px 0;font-size:27px;font-weight:800;letter-spacing:-.02em;color:#fff;font-family:var(--font-display,inherit)}
.acct-hero p{margin:0;font-size:14px;opacity:.92;color:#fff}
.acct-hero-art{flex:none;position:relative;z-index:1}
.acct-hero-art svg{width:118px;height:90px;display:block}
.acct-hero::after{content:"";position:absolute;right:-50px;top:-70px;width:280px;height:280px;border-radius:50%;background:rgba(255,255,255,.09)}
.acct-hero::before{content:"";position:absolute;right:130px;bottom:-90px;width:190px;height:190px;border-radius:50%;background:rgba(255,255,255,.06)}
@media (max-width:620px){.acct-hero-art{display:none}.acct-hero h2{font-size:23px}}

.acct-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:start}
@media (max-width:900px){.acct-grid{grid-template-columns:1fr}}
#view-account .card{max-width:none}
#view-account .card h3{margin-top:0}
.acct-wide{max-width:none;margin-top:18px}
.acct-signout .hint{margin:2px 0 14px}

/* profile */
.acct-profile .acct-id{display:flex;align-items:center;gap:15px;margin:6px 0 18px;padding-bottom:18px;border-bottom:1px solid var(--border)}
.acct-av{width:54px;height:54px;border-radius:15px;flex:none;display:grid;place-items:center;font-size:20px;font-weight:700;color:#fff;letter-spacing:.02em;background:linear-gradient(135deg,var(--accent),var(--good));box-shadow:var(--shadow)}
.acct-meta{flex:1;min-width:0}
.acct-name{font-size:17px;font-weight:700;letter-spacing:-.01em;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.acct-email{font-size:13px;color:var(--muted);margin-top:2px;overflow:hidden;text-overflow:ellipsis}
.acct-role{flex:none;align-self:flex-start;font-size:12px;font-weight:600;padding:5px 12px;border-radius:999px;color:var(--accent);background:var(--accent-soft)}
.acct-fields{display:grid;grid-template-columns:1fr 1fr;gap:16px 18px}
.acct-f-k{font-size:10.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--muted)}
.acct-f-v{font-size:15px;font-weight:600;color:var(--text);margin-top:4px}
.acct-stat{display:inline-flex;align-items:center;gap:7px;font-size:14px;font-weight:600}
.acct-stat::before{content:"";width:8px;height:8px;border-radius:50%;background:var(--muted)}
.acct-stat.on{color:var(--good)}
.acct-stat.on::before{background:var(--good)}
.acct-stat.off{color:var(--bad)}
.acct-stat.off::before{background:var(--bad)}

/* password */
.acct-pw .field{margin-bottom:13px}
.acct-pw .field label{display:block;margin-bottom:6px}
.pw-wrap{position:relative}
.pw-wrap input{width:100%;padding-right:40px}
.acct-eye{position:absolute;right:7px;top:50%;transform:translateY(-50%);background:none;border:0;cursor:pointer;color:var(--muted);padding:5px;display:grid;place-items:center;border-radius:7px}
.acct-eye:hover{color:var(--text);background:var(--surface-2)}
.acct-eye.on{color:var(--accent)}
.acct-pw .pw-reqs{grid-template-columns:1fr;gap:6px;margin:4px 0 2px}
.acct-pw-btn{width:100%;justify-content:center;margin-top:16px;gap:8px}

/* ===== Account page v2 (mockup) ===== */
.acct-top{max-width:none}
#view-account .card{max-width:none}


/* hero banner */
.acct-hero2{position:relative;overflow:hidden;display:flex;align-items:center;gap:20px;
  padding:26px 30px;border-radius:20px;margin:4px 0 20px;color:#fff;
  background:linear-gradient(110deg,#6D5CE7 0%,#7C7BEF 40%,#5E97E0 72%,#54B3DC 100%)}
.acct-hero2-wave{position:absolute;inset:0;pointer-events:none;opacity:.5;
  background:
    radial-gradient(130px 60px at 30% 120%,rgba(255,255,255,.18),transparent 70%),
    radial-gradient(160px 70px at 80% -20%,rgba(255,255,255,.16),transparent 70%)}
.acct-hero2::after{content:"";position:absolute;left:0;right:0;bottom:-2px;height:46px;
  background:radial-gradient(120% 100% at 50% 100%,rgba(255,255,255,.14),transparent 60%)}
.acct-hero2-id{position:relative;z-index:1;display:flex;align-items:center;gap:18px;flex:1;min-width:0}
.acct-av-wrap{position:relative;flex:none}
.acct-av2{width:72px;height:72px;border-radius:18px;display:grid;place-items:center;
  font-size:26px;font-weight:800;color:#fff;letter-spacing:.02em;
  background:linear-gradient(145deg,#2DB5A3,#2E73C9);box-shadow:0 8px 20px rgba(0,0,0,.18)}
.acct-av-edit{position:absolute;right:-5px;bottom:-5px;width:24px;height:24px;border-radius:50%;
  border:0;cursor:pointer;background:#fff;color:#5B6FD6;display:grid;place-items:center;box-shadow:0 2px 6px rgba(0,0,0,.2)}
.acct-av-edit:hover{color:#3f55c4}
.acct-hero2-meta{min-width:0}
.acct-hero2-badge{display:inline-block;font-size:11px;font-weight:700;letter-spacing:.08em;
  padding:3px 11px;border-radius:999px;background:rgba(255,255,255,.22);color:#fff;margin-bottom:7px}
.acct-hero2-name{font-size:25px;font-weight:800;letter-spacing:-.02em;line-height:1.1;color:#fff;
  font-family:var(--font-display,inherit);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.acct-hero2-email{font-size:14px;opacity:.9;margin-top:4px;color:#fff}
.acct-edit-btn{position:relative;z-index:1;flex:none;display:inline-flex;align-items:center;gap:8px;
  height:40px;padding:0 18px;border-radius:11px;border:0;cursor:pointer;font-size:14px;font-weight:600;
  background:#fff;color:#3f55c4}
.acct-edit-btn:hover{background:#eef1ff}
@media (max-width:640px){.acct-edit-btn{display:none}.acct-hero2-name{font-size:21px;white-space:normal}}

/* card headers with chip */
.acct-grid2{display:grid;grid-template-columns:minmax(0,0.82fr) minmax(0,1.18fr);gap:20px;align-items:start}
@media (max-width:1000px){.acct-grid2{grid-template-columns:1fr}}
.acct-ch{display:flex;align-items:center;gap:13px;margin-bottom:18px}
.acct-chip{width:40px;height:40px;border-radius:12px;flex:none;display:grid;place-items:center}
.chip-blue{background:var(--accent-soft);color:var(--accent)}
.chip-red{background:color-mix(in srgb,var(--bad) 15%,transparent);color:var(--bad)}
.acct-ch-t{font-size:18px;font-weight:700;letter-spacing:-.01em;color:var(--text)}
.acct-ch-s{font-size:12.5px;color:var(--muted);margin-top:2px}

.acct-subh{font-size:13px;font-weight:700;color:var(--text);margin:4px 0 6px}
.acct-rows{display:flex;flex-direction:column}
.acct-row{display:flex;align-items:center;gap:13px;padding:13px 2px;border-bottom:1px solid var(--border)}
.acct-row:last-child{border-bottom:0}
.acct-row-ic{width:30px;height:30px;border-radius:9px;flex:none;display:grid;place-items:center;background:var(--surface-2);color:var(--accent)}
.acct-row-k{flex:1;min-width:0;font-size:13.5px;color:var(--muted)}
.acct-row-v{font-size:14px;font-weight:600;color:var(--text);text-align:right;max-width:58%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.acct-row-v .acct-role{white-space:nowrap}

.acct-mini{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:12px;margin-top:16px}
.acct-mini-c{display:flex;align-items:center;gap:11px;padding:13px 14px;border-radius:13px}
.acct-mini-ic{width:34px;height:34px;border-radius:10px;flex:none;display:grid;place-items:center}
.acct-mini-k{font-size:11px;font-weight:600;letter-spacing:.03em;text-transform:uppercase;color:var(--muted)}
.acct-mini-v{font-size:14.5px;font-weight:700;color:var(--text);margin-top:2px}
/* m-blue for the organisation tile, which was drawn with a modifier that
   did not exist and so came out untinted beside three that were. Mixed
   from --fig-worked, the blue this product already uses for worked time,
   rather than a new token: the stylesheet test refuses a custom property
   that is used without being declared, and it is right to. */
.m-blue{background:color-mix(in srgb,var(--fig-worked) 10%,transparent)}.m-blue .acct-mini-ic{background:color-mix(in srgb,var(--fig-worked) 20%,transparent);color:var(--fig-worked)}
.m-violet{background:color-mix(in srgb,var(--accent) 10%,transparent)}.m-violet .acct-mini-ic{background:color-mix(in srgb,var(--accent) 20%,transparent);color:var(--accent)}
.m-green{background:color-mix(in srgb,var(--good) 12%,transparent)}.m-green .acct-mini-ic{background:color-mix(in srgb,var(--good) 22%,transparent);color:var(--good)}
.m-teal{background:color-mix(in srgb,var(--good) 10%,transparent)}.m-teal .acct-mini-ic{background:color-mix(in srgb,var(--good) 20%,transparent);color:var(--good)}

/* change password */
.acct-cp-body{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr);gap:22px;align-items:start}
@media (max-width:1300px){.acct-cp-body{grid-template-columns:1fr}}
.acct-cp-form .field{margin-bottom:14px}
.acct-cp-form .field label{display:block;margin-bottom:6px;font-size:13px;color:var(--muted)}
.acct-cp-reqs{background:var(--accent-soft);border:1px solid color-mix(in srgb,var(--accent) 18%,transparent);border-radius:14px;padding:16px 18px;align-self:start}
.acct-reqs-h{display:flex;align-items:center;gap:9px;font-size:13.5px;font-weight:700;color:var(--text);margin-bottom:12px}
.acct-reqs-h svg{color:#fff;background:var(--accent);border-radius:50%;padding:4px;width:26px;height:26px;box-sizing:border-box}
.pw-reqs2{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:11px}
.pw-reqs2 li{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--muted)}
.pw-reqs2 li.ok{color:var(--text)}
.pw-reqs2 li.ok .pw-ck{background:var(--good);border-color:var(--good)}
.pw-reqs2 li.ok .pw-ck::after{content:"";position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg)}
.acct-cp-note{display:flex;align-items:flex-start;gap:12px;margin-top:18px;padding:15px 16px;border-radius:13px;background:color-mix(in srgb,var(--accent) 7%,transparent)}
.acct-note-ic{flex:none;color:var(--accent)}
.acct-cp-note b{display:block;font-size:13.5px;color:var(--text)}
.acct-cp-note span{display:block;font-size:12.5px;color:var(--muted);margin-top:2px}

/* sign out */
.acct-signout-btn{flex:none;display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 18px;border-radius:11px;cursor:pointer;font-size:14px;font-weight:600;
  background:color-mix(in srgb,var(--bad) 10%,transparent);color:var(--bad);border:1px solid color-mix(in srgb,var(--bad) 30%,transparent)}
.acct-signout-btn:hover{background:color-mix(in srgb,var(--bad) 16%,transparent)}

/* password card: form fills the card after removing the requirements panel */
.acct-cp-body{display:block}

/* account: align the two columns + small centred sign-out */
.acct-grid2{align-items:stretch}
.acct-signout-btn2{display:inline-flex;align-items:center;gap:9px;height:44px;padding:0 26px;border-radius:12px;cursor:pointer;
  font-size:14px;font-weight:600;background:color-mix(in srgb,var(--bad) 10%,transparent);color:var(--bad);
  border:1px solid color-mix(in srgb,var(--bad) 32%,transparent)}
.acct-signout-btn2:hover{background:color-mix(in srgb,var(--bad) 17%,transparent)}

/* ---- fitted to one view, 16 October ----
 *
 * The ask: "reduce size of the cards, try to fit them in full page, i should
 * be able to see all those in one view without scroll", with Email Reports
 * allowed to sit below the fold.
 *
 * The budget is the page area under the app bar -- about 890px on a 1920x1080
 * window -- and it has to hold the header, the profile banner, Overview,
 * Change Password and the Timer Widget. What follows takes room out of
 * padding and oversized type, not out of anything anybody reads. Measured
 * rather than guessed: 1076px before, 740px after.
 */
.acct-top{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;
  margin-bottom:10px}
/* The title takes the free space so the pair lands at the far right;
   without this the heading was squeezed and "Your profile and sign-in
   details." wrapped under a button sitting in the middle of the row. */
/* Wider than the 560px cap on the pane head: that cap is for a column of
   prose, and this row now carries the sign-out pair at its far end. */
#view-account .pane-head.acct-top{max-width:none}
.acct-top-t{flex:1 1 auto;min-width:0}
.acct-top h2{margin:0;font-size:23px}
.acct-top .hint{margin:2px 0 0;font-size:13px}
/* The sign-out pair, top right. Not a card, and not at the foot of a scroll. */
.acct-head-out{display:flex;align-items:center;gap:14px;flex:none;padding-top:2px}
/* flex:none above means this pair never shrinks and, with no wrap, never
   breaks either: "Sign out of all devices" beside a 198px "Sign out from
   Tymake" is 389 pixels, which on a 390 pixel screen starts at an indent
   and ends past the edge. Measured at 425px of document. Allowed to wrap,
   and allowed to shrink, only where there is no room. */
@media (max-width: 640px) {
  .acct-head-out {
    flex-wrap: wrap; flex: 1 1 auto; min-width: 0; width: 100%;
  }
  .acct-head-out .acct-signout-btn2 { flex: 1 1 auto; min-width: 0; }
}
/* Smaller than the button it now sits under, and quieter: it is the
   qualifier on that button, not a second control competing with it.
   11.5px against the button's 13.5, with a tick to match. */
.acct-head-out .acct-signout-all{margin:0;font-size:11.5px;gap:7px}
.acct-head-out .acct-signout-all input{width:14px;height:14px}
/* Smaller than it was at the foot of the page: it is a header control now,
   and it sits beside a 38px Edit Profile button rather than alone. */
.acct-head-out .acct-signout-btn2{height:38px;padding:0 18px;font-size:13.5px}

/* The banner: a third shorter, same information. */
.acct-hero2{padding:16px 22px;margin:0 0 14px;gap:16px;border-radius:16px}
.acct-hero2-id{gap:14px}
.acct-av2{width:54px;height:54px;border-radius:14px;font-size:20px}
.acct-hero2-name{font-size:21px}
.acct-hero2-email{font-size:13px;margin-top:2px}
.acct-edit-btn{height:38px;padding:0 16px;font-size:13.5px}

/* The two cards, and the tiles under the first one. */
.acct-grid2{gap:14px}
#view-account .card{padding:16px 18px}
.acct-ch{margin-bottom:12px}
.acct-chip{width:34px;height:34px;border-radius:10px}
.acct-ch-t{font-size:16px}
.acct-ch-s{font-size:12px}
.acct-subh{margin:0 0 2px}
.acct-row{padding:9px 2px}
.acct-mini{margin-top:12px;gap:10px}
.acct-mini-c{padding:10px 12px;border-radius:11px}
.acct-mini-ic{width:30px;height:30px}
.acct-cp-form .field{margin-bottom:8px}
.acct-cp-form .field label{margin-bottom:4px}
.acct-pw-btn{margin-top:8px}
/* Email Reports and the Timer Widget sit below; the first of them keeps a
   clear gap from the cards above, the rest are tighter than they were. */
.acct-wide{margin-top:14px}
.acct-prefs .srow,#view-account .srow{padding-top:10px;padding-bottom:10px}


/* ---- The Day So Far: states, search and rows per page (17 Oct) ----
 *
 * Asked for as "search bar, pagination, rows per page, filters -- tracking,
 * not tracking, on break, not started". Buttons rather than a second
 * dropdown: the Order picklist stays as it is, and the objection of 22
 * September -- two dropdowns side by side reading as one undecided control --
 * still holds. A pressed button says what the table is showing without being
 * opened.
 *
 * All six class names checked against this stylesheet before being used. It
 * is one file of five thousand lines with generic names in it, and four
 * collisions have shipped from here, including two rules both called .modal.
 */
.home-filters {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin: 2px 0 14px;
}
.home-states { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.home-state {
  display: inline-flex; align-items: center; gap: 7px;
  height: 32px; padding: 0 12px; border-radius: 999px; cursor: pointer;
  font-size: 13px; font-weight: 600; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border);
}
.home-state:hover { color: var(--text); border-color: var(--muted); }
.home-state.on {
  color: var(--accent); background: var(--accent-soft); border-color: var(--accent);
}
/* The count rides inside the button, so the number and the thing it counts
   cannot be read apart from each other. */
.home-state-n {
  font-variant-numeric: tabular-nums; font-size: 12px; font-weight: 700;
  padding: 1px 7px; border-radius: 999px;
  background: var(--surface-2); color: var(--muted);
}
.home-state.on .home-state-n {
  background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent);
}
.home-filters-right { display: flex; align-items: center; gap: 12px; flex: none; }
.home-search { width: min(280px, 46vw); height: 32px; }
.home-per { display: flex; align-items: center; gap: 7px; }
.home-per .auditsel { height: 32px; padding: 0 8px; }
@media (max-width: 900px) {
  .home-filters-right { width: 100%; }
  .home-search { flex: 1 1 auto; width: auto; }
}
/* On a phone the row cannot hold the search box and the Rows picker side by
   side, and it was not allowed to wrap: .home-filters-right is width:100%
   with no flex-wrap, so the picker simply hung over the edge and took the
   whole page with it -- 411 pixels of document in a 390 pixel screen, and
   every screen scrolling sideways because of one control on Home.

   Found by measuring each website screen at 390px rather than by looking;
   it is the kind of overhang that reads as a margin until you try to scroll.
   The search takes a line of its own and the picker follows underneath. */
@media (max-width: 640px) {
  .home-filters-right { flex-wrap: wrap; min-width: 0; }
  .home-search { flex: 1 1 100%; width: 100%; }
  .home-per { min-width: 0; max-width: 100%; }
  .home-per .ftsel { min-width: 0; }
}

/* The Tymake logo watermark was removed on 19 October 2026 at the owner's
   request. It was a half-megapixel PNG inlined as base64 -- about ten
   kilobytes of this file -- painted fixed behind every screen at 14%
   opacity (19% in the dark theme).

   `#app` keeps its stacking context. It was added to lift the application
   above the watermark, but `position: relative` on it is now the
   containing block for anything absolutely positioned inside, and taking
   it away to tidy up would move those rather than tidy anything. */
#app{position:relative;z-index:1}

/* ===== Dark mode: lighter backgrounds + card surfaces ===== */
:root[data-theme="dark"]{
  --bg:#161B24; --surface:#1F2733; --surface-2:#2A3340;
  --surface-3:#2F3947; --border:#35404F; --hair-2:#3E4A5B;
}
@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
  --bg:#161B24; --surface:#1F2733; --surface-2:#2A3340;
  --surface-3:#2F3947; --border:#35404F; --hair-2:#3E4A5B;
}}

/* ---- Audit trail: the detail behind a row ---------------------------- */
/* Added 14 October with the filters (BUG-145). The server has been sending
   `meta` and `ip` on every entry all along and the table was dropping them,
   so a settings change showed as the word 'settings.tracking_updated' and
   nothing about what was actually changed. Names prefixed audit- on purpose:
   this file is one sheet and a class called .detail would have found several
   owners by the end of the week. */
.audit-detail {
  margin: 2px 0 8px; padding: 10px 14px;
  background: var(--surface-2); border-radius: 8px;
  border: 1px solid var(--border);
}
.audit-detail-line {
  display: flex; gap: 12px; align-items: baseline;
  padding: 3px 0; font-size: 13px;
}
.audit-detail-line + .audit-detail-line { border-top: 1px solid var(--border); }
.audit-detail-k {
  flex: none; min-width: 180px; color: var(--muted);
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase;
}
/* A before-and-after can be long, and an address must stay readable rather
   than being clipped to look like a shorter one. */
.audit-detail-v { word-break: break-word; font-variant-numeric: tabular-nums; }

/* The filter row wraps rather than scrolling: five controls do not fit on a
   narrow window, and a filter off the edge of the screen is one nobody can
   clear. */
.filter-row { flex-wrap: wrap; align-items: flex-end; gap: 12px; }

/* ---- One show/hide eye, not two ------------------------------------- */
/* BUG-143: the sign-in password field showed two eyes, one partly behind
   the other. Only one of them is ours. Edge and the WebView2 control the
   desktop app runs on draw their own reveal button inside a password
   field, and it sits exactly where ours does.

   Suppressed wherever we draw our own, which is every password field in
   the product: the app's sign-in (appsignin.html, served from here and so
   covered by this sheet), the web sign-in, and the Account pane's change-
   password form. Written against the input rather than the button because
   the pseudo-element belongs to the input.

   ::-ms-clear goes too: the same control draws an X to empty the field,
   which on a password field offers to discard what somebody has typed by
   accident, next to the control that reveals it. */
input[type="password"]::-ms-reveal,
input[type="password"]::-ms-clear {
  display: none;
  width: 0;
  height: 0;
}


/* ─── The organisation's logo (BUG-137) ──────────────────────────────────
 *
 * Two places: the control on Settings > Organisation Profile, and the mark
 * itself in the rail.
 *
 * Every class here is prefixed. styles.css is one file of five thousand lines
 * and has had four collisions on generic names, two of them on rules both
 * called .modal, so a bare .logo or .frame is not worth the saving.
 */
/* The logo sits at the top of Organisation Profile, centred, with its two
   actions under it and the note under those -- the shape of the screen it was
   asked to match. */
.orglogo-block { margin: 2px 0 26px; }
.orglogo { display: flex; justify-content: center; margin-bottom: 12px; }
.orglogo-acts { display: flex; justify-content: center; align-items: center; gap: 22px; }
.orglogo-note {
  margin: 12px auto 0;
  max-width: 460px;
  text-align: center;
  font-size: 12px;
  color: var(--muted);
}

/* The frame is the control: the whole of it takes a click or a dropped file,
   and it is the size the logo is actually used at rather than a thumbnail
   beside a button. */
.orglogo-frame {
  position: relative;
  width: 190px; height: 92px;
  display: grid; place-items: center;
  padding: 10px;
  border: 1.5px dashed var(--border);
  border-radius: 14px;
  background: var(--surface-2);
  color: var(--muted);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.orglogo-frame:hover,
.orglogo-frame:focus-visible { border-color: var(--accent); color: var(--accent); }
.orglogo-frame.over { border-color: var(--accent); background: var(--accent-soft); }
.orglogo-frame.has { border-style: solid; background: var(--surface); }
.orglogo-frame.busy { opacity: .6; pointer-events: none; }

.orglogo-shot { display: grid; place-items: center; max-width: 100%; max-height: 100%; }
.orglogo-shot img { max-width: 168px; max-height: 70px; object-fit: contain; display: block; }

/* The word sits under the picture while there is one, and in the middle of an
   empty frame while there is not. */
.orglogo-over {
  position: absolute; left: 0; right: 0; bottom: 6px;
  text-align: center;
  font-size: 11.5px; font-weight: 600;
  color: inherit;
  pointer-events: none;
}
.orglogo-frame:not(.has) .orglogo-over { bottom: 14px; }
.orglogo-frame.has .orglogo-over { opacity: 0; transition: opacity .15s ease; }
.orglogo-frame.has:hover .orglogo-over,
.orglogo-frame.has:focus-visible .orglogo-over { opacity: 1; }
.orglogo-frame.has:hover .orglogo-shot img { opacity: .25; transition: opacity .15s ease; }

/* The real control, out of the way. The frame above opens it. Kept in flow at
   no size rather than display:none, which some engines have refused to
   .click() programmatically. */
.orglogo-file { width: 0; height: 0; opacity: 0; border: 0; padding: 0; }

/* BUG-139: Save sat hard left on this pane. A modifier on the row that is
   already there, not a new class. */
.actions.centred { justify-content: center; }

/* The organisation's logo, over the reports rail and above Home.
 *
 * It was in the brand block at the top left until 16 October, with a rule
 * here that hid .brand-mark whenever a logo had loaded -- so an organisation
 * that uploaded a logo had the Tymake mark taken off its own product. The
 * owner put it plainly: "the one i uploaded was replacing tymake logo left
 * top". Both rules are gone; the mark is never hidden by anything now.
 *
 * The frame is a fixed height with the picture contained inside it, so a wide
 * wordmark and a square badge both sit in the same space and neither is
 * stretched -- the rail is 188px and a logo is whatever shape the customer
 * has. `hidden` is a UA rule of the lowest specificity and is beaten by
 * almost anything, so the block says display:none for itself. */
.replogo {
  display: flex; align-items: center; justify-content: center;
  height: 46px; margin: 2px 4px 10px;
  padding-bottom: 10px; border-bottom: 1px solid var(--hairline);
}
.replogo[hidden] { display: none; }
.replogo-img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }

/* Organisation Profile: the name and the timezone are short answers and were
   being given a box the width of the card. Both the same width, so the two
   rows line up rather than stepping. */
/* Both answers are short, both boxes the same width, and the column is
   centred under the logo above it -- a 900px box for a timezone made the
   screen read as a form that had lost its layout. */
#settingsPanes .orgcard .field {
  max-width: 360px;
  margin-left: auto;
  margin-right: auto;
}
#settingsPanes .orgcard .field > input,
#settingsPanes .orgcard .field > select { width: 100%; max-width: 100%; }
#settingsPanes .orgcard .field label { text-align: left; }
/* Save is already centred (BUG-139); keep it on the same axis as the fields. */
#settingsPanes .orgcard .actions.centred { max-width: 360px; margin-left: auto; margin-right: auto; }

/* Devices: the card was flush against the top of the screen once the enrolment
   box above it was removed (BUG-126). */
#view-devices > .card:first-child { margin-top: 18px; }


/* ── The report tabs: back across the top ───────────────────────────────
 *
 * The column below was asked for on 14 October ("bring tabs in reports
 * vertical") because nine names wrapped to two lines on a laptop and pushed
 * the report below the fold. On 19 October the owner asked for the row back,
 * and the reason the column was needed has gone with it: the navigation rail
 * went from 210px to 118px the same day, and the nine tabs measure 933px laid
 * out in a row -- they fit at 1366 as well as at 1920, which is why this is
 * no longer the trade it was.
 *
 * The row itself is not written here. It is the "report chrome pinned like
 * the second app" block further up, which never went away: #reportTabs sticky
 * at top:0, with .rep-bar offset to top:45px so the filter strip pins BELOW
 * the tabs instead of under them. That offset IS BUG-065 -- the strip used to
 * cover the heading and the Export button -- so it matters that it comes back
 * with the row rather than being written again from scratch.
 *
 * The column is kept rather than deleted, scoped to .reports-column, which
 * nothing sets. Putting it back is removing that one class.
 */

/* ─── The report tabs run down the side ──────────────────────────────────
 *
 * Asked for on 14 October: "bring tabs in reports vertical". Nine reports in
 * a horizontal strip wrapped to two lines on a laptop and pushed the report
 * itself below the fold -- and it was that strip, pinned at the top, that was
 * covering the heading and the Export button in BUG-065. A column answers
 * both: there is no bar above the content for anything to hide under, and the
 * nine names read as a list instead of wrapping.
 *
 * The strip and the reports are SIBLINGS in the page -- #view-reports holds
 * only the tabs, and each report is its own <section> beside it -- so the
 * column is made by laying main out as a grid on this screen alone. Every
 * other screen is untouched, and nothing in the HTML moved.
 */
.reports-column #app[data-view="reports"] > main {
  display: grid;
  grid-template-columns: 188px minmax(0, 1fr);
  column-gap: 26px;
  align-items: start;
  /* NOT padded at the top, and this is the third time this line has moved,
     so the reason is written down.

     This element is the one that SCROLLS. A sticky child offsets from the
     scrollport -- the padding box -- so 18px of padding here pushed the
     filter strip 18px down from the top edge, and every summary card
     scrolled through the band above it in front of nothing. Reported on
     16 October as "data is not hiding behind the filters", and that is
     exactly what it was.

     The room above the greeting is real and still wanted; it is on the
     columns below instead, where it scrolls away with the content it
     belongs to. */
  padding-top: 0;
}
/* The room, on the two columns rather than on the scrollport. */
#app[data-view="reports"] > main > section { padding-top: 18px; }
.reports-column #app[data-view="reports"] > main > #view-reports { grid-column: 1; }
.reports-column #app[data-view="reports"] > main > section:not(#view-reports) { grid-column: 2; }

.reports-column #app[data-view="reports"] #reportTabs {
  position: sticky;
  top: 10px;
  /* Same reason as .settings-nav: a column with no room to travel is
     not pinned, it just scrolls. Ten entries fit on any ordinary
     window, so the cap is only ever reached on a very short one. */
  max-height: calc(100vh - 140px);
  overflow-y: auto;
  overscroll-behavior: contain;
  z-index: 40;
  flex-direction: column;
  align-items: stretch;
  flex-wrap: nowrap;
  gap: 2px;
  margin: 0;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 12px;
}
.reports-column #app[data-view="reports"] #reportTabs button {
  width: 100%;
  text-align: left;
  padding: 9px 12px;
}
/* The requests inbox is not one of the reports.

   It belongs beside the report's Export button and is moved there as each
   one renders (see placeInbox). These rules are the fallback for the three
   reports that build no header of their own, where it stays in the rail
   under a line rather than pretending to be a tenth tab. */

/* The filter strip has nothing above it to clear any more. */
.reports-column #app[data-view="reports"] .rep-bar { top: 0; }

/* Below a laptop the column costs more than it gives: the names go back to a
   row, which is what the narrow layout did before any of this. */
@media (max-width: 900px) {
  #app[data-view="reports"] > main { display: block; }
  #app[data-view="reports"] #reportTabs {
    flex-direction: row;
    flex-wrap: wrap;
    border: 0;
    border-bottom: 1px solid var(--border);
    border-radius: 0;
    background: var(--surface-1);
    padding: 0 0 10px;
  }
  #app[data-view="reports"] #reportTabs button { width: auto; }
}

/* ── And on a phone, one row that scrolls sideways ─────────────────
   Wrapped, nine tabs take three rows and 147 pixels on a 390px screen --
   a fifth of the phone spent on navigation, above a report that has not
   begun. One row that scrolls costs 48, and it is the answer the rail
   directly above it already uses, so the two read as one piece of
   furniture rather than two unrelated ones.

   It has to be written at this specificity, in this place. The obvious
   rule, `#reportTabs.subtabs` in a 640px query, is a perfectly good
   selector that loses: the block immediately above is
   `#app[data-view="reports"] #reportTabs`, which carries an attribute
   selector and so outranks it whatever the order. overflow-x and
   scrollbar-width applied, flex-wrap did not, and the strip stayed three
   rows deep while the stylesheet read as correct. The browser's own list
   of matched rules is what found it; reading the file did not, twice.

   The scrollbar is hidden because a visible one under nine tabs is most
   of a tenth tab's worth of height. That leaves nothing on screen saying
   the row continues, so showReport scrolls the active tab into view -- a
   link straight to Devices, the ninth, would otherwise open with the
   strip still showing Home. */
@media (max-width: 640px) {
  #app[data-view="reports"] #reportTabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  #app[data-view="reports"] #reportTabs::-webkit-scrollbar { display: none; }
  #app[data-view="reports"] #reportTabs button {
    flex: none; white-space: nowrap;
  }
}

/* Account: the second sign-out, and the one card of settings that belongs to
   the person rather than the organisation (BUG-136). */
/* A tick on the sign-out, not a second button. */
.acct-signout-all {
  display: inline-flex; align-items: center; gap: 9px;
  color: var(--muted); font: 500 13px/1.3 inherit; cursor: pointer;
}
.acct-signout-all input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
.acct-signout-all:hover { color: var(--text); }
.acct-prefs h3 { margin: 0 0 2px; }
.acct-prefs .hint { margin: 0 0 6px; }

/* Why the address has no pencil beside it. Said once, quietly, under the
   value -- rather than in a message that appears only after somebody has
   pressed a button hoping to change it. */
.acct-row-note { display: block; font-size: 11.5px; color: var(--muted); font-weight: 400; margin-top: 2px; }

/* ─── Audit Log ──────────────────────────────────────────────────────────
 *
 * Built to the owner's reference screens of 14 October: one date control, a
 * user filter, a grouped category filter, Clear Filters, then an "Activity
 * Log" panel with a showing-range and four columns.
 *
 * Every class is prefixed `audit`. styles.css is five thousand lines and has
 * had six collisions on generic names, so a bare .bar or .count is not worth
 * the saving.
 */
.auditbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 14px;
}
.auditrange {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 38px; padding: 0 14px;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--bg); color: var(--text);
  font: 500 13px/1 inherit; cursor: pointer; white-space: nowrap;
}
.auditrange:hover { border-color: var(--accent); color: var(--accent); }
.auditsel { min-height: 38px; min-width: 150px; }
.auditsearch { min-height: 38px; flex: 1 1 220px; min-width: 180px; }
/* Present whether or not anything is filtered, so it is a control people know
   about rather than one that appears only once they need it. */
.auditclear {
  background: none; border: 0; padding: 0 6px;
  color: var(--accent); font: 600 13px/1 inherit; cursor: pointer;
}
.auditclear:disabled { color: var(--muted); cursor: default; }

.auditpanel-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; padding: 4px 2px 12px;
  border-bottom: 1px solid var(--border); margin-bottom: 4px;
}
.auditcount { color: var(--muted); font-size: 12.5px; font-variant-numeric: tabular-nums; }

.audittable td { vertical-align: top; }
.auditwho { display: flex; align-items: center; gap: 11px; }
.auditwho-n { font-weight: 600; }
.auditwho-m { color: var(--muted); font-size: 12px; margin-top: 1px; }
.auditact { display: inline-flex; align-items: center; gap: 8px; font-weight: 500; }
.auditact svg { flex: none; color: var(--muted); }
.auditact-on { color: var(--muted); font-size: 12.5px; margin: 3px 0 0 23px; }
/* The opened row's panel spans the table, under the row it belongs to. */
.auditopen > td { padding-top: 0; border-top: 0; }

/* The application-rule filters on Productivity: a search, a classification, a
   kind, and a pager under eighty-three rules.

   Prefixed `cat`, like every other family in this file. */
.catbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 14px;
}
.catsearch { min-height: 38px; flex: 1 1 220px; min-width: 170px; }
.catsel { min-height: 38px; min-width: 160px; }
/* Present whether or not anything is filtered, so it is a control people know
   about rather than one that appears only once they need it. */
.catclear {
  min-height: 38px; padding: 0 14px;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--bg); color: var(--text);
  font: 500 13px/1 inherit; cursor: pointer; white-space: nowrap;
}
.catclear:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.catclear:disabled { opacity: .5; cursor: default; }
.catcount {
  color: var(--muted); font-size: 12.5px; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* "Some People Have No Manager", at the foot of Roles & Privileges.
 *
 * The card used to put every name in one comma-joined paragraph. At sixty
 * people that is a wall of text with no way to find anybody in it, and it
 * pushed the rest of the page down. A capped, scrolling list keeps the card
 * the same height whether three people or sixty are in it.
 *
 * Prefixed `nomgr`, like every other family here -- this stylesheet has had
 * six collisions on generic names.
 */
.nomgrsearch { min-height: 36px; width: 100%; margin: 10px 0 8px; }
.nomgr-list {
  max-height: 232px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg);
}
.nomgr-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 8px 12px; border-bottom: 1px solid var(--border);
}
.nomgr-row:last-child { border-bottom: 0; }
.nomgr-name { font-size: 13.5px; font-weight: 600; }
/* Pushed to the right and quieter: it is there to tell two people with the
   same name apart, not to be read down the column. */
.nomgr-mail {
  margin-left: auto; color: var(--muted); font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 55%;
}
.nomgr-none { padding: 12px; margin: 0; }

/* Inviting people by email, under the shared link on the Signup Link pane.

   Prefixed `invite`, like every other family in this file -- it is five
   thousand lines and has had six collisions on generic names. */
/* The two rules, side by side under the box they govern. */
.invite-rules {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px; margin-top: 18px;
  padding-top: 16px; border-top: 1px solid var(--border);
}

.invite-box {
  width: 100%; min-height: 96px; resize: vertical;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg); color: var(--text);
  font: 13.5px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.invite-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 12px;
}
.invite-count {
  color: var(--muted); font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
.invite-count.bad { color: var(--bad); font-weight: 600; }

/* One line per address, so a typo in one is visible beside the eleven that
   went rather than hidden behind a single summary. Capped and scrolling: a
   hundred results must not push the queue below it off the page. */
.invite-results {
  margin-top: 14px; max-height: 260px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 10px; background: var(--bg);
}
.invite-results[hidden] { display: none; }
.invite-line {
  display: flex; align-items: baseline; gap: 10px;
  padding: 8px 12px; border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.invite-line:last-child { border-bottom: 0; }
.invite-line > span:first-child { overflow: hidden; text-overflow: ellipsis; }
.invite-outcome { margin-left: auto; font-size: 12px; white-space: nowrap; }
.invite-outcome.good { color: var(--good); }
.invite-outcome.bad { color: var(--bad); }
.invite-outcome.muted { color: var(--muted); }

/* The approval queue: a hundred people arrive through one signup link.
 *
 * .pending-head and .pending-all were in the markup with NO rule anywhere --
 * so the heading and the "Approve All" button were a block element and a
 * button stacked on top of each other, which is part of why this card read as
 * unfinished. Styled here with the search row, the selection bar and the tick
 * column that paging and multi-select needed.
 *
 * Every class is prefixed `pending`, by the same reasoning as the audit block
 * below: this stylesheet is five thousand lines and has had six collisions on
 * generic names, so a bare .bar or .count is not worth the saving.
 */
.pending-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 12px;
}
.pending-head h3 { margin: 0; }
.pending-all { flex: none; }

.pending-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 12px;
}
.pendingsearch { min-height: 38px; flex: 1 1 240px; min-width: 180px; }
.pending-count {
  color: var(--muted); font-size: 12.5px; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Only present while something is selected, so it does not sit there empty
   above every queue. */
.pending-sel {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 12px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2);
}
.pending-sel > span {
  margin-right: auto; font-size: 13.5px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Narrow, and not pushed away from its row by the cell padding that suits a
   column of words. */
table.grid th.pending-tick,
table.grid td.pending-tick { width: 30px; padding-right: 0; }
.pending-tick input { cursor: pointer; }

/* The audit log's range picker: a Quick Select column beside Start Date and
   End Date, on the same .range-modal shell Reports uses.

   It used to be two bare calendars in a 440px .modal, capped at 680 once the
   owner saw them stacked. Two 240px calendars fit that and nothing else did,
   so there was no room for the shortcuts -- and every question this screen is
   asked ("who changed that last Tuesday") was two journeys through two
   calendars. Reports' shell already lays out a 232px shortcut column beside a
   pane and already collapses to one column under 820px, so the layout and the
   mobile behaviour come from rules that are already right on another screen.

   What is left here is only what differs from Reports: a wider box to hold
   three columns, the two calendars side by side inside .range-right, and a
   footer, which Reports has no need of and .range-modal's `padding: 0`
   therefore leaves unpadded. */
.auditrange-modal { width: min(880px, 100%); }
/* Side by side, and only allowed to stack on a window too narrow to hold
   them -- which is what flex-wrap is for, not a layout nobody asked for. */
.auditrange-pair { display: flex; gap: 22px; flex-wrap: wrap; padding: 4px 2px; }
@media (min-width: 860px) { .auditrange-pair { flex-wrap: nowrap; } }
.auditrange-side { flex: 1 1 236px; min-width: 236px; }
.auditrange-cap {
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 8px;
}
.auditrange-foot {
  align-items: center; margin-top: 0;
  padding: 14px 20px; border-top: 1px solid var(--border);
}
/* .modal-actions gives every button flex: 1, which on a footer with a label
   in it stretched Cancel and Apply across the whole width. */
.auditrange-foot button { flex: 0 0 auto; }
.auditrange-chosen { margin-right: auto; color: var(--muted); font-size: 12.5px; }

/* The opened detail, as the reference draws it: a panel across the row with
   the field on the left and what it became on the right, one line each. */
.auditopen .audit-detail {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 16px;
  margin: 0 0 6px;
}
.auditopen .audit-detail-line {
  display: flex; gap: 12px; align-items: baseline;
  padding: 4px 0;
}
.auditopen .audit-detail-line + .audit-detail-line {
  border-top: 1px solid var(--border);
}
.auditopen .audit-detail-k {
  flex: none; min-width: 190px;
  color: var(--muted); font-size: 12.5px;
}
.auditopen .audit-detail-v { font-weight: 600; }

/* In the header, beside Export. The rail rule above pins it with
   `margin-left: auto`, which in a row of header tools would shove it
   away from the button it was asked to sit next to. */
/* The envelope keeps no box, wherever it lands.
 *
 * `.head-tools button:not(.primary)` three thousand lines up gives every
 * other button in that row a surface fill and a border -- and it outranks a
 * lone `.rep-inbox`, so the envelope kept the box it had been asked to lose.
 * Written as `button.rep-inbox` to match that specificity and win on order.
 *
 * Reported twice on 17 October: "remove that border and bg color just only
 * the envelope is enough", and then the screenshot showing it still boxed.
 */
/* The :not()s are not decoration -- they are what makes this win.
 *
 * This rule was written on 17 October to take the border and the fill off
 * the envelope, and its comment says it was "written as button.rep-inbox to
 * match that specificity and win on order". It does not match it. The rule
 * it has to beat is
 *
 *     .head-tools button:not(.primary):not(.switch)        (0,0,3,1)
 *
 * and :not() contributes the specificity of its ARGUMENT, so that is three
 * classes and an element against this one's two. It has been losing ever
 * since: the envelope still carried a white fill, a 1px border and
 * `padding: 8px 14px`, which is why the owner asked twice and saw no
 * change, and why on 21 October the icon measured six pixels right of the
 * middle of its own button -- 14px of padding each side leaves an 8px
 * content box for a 19px icon, and a 19px icon cannot be centred in it.
 *
 * Carrying the same two :not()s makes this (0,0,4,1), which beats it by one
 * class, and the place-items puts the icon in the middle of the box rather
 * than at the start of an 8px track. */
.head-tools button.rep-inbox:not(.primary):not(.switch),
.rep-bar-right button.rep-inbox:not(.primary):not(.switch),
.rep-bar button.rep-inbox:not(.primary):not(.switch),
.pane-head button.rep-inbox:not(.primary):not(.switch) {
  margin-left: 0;
  width: 38px; height: 38px; padding: 0;
  display: inline-grid; place-items: center;
  background: none; border: 0; border-radius: 9px;
  color: var(--muted);
}
.head-tools button.rep-inbox:hover:not(:disabled),
.rep-bar-right button.rep-inbox:hover:not(:disabled),
.pane-head button.rep-inbox:hover:not(:disabled) {
  background: none; border: 0; color: var(--accent);
}

/* The tools sit level with the middle of the heading beside them.
 *
 * The Home greeting is two lines -- "Good afternoon, Muralidhar" over
 * "Saturday, October 17 · 64 people" -- and the row was aligned to its first
 * line, so Export and the envelope floated eleven pixels above the middle of
 * it. That gap is what "envelope is not yet centered" was. */
.pane-head.rep-head { align-items: center; }

/* ── The tools keep their size; the sentence beside them gives way ─────
   .rep-head is a flex row holding a text column and a group of controls,
   and neither said what should happen when they do not both fit. The flex
   default is that both shrink, and a group of controls has a hard floor
   while a paragraph does not -- so the paragraph won and the controls were
   squeezed. On Matrix, whose blurb is three sentences, the group came down
   to the width of one button and "<- Previous", the week, and "Next ->"
   stacked into three rows at 1440 pixels. Reported by the owner on 20
   October with a screenshot of the desktop app.

   So the controls are told not to shrink, and the text column is told it
   may: a wrapped sentence is what a narrow column is for, and a wrapped
   button row is a fault. This is the pair, stated once, for every heading
   built this way rather than for the one that was noticed. */
/* "More Info" sits on the title's line, so collapsing the sentence
   underneath reclaims the whole of its height rather than trading it for a
   button on a line of its own. Baseline-aligned with the h2 beside it. */
/* The title, once it is the first thing in the filter bar. It keeps its
   own size but stops claiming a line of its own, and does not shrink the
   filters beside it. */
/* A baseline row of its own, not inherited layout.
   The h2 carries .rep-title-inline, but that rule is scoped to .pane-head
   and the title now lives in the bar -- so it stayed a block and "More
   Info" dropped underneath it, making the lead 46px tall in a row whose
   other contents are 40. Saying it here, where the title actually is,
   holds whatever the h2's display happens to be. */
.rep-bar .rep-bar-title {
  flex: none; display: flex; align-items: baseline; gap: 10px;
  margin-right: 18px; min-width: 0;
}
.rep-bar .rep-bar-title h2 {
  margin: 0; font-size: 19px; line-height: 1.25; white-space: nowrap;
}
.rep-bar .rep-bar-title .rep-about { flex: none; margin-left: 0; }
.pane-head.rep-head[hidden] { display: none; }

.pane-head h2.rep-title-inline { display: inline-block; vertical-align: baseline; }
.pane-head h2.rep-title-inline + .rep-about {
  display: inline-block; margin-left: 12px; vertical-align: baseline;
  font: 600 12.5px/1.2 inherit;
}
.pane-head .hint[hidden] { display: none; }

.pane-head.rep-head > .head-tools { flex: none; flex-wrap: nowrap; }
.pane-head.rep-head > :first-child { min-width: 0; }

/* ── and stop sharing the row on a phone ──────────────────────────────
   Side by side at 390px the heading gets 225 pixels and the tools 118, so
   "Export overview" and "Export detailed" each break across two lines and
   the envelope drops to a third row of its own, beside a sentence that is
   wrapping at four words. Stacking gives the sentence the full width and
   puts the three tools in one row under it.

   display:block rather than flex-direction:column so .head-tools keeps its
   own flex row; its margin-left:auto is harmless once it is a block. */
@media (max-width: 640px) {
  .pane-head.rep-head { display: block; }
  .pane-head.rep-head > .head-tools { margin-top: 14px; }
}

/* A setting nothing acts on yet: still readable, plainly not usable.
   See BUG-165 and the row builder in admin.js. */
.srow-control-pending { opacity: .55; cursor: not-allowed; }
.srow-control-pending * { pointer-events: none; }

/* ── Attendance over a range: a card per day, scrolling sideways ────────
 *
 * Reported by Sagarika on 19 October 2026. Picking a range showed only the
 * last day of it; each person now keeps one row with their days under it.
 *
 * Sideways rather than stacked, because the rows below belong to other
 * people: a week stacked would put seven strips between one person and the
 * next and the list would stop reading as a list of people. */
/* A table cell grows to fit whatever is in it, so seven 340px cards made
   the table 2,400px wide and pushed clock-out, worked and break off the
   right-hand edge. max-width:0 is the standard way to tell a cell it may
   be narrower than its contents; the scroller inside then has a bounded
   width to scroll within, which is what makes the rail scroll PER PERSON
   rather than moving the whole table. */
.attdays-cell { max-width: 0; }
.attdays-scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* The scrollbar is part of the control here, not chrome to hide: without
     it there is nothing on screen saying the days continue past the edge. */
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
  padding-bottom: 6px;
}
.attdays {
  display: flex;
  gap: 12px;
  /* min-content so the cards keep their width instead of being squeezed
     to fit, which is what makes the rail scroll at all. */
  width: min-content;
}
.attday {
  flex: none;
  width: 340px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
}
.attday-head { display: flex; align-items: center; gap: 8px; }
.attday-head b { font-size: 14px; font-weight: 650; color: var(--text); }
/* Filled only on a day somebody worked, so an empty day is visibly empty
   rather than just numerically zero. */
.attday-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--border); flex: none;
}
.attday-dot.on { background: var(--good); }
.attday-figures {
  display: flex; justify-content: space-between; gap: 16px;
  margin: 8px 0 10px;
  font-size: 13px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 900px) { .attday { width: 280px; } }

/* ══ The rail is a drawer on a phone ══════════════════════════════════════
 *
 * The owner, 21 October, holding eight screenshots of WorkComposer's mobile
 * web app: "do as what we have there". A hamburger, and a drawer over the
 * page.
 *
 * What it replaces, measured at 375x667 before any of this was written:
 *
 *     .rail scrollWidth 1921 against clientWidth 375  ->  1546px hidden
 *     "Settings & Admin" at x=1359   "User Management" at x=1519
 *
 * Four screen-widths of navigation off the right-hand side. And the strip
 * kept its scroll offset between screens and never brought the current
 * entry back into view, so one flick left you on Home with the strip
 * reading "User Management | Downloads | Account" and nothing saying where
 * you were.
 *
 * LAST IN THE FILE ON PURPOSE. There are five earlier
 * `@media (max-width: 640px)` blocks that style .rail -- at lines 1439,
 * 1655, 5424 and 5493 -- and a media query adds NO specificity, so equal
 * selectors are settled by source order alone. Every rule here also carries
 * :root[data-shell="browser"], which wins on specificity as well, so this
 * holds wherever it is moved to. That second belt is not paranoia: a phone
 * rule for .scope-panel written 500 lines above its own base rule lost
 * silently earlier today and put a panel's left edge at -161px.
 *
 * Scoped to the browser shell, so the desktop app keeps the strip: it has
 * its own window chrome and its own bar, and a desktop window dragged
 * narrower than 640px is not the case this is for.
 * ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {

  /* -- the drawer itself ------------------------------------------------ */
  :root[data-shell="browser"] .rail {
    /* Out of the grid entirely. It carried grid-column/grid-row for the
       stacked phone layout; fixed takes it out of flow, and main moves up
       into the row it leaves behind. */
    position: fixed; top: 0; bottom: 0; left: 0; right: auto;
    z-index: 91;
    width: min(86vw, 320px); height: 100%;
    display: flex; flex-direction: column;
    overflow-x: hidden; overflow-y: hidden;
    padding: 8px 0 0;
    background: var(--surface);
    border-right: 1px solid var(--border); border-bottom: 0;
    transform: translateX(-101%);
    transition: transform .22s ease;
    box-shadow: none;
  }
  :root[data-shell="browser"] .rail[data-open] {
    transform: none;
    box-shadow: 0 0 40px rgba(0, 0, 0, .22);
  }
  @media (prefers-reduced-motion: reduce) {
    :root[data-shell="browser"] .rail { transition: none; }
  }

  /* main takes the row the rail has left. The header is row 1; row 2 held
     the strip and would otherwise collapse to nothing with a gap above the
     report where the strip's border used to be. */
  :root[data-shell="browser"] #app > main { grid-row: 2; }

  :root[data-shell="browser"] .railback {
    position: fixed; inset: 0; z-index: 90;
    background: rgba(17, 24, 39, .42);
    border: 0;
  }
  /* The page behind does not scroll under an open drawer. A sheet covering
     the screen while the report slides about underneath reads as a broken
     overlay rather than a menu. */
  body.drawer-open { overflow: hidden; }

  /* -- the top: mark, organisation, product ----------------------------- */
  /* The word and the company name come back.
   *
   * They are switched off by a @media (max-width: 1000px) rule whose own
   * comment explains itself: "62 pixels of rail has room for a mark and
   * nothing else". True of the narrow SIDEBAR; a 320px drawer is not that,
   * and the drawer opened showing a bare logo mark centred over the list
   * where WorkComposer shows "Symaki" with the customer's name beneath it.
   * That rule is (0,4,0), so these carry .rail as well to reach (0,5,0)
   * rather than relying on coming later in the file. */
  :root[data-shell="browser"] .rail .rail-brand .brand-tx { display: block; }
  :root[data-shell="browser"] .rail .rail-brand { justify-content: flex-start; }
  /* The brand comes back. It is display:none in the strip rule, where a
     62px-tall sideways row genuinely had nowhere to put it; a drawer is a
     column with a top, which is where WorkComposer puts theirs -- their
     wordmark with the customer's name beneath it. */
  :root[data-shell="browser"] .rail .rail-brand {
    display: flex; flex: none;
    position: static;
    /* Room on the right for the close button, so the two never share a
       pixel whatever either one's z-index says. */
    padding: 0 56px 4px 20px; margin: 0;
  }
  :root[data-shell="browser"] .rail .rail-close {
    /* Above the brand, and out from under it.
     *
     * The owner: "what is that cross mark?? cross mark not wokring in
     * mobile". It was in the right place and it was unreachable:
     * elementsFromPoint at the middle of the button returned .rail-brand
     * first. The brand is a 319px-wide row carrying z-index: 2 from the
     * sidebar rules, and the close button carried the same 2 -- a tie,
     * settled by document order, and the brand comes second.
     *
     * A programmatic click still worked, which is why the drawer's own
     * test passed: .click() does not hit-test. Only asking what is at the
     * point a thumb lands on found it. */
    position: absolute; top: 14px; right: 12px; z-index: 4;
    width: 36px; height: 36px; padding: 0;
    display: inline-grid; place-items: center;
    background: none; border: 0; border-radius: 9px;
    color: var(--muted); cursor: pointer;
  }
  :root[data-shell="browser"] .rail .rail-close:hover {
    background: var(--surface-2); color: var(--text);
  }
  :root[data-shell="browser"] .rail #replogo {
    display: block; flex: none; padding: 0 18px 12px;
  }
  :root[data-shell="browser"] .rail button.rail-switch {
    width: auto; flex: none;
    margin: 0 14px 8px; padding: 10px 13px;
    justify-content: flex-start; gap: 10px;
  }
  :root[data-shell="browser"] .rail button.rail-switch .rail-switch-name {
    display: inline; flex: 1 1 auto; text-align: left;
  }
  :root[data-shell="browser"] .rail .rail-switch-caret { display: inline; }

  /* The dividers were 1px vertical slivers for a sideways row. In a column
     they are rules across it. */
  :root[data-shell="browser"] .rail .rail-rule,
  :root[data-shell="browser"] .rail .rail-gap-web {
    display: block; flex: 0 0 1px; width: auto; height: 1px;
    min-height: 0; align-self: auto;
    margin: 4px 14px; background: var(--border);
  }

  /* -- the destinations, which are the only part that scrolls ----------- */
  /* display:contents in the strip rule, which flattened this band so its
     children could join the sideways row. A drawer needs it back as a real
     box, because it is what scrolls while the top and the bottom stay. */
  :root[data-shell="browser"] .rail .rail-scroll {
    display: flex; flex-direction: column; gap: 1px;
    flex: 1 1 auto; min-height: 0;
    overflow-y: auto; overflow-x: hidden;
    padding: 2px 14px 6px; margin: 0;
  }

  :root[data-shell="browser"] .rail button,
  :root[data-shell="browser"] .rail a.rail-link {
    width: 100%; flex: none;
    justify-content: flex-start; gap: 13px;
    padding: 9px 13px; border-radius: 10px;
    white-space: nowrap; text-align: left;
  }
  :root[data-shell="browser"] .rail button span { display: inline; }
  :root[data-shell="browser"] .rail button i {
    width: 22px; flex: none; font-size: 15px;
  }

  /* -- the bottom block, pinned, the way theirs is ---------------------- */
  :root[data-shell="browser"] .rail .rail-admin {
    flex: none; width: auto; margin: 0 14px;
  }
  :root[data-shell="browser"] .rail .rail-admin:first-of-type {
    margin-top: 4px; padding-top: 7px;
    border-top: 1px solid var(--border);
  }
  :root[data-shell="browser"] .rail .rail-admin:last-of-type {
    margin-bottom: 8px;
  }
  /* Two lines are fine down here; the strip rule let them wrap for the same
     reason and a column has even less excuse to clip them. */
  :root[data-shell="browser"] .rail button.rail-admin span {
    white-space: normal; line-height: 1.2;
  }

  /* -- the hamburger ---------------------------------------------------- */
  :root[data-shell="browser"] .webhead-burger {
    display: inline-grid; place-items: center; flex: none;
    width: 38px; height: 38px; margin-right: 2px; padding: 0;
    background: none; border: 0; border-radius: 9px;
    color: var(--text); cursor: pointer;
  }
  :root[data-shell="browser"] .webhead-burger:hover {
    background: var(--surface-2);
  }
}

/* Above phone width there is no drawer and no hamburger: the rail is the
   sidebar it has always been, and these two would be a button that opens
   nothing and a grey sheet over the page. */
@media (min-width: 641px) {
  /* Three parts of the phone drawer, none of which has a job above 640.
     The close button is the one that was MISSED: the hamburger and the
     backdrop were hidden here from the start, but the X was left in the
     document with only a phone rule to style it, so on a desk it drew as
     a plain unstyled button in the top-left corner above the wordmark --
     "why cross mark in web app ?? see top left ?? remove it". There is
     nothing to close up here; the rail is the sidebar. */
  .webhead-burger, .railback { display: none; }
  /* Through .rail, because `.rail button` three thousand lines up is
     (0,1,1) and sets display: flex. A bare .rail-close is (0,1,0) and
     loses to it, which is a tie this has to win outright rather than by
     sitting lower in the file. */
  :root[data-shell="browser"] .rail .rail-close { display: none; }
}

/* Five range tabs in 283 pixels.
 *
 * Day/Week/Month/Year/Custom measured 313px of content inside a 283px box
 * at 375px wide, so Custom was cut in half -- and Custom is the one you
 * press to reach the Start and End calendars. The .seg rule above makes it
 * scroll, and hides the scrollbar while it is at it, so nothing on the
 * screen said the tab was there. Taking four pixels off each side of every
 * tab fits all five, which is better than an affordance for scrolling to
 * something that should not have needed scrolling to.
 *
 * (0,3,0) against .seg button at (0,1,1), so it wins on specificity rather
 * than on being further down the file.) */
@media (max-width: 640px) {
  .range-modal .range-tabs { max-width: 100%; }
  .range-modal .range-tabs button { padding: 7px 10px; }
}

/* ── Two phone corrections from the owner's own screens ─────────────────*/

/* Priority and Status stack on a phone.
 *
 * .pj-two is a 1fr 1fr grid, which at 375px gives each select 126.5px --
 * measured, with the Task Details sheet open on a real task. WorkComposer
 * stacks every field in that sheet full width, and the owner's screenshots
 * of it on 21 October show exactly that. Two half-width pickers side by
 * side is the one place our sheet differed from theirs. */
@media (max-width: 640px) {
  .pj-two { grid-template-columns: minmax(0, 1fr); gap: 12px; }
}

/* The 2-up / 3-up / 4-up zoom goes on a phone.
 *
 * The owner, 21 October: "2 up 3 up 4 up you can remove from mobile". It
 * sets how many captures sit across the grid, and at 375px the answer is
 * one whichever is chosen -- so it is three buttons that look like they do
 * something and do not, taking a third of the second toolbar row with
 * them. The control stays on every wider screen, where it works. */
@media (max-width: 640px) {
  .shots-density { display: none; }
}

/* ── The people list is cards on a phone, not a table ──────────────────
 *
 * Measured at 375x667: .users-table is 595px wide inside a 375px screen,
 * so .users-tablewrap scrolled sideways and Role, Status and Actions --
 * the three columns anybody opens this screen to use -- were off the right
 * of every row. The owner's screenshot of WorkComposer's User Management
 * on a phone shows no table at all: one card per person, with the name and
 * address, their team, a role pill, a status pill, and the actions button
 * in the corner.
 *
 * A table that becomes blocks, rather than a second list built in JS. The
 * cells already hold exactly what the card needs and every handler on them
 * keeps working; a parallel renderer would be a second place to fix every
 * future change to a row. The header row goes because its labels cannot
 * follow the cells down, and it is not missed: an avatar with a name and
 * an address, a team, and two pills do not need telling apart.
 * ═════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  /* Not a scroll container at all on a phone.
   *
   * The owner, 21 October: "user management is broken not scrolling up".
   * The wrap declares overflow-y: auto, overscroll-behavior: contain and
   * max-height: calc(100vh - 330px) -- a scrolling panel inside a page
   * that does not scroll, which is right on a desktop. Taking the cap off
   * for the phone left the other two behind: measured at 375px with 22
   * people, the wrap is 4034px tall and scrolls nothing, while the PAGE
   * carries all 4485px of it. A box that cannot scroll but still contains
   * overscroll swallows a touch gesture rather than passing it to the page
   * -- and that box is almost the whole screen, so a thumb anywhere over
   * the list stops working.
   *
   * So the page is the only scroller here, which is what a phone wants. */
  .users-tablewrap {
    overflow: visible; max-height: none; min-height: 0;
    overscroll-behavior: auto;
  }
  .users-table,
  .users-table tbody,
  .users-table tr,
  .users-table td { display: block; width: auto; }
  .users-table thead { display: none; }
  .users-table tr {
    position: relative;
    padding: 12px 14px 12px 0;
    border-top: 1px solid var(--border);
  }
  .users-table td {
    /* Both edges. The base rule sets border-top and a later one sets
       border-bottom, so zeroing only the top left a hairline under every
       field inside the card -- four rules per person where theirs has
       none. Read off the computed style of a real row rather than from the
       rule I expected to be there. */
    border-top: 0; border-bottom: 0; padding: 3px 0;
  }
  /* One left edge down the card.
   *
   * The person cell puts a 36px avatar before the name, so the name starts
   * 48 pixels in (36 + the 12px gap) while the team, the role picker and
   * the status pill all started at 0 -- measured. A card whose five lines
   * begin at two different places reads as broken, which is what the owner
   * called "records are not aligned properly". The avatar keeps hanging to
   * the left of them all, which is what makes it an avatar. */
  .users-table td:not(:first-child):not(:last-child) { padding-left: 48px; }
  /* Actions in the corner, the way theirs sits there, and out of the flow
     so the four cells above it keep the full width of the card. */
  .users-table td:last-child {
    position: absolute; top: 10px; right: 0; padding: 0;
  }
  .users-table td:first-child { padding-right: 46px; }
  .users-table tbody tr:hover { background: none; }
}

/* ── Report tables on a phone ──────────────────────────────────────────
 *
 * table.grid declares `white-space: nowrap` on every cell and a 160px
 * floor under the first column, which is right on a desk and is why Web &
 * app usage measured 688 pixels wide inside a 375px screen: Application
 * 326, Share of tracked time 168, Total 74, % 57, People 63. .gridwrap
 * scrolls sideways so nothing spilled onto the page, but the owner's
 * screenshot shows the header reading "TOTA" at the edge and no sign that
 * two more columns existed.
 *
 * Six screens draw one of these -- Web & app usage, Attendance, pending
 * users, Shifts, Billing, and anything built by tableOf() -- so letting
 * the cells wrap fixes all six without a second renderer for any of them,
 * and keeps every column rather than choosing which data a phone does not
 * deserve.
 *
 * Same specificity as the rules above, so this has to stay below them.
 * ═════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  /* Narrower, and asked to fit -- but it still will not, and the comment
     has to say so rather than imply a fix that did not land.
     
     Measured at 375px on Web & app usage: 688px of table to start with,
     637 once the cells were allowed to wrap, 617 once .person was allowed
     to wrap and the share bar's 120px floor came off. width: 100% changed
     nothing, because the table's MIN-content is 617: five columns of real
     content do not go into the 305px this wrapper has, whatever they are
     asked. So the wrapper keeps its sideways scroll, and the rule below
     makes that scroll visible instead of silent. */
  table.grid { font-size: 12.5px; max-width: none; min-width: 0; }
  table.grid th, table.grid td {
    white-space: normal;
    padding-left: 5px; padding-right: 5px;
  }
  /* The two cells that were actually holding the table open, measured
     rather than guessed:
       .person   295px -- a nowrap flex row of mark (26) + name (110) +
                  verdict pill (76). Let it wrap and the pill drops under
                  the name.
       .meter.wide 148px -- a min-width: 120px floor on the share bar.
     Scoped through table.grid so neither reaches the same classes
     elsewhere, and so each wins on specificity rather than on order. */
  table.grid .person { flex-wrap: wrap; }
  table.grid .appname-open { white-space: normal; }
  table.grid .meter.wide { min-width: 56px; }
  /* The floor exists so a name column does not collapse to one word per
     line beside wide figures. At 375px there are no wide figures left to
     protect it from, and 160 of 343 is half the screen. */
  table.grid td:first-child { min-width: 0; }

  /* A scroll you can SEE.
   *
   * .gridwrap has scrolled sideways all along; what it never did was say
   * so. The owner photographed the result -- a header reading "TOTA" at
   * the right edge of the screen with no hint that two more columns
   * existed -- and the same complaint has now been made about three
   * different controls today, always the same shape: the content is
   * there, and nothing tells you.
   *
   * A thin always-visible scrollbar and a fade at the right edge that
   * disappears when you reach the end. The fade is a background-attachment
   * trick on the wrapper itself, so it costs no element and cannot end up
   * over the content. */
  /* Every sideways scroller, not only this one.
     .scroll-x and .table-wrap carry eleven more tables between them,
     and one of those is Home's day grid -- pinned at min-width: 720px
     inside a 305px card on the DEFAULT landing report, with 415px off
     the right and nothing saying so. The same complaint the owner made
     about "TOTA", on a more-visited screen. */
  .gridwrap, .scroll-x, .table-wrap {
    scrollbar-width: thin;
    background:
      linear-gradient(to right, var(--surface) 30%, transparent) left center,
      linear-gradient(to left, var(--surface) 30%, transparent) right center,
      radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.14), transparent) left center,
      radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.14), transparent) right center;
    background-repeat: no-repeat;
    background-size: 28px 100%, 28px 100%, 12px 100%, 12px 100%;
    background-attachment: local, local, scroll, scroll;
  }
  .gridwrap::-webkit-scrollbar,
  .scroll-x::-webkit-scrollbar,
  .table-wrap::-webkit-scrollbar { height: 7px; }
  .gridwrap::-webkit-scrollbar-thumb,
  .scroll-x::-webkit-scrollbar-thumb,
  .table-wrap::-webkit-scrollbar-thumb {
    background: var(--border); border-radius: 4px;
  }
}

/* The account button is a face on a phone, a name at a desk.
 *
 * See the comment in paintHeader for the arithmetic: 158 of 343 pixels went
 * on a name that was itself clipped, and the page title ellipsised on the
 * three screens with the longest names. A 32px circle hands about 126px
 * back to the title and loses nothing -- the name, address, organisation
 * and role are all in the menu this button opens.
 */
.webhead-avatar { display: none; }

@media (max-width: 640px) {
  .webhead-who {
    max-width: none; padding: 0; gap: 0; flex: none;
  }
  /* (1,1,0) and (1,2,0): these beat anything the generic header rules say,
     so the swap does not depend on sitting at the end of the file. */
  .webhead-who #webhead-name,
  .webhead-who .webhead-caret { display: none; }
  .webhead-who .webhead-avatar {
    display: inline-grid; place-items: center; flex: none;
    width: 32px; height: 32px; border-radius: 50%;
    background: var(--accent-soft); color: var(--accent);
    font-size: 11.5px; font-weight: 700; letter-spacing: .02em;
  }
}

/* The account rows say the whole sentence on a phone.
 *
 * .acct-row-v is max-width: 58% with nowrap, overflow: hidden and an
 * ellipsis -- a value column, which is right for "Admin" or "Active". But
 * the Email Address row carries a note inside it, and at 375px that note
 * had 179 pixels to say something 205 wide: the screen read "how you sign
 * in - it cannot be cha". Clipped, never overflowing, which is why a
 * sweep for elements past the right edge called this page clean and a
 * photograph did not.
 *
 * overflow-wrap: anywhere so the address itself can break if it has to --
 * an email has no spaces to break at, and a value that cannot wrap and
 * cannot be clipped would push the row instead.
 *
 * Below the base rule, which is (0,1,0) like this one. */
@media (max-width: 640px) {
  .acct-row { align-items: flex-start; }
  .acct-row-v {
    max-width: 62%;
    white-space: normal; overflow: visible; text-overflow: clip;
    overflow-wrap: anywhere;
  }
}

/* The stretch's captures go two-up on a phone, and wrap.
 *
 * Measured by building the strip the code builds inside a real stretch
 * row: four 96px tiles and a "+N more" label come to 402 pixels inside a
 * 295px cell, with overflow: visible and flex-wrap: nowrap -- so the
 * spill escapes the cell and takes the whole document sideways.
 *
 * The phone rule for the shared strip, .wd-shots > * at (0,1,0), never
 * reached these tiles: .ad-st-shots .wd-shots > * is (0,2,0) and a media
 * query adds no specificity, so the 96px floor won wherever it was
 * written. This matches that selector and sits later in the file.
 *
 * Wrapping rather than scrolling, because the block this corrects says
 * why: "a box scrolling sideways inside a box scrolling downwards is what
 * the dialog version collected complaints for". Two rows of two in a
 * cell that already scrolls down the page.
 */
@media (max-width: 640px) {
  .ad-st-shots .wd-shots {
    flex: 1 1 auto; min-width: 0; flex-wrap: wrap; overflow: hidden;
  }
  .ad-st-shots .wd-shots > * { flex: 0 0 calc((100% - 6px) / 2); }
}

/* The Account header is one column on a phone.
 *
 * .acct-top is a two-item flex row -- the title block, and the "Sign out
 * of all devices" checkbox with the Sign out button -- and nothing ever
 * gave it flex-wrap. The phone rule somebody already wrote tells
 * .acct-head-out to be width: 100%, which it cannot be while its parent
 * is nowrap: instead of dropping to its own line the pair becomes the
 * biggest shrink target in the row and takes about 199 of the 343 pixels,
 * leaving "Account" and "Your profile and sign-in details." about 124 and
 * three lines to say it in, beside a red Sign out button.
 *
 * One declaration, and the width: 100% below it then does what it reads
 * as. */
@media (max-width: 640px) {
  .acct-top { flex-wrap: wrap; }
}

/* The per-user override editor, on a phone.
 *
 * "Configure per User" and "Review Overrides" open the same dialog. It is
 * built correctly as .modal-back > .modal.ovr-modal and fits the screen at
 * 343x587 -- and everything inside it was laid out for a desk. Measured at
 * 375x667:
 *
 *   .ovr-head   289 wide and 285 TALL: a nowrap row with a .ovr-tally at
 *               flex: 0 0 auto taking 177 of it, so the title and its
 *               paragraph were squeezed into roughly 105px and stacked a
 *               word at a time down half the dialog.
 *   .users-search  43 pixels wide. Its row already says flex-wrap: wrap
 *               (line 6683), but .ovr-tools .users-search is `flex: 1`,
 *               a basis of ZERO, so the browser has no reason to break the
 *               line and the chips beside it took everything. The wrap was
 *               there all along with nothing to act on.
 *   .ovr-list   capped at 46vh and scrolling INSIDE a dialog that is also
 *               scrolling -- two scrollbars one inside the other, which is
 *               the thing this codebase has twice collected complaints
 *               about.
 *
 * Below the base rules at 6642-6659, which these tie with on specificity.
 */
@media (max-width: 640px) {
  .ovr-head { flex-wrap: wrap; }
  .ovr-tally { flex: 1 1 100%; }
  .ovr-tools .users-search { flex: 1 1 100%; min-width: 0; }
  .ovr-list { max-height: none; overflow-y: visible; }
}

/* ── Small print, raised to a phone's floor ────────────────────────────
 *
 * Swept every screen at 320, 375 and 414: ninety pieces of text below
 * 11px, all of it the same kind -- the uppercase micro-labels that sit
 * over a figure or a column. They are drawn at 10 and 10.5px, which is a
 * considered size on a 27-inch monitor at arm's length and is not a size
 * on a phone held at reading distance. 11.5px is the floor here; they
 * keep their weight, letter-spacing and case, so nothing else about them
 * changes.
 *
 * The owner, 21 October: "also adjust font size wherever needed".
 *
 * Each selector matches or beats the rule it raises, and the block sits
 * at the end of the file so order cannot work against it either.
 * ═════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  /* "Team active today", "Worked today", "Productive" -- the caption over
     every figure on Home, Team and Productivity. */
  .stat-label { font-size: 11.5px; }
  /* Column headings on every report table. */
  .grid thead th { font-size: 11.5px; }
  .users-table th { font-size: 11.5px; }
  /* The Matrix's own corner heading, which is a th by another name. */
  .mx-corner { font-size: 11.5px; }
  /* "HIGH" / "MODERATE" / "LOW" against a capture's activity. */
  .band { font-size: 11px; }
  /* "NOT ACTIVE YET" beside a setting that is built but not switched on. */
  .badge-pending { font-size: 11px; }

  /* The four the first pass missed, and why each one needed naming:
   *
   *  .grid th        -- Attendance's header cells are not inside a <thead>,
   *                     so `.grid thead th` never reached them.
   *  .rep-stat .stat-label  -- (0,2,0) against the plain .stat-label above,
   *                     so Productivity's captions kept their 10.5px.
   *  .daygrid th     -- the hour ticks across Home's day grid, "12 AM",
   *                     "6 AM", "12 PM". The grid is 720px inside its own
   *                     scroller, so there is room for the larger size.
   *  .daybar-axis    -- the same ticks under a single person's bar. */
  .grid th { font-size: 11.5px; }
  .rep-stat .stat-label,
  /* Productivity's three figures use .prod-figure, a fifth .stat-label rule
     and the one the last pass still missed. Listed by name rather than
     guessed at: five rules set this class and only the computed style says
     which one a given screen is using. */
  .prod-figure .stat-label,
  .dash-stat .stat-label { font-size: 11.5px; }
  .daygrid th { font-size: 11.5px; }
  .daybar-axis { font-size: 11.5px; }
  /* The hour ticks under Home's week chart -- "12 AM", "6 AM", "12 PM".
     Its height is fixed at 12px, which would clip a larger line, so the
     box grows with the type. */
  .chart-label { font-size: 11px; height: 14px; }
  /* The last two, traced through their ancestors rather than guessed at a
     fourth time: the hour ruler across the day grid's header, and the
     "now" marker on a flagged row. Both are spans with no class of their
     own, so only the chain said which rule to raise. */
  .hourruler span { font-size: 11px; }
  .nowline b { font-size: 11px; }
}

/* ── One left edge down a settings pane ───────────────────────────────
 *
 * .srow-control is justify-content: flex-end, which is right on a desk:
 * the label is on the left of the row and the control is pinned to the
 * right of it, so a column of controls lines up down the right-hand side.
 *
 * Below 860px the row becomes a COLUMN -- label above, control under it,
 * both full width -- and flex-end then pins each control to the right of
 * its own full-width box. Because the controls are different widths, they
 * no longer line up with anything. Measured at 375px on the Tracking
 * pane: four distinct left edges (19, 124, 133 and 147) and three right
 * edges (193, 235, 324) down fourteen rows. That is the ragged column the
 * owner photographed twice and called "not alligned correctly".
 *
 * With the label above it, the control's edge is the card's edge.
 * ═════════════════════════════════════════════════════════════════════ */
@media (max-width: 860px) {
  .srow-control { justify-content: flex-start; }
  /* "Configure per User" goes in that column too.
   *
   * .ovr-strip is an inline-flex with margin-left: 12px, so where it
   * lands depends on how wide the control before it happens to be.
   * Measured on the same pane: four different right edges -- 288, 289,
   * 293 and 324 -- down one column of links, which is the second thing
   * the owner photographed. Full width and left, and it lines up with
   * the control it belongs to. */
  .srow-control .ovr-strip {
    /* (0,2,0), to match the rule at line 963 that already gives it
       flex-basis: 100% and justify-content: flex-end. A plain .ovr-strip
       here is (0,1,0) and loses to it however far down the file it sits --
       measured: the links stayed exactly where they were. */
    margin-left: 0; justify-content: flex-start;
  }
}

/* The "?" that holds a setting's explanation.
 *
 * Small, quiet, and beside the label rather than under it, so a pane of
 * thirty rows reads as thirty settings instead of thirty essays. It lights
 * while its paragraph is open, which is the only state worth showing.
 * Not .iconbtn: that class is for glyph-only tool buttons and a test
 * forbids words inside it, and this wants its own size and roundness.
 */
.srow-why {
  display: inline-grid; place-items: center;
  width: 18px; height: 18px; flex: none;
  margin-left: 7px; padding: 0;
  border: 1px solid var(--border); border-radius: 50%;
  background: none; color: var(--muted);
  font-size: 11px; font-weight: 700; line-height: 1;
  cursor: pointer; vertical-align: middle;
}
.srow-why:hover { border-color: var(--accent); color: var(--accent); }
.srow-why.on {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent);
}
.srow-why:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The label is a plain div, so it needs telling that the button sits on the
   same line as the words and the "Not active yet" badge. */
.srow-title { display: flex; align-items: center; gap: 0; flex-wrap: wrap; }
.field > label { display: inline-flex; align-items: center; }

/* ── Settings panes: tiles and form rows on a phone ───────────────────
 *
 * TILES. .stats is declared as a grid at line 319 and turned back into a
 * flex row at 1037, so the figure tiles size themselves to their
 * contents and wrap wherever they land. Measured at 375px: Productivity
 * had five tiles across THREE left edges (19, 162, 181) with five
 * different widths (73, 92, 99, 115, 134); Audit Log had four across two
 * edges with four widths. That is the ragged block the owner
 * photographed on both panes. A grid again here, so every tile is the
 * same width and the column edges line up.
 *
 * FORM ROWS. .row-form is a flex row of content-sized fields with a
 * 150px floor under each input, which is a toolbar on a desk and a
 * staircase on a phone -- three different widths down the Add Rule form.
 * Full width each, one per line.
 * ═════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  .stats {
    display: grid;
    /* 132, not 150. At 150 the two columns plus the gap wanted 312 of
       the 305 a card has at 375px, so every tile took a line of its own
       and Productivity's five became five rows of scrolling. 132 gives
       two columns of about 146, which is room for "UNPRODUCTIVE" over a
       number, and still falls back to one column at 320px. */
    grid-template-columns: repeat(auto-fit, minmax(min(132px, 100%), 1fr));
    gap: 12px;
  }
  .row-form > .field { flex: 1 1 100%; }
  /* The Audit Log's table cannot shrink to its wrapper because two of its
     four columns hold an email address, and an address has no space to
     break at. Measured: 441px of min-content inside a 305px box, with the
     ACTIVITY column cut off at the right. Breaking mid-address is ugly
     and reading half an address is useless. */
  /* Was `overflow-wrap: anywhere` on every cell here, which is what
     broke "Muralidhar Kurudi" across two lines mid-word. These
     tables stack on a phone now (see the end of this file) and have
     the room to break at spaces; the address keeps the old rule,
     because an address has no space to break at. */
  .auditwho-m { overflow-wrap: anywhere; }
  .row-form .field input, .row-form .field select { min-width: 0; }
  /* A tick box is not a text field and must not be stretched to the width
     of one; the rule it inherits that from is .field input { width: 100% }. */
  .field input[type=checkbox] { width: auto; }
}

/* The "Rows" picklist beside the count, at both ends of a table.
 *
 * Shaped like the one Home has carried all along (.home-per), because it
 * is the same control and the two sit on screens a person moves between.
 * .pagefoot is a space-between row of [count] [pager]; this goes in the
 * middle, which is where the eye already is when it reads "25 of 541".
 */
.rowsper { display: inline-flex; align-items: center; gap: 7px; flex: none; }
/* 74, not the 150 every .auditsel gets. ui.js reads the native select's
   computed min-width and writes it onto the wrapper it draws, as an INLINE
   style -- which no stylesheet rule can then undo -- so the floor has to be
   lowered here, on the select, before the upgrade reads it. A value of two
   or three digits does not need 150 pixels, and on a phone those 150 were
   what pushed "Showing 25 of 65 people" onto a line of its own. */
.rowsper .auditsel { height: 32px; padding: 0 8px; min-width: 74px; }
/* Hugging its own text, not filling the row.
   ui.js decides that for itself by measuring the native select against its
   slot, and in a flex label with nothing else in it the slot is as wide as
   the row -- so "25" came out as a 290px box on a phone, which pushed the
   count onto a line of its own. A value of two or three digits never needs
   more than this. */
.rowsper .ftsel { width: 84px; min-width: 0; flex: none; }
.rowsper .ftsel-btn { min-width: 0; }

@media (max-width: 640px) {
  /* Count, rows, pager -- three things in a row that is 305px wide. They
     wrap rather than squeeze, and the pager keeps its own line so its
     numbers stay a thumb apart. */
  .pagefoot { gap: 8px 12px; }
  .rowsper { order: 2; }
  .pagefoot .pager { order: 3; flex-basis: 100%; }
  /* The count takes what is left of the line after the picker rather than
     claiming a line of its own: "Showing 25 of 65 people" is 150px and the
     picker is 115, which is 265 of the 305 a card has. */
  .pagefoot .hint { flex: 1 1 auto; min-width: 0; }

  /* Five state chips, laid out as a grid rather than left to wrap.
     Wrapping gave 2 + 2 + 1 at three different widths down the left edge,
     which is what the owner meant by "alignment is also not good". Two
     equal columns line their edges up and read as one block. */
  .home-states {
    /* Full width first: as a flex item it was sizing to one chip, so the
       grid below it only ever had room for a single column. */
    flex: 1 1 100%; width: 100%;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(132px, 100%), 1fr));
    gap: 8px;
  }
  .home-state { width: 100%; justify-content: space-between; }
}


/* == User Management fills the room it is in ===========================
 *
 * The owner photographed this one twice and asked "what is this????".
 * Measured at his window size, 1250x525 of page:
 *
 *     .users-tablewrap   220px tall, holding 4651px of rows
 *     its max-height     calc(100vh - 330px) = 195px, so the 220px
 *                        min-height floor was what was actually showing
 *     main               466px tall, of which 138 were empty below the box
 *
 * Three rows of people in a letterbox, a scrollbar inside another
 * scrollbar, and a third of the screen below it holding nothing. The
 * magic number is the fault: 330 was measured once against a tall window
 * and is simply wrong on a short one, where it goes NEGATIVE and the
 * floor takes over.
 *
 * No height is guessed any more. The view is given the height main
 * already has, the shell passes it down, and the table wrap takes
 * whatever is left after the search row -- so the list is as tall as the
 * window allows and there is nothing underneath it. The one number left
 * is a floor, so a filtered list of two people still looks like a list.
 *
 * Only where the shell is two columns. Below 900px it stacks, and a
 * stacked card that fills the screen is a letterbox again; there the page
 * scrolls, which is what the phone rules already assume.
 * ===================================================================== */
@media (min-width: 901px) {
  #view-users { height: 100%; min-height: 0; }
  .users-shell { height: 100%; min-height: 0; }
  .users-main {
    display: flex; flex-direction: column; min-height: 0;
    align-self: stretch;
  }
  .users-tablewrap {
    flex: 1 1 auto;
    /* Beats the base rule on order, not specificity -- both are (0,1,0),
       and this file is read top to bottom. */
    max-height: none; min-height: 180px;
  }
  /* The teams card beside the list pins itself to the page, and the page
     scrolls at every width in this block -- a lot above 1150 where the
     table is not a scroller, a little below it. top: 12px is twelve pixels
     from the top of the WINDOW, which is under the bar either way.
     Measured at 1024 before this: the card sat at 12 against a 59px bar. */
  .users-teams { top: calc(var(--appbar-h, 51px) + 12px); }
}

/* Wide enough for the whole table, and then the page is the scroller.
 *
 * `position: sticky` sticks to the nearest SCROLLING ancestor, so inside a
 * wrapper that scrolls -- even one that no longer has anything to scroll
 * vertically -- the heading row just sits at the top of the table and
 * leaves with it. Taking the wrapper out of the scrolling business gives
 * PERSON TEAM ROLE back for all sixty-five rows.
 *
 * The condition is a measurement, not a preference. The five columns come
 * to 650px of min-content, and the people column is the window less the
 * rail, the teams card, the gaps and the page padding -- 494px of it. So
 * the table fits from 1144 up, and below that the wrapper keeps its
 * sideways scroll rather than pushing the page: at 1024 the page itself
 * moved 100px, which is worse than a scrollbar under a table.
 *
 * An ellipsis cannot rescue that. `overflow: hidden` does not change
 * intrinsic sizing -- a nowrap name still contributes its whole width to
 * the column's minimum -- which is why the Person column measured 253px
 * at min-content with the ellipsis in place.
 * ===================================================================== */
/* The band in between: still a scroll container, so it still needs a height.
 *
 * Caught by a review of this very block, then measured at 1024x768 and
 * 1100x768: the heading row ended up at top -929 after a scroll and the
 * teams card pinned itself 12px from the top of the WINDOW, under a 59px
 * bar. Both are worse than what was here on 14 October.
 *
 * The cause is that the cap came off at 901 while the two corrections that
 * replace it only start at 1150. Below 1150 the wrapper keeps overflow-x:
 * auto -- which forces overflow-y to auto -- so it is a scroll container
 * whose sticky heading resolves against ITSELF; and the flex chain above
 * it hands it no height to scroll inside, because #app is min-height:
 * 100vh rather than a definite height, so its 1fr row grows with the list
 * and `height: 100%` grows with the row. Nothing shrinks, the page scrolls
 * instead, and the heading rides away with it.
 *
 * It cannot be fixed by lowering the two `top:` values: sticky inside a
 * scrollport that has nothing to scroll does not reach the viewport. The
 * height has to come back -- but measured off the real bar and with a
 * floor, not the calc(100vh - 330px) that went negative on a short window
 * and started all of this.
 * ===================================================================== */
@media (min-width: 901px) and (max-width: 1149.98px) {
  .users-tablewrap {
    max-height: max(220px, calc(100vh - var(--appbar-h, 51px) - 260px));
  }
}

@media (min-width: 1150px) {
  .users-tablewrap { overflow: visible; }
  /* ...and the headings stop at the bar instead of sliding under it.
     The page is the scroller now, and the bar at the top of it is sticky
     at z-index 41 against the heading row's 1, so top: 0 put PERSON TEAM
     ROLE exactly where the bar already was. --appbar-h is the bar's own
     measured height, written by paintHeader on every draw and on every
     resize, so this follows a bar that is 59px at a desk and 46 on a
     phone without a number in the stylesheet pretending to know. */
  .users-tablewrap .users-table thead th { top: var(--appbar-h, 51px); }
}

/* The ACTIONS column stops being the one that falls off the edge.
 *
 * A table column is at least as wide as its widest unbreakable content,
 * and an address has nothing to break at: "seeduser110@fingertipplus.com"
 * held the Person column at 283px of a 748px table. On the owner's own
 * org -- longer names, a team pill, "manages nobody yet" under the role --
 * the five columns came to more than the card and the last one was cut
 * off the right with a scrollbar under it.
 *
 * What the ellipsis does and does not do, because the block above turns
 * on the difference. It does NOT lower the column's minimum: `overflow:
 * hidden` leaves intrinsic sizing alone, and Person still measured 253px
 * at min-content with this rule in place. What it does is stop the name
 * and the address from spilling out of whatever width the column is
 * finally given, so the four columns that are short, exact and clickable
 * keep theirs and Person is the one that gives way. The part that did not
 * fit is on the row's own tooltip. */
.users-name, .users-mail {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}


/* == No Refresh on a phone ============================================
 *
 * "refresh button not needed in mobile", 22 October.
 *
 * Right, and for a reason worth writing down: a phone already has pull-to-
 * refresh and a reload that costs one gesture, so the icon buys nothing --
 * while the bar it sits in is the most crowded strip on the screen at
 * 375px, sharing a row with the date, the people filter, Export and the
 * envelope.
 *
 * Hidden rather than not drawn, so repToolbar needs to know nothing about
 * the width and turning a tablet back to landscape brings it straight back
 * with no redraw.
 */
@media (max-width: 640px) {
  .rep-refresh { display: none; }
}

/* == On a phone a list table is a list ================================
 *
 * "look at this very worst" -- the Activity Log at 375px, four columns
 * squeezed into the 305 a card has. What that produced, measured:
 *
 *     the DETAILS heading wrapped to "DET" / "AIL" / "S"
 *     "Muralidhar Kurudi" wrapped to "Muralidha" / "r Kurudi"
 *     the Show button wrapped to "Sho" / "w"
 *
 * The mid-word breaks were mine: `overflow-wrap: anywhere` on every cell,
 * added when the alternative was an address holding the table open at
 * 441px inside a 305px box. It fixed the width and wrecked the words.
 *
 * Neither squeezing nor scrolling is the answer at this width. A row
 * becomes a block instead: the first column is its heading -- on most of
 * these tables the first column is the subject, the person, the
 * application, the pattern -- and each remaining cell reads as its own
 * column name beside its value. Where the first column is a kind rather
 * than a thing, the table asks for every cell to be labelled instead
 * (data-stack-head="0") and the block has no heading. Nothing is hidden,
 * nothing is cut, and no word breaks in the middle.
 *
 * Stamped by stackCells() in admin.js: from gridWrap, which is already
 * what "a list that might not fit" is called here, and by name from the
 * three builders that hang their table somewhere else -- the redactions,
 * the manual-time requests and the generated reports. Team's week, the
 * Matrix and Home's day go through .scroll-x and keep their columns: a
 * matrix read as a list is not a matrix.
 *
 * Nothing in here exists above 640px. The desk keeps its table.
 * ===================================================================== */
@media (max-width: 640px) {
  table[data-stack],
  table[data-stack] > tbody { display: block; width: 100%; min-width: 0; }
  /* The column names move onto the cells, so the heading row has nothing
     left to say -- and display: none takes it out of the accessibility
     tree as well as off the screen, which is the point: what a screen
     reader reads instead is the label on each cell, in the same words,
     beside the value it belongs to. Chrome and Firefox both expose
     ::before content, and the cell's own text is untouched either way. */
  table[data-stack] > thead { display: none; }

  table[data-stack] > tbody > tr {
    display: block;
    padding: 13px 2px;
    border-top: 1px solid var(--border);
  }
  table[data-stack] > tbody > tr:first-child { border-top: 0; padding-top: 4px; }

  table[data-stack] td {
    display: block;
    border: 0;
    padding: 0;
    width: auto;
    max-width: 100%;
    white-space: normal;
    /* Undoing the squeeze. Words break at spaces again; only the two
       things with no space in them are allowed to break anywhere, and
       they are named below. */
    overflow-wrap: break-word;
  }

  /* The row's heading: the first cell, on the tables where the first
     column is the subject -- the person, the application, the pattern.
     Those cells are left unlabelled by stackCells, which is what this
     selector reads. A table whose first column is a TYPE rather than a
     subject (the classification rules open on "app" or "url") asks for
     every cell to be labelled instead, and then there is no heading. */
  table[data-stack] td:first-child:not([data-label]) {
    font-size: 14px;
    font-weight: 600;
    line-height: 1.35;
    margin-bottom: 9px;
    min-width: 0;
  }

  /* Every other cell: the column's name, then the value. A fixed label
     column rather than a label above the value -- the eye runs down one
     edge, and these labels are short ones. */
  table[data-stack] td[data-label] {
    display: grid;
    grid-template-columns: minmax(74px, 34%) 1fr;
    gap: 4px 10px;
    align-items: baseline;
    /* Shrink-wrapped, so a pill stays a pill. As grid items these
       stretched the full width of the value column: "productive" came out
       as a 375px green band, and the No break tick box drifted into the
       middle of the row. */
    justify-items: start;
    padding: 3px 0;
    font-size: 13px;
  }
  /* Everything in the cell goes in the value column, under the first
     line of it -- not only the first child.
     The Activity cell holds two: "Signed in" and the address it was done
     to. Auto-placement put the second one in the LABEL column, where it
     wrapped to "admin@finger" / "tip.local" under the word ACTIVITY. The
     placement cursor never moves backwards, so naming the column on every
     child is what keeps them in a line down the right. */
  table[data-stack] td[data-label] > * { grid-column: 2; }
  table[data-stack] td[data-label]::before {
    content: attr(data-label);
    color: var(--muted);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
    line-height: 1.7;
  }

  /* A cell with nothing in it is a gap on a desk and a blank line here. */
  table[data-stack] td:empty { display: none; }

  /* The last column on most of these holds a button and has no heading;
     and a cell that spans the row is a disclosure, not a value. Both get
     the full width and neither gets a label. */
  table[data-stack] td[colspan] {
    margin-top: 10px;
    font-weight: 400;
    font-size: 13px;
  }
  table[data-stack] td:not([data-label]):not(:first-child) {
    margin-top: 9px;
  }
  /* .right is "put it at the end of its column"; there are no columns. */
  table[data-stack] td.right { text-align: left; }

  /* Figures stay figures: a bar, a pill or a meter sits beside its label
     rather than stretching the row. */
  table[data-stack] td[data-label] > .meter,
  table[data-stack] td[data-label] > .meter.wide { min-width: 0; width: 100%; }

  /* The two strings with nowhere to break.
     An address and a dotted key have no spaces in them, so they are the
     only things on the screen allowed to break mid-word -- which is what
     the blanket rule above them used to do to every name. */
  .auditwho-m, .users-mail, .req-sub, .auditact-on { overflow-wrap: anywhere; }
  /* The 23px indent is the width of the icon it sits under on a desk.
     In a stacked row the value column is the alignment, and the indent
     only pushes a long address nearer the edge. */
  table[data-stack] .auditact-on { margin-left: 0; }
}


/* == The Day So Far: the explanation goes behind the "?" ==============
 *
 * The owner, 22 October, photographing the Home card at 375px: "alignment
 * is also not good / i told you to use '?' everywhere for info saves a
 * lot of space". Five lines above the table were explanation -- the
 * colour key on two lines, the team-average sentence on three -- and the
 * first person did not appear until the second screenful.
 *
 * Both now sit in one block behind the "?" beside the card's title, drawn
 * by the same foldHelp() the settings rows use. Open, it reads as a
 * note: a tinted panel rather than two loose paragraphs, so it is
 * obviously the answer to the question just asked.
 * ===================================================================== */
.home-why {
  margin: 0 0 14px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-2);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* Inside the panel neither of them carries its old separator: the panel
   is the separation now, and a rule across the bottom of a tinted box is
   a line drawn on a line. */
.home-why .home-summary {
  margin: 0; padding-bottom: 0; border-bottom: 0;
}
.home-why .home-key { gap: 10px 16px; }
/* The "?" sits on the title's own baseline rather than above it: an h3 is
   17px here and the button is 18, so without this it rides high. */
.home-head h3 .srow-why { vertical-align: 1px; }

@media (max-width: 640px) {
  /* The toolbar under the title is two controls, not three columns.
     Measured at 375px before this: "Order [Worked time]" and the legend
     were laid out as a space-between row that wrapped, which put the
     order picker hard left and the legend hard right over two ragged
     lines. The legend has gone behind the "?" and this stops the rest
     pretending it is still a wide bar. */
  .home-head { gap: 10px; margin-bottom: 12px; }
  .home-head > * { min-width: 0; }
  .home-showing { width: 100%; justify-content: flex-start; }
  .home-why { padding: 11px 12px; }
  .home-why .home-key { font-size: 11.5px; }
}


/* == Project Tracking on a phone ======================================
 *
 * Measured at 375px before this: four summary cards at 343px wide and
 * 116-160px tall, then a two-line report stamp, then two export buttons
 * squeezed to 120px and wrapping their labels onto three lines each. Five
 * hundred and fifty pixels of chrome above the first project.
 * ===================================================================== */
@media (max-width: 640px) {
  /* Two across, like the figures on every other report. The one-column
     rule this replaces was written when the cards were three, and four of
     them down a phone is most of a screenful. */
  .pt-cards {
    grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
    gap: 12px;
    /* Each card as tall as its own contents. Stretched to the row, the
       People card -- a word and a number -- was as tall as the activity
       card beside it and carried 90px of nothing under the figure. */
    align-items: start;
  }
  .pt-card { padding: 14px 15px; }
  /* 36px of figure does not go in a 166px card: "247h 18m" came out as
     "247h" over "18m". The number is still the biggest thing on the card. */
  .pt-card .stat-big { font-size: 25px; line-height: 1.15; }
  .pt-card .stat-big small { font-size: 13px; }
  /* The glyph is decoration beside a heading that now wraps to two lines;
     it keeps its place but stops taking a third of the width. */
  .pt-card-ic { width: 28px; height: 28px; border-radius: 8px; }

  /* The stamp is small print, so it is set as small print and left where
     the eye already is rather than right-aligned over the title. */
  .pt-generated {
    text-align: left; font-size: 11.5px; line-height: 1.45; margin-bottom: 6px;
  }

  /* "Export user report" at 13px does not go in 120 pixels, and what it
     did instead was wrap into three lines and make a 67px button. Smaller
     type and tighter padding put each label on two lines, and the pair
     plus the refresh and the envelope then fit the width of the card. */
  .rep-bar-right button.with-ic {
    font-size: 12px; padding: 0 10px; line-height: 1.25;
  }
}


/* == Add a Rule: a block, at the top, with a tick that is a tick ======
 *
 * The owner photographed this one on 22 October -- "look at that image,
 * make it proper and bring it top, it is productivity settings".
 *
 * Two faults in the picture. The form was the LAST thing in the card,
 * under eighty-three rules and their pager, so adding a rule meant
 * scrolling past every rule you already had. And "No break" was built as
 * a field like the four beside it, which stretched its checkbox to the
 * field's width (`.field input { width: 100% }` -- measured 157px) and
 * put its label above it, 24 pixels below the row it belonged to.
 *
 * It is a block of its own now, above the filters, with the tick drawn as
 * a tick and its words beside it on the inputs' own line.
 * ===================================================================== */
.addrule {
  margin: 4px 0 18px;
  padding: 16px 16px 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface-2);
}
.addrule h4 {
  margin: 0 0 12px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}
.addrule .row-form { margin: 0; align-items: flex-end; }
/* The tick sits on the row, not above it: its field has no label of its
   own, so it would otherwise align to the top of a row whose other four
   members are a label over a control. */
/* flex-direction named, which is the whole of this rule.
 *
 * `.field` is a COLUMN -- a label over a control -- so in a .field the
 * two alignment properties are the other way round from what they look
 * like: align-items is the horizontal one. `align-items: flex-end` here
 * was meant to say "sit on the bottom of the row" and said "sit on the
 * right of the field" instead, which put the tick 201px in from the left
 * edge of its own row at 375px. Measured: label x=249 inside a field at
 * x=48, width 279, with justify-content reading flex-start the whole
 * time. Say the direction and both of them mean what they look like. */
.field-tick {
  display: flex; flex-direction: row;
  align-items: flex-end; justify-content: flex-start;
  flex: 0 0 auto;
}
.ticklabel {
  display: inline-flex; align-items: center; gap: 9px;
  height: 38px; padding: 0 2px;
  font-size: 13.5px; color: var(--text); cursor: pointer;
  white-space: nowrap;
}
/* A tick box is not a text field and must not be stretched to the width of
   one. TWO rules do that to it, and the second is the one that matters:
   `.field input { width: 100% }` sets the width, and
   `.row-form .field input { min-width: 150px }` sets a FLOOR under it --
   and a floor beats a width, so `width: 16px` alone left the box at 150
   and measured exactly that. min-width: 0 is what lets the width count. */
.ticklabel input[type=checkbox] {
  width: 16px; min-width: 0; height: 16px; margin: 0; flex: none;
  accent-color: var(--accent);
}
.addrule > button.primary { margin-top: 14px; }

@media (max-width: 640px) {
  .addrule { padding: 13px 12px 12px; margin-bottom: 14px; }
  /* One control a line, which is what .row-form > .field already does on a
     phone; the tick keeps its own line and loses the 38px that exists to
     line it up with inputs that are no longer beside it. */
  .field-tick { flex: 1 1 100%; }
  .ticklabel { height: auto; padding: 2px 0; }
  .addrule > button.primary { width: 100%; }
}


/* == The account row in the corner menu opens the account screen ======
 *
 * The owner, 22 October: "remove account and wire it into top right,
 * replace it". The rail's Account entry has gone on the website and this
 * row is the way in -- so it is a button, and has to look exactly like
 * the two rows it sits between while saying, on hover and on focus, that
 * it does something they do not.
 * ===================================================================== */
.whm-go {
  width: 100%;
  margin: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: 8px;
}
.whm-go:hover { background: var(--surface-2); }
.whm-go:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.whm-go .whm-lab { pointer-events: none; }

/* == A privilege explains itself behind its "?" =======================
 *
 * Twenty-odd rows on Roles & Privileges each carried a sentence under the
 * name. At 375px the row is a label column beside a control column, so
 * "Control which user screenshots this role can view" came out one word
 * per line, eight lines deep, under a title overlapping the picker beside
 * it -- photographed by the owner: "see the overlap / hide the info under
 * '?' please". The sentence is behind the "?" now; this is what is left
 * for the row it came out of.
 * ===================================================================== */
.privdesc { margin: 6px 0 0; font-size: 12.5px; line-height: 1.5; }
@media (max-width: 640px) {
  /* One column, not two: a 90px label beside a 150px picker is what put
     the title under its own control in the first place. */
  .privrow { display: block; }
  .privrow .privwords { margin-bottom: 8px; }
  .privrow select, .privrow .ftsel { width: 100%; min-width: 0; }
}


/* == A report's sentence, under the "?" that asked for it =============
 *
 * It used to open in place, which was right until packReportHeader moved
 * the title -- and the "?" with it -- up into the toolbar, leaving the
 * paragraph in the band below. Measured at 375px on Productivity: the
 * answer appeared 228 pixels under the button, past the date chips, the
 * people filter and two export buttons. "in mobile it should open just
 * below it."
 *
 * A panel, placed by script and fixed to the window, like the select
 * menus. Not a hover tooltip: there is no hover on a phone, which is the
 * width that needed this most.
 * ===================================================================== */
.hint.rep-note {
  position: fixed;
  z-index: 1300;
  margin: 0;
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 10px 30px rgba(16, 24, 40, .16), 0 2px 6px rgba(16, 24, 40, .08);
  font-size: 13px;
  line-height: 1.55;
  animation: pickin .13s ease-out;
}


/* == Theme, immediately above the way out ============================
 *
 * The corner menu that used to carry the theme is gone -- the name in
 * the header opens the account screen now -- and the owner asked for the
 * control to keep the place it had there: "just implement themes above
 * signout button".
 *
 * .acct-head-out is a flex ROW, so dropping the picker into it put it
 * beside the sign-out pair rather than over it. The group is a column
 * now, right-aligned under the heading like the pair always was.
 * ===================================================================== */
.acct-head-out {
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
}
.acct-theme-slot { display: flex; justify-content: flex-end; }
/* themePicker draws its own "Theme" label beside the buttons; on this
   screen the card around it is heading enough, and the row reads as a
   control rather than as a labelled field. */
.acct-theme-slot .theme-picker {
  display: flex; align-items: center; gap: 9px; margin: 0;
}

@media (max-width: 640px) {
  /* Full width, like the sign-out pair under it. */
  .acct-head-out { align-items: stretch; }
  .acct-theme-slot { justify-content: flex-start; }
  .acct-theme-slot .theme-picker { flex-wrap: wrap; }
}

/* == Whose time this machine is recording ==============================
 *
 * Beside the day's figures, because it is the sentence that qualifies them:
 * "Today worked 0h 5m" does not say worked by whom, and on 23 October 2026
 * the answer was somebody else for a hundred minutes with nothing on screen
 * to say so. See abRecording in admin.js.
 *
 * Quiet when it agrees with the person reading it -- it is a fact, not a
 * warning -- and loud only when the machine is filing time under somebody
 * other than whoever is signed in.
 */
.ab-recording {
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 22ch;
}
.ab-recording.mismatch {
  color: var(--warn);
  font-weight: 600;
  max-width: 34ch;
}
/* The bar is the most crowded strip in the product and this is the least
   urgent thing on it -- until it disagrees, when it is the most. */
@media (max-width: 1100px) {
  .ab-recording:not(.mismatch) { display: none; }
}

/* ── The way back in when a password is forgotten ──────────────────────── */
/* Two names, both checked against the rest of this file before being used:
   a bare .link or .success would inherit rules that already exist here. */

/* A button that reads as a link. It has to be a button rather than an anchor
   because there is nowhere to navigate to -- the form it opens is a view in
   this same document. */
.linklike {
  border: 0; background: none; padding: 0;
  font: inherit; font-weight: 600; color: var(--accent);
  cursor: pointer; text-decoration: none;
}
.linklike:hover { text-decoration: underline; }
.linklike:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* The green panel that says a link is on its way. Same shape as .error
   beside it, which only draws when it has something in it. */
.okmsg { margin-top: 10px; }
.okmsg:not(:empty) {
  padding: 10px 12px; border-radius: 10px; font-size: 13.5px; line-height: 1.5;
  background: color-mix(in srgb, var(--good) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--good) 30%, transparent);
  color: var(--good);
}
