/* ============ Component primitives ============
 * Buttons, cards, badges, and small utility classes shared across
 * redesigned pages. Ported from the design handoff (styles.css).
 *
 * Loaded globally by base_v2.html so any page extending the new shell
 * can use these primitives without re-declaring them.
 */

/* ---------- Buttons ----------
 * `cursor: pointer` and `text-decoration: none` are set here (rather than
 * relying on the global `button`/`a` resets in tokens.css) so that
 * `<a class="btn">` without an href, and any future non-button element
 * styled as `.btn`, render correctly. */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  height: 30px;
  /* `padding-top: 1px` nudges the line-box down ~0.5px to compensate for the
   * system font's asymmetric ascent/descent — without it, text-only buttons
   * (e.g. modal "Send"/"Cancel") sit visibly above the geometric center.
   * .btn-icon overrides this with `padding: 0`, so icon-only buttons stay
   * unaffected. */
  padding: 1px var(--s-3) 0;
  border-radius: var(--r-2);
  font-size: var(--fs-13);
  font-weight: 500;
  border: 1px solid transparent;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  transition: background 80ms, border-color 80ms, color 80ms;
}
.btn-primary { background: var(--c-fg); color: var(--c-bg); }
.btn-primary:hover { background: oklch(0.32 0.008 80); }
.btn-accent { background: var(--c-accent); color: var(--c-accent-fg); }
.btn-accent:hover { background: var(--c-accent-hover); }
/* Lock the background on disabled accent buttons so the .btn { transition }
 * doesn't animate them between accent and accent-hover when hovered (the
 * worksheet Save button sits in this state on page load and during submit). */
.btn-accent:disabled, .btn-accent[disabled] { background: var(--c-accent); }
.btn-default {
  background: var(--c-panel);
  border-color: var(--c-border);
  color: var(--c-fg);
  box-shadow: var(--sh-1);
}
.btn-default:hover { border-color: var(--c-border-strong); background: var(--c-bg-soft); }
.btn-ghost { background: transparent; color: var(--c-fg-muted); }
.btn-ghost:hover { background: var(--c-bg-sunk); color: var(--c-fg); }
.btn-danger { background: var(--c-panel); border-color: var(--c-border); color: var(--c-danger); box-shadow: var(--sh-1); }
.btn-danger:hover { background: var(--c-danger-soft); }
.btn-success { background: var(--c-success); color: #fff; border-color: transparent; }
.btn-success:hover { background: oklch(0.50 0.14 150); }
.btn-sm { height: 26px; padding: 0 var(--s-2); font-size: var(--fs-12); }
.btn-lg { height: 36px; padding: 0 var(--s-4); font-size: var(--fs-14); }
.btn-icon { width: 30px; padding: 0; }
.btn-icon.btn-sm { width: 26px; }
.btn:disabled, .btn[disabled] { opacity: 0.4; cursor: not-allowed; }

/* ---------- Cards ---------- */
.card {
  background: var(--c-panel);
  border: 1px solid var(--c-border);
  border-radius: var(--r-4);
  box-shadow: var(--sh-1);
}
.card-head {
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--c-border);
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
}
.card-title {
  font-size: var(--fs-14);
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.005em;
}
.card-sub {
  font-size: var(--fs-12);
  color: var(--c-fg-muted);
  margin-top: 1px;
}
.card-body { padding: var(--s-5); }
.card-list { padding: 0; }
.card-list .row {
  display: grid;
  align-items: center;
  padding: var(--s-3) var(--s-5);
  border-bottom: 1px solid var(--c-border);
  font-size: var(--fs-13);
}
.card-list .row:last-child { border-bottom: 0; }
.card-list .row:hover { background: var(--c-bg-soft); }
.card-empty {
  padding: var(--s-6) var(--s-5);
  font-size: var(--fs-13);
  color: var(--c-fg-muted);
  text-align: center;
}

/* ---------- Badges ---------- */
.badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 7px;
  font-size: 11px;
  font-weight: 500;
  border-radius: 999px;
  border: 1px solid var(--c-border);
  background: var(--c-bg-soft);
  color: var(--c-fg-muted);
  line-height: 16px;
  white-space: nowrap;
}
.badge .dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }
.badge.ok { color: oklch(0.42 0.13 150); background: var(--c-success-soft); border-color: oklch(0.85 0.06 150); }
.badge.warn { color: oklch(0.45 0.13 75); background: var(--c-warning-soft); border-color: oklch(0.85 0.08 75); }
.badge.bad { color: oklch(0.45 0.16 25); background: var(--c-danger-soft); border-color: oklch(0.85 0.08 25); }
.badge.info { color: oklch(0.42 0.1 240); background: var(--c-info-soft); border-color: oklch(0.85 0.05 240); }
.badge.accent { color: var(--c-accent); background: var(--c-accent-soft); border-color: oklch(0.85 0.06 255); }

/* ---------- Usage meters ----------
 * Promoted from account/static/account/myAccount.css (LEN-108) because four
 * surfaces now ride this one component: the PDF-import card, the three
 * saved-content meters, the AI assistance card, and the Sage widget footer.
 * Two copies of the warn/bad ladder is exactly what LEN/#183 came to delete.
 * Sits under Badges on purpose — the fill modifiers map 1:1 onto the badge
 * variants above, so the state ladder reads top to bottom:
 *   .calm  → neutral   — nothing is running out (opt-in; AI card only for now)
 *   (none) → accent    — normal usage
 *   .warn  → orange    — Approaching limit (.badge.warn)
 *   .bad   → red       — Limit reached     (.badge.bad)
 * Every colour is literal hex FIRST then literal oklch() second, never
 * var(--c-*): a var() resolving to oklch is parse-time valid, wins the
 * cascade, then fails at computed-value time and DISCARDS the hex before it,
 * leaving the fill transparent on the §4.13 floor browser. */
.usage-meter {
  height: 8px;
  border-radius: 999px;
  background: var(--c-bg-soft);
  border: 1px solid var(--c-border);
  overflow: hidden;
}
.usage-meter-fill {
  height: 100%;
  width: 0%;                           /* no inline width ⇒ empty, not full */
  background: #2971c6;                 /* floor: --c-accent */
  background: oklch(0.55 0.15 255);
  border-radius: 999px;
  transition: width .3s ease;
}
.usage-meter-fill.calm {
  background: #d6d1c8;                 /* floor: --c-border-strong */
  background: oklch(0.85 0.006 80);
}
.usage-meter-fill.warn {
  background: #d18f00;                 /* floor: --c-warning */
  background: oklch(0.68 0.15 75);
}
.usage-meter-fill.bad {
  background: #c43a2b;                 /* floor: --c-danger */
  background: oklch(0.55 0.18 25);
}
.usage-line { font-size: var(--fs-13); color: var(--c-fg); }

/* Ambient variant: a borderless hairline that sits beside an AI affordance.
 * Retires the .lt-assist-meter-track / -fill copy that used to live in
 * assistant.css. */
.usage-meter.is-slim { height: 4px; border: 0; }

/* Ambient row: slim track + caption on one line, under an AI affordance.
 * JS toggles these with el.hidden, so each carries the [hidden] companion
 * §4.13 bite #3 requires — a class rule with display: outranks the UA's
 * [hidden] rule, and the element would render anyway, permanently. */
.usage-meter-row {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--c-fg-faint);
  font-variant-numeric: tabular-nums;
}
.usage-meter-row[hidden] { display: none; }
.usage-meter-row .usage-meter { flex: 1 1 auto; min-width: 40px; }
.usage-meter-caption { flex: 0 0 auto; white-space: nowrap; }
.usage-meter-caption[hidden] { display: none; }

/* The in-place gate: an affordance with no headroom keeps its position, goes
 * disabled, and states the reason where it stands. No modal, no toast. */
.ai-gate {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: 6px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--c-fg-muted);
}
.ai-gate[hidden] { display: none; }
.ai-gate-ico {
  flex: none;
  width: 13px; height: 13px; margin-top: 2px;
  color: #c43a2b;                      /* floor: --c-danger */
  color: oklch(0.55 0.18 25);
}

/* An AI button with no headroom. NOT .btn:disabled's opacity:.4 ghost — the
 * button keeps its size, shape and place and drops its gradient to a neutral,
 * so the row doesn't develop a hole and the label stays readable. The only
 * !important here, and it earns it: it has to beat .send-draft-btn and
 * .ir-write-btn, which both set background twice for the §4.13 floor and would
 * otherwise win on source order from a later stylesheet. */
/* Literal hex first, literal oklch second — the same §4.13 bite #2 rule as the
 * fills above, and this rule broke it. `background: var(--c-bg-sunk)` resolves
 * to oklch, which is parse-time VALID (so it wins the cascade) and then fails at
 * computed-value time on the floor browser — discarding nothing, because there
 * was no hex line before it. Meanwhile `background-image: none` is a plain
 * keyword and applies, so the gradient really did go and nothing replaced it:
 * the spent button rendered transparent with default-coloured text, which reads
 * as broken rather than as "not right now". */
.ai-btn-spent {
  background: #f2efe9 !important;      /* floor: --c-bg-sunk */
  background: oklch(0.96 0.005 80) !important;
  background-image: none !important;
  color: #918d84 !important;           /* floor: --c-fg-faint */
  color: oklch(0.62 0.008 80) !important;
  box-shadow: none !important;
  opacity: 1 !important;
  cursor: not-allowed !important;
}

/* Where the month went: a ledger, not more bars. One bar per card. */
.usage-split { display: flex; flex-direction: column; gap: 2px; }
.usage-split[hidden] { display: none; }
.usage-split-head { margin-bottom: 3px; }
.usage-split-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  font-size: var(--fs-12);
  color: var(--c-fg-muted);
}
.usage-split-row[hidden] { display: none; }
.usage-split-val { color: var(--c-fg); font-variant-numeric: tabular-nums; }

/* Inline plan CTA inside a usage card — the quiet sibling of .upgrade-cta
 * (myAccount.css), for when the remedy belongs to ONE meter. */
.usage-cta {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 5px;
  font-size: var(--fs-12);
  font-weight: 500;
  color: #2971c6;                      /* floor: --c-accent */
  color: oklch(0.55 0.15 255);
  text-decoration: none;
}
.usage-cta[hidden] { display: none; }
.usage-cta svg { width: 13px; height: 13px; transition: transform 120ms ease; }
.usage-cta:hover svg, .usage-cta:focus-visible svg { transform: translateX(2px); }
@media (prefers-reduced-motion: reduce) { .usage-cta svg { transition: none; } }

/* ---------- Utilities ---------- */
.kicker {
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--c-fg-faint);
}
.muted { color: var(--c-fg-muted); }
.faint { color: var(--c-fg-faint); }
.row-flex { display: flex; align-items: center; gap: var(--s-3); }
.col { display: flex; flex-direction: column; }
.gap-1 { gap: var(--s-1); }
.gap-2 { gap: var(--s-2); }
.gap-3 { gap: var(--s-3); }
.gap-4 { gap: var(--s-4); }
.gap-5 { gap: var(--s-5); }
.gap-6 { gap: var(--s-6); }
.grow { flex: 1; }
.hidden { display: none !important; }
.divider { height: 1px; background: var(--c-border); margin: var(--s-5) 0; }

/* ---------- Tables ---------- */
.tbl { width: 100%; border-collapse: separate; border-spacing: 0; }
.tbl th {
  text-align: left;
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-fg-faint);
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--c-border);
  background: var(--c-bg-soft);
  position: sticky; top: 0;
}
.tbl td {
  padding: 11px var(--s-4);
  border-bottom: 1px solid var(--c-border);
  font-size: var(--fs-13);
  vertical-align: middle;
}
.tbl tr:last-child td { border-bottom: 0; }
.tbl tbody tr:hover { background: var(--c-bg-soft); }
.tbl tbody tr.clickable { cursor: pointer; }

/* ---------- Form fields ----------
 * Base form-field primitives shared by Settings (Profile/Company) and the
 * Clients toolbar search. Page-specific extensions (e.g. the Clients toolbar
 * sizing `.card-head-toolbar .input-wrap`, the `.input.has-prefix` modifier,
 * the prefix-icon child) live in the page CSS files. */
.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.label {
  font-size: var(--fs-12);
  font-weight: 500;
  color: var(--c-fg-muted);
}
.help { font-size: 11px; color: var(--c-fg-faint); }
.input-wrap { position: relative; display: flex; align-items: stretch; }
.input {
  flex: 1;
  height: 30px;
  width: 100%;
  padding: 0 var(--s-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-2);
  background: var(--c-panel);
  font-size: var(--fs-13);
  color: var(--c-fg);
  transition: border-color 80ms, box-shadow 80ms;
}
.input:hover { border-color: var(--c-border-strong); }
.input:focus { outline: none; border-color: var(--c-accent); box-shadow: 0 0 0 3px var(--c-accent-soft); }
.input:disabled { background: var(--c-bg-sunk); color: var(--c-fg-faint); }
.field-error { font-size: var(--fs-12); color: var(--c-danger); }
.field-error a { color: inherit; text-decoration: underline; }

/* ---------- Dropzone ----------
 * Shared file-upload primitive used by Settings, About Info, and PDF Import.
 * Expected markup: <label class="dropzone"><div class="ico-big">...</div>
 * <div class="dropzone-copy"><div class="dropzone-headline">…</div>
 * <div class="dropzone-sub">…</div></div><input type="file"></label>
 *
 * The native file input sits inside the label so clicking anywhere on the
 * dropzone triggers it. It's visually hidden but keyboard-accessible — the
 * `:has(...:focus-visible)` rule surfaces focus state on the dropzone when
 * tabbing through. Active drag state via `.over` (added/removed in JS). */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-12) var(--s-6);
  text-align: center;
  border: 1.5px dashed var(--c-border-strong);
  border-radius: var(--r-4);
  background: var(--c-panel);
  cursor: pointer;
  transition: border-color 80ms, background 80ms;
}
.dropzone:hover,
.dropzone.over {
  border-color: var(--c-accent);
  background: var(--c-accent-soft);
}
.dropzone .ico-big {
  width: 44px;
  height: 44px;
  border-radius: 999px;
  background: var(--c-bg-sunk);
  display: grid;
  place-items: center;
  color: var(--c-fg-muted);
}
.dropzone-copy { display: flex; flex-direction: column; gap: 4px; }
.dropzone-headline {
  font-weight: 600;
  font-size: var(--fs-15);
  color: var(--c-fg);
  word-break: break-word;
}
.dropzone-sub { font-size: var(--fs-13); color: var(--c-fg-muted); }
.dropzone input[type="file"] {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.dropzone:has(input[type="file"]:focus-visible) {
  border-color: var(--c-accent);
  box-shadow: 0 0 0 3px var(--c-accent-soft);
}
@media (max-width: 720px) {
  .dropzone { padding: var(--s-8) var(--s-4); }
}

/* ---------- Modal (v2 pattern) ----------
 * Use these class names (`.modal-overlay`/`.modal-card`/...) rather than
 * the legacy `.modal`/`.modal-content`/`.modalheader` rules in shell.css —
 * those still apply to legacy templates and would override these if both
 * sets of class names were used together on the same element.
 *
 * Write top/right/bottom/left longhands, never the `inset:` shorthand:
 * some LOs run browsers that predate it (Safari <14.1, Chrome/Edge <87),
 * which drop the declaration and leave the overlay collapsed at the
 * top-left of the viewport instead of covering it. */
.modal-overlay {
  display: none;
  position: fixed; top: 0; right: 0; bottom: 0; left: 0;
  background: rgba(20, 18, 15, 0.36);
  z-index: 90;
  padding: var(--s-6);
  overflow: auto;
}
.modal-overlay.is-open {
  display: grid;
  place-items: center;
}
.modal-card {
  width: 480px; max-width: 100%;
  background: var(--c-panel);
  border-radius: var(--r-5);
  box-shadow: var(--sh-3);
  overflow: hidden;
  position: relative;
}
.modal-head { padding: var(--s-5); border-bottom: 1px solid var(--c-border); }
.modal-head h3 { margin: 0; font-size: 16px; font-weight: 600; }
.modal-head .modal-sub { font-size: 12px; color: var(--c-fg-muted); margin-top: 2px; }
.modal-body { padding: var(--s-5); }
.modal-foot {
  padding: var(--s-4) var(--s-5);
  border-top: 1px solid var(--c-border);
  display: flex; justify-content: flex-end; gap: var(--s-2);
  background: var(--c-bg-soft);
}
.modal-close {
  position: absolute;
  top: 12px; right: 12px;
  width: 28px; height: 28px;
  border-radius: var(--r-2);
  display: grid; place-items: center;
  color: var(--c-fg-muted);
  background: transparent;
  border: 0;
  font-size: 18px; line-height: 1;
  cursor: pointer;
}
.modal-close:hover { background: var(--c-bg-sunk); color: var(--c-fg); }

/* ---------- Charts ----------
 * Ported from the design handoff (styles.css lines 778-796). Wrap
 * Chart.js canvases (or any embedded chart) in `.chart-frame` so they
 * sit in a design-system card. */
.chart-frame {
  border: 1px solid var(--c-border);
  border-radius: var(--r-3);
  background: var(--c-panel);
  padding: var(--s-4);
}

/* ---------- List/toolbar primitives ----------
 * Shared atoms for the in-card list pages (Clients, global Worksheets,
 * global Reports). Each of those pages renders the same toolbar + table
 * + empty-state pattern: search input, segmented type filter, sort
 * dropdown, scrollable table, and an empty/no-match state. */

/* Card toolbar — extends global .card */
.card-head-toolbar {
  padding: 12px var(--s-4);
  border-bottom: 1px solid var(--c-border);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
}

/* Table wrapper + column helpers */
.tbl-wrap { overflow-x: auto; }
.tbl .num { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
.tbl .col-actions { width: 1px; white-space: nowrap; }

/* Row name cell — avatar + name */
.client-cell { display: flex; align-items: center; gap: 10px; }
.client-cell .avatar {
  width: 24px; height: 24px;
  border-radius: 999px;
  background: var(--c-bg-sunk);
  color: var(--c-fg);
  display: grid; place-items: center;
  font-size: 11px; font-weight: 600;
  letter-spacing: -0.02em;
  flex-shrink: 0;
}
.client-cell .name { font-weight: 500; }
.muted-small { color: var(--c-fg-muted); font-size: var(--fs-12); white-space: nowrap; }

/* Toolbar search input — base .input/.input-wrap live above; this sizes
   the wrap inside the toolbar and adds the prefix-icon child. */
.card-head-toolbar .input-wrap { flex: 1; max-width: 360px; }
.input-wrap .unit-prefix {
  position: absolute;
  left: 9px;
  top: 0; bottom: 0;
  display: flex; align-items: center;
  color: var(--c-fg-faint);
  pointer-events: none;
}
.input.has-prefix { padding-left: 26px; }

/* Segmented control — type filter */
.seg {
  display: inline-flex;
  background: var(--c-bg-sunk);
  border: 1px solid var(--c-border);
  border-radius: var(--r-2);
  padding: 2px;
  gap: 1px;
  flex-shrink: 0;
}
.seg button {
  height: 24px;
  padding: 0 10px;
  font-size: var(--fs-12);
  font-weight: 500;
  border-radius: 4px;
  color: var(--c-fg-muted);
}
.seg button:hover { color: var(--c-fg); }
.seg button.active {
  background: var(--c-panel);
  color: var(--c-fg);
  box-shadow: var(--sh-1);
}

/* Sort dropdown */
.sort-wrap { display: inline-flex; align-items: center; gap: var(--s-2); }
.select-wrap { position: relative; }
.select {
  height: 30px;
  padding: 0 26px 0 var(--s-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-2);
  background: var(--c-panel);
  font-size: var(--fs-13);
  color: var(--c-fg);
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 10px;
}
.select:hover { border-color: var(--c-border-strong); }
.select:focus { outline: none; border-color: var(--c-accent); box-shadow: 0 0 0 3px var(--c-accent-soft); }

/* Ghost text for an optional <select> sitting on its blank placeholder option
   (the client "State" picker). Purely decorative — a browser without :has()
   just paints the placeholder in the normal text color, which is what it did
   before this rule existed. */
select.form-control:has(option[value=""]:checked) { color: var(--c-fg-faint); }

/* Empty states */
.empty {
  padding: var(--s-12) var(--s-5);
  text-align: center;
  color: var(--c-fg-muted);
}
.empty p { margin: 0 0 var(--s-3); }
.empty-no-match {
  padding: var(--s-8) var(--s-5);
  text-align: center;
  color: var(--c-fg-muted);
  font-size: var(--fs-13);
}

@media (max-width: 920px) {
  .card-head-toolbar { gap: var(--s-3); }
  /* .input-wrap and .seg are nested inside .row-flex, so ordering them
     against .sort-wrap (a sibling of .row-flex) never took effect. Instead
     let .row-flex wrap: the search field spans the full width and the
     segmented type filter drops to its own line rather than colliding with
     the Sort control. */
  .card-head-toolbar .row-flex { flex-wrap: wrap; }
  .card-head-toolbar .input-wrap { max-width: 100%; flex: 1 1 100%; }
  .tbl th, .tbl td { padding: 10px var(--s-3); }
}

/* Phone: the search input, segmented type filter, and sort dropdown can't
   share a horizontal band without overlapping (LEN-134 — "Refinance" text
   ran into "Sort"). Stack the toolbar so each control gets a full-width row;
   the segmented buttons split the row evenly and the sort select fills it. */
@media (max-width: 600px) {
  .card-head-toolbar { flex-direction: column; align-items: stretch; }
  .card-head-toolbar .seg { flex: 1 1 auto; }
  .card-head-toolbar .seg button { flex: 1; min-width: 0; }
  .card-head-toolbar .sort-wrap { width: 100%; }
  .card-head-toolbar .sort-wrap .select-wrap { flex: 1; }
  .card-head-toolbar .sort-wrap .select { width: 100%; }
}
