/* ============================================================
   Application shell: left menu bar + topbar + content.
   Org name pinned top, menu scrolls, user block pinned BOTTOM.
   ============================================================ */

/* The shell is exactly the window, and nothing in it may push the window
   about. A scroll that runs off the end of the content used to chain up to
   the document, and although the document has nowhere to go the browser still
   rubber-bands the whole layout — the menu appears to try to scroll with the
   work beside it (owner, 2026-09-22). `overscroll-behavior` is what stops the
   chain; the menu bar itself has never moved, and now cannot appear to. */
html, body { overscroll-behavior: none; }
#app-root { display: flex; height: 100vh; overflow: hidden; }

/* ...except while the person is zoomed in, when that same lock is what makes
   half the page unreachable (owner, 2026-10-15, item 7: "ในหน้าหลักทั้งหมด
   เมื่อ Zoom แล้ว เลื่อนหน้าจอดูไม่ได้").

   Zoomed in, the browser shows a window onto a layout that has not changed
   size, and the only way to move that window is a scroll that runs off the
   end of the innermost scroller and CHAINS out to it. Every
   `overscroll-behavior: contain` in this application — .view, .sb-menu,
   .modal-body and the takeoff menu — breaks that chain on purpose,
   and the breakage is correct at zoom 1 and only there. Measured before this
   rule (tools/uicheck/zoompan.mjs): at 200 % the furthest point reachable on
   a 1280x800 dashboard was (640, 400) and at 300 % it was (427, 267) — the
   rest of the page could not be brought on screen by any gesture. After it,
   (1280, 800) at both.

   js/app.js puts `vv-zoomed` on <html> while visualViewport.scale > 1.02 and
   takes it off again, so at zoom 1 nothing here applies and the menu still
   cannot be made to rubber-band beside a scrolled table (2026-09-22).
   The universal selector is deliberate: the rule has to reach the scrollers
   declared in css/pages/*.css as well, and a new one written next month gets
   it without being listed here. Panning, and only panning, is what it
   restores — no box starts or stops scrolling because of it. */
html.vv-zoomed, html.vv-zoomed body, html.vv-zoomed * { overscroll-behavior: auto !important; }

/* ---------- Sidebar ---------- */
.sidebar {
  /* 220px, not the old 248: the longest name in either language asks for 194,
     and every pixel the menu does not take is a pixel of table (owner,
     2026-09-16). Anything longer than the bar ellipsises rather than wrapping
     — a menu of two-line entries is harder to read than a shortened name. */
  width: 220px; flex: none;
  background: linear-gradient(178deg, var(--c-menu) 0%, var(--c-menu-deep) 100%);
  color: #cdd7e1;
  display: flex; flex-direction: column; transition: width .16s ease;
  overflow: hidden;
}
.sidebar.collapsed { width: 64px; }

/* Pinned org header (never scrolls) */
.sb-head { flex: none; padding: 16px 14px 12px; }
/* The practice's own name goes here, and a practice's own name is whatever it
   is — "บริษัท รีแอคชั่น เอ็นจิเนียริ่ง คอนซัลแตนท์ จำกัด" does not fit 248px on one
   line. Held on one nowrap line inside an overflow:hidden sidebar it was
   simply cut off mid-word, so the first thing the system showed about the
   organisation was its name wrong. It wraps instead. */
.sb-org {
  display: flex; align-items: flex-start; gap: 10px;
  color: #fff; font-weight: 800; font-size: 15.5px; letter-spacing: -.02em;
}
.sb-org .txt {
  min-width: 0; white-space: normal; overflow-wrap: anywhere; line-height: 1.28;
  padding-top: 4px;
}
.sb-org .mark {
  width: 34px; height: 34px; border-radius: 11px; flex: none;
  background: linear-gradient(150deg, var(--c-main), var(--c-main-deep));
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 12px rgba(0, 90, 158, .45), inset 0 1px 0 rgba(255,255,255,.2);
}
.sb-org .mark svg { width: 19px; height: 19px; color: var(--c-accent); }
.sidebar.collapsed .sb-org { justify-content: center; }
.sidebar.collapsed .sb-org .txt { display: none; }

/* Scrollable menu list */
.sb-menu { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 6px 10px 12px;
  /* and so does the menu: scrolling to the end of it moves nothing else */
  overscroll-behavior: contain; }
.sb-menu::-webkit-scrollbar { width: 5px; }
.sb-menu::-webkit-scrollbar-thumb { background: rgba(255,255,255,.15); border-radius: 4px; }

.sb-group {
  font-size: 10px; text-transform: uppercase; letter-spacing: .1em;
  color: #71849a; padding: 14px 9px 5px; white-space: nowrap; font-weight: 700;
}
.sidebar.collapsed .sb-group { padding: 11px 4px 3px; text-align: center; }
.sidebar.collapsed .sb-group .txt { display: none; }
.sidebar.collapsed .sb-group::after { content: "·"; }

/* Groups fold (shell.js). The page you are on never folds away, and on the
   icon-only rail every icon stays: a folded group could not be opened there. */
button.sb-group { display: flex; align-items: center; gap: 6px; width: 100%;
  border: none; background: none; cursor: pointer; font-family: inherit; text-align: left; }
button.sb-group:hover { color: #a9b8c8; }
.sb-chev { width: 12px; height: 12px; margin-left: auto; opacity: .6; transition: transform .15s; }
button.sb-group[aria-expanded="false"] .sb-chev { transform: rotate(-90deg); }
.sb-item.sec-hidden:not(.on) { display: none; }
.sidebar.collapsed .sb-item.sec-hidden { display: flex; }
.sidebar.collapsed .sb-chev { display: none; }
@media (min-width: 901px) and (max-width: 1180px) {
  #app-root:not(.nav-open) .sb-item.sec-hidden { display: flex; }
  #app-root:not(.nav-open) .sb-chev { display: none; }
}

.sb-item {
  position: relative;
  display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 8.5px 10px; border: none; border-radius: 10px; cursor: pointer;
  background: none; color: #c3cfda; font-size: 13px; text-align: left;
  white-space: nowrap; font-weight: 500; letter-spacing: -.006em;
  transition: background .12s, color .12s;
  margin-bottom: 1px;
}
.sb-item svg { width: 17px; height: 17px; flex: none; opacity: .85; }
/* The bar is narrower than it was, so a name longer than it says so with an
   ellipsis instead of being silently cut off at the edge. The tooltip carries
   the whole name either way. */
.sb-item .txt { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.sb-item:hover { background: rgba(255,255,255,.07); color: #fff; }
.sb-item.on {
  background: linear-gradient(180deg, var(--c-main), var(--c-main-deep));
  color: #fff; font-weight: 600;
  box-shadow: 0 3px 10px rgba(0, 90, 158, .4);
}
.sb-item.on svg { opacity: 1; }
.sb-item.on::before {
  content: ''; position: absolute; left: -10px; top: 7px; bottom: 7px;
  width: 3px; border-radius: 3px; background: var(--c-accent);
}
.sidebar.collapsed .sb-item { justify-content: center; padding: 9.5px 0; }
.sidebar.collapsed .sb-item .txt { display: none; }
.sidebar.collapsed .sb-item.on::before { left: -6px; }

/* Pinned user footer (never scrolls) — below the menus by design */
.sb-foot {
  flex: none; padding: 12px;
  border-top: 1px solid rgba(255,255,255,.08);
  background: rgba(0,0,0,.14);
}
.sb-user { display: flex; align-items: center; gap: 10px; }
.sb-avatar {
  width: 36px; height: 36px; border-radius: 12px; flex: none;
  background: linear-gradient(150deg, var(--c-navy), var(--c-main));
  color: #fff; font-weight: 700; font-size: 13.5px;
  display: flex; align-items: center; justify-content: center;
}
.sb-user .info { min-width: 0; flex: 1; }
.sb-user .name {
  color: #fff; font-weight: 600; font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background: none; border: none; cursor: pointer; padding: 0;
  display: block; width: 100%; text-align: left; font-family: inherit;
}
.sb-user .name:hover { color: var(--c-accent); }
.sb-user .role { font-size: 11px; color: #8a9ab0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sb-user .logout {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none;
  background: rgba(255,255,255,.06); border: none; border-radius: 9px;
  color: #9fb0c0; cursor: pointer; transition: background .12s, color .12s;
}
.sb-user .logout:hover { background: rgba(240,90,90,.2); color: #ff9c9c; }
.sb-user .logout svg { width: 15px; height: 15px; }
.sidebar.collapsed .sb-user { flex-direction: column; gap: 8px; }
.sidebar.collapsed .sb-user .info { display: none; }

/* ---------- Main column ---------- */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* Topbar: collapse button + current menu + refresh (max 1 line) */
.topbar {
  flex: none; height: 50px; background: var(--topbar-bg);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 12px; padding: 0 16px;
  position: relative; z-index: var(--z-topbar);
}
.topbar .title { font-size: 15px; font-weight: 700; letter-spacing: -.015em; color: var(--heading); flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topbar .btn-refresh {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--surface); border: 1.5px solid var(--border-strong); border-radius: 9px;
  /* the brand blue as TEXT, which is a different value per theme — #0070C0 on
     the dark surface is 3.08:1 (2026-10-15, audit item 10) */
  color: var(--c-main-fg); font-size: 12.5px; font-weight: 600;
  padding: 6px 12px; cursor: pointer; transition: background .12s, border-color .12s;
}
.topbar .btn-refresh:hover { background: rgba(0,112,192,.06); border-color: var(--c-main); }
.topbar .btn-refresh svg { width: 14px; height: 14px; }

/* The page's own top padding is published, because a table header that
   freezes against the page has to stop at the page's EDGE and not at the top
   of its padding — otherwise a strip of rows shows above the frozen header,
   which is the one thing a frozen header must never allow. */
.view { flex: 1; overflow: auto; padding: 20px 22px; --view-pad-top: 20px;
  /* the work area keeps its scrolling to itself */
  overscroll-behavior: contain; }

/* ---------- Authority matrix ---------- */
/* Two decisions per module — a ladder and a switch — drawn as one table.
   The modal is wider than the standard wide modal because forty rows of
   ladder deserve room; on a phone the table scrolls sideways inside
   .auth-wrap and the page itself never does. */
.modal.auth-modal { width: min(1150px, 96vw); }
.auth-head { display: flex; flex-wrap: wrap; gap: 10px 18px; align-items: flex-end;
  justify-content: space-between; margin-bottom: 10px; }
.auth-intro { max-width: 560px; font-size: 13px; line-height: 1.5; }
.auth-intro .muted { display: block; font-size: 12px; margin-top: 2px; }
.auth-tools { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.auth-copy { min-width: 210px; }
.auth-search input { width: 170px; }

/* a field the context decided: shown, filled, and plainly not yours to change */
.locked-field { position: relative; }
.locked-field input { padding-right: 32px; cursor: not-allowed; }
.locked-field svg { position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  width: 14px; height: 14px; color: var(--text-dim); pointer-events: none; }

/* the named-exceptions strip under the authority matrix */
.auth-vis { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.auth-vis-h { font-weight: 700; font-size: 13px; margin-bottom: 3px; }
.auth-vis > .muted { display: block; font-size: 12px; line-height: 1.5; margin-bottom: 9px; }
.auth-vis-row { display: flex; gap: 10px; align-items: flex-start; flex-wrap: wrap; }
.auth-vis-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; flex: 1; min-width: 200px; }
.auth-vis-chip { display: inline-flex; align-items: center; gap: 5px; padding: 4px 6px 4px 10px;
  border: 1px solid var(--border-strong); border-radius: 999px; font-size: 12.5px; font-weight: 600;
  background: var(--surface-2); }
.auth-vis-x { border: 0; background: transparent; cursor: pointer; font-size: 15px; line-height: 1;
  color: var(--text-dim); padding: 1px 4px; border-radius: 50%; }
.auth-vis-x:hover { color: var(--c-red); background: var(--row-hover); }
.auth-vis-add { min-width: 210px; }
.auth-wrap { max-height: min(62vh, 640px); overflow: auto;
  border: 1px solid var(--border); border-radius: 10px; }

.auth-matrix { border-collapse: separate; border-spacing: 0; width: 100%; font-size: var(--tbl-fs); }
.auth-matrix th { position: sticky; top: 0; z-index: 2; background: var(--surface-2);
  padding: 8px 6px; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-dim); border-bottom: 1px solid var(--border); white-space: nowrap; }
.auth-matrix th.am-mod, .auth-matrix td.am-mod { text-align: left; padding-left: 12px;
  position: sticky; left: 0; background: var(--surface); z-index: 1; min-width: 168px; }
.auth-matrix th.am-mod { background: var(--surface-2); z-index: 3; }
.auth-matrix td { padding: 3px 4px; text-align: center; border-bottom: 1px solid var(--border); }

.am-group td { position: sticky; left: 0; text-align: left; background: var(--surface-3);
  color: var(--text-dim); font-weight: 700; font-size: 11.5px; text-transform: uppercase;
  letter-spacing: .05em; padding: 7px 12px; }
.am-row.am-off .am-name { color: var(--text-dim); }
.am-name { font-weight: 600; }
.am-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--c-orange); margin-left: 7px; vertical-align: 2px; }
/* display:inline-block above would beat the browser's [hidden] rule */
.am-dot[hidden] { display: none; }

/* the ladder: reached rungs fill in behind the active one */
.am-step { width: 34px; height: 28px; border: 1px solid var(--border); border-radius: 7px;
  background: var(--surface); color: var(--text-dim); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; }
.am-step svg { width: 14px; height: 14px; }
.am-step:hover { border-color: var(--c-main); color: var(--c-main); }
.am-step.reached { background: color-mix(in srgb, var(--c-main) 12%, var(--surface));
  border-color: color-mix(in srgb, var(--c-main) 35%, var(--border)); color: var(--c-main); }
.am-step.on { background: var(--c-main); border-color: var(--c-main); color: #fff; }
.am-step.zero { background: var(--surface-3); color: var(--text-dim); border-style: dashed; }

.am-seg { white-space: nowrap; }
.am-seg button { font-size: 11.5px; padding: 4px 9px; }
.am-seg.disabled { opacity: .35; pointer-events: none; }

.am-shield { width: 34px; height: 28px; border: 1px dashed var(--border); border-radius: 7px;
  background: var(--surface); color: var(--text-dim); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; }
.am-shield svg { width: 14px; height: 14px; }
.am-shield:hover { border-color: var(--c-red); color: var(--c-red); }
.am-shield.on { background: var(--c-red); border-color: var(--c-red); color: #fff; border-style: solid; }

@media (max-width: 700px) {
  .auth-intro { max-width: none; }
  .auth-search input { width: 120px; }
}

/* ---------- Month calendar ---------- */
/* The wall-calendar shape: weeks as rows, Sunday first. Bars span the days
   they cover and stack into at most four lanes; the "+n" chip carries the
   overflow. Columns are minmax(0,1fr) so long titles ellipsize instead of
   widening the page — the one sin a calendar must not commit on a phone. */
.cal { border: 1px solid var(--border); border-radius: 12px; background: var(--surface); overflow: hidden; }
.cal-bar { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center;
  justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid var(--border); }
.cal-nav { display: flex; align-items: center; gap: 6px; }
.cal-title { margin: 0 4px; font-size: 16px; min-width: 150px; text-align: center; }
.cal-legend { display: flex; flex-wrap: wrap; gap: 5px 12px; font-size: 11.5px; color: var(--text-dim); }
.cal-key { display: inline-flex; align-items: center; gap: 4px; }
.cal-key svg { width: 13px; height: 13px; }
.cal-key.cal-task svg { color: var(--c-main); }
.cal-key.cal-meeting svg { color: var(--c-purple); }
.cal-key.cal-milestone svg { color: var(--c-navy); }
.cal-key.cal-training svg { color: var(--c-green); }
.cal-key.cal-deliverable svg { color: var(--c-orange); }
.cal-key.cal-issue svg { color: var(--c-red); }
.cal-empty { padding: 8px 14px; font-size: 12.5px; }

.cal-head { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  border-bottom: 1px solid var(--border); background: var(--surface-2); }
.cal-head div { padding: 6px 8px; font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-dim); }
.cal-head div:first-child, .cal-head div:last-child { color: var(--c-red); opacity: .75; }

.cal-week { position: relative; min-height: 96px; border-bottom: 1px solid var(--border); }
.cal-week:last-child { border-bottom: 0; }
.cal-bg { position: absolute; inset: 0; display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.cal-bg .cal-day { border-right: 1px solid var(--border); }
.cal-bg .cal-day:last-child { border-right: 0; }
.cal-bg .cal-day.wknd { background: var(--wknd); }
.cal-bg .cal-day.off { background: var(--surface-2); opacity: .55; }
.cal-bg .cal-day.today { box-shadow: inset 0 3px 0 var(--c-main); }

.cal-grid { position: relative; display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  grid-auto-rows: 22px; gap: 2px 0; padding-bottom: 4px; }
.cal-num { font-size: 11.5px; padding: 4px 7px 0; color: var(--text-dim); }
.cal-num.off { opacity: .45; }
.cal-num.today { color: var(--c-main); font-weight: 800; }

.cal-ev { display: flex; align-items: center; gap: 4px; margin: 0 3px; padding: 1px 6px;
  border: 0; border-radius: 5px; font-size: 11.5px; line-height: 18px; text-align: left;
  cursor: pointer; overflow: hidden; white-space: nowrap; min-width: 0; color: #fff; }
.cal-ev .cal-t { overflow: hidden; text-overflow: ellipsis; }
.cal-ev .cal-time { font-weight: 700; font-size: 10.5px; flex: none; }
.cal-ev.cont { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: 0; }
.cal-ev.tail { border-top-right-radius: 0; border-bottom-right-radius: 0; margin-right: 0; }
.cal-ev.cal-task { background: var(--c-main); }
.cal-ev.cal-task.done { background: var(--c-green); opacity: .75; }
.cal-ev.cal-task.late { background: var(--c-red); }
.cal-ev.cal-meeting { background: var(--c-purple); }
.cal-ev.cal-training { background: var(--c-green); }
.cal-ev.cal-deliverable { background: var(--c-orange); }
.cal-ev.cal-deliverable.done { opacity: .6; }
.cal-ev.cal-issue { background: var(--c-red); }

/* a milestone is a moment: diamond + label, no bar */
.cal-ev.cal-milestone { background: transparent; color: var(--text); font-weight: 600; }
.cal-dia { flex: none; width: 9px; height: 9px; background: var(--c-navy); transform: rotate(45deg); border-radius: 2px; }
.cal-ev.cal-milestone.done .cal-dia { background: var(--c-green); }
.cal-ev.cal-milestone.late .cal-dia { background: var(--c-red); }
.cal-ev.cal-milestone.late { color: var(--c-red); }

.cal-more { border: 0; background: transparent; color: var(--link); font-size: 11px;
  cursor: pointer; text-align: left; padding: 0 7px; }

/* the crowded-day list */
.cal-row { display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px 10px;
  border: 0; background: transparent; border-radius: 8px; cursor: pointer; text-align: left; font-size: 13px; }
.cal-row:hover { background: var(--row-hover); }
.cal-row svg { width: 15px; height: 15px; flex: none; }
.cal-row.cal-task svg { color: var(--c-main); }
.cal-row.cal-meeting svg { color: var(--c-purple); }
.cal-row.cal-milestone svg { color: var(--c-navy); }
.cal-row.cal-training svg { color: var(--c-green); }
.cal-row.cal-deliverable svg { color: var(--c-orange); }
.cal-row.cal-issue svg { color: var(--c-red); }
.cal-row-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 700px) {
  .cal-week { min-height: 84px; }
  .cal-ev .cal-time { display: none; }
  .cal-title { font-size: 14px; min-width: 0; }
}


/* ---------- the send: a small ceremony ---------- */
.fly-plane { position: fixed; left: 40%; top: 60%; z-index: 4000; color: var(--c-main);
  pointer-events: none; animation: fly-off 1.1s cubic-bezier(.3,.1,.3,1) forwards; }
.fly-plane svg { width: 34px; height: 34px; }
@keyframes fly-off {
  0%   { transform: translate(0, 0) rotate(8deg) scale(.8); opacity: 0; }
  15%  { opacity: 1; }
  100% { transform: translate(48vw, -42vh) rotate(24deg) scale(1.1); opacity: 0; }
}

/* ============================================================
   Motion. One vocabulary for the whole app: things ARRIVE (rise
   and fade, top-level blocks a beat apart), progress FILLS from
   zero so a bar is seen becoming its value, and live counters
   COUNT. Everything here is decoration on top of true content —
   and the prefers-reduced-motion block in responsive.css turns
   all of it off at once.
   ============================================================ */

/* Every page arrives — but quickly.
   ---------------------------------------------------------------
   These three staggers used to sum to about 570ms: a block at 230ms + its
   .34s animation, cards a beat behind that, rows behind those. Data comes
   from memory (the bootstrap loads it at sign-in and page changes touch no
   network at all), so every one of those milliseconds was decoration in
   front of content that was already there — and changing pages read as a
   flicker rather than as an arrival. The owner saw exactly that.

   The movement is kept, because it is what makes the app feel alive; it is
   now over in about 200ms and starts from half-visible rather than from
   nothing, so a page never shows blank. Under 200ms reads as "immediate";
   at 570 it reads as waiting. */
.view > * { animation: rise-in .17s cubic-bezier(.2,.7,.25,1) backwards; }
.view > *:nth-child(2) { animation-delay: 25ms; }
.view > *:nth-child(3) { animation-delay: 45ms; }
.view > *:nth-child(n+4) { animation-delay: 60ms; }
@keyframes rise-in { from { opacity: .45; transform: translateY(5px); } }

/* cards and task tiles keep a hint of a beat inside their grid */
.quest, .kpi, .hero-stat { animation: rise-in .18s cubic-bezier(.2,.7,.25,1) backwards; }
.quest:nth-child(2), .kpi:nth-child(2), .hero-stat:nth-child(2) { animation-delay: 25ms; }
.quest:nth-child(3), .kpi:nth-child(3), .hero-stat:nth-child(3) { animation-delay: 45ms; }
.quest:nth-child(n+4), .kpi:nth-child(n+4), .hero-stat:nth-child(n+4) { animation-delay: 60ms; }

/* table rows deal themselves out — the whole deal is over in a tenth of a
   second, because a register is read, not watched. */
table.data tbody tr { animation: row-in .14s ease-out backwards; }
table.data tbody tr:nth-child(1) { animation-delay: 0ms; }
table.data tbody tr:nth-child(2) { animation-delay: 12ms; }
table.data tbody tr:nth-child(3) { animation-delay: 24ms; }
table.data tbody tr:nth-child(4) { animation-delay: 36ms; }
table.data tbody tr:nth-child(5) { animation-delay: 48ms; }
/* Everything below the fifth row arrives together, AFTER the dealt rows —
   without this the bottom of the table appeared before the top had finished,
   which read as the page loading upside-down. */
table.data tbody tr:nth-child(n+6) { animation-delay: 60ms; }
@keyframes row-in { from { opacity: .5; transform: translateY(3px); } }

/* A repaint the person did not ask for does not animate. See the
   startLiveRefresh call in js/app.js: the background refresh redraws the page
   already on screen, and an arrival animation there is a flicker with no
   cause behind it. */
.view.quiet-redraw > *,
.view.quiet-redraw .quest, .view.quiet-redraw .kpi, .view.quiet-redraw .hero-stat,
.view.quiet-redraw table.data tbody tr,
.view.quiet-redraw .progress-fill,
.view.quiet-redraw .ring, .view.quiet-redraw .cal-ev, .view.quiet-redraw .detail-avatar,
.view.quiet-redraw .card, .view.quiet-redraw .section { animation: none !important; }

/* A block or a row that a background sync just replaced (js/livepatch.js,
   components/table.js patch): a soft mark that fades, once, so a colleague's
   change is seen to arrive without the page blinking. Never on every tick —
   only where something is new. */
.live-changed { animation: live-changed 1.4s ease-out; }
@keyframes live-changed { 0% { box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(--c-yellow) 22%, transparent); } 100% { box-shadow: inset 0 0 0 100vmax transparent; } }
tr.live-changed { animation: live-changed-row 1.4s ease-out; }
@keyframes live-changed-row { 0% { background: color-mix(in srgb, var(--c-yellow) 22%, transparent); } 100% { background: transparent; } }
.view.quiet-redraw table.data tbody tr.live-changed { animation: live-changed-row 1.4s ease-out !important; }
@media (prefers-reduced-motion: reduce) { .live-changed, tr.live-changed { animation: none !important; } }

/* progress is seen filling to its value, never just sitting there */
.progress-fill { animation: bar-fill .9s cubic-bezier(.25,.8,.3,1) backwards; }
@keyframes bar-fill { from { width: 0; } }

/* the dashboard ring sweeps round to its percentage */
@property --p { syntax: '<number>'; inherits: true; initial-value: 0; }
.hero .ring { animation: ring-sweep 1.1s cubic-bezier(.25,.8,.3,1) backwards; }
@keyframes ring-sweep { from { --p: 0; } }

/* the menu: the active item carries a sliding accent, hovers nudge */
.sb-item { transition: background .15s, transform .15s; position: relative; }
.sb-item:hover { transform: translateX(2px); }
.sb-item::before { content: ''; position: absolute; left: 0; top: 20%; bottom: 20%;
  width: 3px; border-radius: 2px; background: var(--c-accent);
  transform: scaleY(0); transition: transform .2s ease-out; }
.sb-item.on::before { transform: scaleY(1); }

/* buttons acknowledge the hand */
.btn, .icon-btn { transition: transform .12s, box-shadow .15s, background .15s; }
.btn:active, .icon-btn:active { transform: scale(.96); }
.btn:not(.secondary):not(.ghost):hover { box-shadow: 0 3px 10px rgba(0,0,0,.18); transform: translateY(-1px); }

/* something waiting for you quietly insists */
.bell .badge { animation: badge-breathe 3s ease-in-out infinite; }
@keyframes badge-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.18); } }

/* calendar bars grow into place */
.cal-ev { animation: cal-in .35s cubic-bezier(.2,.7,.25,1) backwards; transform-origin: left center; }
@keyframes cal-in { from { opacity: 0; transform: scaleX(.6); } }

/* the front door: a slow drift behind the card, and the card arrives */
/* The whole stack restated (background-image overrides the theme's
   shorthand wholesale), with %-sized glows so the slow drift reads as the
   light moving across the room rather than the wallpaper sliding. */
.login-screen {
  background-image:
    radial-gradient(55% 45% at 85% 0%, rgba(0, 112, 192, .35), transparent 60%),
    radial-gradient(45% 45% at 0% 100%, rgba(246, 192, 42, .14), transparent 55%),
    radial-gradient(40% 40% at 55% 60%, rgba(46, 158, 143, .10), transparent 65%),
    linear-gradient(150deg, #1d2f42 0%, #16232f 60%, #101a24 100%);
  background-size: 160% 160%;
  animation: login-drift 20s ease-in-out infinite alternate;
}
@keyframes login-drift { from { background-position: 0% 30%; } to { background-position: 100% 70%; } }
.login-card { animation: card-arrive .5s cubic-bezier(.2,.7,.25,1) backwards; }
@keyframes card-arrive { from { opacity: 0; transform: translateY(18px) scale(.985); } }

/* dropdown lists unfold instead of appearing */
.sdd-list { animation: drop-in .16s ease-out; transform-origin: top; }
@keyframes drop-in { from { opacity: 0; transform: scaleY(.92) translateY(-3px); } }

/* record pages: detail header avatar pops once */
.detail-avatar { animation: pop-in .45s cubic-bezier(.3,1.4,.4,1) backwards; }
@keyframes pop-in { from { opacity: 0; transform: scale(.7); } }

/* ---------- Sticky table column ---------- */
/* A column marked sticky stays put while the table scrolls sideways, so the
   reader never loses which row they are on. Solid backgrounds are the whole
   trick: a see-through sticky cell lets the scrolled text ghost underneath. */
table.data th.col-stick, table.data td.col-stick {
  position: sticky; left: 0; z-index: 2;
  background: var(--surface);
  /* The row's hover wash fades in over .12s (the rule near the bottom of
     this file); this cell paints its own background, so without the SAME
     transition — and on EVERY hovered row, not just clickable ones — the
     first column changes colour on a different beat from the rest of its
     row. One number, one beat. */
  transition: background-color .12s ease;
}
/* The edge shadow only exists while the column is actually floating over
   scrolled content (app.js flags the wrap). At rest the first column looks
   exactly like every other column — same borders, same background, nothing
   to mark it out. */
.x-scrolled table.data th.col-stick, .x-scrolled table.data td.col-stick {
  box-shadow: 6px 0 8px -6px rgba(0, 0, 0, .25);
}
table.data thead th.col-stick { background: var(--surface-2); z-index: 3; }
table.data tbody tr:hover td.col-stick { background: var(--row-hover); }
@media (max-width: 700px) {
  /* card layout on phones — nothing scrolls sideways, nothing to stick */
  table.data th.col-stick, table.data td.col-stick { position: static; box-shadow: none; }
}

/* ---------- File card on document records ---------- */
.file-card { border: 1px solid var(--border); border-radius: 12px; padding: 14px; margin-bottom: 16px; }
.fc-main { display: flex; gap: 16px; align-items: flex-start; }
.fc-preview { display: block; flex: none; width: 220px; border-radius: 9px; overflow: hidden;
  border: 1px solid var(--border); cursor: pointer; }
.fc-preview img { display: block; width: 100%; height: auto; }
.fc-icon { flex: none; width: 56px; height: 56px; border-radius: 12px; background: var(--surface-2);
  display: flex; align-items: center; justify-content: center; color: var(--text-dim); }
.fc-icon svg { width: 26px; height: 26px; }
.fc-meta { min-width: 0; }
.fc-name { font-weight: 700; margin-bottom: 2px; overflow-wrap: anywhere; }
.fc-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
/* The document, readable on the page it belongs to. A register whose entries
   can only be opened by saving a copy to the computer first is a filing
   cabinet, not a register (owner, 2026-09-21). */
.fc-view { margin-top: 14px; border-top: 1px dashed var(--border); padding-top: 10px; }
.fc-view-h { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.fc-view-h > span { display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.fc-view-h svg { width: 14px; height: 14px; }
.fc-view-body { background: var(--surface-3); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
/* The evidence behind a payment, looked at rather than downloaded. */
.evi-row { display: flex; align-items: center; gap: 10px; padding: 8px 6px; border-bottom: 1px solid var(--border); }
.evi-thumb {
  flex: none; width: 46px; height: 46px; padding: 0; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--border); background: var(--surface-3); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; position: relative;
}
.evi-thumb.plain { cursor: default; }
.evi-thumb img { width: 100%; height: 100%; object-fit: cover; display: none; }
.evi-thumb img.on { display: block; }
.evi-thumb img.on + i { display: none; }
.evi-thumb svg { width: 18px; height: 18px; color: var(--muted); }
.evi-thumb:hover:not(.plain) { border-color: var(--c-main); }

/* the viewer */
.fv-body { display: flex; flex-direction: column; gap: 10px; }
.fv-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.fv-name { font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }
.fv-walk { display: flex; align-items: center; gap: 8px; flex: none; font-size: 12px; color: var(--muted); }
.fv-stage { background: var(--surface-3); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
.fv-stage .fc-doc-img { max-height: min(72vh, 820px); }

.fc-pdf { background: #fff; max-height: min(70vh, 760px); overflow: auto; }
.fc-pdf canvas { display: block; width: 100%; height: auto; }
.fc-pdf-bar { display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 7px; border-top: 1px solid var(--border); font-size: 12px; color: var(--muted); }
.fc-doc { display: block; width: 100%; height: min(70vh, 760px); border: 0; background: #fff; }
.fc-doc-img { display: block; width: 100%; height: auto; max-height: min(70vh, 760px);
  object-fit: contain; background: #fff; }
/* A filed paper: A4 sheets inside a sandboxed frame, tall enough to read a
   page, scrolling for the rest (owner, 2026-10-02). */
.fc-doc-html { display: block; width: 100%; height: min(78vh, 900px); border: 0; background: #6b7684; }
.fc-doc-text { margin: 0; padding: 12px 14px; max-height: min(60vh, 520px); overflow: auto;
  font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
.fc-view-wait { padding: 26px 14px; text-align: center; color: var(--muted); font-size: 12.5px; }
@media (max-width: 720px) { .fc-doc { height: 56vh; } }

.fc-revs { margin-top: 14px; border-top: 1px dashed var(--border); padding-top: 10px; }
.fc-revs-h { font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-dim); margin-bottom: 6px; }
.fc-rev { display: flex; gap: 9px; align-items: center; width: 100%; padding: 7px 9px;
  border: 0; background: transparent; border-radius: 8px; cursor: pointer; text-align: left; font-size: 12.5px; }
.fc-rev:hover { background: var(--row-hover); }
.fc-rev svg { width: 14px; height: 14px; flex: none; color: var(--text-dim); }
.fc-rev-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fc-rev .muted { margin-left: auto; white-space: nowrap; font-size: 11.5px; }
@media (max-width: 700px) { .fc-main { flex-wrap: wrap; } .fc-preview { width: 100%; } }

/* the same file, seen from the edit window */
.form-file-strip { display: flex; gap: 12px; align-items: center; padding: 24px 16px;
  border: 1px dashed var(--border-strong); border-radius: 10px; margin-bottom: 12px; font-size: 13px;
  /* A shade lighter than the form behind it, with a whisper of the theme
     colour — the drop target has to read as a place, not as empty page. */
  background: color-mix(in srgb, var(--c-main) 5%, var(--surface-2)); }
.form-file-strip svg { width: 19px; height: 19px; color: var(--text-dim); flex: none; }
.ffs-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; margin-right: auto; }

/* ---------- Audit-log pager ---------- */
.al-size { display: flex; gap: 7px; align-items: center; font-size: 12.5px; color: var(--text-dim); }
.al-size select { width: auto; padding: 5px 8px; }
.al-pager { display: flex; gap: 6px; align-items: center; justify-content: center;
  padding: 14px 0 4px; flex-wrap: wrap; }
.al-pager .muted { margin-left: 8px; font-size: 12px; }

/* ---------- Type-to-confirm ---------- */
.type-confirm { margin-top: 12px; }
.type-confirm label { display: block; font-size: 12.5px; font-weight: 600; margin-bottom: 5px; }

/* ---------- Column resize grips ---------- */
table.data th { position: relative; }
.th-grip { position: absolute; top: 0; right: -4px; width: 9px; height: 100%;
  cursor: col-resize; z-index: 4; touch-action: none; }
.th-grip:hover::after, .th-grip:active::after { content: ''; position: absolute;
  top: 15%; bottom: 15%; right: 4px; width: 2px; border-radius: 1px; background: var(--c-main); }
table.data.resized { table-layout: fixed; }
table.data.resized td { overflow: hidden; text-overflow: ellipsis; }
.tbl-pager { justify-content: center; }
.tbl-top { display: flex; justify-content: flex-start; margin-bottom: 8px; }
@media (max-width: 700px) { .th-grip { display: none; } }

/* ---------- Drop zone on document forms ---------- */
.form-file-strip.droppable { cursor: pointer; }
.form-file-strip.drag-over { border-color: var(--c-main); border-style: solid;
  background: color-mix(in srgb, var(--c-main) 7%, var(--surface)); }

/* quick filters in list toolbars */
.qf-select { width: auto; max-width: 210px; padding: 7px 9px; font-size: 12.5px; }
.qf-select.on { border-color: var(--c-main); color: var(--c-main); font-weight: 600; }
/* a one-click narrowing with no values to choose from ("from the meeting") */
.qf-chip { padding: 6px 10px; font-size: 12.5px; }
.qf-chip.on { border-color: var(--c-main); color: var(--c-main); font-weight: 600;
  background: color-mix(in srgb, var(--c-main) 9%, var(--surface)); }

/* the audit log as a table: the diff unfolds under its row */
.al-diff-row td { background: var(--surface-2); padding: 12px 16px; }
.al-row-when { white-space: nowrap; }

/* ============================================================
   Polish pass (2026-08-30) — appearance only, no behaviour.
   ============================================================ */

/* The login screen is a drawing office: a faint blueprint grid sits over the
   drifting light. Two line weights, like grid paper — quiet enough to read
   as texture, not as content. Drawn on an overlay so the drift underneath
   keeps moving while the grid holds still, the way a real sheet would. */
.login-screen::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image:
    repeating-linear-gradient(0deg,   rgba(255,255,255,.045) 0 1px, transparent 1px 140px),
    repeating-linear-gradient(90deg,  rgba(255,255,255,.045) 0 1px, transparent 1px 140px),
    repeating-linear-gradient(0deg,   rgba(255,255,255,.018) 0 1px, transparent 1px 28px),
    repeating-linear-gradient(90deg,  rgba(255,255,255,.018) 0 1px, transparent 1px 28px);
  mask-image: radial-gradient(75% 75% at 50% 45%, #000 30%, transparent 100%);
  -webkit-mask-image: radial-gradient(75% 75% at 50% 45%, #000 30%, transparent 100%);
}
.login-card { position: relative; }

/* Keyboard users get the same ring the inputs already have. Mouse clicks
   stay clean — :focus-visible fires only for keyboard focus. */
.btn:focus-visible, .icon-btn:focus-visible, .tab:focus-visible,
.sb-item:focus-visible, .paper-card:focus-visible, .pick-row:focus-visible {
  outline: 2px solid var(--c-main); outline-offset: 2px;
}

/* Hover states arrive gently instead of snapping. */
table.data tbody tr, table.table tbody tr { transition: background-color .12s ease; }
.sb-item { transition: background-color .15s ease, color .15s ease; }
.sb-item:hover svg { opacity: 1; }
.paper-card { transition: border-color .15s ease, background-color .15s ease,
                          transform .1s ease, box-shadow .15s ease; }
.paper-card:hover:not(.empty) { box-shadow: 0 3px 12px rgba(0, 80, 150, .12); }
.icon-btn { transition: background-color .12s ease, color .12s ease, transform .08s ease; }
.icon-btn:active { transform: scale(.92); }

/* Scrollbars inside the app match the surface they live on — the browser's
   stock grey bar reads as a foreign object on a themed page. The sidebar
   already does this; the content pane and every sideways-scrolling table
   box now do too. Firefox gets the two-value shorthand. */
.view, .table-wrap, .table-scroll, .chart-scroll, .modal-body, .gt-scroll {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
.view::-webkit-scrollbar, .table-wrap::-webkit-scrollbar,
.table-scroll::-webkit-scrollbar, .modal-body::-webkit-scrollbar { width: 8px; height: 8px; }
.view::-webkit-scrollbar-thumb, .table-wrap::-webkit-scrollbar-thumb,
.table-scroll::-webkit-scrollbar-thumb, .modal-body::-webkit-scrollbar-thumb {
  background: var(--border-strong); border-radius: 8px;
  border: 2px solid transparent; background-clip: content-box;
}
.view::-webkit-scrollbar-thumb:hover, .table-wrap::-webkit-scrollbar-thumb:hover,
.table-scroll::-webkit-scrollbar-thumb:hover, .modal-body::-webkit-scrollbar-thumb:hover {
  background: var(--text-dim); background-clip: content-box; border: 2px solid transparent;
}

/* ---------- Skill rubric: what each of the five levels can do ---------- */
.rubric-list { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; }
.rb-head { font-size: 11.5px; font-weight: 700; color: var(--text-dim); margin-bottom: 2px;
  text-transform: uppercase; letter-spacing: .04em; }
.rb-row { display: flex; gap: 9px; align-items: flex-start; padding: 7px 9px; border-radius: 8px;
  background: var(--surface-2); border: 1px solid transparent; font-size: 12.5px; cursor: pointer; }
.rb-row:hover { border-color: var(--border-strong); }
/* The level actually chosen. The whole ladder stays readable — a rubric you
   can only read one rung of is a rubric nobody checks themselves against. */
.rb-row.on { background: color-mix(in srgb, var(--c-main) 12%, var(--surface));
  border-color: var(--c-main); font-weight: 600; }
.rb-n { flex: none; width: 20px; height: 20px; border-radius: 6px; display: flex;
  align-items: center; justify-content: center; font-size: 11.5px; font-weight: 700;
  background: var(--surface-3, var(--surface)); color: var(--text-dim); }
.rb-row.on .rb-n { background: var(--c-main); color: #fff; }
.rb-tx { min-width: 0; line-height: 1.45; }
.rb-none { margin-top: 9px; padding: 8px 10px; border-radius: 8px; font-size: 12px;
  background: var(--surface-2); color: var(--text-dim); border: 1px dashed var(--border); }
/* On a person's skill card: the sentence they are claiming. */
.holder .h-can { font-size: 12px; color: var(--text-dim); line-height: 1.4; margin-top: 3px; }

/* The facts an assessment derives rather than asks for — who confirms it,
   which discipline it counts under. Shown so nobody has to guess. */
.derived-note { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px;
  padding: 9px 11px; border-radius: 9px; background: var(--surface-2);
  border: 1px solid var(--border); }
.derived-note > div { display: flex; align-items: center; gap: 6px; }
.derived-note svg { width: 14px; height: 14px; color: var(--text-dim); flex: none; }
.derived-note .muted { font-size: 11.5px; }

/* ---------- Guidance for people who will not read a manual ----------
   See js/help.js. Everything here uses the theme's tokens, so it follows
   light and dark without a second copy of any rule. */

/* the ? in the top bar: a little more presence than its neighbours */
.tb-help svg { color: var(--c-main); }

/* "What would you like to do?" — the everyday jobs as big verbs */
.quick-actions .qa-grid { display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.qa-tile { display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  padding: 14px 14px 13px; border-radius: 12px; cursor: pointer; text-align: left;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  transition: border-color .12s, background-color .12s, transform .08s; font: inherit; }
.qa-tile:hover { border-color: var(--c-main);
  background: color-mix(in srgb, var(--c-main) 7%, var(--surface)); }
.qa-tile:active { transform: translateY(1px); }
.qa-tile svg { width: 22px; height: 22px; color: var(--c-main); margin-bottom: 2px; }
.qa-tile.orange svg { color: var(--c-orange, #e08a00); }
.qa-l { font-weight: 700; font-size: 14px; line-height: 1.25; }
.qa-s { font-size: 12px; color: var(--text-dim); line-height: 1.35; }

/* a brand-new account with nothing switched on */
.newcomer { display: flex; gap: 14px; align-items: flex-start;
  border-left: 4px solid var(--c-main); }
.newcomer h2 { margin: 0 0 6px; }
.newcomer p { margin: 0 0 6px; line-height: 1.5; }

/* the one-time "press ?" hint */
.first-tip { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 11px 14px; margin-bottom: 14px; border-radius: 12px; font-size: 13.5px;
  background: color-mix(in srgb, var(--c-accent, #F6C02A) 18%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--c-accent, #F6C02A) 55%, var(--border)); }
.first-tip > span { flex: 1; min-width: 220px; }
.first-tip svg { width: 18px; height: 18px; flex: none; }

/* the help panel itself: short sections, big enough to read at a glance */
.help-panel section { margin-bottom: 16px; }
.help-panel h3 { display: flex; align-items: center; gap: 8px; margin: 0 0 6px;
  font-size: 14px; }
.help-panel h3 svg { width: 16px; height: 16px; color: var(--c-main); }
.help-panel p { margin: 0; font-size: 14px; line-height: 1.55; }
.help-panel ol { margin: 0; padding-left: 22px; font-size: 14px; line-height: 1.7; }
.help-terms { display: grid; grid-template-columns: max-content 1fr; gap: 6px 14px; margin: 0; }
.help-terms dt { font-weight: 700; color: var(--c-main); }
.help-terms dd { margin: 0; font-size: 13.5px; line-height: 1.45; }
.help-ask { display: flex; gap: 10px; align-items: flex-start; padding: 11px 13px;
  border-radius: 10px; background: var(--surface-2); border: 1px solid var(--border); font-size: 13.5px; }
.help-ask svg { width: 18px; height: 18px; color: var(--c-main); flex: none; margin-top: 1px; }
.help-ask .muted { font-size: 12px; margin-top: 3px; }
.help-foot { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
@media (max-width: 520px) {
  .help-terms { grid-template-columns: 1fr; }
  .help-terms dd { margin-bottom: 6px; }
}
/* The colour picker's free colour wheel, beside the swatches (colorpicker.js) */
.cp-native { display: inline-flex; margin-left: 4px; }
.cp-native .nui-swatch { width: 34px; height: 30px; border: 1px solid var(--border-strong); border-radius: 8px; }
/* round 8: an element's form beside its detail (no scrolling to see it),
   the colour as one line, and the footings' own window */
.form-with-aside { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: 18px; align-items: start; }
.form-with-aside .fwa-aside { position: sticky; top: 0; margin: 0; }
.form-grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 9px 12px; }
.form-grid.cols-3 .form-head { grid-column: 1 / -1; margin: 6px 0 -3px; }
.form-grid.cols-3 .span2 { grid-column: 1 / -1; }
.cp-drop { position: relative; }
.cp-drop-btn { display: flex; align-items: center; gap: 8px; text-align: left; cursor: pointer; }
.cp-drop-btn .cp-drop-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cp-drop-btn svg { width: 15px; height: 15px; color: var(--text-dim); flex: none; }
.cp-drop-pop { position: absolute; z-index: 40; top: calc(100% + 4px); left: 0; width: 340px; max-width: 80vw; background: var(--surface);
  border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 10px 28px rgba(15, 30, 60, .16); padding: 10px; }
/* a pick-only dropdown reads as a select: a chevron, a pointer, no caret */
.sdd-pick .input { cursor: pointer; caret-color: transparent; padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%235d6c80' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; }
.sdd-pick .input::selection { background: transparent; }
/* four to a row for a footing; tighter rows and no grouped-number echo, so
   every element form fits a 1280 × 720 screen without scrolling */
.form-grid.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 7px 10px; }
.form-grid.cols-4 .form-head { grid-column: 1 / -1; margin: 6px 0 -3px; }
.form-grid.cols-4 .span2 { grid-column: span 2; }
@media (max-width: 640px) { .form-grid.cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.sdd-list .sdd-item { font-size: 13.5px; }
.sdd-pick .input { text-overflow: ellipsis; }
/* a field's hint beside its label, where the form has no room for a line under it */
.f-tip { display: inline-flex; align-items: center; justify-content: center; width: 15px; height: 15px; margin-left: 5px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-dim); font: italic 700 10px/1 Georgia, serif; cursor: help; vertical-align: 1px; }
.f-tip:hover, .f-tip:focus { color: var(--c-main, #0a66b3); border-color: var(--c-main, #0a66b3); outline: none; }
@media (max-width: 900px) { .form-with-aside { grid-template-columns: 1fr; } .form-with-aside .fwa-aside { position: static; order: -1; } }
@media (max-width: 640px) { .form-grid.cols-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* on paper a detail is 145 mm wide at most: a column's and a beam's then
   share a page (at full width the beam went over, leaving half a page blank) */
.rp-figure svg.tk-detail { max-width: 145mm; margin: 0 auto; display: block; }
.st-working { background: #f2a900; } .st-ready { background: #0a66b3; } .st-checked { background: #0a7a45; }
.pdoc .tk-lp > li, .rp-sheet .tk-lp > li { background: #fff; }
/* Quick design and the standards page: a calculator's head, its tabs, and
   the line that says which code the answer is worked to. */
/* ---- Structure: quick design and the standards page (redesigned 2026-09-23) ----
   A narrow input panel with short fields carrying their units, results at the
   right with the figures large. Same field style on the standards page. */
.qd { display: flex; flex-direction: column; gap: 12px; }
@keyframes qd-moved { 0% { box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(--c-yellow) 38%, transparent); } 100% { box-shadow: inset 0 0 0 100vmax transparent; } }
/* the standards page shares the field style, three across */
.ss-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 2px 18px; }
.ss-hint { font-size: 10.5px; color: var(--text-dim); line-height: 1.4; }
.ss-hint b { color: var(--heading); }
@media (max-width: 1100px) { .ss-grid { grid-template-columns: 1fr 1fr 1fr; } }
@media (max-width: 900px) { .ss-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .ss-grid { grid-template-columns: 1fr; } }
.rp-table tr.tk-total td { border-top: 0.6mm solid var(--navy); background: #f3f7fc; }
.pdoc h3 { font-size: 9.5pt; margin: 3.5mm 0 1.5mm; color: var(--navy); }

/* ---------- A project's board of quick posts (views/posts.js, 2026-09-30) ----------
   Read like a conversation, not a register: the box on top, the posts
   newest first under day headings, replies indented under their post.
   Colours from the theme's tokens only — the board is read in both themes. */
/* One column until there is room; then the posts and a side column beside
   them (JS sets .pb-wide from the width of the space the board is given — not
   the window's, the menu takes part of it; not the board's own, which is
   capped until it goes wide). Wide, it takes the whole width the page has
   (owner, 2026-09-30: the space at the side was the complaint); a post's
   own text is what is capped, so a line still reads on a 34-inch screen. */
.pb-root { max-width: 900px; }
.pb-root.pb-wide { max-width: none; display: grid; grid-template-columns: minmax(0, 1fr) clamp(290px, 22%, 380px); gap: 28px; align-items: start; }
.pb-side [data-sidecards] { display: flex; flex-direction: column; gap: 14px; }
.pb-root:not(.pb-wide) .pb-side { display: none; }
.pb-col { min-width: 0; }
.pb-col .pb-body { max-width: 110ch; }
.pb-side { position: sticky; top: 12px; display: flex; flex-direction: column; gap: 14px; max-height: calc(100vh - 110px); overflow-y: auto; padding-bottom: 6px; }
.pb-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; }
.pb-card h4 { display: flex; align-items: center; gap: 6px; margin: 0 0 10px; font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.pb-card h4 .icon { width: 13px; height: 13px; }
.pb-card p { margin: 0; font-size: 13px; }
.pb-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.pb-stats div { display: flex; flex-direction: column; align-items: flex-start; }
.pb-stats b { font-size: 22px; line-height: 1.1; color: var(--heading); font-variant-numeric: tabular-nums; }
.pb-stats span { font-size: 12px; color: var(--muted); }
.pb-last { margin-top: 10px !important; font-size: 12.5px !important; }
.pb-pinlist, .pb-people { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.pb-pinlist button, .pb-people button { width: 100%; text-align: left; background: none; border: 1px solid transparent; border-radius: var(--radius-sm); cursor: pointer; color: inherit; font: inherit; }
.pb-pinlist button { display: flex; flex-direction: column; gap: 2px; padding: 7px 8px; border-left: 3px solid var(--c-yellow); }
.pb-pinlist button:hover, .pb-people button:hover { background: var(--row-hover); }
.pb-pinhead { font-size: 11.5px; color: var(--muted); }
.pb-pinbody { font-size: 13px; line-height: 1.45; overflow-wrap: anywhere; }
.pb-people button { display: flex; align-items: center; gap: 8px; padding: 5px 6px; }
.pb-people button[aria-pressed="true"] { border-color: var(--border-strong); background: var(--row-hover); }
.pb-av.pb-av-sm { width: 24px; height: 24px; font-size: 10px; }
.pb-pname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.pb-pn { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* in the side column the search and the filters stack; above the feed they sit in a row */
.pb-wide .pb-tools { flex-direction: column; align-items: stretch; margin: 0; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; gap: 10px; }
.pb-wide .pb-search { max-width: none; flex: none; }
.pb-chips { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.pb-chip { height: 28px; padding: 0 12px; border-radius: 999px; border: 1px solid var(--border); background: transparent; color: inherit; font: inherit; font-size: 12.5px; cursor: pointer; }
.pb-chip:hover { background: var(--row-hover); }
.pb-chip[aria-pressed="true"] { background: var(--c-main); border-color: var(--c-main); color: #fff; }
.pb-whochip { display: inline-flex; align-items: center; gap: 4px; font-size: 12.5px; padding: 2px 4px 2px 10px; border-radius: 999px; background: var(--row-hover); }
.pb-who:empty { display: none; }
.pb-thread.pb-flash { animation: pb-flash 1.6s ease-out; border-radius: var(--radius-sm); }
@keyframes pb-flash { 0% { background: color-mix(in srgb, var(--c-yellow) 28%, transparent); } 100% { background: transparent; } }
@media (prefers-reduced-motion: reduce) { .pb-thread.pb-flash { animation: none; outline: 2px solid var(--c-yellow); } }
/* posts stay with the team unless they @-name an outside party (2026-10-01) */
.pb-audience { margin: 0 0 8px; font-size: 12px; display: flex; align-items: center; gap: 6px; }
.pb-audience .icon { width: 14px; height: 14px; }
.pb-compose { position: relative; }
.pb-mention { position: absolute; left: 12px; top: calc(100% - 6px); z-index: 30; width: 340px; max-width: calc(100% - 24px); max-height: 320px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 14px 34px -12px rgba(10, 30, 70, .45); padding: 4px; }
.pb-mention-h { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-dim); padding: 6px 9px 4px; }
.pb-mention button { display: flex; align-items: center; gap: 10px; width: 100%; border: none; background: none; padding: 6px 9px; border-radius: 9px;
  font: inherit; color: var(--text); text-align: left; cursor: pointer; }
.pb-mention button.on, .pb-mention button:hover { background: color-mix(in srgb, var(--c-main) 10%, var(--surface-2)); }
.pb-mention-av { flex: none; width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-weight: 700; font-size: 12.5px;
  background-size: cover !important; background-position: center !important; }
.pb-mention-av.has-photo { color: transparent; }
.pb-mention-av.party { background: linear-gradient(135deg, #f59e0b, #ef4444); }
.pb-mention-av.party.other { background: linear-gradient(135deg, #64748b, #94a3b8); }
.pb-mention-av .icon { width: 16px; height: 16px; color: #fff; }
.pb-mention-txt { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.pb-mention-txt b { font-weight: 650; color: var(--heading); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pb-mention-txt b u { text-decoration: none; background: color-mix(in srgb, var(--c-main) 22%, transparent); border-radius: 3px; }
.pb-mention-txt small { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pb-at { color: var(--tag-link); font-weight: 600; cursor: pointer; border-radius: 3px; }
.pb-at:hover { text-decoration: underline; text-underline-offset: 2px; }
.pb-party { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 650; color: var(--c-orange, #c2410c);
  background: color-mix(in srgb, var(--c-orange, #c2410c) 12%, transparent); border-radius: 999px; padding: 1px 8px; }
.pb-party .icon { width: 12px; height: 12px; }
.pb-av-out { outline: 2px dashed color-mix(in srgb, var(--c-orange, #c2410c) 60%, transparent); outline-offset: 1px; }
.pb-compose { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 14px; }
.pb-compose:focus-within { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
.pb-compose .pb-text { width: 100%; min-height: 44px; resize: none; background: transparent; box-shadow: none; }
/* the words of the box are drawn by .pb-ta-mirror behind it, so a tag can be
   coloured as it is typed; the box keeps the caret, the selection and the
   input. Both must share every metric — font, size, padding, wrapping — and
   they share them through ONE rule, below, because two rules drifted apart
   (owner, 2026-10-04: "the blinking cursor does not line up with the
   letters"). Four separate differences added up to the drift:
   - the box did not carry the body's letter-spacing (the browser's own
     stylesheet resets letter-spacing on a textarea to normal, so it has to
     be inherited by name) while the layer did — every letter a fraction
     wider, the whole line a few pixels out;
   - the layer wore a 1px transparent border the box did not, shifting its
     words by a pixel both ways;
   - a finger-screen rule bumps every .input to 16px (so iOS does not zoom)
     and did not reach the layer, so the two wrapped at different widths;
   - past the 320px cap the box scrolls and grew a scrollbar the layer never
     had, so their wrap widths differed by the width of that bar.
   Nothing here is set on inputs in general: the body's letter-spacing is
   inherited by these two only. */
.pb-ta { position: relative; }
.pb-ta .pb-text, .pb-ta-mirror { font-family: inherit; font-size: 14.5px; line-height: 1.6; letter-spacing: inherit; word-spacing: inherit;
  padding: 4px 2px; border: 0; margin: 0; white-space: pre-wrap; overflow-wrap: break-word; word-break: break-word; tab-size: 4; }
.pb-compose-sm .pb-text, .pb-compose-sm .pb-ta-mirror { font-size: 14px; }
.pb-ta-mirror { position: absolute; inset: 0; overflow: hidden; pointer-events: none; color: var(--text); }
.pb-ta .pb-text { position: relative; color: transparent; caret-color: var(--text); }
/* the box still scrolls (the layer follows its scrollTop); only the bar is
   hidden, so both layers keep the same width to wrap in */
.pb-ta .pb-text { scrollbar-width: none; }
.pb-ta .pb-text::-webkit-scrollbar { display: none; }
.pb-ta .pb-text::placeholder { color: var(--text-dim); }
.pb-ta .pb-text::selection { background: color-mix(in srgb, var(--c-main) 28%, transparent); color: transparent; }
.pb-tag { color: var(--tag-link); background: color-mix(in srgb, var(--tag-link) 14%, transparent); border-radius: 3px; }
.pb-compose .pb-text:focus { outline: none; box-shadow: none; }
.pb-compose-bar { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.pb-hint { font-size: 11.5px; color: var(--muted); margin-right: auto; }
.pb-count { font-size: 11.5px; color: var(--muted); }
.pb-compose-sm { margin: 6px 0 4px; padding: 6px 10px; }
.pb-compose-sm .pb-text { min-height: 34px; }   /* its 14px lives in the shared pair above */
.pb-tools { display: flex; align-items: center; gap: 12px; margin: 4px 0 6px; flex-wrap: wrap; }
.pb-search { position: relative; flex: 1 1 240px; max-width: 360px; }
.pb-search .icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: var(--muted); pointer-events: none; }
.pb-search .input { padding-left: 32px; height: 32px; }
.pb-sum { font-size: 12.5px; }
.pb-day { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; letter-spacing: .02em; color: var(--muted); margin: 18px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--border); }
.pb-day .icon { width: 13px; height: 13px; }
.pb-thread { border-bottom: 1px solid var(--border); padding: 4px 0 8px; }
.pb-post { display: flex; gap: 10px; padding: 8px 6px; border-radius: var(--radius-sm); }
.pb-post:hover { background: var(--row-hover); }
.pb-post.pinned > .pb-main .pb-body { border-left: 3px solid var(--c-yellow); padding-left: 8px; }
.pb-av { flex: none; width: 34px; height: 34px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  color: #fff; font-size: 12.5px; font-weight: 700; background-size: cover; background-position: center; }
.pb-av.has-photo { color: transparent; }
.pb-reply .pb-av { width: 26px; height: 26px; font-size: 10.5px; }
.pb-main { flex: 1; min-width: 0; }
.pb-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; min-height: 28px; }
.pb-head b { color: var(--heading); }
.pb-head time, .pb-edited { font-size: 12px; color: var(--muted); }
.pb-edited { font-style: italic; }
.pb-pin { display: inline-flex; align-items: center; gap: 3px; font-size: 11.5px; font-weight: 600; color: var(--muted); }
.pb-pin .icon { width: 12px; height: 12px; }
.pb-acts { margin-left: auto; display: inline-flex; gap: 2px; opacity: 0; transition: opacity .12s; }
.pb-post:hover .pb-acts, .pb-post:focus-within .pb-acts { opacity: 1; }
.pb-acts .icon-btn.on { color: var(--c-yellow); }
.pb-body { font-size: 14.5px; line-height: 1.65; white-space: normal; overflow-wrap: anywhere; margin-top: 1px; }
.pb-body a { color: var(--link); }
.pb-replies { margin-left: 44px; padding-left: 10px; border-left: 2px solid var(--border); }
.pb-replylink { margin-left: 50px; background: none; border: none; padding: 2px 6px; font-size: 12.5px; color: var(--link); cursor: pointer; }
.pb-replylink:hover { text-decoration: underline; }
.pb-empty { text-align: center; padding: 40px 12px; color: var(--muted); }
.pb-empty svg { width: 34px; height: 34px; opacity: .55; }
.pb-empty p { margin: 8px auto 0; max-width: 46ch; }
/* on a touch screen there is no hover to reveal the actions: they stay */
@media (pointer: coarse) { .pb-acts { opacity: 1; } .pb-hint { display: none; } }
@media (max-width: 560px) { .pb-replies { margin-left: 16px; } .pb-replylink { margin-left: 22px; } }

/* ---------- Who is this? — the card on a person's name (components/personcard.js, 2026-09-30) ---------- */
/* the hand while it rests on a name (owner, 2026-10-01: a hand, not "?") —
   set on <html> by the script, which finds names in the text itself */
html.pcard-hint, html.pcard-hint * { cursor: pointer !important; }
.pcard { position: fixed; z-index: var(--z-tooltip); width: 300px; max-width: calc(100vw - 16px); background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow-lg); padding: 14px 16px 12px; animation: pcard-in .12s ease-out; }
.pcard[hidden] { display: none; }
@keyframes pcard-in { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .pcard { animation: none; } }
.pcard-top { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.pcard-av { flex: none; width: 64px; height: 64px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  color: #fff; font-size: 20px; font-weight: 700; background-size: cover; background-position: center; box-shadow: 0 0 0 3px var(--surface-2); }
.pcard-av.has-photo { color: transparent; }
.pcard-av.party { background: linear-gradient(135deg, #f59e0b, #ef4444); color: #fff; border-radius: 16px; }
.pcard-names { display: flex; flex-direction: column; min-width: 0; }
.pcard-names b { font-size: 15.5px; line-height: 1.3; color: var(--heading); overflow-wrap: anywhere; }
.pcard-names span { font-size: 12.5px; color: var(--muted); overflow-wrap: anywhere; }
.pcard-facts { display: grid; grid-template-columns: auto 1fr; gap: 5px 12px; margin: 0; font-size: 13px; }
.pcard-facts dt { color: var(--muted); }
.pcard-facts dd { margin: 0; overflow-wrap: anywhere; }
.pcard-facts a { color: var(--link); font-variant-numeric: tabular-nums; }
.pcard-open { display: inline-block; margin-top: 10px; font-size: 12.5px; color: var(--link); }

/* ============================================================
   The portal for outside people (2026-09-30) — its own small app, drawn in
   #app-root.portal-root instead of the staff shell. Phone first: a client
   checks their job on a phone at least as often as at a desk.
   ============================================================ */
/* #app-root is the staff shell's full-height row (sidebar | page) with its
   overflow hidden; the portal is one scrolling column instead. */
#app-root.portal-root { display: block; height: auto; min-height: 100vh; overflow: visible; background: var(--bg); }

/* the server's room on the Data & backups page (2026-10-01) */
.st-bar { display: flex; height: 14px; border-radius: 99px; overflow: hidden; background: var(--surface-3); }
.st-bar .st-ours { background: var(--c-main); }
.st-bar .st-other { background: color-mix(in srgb, var(--text-dim) 45%, transparent); }
.st-key { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 8px; font-size: 12px; color: var(--text-dim); }
.st-key i { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }
.st-key i.st-ours { background: var(--c-main); }
.st-key i.st-other { background: color-mix(in srgb, var(--text-dim) 45%, transparent); }
.st-key i.st-free { background: var(--surface-3); border: 1px solid var(--border); }
.st-key b { color: var(--heading); }

/* ============================================================
   The portal, redesigned (owner, 2026-10-01: "make it more interesting,
   more exciting — it is too plain"). A deep header, a hero with light in it,
   glass tiles, cards that lift, rings for progress. Colour comes from the
   app's own accent so it stays the practice's, light or dark.
   ============================================================ */
#app-root.portal-root {
  --pt-deep: #0b1d3a; --pt-mid: #12407a; --pt-glow: color-mix(in srgb, var(--c-main) 70%, #38bdf8);
  background:
    radial-gradient(1200px 500px at 110% -10%, color-mix(in srgb, var(--c-main) 14%, transparent), transparent 60%),
    radial-gradient(900px 400px at -10% 110%, color-mix(in srgb, #8b5cf6 10%, transparent), transparent 60%),
    var(--bg);
}
@keyframes pt-swap { from { opacity: .55; } to { opacity: 1; } }
@keyframes pt-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }

@keyframes pt-rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes qd-spin { to { transform: rotate(360deg); } }
