/* ═══════════════════════════════════════════════════════════════════
   GLASS BRIDGE — legacy class names → Liquid Glass
   ═══════════════════════════════════════════════════════════════════

   glass.css is a verbatim copy of the original design system and should
   stay that way, so nothing here edits it. This file adapts the class
   vocabulary the Workers rebuild happened to ship with.

   Two vocabularies need adapting:

     • The short names (.fld .tbl .top .ghost .sm .tile) that the Hub
       used and that I copied into the Portal and Tarly Admin.
     • The .admin-* / .auth-* / .sb-* names I invented for Site Admin.

   Aliasing rather than rewriting ~90 page files is a deliberate call:
   the pages themselves work, and find-and-replace across all of them is
   how subtle breakage gets introduced. New markup should use the glass
   names directly — everything below is a compatibility layer, and a
   name can be deleted from here once no page renders it.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Layout aliases ──────────────────────────────────────────────── */
/* The original had NO wrapper: body was the flex row, with .sidebar and
   .main as its direct children (glass.css still sets body{display:flex}).
   The rebuild wraps both in .shell, which therefore becomes a flex ITEM
   of body — and a flex item does not grow unless told to. Without flex:1
   it shrinks to its content width, so .main's own flex:1 resolves against
   a collapsed parent and the whole page renders as a narrow column beside
   the sidebar. That was the "acting weird" layout on 2026-08-05.
   min-width:0 lets wide tables scroll inside instead of forcing .shell
   wider than the viewport. */
.shell { display: flex; flex: 1; min-width: 0; min-height: 100vh; }
.shell > .main, .shell > main { min-width: 0; }

/* Safari on a Mac rubber-bands the whole document at the top and bottom
   of a scroll, and unlike Chrome it drags sticky and fixed elements with
   it, so the sidebar (position: sticky) visibly slid a few pixels and
   sprang back on every overscroll. Reported 2026-09-13 as "the sidebar
   also kind of scrolls" in Safari but not in the Mac app. Switching off
   the bounce on the root is the only fix that leaves the sidebar sticky;
   `.main` is not a scroller of its own, so nothing else changes. */
html { overscroll-behavior-y: none; }
/* ═══════════════════════════════════════════════════════════════════
   PERCEIVED SPEED
   ═══════════════════════════════════════════════════════════════════

   The portal answers in 107 to 129ms, which is genuinely fast for a
   server-rendered page, and it still FELT slower than a Next.js app
   whose server takes 377ms. Measured both, 2026-08-17.

   The reason is that a single-page app never navigates. Nothing is torn
   down, so there is no flash, and the eye reads the flash rather than
   the milliseconds.

   Three things cause that impression here, and NONE of them is the
   150ms:

     1. the white gap between the old document being destroyed and the
        new one painting
     2. no acknowledgement of the click, so the wait reads as lag
     3. the sidebar, which is identical on both pages, being rebuilt
        from scratch and flickering as it goes

   All three are fixable without prefetching anything, which matters:
   every authenticated page is deliberately `no-store`, because a
   timesheet in a shared cache is a data leak, and prefetching wants to
   store the response. This route leaves that decision untouched.

   PROGRESSIVE ENHANCEMENT. A browser without cross-document view
   transitions navigates exactly as it does today. Nothing is required
   for correctness. */

@view-transition {
  navigation: auto;
}

/* The sidebar is the same element on every page, so naming it makes the
   browser MORPH it rather than cross-fade it out and back in. That is
   what removes the flicker and makes the chrome feel like it never
   went away — the single biggest part of the SPA illusion. */
.side {
  view-transition-name: tarly-side;
}

/* Named separately so it holds its scroll position rather than being
   treated as part of the sidebar's own morph. */
.side nav {
  view-transition-name: tarly-nav;
}

/* Only the content crossfades, and quickly: this is a page change, not
   an animation to admire. 90ms is below the threshold at which people
   report noticing a transition at all, which is the point — it should
   remove the flash without announcing itself. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 90ms;
}

/* The chrome does not fade at all. It is the same thing, in the same
   place, before and after. */
::view-transition-old(tarly-side),
::view-transition-new(tarly-side),
::view-transition-old(tarly-nav),
::view-transition-new(tarly-nav) {
  animation: none;
  mix-blend-mode: normal;
}

/* A click is acknowledged before the network says anything, so the wait
   is spent looking at a UI that has already responded rather than one
   that appears to have ignored you.

   THE COLOURS ARE SET INLINE BY Layout.tsx, not here. A rule at this
   selector did not take effect even though it was present in
   document.styleSheets, matched the element, and used a token that
   resolves — verified live and never explained. An inline style beats
   every stylesheet rule short of !important, so the press state does not
   depend on winning a cascade in a file whose own notes say specificity
   here is load-bearing.

   This selector stays declared so tools/check-classes.mjs can resolve
   the hook, and so the next person finds this note rather than the
   silence. */
.side nav a[data-going] {
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none; }
}

.side  { /* alias of .sidebar */
  width: 232px; min-height: 100vh; flex-shrink: 0;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
  display: flex; flex-direction: column;
  padding: 14px 12px 14px 14px;
  background: transparent; border: none; z-index: 5;
}
.side::before {
  content: ""; position: absolute; inset: 14px 0 14px 12px;
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  border-radius: var(--radius-lg);
  backdrop-filter: var(--blur-md); -webkit-backdrop-filter: var(--blur-md);
  box-shadow: var(--shadow-2), inset 0 1px 0 var(--edge-top);
  z-index: -1;
}
.side > * { position: relative; z-index: 1; }

/* Sidebar internals: .brand/.logo, plain <a>, .on, .side-foot, .who/.av */
.side .brand { padding: 18px 16px 14px; display: flex; align-items: center; gap: 10px; }
.side .brand b { font-family: Sora, sans-serif; font-size: 1rem; font-weight: 800; letter-spacing: -.02em; color: var(--text); display: block; }
.side .brand small { color: var(--muted); font-size: .7rem; letter-spacing: .04em; text-transform: uppercase; }
.side .brand .logo {
  width: 28px; height: 28px; border-radius: 8px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: .85rem; color: #1a1204;
  background: linear-gradient(145deg, var(--amber), var(--amber-strong));
  box-shadow: 0 0 14px var(--amber-dim);
}
/* The nav scrolls when there are more links than room, and it did so
   with NO indication that anything was below the edge. Measured on a
   1470x780 window: 426px of visible nav holding 662px of links, so a
   third of the navigation was invisible and undiscoverable — you had to
   already suspect it was there to find it.

   The fade is a mask on the element's own box, so it stays at the bottom
   edge while the content scrolls under it, and `.at-end` removes it once
   there is genuinely nothing more. A fade that never goes away teaches
   people to ignore it. */
.side nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 8px;
  flex: 1;
  overflow-y: auto;
  scrollbar-width: thin;
  transition: mask-image var(--dur-base, .2s) linear;
}
.side nav.can-scroll {
  mask-image: linear-gradient(to bottom, #000 calc(100% - 32px), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 32px), transparent 100%);
}
.side nav.can-scroll.at-end {
  mask-image: none;
  -webkit-mask-image: none;
}

/* The section headings in glass.css carry 14px above and 6px of margin;
   with the tighter rows that is most of the remaining slack. */
.side nav .sb-section { padding-top: 10px; margin-top: 2px; }

/* prefers-reduced-motion has nothing to say about a static gradient, so
   the mask stays; only the transition goes. */
@media (prefers-reduced-motion: reduce) {
  .side nav { transition: none; }
}
/* 7px, not 9: twelve links plus four section headings and the search
   trigger measured 662px, and the nav's box is 643px on a 997px-tall
   window and about 500px on a 13" laptop, so Settings (and on a laptop
   Team and Monitoring too) sat below the fold behind the fade. Tighter
   rows put the whole list on screen at 997px and leave two items, not
   four, to scroll for on a laptop. */
.side nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 12px; border-radius: var(--radius);
  color: var(--subtle); font-size: .84rem; font-weight: 500;
  text-decoration: none; border: 1px solid transparent;
  transition: background var(--dur-base), color var(--dur-base);
}
.side nav a:hover { background: var(--glass-2); color: var(--text); }
.side nav a.on {
  background: var(--glass-3); color: var(--text);
  border-color: var(--edge); border-top-color: var(--edge-top);
  box-shadow: inset 0 1px 0 var(--edge-top);
}
.side nav a.on svg { color: var(--amber); }
.side nav svg { width: 16px; height: 16px; flex-shrink: 0; }

.side-foot { padding: 10px; display: flex; flex-direction: column; gap: 6px; }
.who {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 11px; border-radius: var(--radius);
  background: var(--glass-1); border: 1px solid var(--edge);
  border-top-color: var(--edge-top);
  font-size: .8rem; color: var(--subtle);
}
.av {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: .72rem; color: #1a1204;
  background: linear-gradient(145deg, var(--amber), var(--amber-strong));
}
/* Rendered as a <button> inside a form. These four declarations used to be
   an inline style attribute on the element, which beat every rule below it —
   the control rendered as bare text. Keep them here, in the cascade. */
.logout {
  width: 100%; text-align: left; cursor: pointer; font: inherit;
  padding: 8px 11px; border-radius: var(--radius);
  background: var(--btn-ghost-bg); border: 1px solid var(--edge);
  color: var(--muted); font-size: .78rem; font-weight: 600;
  transition: background var(--dur-base), color var(--dur-base);
}
.logout:hover { background: var(--btn-ghost-hover); color: var(--red); }

/* .top was the old topbar; .main already exists in glass.css.
   The three .shell panels render page bodies straight into .main with no
   .page-content wrapper, so the gutter has to live on .main itself — which
   is why .top carries no horizontal padding of its own. */
.shell > .main, .shell > main { padding: 0 32px 32px; }
.top {
  padding: 18px 0 6px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  flex-wrap: wrap;
}
.top h1 { font-size: 1.35rem; font-weight: 700; letter-spacing: -.015em; color: var(--text); }
.top .sub, .top p { color: var(--muted); font-size: .84rem; }

/* ── Component aliases ───────────────────────────────────────────── */
.fld { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.fld > label, .lbl {
  font-size: .76rem; font-weight: 600; letter-spacing: .02em;
  text-transform: uppercase; color: var(--muted);
}
.fld input, .fld select, .fld textarea { width: 100%; }
/* A label that wraps its own checkbox is a sentence, not a caption: the
   box keeps its natural width and the text keeps its case and colour. */
.fld > label.check { text-transform: none; letter-spacing: 0; font-size: .84rem; color: var(--text); }
.fld > label.check input[type="checkbox"] { width: auto; flex: none; }

.form-actions { display: flex; gap: 10px; align-items: center; margin-top: 18px; flex-wrap: wrap; }

/* Buttons: pages write class="btn ghost" / "btn sm" / "btn danger". */
.btn.ghost, a.ghost, button.ghost {
  background: var(--btn-ghost-bg); color: var(--text);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
}
.btn.ghost:hover, a.ghost:hover, button.ghost:hover { background: var(--btn-ghost-hover); }
.btn.sm, a.sm, button.sm { padding: 6px 13px; font-size: .78rem; }
.btn.danger, a.danger, button.danger {
  background: var(--red-dim); color: var(--red);
  border: 1px solid rgba(248,113,113,.3);
}
.btn.danger:hover, a.danger:hover, button.danger:hover { background: rgba(248,113,113,.22); }

/* Tables: pages render a bare .tbl rather than .table-wrap > table. */
.tbl {
  width: 100%; border-collapse: collapse; font-size: .84rem;
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  border-radius: var(--radius-lg); overflow: hidden;
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
  box-shadow: var(--shadow-1);
}
.tbl th {
  background: var(--thead-bg); color: var(--muted);
  font-size: .72rem; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  text-align: left; padding: 11px 14px;
  border-bottom: 1px solid var(--edge);
}
.tbl td { padding: 12px 14px; border-bottom: 1px solid var(--row-border); color: var(--subtle); }
.tbl tr:last-child td { border-bottom: none; }
.tbl tbody tr { transition: background var(--dur-base); }
.tbl tbody tr:hover { background: var(--hover-bg); }

/* Stat tiles → glass stat cards. */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 22px; }
.tile {
  padding: 16px 18px; border-radius: var(--radius-lg);
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-md); -webkit-backdrop-filter: var(--blur-md);
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--edge-top);
  transition: transform var(--dur-base), background var(--dur-base), box-shadow var(--dur-base);
}
.tile:hover { transform: translateY(-2px); background: var(--glass-2); box-shadow: var(--shadow-2), inset 0 1px 0 var(--edge-top); }
.tile .big, .tile b { display: block; font-size: 1.7rem; font-weight: 700; letter-spacing: -.02em; color: var(--text); font-family: Sora, sans-serif; }
.tile .meta, .tile small { color: var(--muted); font-size: .74rem; letter-spacing: .04em; text-transform: uppercase; }

.grid { display: grid; gap: 14px; }
.grid.wide, .wide { grid-column: 1 / -1; }
.empty {
  padding: 40px 20px; text-align: center; color: var(--muted); font-size: .88rem;
  background: var(--glass-1); border: 1px dashed var(--edge); border-radius: var(--radius-lg);
}
.err, .flash.err { color: var(--flash-error-text); }
.flash.ok { color: var(--flash-success-text); }
.tone { color: var(--muted); font-size: .8rem; }

/* ── Site Admin's admin-* / sb-* vocabulary ──────────────────────── */
.admin-body { margin: 0; }
.admin-sidebar { /* handled by .sidebar rules when present */ }
.admin-main { flex: 1; display: flex; flex-direction: column; min-height: 100vh; }
.admin-topbar { padding: 18px 32px; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.topbar-title { font-size: 1.35rem; font-weight: 700; letter-spacing: -.015em; color: var(--text); }
.topbar-actions { display: flex; gap: 10px; align-items: center; }

.admin-card {
  padding: 20px 22px; border-radius: var(--radius-lg);
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-md); -webkit-backdrop-filter: var(--blur-md);
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--edge-top);
  margin-bottom: 18px;
}
.admin-section-title { font-size: .78rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-bottom: 12px; }
.admin-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.admin-label { font-size: .76rem; font-weight: 600; letter-spacing: .02em; text-transform: uppercase; color: var(--muted); }
.admin-help { color: var(--muted); font-size: .76rem; }
.admin-error-text { color: var(--red); font-size: .8rem; }
.admin-form-actions { display: flex; gap: 10px; margin-top: 18px; flex-wrap: wrap; }
.admin-empty { padding: 40px 20px; text-align: center; color: var(--muted); background: var(--glass-1); border: 1px dashed var(--edge); border-radius: var(--radius-lg); }
.admin-flash { margin-bottom: 16px; }

.admin-table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.admin-table-wrap {
  border-radius: var(--radius-lg); overflow: hidden;
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
  box-shadow: var(--shadow-1);
}
.admin-table th { background: var(--thead-bg); color: var(--muted); font-size: .72rem; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--edge); }
.admin-table td { padding: 12px 14px; border-bottom: 1px solid var(--row-border); color: var(--subtle); }
.admin-table tbody tr:hover { background: var(--hover-bg); }
.admin-td-title { color: var(--text); font-weight: 600; }
.admin-td-slug { color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .78rem; }
.admin-td-actions { display: flex; gap: 8px; justify-content: flex-end; }
.admin-status-badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: var(--radius-pill); font-size: .72rem; font-weight: 600; background: var(--glass-2); border: 1px solid var(--edge); color: var(--subtle); }

.admin-select, .admin-textarea { width: 100%; }
.admin-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px 18px; border-radius: var(--radius-pill);
  font-size: .86rem; font-weight: 600; letter-spacing: -.005em;
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  background: var(--btn-ghost-bg); color: var(--text);
  cursor: pointer; text-decoration: none;
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
  transition: background var(--dur-base), transform var(--dur-base);
}
.admin-btn:hover { background: var(--btn-ghost-hover); }
.admin-btn:active { transform: translateY(1px); }
.admin-btn-primary {
  background: linear-gradient(145deg, var(--amber), var(--amber-strong));
  color: #1a1204; border-color: var(--amber-border);
  box-shadow: 0 4px 18px var(--amber-dim), inset 0 1px 0 rgba(255,255,255,.35);
}
.admin-btn-primary:hover { filter: brightness(1.06); background: linear-gradient(145deg, var(--amber), var(--amber-strong)); }
.admin-btn-ghost { background: var(--btn-ghost-bg); }
.admin-btn-danger { background: var(--red-dim); color: var(--red); border-color: rgba(248,113,113,.3); }
.admin-btn-sm { padding: 6px 13px; font-size: .78rem; }

.sb-icon { width: 16px; height: 16px; flex-shrink: 0; }
.sb-brand-logo {
  width: 28px; height: 28px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: .85rem; color: #1a1204;
  background: linear-gradient(145deg, var(--amber), var(--amber-strong));
  box-shadow: 0 0 14px var(--amber-dim);
}
.sb-brand-name { font-family: Sora, sans-serif; font-weight: 800; letter-spacing: -.02em; color: var(--text); }
.sb-brand-sub { color: var(--muted); font-size: .7rem; letter-spacing: .04em; text-transform: uppercase; }
.sb-section-label { padding: 14px 14px 5px; font-size: .66rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); opacity: .75; }
.sb-divider { height: 1px; background: var(--edge); margin: 8px 12px; }
.sb-theme-btn { /* alias of .theme-toggle */ }

/* Site Admin renders .sb-item where the glass sidebar uses .sb-link. */
.sb-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: var(--radius);
  color: var(--subtle); font-size: .84rem; font-weight: 500;
  text-decoration: none; border: 1px solid transparent;
  transition: background var(--dur-base), color var(--dur-base);
}
.sb-item:hover { background: var(--glass-2); color: var(--text); }
.sb-item.active {
  background: var(--glass-3); color: var(--text);
  border-color: var(--edge); border-top-color: var(--edge-top);
  box-shadow: inset 0 1px 0 var(--edge-top);
}
.sb-item.active .sb-icon { color: var(--amber); }

/* ── Auth pages (login / 2FA / enrolment / first-run setup) ──────── */
/* Centred glass panel over the ambient background, matching the old
   Tarly login rather than the flat card the rebuild shipped. */
.auth-body {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.auth-card {
  width: 100%; max-width: 400px; padding: 34px 32px;
  border-radius: var(--radius-lg);
  background: var(--glass-2);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-lg); -webkit-backdrop-filter: var(--blur-lg);
  box-shadow: var(--shadow-3), inset 0 1px 0 var(--edge-top);
  animation: glassPanelIn .5s cubic-bezier(.22,1,.36,1) both;
}
.auth-brand { display: flex; align-items: center; gap: 11px; margin-bottom: 24px; }
.auth-brand-logo {
  width: 34px; height: 34px; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 800; color: #1a1204;
  background: linear-gradient(145deg, var(--amber), var(--amber-strong));
  box-shadow: 0 0 18px var(--amber-dim);
}
.auth-brand-name { font-family: Sora, sans-serif; font-weight: 800; font-size: 1.05rem; letter-spacing: -.02em; color: var(--text); }
.auth-brand-sub { color: var(--muted); font-size: .72rem; letter-spacing: .05em; text-transform: uppercase; }
.auth-title { font-family: Sora, sans-serif; font-size: 1.3rem; font-weight: 700; letter-spacing: -.02em; color: var(--text); margin-bottom: 6px; }
.auth-sub { color: var(--muted); font-size: .85rem; line-height: 1.55; margin-bottom: 20px; }
.auth-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.auth-label { font-size: .76rem; font-weight: 600; letter-spacing: .02em; text-transform: uppercase; color: var(--muted); }
.auth-input, .auth-code-input {
  width: 100%; padding: 11px 14px; border-radius: var(--radius);
  background: var(--input-bg); color: var(--text);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  font: inherit; font-size: .9rem;
  transition: border-color var(--dur-base), box-shadow var(--dur-base), background var(--dur-base);
}
.auth-input:focus, .auth-code-input:focus {
  outline: none; background: var(--glass-2);
  border-color: var(--amber-border);
  box-shadow: 0 0 0 3px var(--amber-dim);
}
.auth-code-input {
  text-align: center; letter-spacing: .5em; font-size: 1.4rem; font-weight: 700;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; padding: 14px;
}
.auth-btn {
  width: 100%; margin-top: 6px;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 12px 18px; border-radius: var(--radius-pill);
  font-size: .9rem; font-weight: 700; cursor: pointer;
  color: #1a1204; border: 1px solid var(--amber-border);
  background: linear-gradient(145deg, var(--amber), var(--amber-strong));
  box-shadow: 0 4px 20px var(--amber-dim), inset 0 1px 0 rgba(255,255,255,.35);
  transition: filter var(--dur-base), transform var(--dur-base);
}
.auth-btn:hover { filter: brightness(1.06); }
.auth-btn:active { transform: translateY(1px); }
.auth-error {
  padding: 10px 13px; border-radius: var(--radius); margin-bottom: 16px;
  background: var(--red-dim); border: 1px solid rgba(248,113,113,.28);
  color: var(--flash-error-text); font-size: .84rem;
}
/* Confirmation counterpart to .auth-error — same box, green tokens. Used
   for "password updated" on the sign-in screen, where an amber notice
   would read as another warning. */
.auth-success {
  padding: 10px 13px; border-radius: var(--radius); margin-bottom: 16px;
  background: var(--green-dim); border: 1px solid rgba(52,211,153,.3);
  color: var(--flash-success-text); font-size: .84rem;
}
/* "Keep me signed in" row. A checkbox is the one control on these forms
   that reads horizontally, so it cannot reuse .auth-field (flex-column)
   without the label landing above the box. */
.auth-check {
  display: flex; align-items: center; gap: 9px;
  margin: 2px 0 6px; cursor: pointer;
  color: var(--subtle); font-size: .83rem; line-height: 1.4;
}
.auth-check input[type="checkbox"] {
  width: 16px; height: 16px; flex: none; margin: 0;
  accent-color: var(--amber); cursor: pointer;
}
/* Honeypot on /forgot. Off-canvas rather than display:none — some bots
   skip fields they can tell are hidden, and this is meant to be filled. */
.auth-hp {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  overflow: hidden;
}
.auth-foot { margin-top: 18px; text-align: center; color: var(--muted); font-size: .78rem; }
.auth-foot a { color: var(--amber); text-decoration: none; }
.auth-qr {
  display: block; margin: 0 auto 16px; border-radius: var(--radius);
  background: #fff; padding: 10px;
}
.auth-secret, .code {
  display: block; padding: 10px 13px; border-radius: var(--radius);
  background: var(--input-bg); border: 1px solid var(--edge);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .82rem; letter-spacing: .08em; color: var(--subtle);
  word-break: break-all; text-align: center;
}

/* ── Collapsible filters ─────────────────────────────────────────
   <details open> in the markup, so with no JavaScript the filters are
   exactly as visible as they have always been. mobilenav.js closes it
   under 768px, where the filter block was eating 44 to 48% of the screen
   before any content. On desktop the summary is not shown at all and
   the disclosure is inert. */
.mfilters-sum {
  display: none; align-items: center; gap: 8px;
  min-height: 44px; padding: 0 14px; margin-bottom: 12px;
  border-radius: var(--radius-pill);
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  color: var(--subtle); font-size: .8rem; font-weight: 600;
  cursor: pointer; list-style: none; width: max-content;
}
.mfilters-sum::-webkit-details-marker { display: none; }
/* A caret that turns, so the control says which way it will go. */
.mfilters-sum::after {
  content: ''; width: 7px; height: 7px; margin-left: 2px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--dur-fast);
}
.mfilters[open] > .mfilters-sum { color: var(--text); }
.mfilters[open] > .mfilters-sum::after {
  transform: rotate(-135deg) translate(-2px, -2px);
}
/* The column manager on /tasks is the one disclosure that folds on a
   desktop as well, so its summary is drawn at every width (the phone
   block below styles it; this only lifts the display). Closed, the card
   chrome goes too: a bordered box around a single pill reads as a form
   that lost its fields. */
.colmgr > .mfilters-sum { display: flex; margin-bottom: 0; }
.colmgr[open] > .mfilters-sum { margin-bottom: 14px; }
.colmgr:not([open]) { background: none; border-color: transparent; box-shadow: none; padding: 0; }
.colmgr:not([open]):hover { box-shadow: none; }
/* A CLOSED <details> DOES NOT RELIABLY HIDE ITS CONTENTS. The browser
   hides them with a UA `display: none`, and `.filter-bar` and `.filters`
   both set `display: flex` in the author stylesheet, which wins. So the
   collapsed filters stayed on screen AND grew, and the first attempt at
   this made /tasks worse: content moved from 44% of the phone down to
   60%. Hide them explicitly. */
.mfilters:not([open]) > *:not(summary) { display: none !important; }

/* The "back to the list" link on every detail page. It carried its
   colour as an inline style and had no class, which is why it could not
   be given a tap target without a selector broad enough to hit every
   link in a sentence. */
.backlink { color: var(--muted); text-decoration: none; }
.backlink:hover { color: var(--text); }

/* ── Phone chrome ────────────────────────────────────────────────
   Rendered on every portal page, shown only under 768px. Built from the
   same Liquid Glass tokens as everything else: --glass-* for the
   surfaces, --edge/--edge-top for the lit rim, --amber for the active
   state. No new visual language, only a new arrangement. */
.mtop, .mtabs { display: none; }

/* ── Mobile ──────────────────────────────────────────────────────── */
@media (max-width: 768px) {
  .shell { display: block; }

  /* ── The strip is retired, because the bottom bar now exists ──────
     The old comment here said the sidebar must NOT be display:none,
     because "the original replaced it with a fixed bottom nav plus a
     drawer, and none of the four panels render those yet" and hiding it
     would leave phones with no navigation. The Portal renders both now,
     so for the Portal the condition is met.

     .admin-sidebar is NOT hidden: the Hub and the two admin panels still
     have only the strip, and hiding theirs would strand them exactly as
     that comment warned. */
  /* NOT display:none, and this is load-bearing.
     The timer widget lives inside .side-foot, and it is promoted to a
     fixed bar above the tabs when a timer is running. A `position: fixed`
     element inside a `display: none` ancestor IS NOT RENDERED — the
     whole subtree leaves the box tree — so with .side hidden outright
     the running-timer bar measured 0x0 and never appeared on any phone.

     I did not catch this in review because the mock I verified the CSS
     against had .tw as a direct child of .shell. It took running an
     actual timer on production to see it. `display: contents` keeps .tw
     in the tree while the sidebar itself contributes no box. */
  .shell > .side { display: contents; }
  .side > .brand, .side > nav { display: none; }
  .side-foot { display: contents; }
  .side-foot > *:not(.tw) { display: none; }

  .mtop {
    position: fixed; top: 0; left: 0; right: 0; z-index: 40;
    display: flex; align-items: center; gap: 10px;
    height: 52px; padding: 0 14px;
    /* Opaque, for the reason the sheet is: on iOS the blur does not
       arrive and a translucent bar shows the page scrolling through it. */
    background-color: var(--bg);
    background-image: linear-gradient(var(--glass-2), var(--glass-2));
    border-bottom: 1px solid var(--edge);
    box-shadow: inset 0 1px 0 var(--edge-top);
  }
  .mtop-mark { display: inline-flex; flex-shrink: 0; }
  .mtop-title {
    flex: 1; min-width: 0;
    font-family: Sora, sans-serif; font-weight: 700; font-size: .95rem;
    color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .mtop-btn {
    flex-shrink: 0; width: 44px; height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius-sm); cursor: pointer;
    background: transparent; border: 1px solid transparent; color: var(--muted);
  }
  .mtop-btn svg { width: 20px; height: 20px; }
  .mtop-btn:active { background: var(--hover-bg); }

  .mtabs {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    display: flex; align-items: stretch;
    /* NO backdrop-filter HERE, AND THAT IS THE WHOLE POINT.
       A filtered element becomes the CONTAINING BLOCK for its
       position:fixed descendants. The sheet's scrim lives inside this
       bar, so `inset: 0` resolved to the tab bar's own 56px box instead
       of the viewport — an invisible strip hidden behind the tabs. The
       scrim was therefore untappable and the sheet could not be closed
       at all on a phone, which is exactly what Alwaz reported.

       It also mis-positioned the panel: `bottom: 0` resolved against the
       bar, so the panel ran 12px past the bottom of the screen and
       covered the More tab that opened it.

       An opaque background gives the same look on iOS anyway, since the
       blur was never arriving there. */
    background-color: var(--bg);
    background-image: linear-gradient(var(--glass-2), var(--glass-2));
    border-top: 1px solid var(--edge);
    box-shadow: inset 0 1px 0 var(--edge-top);
    /* Clears the home indicator on a notched phone. Zero elsewhere. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .mtab {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; padding: 8px 2px; min-height: 56px;
    color: var(--muted); text-decoration: none; cursor: pointer;
    font-size: .66rem; font-weight: 600; letter-spacing: .01em;
    list-style: none; /* it is a <summary> for the More tab */
  }
  .mtab::-webkit-details-marker { display: none; }
  .mtab svg { width: 21px; height: 21px; }
  .mtab span {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .mtab.on { color: var(--amber); }
  /* LIGHT MODE ONLY. The tab bar is not the page: it is
     rgba(255,255,255,.72) over #eef2f7, so the effective backdrop is
     near-white and light-mode --amber (#c96e05) lands at about 3.6:1.
     That clears the 3:1 a UI component needs and misses the 4.5:1 a
     10.5px LABEL needs, and this is a label. --amber-strong (#b45309)
     measures 4.88:1 on the same backdrop.

     Scoped to .light so dark mode keeps the brighter amber, where it
     already has enormous contrast against near-black.

     Worth remembering: my first reading said 3.27:1 because I measured
     against `body`. Measure against the surface the text is actually
     sitting on. */
  .light .mtab.on { color: var(--amber-strong); }
  .light .msheet-link.on { color: var(--amber-strong); }
  .mtab:active { background: var(--hover-bg); }

  /* ── The More sheet ──────────────────────────────────────────────
     <details>/<summary>, so it opens with no JavaScript at all.
     mobilenav.js only adds the scroll lock, Esc and the backdrop. */
  .msheet { flex: 1; min-width: 0; display: flex; }
  .msheet > summary { flex: 1; }
  .msheet[open] > summary { color: var(--text); }
  .msheet-panel {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
    max-height: 78vh; overflow-y: auto;
    padding: 8px 14px calc(72px + env(safe-area-inset-bottom, 0px));
    /* OPAQUE BASE, then the glass tier on top.
       This was `background: var(--glass-3)` alone, which is
       rgba(255,255,255,.09) in dark mode — nine per cent white over a
       near-black page. It was legible in Chrome only because
       backdrop-filter blurred what was behind it; on a real iPhone the
       blur does not deliver that, and the sheet rendered SEE-THROUGH
       with the conversation behind it overlapping every link. Found on
       a physical device, not in any emulator.

       This is the CLAUDE.md warning in its purest form: the glass tiers
       are WHITE in both themes, so as a lone fill over a dark page they
       are nearly invisible. A panel that must occlude needs the page
       colour underneath it. */
    background-color: var(--bg);
    background-image: linear-gradient(var(--glass-3), var(--glass-3));
    backdrop-filter: var(--blur-lg); -webkit-backdrop-filter: var(--blur-lg);
    border-top: 1px solid var(--edge);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    box-shadow: inset 0 1px 0 var(--edge-top), 0 -18px 40px rgba(0,0,0,.35);
    animation: msheet-in var(--dur-base) ease-out;
  }
  /* BOTH ends declared. With only `from`, the 12px translate was still
     applied after the animation finished, leaving the panel sitting 12px
     below the viewport. Invisible, because the panel is opaque and the
     overhang is off screen, but it is a real offset and it would have
     clipped the last row on a shorter sheet. */
  @keyframes msheet-in {
    from { transform: translateY(12px); opacity: 0 }
    to   { transform: translateY(0);    opacity: 1 }
  }
  @media (prefers-reduced-motion: reduce) {
    .msheet-panel { animation: none; }
  }
  /* A HANDLE THAT DOES SOMETHING, AND A BUTTON THAT SAYS SO.
     The grip looked draggable and was not, the More tab that opened the
     sheet was underneath it, and the scrim was unreachable — so there
     was genuinely no way out. Now: drag the handle down, tap the scrim,
     tap Close, or press Escape. */
  .msheet-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; margin-bottom: 10px;
  }
  .msheet-grip {
    width: 38px; height: 5px; margin: 2px auto 10px;
    border-radius: 999px; background: var(--edge-strong);
    /* The whole top strip is the drag target, not just the 5px bar. */
    cursor: grab; touch-action: none;
  }
  .msheet-handle { padding: 6px 0 2px; touch-action: none; }
  .msheet-close {
    flex: 0 0 auto; min-height: 44px; min-width: 44px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius-sm);
    background: var(--glass-1); border: 1px solid var(--edge);
    border-top-color: var(--edge-top);
    color: var(--muted); cursor: pointer; font-size: 1.1rem; line-height: 1;
  }
  .msheet-close:active { background: var(--hover-bg); color: var(--text); }
  /* While a drag is in progress the panel follows the finger. */
  .msheet-panel.is-dragging { transition: none; animation: none; }
  .msheet-who {
    flex: 1; min-width: 0;
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px;
    border-radius: var(--radius);
    background: var(--glass-1); border: 1px solid var(--edge);
    border-top-color: var(--edge-top);
    color: var(--text); text-decoration: none; font-size: .86rem; font-weight: 600;
  }
  .msheet-links { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
  .msheet-link {
    display: flex; align-items: center; gap: 9px;
    min-height: 46px; padding: 0 12px;
    border-radius: var(--radius);
    border: 1px solid var(--edge); border-top-color: var(--edge-top);
    background: var(--glass-1);
    color: var(--subtle); text-decoration: none;
    font-size: .8rem; font-weight: 600;
  }
  .msheet-link svg { width: 17px; height: 17px; flex-shrink: 0; }
  .msheet-link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .msheet-link.on { color: var(--amber); border-color: var(--edge-sel); }
  .msheet-foot {
    display: flex; align-items: center; gap: 8px; margin-top: 12px;
  }
  .msheet-foot > form { flex: 0 0 auto; }
  .msheet-foot .logout { width: auto; min-height: 44px; white-space: nowrap; }
  .msheet-foot .theme-toggle, .msheet-foot > button:first-child { flex: 1; min-height: 44px; }
  /* Backdrop, added by mobilenav.js so it can be clicked to dismiss. */
  .msheet-scrim {
    position: fixed; inset: 0; z-index: 44;
    /* Heavier than it looks like it needs to be. Over a near-black page
       a 45% black scrim is invisible, so it read as though no scrim was
       drawn at all. */
    background: rgba(0,0,0,.66);
    backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  }
  body.msheet-open { overflow: hidden; }

  /* Content clears both fixed bars. */
  .shell > .main, .shell > main {
    padding: 52px 16px calc(72px + env(safe-area-inset-bottom, 0px));
  }

  /* The pill itself is styled at base (see "Collapsible filters"); the
     phone only turns it on. */
  .mfilters-sum { display: flex; }

  /* ── 16px, or iOS zooms the page ─────────────────────────────────
     Safari on iOS zooms in when you focus an input whose font-size is
     below 16px, and then leaves you zoomed. Tapping the timer
     description made the whole page jump and the reader had to pinch
     back out every time.

     Measured across the portal: 28 of 29 fields were 12.3 to 15px. The
     one that was already right is the assistant's composer at 16px.

     This is a size the DESIGN did not choose — the glass system uses
     .74 to .82rem here — but a control you cannot focus without the page
     moving is worse than one that is a little chunkier. There is room:
     these are already 44px tall. Phones only; desktop keeps the design's
     own scale.

     THE !important IS REQUIRED, not laziness. Several field rules in
     this file already declare their size that way — `.quick-add-input`
     is `font-size: .82rem !important` — and `!important` beats
     specificity outright, so the first version of this rule lost to
     them silently. It looked correct in the file, shipped, and left
     nine fields on three pages still zooming iOS. Caught by measuring
     font-size on production rather than trusting the deploy. */
  .main input:not([type=checkbox]):not([type=radio]):not([type=hidden]),
  .main select,
  .main textarea {
    font-size: 16px !important;
  }

  /* ── Controls big enough to hit ───────────────────────────────────
     Measured on /tasks: 46 of 49 controls were under 44px, with search
     at 34px, the selects at 35 and some buttons at 22. 44px is the iOS
     floor and these are the controls a VA uses one-handed. */
  .main input:not([type=checkbox]):not([type=radio]):not([type=hidden]),
  .main select,
  .main textarea,
  .main .btn {
    min-height: 44px;
  }
  .main .filter-bar input, .main .filter-bar select,
  .main .filters input, .main .filters select { min-height: 44px; }
  /* Buttons that sit inside a table card or a compact row keep their
     size — they are already inside a 44px-tall row and doubling them
     makes the card taller without making the target easier. */
  .is-cards .btn { min-height: 38px; }

  /* The four the first pass missed, found by listing every control
     under 44px rather than trusting the selector to have covered them.

     A colour input is not a checkbox, so it was not in the exclusion
     list and not in the inclusion list either: the UA gives it an
     intrinsic size and it rendered as a 13px swatch in the Columns
     settings — the smallest control in the portal. */
  .main input[type='color'] { min-height: 44px; width: 56px; padding: 4px; }

  /* CHECKBOXES WERE EXCLUDED FROM THE 44px RULE ON PURPOSE, and that was
     half right. A checkbox stretched to 44px square looks broken. But
     these are 13x13 and one of them marks a subtask done, which is a
     real action at a third of the minimum target.
     So the BOX grows a little and its LABEL becomes the target — which
     is what a wrapping <label> is for. Found by listing every control
     under 44px and reading what they actually were, after a guess at
     input[type=color] matched nothing. */
  .main input[type='checkbox'], .main input[type='radio'] {
    width: 20px; height: 20px; flex-shrink: 0;
  }
  .main label:has(> input[type='checkbox']),
  .main label:has(> input[type='radio']) {
    display: inline-flex; align-items: center; gap: 9px; min-height: 44px;
  }

  /* The record name at the top of a table card. The row's real action is
     its button, but the title is a link and was 17px. */
  .is-cards td:first-child a {
    display: flex; align-items: center; min-height: 44px;
  }

  /* ── The rest, named one by one ──────────────────────────────────
     The blanket rule covers .btn, inputs and selects. These are the
     controls that are neither: bare <a> and <button> with their own
     class. I only found them by sweeping ALL THIRTEEN pages — an
     earlier pass checked three, found nothing, and I wrongly reported
     the job done.

     Listed explicitly rather than caught by something broad like
     `.main a { min-height: 44px }`, which would also hit every link in
     a sentence of prose and make the Knowledge Base unreadable. */
  .ag-link { display: inline-flex; align-items: center; min-height: 44px; }
  .ag-send { min-height: 44px; min-width: 44px; }
  .cap-link { display: inline-flex; align-items: center; min-height: 44px; }
  .qbtn { min-height: 44px; display: inline-flex; align-items: center; }
  .weeknav-arrow { min-height: 44px; min-width: 44px; display: inline-flex;
                   align-items: center; justify-content: center; }
  .addbtn { min-height: 44px; min-width: 44px; }
  .view-btn { min-height: 44px; display: inline-flex; align-items: center; }
  /* 15px on the detail pages, and it is the way back. */
  .backlink { min-height: 44px; display: inline-flex; align-items: center; }

  /* The task DETAIL page, which the thirteen-page sweep never opened
     because it only walked top-level routes. .tk-sub-check is the
     subtask done toggle at 18px — a <button>, not a checkbox, which is
     why the checkbox rule did not reach it — and .tk-linkbtn is both the
     "x" that removes a subtask and the Delete under every comment, at
     17px. Marking work done and deleting things are not 17px actions. */
  .tk-sub-check { min-height: 44px; min-width: 44px; }
  .tk-linkbtn { min-height: 44px; display: inline-flex; align-items: center; }

  /* The Board / List / My tasks switcher: 31px. It is the control that
     changes what the whole page shows. */
  .view-tab { min-height: 44px; display: inline-flex; align-items: center; }

  /* A bare 22px "×" inside a task card. Small, and it removes things. */
  .kanban-board button { min-height: 44px; min-width: 44px; }

  /* The task title is the card's primary action and was a 19px line of
     text. Made a block with a real hit area; the card grows a little and
     the thing you actually tap stops being a hairline. */
  .task-card-title {
    display: flex; align-items: center; min-height: 44px;
  }

  /* ── Wide tables become cards ────────────────────────────────────
     Measured at 390px: Invoices is 584px wide, Team 655px, Reports
     520px. They scroll inside .table-wrap so the page does not overflow,
     but 130 to 265px of every row sits off screen and reading a row
     means dragging it sideways. Seven columns do not fit a phone.

     Gated on [data-cards], which tables-mobile.js sets after it has
     stamped each cell with its column heading. No script, no attribute,
     and the old sideways scroll stands — degraded, not broken.

     glass.css sets `table { min-width: 520px }` in its own mobile block.
     That is overridden here rather than edited there, because glass.css
     is the design system extracted verbatim and this is a layout
     decision, not a change to how the glass reads. */
  /* A grid item's automatic minimum size is its CONTENT, so a 520px
     table made .form-grid.cols-2 resolve to a 550px track on a 358px
     container even though the rule says 1fr. Both need to be allowed to
     shrink. */
  .form-grid, .form-grid > * { min-width: 0; }

  /* glass.css sets `table { min-width: 520px }` on mobile so a wide table
     scrolls inside .table-wrap. A table with NO wrapper has nothing to
     scroll inside, so that floor just breaks the page: the invoice
     detail's line-items table is a bare <table>, and because a grid
     item's automatic minimum is its content, it forced
     .form-grid.cols-2 to a 550px track on a 358px container and the
     page scrolled sideways by 176px.

     Cardified tables become blocks and ignore this anyway; the ones left
     are empty states and small tables, which should shrink rather than
     push the page. */
  .main table { min-width: 0; }

  .table-wrap[data-cards] {
    overflow-x: visible;
    background: none; border: 0; box-shadow: none;
    backdrop-filter: none; -webkit-backdrop-filter: none;
    border-radius: 0;
  }
  .is-cards { min-width: 0 !important; display: block; }
  .is-cards thead { display: none; }
  .is-cards tbody { display: block; }

  /* Each row is its own glass panel — same tokens the wrapper used to
     carry, moved in one level so a row reads as a card. */
  .is-cards tbody tr {
    display: block; margin-bottom: 10px; padding: 2px 0;
    background: var(--glass-1);
    border: 1px solid var(--edge); border-top-color: var(--edge-top);
    border-radius: var(--radius);
    backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
  }
  .is-cards tbody tr:last-child { margin-bottom: 0; }
  .is-cards tbody tr:hover { background: var(--glass-1); }

  .is-cards td {
    display: grid; grid-template-columns: minmax(84px, 36%) 1fr;
    align-items: baseline; gap: 10px;
    padding: 7px 13px; border-bottom: 1px solid var(--row-border);
  }
  .is-cards td::before {
    content: attr(data-label);
    color: var(--muted); font-size: .68rem; font-weight: 600;
    letter-spacing: .06em; text-transform: uppercase;
    overflow: hidden; text-overflow: ellipsis;
  }
  .is-cards tbody tr > *:last-child { border-bottom: 0; }

  /* The first column is the record's name. It reads as the card title
     rather than as another labelled field. */
  .is-cards td:first-child {
    display: block; padding-top: 10px; padding-bottom: 8px;
    color: var(--text); font-weight: 700; font-size: .92rem;
  }
  .is-cards td:first-child::before { content: none; }

  /* A blank cell would otherwise render as a label with nothing beside
     it. Dropping them is most of why a card is shorter than its row. */
  .is-cards td[data-empty] { display: none; }

  /* An actions column has no heading, so it gets no label and its
     buttons run full width instead of being squeezed into 64%. */
  .is-cards td[data-label=""] { display: block; }
  .is-cards td[data-label=""]::before { content: none; }

  /* "No invoices yet" spans the table and is not a record. */
  .is-cards tr[data-plain] { background: none; border: 0; }
  .is-cards tr[data-plain] td { display: block; border-bottom: 0; }
  .is-cards tr[data-plain] td::before { content: none; }

  /* ── The timer, only while it is running ─────────────────────────
     The widget carries a start form and a running clock. The form has a
     page of its own at /time; on a phone it was occupying chrome on all
     thirteen. So it is hidden here, and the running clock is promoted to
     a slim bar above the tabs — which is the one timer state you want
     visible from anywhere, because it is costing money. */
  .tw { display: none; }
  .tw-idle { display: none !important; }
  .shell:has(.tw-run:not([hidden])) .tw {
    display: block;
    position: fixed; left: 0; right: 0; z-index: 39;
    bottom: calc(56px + env(safe-area-inset-bottom, 0px));
    margin: 0; padding: 8px 14px;
    border-radius: 0;
    border-top: 1px solid var(--edge);
    background: var(--glass-2);
    backdrop-filter: var(--blur-md); -webkit-backdrop-filter: var(--blur-md);
  }
  .shell:has(.tw-run:not([hidden])) .tw-run {
    display: flex; align-items: center; gap: 10px;
  }
  .shell:has(.tw-run:not([hidden])) .tw-meta { flex: 1; min-width: 0; }
  .shell:has(.tw-run:not([hidden])) .tw-actions { flex: 0 0 auto; }
  .shell:has(.tw-run:not([hidden])) .tw-actions .btn { min-height: 40px; }
  .side, .admin-sidebar {
    width: auto; height: auto; min-height: 0;
    position: static; overflow: visible;
    padding: 10px 10px 0;
  }
  .side::before { inset: 10px 10px 0; }
  .side .brand, .sb-brand { padding: 12px 14px 10px; }
  /* Nav scrolls sideways rather than stacking to full screen height. */
  .side nav, .sb-nav {
    flex-direction: row; overflow-x: auto; gap: 6px;
    padding: 0 8px 8px; scrollbar-width: none;
  }
  .side nav::-webkit-scrollbar, .sb-nav::-webkit-scrollbar { display: none; }
  .side nav a, .sb-link, .sb-item { white-space: nowrap; flex-shrink: 0; }
  /* THE FOOTER IS A ROW HERE, AND ITS CHILDREN WERE SIZED FOR A COLUMN.
     .logout carries `width: 100%` so it fills the sidebar when the footer
     stacks. In this row it demands the FULL footer width beside the timer,
     the theme toggle and the profile chip, which pushed every page in the
     portal sideways: 86px of horizontal scroll at 320px, 16px at 390px,
     and clean only at 430px and above. Measured on all 13 pages.
     Nothing caught it because a page that scrolls sideways still renders,
     still passes check-classes, and looks correct on a desktop window —
     macOS will not make a window narrow enough to show it. */
  .side-foot, .sb-footer {
    flex-direction: row; align-items: center; gap: 8px; flex-wrap: wrap;
  }
  .side-foot .who, .sb-user { flex: 1; }
  /* Let the profile chip take the slack and ellipsis a long name, rather
     than being the thing that refuses to shrink. */
  .side-foot-row { flex: 1 1 150px; min-width: 0; }
  /* The form is a layout wrapper with no business having width. */
  .side-foot > form { flex: 0 0 auto; margin: 0; }
  .side-foot .logout { width: auto; white-space: nowrap; }

  /* Tap targets. iOS asks for 44px; these were 34 to 36 and sit next to
     each other in the footer, which is where a mis-tap signs you out. */
  .side-foot .logout, .mention-bell, .palette-trigger { min-height: 44px; }
  .mention-bell { width: 44px; }

  /* Monitoring's view switcher is an inline-flex pill holding four tabs.
     At 320px they add up to 57px more than the screen, and being inline
     it cannot wrap, so it pushed the page sideways on its own — the only
     page still broken once the footer was fixed. It scrolls sideways
     instead, like the nav strip above it, keeping the pill shape. */
  .view-switcher {
    display: flex; max-width: 100%; overflow-x: auto;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .view-switcher::-webkit-scrollbar { display: none; }
  .view-tab { flex-shrink: 0; }

  /* The pre-bottom-bar padding lived here and, being LATER in the file
     at equal specificity, silently beat the rule above that clears the
     fixed top bar and tab bar. Content sat under the top bar and behind
     the tabs. Caught by measuring padding-top and finding 0px; a
     screenshot shows content under a translucent bar and reads as a
     design choice. Padding now belongs to that one rule only. */
  .top, .admin-topbar { padding: 16px 0 4px; }
  .tiles { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
}

/* glass.css draws the active-link accent bar as an absolutely positioned
   ::before, but never gives .sb-link a positioning context — so the bar
   escapes to the nearest positioned ancestor and lands at the page edge.
   Establishing the context here keeps glass.css verbatim. */
.sb-link, .sb-item, .side nav a { position: relative; }

/* ── Tarly Super Admin ───────────────────────────────────────────── */
/* glass.css ships .stats-row.cols-2/3/4 — fixed column counts. The estate
   dashboard has SEVEN tiles, and cols-4 leaves a ragged 4+3 with three
   double-width cards on the second row. Auto-fit keeps them even at every
   width without inventing a .cols-7 nobody else would use. */
.stats-row.cols-auto { grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); }
@media (max-width: 900px) {
  .stats-row.cols-auto { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
}

/* Tier pills. The generic .badge-* set in glass.css is named for statuses
   (lead/active/paused), and reusing .badge-lead to mean "super tier" would
   make both names lie. */
.badge-tier-standard { background: rgba(125,138,158,.18); color: var(--muted); border-color: rgba(125,138,158,.3); }
.badge-tier-super    { background: var(--blue-dim);  color: var(--blue);  border-color: rgba(96,165,250,.28); }
.badge-tier-owner    { background: var(--amber-dim); color: var(--amber); border-color: var(--amber-border); }

/* The inline tier switcher lives in a table cell, where a full-width
   100%-padding input looks like a data-entry form rather than a control. */
select.auto-submit { padding: 6px 10px; font-size: .8rem; }

/* Checkbox tile — used by the org feature grid. A bare checkbox in a
   .form-grid inherits the input rules above (width:100%), which stretches
   the box itself across the column. */
.check-tile {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 13px; border-radius: var(--radius-sm);
  background: var(--input-bg);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  color: var(--subtle); font-size: .85rem; cursor: pointer;
  transition: background var(--dur-base), border-color var(--dur-base);
}
.check-tile:hover { background: var(--glass-2); border-color: var(--amber-border); }
.check-tile input[type=checkbox] {
  width: auto; flex-shrink: 0; accent-color: var(--amber); margin: 0;
}

/* ═══════════════════════════════════════════════════════════════════
   PORTAL TASKS — kanban board, task detail, comments
   ═══════════════════════════════════════════════════════════════════
   Ported from the <style> block in pages/portal/tasks.php. It lives here
   rather than in glass.css because glass.css is a verbatim copy of the
   original design system and stays that way; and it lives in a
   stylesheet rather than inline on the page so it is cached once and
   needs no style-src nonce.
   ═══════════════════════════════════════════════════════════════════ */

/* ── View switcher ───────────────────────────────────────────────── */
.view-switcher {
  display: inline-flex; align-items: center; gap: 4px; padding: 5px;
  margin: 4px 0 16px;
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  border-radius: var(--radius-pill);
  backdrop-filter: var(--blur-md); -webkit-backdrop-filter: var(--blur-md);
  box-shadow: inset 0 1px 0 var(--edge-top);
}
.view-tab {
  padding: 7px 15px; border-radius: var(--radius-pill);
  font-size: .82rem; font-weight: 600; color: var(--muted);
  text-decoration: none; border: 1px solid transparent;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.view-tab:hover { background: var(--glass-2); color: var(--text); }
.view-tab.active {
  background: linear-gradient(180deg, var(--glass-4), var(--glass-3));
  color: var(--text);
  border-color: var(--edge); border-top-color: var(--edge-top);
  box-shadow: inset 0 1px 0 var(--edge-top);
}

/* ── Filter bar ──────────────────────────────────────────────────── */
.filter-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
.filter-search {
  flex: 1; min-width: 200px; max-width: 340px;
  padding: 8px 14px !important; border-radius: var(--radius-pill) !important;
  font-size: .86rem;
}
.filter-select {
  width: auto !important; min-width: 130px;
  padding: 8px 14px !important; border-radius: var(--radius-pill) !important;
  font-size: .82rem !important; color: var(--subtle); cursor: pointer;
}
/* Hidden rather than removed: the bar must not reflow as you type. */
.filter-bar #f-clear { visibility: hidden; }
.filter-count { color: var(--muted); font-size: .76rem; margin-left: auto; }

/* ── Board ───────────────────────────────────────────────────────── */
.kanban-board {
  display: grid;
  grid-template-columns: repeat(var(--kanban-cols, 4), minmax(0, 1fr));
  gap: 16px; align-items: start;
}
.kanban-col {
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  border-radius: var(--radius); padding: 14px 12px 12px;
  backdrop-filter: var(--blur-md); -webkit-backdrop-filter: var(--blur-md);
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--edge-top);
}
.kanban-col-orphan { border-color: rgba(248,113,113,.3); }
.kanban-col-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; padding: 0 4px; }
.kanban-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; box-shadow: 0 0 14px currentColor; }
.kanban-label {
  font-size: .72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .1em; color: var(--subtle);
}
.kanban-count {
  margin-left: auto; padding: 2px 9px;
  font-size: .7rem; font-weight: 600; color: var(--muted);
  background: var(--glass-2); border: 1px solid var(--edge);
  border-radius: var(--radius-pill);
}
.kanban-note { margin-top: 10px; color: var(--muted); font-size: .72rem; line-height: 1.5; }

.kanban-col-inner {
  min-height: 44px; display: flex; flex-direction: column; gap: 8px;
  padding: 4px; border-radius: var(--radius-sm);
  outline: 1px dashed transparent;
  transition: background var(--dur-fast), outline-color var(--dur-fast);
}
.kanban-col-inner.drop-armed { outline-color: var(--edge); }
.kanban-col-inner.drag-over { background: rgba(251,191,36,.06); outline-color: var(--amber-border); }
/* Only shown while a drag is in flight, or when the column is empty. */
.kanban-drop-hint {
  padding: 10px 12px; text-align: center; color: var(--muted);
  font-size: .72rem; letter-spacing: .04em; opacity: 0;
  transition: opacity var(--dur-fast);
}
.kanban-col-inner.drop-armed .kanban-drop-hint { opacity: .55; }
.kanban-col-inner > .task-card:not(.filtered-out) ~ .kanban-drop-hint { display: none; }

/* ── Task card ───────────────────────────────────────────────────── */
.task-card {
  position: relative; padding: 12px 14px; cursor: grab; user-select: none;
  background: var(--glass-2);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  border-radius: var(--radius-sm);
  backdrop-filter: blur(12px) saturate(140%); -webkit-backdrop-filter: blur(12px) saturate(140%);
  box-shadow: inset 0 1px 0 var(--edge-top);
  transition: background var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.task-card:hover {
  background: var(--glass-3);
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--edge-top);
  transform: translateY(-1px);
}
.task-card:active { cursor: grabbing; }
.task-card.dragging { opacity: .4; transform: scale(.98); }
.task-card.filtered-out, tr.task-row.filtered-out { display: none; }
.task-card-title {
  display: block; padding-right: 22px;
  font-size: .9rem; font-weight: 600; line-height: 1.4;
  color: var(--text); text-decoration: none; letter-spacing: -.005em;
}
.task-card-title:hover { color: var(--amber); }
.task-meta { margin-top: 6px; font-size: .76rem; color: var(--muted); }
.task-meta-client { color: var(--amber); }
.task-card-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-top: 10px;
}
.task-drag-hint {
  font-size: .6rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .08em; color: var(--muted);
  opacity: 0; transition: opacity var(--dur-fast);
}
.task-card:hover .task-drag-hint { opacity: .5; }

.task-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.tk-chip {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px;
  font-size: .68rem; font-weight: 600; color: var(--muted);
  background: var(--glass-1); border: 1px solid var(--edge);
  border-radius: var(--radius-pill); font-variant-numeric: tabular-nums;
}
.tk-chip-done { color: var(--green); background: var(--green-dim); border-color: rgba(52,211,153,.3); }

.task-card-del { position: absolute; top: 8px; right: 8px; margin: 0; }
.task-card-del button {
  width: 22px; height: 22px; border-radius: 50%; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  font-size: .8rem; cursor: pointer; opacity: 0;
  background: var(--glass-2); border: 1px solid var(--edge); color: var(--muted);
  transition: opacity var(--dur-fast), color var(--dur-fast), background var(--dur-fast);
}
.task-card:hover .task-card-del button { opacity: 1; }
.task-card-del button:hover {
  color: var(--red); background: rgba(248,113,113,.15); border-color: rgba(248,113,113,.3);
}

/* ── Inline quick-add ────────────────────────────────────────────── */
.quick-add { margin: 6px 0 0; }
.quick-add-input {
  width: 100%; padding: 8px 10px !important;
  background: transparent !important;
  border: 1px dashed var(--edge) !important;
  border-radius: var(--radius-sm) !important;
  color: var(--subtle) !important; font-size: .82rem !important;
}
.quick-add-input:focus {
  background: var(--glass-2) !important;
  border-style: solid !important; border-color: var(--amber-border) !important;
}

@media (max-width: 900px) {
  /* ── The board on a phone ────────────────────────────────────────
     It already snapped, and the columns were a fixed 274px inside the
     16px page gutter — so on a 390px screen a column filled 274 of the
     358 available and the next one was sliced through the middle of a
     card. A peek is good: it is what tells you the board continues. A
     peek that cuts a card in half looks like a rendering fault.

     Full-bleed instead, with the gutter moved into the scroller as
     padding so the first column still lines up with the page, and a
     column width derived from the viewport so the peek is the SAME
     44px on every phone rather than whatever is left over. */
  .kanban-board {
    display: flex; flex-direction: row; overflow-x: auto;
    gap: 12px; padding-bottom: 12px;
    scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    margin-inline: -16px;
    padding-inline: 16px;
    /* Without this a snapped column butts against the screen edge and
       loses the gutter every other element on the page has. */
    scroll-padding-inline: 16px;
    scrollbar-width: none;
  }
  .kanban-board::-webkit-scrollbar { display: none; }
  .kanban-board > .kanban-col {
    flex: 0 0 calc(100vw - 60px);
    min-width: 0;
    scroll-snap-align: start;
  }
}

/* ── Task detail: subtasks ───────────────────────────────────────── */
.tk-progress {
  height: 4px; margin-bottom: 14px; overflow: hidden;
  background: var(--glass-2); border-radius: var(--radius-pill);
}
.tk-progress-fill {
  height: 100%; border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--amber), var(--green));
  transition: width var(--dur-base) var(--ease-out);
}
.tk-sub-list { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }
.tk-sub {
  display: flex; align-items: center; gap: 10px; padding: 7px 10px;
  background: var(--glass-1); border: 1px solid transparent;
  border-radius: var(--radius-sm);
}
.tk-sub:hover { background: var(--glass-2); border-color: var(--edge); }
.tk-sub form { margin: 0; display: flex; }
.tk-sub.done .tk-sub-text { text-decoration: line-through; color: var(--muted); }
.tk-sub-check {
  width: 18px; height: 18px; flex-shrink: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: .7rem; line-height: 1; color: #1a0d00;
  background: transparent; border: 1.5px solid var(--edge-strong); border-radius: 5px;
  transition: background var(--dur-fast), border-color var(--dur-fast);
}
.tk-sub.done .tk-sub-check { background: var(--amber); border-color: var(--amber); }
.tk-sub-text { flex: 1; font-size: .88rem; color: var(--text); }
.tk-sub-add { display: flex; gap: 8px; align-items: center; }
.tk-sub-add input { flex: 1; }

.tk-linkbtn {
  background: none; border: none; cursor: pointer; padding: 2px 6px;
  border-radius: 4px; font: inherit; font-size: .74rem; color: var(--muted);
  transition: background var(--dur-fast), color var(--dur-fast);
}
.tk-linkbtn:hover { background: rgba(248,113,113,.12); color: var(--red); }

/* ── Task detail: time logged ────────────────────────────────────── */
.tk-time-list { display: flex; flex-direction: column; gap: 4px; }
.tk-time-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  background: var(--glass-1); border-radius: var(--radius-sm); font-size: .85rem;
}
.tk-time-av {
  width: 22px; height: 22px; flex-shrink: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: .66rem; font-weight: 700; color: #1a0d00; background: var(--amber);
}

/* ── Task detail: comments ───────────────────────────────────────── */
.tk-comment-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }
.tk-comment {
  display: flex; flex-wrap: wrap; gap: 10px; padding: 12px;
  background: var(--glass-1); border: 1px solid var(--edge);
  border-radius: var(--radius-sm);
}
.tk-comment-av {
  width: 28px; height: 28px; flex-shrink: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: .7rem; font-weight: 700; color: #1a0d00; background: var(--amber);
}
.tk-comment-body { flex: 1; min-width: 0; }
.tk-comment-meta {
  display: flex; align-items: center; gap: 8px; margin-bottom: 4px;
  font-size: .74rem; color: var(--muted);
}
.tk-comment-meta strong { font-size: .82rem; color: var(--text); }
.tk-comment-text { font-size: .88rem; line-height: 1.55; color: var(--subtle); word-wrap: break-word; }
.tk-comment-text code {
  padding: 1px 5px; border-radius: 4px; font-size: .82em; color: var(--amber);
  background: var(--glass-2); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.tk-comment-text strong { color: var(--text); font-weight: 700; }
.tk-comment-text a { color: var(--amber); text-decoration: underline; }
.tk-mention {
  padding: 1px 6px; border-radius: 4px; font-weight: 600; font-size: .82rem;
  background: var(--amber-dim); color: var(--amber);
}
.tk-comment-actions { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.tk-comment-actions form { margin: 0; }
.tk-comment-actions summary {
  cursor: pointer; font-size: .74rem; color: var(--muted); list-style: none;
}
.tk-comment-actions summary:hover { color: var(--text); }
.tk-reply { display: flex; gap: 8px; align-items: flex-start; margin-top: 8px; }
.tk-reply textarea { min-height: 54px; font-size: .84rem; }

.tk-composer { position: relative; }
.tk-composer-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 10px; flex-wrap: wrap;
}
.tk-mention-menu {
  position: absolute; left: 12px; top: 62px; z-index: 40; display: none;
  min-width: 190px; max-height: 220px; overflow-y: auto; padding: 4px;
  background: var(--glass-4);
  border: 1px solid var(--edge-strong); border-top-color: var(--edge-top);
  border-radius: var(--radius-sm);
  backdrop-filter: var(--blur-lg); -webkit-backdrop-filter: var(--blur-lg);
  box-shadow: var(--shadow-2), inset 0 1px 0 var(--edge-top);
}
.tk-mention-menu.open { display: block; }
.tk-mention-item {
  padding: 6px 10px; border-radius: var(--radius-sm); cursor: pointer;
  font-size: .85rem; color: var(--subtle);
  transition: background var(--dur-fast), color var(--dur-fast);
}
.tk-mention-item:hover, .tk-mention-item.active { background: var(--glass-3); color: var(--text); }

/* ── Knowledge Base ──────────────────────────────────────────────── */
/* The KB is the one portal screen that is a card grid rather than a
   table, because it groups by category and shows a per-person read
   state on each item. glass.css has no vocabulary for that, so it lives
   here rather than being faked out of .tile. */

.kb-groups { display: flex; flex-direction: column; gap: 26px; }
.kb-group-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.kb-group-head h2 {
  font-size: .7rem; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted); margin: 0;
}
.kb-group-head .rule { flex: 1; height: 1px; background: var(--edge); }
.kb-group-head .count { font-size: .72rem; color: var(--muted); }

.kb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.kb-card-wrap { display: flex; flex-direction: column; gap: 6px; }
.kb-card {
  display: block; flex: 1;
  background: var(--glass-1);
  border: 1px solid var(--edge);
  border-radius: var(--radius-lg);
  padding: 14px 16px; text-decoration: none;
  transition: border-color var(--dur-base), box-shadow var(--dur-base), transform var(--dur-base);
}
.kb-card:hover {
  border-color: var(--amber-border);
  box-shadow: var(--shadow-2);
  transform: translateY(-2px);
}
/* Required and unread reads as a warning, not decoration. */
.kb-card.required { border-color: rgba(248,113,113,.28); }
.kb-card.required:hover { border-color: var(--red); }

.kb-card-head { display: flex; align-items: flex-start; gap: 10px; }
.kb-icon {
  width: 32px; height: 32px; border-radius: 9px;
  background: var(--amber-dim);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.kb-icon svg { width: 15px; height: 15px; color: var(--amber); }
.kb-title {
  font-size: .875rem; font-weight: 600; color: var(--text);
  line-height: 1.3; word-break: break-word;
}
.kb-tags { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 5px; }
.kb-req, .kb-flag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: .63rem; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; border-radius: 3px; padding: 2px 6px;
  white-space: nowrap;
}
.kb-req { color: var(--red); border: 1px solid rgba(248,113,113,.3); }
.kb-flag { color: var(--muted); border: 1px solid var(--edge); }
.kb-status { flex-shrink: 0; margin-top: 2px; display: flex; }
.kb-meta {
  display: flex; gap: 12px; flex-wrap: wrap;
  margin-top: 10px; padding-top: 8px;
  font-size: .72rem; color: var(--muted);
  border-top: 1px solid var(--row-border);
}
.kb-card-actions { display: flex; gap: 6px; justify-content: flex-end; }
.kb-unread-pill {
  display: inline-block; margin-left: 10px; vertical-align: 3px;
  background: var(--red-dim); color: var(--red);
  border: 1px solid rgba(248,113,113,.3);
  border-radius: var(--radius-pill);
  font-size: .68rem; font-weight: 700; padding: 2px 9px;
  letter-spacing: .02em; text-transform: none;
}

/* External-resource callout on a document page. */
.kb-callout {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 18px; padding: 14px 16px;
  background: var(--amber-dim);
  border: 1px solid var(--amber-border);
  border-radius: var(--radius-lg);
}

/* Member chips. The checkbox is visually hidden but still focusable, so
   the control stays keyboard-operable — a display:none input is not. */
.kb-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.kb-chip { position: relative; cursor: pointer; }
.kb-chip > input { position: absolute; opacity: 0; width: 0; height: 0; }
.kb-chip > span {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 11px; border-radius: var(--radius-pill);
  font-size: .75rem; color: var(--subtle);
  background: var(--btn-ghost-bg); border: 1px solid var(--edge);
  transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.kb-chip > input:checked + span {
  background: var(--amber-dim); border-color: var(--amber-border); color: var(--amber);
}
.kb-chip > input:focus-visible + span { outline: 2px solid var(--amber); outline-offset: 2px; }

/* Password state pills on /kb/manage. */
.kb-pw-locked, .kb-pw-open {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .72rem; font-weight: 600;
  padding: 3px 9px; border-radius: var(--radius-pill); white-space: nowrap;
}
.kb-pw-locked { color: var(--amber); background: var(--amber-dim); border: 1px solid var(--amber-border); }
.kb-pw-open { color: var(--muted); background: var(--btn-ghost-bg); border: 1px solid var(--edge); }

/* <details> is used instead of a JS accordion so a row still opens with
   scripting unavailable. */
.kb-manage-row > summary {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  cursor: pointer; list-style: none;
}
.kb-manage-row > summary::-webkit-details-marker { display: none; }
.kb-manage-row > summary::after {
  content: '▾'; color: var(--muted); font-size: .8rem;
  transition: transform var(--dur-fast);
}
.kb-manage-row[open] > summary::after { transform: rotate(180deg); }

/* Rendered markdown-lite body. */
.kb-doc { font-size: .92rem; line-height: 1.8; color: var(--subtle); }
.kb-doc h2, .kb-doc h3, .kb-doc h4, .kb-doc h5, .kb-doc h6 {
  color: var(--text); font-weight: 700; margin: 1.5em 0 .5em; line-height: 1.35;
}
.kb-doc h2 { font-size: 1.15rem; }
.kb-doc h3 { font-size: 1rem; }
.kb-doc h4, .kb-doc h5, .kb-doc h6 { font-size: .92rem; }
.kb-doc > *:first-child { margin-top: 0; }
.kb-doc p { margin: 0 0 1em; }
.kb-doc ul, .kb-doc ol { padding-left: 1.5em; margin: 0 0 1em; }
.kb-doc li { margin-bottom: .35em; }
.kb-doc strong { color: var(--text); font-weight: 600; }
.kb-doc a { color: var(--amber); text-decoration: underline; }
.kb-doc code {
  background: var(--glass-2); border: 1px solid var(--edge);
  border-radius: var(--radius-xs); padding: 1px 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .85em; color: var(--amber);
}
.kb-doc pre {
  background: var(--glass-2); border: 1px solid var(--edge);
  border-radius: var(--radius); padding: 14px 16px;
  overflow-x: auto; margin: 0 0 1em;
}
.kb-doc pre code { background: none; border: none; padding: 0; color: var(--subtle); }
.kb-doc blockquote {
  border-left: 3px solid var(--amber);
  padding-left: 14px; margin: 0 0 1em; color: var(--muted); font-style: italic;
}
.kb-doc hr { border: none; border-top: 1px solid var(--edge); margin: 1.5em 0; }

@media (max-width: 720px) {
  .kb-grid { grid-template-columns: 1fr; }
}


/* ═══════════════════════════════════════════════════════════════════
   PORTAL — Time, Reports and Team Time
   ═══════════════════════════════════════════════════════════════════
   Ports the inline <style> blocks from pages/portal/{time,reports,
   team-time}.php onto the glass tokens. Everything here is scoped to a
   class those three screens render, so nothing leaks into the panels.
   ═══════════════════════════════════════════════════════════════════ */

/* glass.css styles inputs by [type], but an <input> with no type
   attribute is still a text field and was rendering unstyled. */
input:not([type]), input[type="search"] {
  width: 100%; padding: 10px 14px;
  background: var(--input-bg);
  border: 1px solid var(--edge); border-top-color: var(--edge-strong);
  border-radius: var(--radius-sm);
  color: var(--text); font-size: .88rem; outline: none;
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
  transition: border-color var(--dur-fast), background var(--dur-base), box-shadow var(--dur-fast);
}
input:not([type]):focus, input[type="search"]:focus {
  border-color: var(--amber-border); box-shadow: 0 0 0 3px var(--amber-dim);
  background: var(--glass-2);
}

/* ── Shared atoms ────────────────────────────────────────────────── */
.chip {
  display: inline-block; max-width: 100%;
  padding: 1px 7px; border-radius: var(--radius-xs);
  background: var(--amber-dim); border: 1px solid var(--amber-border);
  color: var(--amber); font-size: .72rem; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle;
}
.dot {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
  display: inline-block; background: var(--amber);
}
.dot.good { background: var(--green); box-shadow: 0 0 5px var(--green); }
.dot.warn { background: var(--amber); }
.dot.bad  { background: var(--red); }
.dot.none { background: var(--border2); }
.dot.pulse { animation: portalPulse 1.6s ease-in-out infinite; }
@keyframes portalPulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--amber-dim); }
  60%      { box-shadow: 0 0 0 7px rgba(251,191,36,0); }
}
@media (prefers-reduced-motion: reduce) { .dot.pulse { animation: none; } }

.pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: var(--radius-pill);
  font-size: .7rem; font-weight: 700; letter-spacing: .03em;
  background: var(--glass-2); border: 1px solid var(--edge); color: var(--subtle);
  white-space: nowrap;
}
.pill.good { background: var(--green-dim); border-color: rgba(52,211,153,.3); color: var(--green); }
.pill.warn { background: var(--amber-dim); border-color: var(--amber-border); color: var(--amber); }
.pill.bad  { background: var(--red-dim);   border-color: rgba(248,113,113,.3); color: var(--red); }

/* Progress bar used by report shares, member load and entry duration. */
.pbar { height: 5px; border-radius: 3px; background: var(--border2); overflow: hidden; width: 100%; }
.pbar > i { display: block; height: 100%; border-radius: 3px; background: var(--amber); }
.pbar.good > i { background: var(--green); }
.pbar.warn > i { background: var(--amber); }
.pbar.cool > i { background: var(--blue); }

.sec-head { display: flex; align-items: center; gap: 10px; margin: 4px 0 12px; flex-wrap: wrap; }
.sec-title {
  font-size: .75rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
}
.sec-note { font-size: .75rem; color: var(--muted); }

.filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0; }
.filters label {
  font-size: .72rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted); white-space: nowrap;
}
.filters select, .filters input[type="date"] { width: auto; padding: 7px 10px; font-size: .82rem; }

td.clip, .clip { max-width: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
td.num, .num { font-variant-numeric: tabular-nums; white-space: nowrap; font-size: .82rem; }
td.hot, .hot { color: var(--amber); font-weight: 700; font-variant-numeric: tabular-nums; }
tr.unbilled td, .entryrow.unbilled { opacity: .62; }
.tzs { color: var(--muted); font-size: .65rem; font-style: normal; }

/* ── Timer bar ───────────────────────────────────────────────────── */
.timerbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding: 14px 18px; margin-bottom: 18px;
  border-radius: var(--radius-lg);
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-md); -webkit-backdrop-filter: var(--blur-md);
  box-shadow: var(--shadow-1), inset 0 1px 0 var(--edge-top);
  transition: border-color var(--dur-base), box-shadow var(--dur-base);
}
.timerbar.on { border-color: var(--amber-border); box-shadow: var(--shadow-glow-amber); }
.timer-left { display: flex; align-items: center; gap: 14px; min-width: 0; }
.timer-clock {
  font-size: 2rem; font-weight: 700; line-height: 1; letter-spacing: .05em;
  color: var(--amber); font-variant-numeric: tabular-nums;
}
.timer-meta {
  display: flex; align-items: center; gap: 8px; margin-top: 5px;
  font-size: .8rem; color: var(--subtle); flex-wrap: wrap;
}
.timer-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.timer-actions form { margin: 0; }
.timer-start { display: flex; align-items: center; gap: 10px; width: 100%; flex-wrap: wrap; margin: 0; }
.timer-start select { width: 165px; padding: 8px 10px; font-size: .82rem; }
.timer-desc {
  flex: 1; min-width: 180px;
  background: transparent !important; border: none !important;
  box-shadow: none !important; padding: 0 !important;
  font-size: .95rem; color: var(--text);
}

/* ── Week navigation ─────────────────────────────────────────────── */
.weeknav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 12px;
}
.weeknav-left { display: flex; align-items: center; gap: 10px; }
.weeknav-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; line-height: 1;
  border-radius: var(--radius-xs); border: 1px solid var(--edge);
  background: var(--btn-ghost-bg); color: var(--muted);
  font-size: 1.25rem; text-decoration: none;
  transition: border-color var(--dur-base), color var(--dur-base);
}
.weeknav-arrow:hover { border-color: var(--amber-border); color: var(--amber); }
.weeknav-label { font-size: .92rem; font-weight: 600; color: var(--text); }
.weeknav-stats { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.stat-inline { display: flex; align-items: baseline; gap: 7px; }
.stat-inline em {
  font-style: normal; font-size: .72rem; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted);
}
.stat-inline b { font-size: 1.3rem; font-weight: 700; color: var(--amber); font-variant-numeric: tabular-nums; }
.stat-inline small { font-size: .7rem; color: var(--muted); }
.stat-inline.money { padding-left: 20px; border-left: 1px solid var(--edge); }
.stat-inline.money b { color: var(--green); }

/* ── 7-day week chart ────────────────────────────────────────────── */
.weekchart {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; align-items: end;
  padding: 16px 20px 12px; margin-bottom: 20px;
  border-radius: var(--radius-lg);
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
}
.wc-col { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.wc-barwrap { width: 100%; height: 80px; display: flex; align-items: flex-end; justify-content: center; }
.wc-bar { width: 60%; min-height: 3px; border-radius: 3px 3px 0 0; background: var(--border2); }
.wc-bar.on { background: var(--amber); }
.wc-label { font-size: .7rem; font-weight: 500; letter-spacing: .03em; color: var(--muted); }
.wc-col.today .wc-label { color: var(--amber); font-weight: 700; }
.wc-col.today .wc-bar { box-shadow: 0 0 0 1px var(--amber-border); }
.wc-hours { font-size: .65rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ── Day groups ──────────────────────────────────────────────────── */
.daylist { display: flex; flex-direction: column; gap: 12px; }
.daygroup {
  border-radius: var(--radius-lg); overflow: hidden;
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
}
.dayhead {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 16px; background: var(--thead-bg); border-bottom: 1px solid var(--edge);
}
.dayhead-name { display: flex; align-items: center; gap: 8px; font-size: .85rem; font-weight: 600; color: var(--text); }
.dayhead-right { display: flex; align-items: center; gap: 12px; }
.dayhead-right b { font-size: .9rem; color: var(--text); font-variant-numeric: tabular-nums; }
.badge-today {
  padding: 1px 7px; border-radius: var(--radius-xs);
  background: var(--amber-dim); border: 1px solid var(--amber-border); color: var(--amber);
  font-size: .63rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
}
.addbtn {
  width: 26px; height: 26px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-xs); border: 1px solid var(--edge);
  background: var(--btn-ghost-bg); color: var(--muted); font-size: 1.1rem; font-family: inherit;
  transition: border-color var(--dur-base), color var(--dur-base), background var(--dur-base);
}
.addbtn:hover { border-color: var(--amber-border); color: var(--amber); background: var(--amber-dim); }

.addform { padding: 10px 16px; background: var(--thead-bg); border-bottom: 1px solid var(--edge); }
.addform[hidden] { display: none; }
.addrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0; }
.addrow select { width: 140px; padding: 6px 9px; font-size: .8rem; }
.af-desc { flex: 1; min-width: 170px; padding: 6px 10px !important; font-size: .85rem; }
.af-time { width: 105px !important; padding: 6px 8px !important; font-size: .8rem; }
.af-mins { width: 72px !important; padding: 6px 8px !important; font-size: .8rem; }
.af-sep, .af-or { color: var(--muted); font-size: .75rem; }
.af-bill { display: flex; align-items: center; gap: 4px; font-size: .82rem; color: var(--muted); cursor: pointer; }
.af-bill input { width: auto; }
.dayempty { padding: 14px 16px; font-size: .8rem; color: var(--muted); }

/* ── Entry rows ──────────────────────────────────────────────────── */
.entryrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 150px 150px 74px 52px 34px;
  align-items: center; gap: 10px;
  padding: 11px 16px; border-bottom: 1px solid var(--row-border);
  transition: background var(--dur-fast);
}
.entryrow:last-child { border-bottom: none; }
.entryrow:hover { background: var(--hover-bg); }
.entry-desc { font-size: .875rem; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.entry-client { min-width: 0; overflow: hidden; }
.entry-time { font-size: .78rem; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.entry-dur { font-size: .9rem; font-weight: 700; color: var(--amber); text-align: right; font-variant-numeric: tabular-nums; }
.entry-bill { display: flex; align-items: center; gap: 5px; justify-content: center; }
.entry-bill .bill {
  font-size: .68rem; color: var(--amber);
  border: 1px solid var(--amber-border); border-radius: 3px; padding: 0 4px;
}
.entry-bill .src { font-size: .7rem; color: var(--muted); }
.entry-del { text-align: center; }
.entry-del form { margin: 0; }
.delbtn {
  background: none; border: none; cursor: pointer; font-family: inherit;
  color: var(--border2); font-size: 1.05rem; line-height: 1;
  padding: 2px 5px; border-radius: var(--radius-xs);
  transition: color var(--dur-base), background var(--dur-base);
}
.delbtn:hover { color: var(--red); background: var(--red-dim); }

/* ── Demo workspace banner ───────────────────────────────────────── */
.demo-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 16px; padding: 10px 14px;
  border-radius: var(--radius-sm);
  background: var(--amber-dim); border: 1px solid var(--amber-border);
  color: var(--text); font-size: .82rem;
}
.demo-bar b { color: var(--amber); }
.demo-bar-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--amber); flex: none; }
.demo-bar-cta {
  margin-left: auto; font-weight: 600; color: var(--amber);
  text-decoration: underline; text-underline-offset: 3px;
}
@media (max-width: 768px) {
  .demo-bar-cta { margin-left: 0; flex: 1 0 100%; }
}

/* ── Client detail task rows ─────────────────────────────────────── */
.ctask {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  background: var(--glass-2); border: 1px solid var(--edge); border-radius: 8px;
  color: var(--text); text-decoration: none;
  transition: border-color var(--dur-base), background var(--dur-base);
}
.ctask:hover { border-color: var(--amber-border); background: var(--glass-1); }
.ctask-title { flex: 1; font-size: .85rem; color: var(--text); min-width: 0; }
.ctask-meta { font-size: .74rem; color: var(--muted); white-space: nowrap; }

/* ── Reports ─────────────────────────────────────────────────────── */
.views {
  display: flex; gap: 2px; padding: 3px;
  border-radius: var(--radius); background: var(--glass-1); border: 1px solid var(--edge);
}
.view-btn {
  padding: 5px 14px; border-radius: var(--radius-xs);
  font-size: .78rem; font-weight: 600; color: var(--muted); text-decoration: none;
  transition: background var(--dur-base), color var(--dur-base);
}
.view-btn:hover { color: var(--text); background: var(--glass-2); }
.view-btn.on {
  background: linear-gradient(145deg, var(--amber), var(--amber-strong));
  color: #1a1204;
}
.daybars { display: flex; align-items: flex-end; gap: 4px; min-height: 100px; }
.daybar-wrap { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; justify-content: flex-end; min-width: 0; }
.daybar { width: 100%; min-height: 3px; border-radius: 3px 3px 0 0; background: var(--border2); }
.daybar.on { background: var(--amber); }
.daybar.on:hover { background: var(--amber-strong); }
.daybar-lbl {
  font-size: .62rem; color: var(--muted); text-align: center; line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.daybar-lbl span { display: block; opacity: .7; }
.sharecell { display: flex; align-items: center; gap: 8px; }
.sharecell .pbar { width: 80px; flex-shrink: 0; }
.sharecell span { font-size: .75rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ── Team Time ───────────────────────────────────────────────────── */
.live-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(262px, 1fr)); gap: 12px; margin-bottom: 26px; }
.live-card {
  padding: 16px 18px; border-radius: var(--radius-lg);
  background: var(--glass-1);
  border: 1px solid var(--amber-border); border-top-color: var(--amber-border);
  backdrop-filter: var(--blur-md); -webkit-backdrop-filter: var(--blur-md);
  box-shadow: var(--shadow-glow-amber);
}
.live-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.live-name { display: flex; align-items: center; gap: 8px; font-size: .9rem; font-weight: 700; color: var(--text); }
.live-clock {
  font-size: 1.7rem; font-weight: 700; line-height: 1; letter-spacing: .04em;
  color: var(--amber); font-variant-numeric: tabular-nums;
}
.live-sub { font-size: .72rem; color: var(--muted); margin-top: 5px; }
.live-sub b { color: var(--subtle); }
.live-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--edge);
  font-size: .75rem; color: var(--muted);
}
.live-meta em { font-style: normal; color: var(--border2); }
.live-work { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; margin-top: 8px; }
.live-desc { font-size: .78rem; color: var(--muted); font-style: italic; }
.idle { display: flex; align-items: center; gap: 5px; font-size: .68rem; }
.idle.good { color: var(--green); }
.idle.warn { color: var(--amber); }
.idle.bad  { color: var(--red); }
.idle.none { color: var(--muted); }
.idle.good .dot { background: var(--green); box-shadow: 0 0 4px var(--green); }
.idle.warn .dot { background: var(--amber); }
.idle.bad  .dot { background: var(--red); }
.idle.none .dot { background: var(--border2); }

.mem-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(222px, 1fr)); gap: 12px; margin-bottom: 26px; }
.mem-card {
  padding: 14px 16px; border-radius: var(--radius-lg);
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
}
.mem-card.on { border-color: var(--amber-border); box-shadow: 0 0 0 1px var(--amber-border); }
.mem-name { display: flex; align-items: center; gap: 7px; margin-bottom: 10px; font-size: .88rem; font-weight: 600; color: var(--text); }
.mem-name em { margin-left: auto; font-style: normal; font-size: .65rem; font-weight: 400; color: var(--muted); }
.mem-row { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 4px; font-size: .75rem; color: var(--muted); }
.mem-row b { color: var(--subtle); font-weight: 500; font-variant-numeric: tabular-nums; }
.mem-row b.hot { color: var(--amber); font-weight: 700; }
.mem-card .pbar { margin-top: 8px; height: 4px; }

.qrow { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.qbtn {
  display: inline-flex; align-items: center; padding: 5px 12px;
  border-radius: var(--radius-xs); border: 1px solid var(--edge);
  background: var(--btn-ghost-bg); color: var(--subtle);
  font-size: .78rem; font-weight: 500; text-decoration: none;
  transition: background var(--dur-base), border-color var(--dur-base), color var(--dur-base);
}
.qbtn:hover, .qbtn.on { background: var(--amber-dim); border-color: var(--amber-border); color: var(--amber); }

tr.datehead td {
  background: var(--thead-bg); color: var(--muted);
  font-size: .7rem; font-weight: 600; letter-spacing: .07em; text-transform: uppercase;
  padding: 6px 14px;
}
.datehead-sum {
  margin-left: 10px; padding: 1px 7px; border-radius: var(--radius-pill);
  background: var(--glass-2); color: var(--subtle); font-size: .65rem; letter-spacing: 0;
}
.durcell { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.durcell .pbar { width: 56px; height: 3px; flex-shrink: 0; }
.durcell b { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── Mobile ──────────────────────────────────────────────────────── */
@media (max-width: 768px) {
  .timerbar { flex-direction: column; align-items: stretch; }
  .timer-actions { margin-left: 0; }
  .timer-actions form, .timer-actions .btn { flex: 1; justify-content: center; }
  .timer-clock { font-size: 1.75rem; }
  /* The start form: description, then client and task at full width,
     then a full-width Start. On a desktop the two 165px selects sit
     beside the button; on a phone that row wrapped into two selects
     with Start crammed at the right and dead space beside it. */
  .timer-start { flex-direction: column; align-items: stretch; gap: 10px; }
  .timer-start select { width: 100%; }
  .timer-desc { min-width: 0; padding: 4px 0 !important; }
  .timer-start .btn { width: 100%; justify-content: center; }
  /* The week navigator stays ONE row on a phone: arrow, label, arrow.
     Stacking .weeknav-left into a column put the two arrows on separate
     lines with the label between them, which read as three unrelated
     controls. The stats still drop below. */
  .weeknav, .weeknav-stats { flex-direction: column; align-items: flex-start; gap: 8px; }
  .weeknav-left { width: 100%; gap: 8px; }
  .weeknav-label { flex: 1; text-align: center; min-width: 0; }
  .weeknav-left .btn-xs { order: 3; width: 100%; justify-content: center; margin-top: 4px; }
  .weeknav-left .weeknav-arrow:last-child { order: 2; }

  /* Filter forms (.filters) are label/control pairs laid out inline on
     a desktop. On a phone the pairs wrapped mid-pair, so a label sat at
     the end of one line and its control at the start of the next. Stack
     them: every label above its own full-width control. */
  .filters { display: grid; grid-template-columns: 1fr; gap: 6px; align-items: stretch; }
  .filters label { margin-top: 6px; }
  .filters select, .filters input[type="date"] { width: 100%; }
  .filters .btn, .filters .sec-note { margin-left: 0 !important; margin-top: 6px; width: 100%; justify-content: center; text-align: center; }

  /* Quick-range and tab strips scroll sideways in one row instead of
     wrapping into three. The fade at the right edge says there is more;
     it disappears at the end (background-attachment: local/scroll). */
  .qrow, .view-switcher {
    flex-wrap: nowrap; overflow-x: auto; max-width: 100%;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    background-image:
      linear-gradient(to right, var(--bg) 0, transparent 14px),
      linear-gradient(to left, var(--bg) 0, transparent 14px);
    background-attachment: local, scroll;
    background-repeat: no-repeat; background-size: 14px 100%;
    background-position: left, right;
  }
  .qrow::-webkit-scrollbar, .view-switcher::-webkit-scrollbar { display: none; }
  .qbtn, .view-tab { flex: 0 0 auto; white-space: nowrap; }
  .view-switcher { display: flex; }

  /* Knowledge Base toolbar: search on its own row, then the actions two
     per row with the primary first. Five controls in one flex-wrap row
     became three ragged lines. */
  .kb-tools { width: 100%; }
  .kb-tools form { flex: 1 0 100%; order: -2; }
  .kb-tools form input[type="search"] { width: 100%; }
  .kb-tools .btn { flex: 1 1 calc(50% - 4px); justify-content: center; }
  .kb-tools .btn-primary { order: -1; }

  /* Column manager archive control: select and button on their own
     lines instead of the button being clipped at the card edge. */
  .colmgr .td-actions form { flex-wrap: wrap; }
  .colmgr .td-actions select { width: 100% !important; }

  /* A column the page marked <th class="m-hide"> is dropped from the
     card: tables-mobile.js copies the flag onto each cell. Used for
     columns that are useful in a wide table and noise in a card, like
     "Added" on Clients. */
  .is-cards td[data-mhide] { display: none; }

  /* ── Row mode: table[data-mrows] ─────────────────────────────────
     A phone list, not a stack of labelled fields: title, status badge,
     one muted line (tables-mobile.js joins the m-sub cells into
     data-msub), a chevron when the row opens something. Every other cell
     is hidden; it is all on the record's own page, one tap away. */
  .is-cards.m-rows tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto 12px;
    grid-template-rows: auto auto;
    column-gap: 10px; row-gap: 2px; align-items: center;
    padding: 11px 12px 11px 14px; margin-bottom: 8px;
  }
  .is-cards.m-rows td { display: none; padding: 0; border: 0; }
  .is-cards.m-rows td::before { content: none; }
  .is-cards.m-rows td:first-child {
    display: block; grid-column: 1; grid-row: 1;
    padding: 0; font-size: .92rem; min-width: 0;
  }
  .is-cards.m-rows td:first-child > div { font-size: .74rem; }
  .is-cards.m-rows td[data-mrole="status"] {
    display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end;
    grid-column: 2; grid-row: 1; padding: 0;
  }
  .is-cards.m-rows tr[data-msub]::after {
    content: attr(data-msub);
    grid-column: 1 / 3; grid-row: 2;
    color: var(--muted); font-size: .78rem; line-height: 1.4;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .is-cards.m-rows tr[data-href]::before {
    content: ''; grid-column: 3; grid-row: 1 / 3; justify-self: end;
    width: 7px; height: 7px;
    border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
    transform: rotate(-45deg);
  }

  /* The card opens its own record (tables-mobile.js sets data-href from
     the first cell's link), so the View button at its foot is the card
     twice. Edit stays. */
  .is-cards tr[data-href] { cursor: pointer; }
  .is-cards a[data-mview] { display: none; }

  /* Five stat tiles in two columns leave one orphan at half width. An odd
     last tile takes the full row instead. */
  .stats-row.cols-auto > :last-child:nth-child(odd) { grid-column: 1 / -1; }

  /* Client detail task rows: title on its own line, then the meta. */
  .ctask { flex-wrap: wrap; row-gap: 4px; }
  .ctask-title { flex: 1 0 100%; order: -1; white-space: normal; }
  .stat-inline.money { padding-left: 0; border-left: none; }
  .weekchart { padding: 12px 10px 10px; gap: 3px; }
  /* Six columns won't fit a phone: description and client each take a
     line of their own, and the time/duration/flags/delete cluster share
     the last one. */
  .entryrow { grid-template-columns: minmax(0, 1fr) 74px 52px 30px; row-gap: 5px; }
  .entry-desc { grid-column: 1 / -1; white-space: normal; }
  .entry-client { grid-column: 1 / -1; }
  .entry-time { grid-column: 1 / 2; }
  .views { align-self: flex-start; }
  .live-grid, .mem-grid { grid-template-columns: 1fr; }
  .daybar-lbl { font-size: .56rem; }
}

/* ── Monitoring capture (Time page) ──────────────────────────────── */
/* The transparency panel, the screen-share error and the live status
   readout that public/assets/js/monitor.js writes into. */

/* Several atoms here set an explicit `display` (.chip is inline-block,
   .btn is inline-flex), which beats the UA stylesheet's `[hidden]` rule
   on specificity — so a node toggled with the `hidden` property stayed
   on screen. One global rule, as in most modern resets. */
[hidden] { display: none !important; }

.mon-error {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 14px; margin: -8px 0 14px;
  border-radius: var(--radius-sm);
  background: var(--red-dim); border: 1px solid rgba(248, 113, 113, .3);
  color: var(--red); font-size: .82rem; line-height: 1.5;
}

.mon-panel {
  margin: 0 0 18px;
  border-radius: var(--radius-lg);
  background: var(--glass-1);
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
  font-size: .8rem;
}
.mon-panel > summary {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 16px; cursor: pointer; list-style: none;
  color: var(--subtle); font-weight: 600;
}
.mon-panel > summary::-webkit-details-marker { display: none; }
.mon-panel > summary::after {
  content: '▾'; margin-left: auto; color: var(--muted);
  transition: transform var(--dur-base);
}
.mon-panel[open] > summary::after { transform: rotate(180deg); }
.mon-panel-body { padding: 0 16px 14px; color: var(--muted); line-height: 1.65; }
.mon-list { margin: 2px 0 0; padding: 0; list-style: none; }
.mon-list li {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 7px 0; border-top: 1px solid var(--border2);
}
.mon-list li:first-child { border-top: none; }
.mon-what { color: var(--text); font-weight: 600; flex: 0 0 auto; min-width: 130px; }
.mon-note {
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border2);
  font-size: .74rem; color: var(--muted);
}
.mon-note b { color: var(--subtle); }

.mon-status {
  font-size: .7rem; color: var(--muted); white-space: nowrap;
  display: inline-flex; align-items: center; gap: 6px;
}
.mon-status::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--green); flex-shrink: 0;
}
.mon-status[data-state="warn"] { color: var(--amber); }
.mon-status[data-state="warn"]::before { background: var(--amber); }

@media (max-width: 768px) {
  .mon-list li { flex-direction: column; gap: 2px; }
  .mon-what { min-width: 0; }
  .mon-status { display: none; }
}

/* ─── Monitoring (portal/monitoring.tsx) ────────────────────────────
   The consolidated Live / Screenshots / Activity / Connection area.
   `cap-*` is the "what is being captured" header; `shot-*` is the
   gallery, which previously carried its whole layout in inline styles.
   Deliberately NOT named `mon-*`: that prefix already belongs to the
   member-facing transparency panel on the Time page, and reusing it
   would make one edit change two unrelated screens. */

.cap-summary {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 12px 16px; margin-bottom: 16px;
  background: var(--glass-1); border: 1px solid var(--edge);
  border-radius: var(--radius-lg);
}
.cap-lead { font-size: .82rem; color: var(--muted); white-space: nowrap; }
.cap-lead b { color: var(--text); font-weight: 700; font-size: .95rem; }
.cap-items { display: flex; gap: 8px; flex-wrap: wrap; }
.cap-item {
  display: inline-flex; flex-direction: column; gap: 1px;
  padding: 5px 11px; border-radius: var(--radius-pill);
  background: var(--glass-2); border: 1px solid var(--edge);
}
/* Zero stays muted: "0 / 10 keyboard" is the reassuring answer, and
   colouring it like the others would make it read as switched on. */
.cap-item b { font-size: .78rem; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; }
.cap-item span { font-size: .64rem; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.cap-item.on b { color: var(--amber); }
.cap-link { margin-left: auto; font-size: .76rem; color: var(--amber); text-decoration: none; white-space: nowrap; }
.cap-link:hover { text-decoration: underline; }

/* glass.css sets `a { color: inherit; text-decoration: none }`, so a
   drill-down link inside a table is indistinguishable from plain text
   without this. */
.drill { color: var(--text); border-bottom: 1px dotted var(--border2); }
.drill:hover { color: var(--amber); border-bottom-color: var(--amber-border); }

.shot-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 12px; margin-bottom: 8px;
}
.shot-card { overflow: hidden; text-decoration: none; padding: 0; }
.shot-card img {
  width: 100%; height: 150px; object-fit: cover; display: block;
  /* Solid backdrop while the lazy image loads, so the grid does not
     flicker through the glass panel behind it. */
  background: #111;
}
.shot-meta { padding: .55rem .7rem; display: flex; flex-direction: column; gap: 2px; }
.shot-meta b { font-size: .78rem; font-weight: 600; color: var(--text); }
.shot-meta span { font-size: .7rem; color: var(--muted); font-variant-numeric: tabular-nums; }

@media (max-width: 768px) {
  .cap-summary { align-items: flex-start; flex-direction: column; gap: 10px; }
  .cap-link { margin-left: 0; }
  .shot-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

/* ── Persistent timer widget ───────────────────────────────────────
   The sidebar control rendered by src/portal/timerwidget.tsx on every
   portal page and driven by /assets/js/timer.js.

   It sits in .side-foot, which is a flex column, so everything here is
   sized against a ~220px sidebar: the clock is the only thing allowed to
   be big, and both text rows clip rather than wrap a client name into
   three lines and push "Sign out" off the bottom. */
.tw {
  padding: 10px; margin: 0 0 4px;
  border: 1px solid var(--edge); border-top-color: var(--edge-top);
  border-radius: var(--radius-sm);
  background: var(--glass-2);
  display: flex; flex-direction: column; gap: 8px;
}
.tw[hidden] { display: none; }

.tw-clockline { display: flex; align-items: center; gap: 8px; }
.tw-clock {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.02rem; font-weight: 700; color: var(--text);
  letter-spacing: .01em; font-variant-numeric: tabular-nums;
}

.tw-meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.tw-meta .badge { align-self: flex-start; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tw-desc {
  font-size: .72rem; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.tw-actions { display: flex; gap: 6px; }
.tw-actions .btn { flex: 1; justify-content: center; }

.tw-idle { display: flex; flex-direction: column; gap: 8px; }
/* .fld's default 14px bottom margin would double the flex gap. */
.tw-fld { margin-bottom: 0; gap: 4px; }
.tw-fld select { font-size: .74rem; padding: 5px 8px; }
.tw-start { width: 100%; justify-content: center; }

.tw-note { font-size: .7rem; line-height: 1.4; color: var(--muted); margin: 0; }
.tw-note[data-tone="err"] { color: var(--red); }

/* Idle prompt. .glass-modal/.glass-modal-panel carry the surface; this
   is only the typography and the button row inside it. */
.tw-modal-panel { max-width: 440px; }
.tw-modal-title {
  font-family: Sora, sans-serif; font-size: 1.05rem; font-weight: 700;
  color: var(--text); margin: 0 0 10px;
}
.tw-modal-body { font-size: .86rem; color: var(--subtle); margin: 0 0 8px; line-height: 1.5; }
.tw-modal-hint { font-size: .74rem; color: var(--muted); margin: 0 0 18px; line-height: 1.45; }
.tw-modal-actions { display: flex; gap: 10px; flex-wrap: wrap; }
/* Keep is first and primary; subtracting is the deliberate second choice. */
.tw-modal-actions .btn { flex: 1 1 auto; justify-content: center; }

/* ═══════════════════════════════════════════════════════════════════
   COMMAND PALETTE (⌘K) + MENTION BELL
   ═══════════════════════════════════════════════════════════════════
   Markup comes from public/assets/js/palette.js and the two components
   in src/portal/search.tsx. The overlay and panel reuse .glass-modal /
   .glass-modal-panel from glass.css; everything here is the palette's
   own vocabulary on top of them. */

/* .glass-modal sets display:flex, and a class selector outranks the UA
   sheet's [hidden]{display:none} — so without this the palette would be
   permanently visible over every page. Do not delete. */
.palette-overlay[hidden] { display: none; }

/* Top-aligned rather than centred: a list that grows downwards should
   not push its own search box up the screen as results arrive. */
.palette-overlay {
  align-items: flex-start;
  padding: 12vh 20px 20px;
}
.palette-panel {
  max-width: 620px;
  padding: 0;
  overflow: hidden;          /* the results pane does the scrolling */
  display: flex; flex-direction: column;
  max-height: min(70vh, 640px);
}

/* Visually hidden, still announced. Used for the dialog's title and the
   result-count live region. */
.palette-sr {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

.palette-search {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--edge);
}
.palette-input {
  flex: 1; min-width: 0;
  background: transparent; border: 0; outline: none;
  color: var(--text); font: inherit; font-size: 1rem;
  padding: 2px 0;
}
.palette-input::placeholder { color: var(--muted); }
.palette-close {
  flex-shrink: 0; cursor: pointer; font: inherit;
  font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: 5px 9px; border-radius: var(--radius-sm);
  background: var(--btn-ghost-bg); border: 1px solid var(--edge);
  color: var(--muted);
  transition: background var(--dur-base), color var(--dur-base);
}
.palette-close:hover { background: var(--btn-ghost-hover); color: var(--text); }

.palette-results { overflow-y: auto; padding: 6px; flex: 1; }
.palette-group + .palette-group { margin-top: 4px; }
.palette-group-label {
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
  padding: 10px 10px 5px;
}

.palette-opt {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: var(--radius-sm);
  cursor: pointer; border: 1px solid transparent;
}
/* Selection is a class, not :hover — the keyboard and the mouse drive the
   same highlight, so two rows can never look chosen at once.
 *
 * NOT --glass-3, AND NOT --edge-top. This is the one rule on the page
 * that says what Enter is about to do, and in light mode it said nothing
 * at all: the glass tiers are white in BOTH themes (they are frosted
 * white meant to sit on the page background), so a --glass-3 fill came
 * out as rgba(255,255,255,.85) on a panel that is also
 * rgba(255,255,255,.85). Identical values. The selected row was
 * literally invisible, and so was the specular top edge, which is white
 * in both themes on purpose.
 *
 * --sb-hover and --edge-strong FLIP: white on dark, ink on light. Those
 * are the tokens to reach for whenever something has to read against a
 * surface rather than against the page.
 *
 * This matters more here than it looks. The whole ask-or-navigate design
 * rests on the guess being visible: it only picks which row is
 * pre-selected, and that is only a fair trade if you can see which one. */
.palette-opt.on {
  background: var(--sb-hover);
  /* --edge-sel, not --border2. The fill alone is about 1.1:1 against the
     panel and the --border2 hairline was 1.34:1, both well under the 3:1
     WCAG asks of a control that indicates state. Readable and
     unambiguous are different things, and this is the row that decides
     what Enter does. --edge-sel measures 3.40 in light and 3.3 in dark. */
  border-color: var(--edge-sel);
  border-top-color: var(--edge-sel);
}
.palette-type {
  flex-shrink: 0; width: 62px;
  font-size: .6rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); text-align: right;
}
.palette-opt.on .palette-type { color: var(--amber); }
.palette-text { min-width: 0; flex: 1; }
.palette-label {
  color: var(--text); font-size: .88rem; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.palette-sub {
  color: var(--muted); font-size: .74rem; margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.palette-empty { padding: 32px 20px; border: 0; background: transparent; }
.palette-empty p { margin: 0; }

.palette-foot {
  display: flex; gap: 16px; align-items: center;
  padding: 9px 16px;
  border-top: 1px solid var(--edge);
  color: var(--muted); font-size: .7rem;
}
.palette-hint { display: inline-flex; align-items: center; gap: 4px; }
.palette-foot kbd, .palette-kbd {
  font: inherit; font-size: .66rem; font-weight: 700;
  padding: 2px 5px; border-radius: 5px;
  background: var(--glass-2); border: 1px solid var(--edge);
  color: var(--subtle);
}

/* ═══════════════════════════════════════════════════════════════════
   THE ASSISTANT INSIDE THE PALETTE
   ═══════════════════════════════════════════════════════════════════
   ⌘K is one box for two jobs: jump to a page, or ask the assistant.
   These classes are built by palette.js, not by any JSX, so
   tools/check-classes.mjs cannot see them — it reads route markup. They
   are declared here so a typo in the JS is at least visible as an
   unstyled row rather than a rule with no home.

   Turns inside the expanded panel use the .ag-* vocabulary from
   agent.css, which Layout.tsx now links on every portal page for exactly
   this reason. Nothing about a turn is restyled here. */

/* The ask row.
   Amber at rest, not only when selected: it is the one row that does
   something other than navigate, and it has to be findable in a list of
   eight before you have arrowed onto it. */
.palette-opt-ask .palette-type { color: var(--amber); }
.palette-opt-ask.on {
  background: var(--amber-dim);
  /* SOLID --amber, not --amber-border. Same 3:1 rule as .palette-opt.on,
     and this is the row it matters most on: it is the one that hands
     your text to something that can write to the database.
     --amber-border measured 1.42:1 against the panel in light mode.
     Solid amber is 3.61 in light and 9.92 in dark, so one value clears
     it in both and no new token is needed. */
  border-color: var(--amber);
}
.palette-opt-ask.on .palette-label { color: var(--text); }
.palette-opt-ask .palette-sub {
  /* The echo of what you typed. Quiet, because you just typed it. */
  font-style: normal;
  color: var(--muted);
}

/* ── Expanded ──────────────────────────────────────────────────────
   The panel grows around the input rather than being replaced by a new
   surface, which is the whole point: ⌘K, a question, Enter should read
   as one motion. Only the box's dimensions and the overlay's padding
   move, so the input, the Esc button and the footer all stay exactly
   where the eye left them.

   max-width and max-height are transitioned rather than width/height
   because the panel is sized by its content in the search state and has
   no width to animate from. */
.palette-overlay,
.palette-panel {
  transition:
    padding var(--dur-slow, .38s) cubic-bezier(.2, .8, .25, 1),
    max-width var(--dur-slow, .38s) cubic-bezier(.2, .8, .25, 1),
    max-height var(--dur-slow, .38s) cubic-bezier(.2, .8, .25, 1),
    border-radius var(--dur-slow, .38s) cubic-bezier(.2, .8, .25, 1);
}

.palette-overlay.is-assistant { padding: 5vh 24px 24px; }
.palette-overlay.is-assistant .palette-panel {
  max-width: 56rem;
  max-height: 90vh;
}

/* The thread pane. The results list becomes the transcript; the padding
   is agent.css's panel padding so a turn is set on the same measure it
   has on /agent. */
.palette-thread { padding: 0 1.15rem .5rem; }
.palette-overlay.is-assistant .palette-results { padding: 6px 0 0; }

/* While a run is in flight the caret would otherwise sit blinking in an
   empty box as though nothing had been sent. */
.palette-overlay.is-running .palette-input::placeholder { color: var(--muted); }

.palette-foot-gap { flex: 1; }
.palette-foot-note { color: var(--muted); }
.palette-foot-link {
  color: var(--subtle);
  text-decoration: none;
  border-bottom: 1px solid var(--edge);
  padding-bottom: 1px;
}
.palette-foot-link:hover { color: var(--text); border-bottom-color: var(--amber); }

/* An animated resize is the one thing here that is purely pleasure, so
   it is also the first thing to drop. The expansion still happens; it
   just happens at once. */
@media (prefers-reduced-motion: reduce) {
  .palette-overlay,
  .palette-panel { transition: none; }
}

/* Scroll lock while the dialog owns the screen. */
html.palette-open, html.palette-open body { overflow: hidden; }

/* ── The sidebar trigger ── */
.palette-trigger {
  display: flex; align-items: center; gap: 10px;
  cursor: pointer; font: inherit; text-align: left;
  /* Sits inside .side nav's 8px gutter, so the width has to give it back. */
  padding: 8px 12px; margin: 4px 8px 2px; width: calc(100% - 16px);
  border-radius: var(--radius);
  background: var(--glass-1); border: 1px solid var(--edge);
  border-top-color: var(--edge-top);
  color: var(--muted); font-size: .82rem; font-weight: 500;
  transition: background var(--dur-base), color var(--dur-base);
}
.palette-trigger:hover { background: var(--glass-2); color: var(--text); }
.palette-trigger svg { width: 15px; height: 15px; flex-shrink: 0; }
.palette-trigger .palette-kbd { margin-left: auto; }

/* ── Mention bell ── */
/* .side-foot is a column; the bell wants to sit beside the profile chip,
   so it gets a row of its own inside it. The min-width:0 is what lets a
   long name ellipsis instead of shoving the bell off the sidebar. */
.side-foot-row { display: flex; align-items: center; gap: 6px; }
.side-foot-row .who { flex: 1; min-width: 0; }
.side-foot-row .who > span:last-child {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.mention-bell {
  position: relative; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--radius-sm);
  background: var(--glass-1); border: 1px solid var(--edge);
  border-top-color: var(--edge-top);
  color: var(--muted); text-decoration: none;
  transition: background var(--dur-base), color var(--dur-base);
}
.mention-bell:hover { background: var(--glass-2); color: var(--text); }
.mention-bell svg { width: 16px; height: 16px; }
.mention-bell.has-unread { color: var(--amber); border-color: var(--amber-border); }
.mention-count {
  position: absolute; top: -6px; right: -6px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: var(--radius-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--red); color: #2a0b0b;
  font-size: .62rem; font-weight: 800; line-height: 1;
}

/* ── /mentions ── */
.mention-list { display: flex; flex-direction: column; gap: 8px; }
.mention-row {
  display: block; text-decoration: none;
  padding: 13px 15px; border-radius: var(--radius);
  background: var(--glass-1); border: 1px solid var(--edge);
  border-top-color: var(--edge-top);
  backdrop-filter: var(--blur-sm); -webkit-backdrop-filter: var(--blur-sm);
  transition: background var(--dur-base);
}
.mention-row:hover { background: var(--glass-2); }
.mention-row.unseen { border-left: 2px solid var(--amber); }
.mention-row-head { display: flex; align-items: baseline; gap: 10px; }
.mention-row-head strong { color: var(--text); font-size: .92rem; }
.mention-when { margin-left: auto; color: var(--muted); font-size: .72rem; white-space: nowrap; }
.mention-who {
  display: flex; align-items: center; gap: 8px;
  color: var(--subtle); font-size: .78rem; margin-top: 3px;
}
.mention-excerpt {
  color: var(--muted); font-size: .82rem; line-height: 1.55; margin-top: 7px;
  padding-left: 10px; border-left: 2px solid var(--edge);
}

@media (max-width: 768px) {
  .palette-overlay { padding: 6vh 12px 12px; }
  .palette-panel { max-height: 82vh; }

  /* On a phone the expanded assistant IS the screen. Anything less is a
     dialog with a strip of page showing round the edge, which reads as
     something sitting on top of the app rather than the app itself.
     svh, not vh: vh includes the space behind the browser chrome, so a
     vh-tall panel puts its own footer under the address bar. */
  .palette-overlay.is-assistant { padding: 0; }
  .palette-overlay.is-assistant .palette-panel {
    max-width: 100%;
    width: 100%;
    max-height: 100svh;
    height: 100svh;
    border-radius: 0;
    border-left: 0;
    border-right: 0;
  }
  .palette-type { display: none; }
  /* The footer is keyboard hints, and a phone has no ↵ or esc to hint
     at, so search mode drops it. */
  .palette-foot { display: none; }

  /* ASSISTANT MODE KEEPS IT. What is in that bar there is not a hint:
     it is "Every change can be undone", which is the sentence that makes
     writing without a confirmation step defensible, and the link through
     to the full assistant, which is the only route from here to the
     history and the real Undo control. Hiding those on the device Alwaz
     actually uses this on removed the reassurance and the way out at the
     same time.

     The hints themselves still go. */
  .palette-overlay.is-assistant .palette-foot { display: flex; }
  .palette-overlay.is-assistant .palette-hint { display: none; }
  .palette-overlay.is-assistant .palette-foot-gap { display: none; }
  .palette-overlay.is-assistant .palette-foot {
    justify-content: space-between;
    gap: 12px;
    padding-bottom: max(9px, env(safe-area-inset-bottom));
  }
}

/* ── Attention panel (portal/attention.tsx) ───────────────────────────
   The dashboard's tiles are .tile: one number, one label, no hierarchy
   inside them. A finding needs three levels — the count, what it is, and
   what to do — so it gets its own row rather than being forced through
   .tile and losing the detail line.

   Every row is an <a>. The whole surface is the target, not just the
   trailing link, because a row that only responds on eight characters of
   text is a row people click twice. */
.att-card { margin-bottom: 22px; }
.att-count { color: var(--muted); font-size: .78rem; }
.att-list { display: flex; flex-direction: column; gap: 8px; }

.att-row {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 14px; text-decoration: none;
  background: var(--glass-2); border: 1px solid var(--edge);
  border-left-width: 3px; border-radius: 10px;
  transition: background var(--dur-base), transform var(--dur-base);
}
.att-row:hover { background: var(--glass-3); transform: translateX(2px); }

/* Severity lives on the left edge and on the number, never on the row
   background: five tinted panels stacked up read as decoration and stop
   distinguishing anything. */
.att-red   { border-left-color: var(--red); }
.att-amber { border-left-color: var(--amber); }
.att-blue  { border-left-color: var(--blue); }

.att-num {
  font-family: Sora, sans-serif; font-size: 1.5rem; font-weight: 700;
  letter-spacing: -.02em; line-height: 1; min-width: 42px; text-align: center;
  flex-shrink: 0;
}
.att-red   .att-num { color: var(--red); }
.att-amber .att-num { color: var(--amber); }
.att-blue  .att-num { color: var(--blue); }

.att-text { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
.att-headline { color: var(--text); font-size: .88rem; font-weight: 600; }
.att-detail { color: var(--muted); font-size: .78rem; line-height: 1.45; }

.att-action {
  color: var(--amber); font-size: .78rem; font-weight: 600;
  white-space: nowrap; flex-shrink: 0;
}

/* The "build it from time" offer above the blank invoice form. Amber
   edge so it reads as an offer rather than as the first form section. */
.att-offer { border-color: var(--amber-border); }
.att-offer .card-header { margin-bottom: 10px; }

@media (max-width: 640px) {
  .att-row { flex-wrap: wrap; gap: 10px; }
  .att-num { min-width: 0; text-align: left; }
  .att-action { width: 100%; text-align: right; }
}

/* ── Slack link card (portal/slacklink.tsx) ──────────────────────── */
/* New markup, so this uses the glass names directly (.card, .pill,
   .fld, .form-actions) and only adds what has no equivalent: the code
   field, the numbered steps, and the muted explanatory line. */

.slack-card .card-header { display: flex; align-items: center; gap: 10px; }

.slack-note {
  color: var(--muted); font-size: .8rem; line-height: 1.55; margin: 0 0 10px;
}
.slack-note code, .slack-card code {
  padding: 1px 6px; border-radius: var(--radius-xs);
  background: var(--glass-2); border: 1px solid var(--edge);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .78em; color: var(--subtle);
}
/* Slack ids are opaque strings nobody reads twice — allow them to wrap
   rather than forcing the card wide on a phone. */
.slack-uid { word-break: break-all; }

.slack-steps {
  margin: 0 0 16px; padding-left: 20px;
  color: var(--subtle); font-size: .84rem; line-height: 1.9;
}
.slack-steps li::marker { color: var(--amber); font-weight: 700; }

/* The code field. Monospace and letter-spaced because the whole job of
   this input is retyping six characters correctly, and a proportional
   font is where an O becomes a zero — the reason the alphabet excludes
   both in the first place (see slacklink.tsx). */
.slack-code-fld { max-width: 260px; }
.slack-code-input {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.15rem; font-weight: 700;
  letter-spacing: .32em; text-transform: uppercase;
  /* Compensates for the trailing letter-space so the text sits centred. */
  text-indent: .32em; text-align: center;
}
.slack-code-input::placeholder { letter-spacing: .32em; opacity: .35; font-weight: 400; }

/* ═══════════════════════════════════════════════════════════════════
   PUBLIC API — docs page (/api/v1/docs) + the key manager in Settings
   ═══════════════════════════════════════════════════════════════════
   Same rule as the sections above: glass.css is a verbatim copy of the
   original design system and stays that way, so everything the API
   surfaces need lives here.

   The docs page is the one screen in this codebase served to STRANGERS —
   a developer at a client's agency who has never seen the Portal. It
   carries no sidebar and no inline script (it has to render whether or
   not the CSP nonce middleware ran), so it needs its own page frame
   rather than inheriting .shell's flex row.
   ═══════════════════════════════════════════════════════════════════ */

/* body{display:flex} in glass.css expects .sidebar + .main as children.
   The docs page has only .main, so it would otherwise sit against the
   left edge at full bleed. Centre it and give it a reading measure. */
.api-docs-body { display: block; }
.api-docs {
  max-width: 860px; margin: 0 auto; padding: 40px 20px 96px;
}
.api-docs .top { margin-bottom: 20px; }
.api-vtag {
  font-size: .5em; vertical-align: middle; margin-left: 8px;
  padding: 3px 9px; border-radius: var(--radius-pill);
  background: var(--amber-dim); border: 1px solid var(--amber-border);
  color: var(--amber); letter-spacing: .06em; text-transform: uppercase;
}
.api-sec-title {
  margin: 34px 0 14px; font-family: Sora, system-ui, sans-serif;
  font-size: .74rem; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted);
}
.api-foot { margin-top: 28px; color: var(--muted); font-size: .8rem; text-align: center; }

/* ── One endpoint ────────────────────────────────────────────────── */
.api-ep { margin-bottom: 14px; }
.api-ep-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 8px;
}
/* Fixed width so the verbs form a column and the paths line up — the
   whole point of a reference page is scanning it, not reading it. */
.api-verb {
  flex-shrink: 0; min-width: 62px; text-align: center;
  padding: 3px 8px; border-radius: var(--radius-xs);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  border: 1px solid var(--edge);
}
.api-verb-get   { background: var(--blue-dim);  color: var(--blue);  border-color: rgba(96,165,250,.3); }
.api-verb-post  { background: var(--green-dim); color: var(--green); border-color: rgba(52,211,153,.3); }
.api-verb-patch { background: var(--amber-dim); color: var(--amber); border-color: var(--amber-border); }
.api-path {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .86rem; font-weight: 600; color: var(--text);
  word-break: break-all;
}
.api-scope {
  margin-left: auto; padding: 2px 9px; border-radius: var(--radius-pill);
  background: var(--glass-2); border: 1px solid var(--edge);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .68rem; color: var(--subtle); white-space: nowrap;
}
.api-scope-none { color: var(--muted); font-style: italic; font-family: inherit; }
.api-sum { color: var(--subtle); font-size: .85rem; line-height: 1.65; margin: 0 0 12px; }
.api-sum code, .api-note code, .api-docs td code {
  padding: 1px 6px; border-radius: var(--radius-xs);
  background: var(--glass-2); border: 1px solid var(--edge);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .82em; color: var(--subtle);
}

/* ── Parameter tables ────────────────────────────────────────────── */
.api-fields { margin: 0 0 14px; }
.api-fields-t {
  font-size: .7rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px;
}
.api-fields table { width: 100%; border-collapse: collapse; }
.api-fields td {
  padding: 6px 10px 6px 0; vertical-align: top;
  border-top: 1px solid var(--row-border);
  color: var(--subtle); font-size: .82rem;
}
.api-fields tr:first-child td { border-top: none; }
.api-f {
  white-space: nowrap; color: var(--text); font-weight: 600;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .8rem;
}
.api-req {
  margin-left: 7px; padding: 1px 6px; border-radius: var(--radius-pill);
  background: var(--red-dim); color: var(--red);
  font-family: inherit; font-size: .62rem; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
}
.api-ty { white-space: nowrap; color: var(--muted); font-size: .76rem; }

/* ── curl blocks ─────────────────────────────────────────────────── */
/* pre-wrap rather than a scroller: a curl you have to scroll sideways
   is a curl that gets copied in half. */
.api-curl {
  margin: 0 0 4px; padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--glass-deep); border: 1px solid var(--edge);
  border-top-color: var(--edge-top);
  overflow-x: auto;
}
.api-curl code {
  display: block; white-space: pre-wrap; word-break: break-word;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .78rem; line-height: 1.7; color: var(--subtle);
  background: none; border: none; padding: 0;
}
html.light .api-curl { background: rgba(8,8,13,.045); }

/* ═══════════════════════════════════════════════════════════════════
   API KEYS — the Settings card
   ═══════════════════════════════════════════════════════════════════ */

.api-note { color: var(--muted); font-size: .82rem; line-height: 1.7; margin: 0 0 14px; }

/* The scope picker. auto-fit so eight tiles are four columns on a
   desktop and one on a phone without a media query. */
.api-scope-grid {
  display: grid; gap: 8px; margin-top: 6px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
/* .check-tile centres a single label; these carry a scope and its
   description stacked, so the tile has to align to the top. */
.api-scope-grid .check-tile { align-items: flex-start; }
.api-scope-grid .check-tile input[type=checkbox] { margin-top: 3px; }
.api-scope-grid .check-tile > span { display: flex; flex-direction: column; gap: 2px; }
.api-scope-grid .check-tile code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .78rem; font-weight: 600; color: var(--text);
}
.api-scope-grid .check-tile small { color: var(--muted); font-size: .72rem; }

.api-key-prefix {
  padding: 2px 7px; border-radius: var(--radius-xs);
  background: var(--glass-2); border: 1px solid var(--edge);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .78rem; color: var(--subtle); white-space: nowrap;
}
.api-key-scopes { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.api-key-scope {
  padding: 1px 6px; border-radius: var(--radius-pill);
  background: var(--glass-2); border: 1px solid var(--edge);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .68rem; color: var(--subtle); white-space: nowrap;
}
.api-key-sub { color: var(--muted); font-size: .76rem; }
.api-key-badge {
  margin-left: 8px; background: var(--red-dim); color: var(--red);
  border-color: rgba(248,113,113,.28);
}
/* Revoked and expired rows stay in the list — "did we turn that one
   off?" is the question people ask — but they read as inactive. */
.api-key-dead td { opacity: .5; }
.api-key-dead td:first-child { opacity: .75; }

/* ── The one-time reveal ─────────────────────────────────────────── */
/* Deliberately loud. This is the only moment the secret exists, and a
   card that looks like every other card is a card people scroll past
   and then ask us to resend the key they can no longer be sent. */
.api-key-reveal {
  border-color: var(--amber-border);
  box-shadow: 0 0 0 1px var(--amber-dim), var(--shadow-2);
}
.api-key-warn {
  margin: 0 0 14px; padding: 11px 14px; border-radius: var(--radius-sm);
  background: var(--amber-dim); border: 1px solid var(--amber-border);
  color: var(--amber); font-size: .82rem; line-height: 1.65; font-weight: 500;
}
/* user-select:all so one click selects the whole key — there is no copy
   button because that would need an inline script, and this page is
   rendered by a route that must not depend on the nonce being set. */
.api-key-value {
  margin: 0 0 16px; padding: 16px 18px; border-radius: var(--radius-sm);
  background: var(--glass-deep); border: 1px solid var(--edge-strong);
  border-top-color: var(--edge-top);
  overflow-x: auto;
}
.api-key-value code {
  display: block; user-select: all; word-break: break-all;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1rem; font-weight: 600; letter-spacing: .02em;
  line-height: 1.6; color: var(--amber);
  background: none; border: none; padding: 0;
}
html.light .api-key-value { background: rgba(8,8,13,.05); }

/* ── Tarly brand mark ─────────────────────────────────────────────
   .logo and .auth-brand-logo are amber gradient tiles built to hold a
   LETTERFORM. The tally mark brings its own colour, so the tile has to
   go — an amber mark on an amber square reads as a smudge at 28px.

   SPECIFICITY MATTERS HERE. The sidebar rule is `.side .brand .logo`
   (0,3,0); a plain `.logo.logo-mark` (0,2,0) loses to it, and the first
   attempt at this shipped the mark squashed inside the tile it was
   supposed to replace. Each override below matches or exceeds the rule
   it is overriding — check that before changing a selector. */
.side .brand .logo.logo-mark,
.side .brand .logo-mark,
.auth-brand-logo.auth-brand-mark {
  background: none;
  background-image: none;
  box-shadow: none;
  width: auto;
  height: auto;
  min-width: 0;
  padding: 0;
  border-radius: 0;
  /* The tile centred a glyph; the mark is its own artwork. */
  display: block;
  overflow: visible;
}
.side .brand .logo-mark svg,
.auth-brand-logo.auth-brand-mark svg {
  display: block;
  width: 34px;
  height: auto;
}
/* The mark is wider than tall (380×300), so the brand row should align
   on the optical centre rather than the top of a square box. */
.side .brand,
.auth-brand {
  align-items: center;
}

/* ═══════════════════════════════════════════════════════════════
   ASSISTANT
   The only new class vocabulary this page adds. Everything else it
   uses (.card, .top, .sub, .btn, .table, .fld) already exists.
   ═══════════════════════════════════════════════════════════════ */

.agent-examples {
    display: flex;
    flex-wrap: wrap;
    gap: .45rem;
    margin: .7rem 0 1rem;
}
.agent-chip {
    padding: .35rem .7rem;
    border-radius: 100px;
    border: 1px solid rgba(255, 255, 255, .14);
    background: rgba(255, 255, 255, .04);
    color: rgba(255, 255, 255, .74);   /* 8.9:1 on the panel */
    font: inherit;
    font-size: .78rem;
    cursor: pointer;
    transition: background .18s, border-color .18s, color .18s;
}
.agent-chip:hover {
    background: rgba(255, 255, 255, .09);
    border-color: rgba(255, 255, 255, .3);
    color: #fff;
}

/* The live run log. Monospace because it is a list of tool names and
   arguments, and proportional type makes those harder to scan. */
.agent-stream {
    margin-top: 1rem;
    padding: .85rem 1rem;
    border-radius: 12px;
    background: rgba(0, 0, 0, .28);
    border: 1px solid rgba(255, 255, 255, .08);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: .8rem;
    line-height: 1.75;
    max-height: 340px;
    overflow-y: auto;
}
.agent-stream[hidden] { display: none; }

.agent-line { color: rgba(255, 255, 255, .8); }
.agent-line-tool { color: #4FD99B; }        /* 10.3:1 */
.agent-line-refused { color: #F0876A; }     /* 7.4:1 — not pure red; a
                                               refusal is correct behaviour,
                                               not an error */
.agent-line-text {
    color: #fff;
    font-family: inherit;
    margin-top: .4rem;
    /* The reply is inserted with textContent, never innerHTML — it is
       model output and must not be able to introduce markup. That makes
       pre-wrap the only way a multi-line answer stays multi-line: without
       it, "3.9 h on Bris / 4.5 h on Noctelle" collapsed onto one line and
       read as a single run-on sentence. */
    white-space: pre-wrap;
}
.agent-line-done,
.agent-line-wait { color: #8A8F98; }        /* 5.7:1 */

/* Refusals stay legible rather than being greyed into invisibility —
   "it tried this and was stopped" is the row most worth reading. */
.agent-row-refused td { opacity: .82; }

.agent-tag-refused,
.agent-tag-undone {
    display: inline-block;
    margin-left: .45rem;
    padding: .05rem .4rem;
    border-radius: 100px;
    font-size: .66rem;
    font-weight: 600;
}
.agent-tag-refused {
    background: rgba(240, 135, 106, .16);
    color: #F0876A;
}
.agent-tag-undone {
    background: rgba(255, 255, 255, .1);
    color: #8A8F98;
}

/* One line, ellipsised. The full argument payload is in the database;
   this column exists to be scanned, not read. */
.agent-detail {
    max-width: 34ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (max-width: 768px) {
    .agent-detail { display: none; }
    .agent-log th:nth-child(2),
    .agent-log td:nth-child(2) { display: none; }
}

/* The assistant's PDF link. Amber to match every other link in the
   panel; underlined because inside a monospace log block colour alone
   is not a strong enough affordance. */
.agent-line-link { margin: .35rem 0 .15rem; }
.agent-line-link a {
    color: var(--amber);
    text-decoration: underline;
    text-underline-offset: 3px;
    font-family: inherit;
    font-size: .86rem;
}
.agent-line-link a:hover { color: #FFC55A; }

/* ═══════════════════════════════════════════════════════════════════
   SITE ADMIN, WARM LIGHT ("grouped light", chosen 2026-09-13)

   Scoped to html.admin-warm so the Portal, the Hub and Tarly keep their
   glass palettes. Paper, not glass: warm off-white ground, a slightly
   deeper sidebar, white panels separated by hairlines, one amber, DM Sans
   for text and Sora for headings and numbers. No blur, no ambient
   gradients, no glass edges. Dark stays the glass theme unchanged.

   Rewritten as one section after the 2026-09-13 design audit (see
   ADMIN-AUDIT-2026-09-13.md at the repo root): the first version themed
   the control centre and left every deeper page on the glass component
   layer, which is where the seams were. Every admin-* component the
   panel renders has a rule here now. The old `--main-*` tokens from the
   unloaded admin.css are gone from the markup; nothing should reference
   them again.
   ═══════════════════════════════════════════════════════════════════ */
html.admin-warm.light {
  --bg: #f3f1ec;
  --bg-ambient-1: transparent; --bg-ambient-2: transparent; --bg-ambient-3: transparent;
  --text: #1c1a17; --subtle: #57534e;
  /* #6b665f, not #8a8680: the lighter grey measured 3.6:1 on white and
     under 3:1 on the sidebar, and it carried most of the panel's small
     text. This one is 5.7:1 on white, 4.65:1 on the sidebar. */
  --muted: #6b665f; --muted-2: #8a8680;
  --glass-1: #ffffff; --glass-2: #f7f5f1; --glass-3: #ffffff;
  --surface: #ffffff; --input-bg: #faf9f6;
  --edge: rgba(20,16,8,.07); --edge-strong: rgba(20,16,8,.14); --edge-top: rgba(255,255,255,.9);
  --row-border: #edeae4; --hover-bg: rgba(20,16,8,.03);
  --sb-hover: rgba(20,16,8,.04);
  --amber: #B45309; --amber-strong: #92400e; --amber-dim: rgba(180,83,9,.12); --amber-border: rgba(180,83,9,.3);
  --green: #1f5a2a; --green-dim: #eef6ee; --red: #8a2d1c; --red-dim: #fbeeea;
  --shadow-1: 0 1px 2px rgba(20,16,8,.05), 0 0 0 1px rgba(20,16,8,.06);
  --shadow-2: 0 1px 2px rgba(20,16,8,.05), 0 0 0 1px rgba(20,16,8,.06);
}
html.admin-warm.light body::before { display: none; }

/* ── Type. DM Sans was loaded on every page and applied to nothing; Sora
   to one element. ── */
html.admin-warm body { font-family: 'DM Sans', system-ui, -apple-system, sans-serif; }
html.admin-warm .topbar-title, html.admin-warm h1, html.admin-warm h2, html.admin-warm h3,
html.admin-warm .admin-td-title, html.admin-warm .cc-stat-n, html.admin-warm .enq-title, html.admin-warm .sb-brand-name {
  font-family: Sora, system-ui, sans-serif;
}
html.admin-warm .topbar-title { font-size: 1.25rem; font-weight: 600; letter-spacing: -.02em; }
html.admin-warm .admin-td-title { font-weight: 600; font-size: .9rem; letter-spacing: -.01em; }

/* ── Focus: one ring, amber, everywhere. The browser's blue was the last
   blue on the page. ── */
html.admin-warm :is(a, button, summary, [tabindex]):focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; border-radius: 6px; }
html.admin-warm :is(input, select, textarea):focus-visible { outline: none; }

/* ── Frame ── */
html.admin-warm .admin-body { padding: 4px 32px 48px; max-width: 1240px; }
html.admin-warm .admin-topbar { padding: 22px 32px 14px; }

/* ── Sidebar: fixed width, sticky, so it neither reflows on a rename nor
   scrolls away on a long page. ── */
html.admin-warm .admin-sidebar {
  width: 232px; flex: 0 0 232px; position: sticky; top: 0; height: 100vh; overflow-y: auto;
  display: flex; flex-direction: column; padding: 16px 12px 14px;
  background: #ebe8e2; border-right: 1px solid #e2ded6; scrollbar-width: none;
}
html.admin-warm .admin-sidebar::-webkit-scrollbar { display: none; }
html.admin-warm .sb-brand { padding: 4px 10px 14px; display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center; justify-content: flex-start; text-align: left; gap: 10px; border: 0; }
html.admin-warm .sb-brand > div { display: flex; flex-direction: column; align-items: flex-start; }
html.admin-warm .sb-brand-logo { background: #1c1a17; color: #f5f0e6; box-shadow: none; width: 28px; height: 28px; border-radius: 8px; font-size: .68rem; font-weight: 700; }
html.admin-warm .sb-brand-name { font-size: .88rem; font-weight: 600; letter-spacing: -.01em; }
html.admin-warm .sb-brand-sub { font-size: .62rem; letter-spacing: .08em; color: var(--muted); }
html.admin-warm .sb-nav { flex: 1; padding: 0; }
html.admin-warm .sb-section-label { padding: 16px 10px 6px; font-size: .66rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--subtle); opacity: 1; }
html.admin-warm .sb-divider { display: none; }
html.admin-warm .sb-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 9px; min-height: 36px;
  color: var(--subtle); font-size: .84rem; font-weight: 400; text-decoration: none; border: 0;
}
html.admin-warm .sb-item:hover { background: rgba(20,16,8,.045); color: var(--text); }
html.admin-warm .sb-item.active { background: #ffffff; color: var(--text); font-weight: 500; box-shadow: 0 1px 2px rgba(20,16,8,.06), 0 0 0 1px rgba(20,16,8,.05); }
html.admin-warm .sb-item .sb-icon { color: var(--muted); }
html.admin-warm .sb-item.active .sb-icon { color: var(--amber); }
html.admin-warm .sb-footer { display: flex; flex-direction: column; gap: 6px; padding: 12px 0 0; border-top: 1px solid #e2ded6; margin-top: 8px; }
html.admin-warm .sb-who { padding: 6px 10px 2px; font-size: .74rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
html.admin-warm .sb-who strong { color: var(--subtle); font-weight: 500; }
html.admin-warm .theme-toggle, html.admin-warm .sb-theme-btn { justify-content: flex-start; height: auto; padding: 8px 10px; border-radius: 9px; background: transparent; border: 0; box-shadow: none; color: var(--subtle); font-size: .84rem; width: 100%; }
html.admin-warm .theme-toggle:hover { background: rgba(20,16,8,.045); color: var(--text); }
html.admin-warm .sb-logout {
  display: flex; align-items: center; gap: 10px; justify-content: flex-start; width: 100%;
  height: auto; padding: 8px 10px; border-radius: 9px; background: transparent; border: 0;
  color: var(--subtle); font-size: .84rem; font-family: inherit; cursor: pointer; text-align: left;
}
html.admin-warm .sb-logout:hover { background: rgba(20,16,8,.045); color: var(--text); }

/* ── Panels, cards, tables ── */
html.admin-warm .admin-card, html.admin-warm .admin-table-wrap, html.admin-warm .cc-panel {
  background: #ffffff; border: 0; border-radius: 14px; box-shadow: var(--shadow-1);
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
html.admin-warm .admin-card { padding: 22px 24px; margin-bottom: 18px; }
html.admin-warm .admin-table-wrap { overflow-x: auto; }
html.admin-warm .admin-table { min-width: 560px; font-size: .86rem; }
html.admin-warm .admin-table thead { background: transparent; border-bottom: 1px solid var(--row-border); }
html.admin-warm .admin-table th { padding: 12px 18px; font-size: .74rem; font-weight: 500; letter-spacing: .02em; text-transform: none; color: var(--muted); }
html.admin-warm .admin-table td { padding: 14px 18px; border-bottom: 1px solid var(--row-border); color: var(--subtle); vertical-align: middle; }
html.admin-warm .admin-table tbody tr:last-child td { border-bottom: 0; }
html.admin-warm .admin-table tbody tr:hover { background: var(--hover-bg); }
html.admin-warm .admin-td-slug { font-size: .76rem; color: var(--muted); }
html.admin-warm .admin-td-actions { gap: 6px; }
html.admin-warm .admin-td-actions .admin-btn-ghost { background: transparent; border-color: transparent; color: var(--subtle); }
html.admin-warm .admin-td-actions .admin-btn-ghost:hover { background: rgba(20,16,8,.05); color: var(--text); }
html.admin-warm .admin-section-title { font-family: Sora, system-ui, sans-serif; font-size: .95rem; font-weight: 600; letter-spacing: -.01em; text-transform: none; color: var(--text); margin: 0 0 12px; }
html.admin-warm .admin-count { font-size: .8rem; color: var(--muted); margin: -6px 0 14px; }
html.admin-warm .admin-empty { background: #ffffff; border: 1px dashed var(--edge-strong); border-radius: 14px; padding: 44px 24px; color: var(--muted); }
html.admin-warm .admin-empty p { margin: 0 0 16px; font-size: .9rem; }

/* ── Status: a dot, not a coloured pill. Published green, draft hollow. ── */
html.admin-warm .admin-status-badge { background: transparent; border: 0; padding: 0; font-size: .8rem; font-weight: 500; color: var(--subtle); gap: 7px; }
html.admin-warm .admin-status-badge::before { content: ''; width: 7px; height: 7px; border-radius: 50%; border: 1.5px solid var(--muted-2); background: transparent; }
html.admin-warm .admin-status-published::before { background: #2f8a45; border-color: #2f8a45; }

/* ── Buttons ── */
html.admin-warm .admin-btn { backdrop-filter: none; -webkit-backdrop-filter: none; border-radius: 9px; padding: 9px 14px; font-size: .84rem; font-weight: 500; gap: 8px; }
html.admin-warm .admin-btn-primary { background: #1c1a17; color: #f5f0e6; border-color: #1c1a17; box-shadow: none; }
html.admin-warm .admin-btn-primary:hover { background: #2a2723; filter: none; }
html.admin-warm .admin-btn-ghost { background: #ffffff; border-color: var(--edge-strong); color: var(--subtle); }
html.admin-warm .admin-btn-ghost:hover { background: #f7f5f1; color: var(--text); }
html.admin-warm .admin-btn-danger { background: transparent; border-color: transparent; color: var(--red); }
html.admin-warm .admin-btn-danger:hover { background: var(--red-dim); }
html.admin-warm .admin-btn-sm { padding: 7px 11px; font-size: .78rem; }
html.admin-warm .admin-btn .sb-icon { width: 15px; height: 15px; }

/* ── Forms ── */
html.admin-warm .admin-field { gap: 6px; margin-bottom: 16px; }
html.admin-warm .admin-label { font-size: .8rem; font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--subtle); }
html.admin-warm .admin-label .req { color: var(--amber); }
html.admin-warm .admin-help { font-size: .76rem; color: var(--muted); }
html.admin-warm .admin-error-text { color: var(--red); font-size: .78rem; }
html.admin-warm :is(.admin-input, .admin-select, .admin-textarea, input[type=text], input[type=url], input[type=email], input[type=number], input[type=datetime-local], input[type=date], input[type=password], select, textarea) {
  width: 100%; box-sizing: border-box; min-height: 40px; padding: 9px 12px;
  border: 1px solid var(--edge-strong); border-radius: 9px; background: var(--input-bg); color: var(--text);
  font: inherit; font-size: .88rem; color-scheme: light; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none;
}
html.admin-warm :is(.admin-input, .admin-select, .admin-textarea, input, select, textarea):focus { border-color: var(--amber); box-shadow: 0 0 0 3px var(--amber-dim); background: #ffffff; }
html.admin-warm input[type=checkbox] { width: 16px; height: 16px; min-height: 0; accent-color: var(--amber); padding: 0; }
html.admin-warm .admin-textarea { line-height: 1.55; resize: vertical; }
/* The content field. It used `.code`, a rule written for the 2FA secret box
   (centred, letter-spaced, break-all), and rendered the essay centred with
   words broken in half. */
html.admin-warm textarea.admin-textarea-code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .82rem; line-height: 1.6; tab-size: 2; text-align: left; letter-spacing: 0; word-break: normal; overflow-wrap: anywhere; }
html.admin-warm .admin-form-actions { gap: 8px; margin-top: 16px; }
html.admin-warm .admin-counter { font-size: .72rem; color: var(--muted); text-align: right; margin-top: -2px; font-variant-numeric: tabular-nums; }
html.admin-warm .admin-counter.is-over { color: var(--red); }

/* Editor layout: a class, so it can collapse. */
html.admin-warm .admin-editor { display: grid; grid-template-columns: minmax(0,1fr) 300px; gap: 20px; align-items: start; }
html.admin-warm .admin-editor-aside { position: sticky; top: 20px; }
html.admin-warm .admin-eyebrow { font-size: .74rem; color: var(--muted); margin: 0 0 4px; }
html.admin-warm .admin-back { display: inline-flex; align-items: center; gap: 6px; font-size: .82rem; color: var(--subtle); text-decoration: none; margin-bottom: 14px; }
html.admin-warm .admin-back:hover { color: var(--text); }
html.admin-warm .admin-danger-zone { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--row-border); }

/* ── Flash: one component, block, no glow, two vocabularies collapsed. ── */
html.admin-warm .flash, html.admin-warm .admin-flash {
  display: block; padding: 12px 16px; border-radius: 10px; border: 1px solid var(--edge-strong);
  background: #ffffff; color: var(--text); font-size: .86rem; line-height: 1.5; margin: 0 0 18px;
  backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none;
}
html.admin-warm :is(.flash, .admin-flash)::before { display: none; }
html.admin-warm :is(.flash.error, .flash.err, .admin-flash.error) { background: var(--red-dim); border-color: #f1c9c0; color: var(--red); }
html.admin-warm :is(.flash.success, .flash.ok, .admin-flash.success) { background: var(--green-dim); border-color: #c9e2c9; color: var(--green); }
html.admin-warm .flash code { font-size: .8em; padding: 1px 5px; border-radius: 4px; background: rgba(20,16,8,.06); }
html.admin-warm .flash strong { font-weight: 600; }

/* ── Links inside cards and tables read as links ── */
html.admin-warm :is(.admin-card, .admin-table, .cc-panel) a:not(.admin-btn):not(.cc-row):not(.cc-stat):not(.row-link) { color: var(--amber); text-decoration: none; }
html.admin-warm :is(.admin-card, .admin-table, .cc-panel) a:not(.admin-btn):not(.cc-row):not(.cc-stat):not(.row-link):hover { text-decoration: underline; text-underline-offset: 2px; }

/* ── Auth pages ── */
html.admin-warm.light .auth-card { background: #ffffff; border: 0; box-shadow: 0 1px 2px rgba(20,16,8,.05), 0 0 0 1px rgba(20,16,8,.06), 0 24px 48px -24px rgba(20,16,8,.18); backdrop-filter: none; -webkit-backdrop-filter: none; }
html.admin-warm.light .auth-brand-logo { background: #1c1a17; color: #f5f0e6; box-shadow: none; }
html.admin-warm.light .auth-input, html.admin-warm.light .auth-code-input { background: #faf9f6; }
html.admin-warm.light .auth-btn { background: #1c1a17; color: #f5f0e6; border-color: #1c1a17; box-shadow: none; }
html.admin-warm.light .auth-btn:hover { background: #2a2723; filter: none; }
html.admin-warm.light .auth-secret { letter-spacing: .04em; word-break: normal; white-space: nowrap; overflow-x: auto; }

/* ── Control centre and the shared row/list vocabulary ── */
.cc { max-width: 1120px; }
.cc-date { font-size: .82rem; color: var(--muted); }
.cc-panel { display: flex; flex-direction: column; background: var(--glass-1); border-radius: 14px; overflow: hidden; box-shadow: 0 1px 2px rgba(20,16,8,.05), 0 0 0 1px var(--edge); }
html:not(.light) .cc-panel { box-shadow: 0 0 0 1px var(--edge); }
.cc-stats { flex-direction: row; margin-bottom: 32px; }
.cc-stat { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 6px; padding: 22px 24px; text-decoration: none; color: inherit; transition: background var(--dur-fast) var(--ease-out); }
.cc-stat + .cc-stat { border-left: 1px solid var(--row-border); }
a.cc-stat:hover { background: var(--hover-bg); }
.cc-stat-n { font-family: Sora, system-ui, sans-serif; font-size: 2.1rem; font-weight: 600; letter-spacing: -.03em; line-height: 1; color: var(--text); font-variant-numeric: tabular-nums; }
.cc-stat-l { font-size: .8rem; color: var(--muted); }
.cc-stat.is-live .cc-stat-n { color: var(--amber); }
.cc-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 28px; }
.cc-section { margin-bottom: 28px; }
.cc-label { font-size: .72rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--subtle); margin: 0 0 8px 18px; }
.cc-row { display: grid; grid-template-columns: 28px 176px minmax(0, 1fr) 14px; align-items: center; gap: 14px; height: 48px; padding: 0 18px; text-decoration: none; color: inherit; transition: background var(--dur-fast) var(--ease-out); }
.cc-row + .cc-row { border-top: 1px solid var(--row-border); }
.cc-row:hover { background: var(--hover-bg); }
.cc-ico { width: 28px; height: 28px; border-radius: 7px; display: inline-flex; align-items: center; justify-content: center; background: rgba(20,16,8,.05); color: var(--text); }
html:not(.light) .cc-ico { background: rgba(255,255,255,.07); }
.cc-ico .cc-line { width: 15px; height: 15px; stroke-width: 1.7; }
.cc-ico .cc-brand { width: 15px; height: 15px; }
.cc-name { font-size: .9rem; font-weight: 500; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cc-desc { font-size: .8rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cc-go { color: var(--muted); opacity: .6; transition: opacity var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
.cc-row:hover .cc-go { opacity: 1; color: var(--amber); }

/* Generic list rows (dashboard recents, enquiries, content lists): the
   control centre's row grammar without the icon tile. */
.row-list { display: flex; flex-direction: column; }
.row-link { display: grid; align-items: center; gap: 16px; min-height: 56px; padding: 12px 18px; text-decoration: none; color: inherit; transition: background var(--dur-fast) var(--ease-out); }
.row-link + .row-link, .row-list > * + * { border-top: 1px solid var(--row-border); }
.row-link:hover { background: var(--hover-bg); }
.row-main { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.row-title { font-family: Sora, system-ui, sans-serif; font-size: .9rem; font-weight: 600; letter-spacing: -.01em; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row-sub { font-size: .78rem; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row-meta { font-size: .78rem; color: var(--muted); white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; }
.row-actions { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--amber); flex: none; }
.dot.is-quiet { background: transparent; border: 1.5px solid var(--muted-2); }
.dot.is-green { background: #2f8a45; }

/* Enquiries page header */
.enq-head { margin: 0 0 18px; }
.enq-title { margin: 0 0 4px; font-size: 1.15rem; font-weight: 600; letter-spacing: -.02em; color: var(--text); }
.enq-note { margin: 0; font-size: .82rem; color: var(--muted); max-width: 64ch; }
.enq-grid { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 20px; align-items: start; }
.enq-msg { white-space: pre-wrap; line-height: 1.65; font-size: .92rem; color: var(--text); }
.enq-facts { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 8px 12px; font-size: .84rem; }
.enq-facts dt { color: var(--muted); margin: 0; }
.enq-facts dd { color: var(--text); margin: 0; overflow-wrap: anywhere; }
.enq-reply-list { display: flex; flex-direction: column; gap: 12px; }
.enq-reply { padding: 14px 16px; border-radius: 10px; background: var(--glass-2); font-size: .86rem; line-height: 1.6; white-space: pre-wrap; }
.enq-reply-meta { font-size: .74rem; color: var(--muted); margin-bottom: 6px; white-space: normal; }
.enq-templates { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }

/* ── Phone ── */
.admin-menu { display: none; }
.admin-overlay { display: none; }
.sb-close { display: none; }
@media (max-width: 900px) {
  .cc-stats { flex-wrap: wrap; }
  .cc-stat { flex-basis: 50%; }
  .cc-stat:nth-child(3) { border-left: 0; }
  .cc-stat:nth-child(n+3) { border-top: 1px solid var(--row-border); }
  .cc-cols { grid-template-columns: minmax(0, 1fr); }
  html.admin-warm .admin-editor { grid-template-columns: minmax(0, 1fr); }
  html.admin-warm .admin-editor-aside { position: static; }
  .enq-grid { grid-template-columns: minmax(0, 1fr); }
  .enq-grid aside { order: -1; }
}
@media (max-width: 768px) {
  html.admin-warm body { display: block; }
  html.admin-warm .admin-sidebar {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 60;
    width: min(300px, 84vw); height: 100vh; min-height: 0; padding: 14px 12px calc(14px + env(safe-area-inset-bottom));
    transform: translateX(-104%); visibility: hidden;
    transition: transform 220ms var(--ease-out), visibility 0s 220ms;
    box-shadow: 0 0 0 1px rgba(20,16,8,.06), 24px 0 48px -24px rgba(20,16,8,.25);
  }
  html.admin-warm body.admin-nav-open .admin-sidebar { transform: none; visibility: visible; transition: transform 220ms var(--ease-out), visibility 0s; }
  html.admin-warm .sb-brand { align-items: center; }
  html.admin-warm .sb-close { display: inline-flex; margin-left: auto; width: 40px; height: 40px; align-items: center; justify-content: center; border: 0; border-radius: 10px; background: transparent; color: var(--subtle); cursor: pointer; }
  html.admin-warm .sb-nav { flex-direction: column; overflow-x: visible; padding: 4px 0; gap: 0; }
  html.admin-warm .sb-footer { flex-direction: column; align-items: stretch; }
  html.admin-warm .sb-item { white-space: normal; flex-shrink: 1; min-height: 44px; }
  html.admin-warm .admin-menu { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin-left: -10px; border-radius: 10px; border: 0; background: transparent; color: var(--text); cursor: pointer; }
  html.admin-warm .admin-menu:hover { background: var(--hover-bg); }
  html.admin-warm .admin-topbar { padding: 10px 16px; gap: 10px; position: sticky; top: 0; z-index: 40; background: var(--bg); }
  html.admin-warm .topbar-title { flex: 1; font-size: 1.05rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  html.admin-warm .topbar-actions > span, html.admin-warm .topbar-actions .cc-date { display: none; }
  html.admin-warm .admin-body { padding: 12px 16px 40px; }
  html.admin-warm .admin-overlay { display: block; position: fixed; inset: 0; z-index: 50; background: rgba(20,16,8,.35); opacity: 0; pointer-events: none; transition: opacity 220ms var(--ease-out); }
  html.admin-warm body.admin-nav-open .admin-overlay { opacity: 1; pointer-events: auto; }
  html.admin-warm body.admin-nav-open { overflow: hidden; }
  html.admin-warm .admin-btn { min-height: 44px; }
  html.admin-warm .admin-btn-sm { min-height: 40px; }
  html.admin-warm .cc-row { grid-template-columns: 28px minmax(0, 1fr) 14px; }
  html.admin-warm .cc-row .cc-desc { display: none; }
  /* Tables become cards: each row a block, cells stacked, actions last. */
  html.admin-warm .admin-table { min-width: 0; }
  html.admin-warm .admin-table thead { display: none; }
  html.admin-warm .admin-table, html.admin-warm .admin-table tbody, html.admin-warm .admin-table tr, html.admin-warm .admin-table td { display: block; width: 100%; box-sizing: border-box; }
  html.admin-warm .admin-table tr { padding: 12px 16px; border-bottom: 1px solid var(--row-border); }
  html.admin-warm .admin-table tr:last-child { border-bottom: 0; }
  html.admin-warm .admin-table td { padding: 3px 0; border: 0; }
  html.admin-warm .admin-td-actions { justify-content: flex-start; padding-top: 8px; flex-wrap: wrap; }
  .row-link { grid-template-columns: minmax(0, 1fr) !important; }
  .row-meta { text-align: left; }
  .row-actions { justify-content: flex-start; }
}

/* ── Hub: pieces the entity pages use on top of the admin system ──
   The port's .top/.card/.tbl/.btn vocabulary is gone (2026-09-13); every
   Hub page renders the admin components. */
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
.chip { display: inline-flex; align-items: center; gap: 6px; padding: 6px 11px; border-radius: 999px; font-size: .78rem; font-weight: 500; color: var(--subtle); background: #ffffff; box-shadow: 0 0 0 1px var(--edge); text-decoration: none; min-height: 32px; }
.chip:hover { color: var(--text); box-shadow: 0 0 0 1px var(--edge-strong); }
.chip.is-on { background: #1c1a17; color: #f5f0e6; box-shadow: none; }
.chip b { font-weight: 600; opacity: .8; }
.tone { display: inline-flex; align-items: center; gap: 7px; font-size: .8rem; color: var(--subtle); white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tone::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--muted-2); flex: none; }
.tone.is-good::before { background: #2f8a45; }
.tone.is-warn::before { background: var(--amber); }
.tone.is-bad::before { background: #c2410c; }
.tone:empty { display: none; }
/* A deadline that has passed or is today reads in colour, like the tone dot. */
.row-meta.is-bad { color: #b3400b; font-weight: 500; }
.row-meta.is-warn { color: var(--amber-strong, #b45309); font-weight: 500; }
/* Chips on the left, search on the right, above every list. */
.list-tools { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 14px; flex-wrap: wrap; }
.hub-search { display: flex; align-items: center; gap: 8px; margin: 0; flex: 0 1 260px; }
.hub-search input { width: 100%; min-height: 38px; }
.list-note { margin-top: -4px; }
.list-note a { color: var(--amber); text-decoration: none; }
.list-note a:hover { text-decoration: underline; }
.two-col { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; align-items: start; }
.related-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.related-head .cc-label { margin: 0 0 0 18px; }
.cc-label-n { font-weight: 500; color: var(--muted-2); text-transform: none; letter-spacing: 0; }
/* Forms: two columns, notes full width. */
html.admin-warm .hub .form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 18px; }
html.admin-warm .hub .form-grid > * { min-width: 0; }
html.admin-warm .hub .form-grid .fld.wide { grid-column: 1 / -1; }
@media (max-width: 720px) { html.admin-warm .hub .form-grid { grid-template-columns: minmax(0, 1fr); } }
/* Entity rows: title and facts, status, date, arrow. */
.row-entity { grid-template-columns: minmax(0, 1fr) 150px 120px 14px; }
.row-entity.row-compact { grid-template-columns: minmax(0, 1fr) 118px 96px; }
.row-account { grid-template-columns: minmax(0, 1fr) 130px 130px auto; }
.row-account.is-off .row-main { opacity: .55; }
.row-you { font-weight: 400; color: var(--muted); }
.row-account .row-actions form { margin: 0; }
.cc-stat-n.is-multi { font-size: 1.35rem; line-height: 1.2; }
.admin-field.has-error .admin-input, .admin-field.has-error .admin-select, .admin-field.has-error .admin-textarea { border-color: var(--red); }
@media (max-width: 900px) { .two-col { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 768px) {
  /* Two lines on a phone: title and facts, then status and date side by
     side. The arrow goes; the whole row is the link. */
  .row-entity, .row-entity.row-compact { grid-template-columns: minmax(0, 1fr) auto !important; grid-template-areas: "main main" "tone meta"; gap: 6px 12px; padding: 12px 16px; }
  .row-entity .row-main { grid-area: main; }
  .row-entity .tone { grid-area: tone; }
  .row-entity .row-meta { grid-area: meta; text-align: right; }
  .row-entity .cc-go { display: none; }
  .row-account { grid-template-columns: minmax(0, 1fr) !important; gap: 8px; }
  .row-account .row-actions { justify-content: flex-start; }
  .list-tools { flex-direction: column-reverse; align-items: stretch; flex-wrap: nowrap; } /* wrap on a column flex sizes the line to the widest child, and the chips row is wider than the phone */
  .hub-search { flex: none; }
  html.admin-warm .hub-search input { min-height: 44px; height: 44px; font-size: 16px; }
  .chip { min-height: 40px; padding-inline: 13px; }
  .chips { overflow-x: auto; flex-wrap: nowrap; padding-bottom: 4px; scrollbar-width: none; }
  .chips::-webkit-scrollbar { display: none; }
  .chip { flex: none; }
  .related-head .cc-label { margin-left: 4px; }
}
