/* IT Wallboard
 *
 * Dark only, on purpose: this renders on a screen on a wall for the whole
 * working day, so there is no light mode to switch to and no theme toggle to
 * honour. Every value below is from the validated dark palette.
 *
 * Colour discipline, which is the part that is easy to get wrong later:
 *
 *   - The two chart series (answered, abandoned) are CATEGORICAL - identity,
 *     not judgement. They are blue and orange, palette slots 1 and 2.
 *     Validated all-pairs on the dark surfaces used here: worst CVD separation
 *     26.8 (protan), normal-vision 31.8, both well clear of the 8 / 15 floors.
 *
 *   - The obvious choice, green answered against red abandoned, is WRONG and
 *     was rejected on measurement, not taste: green #0ca30c against red
 *     #d03b3b separates by only 4.1 under deuteranopia, so a red-green viewer
 *     sees one colour in a stacked bar. Do not "improve" the board by putting
 *     it back.
 *
 *   - Red, amber and green are reserved for STATE: the stale banner, the
 *     meters against target, and the deltas. That is why abandoned calls are
 *     orange rather than red - abandoned calls existing is normal, so spending
 *     red on a series that is always present would leave nothing to say
 *     "something is wrong now". Red on this board always means act.
 *
 *   - Status colours never carry meaning alone; each ships with a glyph and a
 *     word, which is also what keeps status-red distinct from series-orange
 *     for a viewer who cannot separate the two hues.
 */

:root {
  /* One scale knob for the whole board. Everything downstream is in rem, so
   * the layout is resolution-independent: 1080p and 4K get an identical board,
   * one at twice the pixels. min() of the two axes means an unusual window
   * shape shrinks the board rather than overflowing it, and the max() floor
   * stops a tiny window collapsing the text to nothing. */
  /* Reduced from 0.55vw/0.98vh when the availability section made it six rows of
   * content. The board is viewed on an 80" 4K panel, where this is still large -
   * a 1.6rem label is ~34px there - and the extra ~10% of vertical room is what
   * lets an outage appear without clipping the panels underneath. */
  font-size: max(5.5px, min(0.5vw, 0.89vh));

  --page: #0d0d0d;
  --card: #1a1a19;

  --ink: #ffffff;
  --ink-2: #c3c2b7;
  --ink-3: #898781;
  --grid: #2c2c2a;
  --axis: #383835;
  --hair: rgba(255, 255, 255, 0.10);

  /* Categorical series - identity only. */
  --answered: #3987e5;
  --abandoned: #d95926;
  /* The agent trend lines. Palette slots 1, 2, 3 and 7, validated together as an
   * adjacent set on this surface: worst CVD separation 9.4, worst normal-vision
   * 24.6, both clear of the 8 / 15 floors.
   *
   * Four is the ceiling and not an arbitrary one. No four colours in this
   * palette clear the *all-pairs* test - only the first three slots do - and
   * lines on a shared axis get compared in every direction, so hue alone cannot
   * carry four series. Each line also has a distinct end-marker shape, a direct
   * end label and a legend entry. Slot 4 (yellow) is deliberately skipped: it
   * sits too close to slot 2's orange. Re-run the validator before adding a
   * fifth. */
  --agent-1: #3987e5;
  --agent-2: #d95926;
  --agent-3: #199e70;
  --agent-4: #9085e9;

  /* One colour per person in the leave planner, in the documented palette order -
   * which is the order validated to clear every ADJACENT gate in dark mode (worst
   * CVD separation 8.4, normal vision 19.3). Adjacent is the right standard here
   * because neighbouring rows are the pairs an eye actually compares.
   *
   * Eight slots is the ceiling, and past three these colours are NOT all
   * distinguishable pairwise - which is fine here and would not be on a chart.
   * Every row carries the person's name and its own fixed position, so the colour
   * reinforces an identity that is already unambiguous rather than carrying it.
   * Don't reuse this set anywhere the colour is the only thing telling two things
   * apart. */
  --person-1: #3987e5;
  --person-2: #d95926;
  --person-3: #199e70;
  --person-4: #c98500;
  --person-5: #d55181;
  --person-6: #008300;
  --person-7: #9085e9;
  --person-8: #e66767;
  /* Defensive third segment for calls that are neither, if the feed ever
   * reports offered > answered + abandoned. Grey, so it never reads as a
   * third meaningful category. */
  --other: #898781;

  /* Status - state only, never a series. */
  --good: #0ca30c;
  --warning: #fab219;
  --serious: #ec835a;
  --critical: #d03b3b;

  --gap: 1.3rem;
  --pad: 1.5rem;
  --radius: 0.8rem;

  --font: system-ui, -apple-system, "Segoe UI", sans-serif;
}

* { box-sizing: border-box; }

/* `clip`, and on both elements.
 *
 * `overflow: hidden` on body alone is not enough for a board that must be one
 * fixed screenful: it does not propagate (html stays the scroll container) and
 * it only stops the *user* scrolling, leaving the page programmatically
 * scrollable. `clip` creates no scroll container at all.
 *
 * This is insurance, not the fix for a specific bug - anything that does
 * overflow should be found and corrected, because clipping only hides it. Worth
 * re-checking with `document.documentElement.scrollHeight` after layout changes:
 * a closed <dialog> that had been accidentally un-hidden was laid out below the
 * fold once and nothing on screen showed it. */
html, body { height: 100%; overflow: clip; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font);
  font-size: 1.6rem;
  line-height: 1.25;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------- layout ---
 * Rows: header and KPI row take their content height, the two chart rows
 * share what is left. minmax(0, Nfr) rather than Nfr so a chart can never
 * push the grid taller than the viewport.
 *
 * To add a panel for a new endpoint: add its area name to the template below
 * and point a LAYOUT entry in js/config.js at it. Nothing else changes. */
.board {
  display: grid;
  height: 100dvh;
  padding: var(--pad);
  gap: var(--gap);
  grid-template-columns: repeat(12, minmax(0, 1fr));
  /* The uptime row is `auto`, not a fraction: on a good day it is one line of
   * text and the charts get the space back, and when things break it takes what
   * it needs. That collapse is the whole reason the availability panel can sit
   * this high up without costing anything. */
  /* The availability row is `auto` - content height - and the cap lives on the
   * panel as a max-height instead. `minmax(auto, 27rem)` looks equivalent and
   * is not: grid gives a track with a length maximum its free space up to that
   * maximum *before* fr tracks expand, so the row sat at a full 27rem with
   * 118px of empty panel while the charts below went short. */
  /* Three equal chart rows, then a by-exception row that collapses. The 90-day
   * trend and the 14-day cover strip share one row at half width each rather than
   * taking a row apiece: eight rows squeezed every plot under the 60px floor
   * where the chart helper refuses to draw, and both panels rendered blank. */
  /* Who is off gets a full-width `auto` row of its own, directly under the
   * headline tiles.
   *
   * `auto`, not a fraction, because its height is content: one line per person,
   * fixed by the size of the team. Measured, five people need 106px and a 1fr row
   * gave the panel 61px - so it trimmed to two and left "+3 more" on the panel the
   * board exists to answer. Weighting the rows instead just starved whichever
   * chart lost, and spanning two rows left 66% of it empty. An `auto` row takes
   * exactly what it needs and hands the rest to the charts, which end up taller
   * than they were before.
   *
   * Charts cannot live in an `auto` row - they have no intrinsic height, so they
   * would collapse to nothing - which is why only content-height panels sit in
   * these rows. */
  grid-template-rows:
    auto auto auto auto
    minmax(0, 1fr) minmax(0, 1fr)
    auto;
  grid-template-areas:
    "head  head  head  head  head   head   head   head   head       head       head       head"
    /* The ticket figures share the headline row with the call figures rather than
     * taking one of their own. Same tiles, same size, equal prominence - and it
     * hands ~100px back to the charts, which a second tile row had left under the
     * height they can draw in. */
    "kpi   kpi   kpi   kpi   kpi    kpi    kpi    kpi    tickets    tickets    tickets    tickets"
    "uptime uptime uptime uptime uptime uptime uptime uptime uptime uptime uptime uptime"
    "leavecover leavecover leavecover leavecover leavecover leavecover leavecover leavecover leavecover leavecover leavecover leavecover"
    /* Calls by hour is much narrower than it was, and quality sits down here - it
     * is the least urgent panel on the board. */
    /* Widths set by what each panel's CONTENT needs, not by symmetry. Every one of
     * these was measured after a narrower version put a panel under the height its
     * chart can draw in and it went dark:
     *   hour    3 - two columns wrapped its LEGEND onto a second line, which took
     *               30px of a 32px head and left a 50px plot.
     *   waits   3 - two columns wrapped its note ("median 16s · p90 33s"), same
     *               outcome.
     *   quality 3 - it has no legend, but it does have two sub-columns, and at two
     *               the measurements were squeezed below the width of their own
     *               labels and overflowed by 16px on a true 4K screen. Three is the
     *               floor; the column it took came from `hour`, which was the one
     *               panel with width to spare.
     *   agents  4 - the cards are 26rem, so at three the list falls to a single
     *               column on a true 16:9 screen and only one of two people fits.
     * Rule of thumb: a chart panel with a legend needs at least three. Measured at
     * 3840x2160 - hour 2 is the only other split that clips nothing. */
    "hour  hour  waits waits  waits  quality quality quality agents agents agents agents"
    "agenttrend agenttrend agenttrend agenttrend trend trend trend trend trend trend trend trend"
    "leavelow leavelow leavelow leavelow leavelow leavelow leavelow leavelow leavelow leavelow leavelow leavelow";
}

/* ---------------------------------------------------------------- header --- */
.head { display: flex; flex-direction: column; gap: var(--gap); min-width: 0; }

.head-row { display: flex; align-items: center; gap: 2.5rem; }

.brand h1 {
  margin: 0;
  font-size: 2.9rem;
  font-weight: 650;
  letter-spacing: 0.01em;
}
.brand-sub {
  margin: 0.25rem 0 0;
  font-size: 1.5rem;
  color: var(--ink-3);
}

.clockbox { margin-left: auto; text-align: right; }
.clock {
  font-size: 3.4rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums; /* a clock must not jitter as digits change */
}
.clock-date { font-size: 1.4rem; color: var(--ink-3); margin-top: 0.2rem; }

.status {
  display: flex;
  align-items: baseline;
  gap: 0.8rem;
  padding: 0.9rem 1.4rem;
  border: 1px solid var(--hair);
  border-radius: var(--radius);
  background: var(--card);
  min-width: 0;
}
.status-glyph { font-size: 1.7rem; line-height: 1; }
.status-text {
  font-size: 1.7rem;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.status-detail {
  font-size: 1.35rem;
  color: var(--ink-3);
  white-space: nowrap;
}
.status[data-state="ok"]      { --tone: var(--good); }
.status[data-state="stale"]   { --tone: var(--warning); }
.status[data-state="error"]   { --tone: var(--critical); }
.status[data-state="loading"] { --tone: var(--ink-3); }
.status .status-glyph, .status .status-text { color: var(--tone, var(--ink-3)); }
.status[data-state="error"] { border-color: var(--critical); }
.status[data-state="stale"] { border-color: var(--warning); }

/* The stale/offline banner. Deliberately loud - it is the one element allowed
 * to shout, because everything else on the board is meaningless if it shows. */
/* `.banner[hidden]` is NOT redundant, and leaving it out is a bug that has
 * already happened twice in this file.
 *
 * The UA stylesheet hides `[hidden]` with `display: none`, but an author
 * `display` on the element beats it - author origin wins over UA regardless of
 * specificity. So `bannerEl.hidden = true` did nothing here: once an alert had
 * set `data-tone`, clearing it left an empty amber bar across the top of the
 * board, with the padding, border and background still painting and no text to
 * explain them. Same trap as `.settings[open]` further down. */
.banner[hidden] { display: none; }
.banner {
  display: flex;
  align-items: center;
  gap: 1.2rem;
  padding: 1.1rem 1.6rem;
  border-radius: var(--radius);
  font-size: 2rem;
  font-weight: 600;
  color: var(--ink);
  background: color-mix(in srgb, var(--tone) 22%, var(--card));
  border: 2px solid var(--tone);
}
.banner[data-tone="warning"]  { --tone: var(--warning); }
.banner[data-tone="critical"] { --tone: var(--critical); }
.banner-glyph { font-size: 2.2rem; line-height: 1; color: var(--tone); }
.banner-detail { color: var(--ink-2); font-weight: 400; font-size: 1.7rem; }
.banner-detail::before { content: "\2014\00a0"; }

/* ----------------------------------------------------------------- panel --- */
.panel {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  padding: 1.4rem 1.6rem;
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: var(--radius);
}
.panel--bare { background: none; border: none; padding: 0; }

.panel-head {
  display: flex;
  align-items: baseline;
  gap: 1.5rem;
  margin-bottom: 1rem;
  flex: none;
  /* Wraps rather than spilling out of the panel.
   *
   * `nowrap` plus a legend pushed to the right by `margin-left: auto` sent the
   * legend 147px past the panel's right edge on the narrowest chart, where it drew
   * over the panel beside it and got visually cut in half. Wrapping costs about
   * 30px of plot height, which is the lesser harm - and if that ever pushes a chart
   * under its minimum, the chart now says "too small to plot" out loud rather than
   * going quietly blank. */
  flex-wrap: wrap;
}
.panel-title {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 650;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
/* margin:0 because this is a <span> in a panel head but a <p> for the "+N more"
 * notes, and there the browser's default margins overflow the panel. */
.panel-note { margin: 0; font-size: 1.3rem; color: var(--ink-3); }
/* `overflow: hidden` so a squeezed panel clips at its own border instead of
 * drawing over the panel below it.
 *
 * The panels that can be squeezed all measure-and-trim to fit, but every one of
 * them has a floor: the agent list always keeps one card even when the row is
 * shorter than a card, and the planner keeps its header. When a row does get that
 * short - a 1080p window, or 4K with the availability panel full of outages - the
 * overflow used to escape the panel entirely and overlap its neighbour, which
 * reads as a rendering fault rather than as a full panel. Clipped at the border it
 * reads as "there is more", and the "+N more - press t" note says so in words. */
.panel-body { flex: 1 1 auto; min-height: 0; position: relative; overflow: hidden; }
/* The chart host fills the body absolutely so its measured size is the body's
 * content box exactly - charts are drawn at 1:1 device pixels, not scaled from
 * a viewBox, so the measurement has to be right. */
.plot { position: absolute; inset: 0; }
.legend-holder { margin-left: auto; display: flex; min-width: 0; }

/* Legend - always present for two or more series. Wraps rather than running off
 * the edge: with four agents and their rates it does not fit one line in a
 * half-width panel, and the panel head can afford to grow before the chart can
 * afford to lose the names. */
.legend { display: flex; gap: 0.4rem 1.4rem; margin-left: auto; flex-wrap: wrap; justify-content: flex-end; }
.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 1.35rem;
  color: var(--ink-2); /* label text stays ink; the swatch carries identity */
  white-space: nowrap;
}
.legend-swatch {
  width: 1.5rem;
  height: 0.75rem;
  border-radius: 0.2rem;
  background: var(--swatch);
  flex: none;
}
/* Matches how a provisional quantity is drawn: outlined, not filled. */
.legend-swatch--outline {
  background: none;
  border: 2px solid var(--swatch);
  height: 0.9rem;
}
/* Matches how a half day is drawn in the planner: half the cell. */
.legend-swatch--half {
  background: linear-gradient(90deg, var(--swatch) 0 50%, transparent 50% 100%);
}

/* ----------------------------------------------------------------- tickets ---
 * The same tiles as the call KPIs, three across the full width, so they read as
 * headline figures rather than supporting detail. Placeholder-first: the values
 * are em dashes until a feed exists, because three zeros from a backend that
 * hasn't been written would be indistinguishable from a genuinely quiet
 * helpdesk. */
/* Doubled class, not for emphasis: this block sits earlier in the file than
 * `.kpis`, and at equal specificity the later rule wins - so a single class here
 * lost the column template and the three tiles rendered squeezed into the first
 * three of the call row's eight columns. */
/* Five: the three standing-queue figures plus opened and closed. The flow pair are
 * narrower on purpose - they are movement rather than the queue, and the three
 * that can turn a tile red deserve the room. */
.kpis.kpis--tickets { grid-template-columns: repeat(3, minmax(0, 1.15fr)) repeat(2, minmax(0, 1fr)); }
.kpis--tickets .tile-value { font-size: 4.2rem; }
/* A dash is not a fault and neither is a zero - neither gets a status colour. */
.kpis--tickets .tile-value[data-tone="none"] { color: var(--ink-3); }

/* Over a threshold, the WHOLE tile changes - tinted fill and a coloured border,
 * not just the digits. A tile that changes colour is visible from the far side of
 * the room; a coloured number is not. The tile's own sub-line names the threshold
 * it crossed and carries a glyph, so the colour is never the only thing saying
 * something is wrong. */
.tile[data-tone] {
  border-color: var(--tone);
  background: color-mix(in srgb, var(--tone) 16%, var(--card));
}
.tile[data-tone="warning"] { --tone: var(--warning); }
.tile[data-tone="critical"] { --tone: var(--critical); }
.tile[data-tone] .tile-value { color: var(--tone); }
.tile[data-tone] .tile-label { color: var(--ink-2); }
.tile-sub[data-tone] { color: var(--tone); font-weight: 600; }

/* ------------------------------------------------------------- leave planner ---
 * A real table: two headed axes and a value per cell, which is what it is. That
 * also means the visible version and the accessible version are the same object,
 * so there is no separate table twin to keep in step - and `t` leaves it alone,
 * which is why its container is not `.plot`. */
/* Flows rather than filling absolutely: its row is `auto`, so the panel's height
 * comes from this content. Absolutely positioned, it would contribute nothing and
 * the row would collapse to zero. The max-height is a backstop for a much larger
 * team - MAX_ROWS in the panel is what normally caps it. */
.planner-wrap { overflow: hidden; }
.planner {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
}
.planner-corner { width: 11.5rem; }
/* Deliberately tight. The whole team has to fit: a planner showing three of five
 * people and "+2 more" doesn't answer "who is off", which is the one thing it is
 * for. Every row costs five times its own height here. */
.planner-day {
  padding: 0 0 0.2rem;
  font-weight: 600;
  line-height: 1.05;
  color: var(--ink-3);
}
/* Weekday and date on one line - two lines cost ~10px of header, which is most of
 * a person's row, and the whole team has to fit. */
.planner-day .planner-wd,
.planner-day .planner-dn { display: inline; font-size: 1rem; }
.planner-day .planner-wd { margin-right: 0.25em; }
.planner-day .planner-dn { font-variant-numeric: tabular-nums; }
.planner-day[data-today="1"] { color: var(--ink); }

.planner-name {
  /* Wide enough for a full name now the panel has six columns - "Benjamin
   * Pepperell" was ellipsising, and a planner whose point is who is off should
   * not truncate who. */
  width: 11.5rem;
  text-align: left;
  font-size: 1.2rem;
  font-weight: 500;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-right: 0.7rem;
}
.planner-footlabel {
  font-size: 1.05rem;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border-top: 1px solid var(--hair);
  padding-top: 0.25rem;
}

.planner-cell { padding: 0.1rem 0.15rem; height: 1.2rem; }
/* A day the question doesn't apply to reads as inert rather than as "nobody off". */
.planner-cell[data-nonworking="1"],
.planner-day[data-nonworking="1"],
.planner-in[data-nonworking="1"] { background: rgba(255, 255, 255, 0.035); }

/* Today's column, and these rules come after the non-working ones so today wins
 * when it falls on a weekend.
 *
 * Ink rather than a hue: every colour on this panel already means a person, and
 * the status colours mean something is wrong. "Which column is today" is neither,
 * so it gets a brighter tint, a rule down each side and a bold header - visible
 * from across a room without spending a colour that means something else. */
.planner-cell[data-today="1"],
.planner-in[data-today="1"],
.planner-day[data-today="1"] {
  background: rgba(255, 255, 255, 0.11);
  box-shadow: inset 2px 0 0 var(--ink-2), inset -2px 0 0 var(--ink-2);
}
.planner-day[data-today="1"] {
  color: var(--ink);
  font-weight: 700;
}
.planner-in[data-today="1"] { color: var(--ink); }

/* Colour is the person (set per row), shape is the kind of absence. */
.planner-block {
  display: block;
  height: 100%;
  min-height: 0.7rem;
  border-radius: 0.25rem;
  background: var(--person, var(--abandoned));
}
/* Half the cell for half a day. */
.planner-block[data-kind="half"] { width: 50%; }
/* Requested but not approved: outlined, because it is not absence yet. */
.planner-block[data-kind="pending"] {
  background: none;
  border: 2px solid var(--person, var(--ink-2));
}
/* The person's colour also tints their name, so the row and its blocks read as
 * one thing. Ink weight, not the raw hue, so it stays legible as text. */
.planner-name { color: var(--ink-2); }
.planner-name::before {
  content: "";
  display: inline-block;
  width: 0.7rem;
  height: 0.7rem;
  margin-right: 0.55rem;
  border-radius: 50%;
  background: var(--person, var(--ink-3));
  vertical-align: baseline;
}

.planner-in {
  text-align: center;
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  border-top: 1px solid var(--hair);
  padding-top: 0.25rem;
}
.planner-in[data-nonworking="1"] { color: var(--ink-3); font-weight: 400; }
/* Below the minimum the feed publishes - strictly below, so a day exactly on the
 * line is not flagged. */
.planner-in[data-low="1"] { color: var(--critical); font-weight: 700; }

/* ------------------------------------------------------------- kpi tiles --- */
.kpis {
  display: grid;
  grid-template-columns: 1.6fr repeat(7, minmax(0, 1fr));
  gap: var(--gap);
}
.tile {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.3rem 1.5rem;
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: var(--radius);
  min-width: 0;
}
.tile-label {
  /* 1.2rem so the longest label - "ANSWERED ≤ 30 SEC" - fits one line in the
   * narrower tiles the ticket figures left behind. The two-line clamp below is a
   * safety net, not the plan: the KPI row's height is set by its tallest tile, so
   * a label that wraps grows the whole row, and at 1.35rem that pushed the wait
   * histogram under the height its chart can draw in. */
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-height: 1.15;
  overflow: hidden;
}
/* Proportional figures, not tabular: these are large standalone numbers and
 * equal-width digits make them look loose. */
.tile-value {
  font-size: 4.4rem;
  font-weight: 620;
  letter-spacing: -0.02em;
  line-height: 1;
}
.tile-sub { font-size: 1.35rem; color: var(--ink-3); }
.tile-foot { margin-top: auto; display: flex; align-items: baseline; gap: 0.9rem; flex-wrap: wrap; }
.tile-meter { flex: 1 0 100%; margin-top: 0.5rem; }

/* Exactly one hero figure on the board: today's answer rate. */
.tile--hero { justify-content: flex-start; }
/* 6.2rem, not 7.6: the hero sets the height of the whole KPI row, and at 7.6 it
 * left every other tile in the row with a third of its box empty. Still
 * comfortably the largest figure on the board, and still well past the 48px
 * minimum a hero figure wants (115px at 4K, 52px at 1080p). */
.tile--hero .tile-value { font-size: 6.2rem; }
.tile--hero .tile-label { font-size: 1.5rem; }

.delta { font-size: 1.4rem; font-weight: 600; white-space: nowrap; }
.delta[data-dir="good"]    { color: var(--good); }
.delta[data-dir="bad"]     { color: var(--critical); }
.delta[data-dir="flat"],
.delta[data-dir="neutral"] { color: var(--ink-3); font-weight: 500; }

/* ------------------------------------------------------------ the meter ---
 * A single ratio against a limit. The track is the fill's own hue at low
 * alpha, so the state reads across the whole bar rather than only the filled
 * part, and the target tick is drawn on the bar so the board never asks the
 * reader to know the target from memory. */
.meter { display: flex; flex-direction: column; gap: 0.45rem; }
.meter-track {
  position: relative;
  height: 0.9rem;
  border-radius: 0.45rem;
  background: var(--track);
  overflow: hidden;
}
.meter-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 0.45rem;
  background: var(--fill);
}
/* Full height rather than standing proud of the track: the track clips to its
 * own rounded corners so the fill can't spill, which would clip an overhanging
 * tick too. */
.meter-target {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 0.2rem;
  background: var(--ink);
  opacity: 0.85;
  border-radius: 0.1rem;
}
.meter-caption {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 1.3rem;
  color: var(--ink-3);
}
.meter-state { font-weight: 650; letter-spacing: 0.04em; text-transform: uppercase; }
.meter-state[data-sev="good"]     { color: var(--good); }
.meter-state[data-sev="warning"]  { color: var(--warning); }
.meter-state[data-sev="critical"] { color: var(--critical); }
.meter-state[data-sev="none"]     { color: var(--ink-3); }

/* ------------------------------------------------------------ agent list ---
 * Rows, not a five-column table. In a quarter-width panel the column headings
 * alone - OFFERED, ANSWERED, ANSWER RATE, AVG TALK - cannot fit without
 * shrinking the type past what is readable from across a room, and a single
 * word will not wrap to help. So each figure is labelled in place instead, and
 * the exact tabular version lives behind `t`. */
/* Two-up (or more) when the panel is wide, which it is - half the board. Laid
 * out in a single column, three-line agent rows only fit one deep in the height
 * this row gets, so a team of two lost one to "+1 more" beside a metre of empty
 * space. */
.agentlist {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(26rem, 1fr));
  gap: 0.9rem 2.4rem;
  align-content: start;
}
.agentrow {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--hair);
  min-width: 0;
}
.agentrow-top { display: flex; align-items: baseline; gap: 1rem; }
.agentrow-name {
  font-size: 1.8rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.agentrow-count {
  margin-left: auto;
  font-size: 1.45rem;
  color: var(--ink-2);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.agentrow-bottom { display: flex; align-items: center; gap: 1rem; }
/* One series, one hue: this is a magnitude bar, not a status. An agent's answer
 * rate has no target on this board, so it gets no severity colour. */
.ratebar {
  position: relative;
  flex: 1 1 auto;
  height: 0.75rem;
  border-radius: 0.4rem;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
  min-width: 0;
}
.ratebar > i {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--answered);
  border-radius: 0.4rem;
}
.rate-value {
  font-size: 1.6rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.agentrow-talk,
.agentrow-detail {
  font-size: 1.25rem;
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------- quality panel ---
 * The measurements sit against the threshold that would make a call poor, so a
 * healthy network reads as four bars well inside their limits rather than as an
 * empty panel. */
.plot--flow { position: absolute; inset: 0; overflow: hidden; display: flex; flex-direction: column; }

/* Verdict beside the measurements, not above them: stacked, this panel wanted
 * more height than its row has once the availability panel fills up. */
[data-panel="itcalls-quality"] .plot--flow {
  display: grid;
  /* Proportional, NOT `auto` on the first column. `auto` sizes to max-content, so
   * the verdict sentence ("no faults recorded") took 381px of 508px and squeezed
   * the measurements into 127px - less than their labels alone need - which
   * overflowed the panel. Neither column may starve the other. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 0.6rem 1.6rem;
  /* stretch, not start: the four measurements then spread down the panel instead
   * of clustering at the top over 56% empty space. */
  align-items: stretch;
}
.q-summary { min-width: 0; }
/* The verdict and caption wrap rather than forcing the column wide - they are
 * sentences, not labels - but they are CAPPED AT TWO LINES.
 *
 * That cap is what makes this panel's height independent of the feed's content.
 * A healthy day says "no faults recorded"; a bad one says "worst: jitter (3) +2
 * other", which is half again as long, and this is the shortest panel on the
 * board. Without the cap the panel fits in testing and overflows on the day
 * something actually goes wrong - the worst possible time to lose the text. Two
 * lines is enough for every string these two lines can produce. */
.q-verdict,
.q-caption {
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
/* Stacked, NOT side-by-side-that-wraps.
 *
 * This was `flex` with `flex-wrap: wrap`, which is height-unstable: the value and
 * its caption sat on one line on a quiet day and wrapped to two the moment the
 * caption grew ("7 poor of 1,284 rated" is wider than "0 poor of 13 rated"), so
 * the panel gained a whole 1rem gap plus a line exactly when the numbers got
 * interesting. Stacking always costs the same, and this is the shortest panel on
 * the board - predictable beats compact here. */
.q-headline { display: flex; flex-direction: column; align-items: flex-start; gap: 0.15rem; }
.q-value {
  /* 3.1rem, not 3.6: the summary column has to hold value + caption + a
   * two-line verdict inside about 149px at 4K, and it was 14px over. */
  font-size: 3.1rem;
  font-weight: 620;
  line-height: 1;
  letter-spacing: -0.02em;
}
.q-value[data-sev="good"]     { color: var(--good); }
.q-value[data-sev="critical"] { color: var(--critical); }
.q-value[data-sev="none"]     { color: var(--ink-3); }
.q-caption { font-size: 1.25rem; color: var(--ink-3); }
/* 1.2rem is sized so the longest verdict the feed can produce - "▼ worst: packet
 * loss (12) +2 other", about 33 characters - still fits ONE line in this column.
 * The two-line clamp above is the safety net, not the expected case. */
.q-verdict { margin-top: 0.25rem; font-size: 1.2rem; font-weight: 600; }
.q-verdict[data-sev="good"]     { color: var(--good); }
.q-verdict[data-sev="critical"] { color: var(--critical); }
.q-verdict[data-sev="none"]     { color: var(--ink-3); }

/* One line per measurement: label, bar, "value / limit". Four of these plus the
 * headline have to fit a fifth of the board's height. */
.limitlist {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 0.25rem;
  min-width: 0;
}
.limitrow { display: flex; align-items: center; gap: 0.9rem; }
.limitrow-label {
  flex: 0 0 8rem;
  font-size: 1.25rem;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.limitrow-track {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 0.7rem;
  border-radius: 0.35rem;
  background: var(--track);
  overflow: hidden;
}
.limitrow-fill { position: absolute; inset: 0 auto 0 0; border-radius: 0.35rem; }
.limitrow-value {
  flex: 0 0 auto;
  font-size: 1.25rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--ink-2);
}
.limitrow-value[data-over="1"] { color: var(--critical); }

/* ------------------------------------------------------ short-staffed days ---
 * By exception, so the panel removes itself when there is nothing to report and
 * its `auto` grid row collapses.
 *
 * `.panel[hidden]` is NOT redundant: an author `display` on .panel beats the UA's
 * `[hidden] { display: none }` regardless of specificity, which is the same trap
 * that left an empty orange banner across the top of this board. */
.panel[hidden] { display: none; }

.lowgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(34rem, 1fr));
  gap: 0.9rem;
  align-items: start;
}
.lowcard {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.8rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--warning);
  background: color-mix(in srgb, var(--warning) 14%, var(--card));
  min-width: 0;
}
.lowcard-head { display: flex; align-items: baseline; gap: 0.8rem; min-width: 0; }
.lowcard-when { font-size: 1.7rem; font-weight: 650; white-space: nowrap; }
.lowcard-level {
  margin-left: auto;
  font-size: 1.5rem;
  font-weight: 650;
  color: var(--warning);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.lowcard-who {
  font-size: 1.35rem;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lowcard-gap { font-size: 1.2rem; color: var(--ink-3); }

/* ------------------------------------------------ availability by exception ---
 * Cards follow Uptime-Robot-Wallboard's tile pattern - name, status line with a
 * glyph and an uppercase word, "since" and a live duration, tag pills - restyled
 * onto this board's validated status tokens. Only monitors with something wrong
 * get one, so on a good day this whole panel is a single green line.
 *
 * The panel body is content-height rather than absolutely positioned, unlike the
 * chart panels: its row is `auto` and it has to be able to size that row. */
/* The chart panels fill a fraction of the viewport, so their plot is absolutely
 * positioned to be measurable. This one has to SIZE its row instead, so its plot
 * flows. `.panel-body` keeps position:relative - that costs no height and is what
 * the visually-hidden table anchors to. */
.panel--flow .plot--flow { position: static; inset: auto; }
/* A bad afternoon with eight outage cards must not starve the charts below to
 * nothing, so the panel is capped and its own "+N more" note carries the rest. */
.panel--flow { max-height: 27rem; overflow: hidden; }

/* The panel's own border reports the worst thing in it, so the section reads at
 * a distance before any individual card does. */
.panel[data-state="warning"] { border-color: var(--warning); }
.panel[data-state="critical"] { border-color: var(--critical); }

.allclear {
  margin: 0;
  font-size: 1.9rem;
  font-weight: 600;
  color: var(--good);
}

.mongrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(32rem, 1fr));
  gap: 1rem;
  align-items: start;
}

.moncard {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.9rem 1.1rem;
  border-radius: var(--radius);
  border: 1px solid var(--tone);
  background: color-mix(in srgb, var(--tone) 14%, var(--card));
  min-width: 0;
}
.moncard[data-tone="critical"] { --tone: var(--critical); }
.moncard[data-tone="warning"] { --tone: var(--warning); }
/* Paused is not a fault, so it gets ink rather than a status colour - it should
 * read as "noted" and never compete with a real outage beside it. */
.moncard[data-tone="paused"] { --tone: var(--ink-3); }

.moncard-head { min-width: 0; }
/* Wraps to at most two lines rather than ellipsising on one. These names are
 * long and are the whole point of the card. */
.moncard-name {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 1.7rem;
  font-weight: 650;
  line-height: 1.15;
  overflow-wrap: anywhere;
}
.moncard-foot { display: flex; align-items: baseline; gap: 0.8rem; min-width: 0; }
.moncard-tags { margin-left: auto; display: flex; gap: 0.4rem; flex: none; }
.tagpill {
  font-size: 1.1rem;
  line-height: 1.5;
  padding: 0 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--hair);
  color: var(--ink-3);
  white-space: nowrap;
}

.moncard-status { display: flex; align-items: baseline; gap: 0.7rem; flex-wrap: wrap; }
/* Glyph and word together: the tone colour never carries the meaning alone. */
.moncard-glyph { font-size: 1.5rem; line-height: 1; color: var(--tone); }
.moncard-word {
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--tone);
}
.moncard-since { font-size: 1.3rem; color: var(--ink-3); }
.moncard-duration {
  font-size: 1.4rem;
  font-weight: 600;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.moncard-detail {
  font-size: 1.3rem;
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* --------------------------------------------------- agent trend lines ---
 * Four series on one shared axis. Shape and the direct end label do as much
 * identity work as colour here - see the --agent-* note at the top of this file
 * for why hue alone is not enough at four series. */
.chart .line-series {
  fill: none;
  stroke-linejoin: round;
  stroke-linecap: round;
}
/* The 2px surface ring keeps a marker legible where it crosses another line. */
.chart .line-end, .chart .line-dot { stroke: var(--card); stroke-width: 2; }
.chart .end-label {
  font-size: 1.35rem;
  font-weight: 600;
  fill: var(--ink-2);
  dominant-baseline: middle;
}
.chart .leader { fill: none; stroke: var(--axis); stroke-width: 1; }
.chart .crosshair {
  stroke: var(--ink-3);
  stroke-width: 1;
  pointer-events: none;
  transition: opacity 90ms;
}
.legend-marker { flex: none; display: block; }
.tooltip-none { color: var(--ink-3); font-weight: 400; }

.empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  /* These are <p> elements: without this the browser's default 1em margins sit on
   * top of a height:100% box and every empty state overflows its panel by its own
   * line height. */
  margin: 0;
  color: var(--ink-3);
  font-size: 1.7rem;
}

/* ---------------------------------------------------------------- charts --- */
.chart { display: block; width: 100%; height: 100%; }
.chart text { fill: var(--ink-3); font-family: var(--font); }
.chart .tick-label { font-size: 1.25rem; font-variant-numeric: tabular-nums; }
.chart .cat-label { font-size: 1.3rem; }
.chart .cat-label--now { fill: var(--ink); font-weight: 650; }
.chart .cat-sub { font-size: 1.1rem; }
.chart .value-label { font-size: 1.4rem; font-weight: 650; fill: var(--ink-2); }
.chart .gridline { stroke: var(--grid); stroke-width: 1; }
.chart .baseline { stroke: var(--axis); stroke-width: 1; }
/* A threshold annotation, not chrome: solid like everything else here, but at
 * 2px against the grid's 1px so weight rather than dashing tells them apart. */
.chart .threshold { stroke: var(--ink-3); stroke-width: 2; }
.chart .threshold-label { font-size: 1.2rem; fill: var(--ink-3); font-weight: 600; }
/* A quantity that is real but unconfirmed - leave awaiting approval. Outlined,
 * not filled: it is not extra cover, it is the slice that could vanish. */
.chart .at-risk { fill: none; stroke: var(--ink-2); }
.chart .value-label--muted { fill: var(--ink-3); font-weight: 500; }
.chart .hit { fill: transparent; outline: none; }
/* Opacity is driven from JS on pointer/focus so the wash doesn't depend on
 * sibling order in the SVG. */
.chart .band-wash { fill: var(--ink); opacity: 0; pointer-events: none; transition: opacity 90ms; }
.chart .hit:focus-visible { stroke: var(--ink); stroke-width: 2; }

.tooltip {
  position: fixed;
  z-index: 20;
  pointer-events: none;
  padding: 0.9rem 1.1rem;
  border-radius: 0.6rem;
  background: #000000;
  border: 1px solid var(--hair);
  font-size: 1.4rem;
  line-height: 1.4;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 90ms;
}
.tooltip[data-show="1"] { opacity: 1; }
.tooltip-title { font-weight: 650; margin-bottom: 0.3rem; }
.tooltip-row { display: flex; align-items: center; gap: 0.6rem; color: var(--ink-2); }
.tooltip-row b { color: var(--ink); margin-left: auto; font-variant-numeric: tabular-nums; }
.tooltip-row--total { border-top: 1px solid var(--hair); margin-top: 0.35rem; padding-top: 0.35rem; }
.tooltip-row--aside { color: var(--ink-3); }
.tooltip-row--aside b { color: var(--ink-2); }
.tooltip-dot { width: 0.9rem; height: 0.9rem; border-radius: 0.2rem; background: var(--swatch); flex: none; }

/* Held at reduced opacity while a refetch is failing: the previous render
 * stays put rather than flashing a skeleton, and the dimming plus the banner
 * say it is not current. */
.board[data-feed="error"] .panel,
.board[data-feed="error"] .tile { opacity: 0.55; transition: opacity 200ms; }

/* -------------------------------------------------- table twins & a11y ---
 * Every chart has a table equivalent. It is always in the accessibility tree;
 * `t` promotes it to visible for anyone who wants to read exact figures off
 * the board. */
.table-twin {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
  /* The table inside is full-size and still laid out - it has to be, that is
   * what keeps it in the accessibility tree. Paint containment stops it
   * contributing scrollable overflow to the page, which it otherwise does even
   * though it is clipped out of sight. */
  contain: paint;
}
/* Shown, it takes exactly the panel body's box and scrolls inside it. Left in
 * normal flow it grows to its content and pushes out of the panel, over the
 * one below and off the bottom of a board that must not scroll. */
body.show-tables .table-twin {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  margin: 0;
  clip: auto;
  overflow-y: auto;
  white-space: normal;
  background: var(--card);
}
body.show-tables .plot,
body.show-tables .planner-wrap { display: none; }
.table-twin table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.table-twin caption { text-align: left; font-size: 1.3rem; color: var(--ink-3); padding-bottom: 0.5rem; }
.table-twin th, .table-twin td { padding: 0.3rem 0.7rem; text-align: right; font-size: 1.35rem; border-bottom: 1px solid var(--hair); }
.table-twin th:first-child, .table-twin td:first-child { text-align: left; }
.table-twin th { color: var(--ink-3); font-weight: 600; }

.keyhint {
  position: fixed;
  right: 1.6rem;
  bottom: 1rem;
  margin: 0;
  font-size: 1.15rem;
  color: var(--ink-3);
  opacity: 0;
  transition: opacity 150ms;
}
body.show-keyhint .keyhint { opacity: 0.75; }
.keyhint kbd {
  font-family: inherit;
  border: 1px solid var(--hair);
  border-radius: 0.25rem;
  padding: 0 0.35rem;
}

/* -------------------------------------------------------------- settings ---
 * Read up close by someone with a keyboard, not from across the room, so this
 * is the one part of the board deliberately scaled down rather than up. */
.settings {
  width: min(92vw, 76rem);
  max-height: 88dvh;
  /* Only .set-body scrolls. Without this the dialog scrolls too and you get two
   * nested scrollbars a few pixels apart. The dialog is also the flex container
   * so the form is sized against its content box - give the form its own dvh
   * cap instead and the dialog's 1px borders push the footer's border out of
   * view. */
  overflow: hidden;
  padding: 0;
  border: 1px solid var(--hair);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font-family: var(--font);
}
/* Scoped to [open], and it matters. An author `display` on the bare selector
 * beats the UA's `dialog:not([open]) { display: none }` - author origin wins over
 * UA regardless of specificity - so a closed dialog stays laid out. That put the
 * whole settings form in the document below the fold and made the board
 * scrollable, which is invisible until someone scrolls the wall. */
.settings[open] { display: flex; flex-direction: column; }
.settings::backdrop { background: rgba(0, 0, 0, 0.72); }
.settings form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }

.set-head { padding: 2rem 2.2rem 1.2rem; border-bottom: 1px solid var(--hair); }
.set-head h2 { margin: 0; font-size: 2.4rem; font-weight: 650; }
.set-scope { margin: 0.6rem 0 0; font-size: 1.3rem; color: var(--ink-3); max-width: 60rem; }
/* Amber when a save would NOT reach the other screens. The line that says who a
 * change affects is the one line on this dialog that must not be missed. */
.set-scope[data-level="warn"] { color: var(--warning); }

/* The password section, revealed only after the server asks. Bordered and tinted
 * so it reads as a demand from the server rather than one more optional field. */
.set-section--auth {
  border: 1px solid color-mix(in srgb, var(--warning) 45%, transparent);
  border-radius: 0.6rem;
  padding: 1.4rem 1.6rem;
  background: color-mix(in srgb, var(--warning) 7%, transparent);
}
/* Author-origin `display` beats the UA rule for [hidden], and this dialog has been
 * bitten by exactly that twice - see the notes on .banner and .settings[open]. The
 * section is a plain block, so nothing here sets display; this is the guard that
 * keeps it that way if somebody later makes it a grid. */
.set-section--auth[hidden] { display: none; }
/* Its own border already separates it, so it does not need the divider rule that
 * sits between every other pair of sections. */
.set-section--auth + .set-section,
.set-section + .set-section--auth { border-top: 0; padding-top: 0; }

.set-body { padding: 1.6rem 2.2rem; overflow-y: auto; }
.set-section + .set-section { margin-top: 2.2rem; padding-top: 1.8rem; border-top: 1px solid var(--hair); }
.set-section h3 {
  margin: 0 0 1.2rem;
  font-size: 1.4rem;
  font-weight: 650;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex;
  align-items: baseline;
  gap: 0.8rem;
}
.set-tag {
  font-size: 1.1rem;
  letter-spacing: 0.04em;
  text-transform: none;
  color: var(--ink-3);
  border: 1px solid var(--hair);
  border-radius: 0.9rem;
  padding: 0.1rem 0.6rem;
}
.set-note { margin: -0.4rem 0 1.4rem; font-size: 1.3rem; color: var(--ink-3); max-width: 62rem; }

.set-field { display: block; margin-bottom: 1.3rem; }
/* Checkbox first, label after: a tick reads better following its text, and the
 * hint stays on its own line underneath both. */
.set-field--bool {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 0 0.8rem;
  cursor: pointer;
}
.set-field--bool .set-label { margin-bottom: 0; font-weight: 500; }
.set-field--bool .set-hint { grid-column: 2; }
.set-check {
  width: 1.7rem;
  height: 1.7rem;
  margin: 0;
  accent-color: var(--answered);
  cursor: pointer;
}
.set-check:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.set-row { display: flex; gap: 1.6rem; flex-wrap: wrap; }
.set-row .set-field { flex: 1 1 16rem; }
.set-label {
  display: block;
  font-size: 1.35rem;
  font-weight: 600;
  color: var(--ink-2);
  margin-bottom: 0.45rem;
}
.set-hint { display: block; font-size: 1.2rem; color: var(--ink-3); margin-top: 0.4rem; }
.set-input {
  width: 100%;
  padding: 0.75rem 0.9rem;
  font-family: var(--font);
  font-size: 1.5rem;
  color: var(--ink);
  background: var(--page);
  border: 1px solid var(--hair);
  border-radius: 0.5rem;
}
.set-input:focus-visible { outline: 2px solid var(--answered); outline-offset: 1px; }
.set-input--num { width: 12rem; font-variant-numeric: tabular-nums; }

.set-advice { margin: -0.8rem 0 1.2rem; font-size: 1.25rem; line-height: 1.4; max-width: 62rem; }
.set-advice[data-level="warn"]  { color: var(--warning); }
.set-advice[data-level="error"] { color: var(--critical); }

.set-foot {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.4rem 2.2rem;
  border-top: 1px solid var(--hair);
}
.set-status { font-size: 1.3rem; color: var(--ink-3); margin-right: auto; }
.set-status[data-level="error"] { color: var(--critical); }
/* "Saved on this screen only" is a warning, not a failure - the save worked, it
 * just did not go where the person expected. */
.set-status[data-level="warn"] { color: var(--warning); }
.btn {
  font-family: var(--font);
  font-size: 1.4rem;
  font-weight: 600;
  padding: 0.75rem 1.3rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--hair);
  border-radius: 0.5rem;
  cursor: pointer;
}
.btn:hover { border-color: var(--ink-3); }
.btn--primary { background: var(--answered); border-color: var(--answered); }
.btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.gear {
  font-family: var(--font);
  font-size: 1.6rem;
  line-height: 1;
  padding: 0.85rem 1rem;
  color: var(--ink-3);
  background: var(--card);
  border: 1px solid var(--hair);
  border-radius: var(--radius);
  cursor: pointer;
}
.gear:hover { color: var(--ink); border-color: var(--ink-3); }
.gear:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* This screen differs from the shipped defaults. Not a status - it is neither
 * good nor bad - so it gets the series blue rather than a status colour. */
.gear[data-overridden] { position: relative; color: var(--ink-2); }
.gear[data-overridden]::after {
  content: "";
  position: absolute;
  top: 0.35rem;
  right: 0.35rem;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--answered);
}

.skip, .noscript { position: absolute; left: -9999px; }
.noscript { position: static; left: auto; padding: 2rem; font-size: 1.8rem; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}
