/* ===========================================================================
   Unsphere — gentle neo-brutalism
   ---------------------------------------------------------------------------
   The brutalist bones: thick ink borders, hard offset shadows (no blur), high
   contrast, chunky type. The gentle skin: a warm cream paper, soft pastel
   accents, generously rounded corners, and unhurried spacing. Loud, but kind.
   =========================================================================== */

:root {
  /* Paper & ink */
  --bg:        #FBF4E6;   /* warm cream */
  --surface:   #FFFDF8;   /* card paper, a hair brighter than bg */
  --ink:       #2B2722;   /* warm near-black — borders, shadows, body text */
  --ink-soft:  #6F665E;   /* muted captions, metadata */

  /* Pastel accents (saturated enough to read as brutalist, soft enough to be kind) */
  --coral:      #FF8A73;
  --coral-soft: #FBD9CE;
  --sage:       #86C29A;
  --sage-soft:  #D6EBDC;
  --butter:     #FFD45E;
  --butter-soft:#FCEBB4;
  --peri:       #9DB0F2;
  --peri-soft:  #DEE3FB;

  /* Brutalist geometry */
  --b:        2.5px;            /* standard border weight */
  --b-thick:  3px;             /* cards */
  --r:        14px;            /* the "gentle" — rounded, never sharp */
  --r-sm:     10px;
  --shadow:     4px 4px 0 var(--ink);
  --shadow-lg:  6px 6px 0 var(--ink);

  --font-display: "Bricolage Grotesque", "Trebuchet MS", sans-serif;
  --font-body:    "Hanken Grotesk", "Segoe UI", system-ui, sans-serif;
}

/* ----------------------------------------------------------------- Reset-ish */
*, *::before, *::after { box-sizing: border-box; }

/* Reserve the vertical scrollbar's gutter on every page, scrolling or not, so
   short pages (login) and long ones (workout list) share the same content width
   — otherwise the centred nav and container jump when the scrollbar appears. */
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--ink);
  background-color: var(--bg);
  /* Faint dotted-grid paper texture: atmosphere without noise. */
  background-image: radial-gradient(var(--ink) 0.6px, transparent 0.6px);
  background-size: 22px 22px;
  background-position: -11px -11px;
  /* Tame the dots so text stays comfortable. */
  background-blend-mode: normal;
}
body::before {
  /* A wash that fades the dots toward the paper colour. */
  content: "";
  position: fixed;
  inset: 0;
  background: var(--bg);
  opacity: 0.86;
  z-index: -1;
  pointer-events: none;
}

/* --------------------------------------------------------------- Typography */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.08;
  color: var(--ink);
}

h1 {
  font-size: clamp(2.1rem, 5vw, 3.1rem);
  margin: 0.2rem 0 1.4rem;
}
/* The signature flourish: a stubby coral underline tucked beneath every h1. */
h1::after {
  content: "";
  display: block;
  width: 3.25rem;
  height: 0.55rem;
  margin-top: 0.7rem;
  background: var(--coral);
  border-radius: 99px;
  box-shadow: 2px 2px 0 var(--ink);
}

h2 {
  font-size: clamp(1.4rem, 3vw, 1.8rem);
  margin: 2.2rem 0 1rem;
}
h3 { font-size: 1.25rem; margin: 1.4rem 0 0.75rem; }

p { margin: 0.75rem 0; }

a {
  color: var(--ink);
  text-decoration-color: var(--coral);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  font-weight: 600;
}
a:hover { text-decoration-color: var(--ink); }

strong { font-weight: 800; }

/* ------------------------------------------------------------------- Layout */
.container {
  max-width: 60rem;
  margin: 0 auto;
  padding: 1.25rem 1.25rem 5rem;
}

/* ---------------------------------------------------------------------- Nav */
/* Anchored header: a sticky bar the width of the main content column (centred,
   matching .container), so it — and the live gem counter it carries — stays on
   screen as you scroll, including right after an htmx "Save" deep down a workout
   page where the count animates. z-index keeps it above scrolling content (the
   gem toast at 1000 and the achievement <dialog> in the top layer still sit above
   it). It sits flush to the viewport top (top:0) with square top corners: since
   the bar and the content column are the same width, nothing scrolls beside or
   above it to peek through — and squaring the top avoids corner notches there.
   The bottom corners round like every other card. */
nav {
  position: sticky;
  top: 0;
  z-index: 100;
  max-width: 60rem;
  margin: 0 auto;
  padding: 0.7rem 1.1rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.15rem 0.55rem;
  background: var(--surface);
  border: var(--b) solid var(--ink);
  border-radius: 0 0 var(--r) var(--r);
  box-shadow: var(--shadow);
  font-family: var(--font-display);
  font-weight: 700;
}
/* The bar has exactly two flex children: the wordmark and the controls group.
   space-between pins the wordmark left and the controls right when they fit on
   one line. When they don't, the whole controls group drops to its own line and
   wraps internally into tidy left-aligned rows — so no single button is ever
   orphaned out to the side. The tagline sits beneath the logo in muted italic,
   broken to two short lines so the brand stays narrow. */
.nav-brand {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  line-height: 1;
}
.nav-links {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem 0.55rem;
}
/* Once the bar is too narrow to hold the wordmark and the controls side by side,
   the controls drop onto their own line. At that point give the wordmark the
   full first line to itself and center it, so the brand sits centered above the
   button rows rather than hugging the left edge. The 52rem cutoff is roughly
   where the (longer) signed-in control set stops fitting beside the wordmark. */
@media (max-width: 52rem) {
  .nav-brand {
    flex-basis: 100%;
    align-items: center;
    text-align: center;
  }
  /* Center the button rows too, so they sit symmetrically under the centered
     wordmark instead of hugging the left edge. */
  .nav-links { justify-content: center; }
}
.nav-logo {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.4rem;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--ink);
}
.nav-tagline {
  font-family: var(--font-body);
  font-size: 0.68rem;
  font-weight: 400;
  font-style: italic;
  line-height: 1.15;
  color: var(--ink-soft);
}
/* Every nav control — the section links and the logout button — reads as the
   same small sage chip, matching "New Workout". They carry their own borders
   and offset shadows, so the bar needs no "|" separators between them; the
   flex gap does the spacing. */
nav a,
nav button {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--ink);
  text-decoration: none;
  background: var(--sage);
  border: var(--b) solid var(--ink);
  border-radius: var(--r-sm);
  box-shadow: 2px 2px 0 var(--ink);
  padding: 0.3rem 0.7rem;
  line-height: 1.2;
  cursor: pointer;
  transition: transform 0.08s ease, box-shadow 0.08s ease, background 0.12s ease;
}
nav a:hover,
nav button:hover {
  transform: translate(-1px, -1px);
  box-shadow: 3px 3px 0 var(--ink);
}
nav a:active,
nav button:active {
  transform: translate(2px, 2px);
  box-shadow: 0 0 0 var(--ink);
}

/* The gem pill links to the ledger, so it's a control like the other nav chips
   (it picks up nav a's border, offset shadow, and hover lift) — but tinted
   butter instead of sage to set the gem readout apart from the section links. */
nav .gem-pill {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--ink);
  background: var(--butter-soft);
  border: var(--b) solid var(--ink);
  border-radius: var(--r-sm);
  padding: 0.3rem 0.6rem;
  white-space: nowrap;
}

/* Gem ledger page: reuse the generic .container > ul card rows; just bolden the
   amount and push the timestamp to the right edge of each row. */
.gem-txn-amount { font-family: var(--font-display); font-weight: 700; }
.gem-txn-category { color: var(--ink-soft); }
.gem-txn-when { margin-left: auto; color: var(--ink-soft); font-size: 0.85rem; }

/* The achievements link and gem readout are direct nav children carrying the id
   HTMX swaps into, so they're laid out exactly like the other nav items. Each
   is present from the start as a swap target — empty until the first unlock —
   so hide it while empty (an empty flex item would otherwise take a gap on each
   side, padding out the space after Movements); HTMX filling it removes :empty
   and brings it back. */
#nav-achievements-link:empty,
#nav-gems:empty { display: none; }

/* The nav already reads as a bar; the trailing <hr> is now redundant. */
nav + hr { display: none; }

/* Other horizontal rules become a chunky dashed seam. */
hr {
  border: none;
  border-top: var(--b) dashed var(--ink);
  opacity: 0.55;
  margin: 2rem 0;
}

/* ------------------------------------------------------------------ Buttons */
/* Buttons come in two colors: coral for form submits (below) and sage for
   everything else. Butter is reserved for non-interactive accents (the Draft
   pill, the h1 underline), so nothing yellow reads as a button. */
button,
input[type="submit"] {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--sage);
  border: var(--b) solid var(--ink);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow);
  padding: 0.5rem 0.95rem;
  cursor: pointer;
  line-height: 1.2;
  transition: transform 0.08s ease, box-shadow 0.08s ease, background 0.12s ease;
}
button:hover,
input[type="submit"]:hover {
  transform: translate(-1px, -1px);
  box-shadow: 5px 5px 0 var(--ink);
}
/* The satisfying press: the button sinks into its own shadow. */
button:active,
input[type="submit"]:active {
  transform: translate(4px, 4px);
  box-shadow: 0 0 0 var(--ink);
}
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
a:focus-visible {
  outline: 3px solid var(--peri);
  outline-offset: 2px;
}

/* The primary "Save" / submit action wears coral. */
form button[type="submit"],
form input[type="submit"] { background: var(--coral); }

/* ----------------------------------------------------- Form fields & inputs */
form p { margin: 0 0 1rem; }

label {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.9rem;
  margin-bottom: 0.3rem;
}

input[type="text"],
input[type="number"],
input[type="email"],
input[type="password"],
input[type="url"],
input[type="search"],
input[type="datetime-local"],
input[type="date"],
input[type="time"],
select,
textarea {
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--ink);
  background: var(--surface);
  border: var(--b) solid var(--ink);
  border-radius: var(--r-sm);
  padding: 0.5rem 0.65rem;
  width: 100%;
  max-width: 22rem;
  display: block;
  transition: box-shadow 0.12s ease, transform 0.12s ease;
}
textarea { max-width: 32rem; min-height: 5rem; }
input:focus,
select:focus,
textarea:focus {
  outline: none;
  box-shadow: var(--shadow);
  transform: translate(-1px, -1px);
}
::placeholder { color: var(--ink-soft); opacity: 0.7; }

/* Django renders inline help & errors as small text / lists under fields. */
.helptext, .helptext small, small { color: var(--ink-soft); font-size: 0.85rem; }
.errorlist {
  list-style: none;
  margin: 0.3rem 0;
  padding: 0.4rem 0.7rem;
  background: var(--coral-soft);
  border: 2px solid var(--ink);
  border-radius: var(--r-sm);
  font-size: 0.9rem;
  font-weight: 600;
}

/* ----------------------------------------------------------------- Messages */
.messages {
  list-style: none;
  max-width: 60rem;
  margin: 1rem auto 0;
  padding: 0 1.25rem;
  display: grid;
  gap: 0.6rem;
}
.messages li {
  padding: 0.7rem 1rem;
  background: var(--peri-soft);
  border: var(--b) solid var(--ink);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow);
  font-weight: 600;
}
.messages li.success { background: var(--sage-soft); }
.messages li.error   { background: var(--coral-soft); }
.messages li.warning { background: var(--butter-soft); }

/* --------------------------------------------------------------- Card
   The shared paper-card idiom: surface paper, thick ink border, gentle
   radius, hard offset shadow. Several components (stat tiles, charts, list
   rows) currently inline these same rules; this class is the canonical
   version to backport them onto. */
.card {
  background: var(--surface);
  border: var(--b-thick) solid var(--ink);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  padding: 1rem 1.25rem;
}

/* ----------------------------------------------------------------- Asides, info boxes, heads up, that kind of thing */
/* An info box is a card with a butter tint instead of plain paper. Defined
   after .card so this background wins over the card's surface colour. As a
   standalone flow block it carries its own bottom margin (unlike cards in a
   gap-spaced grid). */
.info {
  background: var(--butter-soft);
  margin-bottom: 1.5rem;
}

/* ------------------------------------------- Generic content lists as cards */
/* Workout list, movement list, exercise list: stacked "index card" rows.     */
.container > ul,
#exercise-list {
  list-style: none;
  padding: 0;
  margin: 1.25rem 0;
  display: grid;
  gap: 0.75rem;
}
.container > ul > li,
#exercise-list > li {
  background: var(--surface);
  border: var(--b) solid var(--ink);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  padding: 0.85rem 1.1rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}
/* The row card itself is never a click target — only the links and buttons
   inside it are — so the card doesn't lift on hover (that would imply the whole
   row is clickable). The inner controls carry their own hover affordance: the
   title link underlines (below) and the buttons lift on their own. */
.container > ul > li > a:first-child:hover {
  text-decoration: underline;
  text-decoration-color: var(--coral);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}

/* A whole workout row is one click target: the anchor's ::after is stretched
   over the card, so clicking anywhere on the row navigates to the detail. The
   row lifts on hover to advertise that it's clickable. */
.container > ul > li.row-link {
  position: relative;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.container > ul > li.row-link > a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}
.container > ul > li.row-link:hover {
  transform: translate(-2px, -2px);
  box-shadow: var(--shadow-lg);
}
/* Row action links/buttons drift to the right edge of their card. When a row
   has several actions (e.g. Edit + Delete), only the first absorbs the free
   space so the whole cluster sits together at the right edge. */
.container > ul > li > a:not(:first-child),
#exercise-list > li button { margin-left: auto; }
.container > ul > li > a:not(:first-child) ~ a,
#exercise-list > li button + button { margin-left: 0.4rem; }
/* Make the main link of a workout row read as the row's title. */
.container > ul > li > a:first-child {
  font-family: var(--font-display);
  font-weight: 700;
  text-decoration: none;
}

/* "New …" call-to-action paragraph above each list. */
.container > p > a[href*="create"],
.container > p > a[href$="/new/"] {
  display: inline-block;
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 700;
  background: var(--sage);
  border: var(--b) solid var(--ink);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow);
  padding: 0.45rem 0.9rem;
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}
.container > p > a[href*="create"]:hover,
.container > p > a[href$="/new/"]:hover {
  transform: translate(-1px, -1px);
  box-shadow: 5px 5px 0 var(--ink);
}

/* Add-exercise area on the workout detail page. */
#add-exercise-area {
  margin-top: 1.25rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
/* When it swaps to a form, give it room to breathe as a panel. */
#add-exercise-area:has(form) {
  display: block;
  background: var(--surface);
  border: var(--b-thick) solid var(--ink);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
  padding: 1.25rem;
}
#add-exercise-area h3 { margin-top: 0; }

/* =================================================== Achievements as a grid */
#achievement-list {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 1.25rem;
}
#achievement-list > li {
  position: relative;
  min-height: 10.5rem;
  background: var(--surface);
  border: var(--b-thick) solid var(--ink);
  border-radius: var(--r);
  box-shadow: var(--shadow-lg);
  padding: 1.3rem 1.2rem 1.1rem;
  transition: transform 0.14s ease, box-shadow 0.14s ease;
}
#achievement-list > li:hover {
  transform: translate(-3px, -3px);
  box-shadow: 9px 9px 0 var(--ink);
}

/* Earned cards (the only ones carrying a <strong> name) glow butter-gold. */
#achievement-list > li:has(strong) {
  background: var(--butter-soft);
}
/* Locked cards stay quiet and papery, with a faint hatch of mystery. */
#achievement-list > li:not(:has(strong)) {
  color: var(--ink-soft);
  background-color: var(--surface);
  background-image: repeating-linear-gradient(
    45deg,
    var(--peri-soft) 0,
    var(--peri-soft) 10px,
    transparent 10px,
    transparent 20px
  );
}
/* Fill the otherwise-empty locked card with a quiet "Locked" title. */
/*
#achievement-list > li:not(:has(strong))::before {
  content: "Locked";
  display: block;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.15rem;
  color: var(--ink-soft);
  letter-spacing: -0.01em;
}
*/

/* The trophy / "?" token becomes a circular brutalist stamp in the corner. */
.achievement-icon {
  position: absolute;
  top: 1.1rem;
  right: 1.1rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  font-size: 1.5rem;
  background: var(--surface);
  border: var(--b) solid var(--ink);
  border-radius: 50%;
  box-shadow: 2px 2px 0 var(--ink);
}
#achievement-list > li:has(strong) .achievement-icon { background: var(--butter); }
#achievement-list > li:not(:has(strong)) .achievement-icon {
  background: var(--peri-soft);
  font-family: var(--font-display);
  font-weight: 800;
  color: var(--ink-soft);
}

/* Achievement name reads as the card title, with the description on its own
   line beneath it. Only the title and description sit alongside the corner
   trophy stamp, so the right lane is reserved on them rather than on the whole
   card — that lets the "earned on" date and others-list span the full width. */
#achievement-list strong,
.achievement-desc {
  padding-right: 3.3rem;
}
#achievement-list strong {
  display: block;
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 800;
  letter-spacing: -0.01em;
}
.achievement-desc {
  display: block;
  margin-top: 0.3rem;
}
/* The earned/also-earned sub-blocks: small, muted, set off by a hairline. */
#achievement-list > li div {
  font-size: 0.85rem;
  color: var(--ink-soft);
}
#achievement-list > li div:has(ul) {
  margin-top: 0.35rem;
  padding-top: 0.5rem;
  border-top: 2px dashed var(--ink);
}
#achievement-list ul {
  list-style: none;
  margin: 0.35rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.2rem;
}
#achievement-list ul li {
  font-size: 0.82rem;
  padding: 0;
  border: none;
  box-shadow: none;
  background: none;
}
time { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------ Achievement celebration */
/* (The open/scale keyframes live inline in base.html; this is the chrome.) */
#achievement-dialog {
  border: var(--b-thick) solid var(--ink);
  border-radius: var(--r);
  box-shadow: 8px 8px 0 var(--ink);
  background: var(--butter-soft);
  padding: 1.75rem 2rem;
  max-width: 26rem;
  color: var(--ink);
}
#achievement-dialog::backdrop {
  background: rgba(43, 39, 34, 0.35);
}
#achievement-dialog h2 {
  margin: 0 0 0.5rem;
  font-size: 1.5rem;
}
#achievement-dialog .trophy { font-size: 1.6rem; }
#achievement-dialog form { margin-top: 1.25rem; }
#achievement-dialog button { background: var(--coral); }

/* ----------------------------------------------------------------- Details */
:where(h1, h2) + p a { font-weight: 700; }

@media (max-width: 30rem) {
  body { font-size: 1rem; }
  .container { padding: 1rem 1rem 4rem; }
  #achievement-list { grid-template-columns: 1fr; }
}

/* Respect reduced-motion: drop the press/hover travel. */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}

/* -------------------------------------------------- Workout draft status */
/* These are read-only status tags, not controls — so they deliberately avoid
   the button look (slightly-rounded rectangle + offset shadow). A fully-round
   pill with a small uppercase label reads as a state, not something to click. */
.draft-badge,
.saved-badge {
  display: inline-block;
  border: var(--b) solid var(--ink);
  border-radius: 999px;
  padding: 0.1rem 0.6rem;
  font-family: var(--font-display);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  vertical-align: middle;
}
.draft-badge { background: var(--butter); }
.saved-badge { background: var(--sage-soft); }
#workout-status .hint { color: var(--ink-soft); font-size: 0.85rem; margin-top: 0.25rem; }

/* ---------------------------------------------------- Movement kind chips */
/* Same pill vocabulary as the draft/saved status badges, but a distinct
   colour pair so the two never read as the same thing: cardio takes the cool
   periwinkle (breath, endurance), strength the warm coral (exertion, iron).
   These are labels, not controls — no shadow, no hover. */
.kind-chip {
  display: inline-block;
  border: var(--b) solid var(--ink);
  border-radius: 999px;
  padding: 0.1rem 0.6rem;
  font-family: var(--font-display);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  vertical-align: middle;
}
.kind-chip--cardio   { background: var(--peri-soft); }
.kind-chip--strength { background: var(--coral-soft); }


/* ── Stats page ─────────────────────────────────────────────────────────── */
/* Headline tiles: a couple of big numbers (total workouts, current streak) in
   the same paper-card idiom as the rest of the app — thick border, hard offset
   shadow, gentle radius. Auto-fit so 2 tiles sit side by side and wrap on
   narrow screens. */
.stat-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}
.stat-tile {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.7rem 1rem;
  background: var(--surface);
  border: var(--b-thick) solid var(--ink);
  border-radius: var(--r);
  box-shadow: var(--shadow);
}
.stat-value {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 2.2rem;
  line-height: 1;
  color: var(--ink);
}
.stat-label {
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Each chart sits in its own paper card; they stack in a single column. */
.stat-charts {
  display: grid;
  gap: 1.25rem;
}
.chart {
  padding: 1rem 1.1rem;
  background: var(--surface);
  border: var(--b-thick) solid var(--ink);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  overflow-x: auto;
}
/* Heading rendered by the template (not Plot's own title), so spacing is under
   our control and sits tight above the chart. */
.chart-title {
  margin: 0 0 0.6rem;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.3rem;
  color: var(--ink);
}
.chart-canvas figure { margin: 0; }     /* Plot wraps output in <figure> */
.chart-empty {
  margin: 0;
  padding: 2rem 0;
  text-align: center;
  color: var(--ink-soft);
  font-weight: 600;
}

/* --------------------------------------------------------------- Build info */
/* Tiny fixed watermark showing the running git commit, for debugging deploys. */
.build-info {
  position: fixed;
  bottom: 0.4rem;
  right: 0.6rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
  opacity: 0.7;
  pointer-events: none;
  font-family: var(--font-body);
}
