/* Thuma Express — status.thumaexpress.com
 *
 * ── No web fonts, on purpose ──────────────────────────────────────────────
 * This page is opened on mobile data by somebody who already thinks we are
 * broken. A font request is one more thing between them and the answer, and
 * on the day it matters most it is a request to a third party that may itself
 * be having the bad day. The system stack renders instantly and everywhere,
 * and it keeps the CSP at `default-src 'self'` with one exception rather than
 * three.
 *
 * ── The status colours were validated, not chosen by eye ───────────────────
 * Light and dark sets were each run through a colour-vision check. Both pass
 * the lightness band, the chroma floor and contrast against their surface.
 *
 * **One pair cannot be fixed with colour and is not pretended otherwise:**
 * degraded (amber) against outage (red) separates by only ~5 ΔE under
 * deuteranopia and ~3 under tritanopia. That is not a palette that needs more
 * tuning — yellow and red converge under tritanopia as a matter of physics,
 * inside the lightness band any usable status palette must sit in.
 *
 * So severity is never carried by colour alone anywhere on this page:
 *
 *   operational   full-height bar, solid          ✓ tick
 *   degraded      HALF-height bar, sitting low    ◑ half glyph
 *   outage        full-height bar, notched top    ✕ cross
 *   maintenance   full-height bar, banded across  ≡ bands
 *   unknown       full-height bar, hollow outline ? hollow
 *
 * Height, fill and glyph each carry the state independently. Turn the page
 * greyscale and every distinction survives. Every state also ships its word.
 */

/* ══════════════════════════════════════════════════════════════════ tokens */

:root {
  color-scheme: light dark;

  --ground:     #FBFAF8;
  --surface:    #FFFFFF;
  --surface-2:  #F4F1EC;
  --ink:        #1A1815;
  --ink-2:      #3B3730;
  --muted:      #6B655D;
  --rule:       #E4DED5;
  --rule-2:     #CDC5B9;
  --accent:     #C2410C;

  /* Marks — validated for light surface */
  --op:         #1F7A4C;
  --deg:        #D69A00;
  --out:        #A32219;
  --mnt:        #3A6EA5;
  --unk:        #B3ABA0;

  /* Text versions. The amber mark is deliberately bright and therefore too
   * low-contrast for type; text gets its own darker step. A mark colour and a
   * text colour are different jobs. */
  --op-t:       #19613C;
  --deg-t:      #8A6200;
  --out-t:      #962017;
  --mnt-t:      #2F5A88;

  --op-bg:      #E7F2EB;
  --deg-bg:     #FBF0D8;
  --out-bg:     #F9E7E4;
  --mnt-bg:     #E8EFF7;
  --unk-bg:     #EFEDE9;

  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --shell: 1080px;
  --radius: 3px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ground:    #141310;
    --surface:   #1C1B17;
    --surface-2: #24221D;
    --ink:       #F3EFE9;
    --ink-2:     #D8D2C8;
    --muted:     #A29B8F;
    --rule:      #302C26;
    --rule-2:    #46413A;
    --accent:    #F26522;

    /* Re-stepped for the dark surface, not inverted. */
    --op:        #22A07E;
    --deg:       #BE8A0E;
    --out:       #DC5340;
    --mnt:       #4C8FE0;
    --unk:       #565049;

    --op-t:      #4FC79E;
    --deg-t:     #D9A62E;
    --out-t:     #EE7C6B;
    --mnt-t:     #7AAEEC;

    --op-bg:     #14261F;
    --deg-bg:    #2A2314;
    --out-bg:    #2E1B18;
    --mnt-bg:    #17222F;
    --unk-bg:    #211F1B;
  }
}

/* ═════════════════════════════════════════════════════════════════ skeleton */

* { box-sizing: border-box; }

/* `hidden` has to beat the display values set below it, or an emptied grid
 * still draws its own border and leaves a rule floating in white space. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

.shell { max-width: var(--shell); margin: 0 auto; padding: 0 24px; }

.skip {
  position: absolute; left: -9999px;
  background: var(--ink); color: var(--ground);
  padding: 10px 16px; z-index: 20;
}
.skip:focus { left: 12px; top: 12px; }

a { color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

h1, h2, h3 { line-height: 1.2; margin: 0; text-wrap: balance; }

/* ═══════════════════════════════════════════════════════════════════ header */

.mast { border-bottom: 1px solid var(--rule); background: var(--surface); }
.mast-in {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding-top: 14px; padding-bottom: 14px;
}
/* The real wordmark, not the name set in the page font.
   Every other surface ships thuma_wordmark.svg and this one set "Thuma
   Express" as plain text beside the icon — so the status page was the only
   place in the estate where the brand was an approximation of itself, drawn
   in whatever weight the body font happened to have. The capsule beside it
   is the same "kind" chip the careers site uses. */
.brand { display: flex; align-items: center; gap: 11px; text-decoration: none; color: var(--ink); }
.wordmark { height: 21px; width: auto; display: block; }
.kind {
  font-size: 10.5px; font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--rule-2); border-radius: 999px; padding: 4px 9px;
  white-space: nowrap;
}

.mast-right { display: flex; align-items: center; gap: 12px; }
.checked { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

.ghost {
  font: inherit; font-size: 13px;
  background: transparent; color: var(--ink-2);
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  padding: 5px 12px; cursor: pointer;
}
.ghost:hover { background: var(--surface-2); }
.ghost.sm { font-size: 12.5px; padding: 4px 10px; }
.ghost[aria-busy="true"] { opacity: .55; cursor: default; }

/* ═══════════════════════════════════════════════════════════════════ banner */

.banner-wrap { background: var(--surface); border-bottom: 1px solid var(--rule); }
.banner {
  display: flex; align-items: center; gap: 18px;
  padding: 34px 0 36px;
}
.banner-txt h1 { font-size: clamp(21px, 3.4vw, 29px); font-weight: 650; letter-spacing: -.018em; }
.banner-txt p { margin: 7px 0 0; color: var(--muted); font-size: 15px; max-width: 62ch; }

.banner-glyph {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 19px; font-weight: 700; color: #fff;
}
.banner.is-op      .banner-glyph { background: var(--op); }
.banner.is-deg     .banner-glyph { background: var(--deg); color: #2A1F00; }
.banner.is-out     .banner-glyph { background: var(--out); }
.banner.is-mnt     .banner-glyph { background: var(--mnt); }
.banner.is-unknown .banner-glyph,
.banner.is-loading .banner-glyph {
  background: transparent; color: var(--muted);
  border: 2px dashed var(--rule-2);
}
.banner.is-out .banner-txt h1 { color: var(--out-t); }
.banner.is-unknown .banner-txt h1 { color: var(--ink); }

/* ═════════════════════════════════════════════════════════════════════ tiles */

main { padding-bottom: 72px; }

section { margin-top: 52px; }

.sec-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 8px;
}
.sec-head h2 { font-size: 17px; font-weight: 650; letter-spacing: -.008em; }
.sec-note { margin: 0 0 20px; color: var(--muted); font-size: 13.5px; max-width: 74ch; }

.tiles {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--radius);
  overflow: hidden; margin-top: 40px;
}
.tile { background: var(--surface); padding: 18px 20px 20px; display: flex; flex-direction: column; gap: 4px; }
.tile-k {
  font-size: 10.5px; font-weight: 650; letter-spacing: .11em;
  text-transform: uppercase; color: var(--muted);
}
.tile-v {
  font-size: 30px; font-weight: 650; letter-spacing: -.025em;
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.tile-n { font-size: 12.5px; color: var(--muted); }
.tile.ok  .tile-v { color: var(--op-t); }
.tile.bad .tile-v { color: var(--out-t); }
.tile.unk .tile-v { color: var(--muted); }

/* ════════════════════════════════════════════════════════════════════ legend */

.legend { display: flex; flex-wrap: wrap; gap: 4px 14px; }
.lg { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }

/* The glyph is the secondary encoding — it must survive greyscale. */
.gl { width: 11px; height: 11px; flex: none; border-radius: 2px; position: relative; }
.gl.op  { background: var(--op); }
.gl.deg { background: var(--deg); height: 6px; align-self: center; }
.gl.out {
  background: var(--out);
  clip-path: polygon(0 0, 38% 0, 50% 26%, 62% 0, 100% 0, 100% 100%, 0 100%);
}
/* A notch, not a hatch. A 45° stripe at a 2px period disappears at the width a
 * daily bar actually gets (~4px on a phone) and reads as an empty slot — which
 * is the one thing maintenance must not be confused with. A band straight
 * across survives any width. */
.gl.mnt {
  background: linear-gradient(var(--mnt) 0 36%, var(--surface) 36% 58%, var(--mnt) 58% 100%);
  box-shadow: inset 0 0 0 1px var(--mnt);
}
.gl.unk { background: transparent; border: 1px dashed var(--rule-2); }

/* ════════════════════════════════════════════════════════════════ components */

.components {
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--surface); overflow: hidden;
}
.comp { padding: 18px 20px 16px; border-bottom: 1px solid var(--rule); position: relative; }
.comp:last-child { border-bottom: none; }

.comp-top {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
}
.comp-name { font-size: 15.5px; font-weight: 600; letter-spacing: -.006em; }
.comp-blurb { font-size: 13px; color: var(--muted); margin-top: 2px; max-width: 58ch; }

.badges { display: flex; gap: 6px; margin-top: 7px; flex-wrap: wrap; }
.badge {
  font-size: 10.5px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 2px;
  border: 1px solid var(--rule-2); color: var(--muted);
}
.badge.sla { border-color: var(--rule-2); color: var(--ink-2); }

.comp-state { display: flex; align-items: center; gap: 8px; flex: none; }
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 620; letter-spacing: .03em;
  padding: 4px 10px; border-radius: 2px; white-space: nowrap;
}
.pill.op  { color: var(--op-t);  background: var(--op-bg); }
.pill.deg { color: var(--deg-t); background: var(--deg-bg); }
.pill.out { color: var(--out-t); background: var(--out-bg); }
.pill.mnt { color: var(--mnt-t); background: var(--mnt-bg); }
.pill.unk { color: var(--muted); background: var(--unk-bg); }

.comp-pct {
  font-size: 13px; font-variant-numeric: tabular-nums;
  color: var(--ink-2); font-weight: 600;
}

/* ── the 90-day strip ─────────────────────────────────────────────────────
 * One element per day. Height and fill carry severity as well as colour; see
 * the note at the top of this file. */
.strip {
  display: flex; gap: 2px; align-items: flex-end;
  height: 34px; margin-top: 14px;
  overflow: hidden;
}
.day {
  flex: 1 1 0; min-width: 2px; border-radius: 1px;
  background: var(--op); height: 100%;
  position: relative;
  /* A <button> now, so the browser's own control styling has to go — the
     detail used to live in a `title`, which no phone can show. */
  appearance: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  font: inherit; color: inherit;
}
.day:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.day[aria-expanded="true"] { outline: 2px solid var(--ink); outline-offset: 2px; }

/* The callout a tap opens, anchored to the bar with a caret pointing at it.
   On the .comp card and not inside .strip, which is overflow:hidden and would
   clip it — so the left offset is computed in app.js and clamped to the card,
   because a bar near the right edge would otherwise push this off a phone. */
.daypop {
  position: absolute; z-index: 5;
  max-width: min(320px, calc(100% - 24px));
  padding: 13px 15px 14px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  box-shadow: 0 6px 22px rgba(0, 0, 0, .28);
}
.dp-caret {
  position: absolute; top: -6px; width: 11px; height: 11px;
  margin-left: -5px; transform: rotate(45deg);
  background: var(--surface);
  border-left: 1px solid var(--rule-2); border-top: 1px solid var(--rule-2);
}
.dp-x {
  position: absolute; top: 6px; right: 8px;
  font: inherit; font-size: 18px; line-height: 1;
  background: transparent; color: var(--muted);
  border: 0; padding: 4px 6px; cursor: pointer; border-radius: 4px;
}
.dp-x:hover { color: var(--ink); }
.dp-date { font-size: 14.5px; font-weight: 600; padding-right: 22px; }
.dp-state {
  display: inline-block;
  font-size: 10.5px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 2px;
  background: var(--rule); color: var(--ink);
}
.dp-state.op  { background: var(--op-bg);  color: var(--op-t); }
.dp-state.deg { background: var(--deg-bg); color: var(--deg-t); }
.dp-state.out { background: var(--out-bg); color: var(--out-t); }
.dp-state.mnt { background: var(--mnt-bg); color: var(--mnt-t); }
.dp-figs {
  display: flex; gap: 14px; flex-wrap: wrap;
  margin-top: 6px; font-size: 12.5px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.dp-inc { margin-top: 11px; font-size: 13px; line-height: 1.5; }
.dp-inc b { display: block; margin-top: 4px; }
.dp-inc p { margin: 4px 0 0; color: var(--ink-2); }
.dp-none { margin: 8px 0 0; font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.day.deg { background: var(--deg); height: 52%; }
.day.out {
  background: var(--out);
  clip-path: polygon(0 0, 38% 0, 50% 22%, 62% 0, 100% 0, 100% 100%, 0 100%);
}
.day.mnt {
  background: linear-gradient(var(--mnt) 0 36%, var(--surface) 36% 58%, var(--mnt) 58% 100%);
  box-shadow: inset 0 0 0 1px var(--mnt);
}
.day.unk { background: transparent; box-shadow: inset 0 0 0 1px var(--rule-2); }
.day:hover, .day:focus-visible { filter: brightness(1.12); }

.strip-axis {
  display: flex; justify-content: space-between;
  font-size: 11.5px; color: var(--muted); margin-top: 6px;
}

/* ════════════════════════════════════════════════════════════════════ chart */

.chart-fig { margin: 0; }
.chart-hold { position: relative; background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--radius); padding: 14px 14px 8px; }
.chart-hold svg { display: block; width: 100%; height: auto; }
.chart-cap { font-size: 12.5px; color: var(--muted); margin-top: 10px; }

.gridline { stroke: var(--rule); stroke-width: 1; vector-effect: non-scaling-stroke; }
.refline   { stroke: var(--rule-2); stroke-width: 1; stroke-dasharray: 4 3; vector-effect: non-scaling-stroke; }
.reftext   { fill: var(--muted); font-size: 11px; font-family: var(--sans); }
.axistext  { fill: var(--muted); font-size: 11px; font-family: var(--sans); }
.serie     { fill: none; stroke: var(--op); stroke-width: 2; vector-effect: non-scaling-stroke;
             stroke-linejoin: round; stroke-linecap: round; }
.serie-fill{ fill: var(--op); opacity: .10; }
.endpoint  { fill: var(--op); stroke: var(--surface); stroke-width: 2; }
.crosshair { stroke: var(--ink-2); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.hitzone   { fill: transparent; }

.chart-tip {
  position: absolute; pointer-events: none; opacity: 0;
  background: var(--ink); color: var(--ground);
  font-size: 12.5px; line-height: 1.45;
  padding: 7px 10px; border-radius: var(--radius);
  transform: translate(-50%, -120%); white-space: nowrap; z-index: 4;
  transition: opacity .1s linear;
}
.chart-tip.on { opacity: 1; }
.chart-tip b { font-variant-numeric: tabular-nums; }

.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.table-hold { margin-top: 16px; overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: 13.5px; min-width: 380px; }
th, td { text-align: left; padding: 7px 14px 7px 0; border-bottom: 1px solid var(--rule); }
th { font-size: 11px; text-transform: uppercase; letter-spacing: .09em; color: var(--muted); font-weight: 650; }
td { color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* ═══════════════════════════════════════════════════════════════════ months */

.months { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; }
.month {
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 16px 18px 18px;
}
.month-k { font-size: 12.5px; color: var(--muted); font-weight: 600; }
.month-v {
  font-size: 25px; font-weight: 650; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; margin-top: 2px;
}
.month-n { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.month.met .month-v    { color: var(--op-t); }
.month.missed .month-v { color: var(--out-t); }

.targets { display: flex; gap: 12px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rule); }
.target { font-size: 11.5px; color: var(--muted); display: flex; align-items: center; gap: 5px; }
.target b { color: var(--ink-2); font-variant-numeric: tabular-nums; font-weight: 600; }
.tmark { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.tmark.ok { background: var(--op); }
.tmark.no { background: var(--out); }

/* ════════════════════════════════════════════════════════════════ incidents */

.inc {
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 18px 20px; margin-bottom: 12px;
}
.inc.open { border-left: 3px solid var(--out); }
.inc.open.deg { border-left-color: var(--deg); }
.inc.open.mnt { border-left-color: var(--mnt); }

.inc-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.inc-title { font-size: 15.5px; font-weight: 620; letter-spacing: -.006em; }
.inc-meta { font-size: 12.5px; color: var(--muted); margin-top: 5px; }
.inc-meta .dot { margin: 0 7px; opacity: .5; }
.inc-body { margin: 10px 0 0; font-size: 14.5px; color: var(--ink-2); max-width: 74ch; }

.updates { margin: 14px 0 0; padding: 0 0 0 18px; list-style: none; border-left: 1px solid var(--rule); }
.update { position: relative; padding: 0 0 12px 12px; font-size: 14px; color: var(--ink-2); }
.update:last-child { padding-bottom: 0; }
.update::before {
  content: ""; position: absolute; left: -22px; top: 8px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--rule-2);
}
.update time { display: block; font-size: 12px; color: var(--muted); margin-bottom: 2px; }

.empty {
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 26px 20px; text-align: center;
  color: var(--muted); font-size: 14px;
}

.month-sep {
  font-size: 11px; font-weight: 650; letter-spacing: .11em; text-transform: uppercase;
  color: var(--muted); margin: 26px 0 10px;
}

/* ═══════════════════════════════════════════════════════════════════ footer */

.foot { border-top: 1px solid var(--rule); background: var(--surface); padding: 40px 0 46px; margin-top: 60px; }
.foot p { margin: 0 0 12px; font-size: 13px; color: var(--muted); max-width: 76ch; }
.foot-fine { font-size: 12px; opacity: .85; margin-bottom: 0; }

/* The estate, in columns.
 *
 * `auto-fit` with a 150px floor rather than a fixed column count: the five
 * groups are different lengths and a hard 5-up breaks at exactly the widths
 * a phone in a hand actually is. The newsletter column asks for more room
 * because it holds a sentence and a field, not seven words. */
.foot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 30px 24px;
  padding-bottom: 34px;
  margin-bottom: 26px;
  border-bottom: 1px solid var(--rule);
}
.foot-h {
  font-size: 12px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink); margin: 0 0 12px;
}
.foot-col ul { list-style: none; margin: 0; padding: 0; }
.foot-col li { margin: 0 0 9px; }
.foot-col a {
  font-size: 13.5px; color: var(--ink-2); text-decoration: none;
  /* Underline on hover only. Twenty-eight permanently underlined links is a
   * hedge, and this footer is meant to be scanned. */
}
.foot-col a:hover, .foot-col a:focus-visible {
  color: var(--accent); text-decoration: underline;
}

/* The newsletter column spans two tracks where there is room, because a
 * 150px-wide email field is a field nobody can read what they typed in. */
.foot-news { grid-column: span 2; min-width: 0; }
@media (max-width: 620px) { .foot-news { grid-column: span 1; } }
.foot-news-say { font-size: 13px; margin: 0 0 12px; max-width: 42ch; }
.foot-news-form { display: flex; gap: 8px; flex-wrap: wrap; }
.foot-news-form input {
  flex: 1 1 190px; min-width: 0;
  padding: 9px 11px; font: inherit; font-size: 13.5px;
  color: var(--ink); background: var(--ground);
  border: 1px solid var(--rule-2); border-radius: 7px;
}
.foot-news-form input:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);
}
.foot-news-form button {
  flex: 0 0 auto; padding: 9px 16px; font: inherit; font-size: 13.5px;
  font-weight: 600; color: #fff; background: var(--accent);
  border: 1px solid var(--accent); border-radius: 7px; cursor: pointer;
}
.foot-news-form button:hover { filter: brightness(1.08); }
.foot-news-form button[disabled] { opacity: .55; cursor: default; filter: none; }
.foot-news-note { font-size: 12.5px; margin: 10px 0 0; min-height: 1.2em; }
.foot-news-note.ok { color: var(--op-t); }
.foot-news-note.bad { color: var(--out-t); }
.foot-news-fine { font-size: 11.5px; opacity: .8; margin: 10px 0 0; max-width: 44ch; }
.foot-base p { max-width: 76ch; }
.foot-corp { margin-top: 10px; }

/* The social row, under the legal print. Chips, so the tap target is 34px
 * whatever the mark's own proportions are, and one colour throughout —
 * including LinkedIn, whose pack ships no vector, so their PNG is masked and
 * takes currentColor like the four that are drawn. */
/* 40 and 21 rather than something smaller: LinkedIn's guidelines set a 21px
 * floor for the In-bug in digital, and one mark in a row cannot be a
 * different size from the rest without reading as a mistake. The app's social
 * row landed on the same two numbers for the same reason. */
.foot-social { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 22px; }
.foot-social a {
  width: 40px; height: 40px; border-radius: 999px;
  border: 1px solid var(--rule); background: var(--surface);
  display: grid; place-items: center; color: var(--muted);
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.foot-social a:hover, .foot-social a:focus-visible {
  color: #fff; background: var(--accent); border-color: var(--accent);
}
.foot-social svg { width: 21px; height: 21px; display: block; }
.foot-social .mark-li {
  width: 21px; height: 21px; display: block; background-color: currentColor;
  -webkit-mask: url(/brand/social/linkedin.png) center / contain no-repeat;
          mask: url(/brand/social/linkedin.png) center / contain no-repeat;
}

/* Visually hidden, still read aloud. The email field has a placeholder and
 * no visible label, and a placeholder is not a label — it disappears the
 * moment somebody types, which is exactly when a screen reader user needs it. */
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.noscript { padding: 40px 24px; }
.noscript h2 { font-size: 18px; margin-bottom: 8px; }

/* ═════════════════════════════════════════════════════════════════ responsive */

@media (max-width: 680px) {
  .shell { padding: 0 16px; }
  .banner { padding: 26px 0 28px; gap: 14px; }
  .banner-glyph { width: 34px; height: 34px; font-size: 16px; }
  .comp-top { flex-direction: column; gap: 10px; }
  .strip { height: 28px; gap: 1px; }
  .checked { display: none; }
  /* Two buttons up here now, not one. The chip beside the wordmark is the
     first thing to go: it says STATUS on the status page, and keeping it cost
     the masthead 26 pixels of sideways scroll on a 390px screen. */
  .kind { display: none; }
  .mast-in { gap: 10px; }
  .mast-right { gap: 8px; }
  .ghost { font-size: 12.5px; padding: 5px 10px; }
  section { margin-top: 40px; }
}

/* Very narrow: 90 bars at 1px each is a smear. app.js drops the window to the
 * last 30 days below this width and says so in the axis label. */
@media (max-width: 400px) {
  .strip { gap: 1px; }
}

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

@media print {
  .mast-right, .ghost, .skip { display: none; }
  body { background: #fff; color: #000; }
  .comp, .month, .inc, .chart-hold { break-inside: avoid; }
}


/* ═══════════════════════════════════════════════════════════════ subscribe

   A native <dialog>. The focus trap, the Escape key, the inert background and
   the top-layer stacking all come from the element rather than from JavaScript
   that has to remember them — which matters more here than usual, because the
   person opening this is mid-incident and will not forgive a dialog that eats
   their keyboard.

   On a phone it is a sheet at the bottom of the screen, where a thumb is. The
   pages we looked at centre a small card, which on a 390px screen lands under
   the notch and above the keyboard at the same time. */
.sub {
  position: relative;                    /* the × is positioned against this */
  width: min(452px, calc(100vw - 24px));
  padding: 22px 22px 18px;
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
  box-shadow: 0 18px 60px rgba(0, 0, 0, .4);
}
.sub::backdrop { background: rgba(0, 0, 0, .55); }
.sub h2 { font-size: 19px; letter-spacing: -.012em; padding-right: 44px; }
.sub-lede { margin-top: 6px; font-size: 13.5px; color: var(--muted); line-height: 1.5; }

.sub-x-form { position: absolute; top: 6px; right: 6px; margin: 0; }
.sub-x {
  /* 44 square. It is the control somebody reaches for with a thumb while
     holding the phone one-handed, and a 28px glyph is a miss. */
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  font: inherit; font-size: 22px; line-height: 1;
  background: transparent; color: var(--muted); border: 0;
  cursor: pointer; border-radius: 6px;
}
.sub-x:hover { color: var(--ink); background: var(--surface-2); }
.sub-x:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Labelled, not a row of bare glyphs. A Slack mark beside a Teams mark at
   20px is a guess, and guessing wrong means pasting a webhook URL into the
   wrong renderer. */
/* A grid of three, not a wrapping row.
   Six pills of six different widths wrap to five and one, which reads as a
   mistake rather than a layout — and the orphan moves between five and one
   and four and two as the words change length. Three columns is the same
   shape at every width the dialog has. */
.sub-tabs {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px;
  margin-top: 18px; padding-bottom: 16px;
  border-bottom: 1px solid var(--rule);
}
.sub-tab {
  font: inherit; font-size: 12.5px; font-weight: 550;
  background: transparent; color: var(--ink-2);
  border: 1px solid var(--rule-2); border-radius: 999px;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 6px; min-height: 34px; cursor: pointer; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.sub-tab:hover { color: var(--ink); }
.sub-tab[aria-selected="true"] {
  background: var(--ink); color: var(--surface); border-color: var(--ink);
}
.sub-tab:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* Every tab carries a glyph, and there are two kinds.
   
   Three are OUR icons — email, SMS, webhook — and they follow the tab's own
   colour through `currentColor`, so they invert with the pill when it is
   selected and again with the scheme. Three are somebody else's brand marks —
   WhatsApp, Slack, Teams — and those keep their own colours in every state. A
   brand mark tinted to match our palette is an altered logo, which is the one
   thing every set of brand guidelines says not to do.
   
   Inline SVG rather than <img>, because `currentColor` inside an <img> resolves
   against the image's own document, not this one: the three house glyphs would
   have come out black on the near-black selected pill. It also means there is
   nothing here that can 404. */
.sub-tab-mark { width: 15px; height: 15px; flex: none; display: block; }

.sub-label {
  display: block; margin-top: 18px; margin-bottom: 6px;
  font-size: 11px; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted);
}
.sub-input {
  width: 100%; font: inherit; font-size: 15.5px; color: var(--ink);
  background: var(--surface-2, var(--surface));
  border: 1px solid var(--rule-2); border-radius: var(--radius);
  padding: 11px 12px; min-height: 44px;
}
.sub-input:focus-visible { outline: 2px solid var(--ink); outline-offset: 1px; }
.sub-hint { margin-top: 7px; font-size: 12.5px; color: var(--muted); line-height: 1.5; }

.sub-go {
  width: 100%; margin-top: 16px;
  font: inherit; font-size: 14.5px; font-weight: 600;
  background: var(--ink); color: var(--surface);
  border: 0; border-radius: var(--radius);
  padding: 12px; min-height: 46px; cursor: pointer;
}
.sub-go:disabled { opacity: .55; cursor: default; }
.sub-go:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Result mode: the dialog opened itself off a /confirm or /unsubscribe link,
   so there is nothing to choose and nothing to type. Hiding the controls
   rather than disabling them — a form nobody is meant to fill in is noise on
   a screen somebody opened to read one sentence. */
.sub.is-result .sub-tabs,
.sub.is-result #sub-form,
.sub.is-result .sub-foot { display: none; }
.sub.is-result .sub-note { margin-top: 18px; }

.sub-note {
  margin-top: 14px; padding: 11px 13px; font-size: 13px; line-height: 1.5;
  border-radius: var(--radius); border: 1px solid var(--rule-2);
  background: var(--surface-2, var(--surface));
}
.sub-note.ok  { border-color: var(--op);  color: var(--op-t);  background: var(--op-bg); }
.sub-note.bad { border-color: var(--out); color: var(--out-t); background: var(--out-bg); }

.sub-foot {
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--rule);
  font-size: 12px; color: var(--muted); line-height: 1.5;
}

@media (max-width: 560px) {
  /* A sheet at the bottom, where a thumb is. */
  .sub {
    width: 100vw; max-width: none;
    margin: auto auto 0; border-radius: var(--radius) var(--radius) 0 0;
    border-bottom: 0; padding-bottom: max(18px, env(safe-area-inset-bottom));
  }
}

/* ── the pre-hydration heading ───────────────────────────────────────────
 *
 * `prerender.js` puts a real <h1> and a paragraph into every served page, so
 * a crawler is given prose rather than an empty shell, and `app.js` removes it
 * on the first paint. Between those two moments it is on screen, so it is
 * styled — a flash of unstyled heading is worse than a flash of nothing.
 *
 * Only the state is left out of it. A status figure baked into a file nginx
 * caches would make this page lie, on the one page in the estate whose whole
 * value is that it does not. */
.seo-intro {
  padding: 28px 0 4px;
}
.seo-intro h1 {
  margin: 0 0 8px;
  font-size: 26px;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.seo-intro p {
  margin: 0;
  max-width: 62ch;
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
}
