/* ═══════════════════════════════════════════════════════════════════════════
   Weektally — shell and shared components
   ═══════════════════════════════════════════════════════════════════════════
   Colour, type and spacing come from tokens.css. Nothing here hardcodes a
   colour: swapping identity must be a token change, never a CSS rewrite.

   Responsive is structural, not bolted on. There is exactly ONE
   implementation of every surface — the legacy app carried a whole second
   mobile timesheet (`m-layout`) that drifted from the desktop one, and
   replacing that duplication is a core goal of this rebuild.
   ═══════════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
h1,h2,h3,h4 { margin: 0; font-weight: var(--fw-heavy); letter-spacing: -.015em; line-height: var(--lh-tight); }
p { margin: 0; }
button, input, select, textarea { font: inherit; color: inherit; }
[hidden] { display: none !important; }

:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-sm); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Skip link — first tab stop, invisible until focused. */
.skip-link {
  position: absolute; left: var(--sp-3); top: -60px; z-index: 100;
  background: var(--surface); color: var(--accent); padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-md); font-weight: var(--fw-bold); box-shadow: var(--elev-3);
  transition: top .15s;
}
.skip-link:focus { top: var(--sp-3); }

/* ── App frame ────────────────────────────────────────────────────────────*/
.app { display: flex; flex-direction: column; min-height: 100dvh; }

.topbar {
  display: flex; align-items: center; gap: var(--sp-3);
  height: var(--topbar-h); flex: 0 0 auto;
  padding: 0 var(--sp-4);
  background: var(--topbar); color: var(--on-topbar);
}
.topbar__spacer { flex: 1; }

.brand { display: flex; align-items: center; gap: var(--sp-2); text-decoration: none; color: inherit; }
.brand__mark { width: 26px; height: 22px; flex: 0 0 auto; }
.brand__name { font-size: var(--fs-lg); font-weight: var(--fw-heavy); letter-spacing: -.02em; }
.brand__name .lite { font-weight: var(--fw-medium); opacity: .72; }


/* Hamburger — only below the rail breakpoint */
.nav-toggle {
  display: none; background: none; border: 0; cursor: pointer;
  color: inherit; padding: var(--sp-2); margin-left: calc(var(--sp-2) * -1);
  border-radius: var(--r-md); line-height: 0;
}
.nav-toggle:hover { background: rgba(255,255,255,.1); }

/* ── Account menu ─────────────────────────────────────────────────────────*/
.account { position: relative; }
.account__btn {
  display: flex; align-items: center; gap: var(--sp-2);
  background: none; border: 0; cursor: pointer; color: var(--on-topbar);
  padding: var(--sp-1) var(--sp-2); border-radius: var(--r-md);
  font-size: var(--fs-md); font-weight: var(--fw-semi);
}
.account__btn:hover { background: rgba(255,255,255,.1); }
.avatar {
  width: 28px; height: 28px; border-radius: var(--r-pill); flex: 0 0 auto;
  display: grid; place-items: center;
  background: rgba(255,255,255,.16); color: var(--on-topbar);
  font-size: var(--fs-xs); font-weight: var(--fw-bold); letter-spacing: .02em;
}
.menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
  min-width: 216px; padding: var(--sp-2);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--elev-3);
}
.menu__label {
  padding: var(--sp-2) var(--sp-3) var(--sp-1);
  font-size: var(--fs-xs); font-weight: var(--fw-bold);
  letter-spacing: .07em; text-transform: uppercase; color: var(--dim);
}
.menu__item {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  width: 100%; text-align: left; background: none; border: 0; cursor: pointer;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md);
  font-size: var(--fs-md); font-weight: var(--fw-medium); color: var(--ink);
}
.menu__item:hover { background: var(--sunken); }
.menu__item[aria-checked="true"]::after { content: "✓"; color: var(--accent); font-weight: var(--fw-bold); }
.menu__sep { height: 1px; background: var(--line); margin: var(--sp-2) var(--sp-1); }
.menu__meta { padding: var(--sp-1) var(--sp-3) var(--sp-2); font-size: var(--fs-sm); color: var(--dim); }

/* ── Body: rail + content ─────────────────────────────────────────────────*/
.body { display: flex; flex: 1; min-height: 0; }

.rail {
  flex: 0 0 var(--rail-w); width: var(--rail-w);
  background: var(--surface); border-right: 1px solid var(--line);
  padding: var(--sp-3) var(--sp-3) var(--sp-6);
  display: flex; flex-direction: column; gap: 2px;
  overflow-y: auto;
}
.rail__link {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md);
  font-size: var(--fs-md); font-weight: var(--fw-semi);
  color: var(--ink-2); text-decoration: none;
}
.rail__link:hover { background: var(--sunken); color: var(--ink); }
.rail__link[aria-current="page"] { background: var(--accent-soft); color: var(--accent); }
.rail__sep { height: 1px; background: var(--line); margin: var(--sp-3) var(--sp-2); }
.rail__heading {
  padding: var(--sp-2) var(--sp-3) var(--sp-1);
  font-size: var(--fs-xs); font-weight: var(--fw-bold);
  letter-spacing: .07em; text-transform: uppercase; color: var(--dim);
}

.content { flex: 1; min-width: 0; padding: var(--sp-5); overflow-x: auto; }
.view-head { margin-bottom: var(--sp-4); }
.view-head h1 { font-size: var(--fs-2xl); }
.view-head p { color: var(--dim); font-size: var(--fs-md); margin-top: 2px; }
.view-head__aside { gap: 10px; align-items: center; flex-wrap: wrap; }
/* The filtered count. Mono so the digits don't reflow as they change. */
.view-head__count {
  font-family: var(--mono); font-size: var(--fs-sm);
  font-weight: var(--fw-semi); color: var(--ink-2);
}

/* Scrim behind the mobile drawer */
.scrim {
  position: fixed; inset: 0; z-index: 40; background: var(--overlay);
  opacity: 0; pointer-events: none; transition: opacity .18s;
}
.scrim.is-open { opacity: 1; pointer-events: auto; }

/* ── Modal ────────────────────────────────────────────────────────────────
   .mo = overlay/backdrop, .md = the dialog card. Names inherited from the
   legacy app's convention (components/modal.js was written against that
   vocabulary) — the rules themselves are new. */
.mo {
  position: fixed; inset: 0; z-index: 80;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-4);
  background: var(--overlay);
}
.md {
  /* Column layout with a capped height, found necessary 2026-08-18: a tall
     body (the People form's manager-links section, the holiday preview
     list) was overflowing BOTH edges of the viewport with nothing to
     scroll it — the Save button sat below the fold with no way to reach
     it. .md-body is the one part allowed to scroll; the title stays put
     above it and the actions stay put below, so Save is always visible. */
  width: 100%; max-width: 440px;
  max-height: calc(100vh - var(--sp-4) * 2);
  display: flex; flex-direction: column;
  background: var(--surface);
  border-radius: var(--r-xl);
  border-top: 4px solid var(--accent);
  box-shadow: var(--elev-4);
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
}
.md-ttl { font-size: var(--fs-xl); font-weight: 800; margin-bottom: var(--sp-3); flex: none; }
.md-body {
  font-size: var(--fs-md); color: var(--ink-2); line-height: var(--lh-loose);
  overflow-y: auto; min-height: 0;   /* min-height:0 — a flex child otherwise refuses to shrink below its content */
}
.md-acts { display: flex; justify-content: flex-end; gap: var(--sp-2); margin-top: var(--sp-5); flex: none; }

/* ── Mobile: rail becomes a drawer ────────────────────────────────────────
   Same markup, same links, same code — only the presentation changes. */
@media (max-width: 900px) {
  .nav-toggle { display: block; }
  .rail {
    position: fixed; z-index: 50; inset: var(--topbar-h) auto 0 0;
    width: 264px; max-width: 84vw;
    transform: translateX(-100%); transition: transform .2s ease;
    box-shadow: var(--elev-3);
  }
  .rail.is-open { transform: translateX(0); }
  .content { padding: var(--sp-4) var(--sp-3); }
  .brand__name { font-size: var(--fs-base); }
}

/* ── Buttons ──────────────────────────────────────────────────────────────*/
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  padding: 9px 16px; border-radius: var(--r-md); border: 1.5px solid transparent;
  font-size: var(--fs-md); font-weight: var(--fw-bold); cursor: pointer;
  background: var(--sunken); color: var(--ink); text-decoration: none;
  transition: background .12s, border-color .12s;
}
.btn:hover { background: var(--line); }
.btn--primary { background: var(--accent); color: var(--on-accent); }
.btn--primary:hover { background: var(--accent-hover); }
.btn--ghost { background: transparent; border-color: var(--line-strong); color: var(--ink-2); }
.btn--ghost:hover { background: var(--sunken); color: var(--ink); }
.btn--danger { background: var(--bad-soft); color: var(--bad); }
.btn[disabled] { opacity: .55; cursor: not-allowed; }
.btn--lg { padding: 12px 22px; font-size: var(--fs-base); }

/* ── Cards, chips, tables ─────────────────────────────────────────────────*/
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--elev-1);
  padding: var(--sp-4);
}
.card + .card { margin-top: var(--sp-3); }

.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-weight: var(--fw-bold);
  background: var(--sunken); color: var(--ink-2);
}
.chip--ok   { background: var(--ok-soft);   color: var(--ok); }
.chip--warn { background: var(--warn-soft); color: var(--warn); }
.chip--bad  { background: var(--bad-soft);  color: var(--bad); }
.chip--accent { background: var(--accent-soft); color: var(--accent); }

/* ── Filter bar ───────────────────────────────────────────────────────────
   DECISIONS.md §11a direction 2a. Four facet dropdowns on one line, chips
   below them, both above the view's content. */

.filterbar { margin-bottom: var(--sp-3); }
.filterbar__row {
  display: flex; align-items: center; gap: var(--sp-2);
  flex-wrap: wrap; margin-bottom: var(--sp-2);
}
.filterbar__chips {
  display: flex; align-items: center; gap: 6px;
  flex-wrap: wrap; margin-bottom: var(--sp-3);
}

/* ── Saved views (direction 2d) ───────────────────────────────────────────
   Underline tabs on a hairline, the active one sitting ON the rule. Scrolls
   rather than wraps: a wrapping strip shoves the table down by an
   unpredictable amount every time you save one. */
.filterviews {
  display: flex; align-items: center; gap: var(--sp-1);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--sp-3); overflow-x: auto;
}
.filterviews__tab {
  flex: 0 0 auto; cursor: pointer; background: none; border: 0;
  border-bottom: 2px solid transparent; padding: 7px 10px; margin-bottom: -1px;
  font-size: var(--fs-md); font-weight: var(--fw-semi); color: var(--ink-2);
}
.filterviews__tab:hover { color: var(--ink); }
.filterviews__tab--active {
  border-bottom-color: var(--accent); font-weight: var(--fw-bold); color: var(--accent);
}
.filterviews__tab:focus-visible { outline: none; box-shadow: var(--focus); }
.filterviews__more {
  flex: 0 0 auto; background: none; border: 0; cursor: pointer;
  color: var(--ink-2); padding: 4px 6px; border-radius: var(--r-sm);
  font-size: var(--fs-md); line-height: 1;
}
.filterviews__more:hover { background: var(--sunken); color: var(--ink); }
.filterviews__dirty { flex: 0 0 auto; margin-left: var(--sp-2); }
.filterviews__acts {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-left: auto; padding-bottom: 6px; flex: 0 0 auto;
}

.facet { position: relative; }
.facet__btn {
  cursor: pointer; border-radius: var(--r-md); padding: 6px 12px;
  font-size: var(--fs-md); font-weight: var(--fw-semi);
  border: 1.5px solid var(--line-strong);
  background: var(--surface); color: var(--ink-2);
}
.facet__btn:hover { border-color: var(--accent); }
.facet__btn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
/* Three states, and the fill is the third: idle, open, and SELECTED. */
.facet__btn--active {
  border-color: var(--accent); color: var(--accent); background: var(--accent-tint);
}
.facet__btn:focus-visible { outline: none; box-shadow: var(--focus); }

/* A filter chip is a chip plus a facet-name prefix and a remove button.
   The prefix is what makes "SE" legible without the dropdown open. */
.filter-chip { display: inline-flex; align-items: center; gap: 6px; padding: 3px 6px 3px 10px; }
.filter-chip__facet { opacity: .65; font-weight: var(--fw-semi); }
/* A chip for a facet this view doesn't honour. It stays, and it says so —
   the wording carries it, never the colour alone (DECISIONS.md §1). */
.filter-chip--inactive { background: var(--sunken); color: var(--dim); }
.chip__x {
  background: none; border: 0; color: inherit; cursor: pointer;
  font: inherit; padding: 0 2px; line-height: 1;
}
.chip__x:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-sm); }

/* Bare text button — Clear all, the empty state's clear, saved-views Reset. */
.linkbtn {
  background: none; border: 0; padding: 6px 4px; cursor: pointer;
  font: inherit; font-size: var(--fs-sm); font-weight: var(--fw-bold);
  color: var(--accent); text-decoration: underline;
}
.linkbtn--neutral { color: var(--ink-2); }
.linkbtn:focus-visible { outline: none; box-shadow: var(--focus); border-radius: var(--r-sm); }

/* §11's first consequence: a filter that hides everything needs a real empty
   state with a one-click clear, not a blank table. Below the content, so the
   table header stays put and you can see what you filtered. */
.filter-empty { margin: var(--sp-4) 0 0; font-size: var(--fs-md); color: var(--dim); }

/* ── Popover ──────────────────────────────────────────────────────────────
   z-index 30 is deliberate: BELOW .menu (60) and the modal (80). A facet
   panel must never cover the account menu or a dialog. */
.popover {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  width: var(--popover-w); padding: var(--sp-2);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--elev-3);
}
.popover--right { left: auto; right: 0; }
.popover__search {
  width: 100%; box-sizing: border-box; margin-bottom: 6px;
  border: 1.5px solid var(--line-strong); border-radius: var(--r-md);
  padding: 6px 10px; font: inherit; font-size: var(--fs-sm); color: var(--ink);
  background: var(--surface);
}
.popover__search:focus-visible { outline: none; box-shadow: var(--focus); border-color: var(--accent); }
.popover__list { max-height: var(--popover-max-h); overflow: auto; }
.popover__opt {
  display: flex; align-items: center; gap: 9px;
  padding: 6px 8px; border-radius: var(--r-md);
  cursor: pointer; font-size: var(--fs-md);
}
.popover__opt:hover { background: var(--bg); }
.popover__opt:focus-visible { outline: none; box-shadow: var(--focus); }
.popover__opt-label { flex: 1; min-width: 0; }
.popover__opt-note { font-size: var(--fs-xs); color: var(--dim); }
.popover__box {
  width: 15px; height: 15px; flex: 0 0 auto; border-radius: var(--r-xs);
  border: 1.5px solid var(--line-strong); background: var(--surface);
  color: transparent; font-size: 10px; line-height: 13px; text-align: center;
}
.popover__opt[aria-selected="true"] .popover__box {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
.popover__empty { margin: 0; padding: 6px 8px; font-size: var(--fs-sm); color: var(--dim); }

.table { width: 100%; border-collapse: collapse; font-size: var(--fs-md); }
.table th {
  text-align: left; padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-xs); font-weight: var(--fw-bold);
  letter-spacing: .05em; text-transform: uppercase; color: var(--dim);
  border-bottom: 2px solid var(--line); white-space: nowrap;
}
.table td { padding: var(--sp-3); border-bottom: 1px solid var(--line); }
.table tr:last-child td { border-bottom: 0; }
.table .num { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; }

/* ── Week grid ────────────────────────────────────────────────────────────
   Additive modifier on top of .table, scoped to the Week view's timesheet
   grid only — My Plan's table (variable month count, sticky first column)
   is a different shape and stays on plain .table. Fixes a real alignment
   bug found in production (2026-08-04): without table-layout:fixed + a
   colgroup, mixing colspan-merged project-header rows with normal 10-cell
   data rows in the same table let the browser compute different column
   widths per row, so day headers drifted out of line with the cells below
   them. Legacy avoided this the same way (index.html:128-130). */
.grid-table { table-layout: fixed; }
.grid-table th, .grid-table td { border-right: 1px solid var(--line); }
.grid-table th:last-child, .grid-table td:last-child { border-right: none; }

.grid-table .day-hdr { text-align: center; }
.grid-table .day-hdr-name { display: block; }
.grid-table .day-hdr-num {
  display: block; font-size: var(--fs-lg); font-weight: var(--fw-heavy);
  color: var(--ink); margin-top: 1px;
}

/* Hour-entry cells: no box of their own — the grid's own vertical dividers
   already draw the cell boundary, so a bordered input on top of that read
   as a "cell within a cell" (found 2026-08-04). Border-free and full-width
   instead, matching legacy's .hi exactly; a background tint on focus
   stands in for the border as the "you're editing this" signal. Padding
   moves from the <td> onto the input itself (only the hour cell, via
   .hour-cell — every other cell in the grid keeps the normal .table
   padding), so the input can fill its cell exactly. */
.grid-table td.hour-cell { padding: 0; }
.grid-input {
  width: 100%; border: none; background: transparent;
  padding: var(--sp-3);
  -moz-appearance: textfield;
}
/* type="number"'s spin-button arrows reserve space on the right even
   when not shown, so centered text sat visibly left of true-center
   (still off after the first centering fix). Removing the spinner
   entirely — not just hiding it visually — is what actually frees that
   space back up; legacy's .hi does the same (index.html:153-154). */
.grid-input::-webkit-outer-spin-button,
.grid-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* Beats .table .num's text-align:right (higher specificity, two classes)
   — the hour inputs also carry .num for the monospace/tabular-nums
   styling, but Peter wants the value centered in its cell, not right-
   aligned like the Total column. */
.grid-table .grid-input { text-align: center; }
.grid-input:focus { outline: none; background: var(--accent-soft); }
.grid-input:disabled { color: var(--dim); }
.grid-table .day-hdr.tod { background: var(--bad-soft); }
.grid-table td.tod { background: color-mix(in srgb, var(--bad-soft) 30%, transparent); }

/* ── Toast ────────────────────────────────────────────────────────────────*/
.toast-stack {
  position: fixed; z-index: 90; right: var(--sp-4); bottom: var(--sp-4);
  display: flex; flex-direction: column-reverse; gap: var(--sp-2);
  max-width: min(420px, calc(100vw - var(--sp-6)));
}
.toast {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  background: var(--ink); color: var(--surface);
  border-radius: var(--r-md); box-shadow: var(--elev-3);
  font-size: var(--fs-md); font-weight: var(--fw-semi);
  border-left: 4px solid var(--accent);
  animation: toast-in .2s ease;
}
.toast--success { border-left-color: var(--reward); }
.toast--warning { border-left-color: var(--warn); }
.toast--error   { border-left-color: var(--bad); }
.toast.is-leaving { animation: toast-out .25s ease forwards; }
@keyframes toast-in  { from { opacity:0; transform: translateY(14px); } }
@keyframes toast-out { to   { opacity:0; transform: translateY(14px); } }

/* ── Login ────────────────────────────────────────────────────────────────*/
.login {
  min-height: 100dvh; display: grid; place-items: center;
  padding: var(--sp-5); background: var(--bg);
}
.login__card {
  width: 100%; max-width: 396px; text-align: center;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-xl); box-shadow: var(--elev-2);
  padding: var(--sp-8) var(--sp-6) var(--sp-6);
}
.login__mark { width: 62px; height: 51px; color: var(--accent); }
.login__title { font-size: var(--fs-2xl); margin-top: var(--sp-4); }
.login__title .lite { font-weight: var(--fw-medium); opacity: .72; }
.login__tagline { color: var(--dim); font-size: var(--fs-md); margin-top: var(--sp-1); }
.login__actions { margin-top: var(--sp-6); }
.login__vendor { margin-top: var(--sp-6); font-size: var(--fs-sm); color: var(--dim); }
.login__msg {
  margin-top: var(--sp-4); padding: var(--sp-3);
  border-radius: var(--r-md); font-size: var(--fs-md); font-weight: var(--fw-semi);
}
.login__msg--error { background: var(--bad-soft); color: var(--bad); }
.login__msg--info  { background: var(--accent-soft); color: var(--accent); }

/* ── The reward stroke ────────────────────────────────────────────────────
   The identity's payoff: on submit, the mark's fifth stroke draws itself
   across, so the logo completing IS the week completing. Used in Phase D.
   Applied to the diagonal line only (the 5th <line> in the mark). */
.mark--tallying line:nth-child(5) {
  stroke-dasharray: 32; stroke-dashoffset: 32;
  animation: tally-stroke .5s cubic-bezier(.65,0,.35,1) forwards;
}
@keyframes tally-stroke { to { stroke-dashoffset: 0; } }

/* ── Utilities ────────────────────────────────────────────────────────────*/
.stack   { display: flex; flex-direction: column; gap: var(--sp-3); }
.row     { display: flex; align-items: center; gap: var(--sp-3); }
.row--between { justify-content: space-between; }
.wrap    { flex-wrap: wrap; }
.muted   { color: var(--dim); }
.mono    { font-family: var(--mono); }
.grow    { flex: 1; }

.btn--sm { padding: 5px 11px; font-size: var(--fs-sm); }

/* ── People view (Phase E) ───────────────────────────────────────────────
   Scoped under .people-view, same discipline Week's .grid-table modifier
   used — nothing here can leak into another view's styles. */
.people-view .people-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--sp-3); margin-bottom: var(--sp-5);
}
.people-view .person-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.people-view .person-card--inactive { opacity: .65; }
.people-view .person-card-hdr { display: flex; align-items: center; gap: var(--sp-2); }
.people-view .person-avatar {
  width: 32px; height: 32px; border-radius: var(--r-pill); flex: 0 0 auto;
  display: grid; place-items: center;
  background: var(--sunken); color: var(--ink-2);
  font-size: var(--fs-xs); font-weight: var(--fw-bold); letter-spacing: .02em;
}
.people-view .person-name { font-weight: var(--fw-bold); }
.people-view .person-badges,
.people-view .person-skills,
.people-view .person-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.people-view .person-skills:empty { display: none; }
.people-view .people-section-heading {
  font-size: var(--fs-sm); font-weight: var(--fw-bold); text-transform: uppercase;
  letter-spacing: .05em; color: var(--dim); margin: var(--sp-5) 0 var(--sp-2);
}

/* ── My Plan ──────────────────────────────────────────────────────────────
   Everything here exists to stop the Months ⇄ Weeks toggle moving the page.
   Both zooms render different text at every level — the selects' option
   labels, the hint sentence, and the column headers (weeks carry a second
   date line) — so left alone, toggling reflowed the toolbar horizontally and
   the table vertically. Sizes are fixed to the WIDER/TALLER of the two
   variants so neither zoom is the one that looks cramped. */
.myplan-sel {
  padding: var(--sp-1) var(--sp-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--surface); color: inherit;
  /* Sized for "12 months" / "0 months back" — the longer of each pair. */
  min-width: 8.5rem;
}
.myplan-sel--wide { min-width: 11rem; }

/* Reserves the taller sentence's height so the table below never jumps. */
.myplan-hint { min-height: 2.4em; display: flex; align-items: center; }

/* Column widths come from the colgroup, not from the text, so 'AUG-26' and
   'W32'/'3 AUG' can't resize the grid between zooms. */
.myplan-grid { table-layout: fixed; }

/* One header height for both zooms, with the label vertically centred inside
   it — so a single-line month label sits in the middle rather than riding at
   the top of a two-line box. Weeks fill the same box with their date line. */
.myplan-grid thead th { height: 3.25rem; vertical-align: middle; }

.myplan-sub { font-weight: var(--fw-normal); display: block; }

/* The ✎ on the Leave row — the only editable row on this screen. */
.myplan-edit-hint {
  color: var(--accent); font-size: var(--fs-sm); font-weight: var(--fw-bold);
  cursor: help;
}

/* ── Plan board ───────────────────────────────────────────────────────────
   Rows = project→task, columns = people, time is a stepping selector.
   Everything scoped; nothing here leaks to another view. */
.plan-toolbar { gap: var(--sp-4); align-items: center; margin-bottom: var(--sp-2); }
.plan-period { gap: var(--sp-2); align-items: center; }
.plan-period-label { font-weight: var(--fw-bold); min-width: 11rem; text-align: center; }

/* The person selector occupies the period label's slot when the orientation
   pivots, so it takes the same width — the toolbar must not reflow just
   because the stepper now steps through people. */
.plan-person-sel {
  min-width: 11rem; font-weight: var(--fw-bold); text-align: center;
  padding: var(--sp-1) var(--sp-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--surface); color: inherit;
}
.plan-range-sel {
  padding: var(--sp-1) var(--sp-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--surface); color: inherit;
  min-width: 8.5rem;
}
/* A button, not a span, since it now opens Review · Balance — the chip's own
   look is kept, only the affordance is added. */
.plan-org { margin-left: auto; cursor: pointer; font: inherit; }
.plan-org:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Always present, so filling it in never moves the grid. C3's "ambient
   headroom, never a popup" — a strip rather than a panel anchored to the
   cell, which inside a sticky scrolling table either clips or needs
   repositioning on every scroll. */
.plan-headroom {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  min-height: 2.75rem; padding: 0 var(--sp-3); margin-bottom: var(--sp-2);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--sunken); font-size: var(--fs-sm);
}
.plan-headroom--over { border-color: var(--bad); background: var(--bad-soft); }
.plan-headroom-arrow { color: var(--dim); }

.plan-scroll { padding: 0; overflow: auto; max-height: 70vh; }

/* Fixed layout so column widths come from the colgroup rather than from
   whatever names happen to be in the header — the same reason week.js and
   My Plan need it. */
.plan-grid { table-layout: fixed; }
.plan-grid thead th { position: sticky; top: 0; z-index: 2; background: var(--surface); }
.plan-grid .plan-rowhdr {
  position: sticky; left: 0; z-index: 1; background: var(--surface);
  text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.plan-grid thead .plan-rowhdr { z-index: 3; }

/* A column header is a person OR a period, depending on orientation — hence
   `col` rather than `person`. The avatar is the only person-specific part and
   simply isn't rendered for a period column. */
.plan-col-hdr { text-align: center; vertical-align: middle; height: 4rem; }
.plan-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; border-radius: var(--r-pill);
  background: var(--accent-soft); color: var(--accent);
  font-size: var(--fs-xs); font-weight: var(--fw-bold);
}
.plan-col-name {
  display: block; font-weight: var(--fw-medium); font-size: var(--fs-xs);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.plan-col-sub  { font-size: var(--fs-xs); }
.plan-col-load { display: block; font-size: var(--fs-xs); font-weight: var(--fw-bold); }
.plan-col-hdr--over  { background: var(--bad-soft); }
.plan-col-hdr--under { background: var(--warn-soft); }
.plan-col-hdr--well  { background: var(--ok-soft); }

.plan-proj-row { cursor: pointer; }
.plan-proj-cell { font-weight: var(--fw-bold); background: var(--sunken); }
.plan-proj-filler { background: var(--sunken); }
.plan-caret { color: var(--dim); }
.plan-proj-count { font-weight: var(--fw-normal); font-size: var(--fs-xs); margin-left: var(--sp-1); }
.plan-task-cell { padding-left: var(--sp-5); }

.plan-cell {
  position: relative; text-align: center; cursor: cell;
  padding: 0; height: 2.5rem; color: var(--dim);
}
.plan-cell--has { color: inherit; font-weight: var(--fw-medium); }
.plan-cell:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
.plan-cell--over  { background: var(--bad-soft); }
.plan-cell--under { background: var(--warn-soft); }
.plan-cell--well  { background: var(--ok-soft); }
.plan-cell--dragover { outline: 2px dashed var(--accent); outline-offset: -2px; }
.plan-mixed { font-size: var(--fs-xs); color: var(--dim); }

/* An archived project that still carries allocation this period: present so
   committed time stays visible, dimmed and cursor-default so it reads as
   information rather than as somewhere to type. Matches the dimming the
   people list already uses for inactive rows (.person-card--inactive). */
.plan-proj-row--archived, .plan-row--archived { opacity: .65; }
.plan-proj-row--archived .plan-proj-cell { font-style: italic; }
.plan-ro {
  text-align: center; padding: 0; height: 2.5rem;
  color: var(--dim); cursor: default;
}

/* Borderless and full-bleed: a bordered input inside a bordered cell reads as
   a cell within a cell, and a fixed pixel width overflows a fixed-layout
   column. Both lessons already paid for in week.js's .grid-input. */
.plan-input {
  width: 100%; height: 100%; border: 0; padding: 0; text-align: center;
  font: inherit; color: inherit; background: var(--accent-soft);
}
.plan-input:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
.plan-input::-webkit-outer-spin-button,
.plan-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Revealed on hover/focus so the grid isn't peppered with grips at rest. */
.plan-handle {
  position: absolute; right: 0; bottom: 0; width: 8px; height: 8px;
  background: var(--accent); cursor: crosshair; opacity: 0;
  touch-action: none;
}
.plan-cell:hover .plan-handle, .plan-cell:focus .plan-handle { opacity: 1; }

.plan-sum-hdr, .plan-sum { text-align: center; font-weight: var(--fw-bold); }
.plan-footrow td { position: sticky; bottom: 0; background: var(--surface); font-weight: var(--fw-bold); }
.plan-foot-cell { text-align: center; }
.plan-over  { color: var(--bad); }
.plan-under { color: var(--warn); }
.plan-well  { color: var(--ok); }
.plan-empty { color: var(--dim); }

/* ── Projects — master-detail per wireframes.html §6 ──────────────────────
   Narrow list column left, per-project detail right. The first build's card
   grid (.projects-grid/.project-card) was deleted in the wireframe rework. */
.projects-layout {
  display: grid; grid-template-columns: 300px 1fr; gap: var(--sp-4);
  align-items: start;
}
/* Grid items default to min-width:auto, so the wide schedule/needs tables
   stretched the column instead of scrolling inside their own container —
   pushing the detail's tab strip off-screen. min-width:0 is what lets
   overflow:auto actually do its job here. */
.projects-layout > * { min-width: 0; }
@media (max-width: 900px) {
  .projects-layout { grid-template-columns: 1fr; }
}

.projects-master { display: flex; flex-direction: column; gap: var(--sp-2); }
.proj-list-bar { align-items: center; padding: 0 var(--sp-1); }
.proj-items { display: flex; flex-direction: column; gap: var(--sp-2); }

.proj-item {
  padding: var(--sp-3); border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); cursor: pointer;
}
.proj-item:hover { border-color: var(--accent); }
.proj-item:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
.proj-item--selected { border-color: var(--accent); background: var(--accent-soft); }
/* The wireframe's archived card: no fill, struck name, no metadata line. */
.proj-item--archived { background: transparent; opacity: .75; }
.proj-item-name { font-weight: var(--fw-bold); }
.proj-item-name--struck { text-decoration: line-through; color: var(--dim); }
.proj-item-meta { font-size: var(--fs-sm); margin-top: 2px; }
.proj-item-norollup { font-size: var(--fs-xs); white-space: nowrap; }
.proj-archived-toggle { padding: 0 var(--sp-1); }

/* Detail topbar: name left, text-tab strip + actions right. Tabs are the
   wireframe's dim-with-active-bold text style, not buttons. */
.proj-detail-bar { align-items: center; margin-bottom: var(--sp-2); }
.proj-detail-name { font-size: var(--fs-lg); }
.proj-tabs { display: flex; gap: var(--sp-2); align-items: center; }
.proj-tab {
  border: 0; background: none; padding: 2px 4px; cursor: pointer;
  font: inherit; color: var(--dim);
}
.proj-tab--active { color: inherit; font-weight: var(--fw-bold); }
.proj-tab:focus-visible { outline: 2px solid var(--accent); border-radius: var(--r-sm); }

/* Resource needs — same fixed-layout + sticky-header discipline as the Plan
   board and My Plan, for the same reason: column widths and header height
   must not depend on the text inside them. */
.needs-scroll { padding: 0; overflow: auto; max-height: 70vh; }
.needs-grid { table-layout: fixed; }
.needs-grid thead th { position: sticky; top: 0; z-index: 2; background: var(--surface); }
.needs-grid .plan-rowhdr { position: sticky; left: 0; z-index: 1; background: var(--surface); text-align: left; }
.needs-grid thead .plan-rowhdr { z-index: 3; }

.needs-cell {
  position: relative; text-align: center; cursor: cell;
  padding: 0; height: 2.25rem;
}
.needs-cell:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
.needs-cell--dragover { outline: 2px dashed var(--accent); outline-offset: -2px; }

/* §6b's row pair: the need row is the editable one; `assigned` sits under it,
   dim and indented, carrying the gap. Tints only on assigned cells — the
   mock carries meaning by weight alone, but this app's rule is hue+glyph
   never hue alone, and never hue on the row you type into. */
.needs-row-assigned .plan-rowhdr { font-weight: var(--fw-normal); }
.needs-assigned-hdr { color: var(--dim); padding-left: var(--sp-5) !important; font-size: var(--fs-sm); }
/* nowrap: "1.6 gap .4" is one phrase and wrapping it mid-way reads as two
   separate numbers. The column width below is sized to hold it. */
.needs-acell {
  text-align: center; color: var(--dim); font-size: var(--fs-xs);
  height: 1.9rem; padding: 0; white-space: nowrap;
}
.needs-acell--staffed { background: var(--ok-soft); color: var(--ok); }
.needs-acell--short   { background: var(--bad-soft); color: var(--bad); }
.needs-acell--over    { background: var(--warn-soft); color: var(--warn); }

.needs-handle {
  position: absolute; right: 0; bottom: 0; width: 8px; height: 8px;
  background: var(--accent); cursor: crosshair; opacity: 0; touch-action: none;
}
.needs-cell:hover .needs-handle, .needs-cell:focus .needs-handle { opacity: 1; }

/* Schedule — month-span bars as the mock draws them: cell spans, tree
   indent + └ on children (lighter bar), ◆ milestone rows in the same table. */
.sched-grid { table-layout: fixed; }
.sched-grid thead th { position: sticky; top: 0; z-index: 2; background: var(--surface); }
.sched-grid .plan-rowhdr { position: sticky; left: 0; z-index: 1; background: var(--surface); text-align: left; }
.sched-grid thead .plan-rowhdr { z-index: 3; }
.sched-month { text-align: center; font-size: var(--fs-xs); }
.sched-rowhdr { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sched-empty { height: 2rem; }
.sched-bar        { background: var(--accent); border-radius: var(--r-sm); }
.sched-bar--child { background: var(--accent-soft); border-radius: var(--r-sm); }
.sched-bar--proj  { background: var(--accent-soft); border: 1px solid var(--accent); border-radius: var(--r-sm); }
.sched-ms { color: var(--dim); font-size: var(--fs-sm); }
.sched-ms-cell { text-align: center; color: var(--accent); font-weight: var(--fw-bold); }

/* ── Today — the manager landing ──────────────────────────────────────────
   Exception cards only (wireframes §4 note ①): a class with nothing wrong
   renders nothing at all, so there is no "healthy" styling to define. */
.today-cards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--sp-3); margin-bottom: var(--sp-3);
}
/* An adjacent-sibling margin from the shared card styles was landing on every
   card but the first, pushing them 12px down so the row's tops didn't line up
   and grid stretch couldn't equalise the heights. The grid's own `gap` owns
   the spacing here. */
.today-cards > .card { margin: 0; }
/* Plain uniform card, matching .person-card — no coloured left strip. The
   strip was a legacy-app habit, and it was redundant here anyway: severity
   is already carried by the glyph and by the chips on the count and pills,
   which is what satisfies the hue+glyph rule. */
.today-card { display: flex; flex-direction: column; gap: var(--sp-2); }
.today-card-head { display: flex; align-items: center; gap: var(--sp-2); }
.today-glyph { font-size: var(--fs-lg); }
.today-card--bad  .today-glyph { color: var(--bad); }
.today-card--warn .today-glyph { color: var(--warn); }
/* The cards sit in one stretching row, so their internal blocks have to line
   up or the row reads as broken. Reserving two rows of pills means the action
   and note sit at the same height on every card whether it has one item or
   four — and `align-content:start` keeps a single row hugging the top rather
   than floating in the middle of the reserved space. */
.today-pills {
  display: flex; flex-wrap: wrap; gap: 6px;
  min-height: 3.75rem; align-content: start;
}
.today-more { font-size: var(--fs-sm); align-self: center; }
.today-action { margin-top: auto; }
/* Reserved for the same reason as the pills: a card without a second line of
   note text would otherwise pull its button up out of line with its
   neighbours. */
.today-note { font-size: var(--fs-sm); margin: 0; min-height: 2.5em; }

.today-empty { display: flex; align-items: center; gap: var(--sp-4); }
.today-empty-glyph {
  font-size: var(--fs-3xl); color: var(--ok);
  width: 3rem; height: 3rem; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-pill); background: var(--ok-soft);
}
.today-empty p { margin: 4px 0 0; }

/* ── Review — variance / compliance / balance ─────────────────────────────
   Same text-tab strip as the Projects detail pane (.proj-tab): a heavier
   segmented control here would compete with the toolbars inside each view. */
.review-tabs { display: flex; gap: var(--sp-4); margin-bottom: var(--sp-3); }
.review-tab {
  background: none; border: 0; padding: 0 0 var(--sp-1);
  color: var(--dim); cursor: pointer; font: inherit;
  border-bottom: 2px solid transparent;
}
.review-tab--active { color: inherit; font-weight: var(--fw-bold); border-bottom-color: var(--accent); }
.review-tab:focus-visible { outline: 2px solid var(--accent); border-radius: var(--r-sm); }

.review-toolbar { align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.review-table td, .review-table th { vertical-align: middle; }
.review-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.review-rowhead { text-align: left; }
.review-caret { color: var(--dim); }
.review-row { cursor: pointer; }
.review-row:hover { background: var(--sunken); }
.review-child td { color: var(--dim); font-size: var(--fs-sm); }

/* Hue AND glyph, never hue alone (DECISIONS.md §1) — the ✓/⚠/✗ beside every
   percentage is what carries the state for a reader who can't separate the
   greens from the reds. */
.review-ok   { color: var(--ok); }
.review-warn { color: var(--warn); }
.review-bad  { color: var(--bad); }
.review-none { color: var(--dim); }


/* Variance chart: label · bars · figure. The bar column is the flexible one,
   so rows stay aligned however long the project names are. */
.review-chart-row {
  display: grid; grid-template-columns: 180px 1fr 150px;
  gap: var(--sp-3); align-items: center; padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line);
}
.review-chart-row:last-child { border-bottom: 0; }
.review-chart-row--drillable { cursor: pointer; }
.review-chart-row--drillable:hover { background: var(--sunken); }
.review-chart-row--drillable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.review-chart-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.review-chart-bars { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* Same height, same radius, same left edge — the two bars are one comparison,
   so anything that differs between them should be carrying meaning. What
   differs is outline vs fill: the pairing has to survive greyscale, and there
   are no --chart-N-light tokens to lighten with.
   Both are width-sized to their own value (see the note in variance.js), so
   the pill radius lands on the real ends of the bar. */
.review-bar-planned,
.review-bar-actual { height: 12px; border-radius: var(--r-pill); min-width: 3px; }
.review-bar-planned { border: 1px solid var(--dim); background: transparent; }
.review-bar-actual { display: flex; overflow: hidden; }
.review-seg { display: block; min-width: 1px; }
.review-chart-figure { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Compliance: stat cards double as filters, so they have to look pressable. */
.review-stats { gap: var(--sp-3); margin-bottom: var(--sp-3); align-items: stretch; }
/* The shared `.card + .card` margin lands on every card after the first, so
   under align-items:stretch cards 2 and 3 gave up 12px of height to their own
   margin and came out shorter than card 1. Third time this has caught a card
   row (see .today-cards) — any horizontal row of .card needs this. */
.review-stats > .card { margin: 0; }
/* Fixed width, not content width. These are three readings of one measure and
   they sit side by side, so letting "Under 30 h" make its card wider than
   "Missing" reads as a difference in importance rather than in label length. */
.review-stat {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  cursor: pointer; text-align: left; margin: 0;
  flex: 0 0 auto; width: 150px;
}
.review-stat--active { border-color: var(--accent); background: var(--accent-soft); }
.review-stat-num { font-size: var(--fs-2xl); font-weight: var(--fw-bold); line-height: 1.1; }

.compliance-grid { table-layout: fixed; }
/* Without this the button wraps to two lines and each row grows to ~90px,
   which turns an 8-week grid into a scroll. */
.compliance-grid .btn-nudge { white-space: nowrap; }
.compliance-wk { text-align: center; font-size: var(--fs-xs); }
.compliance-cell { text-align: center; font-weight: var(--fw-bold); }
.compliance-cell--ok   { color: var(--ok);   background: var(--ok-soft); }
.compliance-cell--warn { color: var(--warn); background: var(--warn-soft); }
.compliance-cell--bad  { color: var(--bad);  background: var(--bad-soft); }

/* Balance: shape only, one neutral accent. No good/bad colouring anywhere —
   see the no-judgement note in review/balance.js. */
.balance-chart { display: flex; gap: var(--sp-3); align-items: flex-end; height: 180px; }
.balance-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; height: 100%; }
.balance-track {
  flex: 1; width: 100%; display: flex; align-items: flex-end;
  background: var(--sunken); border-radius: var(--r-sm); overflow: hidden;
}
.balance-fill { width: 100%; background: var(--accent); border-radius: var(--r-sm) var(--r-sm) 0 0; }
.balance-col--beyond .balance-fill { opacity: .45; }
.balance-figure { font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.balance-label { font-size: var(--fs-xs); }
.balance-beyond td { color: var(--dim); }

/* ── First run — setup strip (Today) and coach card (My week) ─────────────
   From the "Nudge" design handoff, 2026-08-09. Both surfaces are temporary
   by design: they derive from data and delete themselves once the work they
   describe is done, so nothing here should read as permanent chrome. */

.setup-strip { margin-bottom: var(--sp-3); }
.setup-strip-head { align-items: center; gap: var(--sp-3); }
.setup-strip-title { font-size: var(--fs-lg); font-weight: var(--fw-heavy); }
.setup-strip-sub { font-size: var(--fs-md); margin: 2px 0 0; }
/* Mono, like every other counter in the app — it changes 0→1→2→3 in place
   and a proportional font makes that shuffle sideways. */
.setup-progress-label {
  font-family: var(--mono); font-size: var(--fs-sm);
  font-weight: var(--fw-semi); color: var(--ink-2); white-space: nowrap;
}

.setup-progress {
  height: 5px; background: var(--sunken);
  border-radius: var(--r-pill); overflow: hidden; margin: var(--sp-3) 0 14px;
}
.setup-progress-fill { height: 100%; background: var(--accent); transition: width .25s; }

.setup-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-3); }
.setup-step {
  border: 1px solid var(--line-strong); background: var(--bg);
  border-radius: var(--r-md); padding: var(--sp-3);
  display: flex; flex-direction: column; gap: 6px;
}
/* A done step recedes rather than celebrating: hairline border, plain
   surface. The work it points at is finished, so it should stop asking. */
.setup-step--done { border-color: var(--line); background: var(--surface); }

.setup-badge {
  width: 22px; height: 22px; flex: 0 0 auto;
  display: grid; place-items: center; border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-weight: var(--fw-bold);
  background: var(--sunken); color: var(--ink-2);
}
.setup-step--done .setup-badge { background: var(--ok-soft); color: var(--ok); }
.setup-step-title { font-size: var(--fs-md); }
/* Reserved height so the three action buttons sit on one line regardless of
   how long each body runs — same reasoning as .today-note. */
.setup-step-body { font-size: var(--fs-sm); margin: 0; min-height: 3em; }
.setup-step-btn { align-self: flex-start; }

.today-empty-glyph--setup { color: var(--dim); background: var(--sunken); }

/* Coach card — accent-tinted so it reads as guidance rather than as another
   card of content, and bordered in the accent so it survives greyscale. */
.coach-card {
  background: var(--accent-soft); border: 1px solid var(--accent);
  border-radius: var(--r-lg); padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-3);
  display: flex; align-items: center; gap: var(--sp-4);
}
.coach-title { color: var(--accent); }
.coach-steps {
  display: flex; flex-wrap: wrap; gap: 18px; margin-top: 6px;
  font-size: var(--fs-sm); color: var(--ink-2);
}
.coach-steps b { font-weight: var(--fw-bold); }
.coach-dismiss {
  flex: 0 0 auto; align-self: flex-start;
  background: transparent; border: 1.5px solid var(--accent); color: var(--accent);
}

/* Submit nudge — only rendered for a first-timer with a full unsubmitted
   week, so it never becomes the weekly wallpaper an always-on pulse would. */
.submit-hint {
  font-size: var(--fs-sm); font-weight: var(--fw-bold);
  color: var(--accent); align-self: center;
}
/* "Submit week" and "Recall" are two-word labels in a tight row; letting them
   break mid-label makes a working button look broken. */
.week-actions .btn { white-space: nowrap; }
/* color-mix rather than a literal rgba: this pulsed Fjord's teal regardless of
   theme, so a Graphite or dark user got a teal halo around a button that has
   no teal anywhere near it. The alpha has to be mixed in because a box-shadow
   spread cannot fade a solid colour on its own. */
@keyframes fr-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  50%      { box-shadow: 0 0 0 8px color-mix(in srgb, var(--accent) 0%, transparent); }
}
.submit-pulse { animation: fr-pulse 1.6s ease-in-out infinite; }
/* An indefinite pulse is exactly what this media query exists for. The hint
   text carries the same message without motion, so nothing is lost. */
@media (prefers-reduced-motion: reduce) {
  .submit-pulse { animation: none; }
  .setup-progress-fill { transition: none; }
}

/* Handoff §Responsive: below ~900px the steps stack and the header wraps,
   matching how the Today cards already behave. */
@media (max-width: 900px) {
  .setup-steps { grid-template-columns: 1fr; }
  .setup-step-body { min-height: 0; }
  .coach-card { align-items: flex-start; }
}

/* Narrow: the hint takes its own line above the buttons rather than being
   squeezed between Recall and Submit, where all three labels wrapped. It
   still reads as belonging to Submit because it sits directly above it and
   both are right-aligned. */
@media (max-width: 640px) {
  .week-actions { flex-wrap: wrap; justify-content: flex-end; }
  /* order:-1 puts the hint on the first line so Recall and Submit stay
     together on the second. Without it the full-width hint splits the two
     buttons onto separate lines and the row runs to three. */
  .submit-hint { flex: 1 0 100%; text-align: right; order: -1; }
}

/* ── My time ───────────────────────────────────────────────────────────────
   Its own classes rather than borrowing Review's: the two views answer
   different questions, and Review's bar carries a planned reference bar this
   one has no equivalent of. Sharing the markup would have coupled a personal
   summary to a manager's analysis for the sake of ~15 lines. */
.mytime-summary {
  display: flex; flex-wrap: wrap; gap: var(--sp-5);
  align-items: baseline; margin-bottom: 12px;
}
.mytime-total { display: flex; flex-direction: column; gap: 2px; }
.mytime-total-value { font-size: var(--fs-2xl); font-weight: var(--fw-bold); }
.mytime-splits { display: flex; flex-wrap: wrap; gap: var(--sp-5); }
.mytime-split { display: flex; flex-direction: column; gap: 2px; }
.mytime-split-value { font-weight: var(--fw-bold); }

.mytime-h { margin: 0 0 2px; font-size: var(--fs-md); }
.mytime-sub { margin: 0 0 var(--sp-4); font-size: var(--fs-sm); }

.mytime-group { margin-bottom: var(--sp-2); }
.mytime-group:last-child { margin-bottom: 0; }

/* The project row is a real <button> so the disclosure is keyboard-operable
   and announces its state; these rules undo the button chrome so it still
   reads as a row of the table it sits in. */
.mytime-row--project {
  width: 100%; background: none; border: 0; cursor: pointer;
  font: inherit; color: inherit; text-align: left;
  border-radius: var(--r-md); padding: var(--sp-2) var(--sp-2);
}
.mytime-row--project:hover { background: var(--sunken); }
.mytime-row--project .mytime-label { font-weight: var(--fw-bold); }
.mytime-row--project .mytime-bar { height: 14px; }

/* Indented under their project, and dimmer, so the eye reads the project
   level first and the tasks as detail belonging to it. */
.mytime-row--task .mytime-label { padding-left: var(--sp-5); }
.mytime-row--task { padding-left: var(--sp-2); padding-right: var(--sp-2); }

.mytime-count { font-weight: var(--fw-normal); font-size: var(--fs-xs); margin-left: var(--sp-1); }

/* Label | bar | figure. The label column is fixed so every bar starts at the
   same x — bars that begin at different offsets can't be compared by eye,
   which is the entire job of this page. */
.mytime-row {
  display: grid; grid-template-columns: 12rem 1fr 4.5rem;
  align-items: center; gap: var(--sp-3); padding: 3px 0;
}
.mytime-label {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-sm);
}
.mytime-track { min-width: 0; }
.mytime-bar {
  display: block; height: 12px; border-radius: var(--r-pill); min-width: 3px;
}
.mytime-figure {
  text-align: right; font-family: var(--mono);
  font-size: var(--fs-sm);
}

/* One column on a phone: a 12rem label plus a bar plus a figure cannot all
   survive 375px, and the bar is the part worth keeping full width. */
@media (max-width: 640px) {
  .mytime-row { grid-template-columns: 1fr 4.5rem; }
  .mytime-label { grid-column: 1 / -1; }
}

/* A schedule task row is now a control — it opens the date editor. Cursor and
   hover say so, and the focus ring matters because the row is reachable by Tab
   (milestone and project rows deliberately are not). */
.sched-row--editable { cursor: pointer; }
.sched-row--editable:hover .sched-rowhdr { background: var(--sunken); }
.sched-row--editable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ── Import preview ────────────────────────────────────────────────────────
   The list can run to hundreds of rows on a real plan, so it scrolls inside
   the dialog rather than pushing the Import button off-screen — the button
   must stay reachable without scrolling past everything it is about to do. */
.import-preview { margin-top: var(--sp-2); }
.import-list {
  max-height: 34vh; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--sunken);
}
.import-row {
  display: grid; grid-template-columns: 20px 1fr auto; gap: var(--sp-2);
  align-items: baseline; padding: 5px var(--sp-3);
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm);
}
.import-row:last-child { border-bottom: 0; }
.import-glyph { font-weight: var(--fw-bold); text-align: center; }
.import-glyph--add  { color: var(--ok); }
.import-glyph--upd  { color: var(--accent); }
/* Dimmer on purpose: these rows are the ones NOT being touched, and the eye
   should skip them on the way to what is. */
.import-glyph--keep { color: var(--warn); }
.import-row:has(.import-glyph--keep) { opacity: .6; }
.import-name { overflow: hidden; text-overflow: ellipsis; }
.import-detail { font-size: var(--fs-xs); white-space: nowrap; }
.import-bookable { margin-left: var(--sp-2); font-size: var(--fs-xs); }

/* Column mapping — CSV imports only. Two columns on desktop so six pickers
   don't push the preview below the fold; one on narrow screens. */
.import-map {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3); margin-bottom: var(--sp-4);
  background: var(--sunken); border-radius: var(--r-md);
}
.import-map-head {
  grid-column: 1 / -1; display: flex; justify-content: space-between;
  align-items: baseline; font-weight: var(--fw-bold); font-size: var(--fs-sm);
}
.import-map-head .muted { font-weight: var(--fw-normal); font-size: var(--fs-xs); }
.import-map-field { margin: 0; }
.import-map-note { grid-column: 1 / -1; font-size: var(--fs-xs); margin: 0; }
/* Only when the format could not be inferred — then it really is a guess. */
.import-map-note.import-map-warn { color: var(--warn); font-weight: var(--fw-bold); }
.import-map .md-err { grid-column: 1 / -1; }
@media (max-width: 560px) { .import-map { grid-template-columns: 1fr; } }

/* A compliance cell for someone who is not measured at all — an ad-hoc
   consultant. Deliberately neutral rather than green: "there is no bar" is a
   different fact from "met the bar", and colouring it as a pass would make the
   grid claim something it does not know. */
.compliance-cell--none { color: var(--dim); background: var(--sunken); }

/* The scope toggle — a lens, not a lock (Phase 1, DECISIONS §18). Chip-shaped
   like the org-load chip, because it is the same kind of thing: a compact
   statement of what you are looking at that you can act on. */
.scope-toggle { cursor: pointer; font: inherit; background: var(--accent-soft); color: var(--accent); }
.scope-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Resource-need cell badges (Phase 3). Small and quiet: most cells carry no
   badge at all, and the ones that do are saying something short — who was
   asked for, or where the ask stands. */
.needs-badge { display:block; font-size:var(--fs-xs); line-height:1.2; margin-top:2px;
               white-space:nowrap; overflow:hidden; text-overflow:ellipsis; cursor:pointer; }
.needs-badge--muted { color: var(--ink-3); }
.needs-badge--warn  { color: var(--warn); }
.needs-badge--ok    { color: var(--ok); }

/* ── The tour (DECISIONS §19) ─────────────────────────────────────────────
   The ring is pointer-events:none and its giant box-shadow provides the page
   dim — deliberately NOT a modal scrim: the page stays clickable, including
   the thing being pointed at. The card is the only interactive element. */
.tour { position: fixed; inset: 0; z-index: 90; pointer-events: none; }
.tour-ring {
  position: fixed; display: none;
  border: 2px solid var(--accent);
  border-radius: var(--r-md);
  box-shadow: 0 0 0 100vmax var(--overlay);
  transition: top .18s ease, left .18s ease, width .18s ease, height .18s ease;
}
.tour-card {
  position: fixed; display: none;
  width: min(340px, calc(100vw - 24px));
  pointer-events: auto;
  background: var(--surface);
  border-radius: var(--r-lg);
  border-top: 3px solid var(--accent);
  box-shadow: var(--elev-4);
  padding: var(--sp-4) var(--sp-4) var(--sp-3);
}
.tour-card__title { font-weight: 800; margin-bottom: var(--sp-1); }
.tour-card__body { color: var(--ink-2); font-size: var(--fs-sm); line-height: var(--lh-loose); margin: 0 0 var(--sp-3); }
.tour-card__row { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3); }
.tour-card__btns { display: flex; gap: var(--sp-2); }
.tour-card__close {
  position: absolute; top: 6px; right: 8px;
  background: none; border: 0; cursor: pointer;
  color: var(--ink-3); font-size: var(--fs-sm); padding: 4px;
}
.tour-card__close:hover { color: var(--ink-1); }

/* The topbar ? — same visual family as the account button. */
/* Its own positioned host, matching .account — openPopover() appends into
   this element and needs it non-static or the menu positions against the
   page instead of the button. Found live 2026-08-19: the button opened the
   menu (aria-expanded flipped, the DOM node existed) but rendered nowhere
   near the ?, indistinguishable from the button doing nothing at all. */
.topbar__help-wrap { position: relative; margin-right: var(--sp-3); }
.topbar__help {
  width: 30px; height: 30px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--ink-2); font-weight: 700; cursor: pointer;
}
.topbar__help:hover { color: var(--ink-1); border-color: var(--ink-3); }
