/* ============================================================
   EMPIRE OS — Component library (shared across every tab)
   ============================================================ */

/* ---------- Buttons ---------- */
/* ---- THE BUTTON SCALE (P1-03) ----
   Three heights, ONE radius, one focus ring. Every value is a token; a button
   anywhere in either product that sets its own height or radius is a bug.
   Heights: --ctl-h-sm 32 · --ctl-h-md 38 (default) · --ctl-h-lg 44.
   Radius:  --r-btn, at every size. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  height: var(--ctl-h-md); padding: 0 14px; border-radius: var(--r-btn);
  font-size: var(--fs-sm); font-weight: 560; color: var(--text-200);
  background: var(--surface-2); border: 1px solid var(--border);
  transition: background var(--t-fast) var(--ease-smooth), border-color var(--t-fast) var(--ease-smooth), color var(--t-fast) var(--ease-smooth), box-shadow var(--t-fast) var(--ease-smooth), transform var(--t-fast) var(--ease-smooth);
  white-space: nowrap; user-select: none;
}
.btn { background: var(--surface-0); border: 1px solid var(--border-strong); }
.btn:hover { background: var(--surface-2); border-color: var(--border-strong); color: var(--text-100); }
.btn:active { transform: scale(0.98); }
.btn svg, .btn .ico { width: 16px; height: 16px; }
.btn-primary {
  color: var(--accent-fg); font-weight: 600;
  background: var(--accent);
  border: 1px solid var(--accent); box-shadow: var(--shadow-sm);
}
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-fg); }
.btn-ghost { background: transparent; border-color: transparent; }
.btn-ghost:hover { background: var(--surface-2); border-color: var(--border); }
.btn-sm { height: var(--ctl-h-sm); padding: 0 11px; font-size: var(--fs-xs); border-radius: var(--r-btn); }
.btn-lg { height: var(--ctl-h-lg); padding: 0 20px; font-size: var(--fs-body); border-radius: var(--r-btn); }
/* Any button that hard-codes its own height is off the scale by definition.
   These are the four strays the census found in shared CSS; a fifth in a view
   is a bug in that view, not a reason for a fifth rung. */
.btn.btn-block { width: 100%; }

/* ---- The 44px floor for buttons (teardown 01 §6.3) ----
   Every .btn in the product measured 36px tall and every .btn-sm 30px, which
   is 60 of 67 interactive elements on a desktop screen and 41 of 91 on
   mobile CRM. Raising the PAINT to 44 would rebuild every toolbar in the
   app, so the paint moves one notch (36→38, 30→32, enough that the visual
   rhythm is unchanged) and the HIT REGION goes to 44 with a vertical pad.
   Vertical, not horizontal: buttons in these toolbars sit shoulder to
   shoulder, so a horizontal pad would eat its neighbour's clicks, whereas
   rows always have gap between them. Icon buttons — which stand alone and
   fail on BOTH axes — get the full 44×44 pad below. */
.btn { position: relative; }
/* ::before, not ::after — ::after on a button is already spoken for by the
   `[data-tip]` tooltip (399 call sites), and the tooltip rule wins on source
   order. ::before is free on .btn everywhere in the product. */
.btn::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-50%);
  height: var(--tap-pad); min-height: 100%;
  pointer-events: inherit;
}
/* A button whose own ::before is load-bearing must opt out. */
.btn.no-tap-pad::before { content: none; }

/* Global keyboard focus ring (a11y) — visible only for keyboard users.
   Views may declare their own scoped :focus-visible; these are the OS-wide defaults. */
.btn:focus-visible, .btn-icon:focus-visible,
.segmented button:focus-visible, .subtabs button:focus-visible, .toggle:focus-visible,
[role="button"]:focus-visible, [role="tab"]:focus-visible {
  outline: none; box-shadow: 0 0 0 3px var(--accent-ring);
}
.card-hover:focus-visible, .kard:focus-visible {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring);
}
/* WS-14 · GLOBAL KEYBOARD FOCUS RING (a11y baseline) — see controls.css for the
   full rationale. Duplicated here because login.html loads components.css but NOT
   controls.css, and the defect-#1 offender (login.html "Forgot password?" is a
   <button class="login-link">) lives on that page. :where() = 0 specificity so any
   component rule still wins; the box-shadow ring auto-hugs each element's own
   border-radius (never forces radius). tabindex="-1" excluded. */
:where(a[href], button, input, select, textarea, summary,
       [contenteditable="true"], [contenteditable="plaintext-only"],
       [tabindex]:not([tabindex="-1"]),
       [role="button"], [role="tab"], [role="switch"], [role="menuitem"],
       [role="option"], [role="checkbox"], [role="radio"], [role="link"]):focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-ring);
}
/* FOCUS-SQUARE FIX (Vincent 2026-07-09) — see controls.css for the full rationale.
   The safety-net box-shadow ring above hugs each element's own border-radius, so
   the app's bare radius-0 composite text fields (inside a `:focus-within` wrapper)
   painted a hard SQUARE ring inside their rounded wrapper. Text inputs/textareas
   are covered by `.input`/`.textarea` (own rounded ring) or by their wrapper, so
   suppress the safety-net ring on text-entry fields only. `:where()` = 0
   specificity → `.input:focus` still wins; a bare field's own `outline:none` keeps
   it silent; a stray unstyled input falls back to the UA outline (`outline:
   revert`, radius-following, a11y safe). Range/checkbox/radio keep their ring. */
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):not([type="color"]):not([type="file"]),
       textarea):focus-visible {
  box-shadow: none;
  outline: revert;
}
.btn-icon { width: var(--ctl-h-md); padding: 0; position: relative; border-radius: var(--r-btn); }
.btn-icon.btn-sm { width: var(--ctl-h-sm); }
/* Icon buttons fail on BOTH axes (topbar was 36×36, list rows 30×30) and
   they stand alone, so they get the full square pad. Spelled out in full
   rather than inheriting from .btn::after because plenty of call sites write
   `class="btn-icon btn-ghost"` with no `btn`. */
.btn-icon::before {
  content: ""; position: absolute; left: 50%; top: 50%; right: auto;
  transform: translate(-50%, -50%);
  width: var(--tap-pad); min-width: 100%;
  height: var(--tap-pad); min-height: 100%;
  pointer-events: inherit;
}
.btn-icon.no-tap-pad::before { content: none; }
.btn-danger { color: var(--red-400); }
.btn-danger:hover { background: var(--red-bg); border-color: var(--red-400); color: var(--red-400); }

/* ---------- Icon button (WS-14 canonical) ----------
   THE fix for "hover target is off the icon". A real square <button>
   whose hitbox is fixed, centered, and always ≥ the glyph + padding —
   never an absolutely-offset span. The glyph centers via grid; the
   hitbox (--ib) is the clickable/hover area, guaranteed larger than the
   icon so hover/click never drifts off the visible mark. Adopt via
   ui.js iconBtn() or the .icon-btn class directly. */
.icon-btn {
  --ib: var(--ctl-h-sm);
  width: var(--ib); height: var(--ib); flex-shrink: 0;
  display: inline-grid; place-items: center; padding: 0;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: transparent; color: var(--text-300); cursor: pointer;
  transition: background var(--t-fast) var(--ease-smooth), color var(--t-fast) var(--ease-smooth), border-color var(--t-fast) var(--ease-smooth);
}
.icon-btn:hover { background: var(--surface-2); color: var(--text-100); }
.icon-btn:active { transform: scale(0.94); }
.icon-btn.on, .icon-btn.is-active { background: var(--surface-2); color: var(--text-100); }
.icon-btn.danger { color: var(--red-400); }
.icon-btn.danger:hover { background: var(--red-bg); color: var(--red-400); }
.icon-btn:disabled { opacity: .45; cursor: not-allowed; }
.icon-btn svg, .icon-btn .ico { pointer-events: none; width: 16px; height: 16px; }
/* The hit region, which `.btn-icon` has had since the first tap-target pass and
   this one somehow never got — so the calendar's own arrows measured 28×28 and
   every `.icon-btn.sm` in the product was a sub-floor target while the visually
   identical `.btn-icon` next to it passed. Same pad, same reasoning: an
   absolutely-positioned transparent ::before, never a min-height (Findings F7).
   `min-width/height: 100%` means an `.icon-btn.lg` gains no overhang at all. */
.icon-btn { position: relative; }
/* NO `min-width/height: 100%` here, and that is load-bearing. A percentage on
   an absolutely-positioned ::before resolves against the CONTAINING BLOCK, and
   the containing block is the host only while the host is positioned. One view
   rule setting `position: static` on a host turns `100%` into the whole card —
   which is exactly how a 18px checkbox grew a 290×81px pad and made the record
   title beside it unclickable during this pass. A fixed 46px pad cannot do
   that in any context, and a control already bigger than 46 owns its own box
   anyway, so the min-* bought nothing. */
.icon-btn::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: var(--tap-pad); height: var(--tap-pad);
  pointer-events: inherit;
}
/* `.xs` is 24px and only ever appears in a tight cluster (card corners, chip
   rows), where a 46px pad would reach 11px onto its neighbour. It takes the
   normative 24×24 floor instead — see `.u-tap24` in base.css. */
.icon-btn.xs::before { width: var(--tap-pad-sm); height: var(--tap-pad-sm); }
.icon-btn.no-tap-pad::before { content: none; }
/* 24 and 28 are the only two sizes in the product that are NOT on the
   --ctl-h-* scale, and that is deliberate rather than an oversight: they are
   bare glyphs inside another control (a card corner, a list-header action),
   not buttons in their own right — `--ctl-h-sm` (32) is the smallest thing
   that reads as a button. They carry the same hit region as everything else,
   so the paint is small and the target is not. Do not add a third. */
.icon-btn.xs { --ib: 24px; } .icon-btn.xs svg { width: 13px; height: 13px; }
.icon-btn.sm { --ib: 28px; } .icon-btn.sm svg { width: 14px; height: 14px; }
.icon-btn.lg { --ib: var(--ctl-h-md); } .icon-btn.lg svg { width: 19px; height: 19px; }
/* on subtle/dark corners (thumbnails, media) a scrimmed variant reads better */
.icon-btn.scrim { background: color-mix(in srgb, var(--ink-600) 55%, transparent); color: #fff; }
.icon-btn.scrim:hover { background: color-mix(in srgb, var(--ink-600) 78%, transparent); }

/* ---------- Cards ---------- */
.card {
  background: var(--surface-0);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
}
.card-pad { padding: var(--sp-5); }
.card-hover { transition: transform var(--t-med) var(--ease-smooth), border-color var(--t-med) var(--ease-smooth), box-shadow var(--t-med) var(--ease-smooth); }
.card-hover:hover { transform: translateY(-2px); border-color: var(--border-strong); box-shadow: var(--shadow-elevated); }
.card-glow { position: relative; overflow: hidden; }
.card-glow::before { content: none; }
.panel { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--r-md); }

/* ---------- Section header ---------- */
.section-head { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: var(--sp-5); gap: var(--sp-4); }
.section-head h2 { font-size: var(--fs-h2); }
.section-title { display: flex; align-items: center; gap: var(--sp-3); }
.section-title .ico { color: var(--text-300); }

/* ---------- Tags / pills / badges ---------- */
.tag {
  display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 9px;
  border-radius: var(--r-pill); font-size: var(--fs-2xs); font-weight: 600;
  letter-spacing: 0.02em; border: 1px solid transparent; white-space: nowrap;
  background: var(--surface-3); color: var(--text-300);
}
.tag .dot { width: 6px; height: 6px; border-radius: var(--r-pill); background: currentColor; }
.tag-green  { color: var(--green-400);  background: var(--green-bg);  border-color: color-mix(in srgb, var(--green-400) 20%, transparent); }
.tag-red    { color: var(--red-400);    background: var(--red-bg);    border-color: color-mix(in srgb, var(--red-400) 20%, transparent); }
.tag-amber  { color: var(--amber-400);  background: var(--amber-bg);  border-color: color-mix(in srgb, var(--amber-400) 20%, transparent); }
.tag-blue   { color: var(--blue-400);   background: var(--blue-bg);   border-color: color-mix(in srgb, var(--blue-400) 20%, transparent); }
.tag-violet { color: var(--violet-400); background: var(--violet-bg); border-color: color-mix(in srgb, var(--violet-400) 20%, transparent); }
.tag-teal   { color: var(--teal-400);   background: var(--teal-bg);   border-color: color-mix(in srgb, var(--teal-400) 20%, transparent); }
.tag-pink   { color: var(--pink-400);   background: var(--pink-bg);   border-color: color-mix(in srgb, var(--pink-400) 20%, transparent); }
.tag-gold   { color: var(--text-100);   background: var(--accent-soft); border-color: var(--border-strong); }
.badge { display:inline-flex; align-items:center; justify-content:center; min-width: 18px; height: 18px; padding: 0 5px; border-radius: var(--r-pill); font-size: var(--fs-2xs); font-weight: 600; background: var(--accent); color: var(--accent-fg); }
.badge-soft { background: var(--surface-3); color: var(--text-300); }

/* ---------- Skeleton shimmer (shared) ----------
   Global loading-placeholder class. Views that already emit `.skeleton` boxes
   (workflows/integrations/team/settings) get a real shimmer here; matches the
   per-view idiom (home `.bsk`, budget `.bsk`, scheduling `.sk-bar`): a subtle
   monochrome surface-2→surface-3 sweep. prefers-reduced-motion is honoured by
   the global clamp in motion.css; the explicit rule below is a belt-and-braces
   fallback that freezes it to a static neutral surface. */
.skeleton {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  border-radius: var(--r-xs);
  animation: skel 1.3s linear infinite;
}
@keyframes skel { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; background: var(--surface-2); }
}

/* status dot */
.sdot { width: 8px; height: 8px; border-radius: var(--r-pill); flex-shrink: 0; box-shadow: 0 0 0 3px rgba(255,255,255,0.04); }
.sdot.green { background: var(--green-400); box-shadow: 0 0 0 3px var(--green-bg), 0 0 8px var(--green-400); }
.sdot.amber { background: var(--amber-400); box-shadow: 0 0 0 3px var(--amber-bg); }
.sdot.red   { background: var(--red-400);   box-shadow: 0 0 0 3px var(--red-bg); }
.sdot.gray  { background: var(--text-500); }
.sdot.blue  { background: var(--blue-400); box-shadow: 0 0 0 3px var(--blue-bg); }

/* ---------- Avatars ---------- */
.avatar {
  --sz: 32px; width: var(--sz); height: var(--sz); border-radius: var(--r-pill); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-2xs); font-weight: 600; color: var(--text-100); letter-spacing: 0.02em;
  background: var(--surface-3);
  border: 1px solid var(--border-strong); box-shadow: none;
  overflow: hidden; background-size: cover; background-position: center;
}
/* Initials in a 24px disc are a glyph pair, not prose — --fs-micro, the
   documented exception, never below it. */
.avatar.sm { --sz: 24px; font-size: var(--fs-micro); }
.avatar.lg { --sz: 44px; font-size: 0.92rem; }
.avatar.xl { --sz: 64px; font-size: 1.3rem; }
.avatar.sq { border-radius: var(--r-sm); }
.avatar-stack { display: flex; }
.avatar-stack .avatar { margin-left: -8px; border: 2px solid var(--ink-800); }
.avatar-stack .avatar:first-child { margin-left: 0; }

/* avatar color variants (hashed) — neutral monochrome tints */
.av-1, .av-2, .av-3, .av-4, .av-5, .av-6 { background: var(--surface-3); color: var(--text-200); border-color: var(--border-strong); }

/* ---------- Inputs ---------- */
/* 40px, not 38 (teardown 01 §6.3). A form field can't use a transparent hit
   pad — ::after doesn't render on a replaced element — so the only lever is
   the real height, and the only cost of the extra 2px is a slightly roomier
   form. Placeholder colour moved off the retired faint token below: a visible
   placeholder is normal text for WCAG 1.4.3 and has to hit 4.5:1 too. */
.input, .textarea, .select {
  width: 100%; height: var(--ctl-h-fld); padding: 0 12px; border-radius: var(--r-btn);
  background: var(--surface-1); border: 1px solid var(--border);
  color: var(--text-100); font-size: var(--fs-sm); transition: border-color var(--t-fast) var(--ease-smooth), background var(--t-fast) var(--ease-smooth), box-shadow var(--t-fast) var(--ease-smooth);
}
.textarea { height: auto; padding: 10px 12px; resize: vertical; line-height: 1.5; }
.input::placeholder, .textarea::placeholder { color: var(--text-500); }
/* A value longer than the field used to be cut mid-glyph, which reads as a
   rendering bug ("you can't really see it fully"). Idle fields ellipsize; the
   moment you focus one, the ellipsis is dropped so caret/selection track the
   real text. Textareas keep their full text and just scroll. */
.input { text-overflow: ellipsis; }
.input:focus { text-overflow: clip; }
.textarea { overflow: auto; }
/* <textarea class="input"> — the idiom in dozens of views — used to inherit
   .input's fixed 38px single-line height: the rows attribute was ignored and
   any second line was invisible ("you can't really see it fully"). Every
   textarea gets the multiline treatment no matter which class it carries. */
textarea.input {
  height: auto; padding: 10px 12px; resize: vertical; line-height: 1.5;
  text-overflow: clip; overflow: auto;
}
/* Auto-grow with content (field-sizing: Chromium 123+/modern engines): the box
   tracks what you type up to a sane cap, then scrolls. Engines without
   field-sizing keep rows-based sizing from the height:auto above. min-height
   keeps the empty state a comfortable ~2-line target; single-line intents
   (rows="1") stay compact. Inline JS autosize (style.height) still wins where
   a view manages its own composer. */
textarea.input, .textarea {
  min-height: 62px; max-height: min(40vh, 480px); field-sizing: content;
}
textarea.input[rows="1"], .textarea[rows="1"] { min-height: var(--ctl-h-fld); }
.input:focus, .textarea:focus, .select:focus { outline: none; border-color: var(--accent); background: var(--surface-0); box-shadow: 0 0 0 3px var(--accent-ring); }
.input-group { position: relative; display: flex; align-items: center; }
.input-group .ico { position: absolute; left: 11px; color: var(--text-400); width: 16px; height: 16px; pointer-events: none; }
.input-group .input { padding-left: 34px; }
.field-label { font-size: var(--fs-xs); font-weight: 600; color: var(--text-300); margin-bottom: 6px; display: block; }
.toggle { width: 38px; height: 24px; border-radius: var(--r-pill); background: var(--surface-3); border: 1px solid var(--border); position: relative; transition: background var(--t-fast) var(--ease-smooth), border-color var(--t-fast) var(--ease-smooth); flex-shrink: 0; cursor: pointer; }
.toggle:active { transform: scale(0.96); }
.toggle::after { content: ""; position: absolute; top: 3px; left: 2px; width: 16px; height: 16px; border-radius: var(--r-pill); background: var(--text-300); transition: all var(--t-med) var(--ease-spring); }
.toggle.on { background: var(--accent); border-color: var(--accent); }
.toggle.on::after { left: 18px; background: var(--accent-fg); }

/* ---------- "Sign in with Google" button ----------
   Follows Google's Sign-in branding guidelines: fixed 40px (32px compact) height,
   4px radius, Roboto 14px/500 at 0.25px tracking, and the exact surface / text /
   border colors carried by the --g-* tokens (themed light + dark in tokens.css).
   Deliberately NOT built on .btn — the spec locks these values, so inheriting our
   button styling would break compliance. Markup: assets/js/google-panel.js. */
.g-btn { display: inline-flex; align-items: center; gap: 10px; height: 40px; padding: 0 12px; border-radius: 4px; background: var(--g-surface); color: var(--g-text); border: 1px solid var(--g-border); cursor: pointer; font-family: 'Roboto', 'Helvetica Neue', Arial, var(--font-sans), sans-serif; font-size: 14px; font-weight: 500; letter-spacing: 0.25px; transition: background var(--t-fast) var(--ease-smooth), box-shadow var(--t-fast) var(--ease-smooth); }
.g-btn:hover { background: var(--g-surface-hover); box-shadow: 0 1px 2px rgba(60, 64, 67, 0.30), 0 1px 3px 1px rgba(60, 64, 67, 0.15); }
.g-btn:active { background: var(--g-surface-hover); }
.g-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--g-focus-ring); }
.g-btn:disabled { opacity: 0.55; cursor: default; box-shadow: none; }
.g-btn .g-mark { flex-shrink: 0; }
.g-btn-t { white-space: nowrap; }
.g-btn.g-btn-sm { height: 32px; font-size: 13px; gap: 8px; padding: 0 10px; }
.g-btn.g-btn-sm .g-mark { width: 15px; height: 15px; }

/* ---------- Segmented control / sub-tabs ---------- */
/* Overflow safety net: when a segmented row is wider than its container (long
   tab labels on a phone) it scrolls horizontally instead of clipping the final
   tab. max-width caps it to the parent; the scrollbar is hidden. Views that
   already ship their own scroll wrapper are unaffected (this only kicks in when
   the row is actually constrained). */
/* NESTED SCROLLERS (Findings F11). A segmented row that scrolls sideways
   INSIDE a page that scrolls down is the single most repeated nested-scroll
   pair in the product (`toolbar.tk-toolbar > segmented` ×6, `ct-subtabs >
   segmented` ×2) and it is the mechanism behind "the drawer body scrolls
   horizontally": you reach for the page and the strip under your finger moves
   instead. On a pointer device there is room to WRAP, so it wraps and the
   second scroller stops existing. Touch keeps the swipe-able strip, where a
   sideways swipe is a deliberate gesture rather than an accident. */
.segmented { display: inline-flex; flex-wrap: wrap; row-gap: 2px; padding: 3px; gap: 2px; background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--r-sm); max-width: 100%; }
@media (pointer: coarse) {
  .segmented { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
}
.segmented::-webkit-scrollbar { display: none; }
.segmented button { flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: var(--ctl-h-md); padding: 6px 13px; border-radius: var(--r-btn); font-size: var(--fs-sm); font-weight: 560; color: var(--text-400); transition: background var(--t-fast) var(--ease-smooth), color var(--t-fast) var(--ease-smooth), box-shadow var(--t-fast) var(--ease-smooth), transform var(--t-fast) var(--ease-smooth); white-space: nowrap; line-height: 1; }
/* A segmented button is 38 tall and sits shoulder-to-shoulder with its
   neighbours, so it takes the VERTICAL pad only — a horizontal one would eat
   the next segment's clicks. */
.segmented button { position: relative; }
.segmented button::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-50%); height: var(--tap-pad); min-height: 100%;
}
.segmented button svg, .segmented button .ico { width: 15px; height: 15px; flex-shrink: 0; }
.segmented button:hover { color: var(--text-200); }
.segmented button:active { transform: scale(0.97); }
.segmented button.active { background: var(--surface-3); color: var(--text-100); box-shadow: var(--shadow-sm); }

.subtabs { display: flex; gap: var(--sp-1); border-bottom: 1px solid var(--border); }
.subtabs button { position: relative; min-height: var(--tap-min); padding: 11px 4px; margin-right: 18px; font-size: var(--fs-sm); font-weight: 560; color: var(--text-400); transition: color var(--t-fast); }
.subtabs button:hover { color: var(--text-200); }
.subtabs button.active { color: var(--text-100); }
.subtabs button.active::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; border-radius: var(--r-xs); background: var(--accent); }

/* ---------- KPI / stat cards ---------- */
.kpi { padding: var(--sp-5); display: flex; flex-direction: column; gap: 10px; position: relative; overflow: hidden; }
.kpi .kpi-label { font-size: var(--fs-xs); color: var(--text-400); font-weight: 560; display: flex; align-items: center; gap: 7px; }
.kpi .kpi-label .ico { width: 15px; height: 15px; color: var(--text-400); }
.kpi .kpi-value { font-family: var(--font-display); font-size: 2rem; font-weight: 560; color: var(--text-100); letter-spacing: -0.02em; line-height: 1; }
.kpi .kpi-value.mono { font-family: var(--font-mono); font-size: 1.7rem; }
.kpi .kpi-delta { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-xs); font-weight: 600; }
.kpi-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

/* ---------- Tables ---------- */
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.table thead th { text-align: left; padding: 10px 14px; font-size: var(--fs-2xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-400); border-bottom: 1px solid var(--border); white-space: nowrap; }
.table tbody td { padding: 12px 14px; border-bottom: 1px solid var(--border-soft); color: var(--text-200); vertical-align: middle; }
.table tbody tr { transition: background var(--t-fast); }
.table tbody tr:hover { background: var(--surface-2); }
.table tbody tr:last-child td { border-bottom: none; }
.table .num { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.table-wrap { overflow: hidden; border-radius: var(--r-lg); border: 1px solid var(--border); background: var(--surface-1); }
.cell-strong { color: var(--text-100); font-weight: 560; }

/* ---------- Kanban ---------- */
.board { display: flex; gap: var(--sp-4); overflow-x: auto; padding-bottom: var(--sp-4); align-items: flex-start; }
.board-col { flex: 0 0 290px; display: flex; flex-direction: column; gap: var(--sp-3); }
.board-col-head { display: flex; align-items: center; justify-content: space-between; padding: 4px 6px; }
.board-col-head .row { gap: 8px; }
.board-col-head .title { font-size: var(--fs-sm); font-weight: 640; color: var(--text-100); }
.board-col-body { display: flex; flex-direction: column; gap: var(--sp-3); min-height: 60px; }
.kard {
  position: relative;
  background: var(--surface-0);
  border: 1px solid var(--border); border-radius: var(--r-md); padding: 13px;
  display: flex; flex-direction: column; gap: 9px; cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-smooth), transform var(--t-fast) var(--ease-smooth), box-shadow var(--t-fast) var(--ease-smooth); box-shadow: var(--shadow-card);
}
.kard:hover { border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow-elevated); }
.kard:active { transform: scale(0.994); }
.kard-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.kard-title { font-size: var(--fs-sm); font-weight: 560; color: var(--text-100); line-height: 1.35; }
/* The title as the WAY IN to the record. The card body expands in place, so the
   name has to carry "this one opens the thing" on its own — it underlines on
   hover/focus to say so. Kept here rather than in a view so every board that
   adopts the shared card gets the same affordance instead of reinventing it.
   Inert until the title is actually a <button>. */
button.kard-title { appearance: none; background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; text-align: left; cursor: pointer; }
button.kard-title:hover, button.kard-title:focus-visible { text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
button.kard-title:focus-visible { outline: none; text-decoration-color: var(--text-300); }
.kard-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 2px; }

/* ---------- DENSE CARD (SHARED: CRM records, Tasks, Content pieces) ----------
   One card language for every board, so a column shows a STACK of things
   instead of two tall tiles. Structure, top to bottom:

     .kd-head   optional headline strip — the ONE decisive thing this card
                knows (a booked call · a due date · a scheduled slot). Views
                add their own tone modifier for colour; the text always
                carries the meaning, colour is decorative.
     .kd-row    select · title · trailing meta · expand chevron
     .kd-sub    who/what it is + quiet signals for anything the strip isn't
                showing, so a card never hides that there's more
     .kd-more   the detail, revealed in place by the chevron

   `flex:0 0 auto` on .kard is LOAD-BEARING here: a board column is a
   fixed-height flex column, and the overflow:hidden that clips the strip to
   the rounded corners also zeroes a flex item's automatic minimum size —
   without it a crowded column squashes every card down to its strip instead
   of scrolling. */
.kard.kd { flex: 0 0 auto; padding: 0; gap: 0; overflow: hidden; }
.kd-head {
  display: flex; align-items: center; gap: 6px; padding: 5px 10px; min-height: 24px;
  font-size: var(--fs-2xs); font-weight: 650; line-height: 1.25;
  color: var(--text-300); background: var(--surface-2);
  border-bottom: 1px solid var(--border-soft);
}
.kd-head .ico { width: 11px; height: 11px; flex: 0 0 auto; }
.kd-head-t { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kd-head-x { flex: 0 0 auto; font-weight: 560; opacity: .72; max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* urgency tones — shared so "overdue" looks the same on a lead and on a task */
.kd-head-late { color: var(--red-400); background: color-mix(in srgb, var(--red-400) 10%, transparent); border-bottom-color: color-mix(in srgb, var(--red-400) 22%, transparent); }
.kd-head-soon { color: var(--amber-400); background: color-mix(in srgb, var(--amber-400) 10%, transparent); border-bottom-color: color-mix(in srgb, var(--amber-400) 20%, transparent); }
.kd-head-ok   { color: var(--green-400); background: color-mix(in srgb, var(--green-400) 9%, transparent); border-bottom-color: color-mix(in srgb, var(--green-400) 18%, transparent); }
.kd-head-calm { color: var(--text-400); font-weight: 560; }
.kd-head-calm .kd-head-t { font-weight: 560; }

.kd-row { display: flex; align-items: center; gap: 7px; padding: 7px 10px 0; }
.kd-row .kard-title { flex: 1; min-width: 0; font-weight: 640; letter-spacing: -.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* `relative`, not `static`, and that is load-bearing rather than cosmetic:
   relative changes nothing about flex layout (which is all this rule wanted —
   it exists to undo `.kard > .sel-check`'s absolute), but it makes the
   checkbox the containing block for its own 26px hit pad. With `static` the
   pad's `left/top: 50%` resolved against the CARD instead and parked a 26px
   click-eater dead centre over the record title. Measured: 18 titles across
   Nova Home CRM and Tasks were un-clickable at their own centre. */
.kd-row .sel-check { position: relative; flex: 0 0 auto; opacity: 0; transition: opacity var(--t-fast); }
.kard.kd:hover .sel-check, .kard.kd.is-sel .sel-check, .kard.kd:focus-within .sel-check { opacity: 1; }
.kd-exp {
  flex: 0 0 auto; display: grid; place-items: center; width: 20px; height: 20px; padding: 0;
  border: none; border-radius: var(--r-sm); background: transparent; color: var(--text-500);
  cursor: pointer; transition: color var(--t-fast), background var(--t-fast), transform var(--t-fast);
}
.kd-exp:hover { color: var(--text-100); background: var(--surface-2); }
.kd-exp:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--accent-ring); }
.kard.kd.is-open .kd-exp { transform: rotate(90deg); }

.kd-sub { display: flex; align-items: center; gap: 6px; padding: 2px 10px 8px 32px; min-height: 19px; font-size: var(--fs-2xs); color: var(--text-400); }
.kd-sub-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kd-sig { display: inline-flex; align-items: center; gap: 5px; margin-left: auto; flex: 0 0 auto; }
.kd-sig-i { display: grid; place-items: center; color: var(--text-500); opacity: .8; }
.kd-sig-i .ico { width: 11px; height: 11px; }
.kd-sig-i.is-late { color: var(--red-400); opacity: 1; }

.kd-pad { height: 8px; }   /* a card with nothing to say still breathes */
.kd-more { display: flex; flex-direction: column; gap: 8px; padding: 9px 10px 9px 32px; border-top: 1px solid var(--border-soft); background: var(--surface-1); }
.kd-more-foot { display: flex; align-items: center; gap: 10px; }
.kd-more-in { animation: kdMore var(--t-med) var(--ease-out); }
@keyframes kdMore { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .kd-exp, .kd-more-in { transition: none; animation: none; } }

/* ---------- Bulk selection (SHARED: CRM leads, Tasks, Content pieces) ----------
   One design for every board/table with multi-select. Views must use these
   classes so selection looks + works identically everywhere:
   • .selbar  — the bulk action bar (container with [data-bulkbar])
   • .sel-check — the checkbox button on a card/row ([data-sel] / [data-selall]); add .on when selected
   • .kard.is-sel — selected card ring;  tr.is-sel — selected table row;  .td-sel — the checkbox cell
   Selection toggling must be done IN-PLACE (toggle classes, never repaint the whole body). */
.selbar[hidden] { display: none !important; }
/* Floating bulk-action bar — ONE design for every board/table (CRM, Tasks,
   Content). Docks bottom-center and springs up when a selection exists; the count
   leads (with a single accent dot), actions are quiet ghost buttons so the bar
   reads calm, not like a row of heavy controls. Shared so multi-select looks and
   feels identical everywhere. */
.selbar {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  z-index: var(--z-dock);
  display: flex; align-items: center; gap: 3px;
  padding: 7px 8px 7px 4px;
  max-width: min(720px, calc(100vw - 28px));
  background: var(--surface-0); border: 1px solid var(--border-strong);
  border-radius: var(--r-pill); box-shadow: var(--shadow-xl);
  animation: selbar-in var(--t-slow) var(--ease-spring);
}
@keyframes selbar-in {
  from { opacity: 0; transform: translateX(-50%) translateY(12px) scale(0.98); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0)    scale(1); }
}
.selbar-count {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-100);
  white-space: nowrap; padding: 0 6px 0 13px; font-variant-numeric: tabular-nums;
}
.selbar-count::before {
  content: ""; width: 7px; height: 7px; border-radius: var(--r-pill);
  background: var(--accent); flex-shrink: 0;
}
.selbar-divider { width: 1px; align-self: stretch; background: var(--border); margin: 6px 2px; }
.selbar-actions { display: flex; align-items: center; gap: 2px; flex-wrap: nowrap; }
.selbar-actions .btn { height: var(--ctl-h-sm); background: transparent; border-color: transparent; color: var(--text-200); border-radius: var(--r-sm); }
.selbar-actions .btn:hover { background: var(--surface-2); color: var(--text-100); }
.selbar-actions .btn .ico { color: var(--text-300); }
.selbar-actions .btn:hover .ico { color: var(--text-100); }
.selbar-actions .select, .selbar-actions .select-sm { height: var(--ctl-h-sm); font-size: var(--fs-sm); }
.selbar-del, .selbar-del .ico { color: var(--red-400); }
.selbar-del:hover, .selbar-del:hover .ico { color: var(--red-400); }
.selbar-del:hover { background: var(--red-bg); }

.sel-check {
  width: 18px; height: 18px; border-radius: var(--r-xs); flex-shrink: 0;
  border: 1.5px solid var(--border-strong); background: var(--surface-1);
  display: grid; place-items: center; cursor: pointer; color: var(--accent-fg);
  transition: opacity var(--t-fast), background var(--t-fast), border-color var(--t-fast);
}
.sel-check .ico { width: 12px; height: 12px; }
.sel-check.on { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.sel-check:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
/* 18×18 painted, 26×26 to aim at. It sits in a reserved gutter beside a title
   or in a row head, i.e. 4px from real content on both sides, so it takes the
   normative SC 2.5.8 floor rather than the AAA 44 — a 46px pad here would
   reach straight onto the row title and make THAT unclickable. */
.sel-check { position: relative; }
/* views/crm.js still sets `.v-crm .crm-krow .sel-check { position: static }`,
   which outranks the line above (three classes, and view styles load last), so
   the pad there would escape onto the card exactly as described under
   `.kd-row .sel-check`. Until that one word becomes `relative` (handoff — it is
   a no-op for layout), the pad is switched OFF in that context rather than
   allowed to misfire. The checkbox stays 18×18 there; a small target beats a
   dead title. */
.crm-krow .sel-check::before { content: none; }
.sel-check::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: var(--tap-pad-sm); height: var(--tap-pad-sm);
  pointer-events: inherit;
}

/* kanban card checkbox: floats top-LEFT in a reserved gutter, revealed on
   hover or when selected. Cards that carry this control add .kard-selectable,
   which reserves a permanent left gutter on the card's first content row so the
   checkbox never overlaps title/score/icon, and revealing it (opacity only,
   no display change) causes no layout shift. */
.kard > .sel-check { position: absolute; top: 11px; left: 11px; opacity: 0; z-index: 2; }
.kard:hover > .sel-check, .kard.is-sel > .sel-check { opacity: 1; }
.kard:focus-within > .sel-check, .kard > .sel-check:focus-visible { opacity: 1; }
/* reserved gutter so content clears the checkbox slot (18px box + gap) */
.kard-selectable > .kard-top,
.kard-selectable > .kard-title { padding-left: 28px; }
.kard.is-sel { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.kard.is-sel:hover { box-shadow: 0 0 0 1px var(--accent), var(--shadow-md); }

/* table row selection */
tr.is-sel { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.td-sel { width: 34px; text-align: center; }
.td-sel .sel-check { opacity: .55; margin: 0 auto; }
tr:hover .td-sel .sel-check, tr.is-sel .td-sel .sel-check { opacity: 1; }

@media (max-width: 640px) {
  .selbar { left: 12px; right: 12px; bottom: 12px; transform: none; max-width: none; border-radius: var(--r-xl); flex-wrap: wrap; animation-name: selbar-in-m; }
  .selbar-actions { flex: 1 1 auto; justify-content: flex-end; }
}
@keyframes selbar-in-m { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* ---------- Progress ---------- */
.bar { height: 6px; border-radius: var(--r-pill); background: var(--surface-3); overflow: hidden; }
.bar > i { display: block; height: 100%; border-radius: var(--r-pill); background: var(--accent); }
.bar.green > i { background: var(--green-400); }
.bar.blue > i  { background: var(--blue-400); }
.ring { --p: 60; --sz: 44px; position: relative; width: var(--sz); height: var(--sz); border-radius: var(--r-pill); display: grid; place-items: center; background: conic-gradient(var(--accent) calc(var(--p)*1%), var(--surface-3) 0); }
.ring::after { content: ""; position: absolute; width: calc(var(--sz) - 9px); height: calc(var(--sz) - 9px); border-radius: var(--r-pill); background: var(--surface-0); }
.ring span { position: relative; font-size: var(--fs-2xs); font-weight: 700; }

/* ---------- List rows ----------
   A list row is the one control whose height is NOT a designer's decision —
   it is content plus padding — so it takes option 1 from the tap-target rules
   in tokens.css and is simply 44px tall. It measured 42, which is not a
   rounding error: the row's own icon buttons carry a 44px pad and were being
   clipped 2px short by the neighbouring row's pad, so every "Task actions"
   menu in the product reported 44×42. Fixing the row fixes the buttons in it.
   (min-height is correct HERE and wrong on `.brand-logo-link` — Findings F7 —
   because nobody designed this row to be 42.) */
.lrow { display: flex; align-items: center; gap: var(--sp-3); min-height: var(--tap-min); padding: 11px 14px; border-radius: var(--r-sm); transition: background var(--t-fast) var(--ease-smooth); }
.lrow:hover { background: var(--surface-2); }
.lrow + .lrow { border-top: 1px solid var(--border-soft); }

/* ---------- Toolbar ---------- */
.toolbar { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.divider { height: 1px; background: var(--border); border: none; margin: var(--sp-4) 0; }
.vdivider { width: 1px; align-self: stretch; background: var(--border); margin: 0 4px; }

/* ---------- Empty / placeholder ---------- */
.empty { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: var(--sp-3); padding: var(--sp-9); text-align: left; color: var(--text-400); }
/* Direct child only — the hero glyph. A bare `.empty .ico` also swallowed the
   14px icon inside the action button below (36px plus overflowing a 30px
   button — the oversized/misaligned "+" on New reminder etc.). */
.empty > .ico { flex-shrink: 0; width: 36px; height: 36px; color: var(--text-500); display: grid; place-items: center; }
/* multi-line empty states wrap their text so the icon stays left of the block */
.empty .empty-text { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
.empty .empty-title { font-size: var(--fs-sm); font-weight: 600; color: var(--text-200); }
.empty .empty-sub { font-size: var(--fs-xs); color: var(--text-400); }
.empty .empty-text .btn { margin-top: var(--sp-2); }
/* Compact variant — for empties tucked inside a card/section (ledgers, sub-panels)
   where the full-size hero glyph + padding would overwhelm the surrounding UI. */
.empty.empty-sm { padding: var(--sp-5) var(--sp-4); gap: var(--sp-3); }
.empty.empty-sm > .ico { width: 22px; height: 22px; }
.empty.empty-sm .empty-title { font-size: var(--fs-xs); }

/* ---------- Dropdown / menu / popover ---------- */
.menu { background: var(--ink-600); border: 1px solid var(--border-strong); border-radius: var(--r-md); box-shadow: var(--shadow-xl); padding: 6px; min-width: 200px; }
.menu-item { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--r-xs); font-size: var(--fs-sm); color: var(--text-200); cursor: pointer; transition: background var(--t-fast); }
.menu-item:hover { background: var(--surface-3); }
.menu-item:active { transform: scale(0.98); }
.menu-item .ico { width: 16px; height: 16px; color: var(--text-400); }
.menu-sep { height: 1px; background: var(--border); margin: 5px 0; }
/* when a .menu-item is a real <button> (canonical openPopover) reset native chrome */
button.menu-item { width: 100%; background: transparent; border: 0; font: inherit; text-align: left; }
.menu-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-item-check { color: var(--text-100); display: inline-flex; }
.menu-item.is-active { color: var(--text-100); font-weight: 600; background: var(--accent-soft); }
.menu-item.is-active:hover { background: var(--accent-soft); }
.menu-item-danger, .menu-item-danger .ico { color: var(--red-400); }
.menu-item-danger:hover { background: var(--red-bg); }
.menu-item:disabled { opacity: .45; cursor: not-allowed; }
/* popover placement polish: grow from the anchored edge (set by openPopover) */
.popover { transform-origin: top left; }
.popover[data-placement="top"] { transform-origin: bottom left; }

/* ---------- Tooltip (WS-14 canonical, JS-driven via ui.js tip()) ----------
   A styled, delayed, edge-aware, position:fixed tooltip — the replacement
   for 193 native title="" attrs. Positioned by the shared placeFloating
   engine so it flips/clamps at the viewport edge (the [data-tip]::after CSS
   tooltip has no collision handling; prefer tip() for anything near an edge). */
.tip-float {
  position: fixed; z-index: var(--z-tooltip); pointer-events: none;
  max-width: 260px; padding: 5px 9px; border-radius: var(--r-xs);
  background: var(--ink-600); color: var(--text-100); border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-lg); font-size: var(--fs-2xs); line-height: 1.35; font-weight: 500;
  opacity: 0; transform: translateY(2px); transition: opacity var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
.tip-float.in { opacity: 1; transform: none; }

/* ---------- Chip (WS-14) — alias of .tag; ui.js chip() maps to tag() ---------- */
.chip { display: inline-flex; align-items: center; gap: 6px; padding: 2px 9px; border-radius: var(--r-pill); font-size: var(--fs-2xs); font-weight: 600; border: 1px solid var(--border); background: var(--surface-2); color: var(--text-300); }

/* ---------- Icon chip ---------- */
.ichip { width: var(--ctl-h-md); height: var(--ctl-h-md); border-radius: var(--r-sm); display: grid; place-items: center; background: var(--surface-3); border: 1px solid var(--border); color: var(--text-300); flex-shrink: 0; }
.ichip.gold { background: var(--accent-soft); border-color: var(--border-strong); color: var(--text-100); }
.ichip.lg { width: 46px; height: 46px; border-radius: var(--r-md); }
.ichip svg { width: 18px; height: 18px; }

/* ---------- Generic grid ---------- */
.grid { display: grid; gap: var(--sp-4); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1280px) { .grid-4 { grid-template-columns: repeat(2,1fr); } .grid-3 { grid-template-columns: repeat(2,1fr);} }
@media (max-width: 860px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } }

/* ---------- Tooltip ---------- */
/* A small, subtle, slightly-delayed hint — not a big instant overlay. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip); position: absolute; bottom: calc(100% + 6px); left: 50%;
  transform: translateX(-50%) translateY(2px); background: var(--ink-600); color: var(--text-100);
  font-size: var(--fs-2xs); line-height: 1.3; font-weight: 500; padding: 4px 8px; border-radius: var(--r-xs);
  white-space: nowrap; box-shadow: var(--shadow-sm); border: 1px solid var(--border-strong); z-index: var(--z-toast);
  pointer-events: none; opacity: 0; transition: opacity var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
[data-tip]:hover::after, [data-tip]:focus-visible::after { opacity: 1; transform: translateX(-50%) translateY(0); transition-delay: .35s; }

/* WS-30: [data-tip]::after CLOBBERS decorative ::after pseudo-elements. The
   switch knob (.toggle::after) was the live casualty — a toggle carrying
   data-tip (scheduling event types/questions, workflows list) rendered as a
   blank pill (knob got content:attr(data-tip) + opacity:0 at rest) and showed
   a garbled text box on hover ("weird boxes"). For toggles, the tooltip moves
   to ::before (free on .toggle) and the knob is re-asserted on ::after. If a
   future element mixes data-tip with its own ::after, follow this pattern —
   or put data-tip on a wrapper instead. */
.toggle[data-tip]::before {
  content: attr(data-tip); position: absolute; bottom: calc(100% + 6px); left: 50%;
  transform: translateX(-50%) translateY(2px); background: var(--ink-600); color: var(--text-100);
  font-size: var(--fs-2xs); line-height: 1.3; font-weight: 500; padding: 4px 8px; border-radius: var(--r-xs);
  white-space: nowrap; box-shadow: var(--shadow-sm); border: 1px solid var(--border-strong); z-index: var(--z-toast);
  pointer-events: none; opacity: 0; transition: opacity var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
.toggle[data-tip]:hover::before, .toggle[data-tip]:focus-visible::before { opacity: 1; transform: translateX(-50%) translateY(0); transition-delay: .35s; }
.toggle[data-tip]::after {
  content: ""; position: absolute; top: 3px; bottom: auto; left: 2px; width: 16px; height: 16px;
  border-radius: var(--r-pill); background: var(--text-300); border: 0; padding: 0; box-shadow: none; z-index: auto;
  opacity: 1; transform: none; white-space: normal; pointer-events: none;
  transition: all var(--t-med) var(--ease-spring);
}
.toggle.on[data-tip]::after { left: 18px; background: var(--accent-fg); }

/* ---------- AI accents (used in chat/setter/workflows) ---------- */
.ai-pill { display:inline-flex; align-items:center; gap:7px; padding:5px 11px; border-radius:var(--r-pill); font-size:var(--fs-xs); font-weight:600; color:var(--text-300); background: var(--surface-2); border:1px solid var(--border); }

/* ── WCAG 2.2.2 Pause, Stop, Hide ──────────────────────────────────────────
   Separate from prefers-reduced-motion: ANY auto-playing, non-essential
   animation that runs for more than five seconds needs a pause/stop control
   available to EVERY user, not only the ones who set a motion preference.
   The product had no such control, so the decorative loops below are capped
   instead — the SC is satisfied by motion that stops on its own inside five
   seconds. Loops that report STATUS (spinners, skeletons, typing dots,
   progress shimmers) are "essential" and are deliberately left running.
   ────────────────────────────────────────────────────────────────────────── */
.ai-pill .dot { width:6px;height:6px;border-radius: var(--r-pill);background:var(--green-400); animation: eos-pulse 2s 2; }
@keyframes eos-pulse { 0%,100%{opacity:1;} 50%{opacity:.4;} }
.typing { display:inline-flex; gap:3px; }
.typing i { width:5px;height:5px;border-radius: var(--r-pill);background:var(--text-400); animation: eos-typing 1.2s infinite; }
.typing i:nth-child(2){animation-delay:.15s;} .typing i:nth-child(3){animation-delay:.3s;}
@keyframes eos-typing { 0%,60%,100%{transform:translateY(0);opacity:.4;} 30%{transform:translateY(-4px);opacity:1;} }

/* sparkline svg sizing */
.spark { width: 100%; height: 40px; display:block; overflow: visible; }

/* scroll area used inside views */
.scroll-y { overflow-y: auto; }
.scroll-x { overflow-x: auto; }

/* ============================================================
   Shared DETAIL DRAWER (assets/js/drawer.js)
   Slide-in-from-right panel. Reused by CRM + Tasks. Monochrome,
   soft radii, hairline borders, tokens only. Mobile = full sheet.
   ============================================================ */
.drawer-scrim {
  position: fixed; inset: 0; z-index: var(--z-modal);
  display: flex; justify-content: flex-end;
  background: color-mix(in srgb, var(--ink-900, #04070d) 46%, transparent);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  opacity: 0; transition: opacity var(--t-med) var(--ease-out);
}
.drawer-scrim.in { opacity: 1; }
.drawer {
  --drawer-w: 440px;
  width: min(var(--drawer-w), 94vw); height: 100%;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface-0); border-left: 1px solid var(--border-strong);
  box-shadow: var(--shadow-xl);
  transform: translateX(28px); opacity: .6;
  transition: transform var(--t-med) var(--ease-out), opacity var(--t-med) var(--ease-out);
}
.drawer-scrim.in .drawer { transform: none; opacity: 1; }

.drawer-head {
  flex-shrink: 0; display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  border-bottom: 1px solid var(--border); position: relative;
}
.drawer-head-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.drawer-title { font-family: var(--font-display); font-size: 1.3rem; font-weight: 560; line-height: 1.15; color: var(--text-100); word-break: break-word; }
.drawer-sub { font-size: var(--fs-sm); color: var(--text-400); line-height: 1.3; }
.drawer-sub[hidden] { display: none; }
.drawer-head-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.drawer-head-actions:empty { display: none; }
.drawer-close {
  flex-shrink: 0; width: var(--ctl-h-sm); height: var(--ctl-h-sm); border-radius: var(--r-btn);
  display: grid; place-items: center; color: var(--text-400);
  background: transparent; border: 1px solid transparent; transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.drawer-close:hover { background: var(--surface-2); color: var(--text-100); border-color: var(--border); }

.drawer-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-5); }

/* tabbed body (drawerTabs) */
.drawer-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin: calc(var(--sp-5) * -1) calc(var(--sp-5) * -1) 0; padding: 0 var(--sp-5); position: sticky; top: calc(var(--sp-5) * -1); background: var(--surface-0); z-index: 2; }
.drawer-tab { display: inline-flex; align-items: center; gap: 6px; padding: 11px 12px; font-size: var(--fs-sm); font-weight: 560; color: var(--text-400); position: relative; transition: color var(--t-fast); }
.drawer-tab:hover { color: var(--text-200); }
.drawer-tab.active { color: var(--text-100); }
.drawer-tab.active::after { content: ""; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 2px; border-radius: var(--r-xs); background: var(--text-100); }
.drawer-tab-badge { font-size: var(--fs-2xs); font-weight: 700; background: var(--surface-3); color: var(--text-400); border-radius: var(--r-pill); padding: 0 6px; }
.drawer-panels { display: flex; flex-direction: column; }
.drawer-panel { display: flex; flex-direction: column; gap: var(--sp-4); padding-top: var(--sp-5); }
.drawer-panel.hidden { display: none; }

/* mobile (≤640px): full-screen sheet */
@media (max-width: 640px) {
  .drawer { width: 100vw; border-left: none; border-radius: 0; }
}

/* ============================================================
   USER-DATA COLOUR + ICON PICKERS
   (assets/js/color.js · icon-picker.js — see those files for scope)

   Everything below styles the CHOOSING of colour, and the rendering of
   colour a user already chose for THEIR data (pipelines, folders, tags).
   The chrome around it stays monochrome + token-driven; the only raw
   colour values here are the checkerboard/hue-wheel of the picker itself,
   which are physical properties of a colour control, not brand chrome.
   ============================================================ */

/* ---------- User-colour surfaces ----------
   color.js colorVars() stamps --uc / --uc-fg / --uc-ink-{light,dark} /
   --uc-soft / --uc-border onto an element. It emits BOTH theme inks up
   front and CSS picks the live one here, so flipping light/dark never
   needs a JS repaint and a user colour can never go unreadable. */
.u-color { --uc-ink: var(--uc-ink-light); }
[data-theme="dark"] .u-color { --uc-ink: var(--uc-ink-dark); }
/* Respect an explicit light theme nested inside dark (and vice versa). */
[data-theme="light"] .u-color { --uc-ink: var(--uc-ink-light); }

/* The three ways user colour is allowed to appear. */
.u-dot { width: 8px; height: 8px; border-radius: var(--r-pill); background: var(--uc); flex-shrink: 0; }
.u-chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 22px; padding: 0 9px; border-radius: var(--r-pill);
  font-size: var(--fs-2xs); font-weight: 600; white-space: nowrap;
  background: var(--uc-soft); color: var(--uc-ink); border: 1px solid var(--uc-border);
}
/* Solid variant — the user's exact colour as a fill, with the derived
   readable foreground (color.js onColor) rather than a guess. */
.u-chip-solid { background: var(--uc); color: var(--uc-fg); border-color: transparent; }
.u-spine { background: var(--uc); }

/* ---------- colorSwatch() trigger ---------- */
.color-swatch {
  width: var(--ctl-h-sm); height: var(--ctl-h-sm); flex-shrink: 0; padding: 3px;
  border-radius: var(--r-xs); border: 1px solid var(--border-strong);
  background: var(--surface-0); display: grid; place-items: center;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
}
.color-swatch:hover { border-color: var(--text-400); }
.color-swatch:active { transform: scale(0.94); }
.color-swatch:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
.color-swatch > span { display: block; width: 100%; height: 100%; border-radius: var(--r-xs); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.14); }
.color-swatch.sm { width: 24px; height: 24px; padding: 2px; }

/* ---------- Colour picker (color.js openColorPicker) ---------- */
.color-picker { width: 232px; padding: 10px; display: flex; flex-direction: column; gap: 9px; }

/* Saturation/brightness area: hue base + white→transparent (x) + transparent→black (y). */
.cp-area {
  position: relative; height: 132px; border-radius: var(--r-sm);
  background: var(--cp-hue, #4f6fa3); cursor: crosshair; overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.16); touch-action: none;
}
.cp-area:focus-visible { outline: none; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.16), 0 0 0 3px var(--accent-ring); }
.cp-area-sat, .cp-area-lum { position: absolute; inset: 0; border-radius: inherit; }
.cp-area-sat { background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0)); }
.cp-area-lum { background: linear-gradient(to top, #000, rgba(0, 0, 0, 0)); }
.cp-thumb {
  position: absolute; width: 13px; height: 13px; border-radius: var(--r-pill);
  border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4), var(--shadow-sm);
  transform: translate(-50%, -50%); pointer-events: none;
}

/* Hue rail — the full spectrum, because "why can't we use every color?". */
.cp-hue {
  position: relative; height: 12px; border-radius: var(--r-pill); cursor: pointer; touch-action: none;
  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.16);
}
.cp-hue:focus-visible { outline: none; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.16), 0 0 0 3px var(--accent-ring); }
.cp-hue-thumb {
  position: absolute; top: 50%; width: 14px; height: 14px; border-radius: var(--r-pill);
  background: #fff; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4), var(--shadow-sm);
  transform: translate(-50%, -50%); pointer-events: none;
}

.cp-row { display: flex; align-items: center; gap: 7px; }
.cp-preview { width: 28px; height: 28px; border-radius: var(--r-xs); flex-shrink: 0; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.16); }
.cp-hexwrap { position: relative; flex: 1; display: flex; align-items: center; min-width: 0; }
.cp-hash { position: absolute; left: 9px; color: var(--text-400); font-size: var(--fs-xs); pointer-events: none; }
.cp-hex {
  width: 100%; height: var(--ctl-h-sm); padding: 0 8px 0 20px;
  font-family: var(--font-mono); font-size: var(--fs-xs); text-transform: lowercase;
}
.cp-auto { flex-shrink: 0; }

.cp-presets { display: grid; grid-template-columns: repeat(8, 1fr); gap: 5px; }
.cp-preset {
  aspect-ratio: 1; border-radius: var(--r-xs); padding: 2px;
  border: 1px solid transparent; display: grid; place-items: center;
  transition: border-color var(--t-fast), transform var(--t-fast);
}
.cp-preset > span { display: block; width: 100%; height: 100%; border-radius: var(--r-xs); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.16); }
.cp-preset:hover { transform: scale(1.12); }
.cp-preset.on { border-color: var(--text-100); }
.cp-preset:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }

/* ---------- Icon picker (icon-picker.js openIconPicker) ---------- */
.icon-picker { width: 296px; padding: 9px; display: flex; flex-direction: column; gap: 8px; }
.ip-tabs { display: flex; gap: 2px; }
.ip-tab {
  flex: 1; height: var(--ctl-h-sm); border-radius: var(--r-btn); font-size: var(--fs-xs); font-weight: 560;
  color: var(--text-400); background: transparent; transition: background var(--t-fast), color var(--t-fast);
}
.ip-tab:hover { color: var(--text-200); background: var(--surface-2); }
.ip-tab.on { color: var(--text-100); background: var(--surface-3); }
.ip-tab:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
.ip-search .ip-input { height: var(--ctl-h-sm); font-size: var(--fs-xs); }

.ip-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(34px, 1fr)); gap: 2px;
  max-height: 244px; overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: thin; margin: 0 -2px; padding: 0 2px;
}
.ip-tile {
  aspect-ratio: 1; border-radius: var(--r-xs); display: grid; place-items: center;
  color: var(--text-300); border: 1px solid transparent;
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.ip-tile:hover { background: var(--surface-2); color: var(--text-100); }
.ip-tile.on { border-color: var(--accent); color: var(--text-100); background: var(--surface-3); }
.ip-tile:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
.ip-tile-emoji > span { font-size: 17px; line-height: 1; }
.ip-none { grid-column: 1 / -1; padding: 22px 8px; text-align: center; font-size: var(--fs-xs); }
.ip-foot { display: flex; justify-content: flex-end; }
.ip-count { font-size: var(--fs-2xs); color: var(--text-500); font-variant-numeric: tabular-nums; }

/* ---------- Mobile (≤480px): both pickers dock as a centred sheet ----------
   390px is a hard requirement; an anchored popover would overflow. */
@media (max-width: 480px) {
  .color-picker.cp-sheet, .icon-picker.ip-sheet {
    position: fixed; left: 50%; top: auto; bottom: 12px;
    transform: translateX(-50%) translateY(6px);
    width: calc(100vw - 24px); max-width: 360px;
  }
  .color-picker.cp-sheet.in, .icon-picker.ip-sheet.in { transform: translateX(-50%); }
  .cp-area { height: 148px; }
  /* ≥40px tap targets on touch. */
  .cp-preset, .ip-tile { min-height: 40px; }
  .ip-grid { grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); max-height: 40vh; }
  .color-swatch { width: 32px; height: 32px; }
}

/* ---------------------------------------------------------------
   Workspace switch animation — a brief full-screen wipe showing the
   target workspace's mark while the app reloads into it. Injected by
   playWorkspaceSwitch() in app.js. Mirrors the login.html overlay.
--------------------------------------------------------------- */
.ws-switch-overlay {
  position: fixed; inset: 0; z-index: 4000; display: grid; place-items: center;
  background: var(--surface-0); opacity: 0; transition: opacity .28s ease;
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.ws-switch-overlay.show { opacity: 1; }
.wso-card {
  display: flex; flex-direction: column; align-items: center; gap: 16px;
  transform: scale(.94); opacity: 0;
  transition: transform .42s cubic-bezier(.2,.8,.2,1), opacity .42s ease;
}
.ws-switch-overlay.show .wso-card { transform: scale(1); opacity: 1; }
.wso-icon {
  width: 62px; height: 62px; border-radius: var(--r-lg); display: grid; place-items: center;
  object-fit: cover; background: var(--surface-3); color: var(--text-100);
  border: 1px solid var(--border); font-size: 25px; font-weight: 680;
  box-shadow: 0 12px 44px rgba(0,0,0,.30);
}
.wso-name { font-size: var(--fs-lg); font-weight: 640; color: var(--text-100); letter-spacing: -.01em; }
.wso-sub { display: flex; align-items: center; gap: 9px; font-size: var(--fs-sm); color: var(--text-400); }
.wso-dots { width: 36px; height: 5px; border-radius: var(--r-xs); overflow: hidden; background: var(--surface-3); position: relative; }
.wso-dots::after {
  content: ''; position: absolute; top: 0; bottom: 0; width: 40%; border-radius: var(--r-xs);
  background: var(--text-300); animation: wso-slide 1s ease-in-out infinite;
}
@keyframes wso-slide { 0% { left: -40%; } 100% { left: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .ws-switch-overlay, .wso-card { transition: none; }
  .wso-dots::after { animation: none; }
}

/* ============================================================
   THE HIT-REGION FLOOR FOR DENSE CONTROLS  (P1-03 / Findings F12)
   ------------------------------------------------------------
   The 44px floor is a promise the DESIGN SYSTEM makes, so where a control is
   painted small on purpose the system is what has to make up the difference.
   Every selector below was measured under the floor on 2026-08-01 across the
   three products; each is a 13–21px glyph button living in a grid or a dense
   row, where a 46px pad would land on the neighbouring control and make THAT
   unclickable (which is the F12 failure mode — a control that is dead at the
   exact pixel a user aims at). So they clear the NORMATIVE floor, WCAG 2.2
   SC 2.5.8's 24×24, with the shared `--tap-pad-sm` region.

   These live here rather than in each view because the floor is not a view's
   decision, and because the same control is currently duplicated across views
   under four names. (Handoff, and it is the real fix: `.kd-exp` in tasks.js,
   crm.js and content.js and `.crm-kexp` in crm.js are one control — a card
   disclosure chevron — written out four times. Merge them into a single
   `.kard-exp` in this file and this block loses half its selectors.)
   ============================================================ */
.tk-check,          /* task row "done" checkbox            17×18 ×160 */
.v-pri-check,       /* priority item "done" checkbox       16×17 ×32  */
.cal-ev-chk,        /* calendar tray "Mark done"           14×15 ×66  */
.kd-exp,            /* card disclosure chevron             20×20 ×84  */
.crm-kexp,          /* …the same control, fourth spelling  20×20 ×28  */
.cal-list-refresh { /* calendar sidebar glyph actions      17×17      */
  position: relative;
}
/* NOT here, and deliberately: `.cal-mini-d` (the 7-column mini-calendar day
   cell) measured 31×31 ×180 and is the single biggest sub-44 offender left.
   A pad cannot fix it — `min-width: 100%` clamps the region up to the cell,
   the cells are shoulder to shoulder with no gap, and anything wider steals
   the neighbouring day. It already clears the normative 24×24. The only real
   fix is a wider calendar sidebar, which is a layout decision, not a token. */
.tk-check::before,
.v-pri-check::before,
.cal-ev-chk::before,
.kd-exp::before,
.crm-kexp::before,
.cal-list-refresh::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: var(--tap-pad-sm); height: var(--tap-pad-sm);
  background: none; border: 0; pointer-events: inherit;
}

/* A FIELD cannot use the ::before trick — `<input>` is a replaced element and
   does not render one — so its own height is the only lever there is, and
   `min-height` is the correct tool here rather than the wrong one (tokens.css,
   `--ctl-h-fld`). These three are the inline "add one more" fields that sit
   inside a list row; they measured 19–26px tall, i.e. under the normative
   floor, while the row around them is comfortable. */
.tk-quickadd-input,
.crm-quickadd-input,
.v-pri-add-inp { min-height: var(--tap-pad-sm); }

/* ============================================================
   NUMBERS THAT DON'T JITTER  (P5-10 rule 1)
   ------------------------------------------------------------
   Inter's default figures are PROPORTIONAL: "1" is narrower than "8", so a
   count that ticks 8 → 9 → 10, or a KPI that refreshes on a poll, visibly
   shifts the text beside it. Every surface where a number is read, compared
   or watched gets lining tabular figures — the digits become one width and
   the row stops twitching. Applied by ROLE, not sprinkled per view, so a new
   card or table gets it for free.

   Not applied to prose: tabular figures in a sentence look mechanical, and
   nobody compares the "3" in "3 people replied" to anything above it.
   ============================================================ */
.kpi .kpi-value,
.kpi-sub,
.ring span,
.bar + .muted,
.tag, .chip, .badge,
.drawer-tab-badge,
.avatar,
table th, table td,
time {
  font-variant-numeric: tabular-nums lining-nums;
}
/* Anything the roles above miss (a bespoke stat block, a duration, a version)
   opts in with `.tnum` — which already exists in base.css, so it is NOT
   redeclared here. */
