/* The Hart Centre - core brand stylesheet.
 *
 * Project-agnostic extract of the GBP Manager stylesheet. Everything here is
 * app-neutral: tokens, base type, nav, buttons, tables, forms, alerts, badges,
 * pills/tabs, cards, stat tiles, chart chrome. Drop it into any internal THC
 * app and build on top; put app-specific components in a SECOND stylesheet,
 * not in here, so this file stays portable to the next project.
 *
 * Plain CSS custom properties. No build step, no Tailwind, no CSS-in-JS.
 * One stylesheet for the whole app - do not scatter inline styles.
 *
 * Contrast, computed (WCAG 2.1 relative luminance, not eyeballed):
 *   --brand-primary (#887AEC) vs white: 3.48:1 - large text, borders, focus
 *     rings, nav accents ONLY. Below AA for normal text.
 *   --brand-primary-dark (#6A59E7) vs white: 5.01:1, vs --brand-bg: 4.66:1.
 *     Same hue/saturation as --brand-primary, darkened until white text clears
 *     4.5:1 with real margin (a first pass at 4.507:1 was too thin once the
 *     tinted background's luminance was subtracted; re-derived with a 5.0:1
 *     floor on white). Use for standard-size buttons, links, and small text
 *     sitting on a purple fill.
 *   All --ok/--warn/--danger/--info/--neutral text/bg pairs: 6.6-11:1.
 *   --text-primary (#081040, sampled from the THC logo) vs surface: 18.13:1,
 *     vs bg: 16.86:1.
 *   --text-muted vs surface: 6.13:1, vs bg: 5.70:1.
 *
 * Assets this file expects alongside it (copy from the GBP Manager repo):
 *   fonts/figtree-variable.woff2   - self-hosted, no font CDN at runtime
 *   images/THC_Logo_Full.png       - nav logo
 *   images/favicon.ico, favicon-32.png, apple-touch-icon.png
 * Adjust the @font-face url() below to match your static layout.
 */

@font-face {
  /* Figtree ships as ONE variable file covering the whole weight axis -
     Google's own generated CSS does exactly this (several @font-face blocks,
     one physical file, a discrete font-weight per block) rather than four
     separate static files. */
  font-family: "Figtree";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/figtree-variable.202cfb54d4e6.woff2") format("woff2");
}
@font-face {
  font-family: "Figtree";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/figtree-variable.202cfb54d4e6.woff2") format("woff2");
}
@font-face {
  font-family: "Figtree";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/figtree-variable.202cfb54d4e6.woff2") format("woff2");
}
@font-face {
  font-family: "Figtree";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/figtree-variable.202cfb54d4e6.woff2") format("woff2");
}

:root {
  /* Brand */
  --brand-primary: #887aec;
  --brand-primary-dark: #6a59e7;
  --on-primary: #ffffff;

  /* Neutrals */
  --brand-bg: #f7f6fb;
  --brand-surface: #ffffff;
  --text-primary: #081040;
  --text-muted: #5b6178;
  --border: #e3e1ed;

  /* Semantic status - visually distinct from brand purple. These carry
     MEANING (state, severity, outcome); purple never does. */
  --ok: #1e4620;
  --ok-bg: #e6f4ea;
  --warn: #7a4a00;
  --warn-bg: #fff4e5;
  --danger: #611a15;
  --danger-bg: #fdecea;
  --info: #1e3a70;
  --info-bg: #e8edfb;
  --neutral: #4b5563;
  --neutral-bg: #eef0f2;

  /* Table/list chrome:
     --th-text: --text-muted darkened 20% (RGB x 0.8) - column headers needed
       more presence against striped rows.
     --border-strong: --border darkened 35% (RGB x 0.65) - the OUTER border of
       tables and panels only; internal row/cell lines stay on --border. */
  --th-text: #494e60;
  --border-strong: #94929a;

  /* Row striping tint. Applied at 0.35 alpha on cells (see the table section);
     composites to ~#fbfbfd on white - enough to track a row across a wide
     table without reading as a painted band. */
  --row-stripe: rgba(244, 243, 249, 0.35);
  --row-hover: #ebe9f4;

  /* Highlight ink (stars, flags, attention glyphs at body size). A darkened
     gold: pure yellow fails contrast on light surfaces. */
  --star: #b8860b;

  /* Chart series - DATA ink, not status. The "purple never carries meaning"
     rule applies to badges and state; here purple is exactly the
     brand-as-chrome role. Four steps around --brand-primary, ordered
     dark -> light so adjacent grouped bars stay distinguishable. */
  --chart-1: #6a59e7;
  --chart-2: #a89df1;
  --chart-3: #453798;
  --chart-4: #d3cdf8;
  /* Crosshair/guide rule - muted, so it guides the eye without competing with
     the series it is measuring. */
  --chart-crosshair: #5b6178;

  /* Trend scale - the one place a chart line carries STATUS rather than data:
     coloured by its own direction, red -> amber -> green. Purple stays the
     data colour, so the badge rule still holds; this is its mirror image,
     meaning is never rendered in purple.
     Contrast vs white (WCAG 1.4.11 wants 3:1 for graphics):
       --trend-up   #1f8a4c -> 3.94:1
       --trend-flat #b8860b -> 3.21:1  (true yellow lands near 1.2:1 and is
                                        unusable; this is the yellowest amber
                                        that still clears 3:1)
       --trend-down #c0392b -> 4.63:1
     Interpolated midpoints sit between these luminances, so every step of the
     scale clears 3:1. Colour is never the only signal - state the direction
     in words as well. */
  --trend-up: #1f8a4c;
  --trend-flat: #b8860b;
  --trend-down: #c0392b;

  /* Scale */
  --radius-pill: 9999px;
  --radius-md: 8px;
  --radius-sm: 6px;
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --font-sans: "Figtree", system-ui, sans-serif;
}

/* ---------- Base ---------- */

* {
  box-sizing: border-box;
}

body {
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.55;
  color: var(--text-primary);
  background: var(--brand-bg);
  margin: 0;
  padding: var(--space-6) var(--space-8);
}

/* ---------- Typography ---------- */

h1,
h2,
h3,
h4 {
  font-family: var(--font-sans);
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.25;
  margin: 0 0 var(--space-4);
}

h1 {
  font-size: 1.75rem;
  margin-top: 0;
}
h2 {
  font-size: 1.25rem;
  margin-top: var(--space-8);
}
h3 {
  font-size: 1.05rem;
  margin-top: var(--space-6);
}
h4 {
  font-size: 0.95rem;
  margin-top: var(--space-4);
}

p {
  margin: 0 0 var(--space-4);
}

small,
.meta {
  color: var(--text-muted);
  font-size: 0.85em;
}

a {
  color: var(--brand-primary-dark);
  text-decoration: none;
}
a:hover,
a:focus-visible {
  text-decoration: underline;
}

/* ---------- Focus ---------- */

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

/* ---------- Nav ---------- */

nav {
  display: flex;
  align-items: center;
  /* Wrap, not overflow: title plus many links exceed narrow viewports; links
     flow to a second row rather than pushing the page wide. */
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-bottom: var(--space-8);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--border);
}

/* Logo + app title, linking home. */
nav .brand-home {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-right: var(--space-2);
}

nav .app-title {
  font-size: 1.05rem;
  font-weight: 600;
  white-space: nowrap;
}

nav .brand-logo {
  height: 32px;
  width: auto;
  display: block;
}

/* Deployed version, far right of the nav - quiet reference text. */
nav .app-version {
  font-size: 0.75rem;
  color: var(--text-muted);
}

nav a {
  color: var(--text-primary);
  font-weight: 500;
  font-size: 0.95rem;
}

nav a:hover,
nav a:focus-visible {
  color: var(--brand-primary-dark);
  text-decoration: none;
}

/* ---------- Buttons ---------- */

button,
.button {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 0.9rem;
  border-radius: var(--radius-pill);
  padding: 12px 25px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: all 0.3s ease;
  background: var(--brand-primary-dark);
  color: var(--on-primary);
}

button:hover,
.button:hover {
  filter: brightness(1.08);
}

button:disabled,
.button:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  filter: none;
}

/* Secondary: outlined, for non-primary actions sitting beside a primary one. */
button.secondary,
.button.secondary {
  background: transparent;
  color: var(--brand-primary-dark);
  border-color: var(--border);
}
button.secondary:hover,
.button.secondary:hover {
  background: var(--brand-bg);
  filter: none;
}

/* Destructive: stays red-based, never purple - abort/cancel/remove. */
button.destructive,
.button.destructive {
  background: var(--danger-bg);
  color: var(--danger);
  border-color: var(--danger);
}
button.destructive:hover,
.button.destructive:hover {
  background: var(--danger);
  color: #ffffff;
  filter: none;
}

/* ---------- Layout utilities ---------- */

/* Inline action forms - a class, never style="display:inline". */
.inline-form {
  display: inline;
}

.action-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  margin: var(--space-4) 0;
}

/* Pushes this item (and everything after it) to the right edge of a flex row. */
.push-right {
  margin-left: auto;
}

/* Collapsible <details> sections: a uniform gap whether the section ends in a
   table (no bottom margin) or an empty-state paragraph (which carries one),
   and a uniform gap under the summary line whichever of the two follows. */
.collapse-section {
  margin: 0 0 var(--space-6);
}
.collapse-section > summary {
  margin-bottom: var(--space-3);
}
.collapse-section > p:last-child {
  margin-bottom: 0;
}

/* ---------- Tables ---------- */

table {
  /* Separated borders, NOT collapse: collapse makes browsers ignore
     border-radius, which washes the outer border out against the rounded
     corners. Zero spacing renders identically because cells only carry
     bottom borders. */
  border-collapse: separate;
  border-spacing: 0;
  width: 100%;
  background: var(--brand-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  overflow: hidden;
  font-feature-settings:
    "lnum" 1,
    "pnum" 1;
}

th,
td {
  text-align: left;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
}

thead th {
  font-weight: 600;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--th-text);
  background: var(--brand-bg);
}

/* Zebra striping, app-wide. Painted on the CELLS, not the row: a tr
   background ignores the td corner radii, so a striped (or hovered) last row
   would poke square shoulders past the rounded corners. */
tbody tr:nth-child(even) td {
  background: var(--row-stripe);
}

/* Hover must beat BOTH stripe shades - a --brand-bg hover is near
   indistinguishable from the stripe tint. */
tbody tr:hover td {
  background: var(--row-hover);
}

tbody tr:last-child td {
  border-bottom: none;
}

/* Opt-in sticky header row, for tables long enough that the column meanings
   scroll out of reach. `position: sticky` is broken by ANY ancestor with a
   non-visible overflow, and the base `table` rule sets `overflow: hidden` to
   clip its own rounded corners - so this variant gives that up and re-rounds
   all four corner cells by hand. */
.table-sticky {
  overflow: visible;
}

.table-sticky thead th {
  position: sticky;
  top: 0;
  /* Above the rows scrolling beneath. The header background is already opaque
     (--brand-bg), which is what stops rows showing through. */
  z-index: 2;
}

.table-sticky thead th:first-child {
  border-top-left-radius: var(--radius-md);
}
.table-sticky thead th:last-child {
  border-top-right-radius: var(--radius-md);
}
.table-sticky tbody tr:last-child td:first-child {
  border-bottom-left-radius: var(--radius-md);
}
.table-sticky tbody tr:last-child td:last-child {
  border-bottom-right-radius: var(--radius-md);
}

/* Stacked tables that share leading columns: fixed fractional widths make
   those columns line up from one table to the next even when the trailing
   columns differ. Fixed layout means long values wrap instead of widening.
   Declare the per-column widths in your app stylesheet, e.g.
   .table-aligned th.col-name { width: 19%; } */
.table-aligned {
  table-layout: fixed;
}
.table-aligned td {
  overflow-wrap: break-word;
}

/* ---------- Forms ---------- */

label {
  display: inline-block;
  font-weight: 500;
  font-size: 0.9rem;
  color: var(--text-primary);
  margin-bottom: var(--space-1);
}

input[type="text"],
input[type="email"],
input[type="number"],
input[type="search"],
input[type="url"],
input[type="password"],
input[type="datetime-local"],
input[type="date"],
input[type="time"],
input[type="file"],
select,
textarea {
  font-family: var(--font-sans);
  font-size: 0.9rem;
  color: var(--text-primary);
  background: var(--brand-surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}

textarea {
  width: 100%;
  max-width: 40rem;
}

input[type="checkbox"] {
  accent-color: var(--brand-primary-dark);
  width: 16px;
  height: 16px;
}

/* ---------- Messages / alerts ---------- */

.messages {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.messages li {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  border: 1px solid transparent;
}

.messages .success {
  background: var(--ok-bg);
  color: var(--ok);
  border-color: var(--ok);
}
.messages .warning {
  background: var(--warn-bg);
  color: var(--warn);
  border-color: var(--warn);
}
.messages .info {
  background: var(--info-bg);
  color: var(--info);
  border-color: var(--info);
}
.messages .error {
  background: var(--danger-bg);
  color: var(--danger);
  border-color: var(--danger);
}

/* Standalone warning callout. */
.warning {
  background: var(--warn-bg);
  color: var(--warn);
  border: 1px solid var(--warn);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
}

/* Validation errors rendered inline on a form. */
.errors {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-4);
}
.errors li,
.errors li.error {
  background: var(--danger-bg);
  color: var(--danger);
  border: 1px solid var(--danger);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-2);
  font-size: 0.9rem;
}

/* ---------- Badges (status pills) ---------- */
/* State, severity, outcome, category - never purple, since purple is chrome,
   not status. Pair a badge with its word; colour is never the only signal. */

.badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 0.78rem;
  line-height: 1;
  padding: 5px 11px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  font-feature-settings:
    "lnum" 1,
    "pnum" 1;
}

.badge-ok {
  background: var(--ok-bg);
  color: var(--ok);
}
.badge-warn {
  background: var(--warn-bg);
  color: var(--warn);
}
.badge-danger {
  background: var(--danger-bg);
  color: var(--danger);
}
.badge-info {
  background: var(--info-bg);
  color: var(--info);
}
.badge-neutral {
  background: var(--neutral-bg);
  color: var(--neutral);
}

/* ---------- Tabs, toggles and filter pills ---------- */
/* Selection chrome reads as brand purple, not status - one visual language
   for every tab bar, segmented toggle and filter preset in the app. */

.pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* An author-level `display` beats the UA stylesheet's [hidden] rule, so a
   hidden pill group needs this explicitly - without it a group renders even
   though the attribute is set. */
.pill-row[hidden] {
  display: none;
}

/* Two levels of control (a toggle plus its presets, say) sit in one row,
   separated by a wider gap so they do not read as one long strip of pills. */
.control-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-bottom: var(--space-3);
}

.pill {
  background: var(--brand-surface);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  font-family: var(--font-sans);
  font-size: 0.9rem;
  cursor: pointer;
}

.pill[aria-selected="true"] {
  background: var(--brand-primary-dark);
  border-color: var(--brand-primary-dark);
  color: var(--on-primary);
}

/* Secondary pills - outlined when active, so the primary selection stays the
   loudest selected thing on the page. */
.pill.pill-secondary[aria-selected="true"] {
  background: var(--brand-surface);
  color: var(--brand-primary-dark);
  border-color: var(--brand-primary-dark);
  font-weight: 600;
}

/* Panes driven by the pills above. */
.pane {
  display: none;
}
.pane.active {
  display: block;
}

/* ---------- Cards / thumbnails ---------- */

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--space-4);
}

.card {
  margin: 0;
}

.card img {
  width: 100%;
  height: 130px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  display: block;
}

.card figcaption {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.thumb {
  max-width: 80px;
  max-height: 60px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
}

/* ---------- Stat tiles ---------- */

.stat-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.stat-tile {
  background: var(--brand-surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: var(--space-4);
}

.stat-tile .label {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--th-text);
  margin-bottom: var(--space-1);
}

.stat-tile .value {
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1.1;
  font-feature-settings:
    "lnum" 1,
    "tnum" 1;
}

.stat-tile .sub {
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* ---------- Charts ---------- */
/* Chart.js with maintainAspectRatio:false needs an explicitly sized box. */

.chart-wrap {
  position: relative;
  height: 260px;
  max-width: 720px;
}

/* Side-by-side charts: ONE grid, items placed row-major (titles, blurbs,
   controls, then the canvases). Every row sizes to its taller cell, so however
   the text wraps at any viewport width the plot areas stay exactly level.
   Two charts side by side must both carry axis titles or neither - asymmetric
   titles or legends unlevel the plots. */
.chart-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--space-8);
  align-items: end;
}

/* Grid items default to min-width:auto, which lets a canvas's render width
   prop the columns open and force a horizontal scroll. min-width:0 lets them
   shrink; Chart.js then resizes the canvas to follow. */
.chart-row > * {
  min-width: 0;
}

/* ---------- Misc ---------- */

/* Inline explanatory text under a control or heading. */
.hint {
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* Lining, proportional figures for any standalone number in the UI. */
.counter {
  font-feature-settings:
    "lnum" 1,
    "pnum" 1;
}
