/* ===========================================================================
   CRUXMS Product Design System — component vocabulary (plain CSS / EJS target)
   Mirrors how the real app is actually built (546 EJS views, plain CSS —
   the Tailwind/React variant used by Event Manager/Contractor Portal can
   consume the same --tokens and follow the same class shapes if needed).

   Every family below states what EXISTS today (main.css/design-system.css/
   theme.css/ib-tokens.css per the 2026-09-18 audit) and what CHANGES. Where a
   family doesn't exist yet (modal, drawer, toast, empty-state, alert), this
   IS the first canonical definition — stop hand-building it per screen.
   =========================================================================== */

/* ===================== HERO / PAGE HEADER =====================
   TODAY: .hero-strip (main.css) is solid — keep the shape and the dark→blue
   gradient signature (ui-quality-bar explicitly protects this one). CHANGE:
   the gradient's end colour becomes the active MODULE accent instead of
   always blue, via --sc set once per page (see "Module accent wiring" below)
   — this is what makes SM vs TM vs IB visually distinguishable at a glance,
   which today's identical #BE123C accent does not do. */
.hero-strip {
  display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-4); padding: var(--sp-6) var(--sp-8); color: #fff;
  background-image: linear-gradient(135deg, var(--ink) 0%, color-mix(in srgb, var(--ink) 40%, var(--sc, var(--primary))) 55%, var(--sc, var(--primary)) 100%);
  border-top-left-radius: var(--r-md); border-top-right-radius: var(--r-md);
}
.hero-strip.hero-standalone { border-radius: var(--r-md); margin-bottom: var(--sp-6); }
.hero-breadcrumb { font-size: .74rem; opacity: .75; margin-bottom: 4px; }
.hero-breadcrumb a { color: rgba(255,255,255,.85); text-decoration: none; }
.hero-breadcrumb a:hover { text-decoration: underline; }
.hero-name { font-family: var(--font-head); font-weight: 700; font-size: 1.4rem; letter-spacing: -0.01em; display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.hero-meta { display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-1); font-size: .82rem; color: rgba(255,255,255,.82); margin-top: var(--sp-2); }
.hero-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.hero-tile { display: inline-flex; align-items: center; gap: 5px; font-size: .74rem; color: #fff; background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.18); border-radius: var(--r-full); padding: 6px 12px; }
.hero-tile strong { font-size: .88rem; font-weight: 700; }

/* ===================== TABS =====================
   TODAY: .page-tabs/.page-tab exist and work well; ZERO dark-mode coverage.
   CHANGE: active tab underline uses --sc (module accent) not a hardcoded blue. */
.page-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); background: var(--surface); padding: 0 var(--sp-4); }
.page-tab {
  font-family: var(--font-head); font-weight: 600; font-size: .84rem; color: var(--text-muted);
  background: none; border: none; border-bottom: 2px solid transparent; padding: 11px 6px; margin-right: var(--sp-5);
  cursor: pointer; transition: color var(--motion-base), border-color var(--motion-base);
}
.page-tab:hover { color: var(--ink); }
.page-tab.active { color: var(--ink); border-bottom-color: var(--sc, var(--primary)); }
.tab-count { display: inline-flex; align-items: center; justify-content: center; font-size: .68rem; font-weight: 700; background: var(--surface-sunk); color: var(--text-muted); border-radius: var(--r-full); padding: 1px 7px; margin-left: 4px; }
.page-tab.active .tab-count { background: var(--sc-pale, var(--primary-pale)); color: var(--sc, var(--primary)); }
.tab-pane { display: none; }
.tab-pane.active { display: block; }

/* ===================== CARDS =====================
   TODAY: .card exists but reads via a 3px coloured top-border on a flat
   1px-bordered box — the audit's own "bordered-box grid" tell. CHANGE: lead
   with shadow/elevation; the top-border becomes optional emphasis, not the
   primary way a card reads as "contained." */
.card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: var(--shadow-1); transition: box-shadow var(--motion-base), transform var(--motion-base);
}
.card-pad { padding: var(--sp-5); }
.card-interactive { cursor: pointer; }
.card-interactive:hover { box-shadow: var(--shadow-2); transform: translateY(-2px); }
.card-emphasis { box-shadow: var(--shadow-2); border-top: 3px solid var(--sc, var(--primary)); }
.card-urgent { border-top: 3px solid var(--danger); }
.card-header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5); border-bottom: 1px solid var(--border); background: var(--surface-sunk);
  border-top-left-radius: var(--r-md); border-top-right-radius: var(--r-md);
}

/* ===================== BUTTONS =====================
   TODAY: .btn family is the best-built part of the current system. CHANGE:
   press-state (translateY on :active) added — today's hover is colour-only,
   which ui-quality-bar names as the single biggest "flat/dated" tell. */
.btn {
  font-family: var(--font-body); font-weight: 600; font-size: .86rem; border-radius: var(--r-sm);
  padding: 9px 16px; border: 1px solid transparent; cursor: pointer; display: inline-flex;
  align-items: center; gap: 7px; transition: box-shadow var(--motion-base), transform var(--motion-fast), background var(--motion-fast);
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.btn-primary { background: var(--primary); color: #fff; box-shadow: 0 1px 2px color-mix(in srgb, var(--primary) 30%, transparent), inset 0 1px 0 rgba(255,255,255,.12); }
.btn-primary:hover { background: var(--primary-d); box-shadow: 0 4px 10px color-mix(in srgb, var(--primary) 28%, transparent); }
.btn-secondary { background: var(--surface); color: var(--text); border-color: var(--border-2); box-shadow: var(--shadow-1); }
.btn-secondary:hover { box-shadow: var(--shadow-2); border-color: var(--border-2); }
.btn-ghost { background: transparent; color: var(--text-muted); }
.btn-ghost:hover { background: var(--surface-sunk); color: var(--text); }
.btn-danger { background: var(--danger); color: #fff; box-shadow: 0 1px 2px color-mix(in srgb, var(--danger) 30%, transparent); }
.btn-danger:hover { box-shadow: 0 4px 10px color-mix(in srgb, var(--danger) 28%, transparent); }
.btn-sm { font-size: .76rem; padding: 6px 12px; }
.btn:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }

/* ===================== BADGES / PILLS =====================
   TODAY: 21 badge classes exist and are the best dark-mode-covered family
   (19 of 21). CHANGE: consolidated to 5 semantic + neutral, since 21 ad hoc
   variants (badge-enrolled, badge-withdrawal, badge-rpl_complete, …) is the
   drift itself — compose meaning from the 5 states + label text instead. */
/* text-transform:none and letter-spacing:normal below are DELIBERATE and load-bearing.
   A host app may declare .badge { text-transform: capitalize } at lower specificity (Calibr
   main.css does). Our scoped rule wins only for properties it actually declares, so without
   these two the host value still applies: "Newer version at NZQA" renders as "Newer Version
   At NZQA" and "v3" renders as "V3". Do not delete them as redundant defaults. */
.badge { display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-body); font-size: .72rem; font-weight: 600; padding: 3px 10px; border-radius: var(--r-full); border: 1px solid transparent; line-height: 1.4; text-transform: none; letter-spacing: normal; }
.badge-success { background: var(--success-pale); color: var(--success); border-color: color-mix(in srgb, var(--success) 30%, white); }
.badge-warning { background: var(--warning-pale); color: var(--warning); border-color: color-mix(in srgb, var(--warning) 30%, white); }
.badge-danger  { background: var(--danger-pale);  color: var(--danger);  border-color: color-mix(in srgb, var(--danger) 30%, white); }
.badge-info    { background: var(--info-pale);    color: var(--info);    border-color: color-mix(in srgb, var(--info) 30%, white); }
.badge-neutral { background: var(--surface-sunk); color: var(--neutral-text); border-color: var(--border); }
.badge-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }

/* ===================== LIFECYCLE CHIPS =====================
   A SECOND pill vocabulary, and the distinction is the whole point:
     .badge  = a judgement  — is this good, risky, broken?   Sentence case.
     .chip   = a stage      — how far along the journey is it? UPPERCASE.
   Calibr's .status-pill carried 12 enrolment stages in the same visual language as
   its judgement states, so "Completed" and "Danger" read as the same kind of fact.
   Splitting them means a reader never has to work out which question a pill answers,
   and it stops the 5 semantic states being stretched to cover 12 meanings they
   cannot express (collapsing Enrolled / Completed / Waitlist into one green would
   destroy distinctions staff scan for daily).

   UPPERCASE + letter-spacing here is deliberate and load-bearing — it is the live
   .status-pill treatment, and it is what separates a chip from a badge at a glance.
   Unlike .badge, this vocabulary therefore does NOT reset text-transform (RULES §516). */
.chip { display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-body); font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: 2px 10px; border-radius: var(--r-full); border: 1px solid transparent; line-height: 1.5; white-space: nowrap; }
.chip-enquiry   { background: var(--stage-enquiry-bg);   color: var(--stage-enquiry); }
.chip-validated { background: var(--stage-validated-bg); color: var(--stage-validated); }
.chip-waitlist  { background: var(--stage-waitlist-bg);  color: var(--stage-waitlist); }
.chip-admission { background: var(--stage-admission-bg); color: var(--stage-admission); }
.chip-enrolled  { background: var(--stage-enrolled-bg);  color: var(--stage-enrolled); }
/* Active is the one LIVE state — it earns the outline so it reads as current, not just
   another completed step. Same ink as enrolled by design: active IS enrolled, in progress. */
.chip-active    { background: var(--stage-active-bg);    color: var(--stage-active);    border-color: var(--stage-active-line); }
.chip-completed { background: var(--stage-completed-bg); color: var(--stage-completed); }
.chip-inactive  { background: var(--stage-inactive-bg);  color: var(--stage-inactive); }
/* Dashed = nothing real behind it yet (never saved / never checked), as distinct from
   a record that genuinely sits at a dormant stage. */
.chip-draft     { background: var(--stage-draft-bg);     color: var(--stage-draft);     border-style: dashed; border-color: color-mix(in srgb, var(--stage-draft) 35%, white); }
.chip-unset     { background: var(--stage-unset-bg);     color: var(--stage-unset);     border-style: dashed; border-color: color-mix(in srgb, var(--stage-unset) 35%, white); }

/* ===================== STAT TILES =====================
   TODAY: Standards Management alone had three independently hand-rolled "row of summary
   numbers" patterns (a flat .stat-bar, an inline-styled tile grid, and a bespoke .sm-kpi
   card set) — plus ~20 more pages elsewhere in Calibr repeating the same ad hoc
   display:grid;grid-template-columns:repeat(auto-fit,minmax(...)) pattern inline, each with
   its own copy-pasted tile CSS. CHANGE: one real component — a bordered tile, big number,
   small uppercase label (the shape preferred over a flat bar). Optional semantic colour reuses
   .badge's 5-state vocabulary (success/warning/danger/info/neutral) so a tile's colour meaning
   never diverges from a badge's elsewhere on the same page. */
.stat-tile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--sp-3); }
.stat-tile {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: var(--sp-4); text-align: center;
}
a.stat-tile { text-decoration: none; color: inherit; display: block; transition: box-shadow var(--motion-base), transform var(--motion-base); }
a.stat-tile:hover { box-shadow: var(--shadow-2); transform: translateY(-2px); }
.stat-tile-value { font-family: var(--font-head); font-size: 1.6rem; font-weight: 800; line-height: 1.1; color: var(--ink); }
.stat-tile-label { font-family: var(--font-body); font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); margin-top: 4px; }
.stat-tile.is-success .stat-tile-value { color: var(--success); }
.stat-tile.is-warning .stat-tile-value { color: var(--warning); }
.stat-tile.is-danger  .stat-tile-value { color: var(--danger); }
.stat-tile.is-info    .stat-tile-value { color: var(--info); }
.stat-tile.is-neutral .stat-tile-value { color: var(--text-muted); }
.stat-tile.is-accent  .stat-tile-value { color: var(--sc, var(--primary)); }

/* ===================== FORMS =====================
   TODAY: .form-control/.form-input have good focus states. CHANGE: unified
   under one .field wrapper (label + input + hint + error), since today
   label/input/hint markup is inconsistent per-view. */
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-family: var(--font-mono); font-size: .68rem; font-weight: 500; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); }
.field input, .field select, .field textarea {
  font-family: var(--font-body); font-size: .88rem; padding: 9px 11px; border: 1px solid var(--border-2);
  border-radius: var(--r-sm); background: var(--surface); color: var(--text);
  transition: border-color var(--motion-base), box-shadow var(--motion-base);
}
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-pale); }
.field .hint { font-size: .74rem; color: var(--text-faint); }
.field.error input, .field.error select { border-color: var(--danger); }
.field.error .hint { color: var(--danger); }

/* ===================== TABLES =====================
   TODAY: .table exists, partial dark coverage. CHANGE: row hover uses
   --surface-sunk consistently (today several tables hand-roll their own
   hover grey); numeric columns get tabular-nums by default via .num. */
table.dtable { width: 100%; border-collapse: collapse; font-size: .86rem; }
table.dtable th { text-align: left; font-family: var(--font-mono); font-size: .68rem; font-weight: 500; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); padding: 9px 14px; border-bottom: 1px solid var(--border-2); }
table.dtable td { padding: 11px 14px; border-bottom: 1px solid var(--border); }
table.dtable tbody tr { transition: background var(--motion-fast); }
table.dtable tbody tr:hover { background: var(--surface-sunk); }
table.dtable td.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; }

/* ===================== EMPTY STATE ===================== (did not exist — new) */
.empty { text-align: center; padding: var(--sp-10) var(--sp-6); color: var(--text-muted); }
.empty svg { margin-bottom: var(--sp-3); opacity: .5; }
.empty h4 { font-family: var(--font-head); font-size: .95rem; color: var(--ink); margin-bottom: 6px; }
.empty p { font-size: .84rem; max-width: 36ch; margin: 0 auto var(--sp-4); }

/* ===================== ALERT ===================== (did not exist as a class — new) */
.alert { display: flex; gap: 10px; padding: 12px 14px; border-radius: var(--r-md); font-size: .84rem; border: 1px solid transparent; }
.alert-warning { background: var(--warning-pale); border-color: color-mix(in srgb, var(--warning) 30%, white); color: var(--warning); }
.alert-danger  { background: var(--danger-pale);  border-color: color-mix(in srgb, var(--danger) 30%, white);  color: var(--danger); }
.alert-info    { background: var(--info-pale);    border-color: color-mix(in srgb, var(--info) 30%, white);    color: var(--info); }
.alert-success { background: var(--success-pale); border-color: color-mix(in srgb, var(--success) 30%, white); color: var(--success); }

/* ===================== TOAST ===================== (did not exist — new) */
.toast { display: flex; align-items: center; gap: 10px; background: var(--ink); color: #fff; padding: 12px 16px; border-radius: var(--r-md); font-size: .84rem; box-shadow: var(--shadow-3); max-width: 340px; }
.toast.success { background: var(--success-deep); }
.toast .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--success-bright); flex-shrink: 0; }

/* ===================== MODAL / DRAWER ===================== (did not exist in the
   core — 5 different one-off implementations existed per audit; this is the
   ONE canonical shape now) */
.modal-scrim { position: fixed; inset: 0; background: rgba(14,27,38,.45); display: flex; align-items: center; justify-content: center; z-index: 100; }
.modal { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--shadow-3); max-width: 480px; width: 92%; padding: var(--sp-6); }
.modal-title { font-family: var(--font-head); font-weight: 700; font-size: 1.1rem; margin-bottom: var(--sp-2); }
.drawer { position: fixed; top: 0; right: 0; bottom: 0; width: 400px; max-width: 92vw; background: var(--surface); box-shadow: var(--shadow-3); padding: var(--sp-6); overflow-y: auto; z-index: 100; }

/* ===================== MODULE ACCENT WIRING =====================
   Every page sets --sc / --sc-pale ONCE (server-rendered, on the outer
   container — same idiom as the existing --sd-accent pattern in
   cockpit/strands-dashboard.ejs, generalised). Every component above reads
   --sc rather than hardcoding a colour, so a page is correctly accented
   just by setting these two variables:
     TM  --sc:var(--tm);   --sc-pale:var(--tm-pale);
     SM  --sc:var(--sm);   --sc-pale:var(--sm-pale);
     LX and LD share ONE accent (see tokens.css): --sc:var(--learning); --sc-pale:var(--learning-pale);
     ME  --sc:var(--me);   --sc-pale:var(--me-pale);
     IB  --sc:var(--ib);   --sc-pale:var(--ib-pale);
   See guidelines/module-accent-map.md for the reasoning and icon mapping. */

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* ===========================================================================
   ADDED v1.1 (post design review) — flat page header + fixed focus ring
   =========================================================================== */

/* ===================== FLAT PAGE HEADER =====================
   REPLACES the module-accent gradient hero as the default for routine
   detail/list pages. A dark→accent gradient on EVERY page (5 products × every
   screen) was flagged as a template tell AND the source of several contrast
   failures (white text at reduced opacity over an uncontrolled gradient).
   .hero-strip (the dark→blue gradient, main.css) stays — it's a real, rare,
   protected signature for top-level landing/dashboard moments — but it is
   not the default page header any more. IB was already closest to this
   shape; every module now shares the SAME header, so IB stops being a
   special case instead of the other four modules acquiring IB's problem. */
.page-header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  flex-wrap: wrap; padding-bottom: var(--sp-3); margin-bottom: var(--sp-4);
  border-bottom: 1px solid var(--border);
}
.page-header--flat { border-left: 4px solid var(--sc, var(--primary)); padding-left: var(--sp-4); }
.page-header .bc { font-size: .7rem; color: var(--text-faint); margin-bottom: 2px; }
.page-header h3 { font-family: var(--font-head); font-weight: 700; font-size: 1.05rem; color: var(--ink); }
.page-header .meta { font-size: .78rem; color: var(--text-muted); display: flex; gap: var(--sp-4); flex-wrap: wrap; margin-top: 2px; }
.page-header .icon-chip {
  width: 34px; height: 34px; border-radius: var(--r-sm); background: var(--sc-pale, var(--primary-pale));
  color: var(--sc, var(--primary)); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
}

/* ---- Density variant — set on a shell root (e.g. IB), not per-page ---- */
[data-density="compact"] { --row-pad: 7px; }
[data-density="compact"] .card-pad { padding: var(--sp-3) var(--sp-4); }
[data-density="compact"] table.dtable td { padding: var(--row-pad) 14px; }
[data-density="compact"] .page-header { padding-bottom: var(--sp-2); margin-bottom: var(--sp-3); }

/* ===================== FOCUS RING — fixed =====================
   TODAY (v1.0 mistake): 0 0 0 3px var(--primary-pale) measured 1.13:1 against
   white — effectively invisible as the ONLY visible focus indicator on an
   input. Use --focus-ring (a 45%-alpha primary) and keep a solid border. */
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--focus-ring);
}
/* Every other focusable element in this system needs an equally visible
   focus-visible state — not just .btn and .field. At minimum: */
.page-tab:focus-visible, .tabbtn:focus-visible, .card-interactive:focus-visible,
a:focus-visible, [role="tab"]:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px;
}

/* ===================== TABS — ARIA shape (markup contract, not just CSS) =====================
   The tab bar MUST be built with real semantics, not bare buttons:
     <div class="page-tabs" role="tablist">
       <button class="page-tab" role="tab" aria-selected="true" aria-controls="pane-x" id="tab-x">…</button>
     </div>
     <div class="tab-pane" role="tabpanel" id="pane-x" aria-labelledby="tab-x">…</div>
   `aria-selected` toggles with `.active`; only one tabpanel is un-hidden at a time.
   This was missing entirely in the first pass — treat it as part of the component,
   not an optional add-on, since it will be copied into all five products. */

/* ===================== FORM LABELS — markup contract =====================
   Every <label> needs a matching for="id" / input id="id" pair. The first pass
   shipped an example with an unassociated label — do not repeat that pattern. */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}
