/*
 * GrapeBeaver Intelligence — design system.
 *
 * Built from the brand guide and the landing mockup. Two rules govern
 * everything here:
 *
 *   1. Calm by default, loud only where severity earns it. Chrome is Ink Navy /
 *      Slate / Paper. Grape marks actions and identity, never status.
 *   2. Amber and Red appear ONLY in severity badges. If you find either
 *      hex outside the .sev-* rules, it is a bug — the whole credibility of the
 *      product rests on urgency having somewhere to go.
 */

/*
 * Themes.
 *
 * Three, chosen per user and stamped on <html data-theme>. Everything below is
 * token-level: a component reads --surface, never a hex, so a new theme is a
 * block of variables rather than a second stylesheet to keep in step.
 *
 * Rule 2 above survives every theme. The severity ramp is the only place amber
 * and red appear, and it is redefined per theme only to stay legible on that
 * theme's ground — never to make anything else louder. "Colorful" saturates the
 * grape/teal/bark family precisely because those are the colours that are *not*
 * spoken for.
 */

:root, [data-theme="light"] {
  /* Chart intensity. Grape, because amber and red are severity's — see the
     header. Five steps rather than a gradient: a reader comparing two cells
     needs to see that they differ, not estimate by how much. */
  --heat-0: #EEF2F6;
  --heat-1: #DCD2E3;
  --heat-2: #B99FC7;
  --heat-3: #8A5FA3;
  --heat-4: #5B2C6F;
  --heat-line: #C9D2DB;

  --ink: #0C2A42;             /* body text */
  --deep: #0C2A42;            /* brand navy used as a *fill*; fixed in every theme */
  --on-deep: #FFFFFF;         /* text on that fill */
  --grape: #5B2C6F;
  --grape-dark: #48225A;
  --bark: #6B4226;
  --teal: #0F6E56;
  --slate: #5F6B76;
  --hairline: #DCE2E8;

  --ground: #F5F7FA;          /* the page behind everything */
  --surface: #FFFFFF;         /* cards, inputs, tables */
  --surface-2: #EEF2F6;       /* inset chrome: toolbars, shot frames */
  --surface-hover: #FCFDFE;
  --hairline-2: #C9D2DB;
  --on-accent: #FFFFFF;       /* text on grape — fixed, not a surface */
  --bark-tint: #F4EFEA;
  --bark-line: #E4D8CB;
  --grape-wash: rgba(91, 44, 111, 0.05);

  /* Severity only. Never use these for anything else. */
  --sev-low: #0F6E56;
  --sev-low-bg: #E6F2EE;
  --sev-medium: #8A560F;      /* darkened from #BA7517 for AA on tint */
  --sev-medium-bg: #FBF2E3;
  --sev-high: #A32D2D;
  --sev-high-bg: #FAEBEB;

  /* The sticky nav's height, named because three rules depend on it and they
     were three separate magic numbers. An in-page anchor has to clear it, the
     guide's contents list has to stick below it, and the bar itself has to be
     it — and the first of those was wrong: every `#anchor` in the app landed
     *under* the nav, so a reader who clicked a section arrived with its heading
     hidden and had to scroll up to start. */
  --nav-height: 60px;
  --radius: 10px;
  /* The briefing's figure column. One number, because the whole argument for
     the rail is that every figure on the page shares a width and an edge. */
  --rail: 300px;
  --radius-lg: 14px;
  --shadow-focus: 0 0 0 3px rgba(91, 44, 111, 0.18);

  --font-display: 'Space Grotesk', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-body: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}

/*
 * Dark. Ink Navy taken all the way down rather than to neutral grey, so it
 * still reads as the same product. Grape and Teal are lifted, because the
 * light-theme values fail contrast on a dark ground — the same hue, not a
 * different identity.
 */
[data-theme="dark"] {
  --heat-0: #16222E;
  --heat-1: #2E2540;
  --heat-2: #4A3A63;
  --heat-3: #7B62A0;
  --heat-4: #C0A0D2;
  --heat-line: #2C3D4E;

  --ink: #E4ECF2;             /* text, not chrome: the name is the role */
  --deep: #061019;            /* deepened so it still reads as a panel on a dark ground */
  --on-deep: #FFFFFF;
  --grape: #C0A0D2;
  --grape-dark: #D3BBE0;
  --bark: #D2A87F;
  --teal: #58C5A5;
  --slate: #9DAFBD;
  --hairline: #27394A;

  --ground: #0A141D;
  --surface: #111F2B;
  --surface-2: #182838;
  --surface-hover: #162734;
  --hairline-2: #31465A;
  --on-accent: #14202B;       /* dark text on a light-grape button */
  --bark-tint: #241C14;
  --bark-line: #3C2E20;
  --grape-wash: rgba(192, 160, 210, 0.12);

  --sev-low: #6BD6B4;
  --sev-low-bg: #0E2A23;
  --sev-medium: #E6B45C;
  --sev-medium-bg: #2C2413;
  --sev-high: #F2908F;
  --sev-high-bg: #33191B;

  --shadow-focus: 0 0 0 3px rgba(192, 160, 210, 0.28);
}

/*
 * Colorful. A light theme that spends its saturation on the brand family —
 * a grape-washed ground, deeper plum text, stronger teal and bark. Severity is
 * left exactly where it is: making the chrome louder is only safe as long as
 * the one thing that must shout still shouts louder.
 */
[data-theme="colorful"] {
  --heat-0: #F0E5F8;
  --heat-1: #E0CBEF;
  --heat-2: #C39FDC;
  --heat-3: #9460BE;
  --heat-4: #6A2E92;
  --heat-line: #D8C2E8;

  --ink: #241033;
  --deep: #1E0C2C;
  --on-deep: #FFFFFF;
  --grape: #7A2E9E;
  --grape-dark: #5E2079;
  --bark: #9A5B2A;
  --teal: #0C8467;
  --slate: #6A5476;
  --hairline: #E0D2EC;

  --ground: #F7F1FB;
  --surface: #FFFFFF;
  --surface-2: #F0E5F8;
  --surface-hover: #FBF6FE;
  --hairline-2: #CDB6E0;
  --on-accent: #FFFFFF;
  --bark-tint: #FBEFE3;
  --bark-line: #EBD6BF;
  --grape-wash: rgba(122, 46, 158, 0.07);

  --sev-low: #0F6E56;
  --sev-low-bg: #E1F3ED;
  --sev-medium: #8A560F;
  --sev-medium-bg: #FBF0DE;
  --sev-high: #A32D2D;
  --sev-high-bg: #FBE7E7;

  --shadow-focus: 0 0 0 3px rgba(122, 46, 158, 0.22);
}

*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, h3, h4, p, ul, ol, figure, blockquote { margin: 0; padding: 0; }

body {
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--ground);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, .display {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

a { color: var(--grape); text-decoration: none; }
a:hover { text-decoration: underline; }
ul { list-style: none; }

/* Visible focus everywhere — this audience includes teams with their own
   accessibility requirements as part of vendor review. */
:focus-visible {
  outline: 2px solid var(--grape);
  outline-offset: 2px;
  border-radius: 4px;
}

.mono { font-family: var(--font-mono); }
.muted { color: var(--slate); }
.small { font-size: 13px; }
.tiny { font-size: 12px; }
.strong { font-weight: 600; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--deep); color: var(--on-deep); padding: 10px 16px;
}
.skip-link:focus { left: 8px; top: 8px; }

.wrap { max-width: 1120px; margin: 0 auto; padding: 0 32px; }
.wrap-narrow { max-width: 760px; margin: 0 auto; padding: 0 32px; }

/* ---------------------------------------------------------------- Buttons */

.btn {
  display: inline-block;
  padding: 10px 20px;
  border-radius: 8px;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  cursor: pointer;
  border: 0.5px solid var(--hairline);
  background: var(--surface);
  color: var(--ink);
  text-align: center;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}
.btn:hover { text-decoration: none; border-color: var(--slate); }
.btn-primary { background: var(--grape); color: var(--on-accent); border-color: var(--grape); }
.btn-primary:hover { background: var(--grape-dark); border-color: var(--grape-dark); }
.btn-secondary { background: transparent; }
.btn-quiet { background: transparent; border-color: transparent; color: var(--slate); padding: 6px 10px; }
.btn-quiet:hover { color: var(--ink); border-color: var(--hairline); }
.btn-danger { color: var(--sev-high); border-color: var(--hairline); }
.btn-danger:hover { border-color: var(--sev-high); }
.btn-lg { padding: 13px 26px; font-size: 15px; }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.btn-block { width: 100%; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }

/* ----------------------------------------------------------------- Forms */

label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 6px; }
input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="url"], input[type="search"], select, textarea {
  width: 100%;
  padding: 10px 12px;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 8px;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--grape);
  box-shadow: var(--shadow-focus);
}
textarea { resize: vertical; min-height: 80px; }
.field { margin-bottom: 18px; }
.field .helptext { display: block; font-size: 13px; color: var(--slate); margin-top: 6px; }
.field .errorlist { list-style: none; color: var(--sev-high); font-size: 13px; margin-top: 6px; }
.field-inline { display: flex; gap: 10px; align-items: center; }
.field-inline label { margin: 0; font-weight: 400; }
.checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px 20px;
}
.checkbox-grid li, .checkbox-row { display: flex; gap: 8px; align-items: flex-start; }
.checkbox-grid input, .checkbox-row input { margin-top: 4px; flex-shrink: 0; }
.form-actions { display: flex; gap: 12px; align-items: center; margin-top: 24px; }

/* ----------------------------------------------------------------- Cards */

.card {
  background: var(--surface);
  border: 0.5px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: 24px;
}
.card + .card { margin-top: 16px; }
.card-header {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; margin-bottom: 16px;
}
.card-title { font-size: 17px; }
.card-tight { padding: 18px 20px; }

.panel-grid { display: grid; gap: 16px; }
.panel-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.panel-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.panel-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }

.stat { background: var(--surface); border: 0.5px solid var(--hairline); border-radius: var(--radius); padding: 18px 20px; }
.stat-value { font-family: var(--font-display); font-size: 28px; font-weight: 600; line-height: 1.1; }
.stat-label { font-size: 13px; color: var(--slate); margin-top: 4px; }

/* -------------------------------------------------------- Severity badges */
/*
 * Text on a light tint, never a solid fill with white text — a solid badge is
 * too loud for a product whose default state is calm. Every badge carries a
 * text label, never colour alone, for colourblind readers.
 */

.sev {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px; border-radius: 20px;
  font-size: 12px; font-weight: 600; letter-spacing: 0.01em;
  white-space: nowrap;
}
.sev::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.sev-low { color: var(--sev-low); background: var(--sev-low-bg); }
.sev-medium { color: var(--sev-medium); background: var(--sev-medium-bg); }
.sev-high, .sev-critical { color: var(--sev-high); background: var(--sev-high-bg); }

.tag {
  display: inline-block; padding: 2px 8px; border-radius: 6px;
  font-family: var(--font-mono); font-size: 11px;
  color: var(--slate); background: var(--ground); border: 0.5px solid var(--hairline);
}
.tag-grape { color: var(--grape); border-color: rgba(91, 44, 111, 0.25); background: var(--grape-wash); }
/* State tags reuse the severity ramp: an operator already reads that palette as
   fine / worth a look / act now, and a live-key warning wants exactly that. */
.tag-green { color: var(--sev-low); background: var(--sev-low-bg); border-color: rgba(15, 110, 86, 0.25); }
.tag-amber { color: var(--sev-medium); background: var(--sev-medium-bg); border-color: rgba(138, 86, 15, 0.25); }
.tag-red { color: var(--sev-high); background: var(--sev-high-bg); border-color: rgba(163, 45, 45, 0.3); }
.tag-list { display: flex; flex-wrap: wrap; gap: 6px; }

/* ------------------------------------------------------------------ Nav */

/* Every in-page anchor clears the sticky nav, once, here.
   `scroll-padding-top` on the scrolling element is the mechanism for a fixed
   header — it applies to every anchor target in the app rather than needing a
   `scroll-margin-top` remembered on each one, which is the version that is
   right until somebody adds the next `id`. */
html { scroll-padding-top: calc(var(--nav-height) + 16px); }

.app-nav {
  background: var(--surface);
  border-bottom: 0.5px solid var(--hairline);
  position: sticky; top: 0; z-index: 20;
}
.app-nav-inner {
  max-width: 1280px; margin: 0 auto; padding: 0 24px;
  display: flex; align-items: center; gap: 28px; height: var(--nav-height);
}
.logo { display: flex; align-items: center; gap: 10px; font-family: var(--font-display); font-weight: 600; font-size: 18px; color: var(--ink); }
.logo:hover { text-decoration: none; }
.nav-links { display: flex; align-items: center; gap: 4px; flex: 1; }
.nav-links a {
  padding: 8px 12px; border-radius: 8px;
  font-size: 14px; color: var(--slate);
}
.nav-links a:hover { color: var(--ink); background: var(--ground); text-decoration: none; }
.nav-links a.active { color: var(--grape); font-weight: 500; background: var(--grape-wash); }
/* Sits between the links and the account menu, shrinking before either does —
   it is a way in, not the point of the bar. */
.nav-search { flex: 0 1 240px; min-width: 0; margin-left: auto; }
.nav-search input {
  width: 100%; padding: 6px 10px; font-size: 13px;
  border: 0.5px solid var(--hairline); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
}
.nav-search input::placeholder { color: var(--slate); }

.nav-right { display: flex; align-items: center; gap: 12px; }
.nav-user { font-size: 13px; color: var(--slate); }

.page { max-width: 1280px; margin: 0 auto; padding: 32px 24px 80px; }
/*
 * The page header is one panel, on inset chrome rather than on the page
 * ground, so every screen opens by saying what it is before it starts showing
 * rows.
 *
 * Grey, not tan. --bark-tint is the obvious warm candidate and it is spoken
 * for: it means *simulated* on the banner and the plan card, so putting it
 * behind every page title would spend a signal that currently costs nothing to
 * read. Amber was never available — that is severity's, and only severity's.
 *
 * Two lines live in here and they are not the same thing. `.page-meta` is
 * status — the plan, the counts, the dates — and belongs beside the title.
 * `.page-intro` is the explanation of what the page is for, which is the part
 * a reader needs once and then never again, so it is set quieter and wider.
 */
.page-header {
  margin-bottom: 28px;
  padding: 22px 26px;
  background: var(--surface-2);
  border: 0.5px solid var(--hairline);
  border-radius: var(--radius-lg);
}
.page-header h1 { font-size: 28px; margin-bottom: 6px; }
.page-header p { color: var(--slate); font-size: 15px; }
/*
 * A readable measure, and wide enough not to look truncated. Prose set across
 * the full 1280px page is hard to track back from the end of one line to the
 * start of the next; 84ch is about as wide as that stays comfortable.
 */
.page-intro { max-width: 84ch; line-height: 1.55; }
.page-meta { font-size: 14px; }
.page-meta + .page-intro { margin-top: 8px; }

.page-header-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; flex-wrap: wrap; }
/*
 * Without this the text column is `flex: 0 1 auto`, so its width comes out of
 * shrink arithmetic weighted by content size — the description wrapped early
 * and looked like a forced break rather than a measure. Give it the row and
 * let the actions take what they need.
 */
.page-header-row > :first-child { flex: 1 1 420px; min-width: 0; }
.page-header-row > :first-child + * { flex: 0 0 auto; }

/* ---------------------------------------------------------------- Banners */

.banner {
  padding: 12px 20px; font-size: 14px;
  border-bottom: 0.5px solid var(--hairline);
  display: flex; gap: 12px; align-items: center; justify-content: center;
  flex-wrap: wrap;
}
/* Simulation banner uses Bark, not Amber — amber is reserved for severity. */
.banner-simulation { background: var(--bark-tint); color: var(--bark); }
.banner-simulation .mono { font-size: 12px; }

.messages { margin-bottom: 24px; display: grid; gap: 10px; }
.message {
  padding: 12px 16px; border-radius: var(--radius); font-size: 14px;
  background: var(--surface); border: 0.5px solid var(--hairline);
  border-left: 3px solid var(--slate);
}
.message-success { border-left-color: var(--teal); }
.message-error { border-left-color: var(--sev-high); }
.message-warning { border-left-color: var(--bark); }
.message-info { border-left-color: var(--grape); }

/* ------------------------------------------------------------------ Feed */

.feed-list { display: grid; gap: 1px; background: var(--hairline); border: 0.5px solid var(--hairline); border-radius: var(--radius-lg); overflow: hidden; }
.feed-item { background: var(--surface); padding: 20px 24px; display: block; color: inherit; }
.feed-item:hover { background: var(--surface-hover); text-decoration: none; }
.feed-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.feed-source { font-family: var(--font-mono); font-size: 11px; color: var(--slate); text-transform: uppercase; letter-spacing: 0.04em; }
.feed-title { font-family: var(--font-display); font-size: 16px; font-weight: 500; margin-bottom: 6px; line-height: 1.35; }
/* A finding inside a document-impact card: which document, which clause, what
   the agent suggested, and the reader's own answer. Indented under the advisory
   rather than tabulated beside it — the card is the advisory, and these are the
   places it landed. */
.finding-list { display: flex; flex-direction: column; gap: 14px; margin-top: 14px; }
.finding { padding-left: 12px; border-left: 2px solid var(--hairline); }
.finding-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
                font-size: 13.5px; font-weight: 500; }
.finding-head a { font-weight: 500; }
.finding-decide { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
                  margin-top: 8px; }
.finding .richtext { margin-top: 4px; }

/* The stored copy on a feed row: an affordance, not a section. Quiet enough
   that a row with three attachments does not out-shout its own headline. */
.feed-doc { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
            margin: 8px 0 0; font-size: 13px; }
.feed-doc a { font-weight: 500; }

.feed-summary { font-size: 14px; color: var(--slate); }
/* The new-tab mark on a feed headline. Quiet until the row is hovered — it is a
   reassurance about what the click does, not a decoration on every title. */
.feed-title-tab { margin-left: 5px; font-size: 11px; color: var(--hairline-2); }
.impact-row:hover .feed-title-tab, .feed-title:hover .feed-title-tab { color: var(--grape); }
.feed-matches { margin-top: 10px; font-size: 13px; color: var(--teal); }

/* The three layers a Business digest adds beneath an item, on /sample-digest/.
   Colour carries meaning here and stays inside the rule: teal for the portfolio
   match, because rule-based matching is the deterministic half of the pipeline;
   grape for the document findings, because that is the brand accent and these
   are advisory prose; slate for the retrieval note. Amber and red are severity
   badges and appear nowhere else — tests/test_themes.py checks by hue. */
.sample-line {
  margin-top: 10px; padding-left: 12px; font-size: 13px; line-height: 1.6;
  border-left: 2px solid var(--hairline);
}
.sample-line-match { color: var(--teal); border-left-color: var(--teal); }
.sample-line-docs { color: var(--grape); border-left-color: var(--grape); }
.sample-line-source { color: var(--slate); }

/* Review queue bulk selection. The checkbox is a sibling of the row link, not a
   child: a checkbox inside an anchor is swallowed by the link before it can
   toggle. */
.bulk-row { display: flex; align-items: flex-start; gap: 12px; background: var(--surface); }
.bulk-row .feed-item { flex: 1; padding-left: 0; }
.bulk-check { width: 16px; height: 16px; margin: 22px 0 0 20px; flex: none; accent-color: var(--grape); }
.bulk-check:disabled { opacity: 0.35; }
.bulk-bar { margin-top: 16px; }
.bulk-bar .field { margin-bottom: 12px; }

/* Feed read state. Unread is the brand accent, never amber or red — those are
   severity and appear nowhere else (tests/test_themes.py checks by hue). The
   dot never travels alone: it is always beside the word, because a colour-only
   status indicator is unreadable to a reader who cannot distinguish it. */
.read-tabs { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.feed-item-unread { border-left: 3px solid var(--grape); }

/*
 * A merged row: the advisory on the left, where you are with it on the right.
 *
 * It is a grid rather than an anchor, which the feed row used to be. A form
 * cannot live inside an anchor, and the whole point of the merge is that the
 * status is settable without opening the item — so the title carries the link
 * and the row carries the controls.
 */
/* One column. The decision used to be a right-hand rail, which put the buttons
   level with the headline — so somebody read the summary and the control they
   wanted was back up beside the title. Reading ends at the bottom of the card. */
.impact-row { background: var(--surface); padding: 20px 24px; }
.impact-row:hover { background: var(--surface-hover); }
.impact-main { min-width: 0; }
.impact-main .feed-title { display: block; color: inherit; }
.impact-main .feed-title:hover { color: var(--grape); }
.impact-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
                  margin-top: 14px; padding-top: 12px; border-top: 0.5px solid var(--hairline); }
.impact-actions-link { margin-left: auto; }

/* ---------------------------------------------------------------------------
   The review overlay.

   A native <dialog>, so focus trapping, Escape and the backdrop come from the
   browser rather than from three hundred lines that get them subtly wrong. The
   footer is sticky inside it: an assessment can run long, and a decision that
   scrolls out of reach is one somebody abandons.
   --------------------------------------------------------------------------- */
.review-dialog { width: min(760px, calc(100vw - 32px)); max-height: min(86vh, 900px);
                 padding: 0; border: 0.5px solid var(--hairline);
                 border-radius: var(--radius-lg); background: var(--surface);
                 color: var(--ink); overflow: hidden; }
.review-dialog::backdrop { background: rgba(20, 16, 28, 0.55); }
.review-body { max-height: min(86vh, 900px); overflow-y: auto; }
.review-close { position: absolute; top: 10px; right: 12px; z-index: 2; width: 28px;
                height: 28px; border: 0; border-radius: 50%; cursor: pointer;
                background: var(--surface-hover); color: var(--slate); font-size: 18px;
                line-height: 1; }
.review-close:hover { color: var(--grape); }

/* The row's one control, kept out of the reading line.

   A calendar row is a date, what it is and where it came from, read left to
   right; the action belongs after that rather than inside it. Quiet until the
   row is hovered or the link is focused, because thirty rows each shouting an
   action is a page of buttons with a calendar behind it — but never hidden from
   the keyboard, which is what `:focus-within` is doing here. */
.cal-actions { align-self: center; margin-left: auto; padding-left: 14px;
               opacity: 0; transition: opacity 120ms; }
.cal-row:hover .cal-actions,
.cal-row:focus-within .cal-actions { opacity: 1; }
/* Coarse pointers get no hover, so it is simply always there. */
@media (hover: none) { .cal-actions { opacity: 1; } }

/* ---------------------------------------------------------------------------
   Sharing an advisory.

   The same overlay machinery as the review dialog, narrower — this is a form
   with two fields, and a 760px box around it would say it is a bigger act than
   it is. The dialog is padded here rather than in the body, because the fetched
   fragment is a form and not a document.
   --------------------------------------------------------------------------- */
.form-dialog { width: min(520px, calc(100vw - 32px)); max-height: min(86vh, 800px);
                padding: 24px; border: 0.5px solid var(--hairline);
                border-radius: var(--radius-lg); background: var(--surface);
                color: var(--ink); overflow-y: auto; }
.form-dialog::backdrop { background: rgba(20, 16, 28, 0.55); }
.form-dialog-title { font-family: var(--font-display); font-size: 19px; font-weight: 600;
                      margin: 0 28px 0 0; }
.form-dialog-note { margin-top: 6px; }
.form-dialog [data-form-dialog-body] { margin-top: 16px; }

/* The advisory being shared, so somebody who opened the wrong row can see it
   before they type an address rather than after they press send. */
.share-item { padding: 12px 14px; margin-bottom: 18px; border-radius: var(--radius);
              background: var(--surface-2); border-left: 3px solid var(--grape); }
.share-item-title { margin: 4px 0 0; font-size: 14px; font-weight: 500; line-height: 1.4; }

/* What travels and what does not, said before sending. A sender who believes
   they attached the guidance and did not is the failure worth designing
   against — it is silent at both ends. */
.share-docs { padding: 14px; border-radius: var(--radius); background: var(--surface-2);
              margin-bottom: 18px; }
.share-docs-head { font-weight: 600; color: var(--slate); text-transform: uppercase;
                   letter-spacing: 0.06em; margin: 0 0 6px; }
.share-docs-head + .share-doc-list { margin-bottom: 12px; }
.share-doc-list { list-style: none; margin: 0; padding: 0; font-size: 13px; }
.share-doc-list li { display: flex; justify-content: space-between; gap: 12px;
                     padding: 3px 0; }
.share-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 4px; }

/* A refusal, not a crash: a bad address or a reached limit. Slate rather than a
   severity colour — amber and red are the item-severity ramp and appear nowhere else,
   which tests/test_themes.py enforces by hue. */
.share-error { margin: 0 0 14px; padding: 10px 12px; border-radius: var(--radius);
               background: var(--surface-2); border-left: 3px solid var(--bark);
               font-size: 13px; color: var(--ink); }
.share-link { margin-top: 12px; font-size: 13px; }

/* What this overlay is, before what it says. It opened straight onto a severity
   badge and a headline — the same shape as a feed row — so it was not obvious it
   was a different thing, or that the buttons at the foot belonged to it. */
.panel-intro { padding: 20px 24px 0; }
.panel-intro-title { font-size: 12px; letter-spacing: 0.07em; text-transform: uppercase;
                     font-weight: 600; color: var(--grape); margin: 0; }
.panel-intro p { margin: 4px 0 0; }

.panel-head { padding: 14px 24px 0; }
/* Sharing sits with the advisory, not with the decision: the footer is three
   actions widening in commitment and a share is not a point on that axis. */
.panel-share { margin-top: 8px; }
.panel-title { font-family: var(--font-display); font-size: 20px; font-weight: 500;
               line-height: 1.3; margin: 10px 0 0; }
.panel-body { padding: 16px 24px 24px; display: flex; flex-direction: column; gap: 14px; }

/* Sticky, so the decision is reachable from anywhere in a long assessment. */
.panel-foot { position: sticky; bottom: 0; display: flex; align-items: center; gap: 12px;
              flex-wrap: wrap; padding: 12px 24px; background: var(--surface);
              border-top: 0.5px solid var(--hairline); }
.panel-foot-actions { display: flex; gap: 8px; margin-left: auto; }

@media (max-width: 640px) {
  .review-dialog { width: 100vw; max-width: 100vw; max-height: 100vh; border-radius: 0; }
  .panel-foot-actions { margin-left: 0; width: 100%; }
}

/* One row per matched device, inside the overlay. Each is set on its own; the
   footer applies one status to all of them. */
.match-rows { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.match-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.match-what { display: flex; flex-direction: column; gap: 2px; flex: 1 1 200px; min-width: 0; }
.match-row .chip-row { margin-left: auto; }
.chip-tight { padding: 2px 7px; font-size: 11px; }
.chip-row.is-busy { opacity: 0.55; pointer-events: none; }

@media (max-width: 640px) {
  .match-row .chip-row { margin-left: 0; }
}

/* One click per decision, on the feed card. A dropdown made the commonest act
   on the page into pick-then-confirm; four words are four buttons. */
/* A decided row leaves rather than blinking out. The height is fixed by script
   first, because a card in normal flow has no measurable height to animate from. */
.impact-row.is-leaving { height: 0 !important; opacity: 0; padding-top: 0; padding-bottom: 0;
                         overflow: hidden; transition: height 180ms ease, opacity 140ms ease,
                         padding 180ms ease; }
@media (prefers-reduced-motion: reduce) {
  .impact-row.is-leaving { transition: none; }
}
/* While the decision is in flight. Not disabled — a button that stops being a
   button under the cursor is worse than one that looks busy. */
.chip-row.is-busy { opacity: 0.55; pointer-events: none; }

.chip-row { display: flex; flex-wrap: wrap; gap: 5px; }
.chip { padding: 3px 9px; border-radius: 12px; border: 0.5px solid var(--hairline-2);
        background: var(--surface); color: var(--slate); font-size: 12px; cursor: pointer;
        font-family: inherit; }
.chip:hover { border-color: var(--grape); color: var(--grape); }
.chip-on { background: var(--grape); border-color: var(--grape); color: var(--on-accent); }
.impact-status { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--slate); }
/* Open work is the brand accent, not the severity red: the severity badge is
   already on this row and two loud colours on one line compete. */
.impact-status-open { color: var(--grape); font-weight: 600; }
.impact-status-none { color: var(--hairline-2); }

@media (max-width: 820px) {
  /* The link stops being pushed to the far edge once the row wraps. */
  .impact-actions-link { margin-left: 0; }
}
.unread-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--grape); display: inline-block; flex: none;
}
/* The obligations calendar.
 *
 * Colour here answers "what does this mean to us", not "how severe is it":
 * slate for a date that does not touch this organization, the severity ramp's
 * red for one that does and is unfinished, its green for one that was dealt
 * with. Reusing --sev-high and --sev-low keeps the palette to the hues it
 * already has — no new warm token enters, which is what the colour rule is
 * protecting — and calendar rows carry no severity badge, so the two never
 * compete on the same line. The written label is always there; colour is the
 * second channel, never the only one. */
/* ---------------------------------------------------------------------------
   The block calendar: thirty rolling days as weeks.

   A traditional grid rather than the strip of tiles this replaced — a row of
   ten days is a row, and a reader looking for "what is coming" reads a calendar
   shape faster than they read a queue. Fixed seven columns so the weekday
   headings mean something; cells outside the window are blank rather than
   filled with last week's dates.

   Colour is the same four states the rows below carry, so the picture and the
   list cannot say different things about one day.
   --------------------------------------------------------------------------- */
.cal-block { margin-top: 14px; }
.cal-block-head, .cal-block-week {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px;
}
.cal-block-head { margin-bottom: 6px; }
.cal-block-head span {
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--slate); text-align: center;
}
.cal-block-week + .cal-block-week { margin-top: 6px; }

.cal-cell {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; min-height: 56px; padding: 6px 4px; border-radius: var(--radius);
  border: 0.5px solid var(--hairline); background: var(--surface);
  color: var(--ink); text-decoration: none;
}
a.cal-cell:hover { border-color: var(--grape); text-decoration: none; }
.cal-cell-day { font-family: var(--font-display); font-size: 15px; font-weight: 600; }
.cal-cell-count {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  padding: 0 6px; border-radius: 999px; background: var(--surface-2);
}
/* A day with nothing on it is drawn and stays quiet — the gaps are what make a
   clustered week look clustered. */
.cal-cell-empty { color: var(--slate); background: none; }
.cal-cell-weekend { background: var(--surface-2); }
.cal-cell-blank { border: 0; background: none; min-height: 0; }
.cal-cell-today { border-color: var(--grape); border-width: 1.5px; }

/* The same four states, on the cell's edge rather than its whole face: a grid
   of solid red squares is a page nobody looks at twice. */
.cal-cell.cal-impacting_open { border-color: var(--sev-high); }
.cal-cell.cal-impacting_open .cal-cell-count { background: var(--sev-high); color: #fff; }
.cal-cell.cal-resolved { border-color: var(--sev-low); }
.cal-cell.cal-regulatory { border-color: var(--grape); }
/* An informational date takes the default cell border: a webinar is not a
   deadline and must not draw the eye the way an open obligation does. */
.cal-cell.cal-informational { border-color: var(--hairline-2); }

.cal-agenda { display: flex; flex-direction: column; gap: 22px; margin-top: 14px; }
.cal-agenda-date {
  font-family: var(--font-display); font-size: 14px; font-weight: 600;
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding-bottom: 6px; border-bottom: 0.5px solid var(--hairline); margin-bottom: 4px;
}
/* Folded, not dropped: a date carrying a dozen entries buries the four dates
   after it, and `<details>` needs no script, prints open and is findable. */
.cal-more > summary, .cal-passed > summary {
  cursor: pointer; font-size: 13px; color: var(--grape); padding: 6px 0;
}
.cal-passed { padding: 16px 20px; }
/* The rest of the thirty days, folded under the four that are written out. It
   is a landing point as well as a control: a grid cell links to a date's own
   heading, and where that heading is inside this fold, a browser without
   `<details>` fragment expansion scrolls to the summary instead — the right
   place, saying how much is behind it, rather than nowhere. */
.cal-more-rest { margin-top: 18px; border-top: 0.5px solid var(--hairline); }
.cal-more-rest > summary { font-weight: 500; }

/* The three figures. `.stat` is the platform's counter and is reused unchanged;
   only the two states it has no vocabulary for are added here.

   `--sev-high` on the overdue figure introduces no new hue — this calendar
   already maps open work to the severity ramp's red — and it is deliberately
   the *only* warm thing in the row, so a passed obligation is the first thing
   the eye lands on. It is applied only when the count is non-zero: a permanent
   red box reading 0 is how a page teaches somebody to stop seeing red. */
.cal-figures { margin-bottom: 16px; }
.stat-alarm { border-color: var(--sev-high); background: var(--sev-high-bg); }
.stat-alarm .stat-value { color: var(--sev-high); }
/* An em dash where a number would be: "nothing ahead touches you" is an answer,
   and rendering it as 0 would claim a countdown that does not exist. */
.stat-quiet { color: var(--slate); }

.cal-row {
  display: flex; gap: 14px; align-items: baseline;
  padding: 10px 0 10px 12px;
  border-top: 0.5px solid var(--hairline);
  border-left: 3px solid var(--hairline);
}
.cal-row:first-child { border-top: 0; }
.cal-when { flex: none; width: 76px; }
.cal-date { font-family: var(--font-display); font-size: 15px; font-weight: 600; }
.cal-away { font-family: var(--font-mono); font-size: 11px; color: var(--slate); }
.cal-what { min-width: 0; }
.cal-kind { font-family: var(--font-mono); font-size: 11px; color: var(--slate); text-transform: uppercase; letter-spacing: 0.04em; }
.cal-impact { font-weight: 600; }
.cal-label { font-size: 14px; margin: 2px 0 3px; }

.cal-impacting_open { border-left-color: var(--sev-high); }
.cal-impacting_open .cal-date,
.cal-impacting_open .cal-impact { color: var(--sev-high); }

.cal-informational { border-left-color: var(--hairline-2); }
.cal-informational .cal-date,
.cal-informational .cal-impact { color: var(--slate); }

.cal-resolved { border-left-color: var(--sev-low); }
.cal-resolved .cal-date,
.cal-resolved .cal-impact { color: var(--sev-low); }

.cal-not_impacting { border-left-color: var(--hairline-2); }
.cal-not_impacting .cal-date { color: var(--slate); }
.cal-not_impacting .cal-impact { color: var(--slate); }

/* A date a published regulation states. Grape, the brand accent — no new hue,
   and deliberately neither the severity red nor the resolved green: this is
   not urgency and it is not something anybody closed. */
.cal-regulatory { border-left-color: var(--grape); }
.cal-regulatory .cal-date,
.cal-regulatory .cal-impact { color: var(--grape); }

/* A date already gone reads as struck through whatever it means. */
.cal-when-past .cal-date, .cal-when-past .cal-away { text-decoration: line-through; }

/*
 * The ten-day strip at the top of the calendar.
 *
 * `auto-fit` rather than ten fixed columns: at a narrow width ten cells would
 * each be twenty pixels wide, which is not a calendar. It wraps to two rows and
 * stays readable.
 */
.day-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(64px, 1fr)); gap: 6px; margin-top: 14px; }
.day-cell {
  border: 0.5px solid var(--hairline); border-left: 3px solid var(--hairline);
  border-radius: var(--radius); padding: 8px 6px 6px; text-align: center;
  background: var(--surface);
}
.day-weekday { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--slate); }
.day-number { font-family: var(--font-display); font-size: 19px; font-weight: 600; line-height: 1.2; }
.day-count { font-family: var(--font-mono); font-size: 11px; color: var(--slate); min-height: 15px; }
/* An empty day still occupies its cell — the gaps are what make a cluster
   next week look like a cluster. */
.day-count-empty { color: var(--hairline-2); }
.day-weekend { background: var(--surface-2); }
.day-today { border-color: var(--grape); box-shadow: var(--shadow-focus); }
.day-today .day-weekday { color: var(--grape); font-weight: 600; }

/* Reuses the row colours above rather than introducing a second set, so a red
   cell and a red row below it mean the same thing. */
.day-has.cal-impacting_open { border-left-color: var(--sev-high); }
.day-has.cal-impacting_open .day-count { color: var(--sev-high); font-weight: 600; }
.day-has.cal-informational { border-left-color: var(--hairline-2); }
.day-has.cal-informational .day-count { color: var(--slate); font-weight: 600; }
.day-has.cal-resolved { border-left-color: var(--sev-low); }
.day-has.cal-resolved .day-count { color: var(--sev-low); font-weight: 600; }
.day-has.cal-regulatory { border-left-color: var(--grape); }
.day-has.cal-regulatory .day-count { color: var(--grape); font-weight: 600; }
.day-has.cal-not_impacting { border-left-color: var(--hairline-2); }

.day-agenda-heading {
  font-family: var(--font-display); font-size: 12px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--slate);
  margin: 20px 0 8px; padding-top: 14px; border-top: 0.5px solid var(--hairline);
}
.day-agenda-date { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--slate); margin-top: 8px; }

.cal-legend { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; font-size: 13px; }
.cal-key { display: inline-flex; align-items: center; gap: 7px; color: var(--slate); }
.cal-key::before { content: ""; width: 10px; height: 10px; border-radius: 3px; background: var(--hairline-2); }
.cal-key-impacting_open::before { background: var(--sev-high); }
.cal-key-resolved::before { background: var(--sev-low); }
.cal-key-regulatory::before { background: var(--grape); }
/* Slate, never a severity hue: an informational date carries no urgency, and
   amber and red are reserved for item severity. Distinct from `not_impacting`
   so the legend can separate "we looked and it does not touch you" from "this
   is not a deadline at all". */
/* An archived digest, framed so its own stylesheet cannot reach the page around
   it. Tall by default because an email is long and a scrollbar inside a
   scrollbar is the worst way to read one; the frame scrolls only when the
   message runs past it. */
.digest-frame {
  width: 100%; height: 70vh; min-height: 420px; border: 0.5px solid var(--hairline);
  border-radius: var(--radius); background: #fff; display: block;
}

.cal-key-informational::before { background: var(--slate); }

/* Account menu. <details> gives the open/close state, the keyboard and the
   ARIA; everything here is presentation. The panel is positioned rather than
   flowed so opening it does not push the nav around. */
.user-menu { position: relative; }
.user-menu > summary {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 10px; border-radius: 8px; cursor: pointer; list-style: none;
  font-size: 13px; color: var(--slate);
}
.user-menu > summary::-webkit-details-marker { display: none; }
.user-menu > summary:hover { background: var(--surface-2); color: var(--ink); }
.user-menu[open] > summary { background: var(--surface-2); color: var(--ink); }
.user-menu-caret { font-size: 10px; }

.user-menu-panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  min-width: 232px; padding: 6px;
  background: var(--surface); border: 0.5px solid var(--hairline);
  border-radius: var(--radius); box-shadow: 0 8px 24px rgba(12, 42, 66, 0.12);
}
.user-menu-head { padding: 10px 10px 12px; border-bottom: 0.5px solid var(--hairline); margin-bottom: 6px; }
.user-menu-panel a,
.user-menu-signout button {
  display: block; width: 100%; text-align: left;
  padding: 8px 10px; border-radius: 6px;
  font-family: var(--font-body); font-size: 14px; color: var(--ink);
  background: none; border: 0; cursor: pointer;
}
.user-menu-panel a:hover,
.user-menu-signout button:hover { background: var(--surface-2); text-decoration: none; }
.user-menu-signout { margin-top: 6px; padding-top: 6px; border-top: 0.5px solid var(--hairline); }

/* Overview: the card that answers "what needs me today". A left rule rather
   than a tinted panel — amber and red are severity and the card must not
   compete with the badges inside it. */
.attention-card { border-left: 3px solid var(--grape); }
.attention-row {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 0; border-top: 0.5px solid var(--hairline);
}
.attention-body { flex: 1; min-width: 0; font-size: 14px; }
.attention-body a { color: inherit; }
.attention-body a:hover { color: var(--grape); }

.dash-item { display: block; padding: 12px 0; border-top: 0.5px solid var(--hairline); color: inherit; }
.dash-item:hover { text-decoration: none; }
.dash-item:hover .feed-title { color: var(--grape); }
.dash-item-unread { padding-left: 10px; margin-left: -10px; border-left: 3px solid var(--grape); }

.stat-note { font-size: 12px; color: var(--slate); margin-top: 6px; }
.stat-link { display: block; color: inherit; }
.stat-link:hover { text-decoration: none; border-color: var(--slate); }

/* Collapsed pickers: a long checkbox list that is usually already right.
   <details> rather than JavaScript — it works with scripting off and the
   browser handles the keyboard and the ARIA. */
.picker { border: 0.5px solid var(--hairline); border-radius: var(--radius); background: var(--surface); }
.picker > summary {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding: 12px 16px; cursor: pointer; list-style: none;
}
.picker > summary::-webkit-details-marker { display: none; }
.picker > summary::before {
  content: "▸"; color: var(--slate); font-size: 12px; line-height: 1.6;
}
.picker[open] > summary::before { content: "▾"; }
.picker > summary:hover { background: var(--surface-hover); }
.picker-label { font-size: 14px; font-weight: 500; }
.picker-summary { font-size: 13px; color: var(--slate); flex: 1; min-width: 0; }
.picker-body { padding: 4px 16px 16px; border-top: 0.5px solid var(--hairline); }
.picker-group + .picker-group { margin-top: 14px; }
.picker-group-label {
  font-family: var(--font-mono); font-size: 11px; color: var(--slate);
  text-transform: uppercase; letter-spacing: 0.04em; margin: 12px 0 6px;
}
.checkbox-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 6px 16px; }
.checkbox-grid .checkbox-row { display: flex; align-items: center; gap: 8px; font-weight: 400; margin: 0; }
.checkbox-grid .checkbox-row input { width: auto; margin: 0; flex: none; }

.unread-label {
  font-family: var(--font-mono); font-size: 11px; color: var(--grape);
  text-transform: uppercase; letter-spacing: 0.04em; margin-left: -4px;
}

.filters {
  display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end;
  padding: 16px 20px; background: var(--surface);
  border: 0.5px solid var(--hairline); border-radius: var(--radius);
  margin-bottom: 20px;
}
.filters .field { margin: 0; min-width: 150px; }
.filters label { font-size: 12px; color: var(--slate); }

.empty {
  padding: 56px 32px; text-align: center;
  background: var(--surface); border: 0.5px dashed var(--hairline); border-radius: var(--radius-lg);
}
.empty h3 { font-size: 17px; margin-bottom: 8px; }
.empty p { color: var(--slate); font-size: 14px; max-width: 460px; margin: 0 auto 20px; }

.pagination { display: flex; gap: 8px; justify-content: center; align-items: center; margin-top: 28px; font-size: 14px; }

/* ---------------------------------------------------------------- Tables */

.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
  text-align: left; padding: 10px 12px; font-size: 12px; font-weight: 500;
  color: var(--slate); text-transform: uppercase; letter-spacing: 0.04em;
  border-bottom: 1px solid var(--hairline);
}
.table td { padding: 12px; border-bottom: 0.5px solid var(--hairline); vertical-align: top; }
.table tr:last-child td { border-bottom: none; }
.table-scroll { overflow-x: auto; }

.definition-list { display: grid; grid-template-columns: 180px 1fr; gap: 10px 20px; font-size: 14px; }
.definition-list dt { color: var(--slate); }
.definition-list dd { margin: 0; }

/* --------------------------------------------------------------- Sidebar */

.split { display: grid; grid-template-columns: 1fr 320px; gap: 24px; align-items: start; }
.split-reverse { grid-template-columns: 300px 1fr; }

/* ------------------------------------------------------------- Marketing */

.marketing-nav {
  display: flex; align-items: center; justify-content: space-between;
  padding: 22px 32px; border-bottom: 0.5px solid var(--hairline); background: var(--surface);
}
.marketing-nav .nav-links { flex: 0; gap: 32px; }
.marketing-nav .nav-links a { padding: 0; font-size: 14px; }

.hero { padding: 96px 32px 0; text-align: center; }
.eyebrow {
  font-family: var(--font-mono); font-size: 13px; color: var(--grape);
  text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 20px;
}
.headline { font-size: 56px; line-height: 1.08; max-width: 760px; margin: 0 auto 24px; }
.subhead { font-size: 18px; color: var(--slate); max-width: 560px; margin: 0 auto 36px; }
.hero-ctas { display: flex; gap: 14px; justify-content: center; margin-bottom: 72px; flex-wrap: wrap; }

/* The signature signal cluster strip — a monitoring readout, not decoration. */
/* The signal strip: one cluster per regulator, on a line, name underneath.

   The band is sized by its content. It was one SVG at `width: 100%;
   height: auto` over a 1120x90 viewBox, so the height scaled with the
   viewport — 96px at 1200 and 217px at 2700, most of it empty dark space with
   a centred caption floating below marks it had no relationship to. */
.signal-strip { background: var(--deep); padding: 30px 24px; overflow: hidden; }

/* The line the clusters sit on, behind them and full-bleed. A pseudo-element
   rather than an element, so the marks can overlap it the way they did in the
   original drawing — the base dot sits *on* the line, which is what makes it
   read as a readout rather than a row of icons above a rule. */
.strip-row {
  position: relative; display: flex; flex-wrap: wrap;
  /* Spread across the measure rather than huddled in the middle of it, which
     is what the original drawing did with its clusters at 150, 380, 610 and
     840 of 1120. `space-evenly` keeps that at any count; the row gap is what
     takes over once they wrap. */
  justify-content: space-evenly; align-items: flex-start;
  gap: 18px 28px; max-width: 1000px; margin: 0 auto;
}
.strip-row::before {
  content: ""; position: absolute; left: -50vw; right: -50vw; top: 26px;
  border-top: 1px solid #3A2447;
}
.strip-item {
  position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 10px;
}
.strip-mark { display: block; width: 30px; height: 34px; fill: #B98FC9; }
.strip-name {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .06em;
  color: #C9A8D6; white-space: nowrap;
}

@media (max-width: 700px) {
  /* The line stops making sense once the marks wrap onto two rows — it would
     run through the middle of the second one. */
  .strip-row { gap: 18px 32px; }
  .strip-row::before { display: none; }
}

.section { padding: 96px 0; }
.section-label { font-family: var(--font-mono); font-size: 13px; color: var(--slate); text-align: center; margin-bottom: 12px; }
.section-title { font-size: 32px; text-align: center; margin-bottom: 64px; }

.feature-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
  background: var(--hairline); border: 0.5px solid var(--hairline);
  border-radius: 12px; overflow: hidden;
}
.feature-card { background: var(--surface); padding: 36px 32px; }
.feature-tag { font-family: var(--font-mono); font-size: 12px; color: var(--grape); margin-bottom: 16px; }
.feature-card h3 { font-size: 20px; margin-bottom: 10px; }
.feature-card p { font-size: 14.5px; color: var(--slate); }

.pricing-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.plan {
  background: var(--surface); border: 0.5px solid var(--hairline); border-radius: var(--radius-lg);
  padding: 32px 28px; display: flex; flex-direction: column; position: relative;
}
.plan.featured { border: 2px solid var(--grape); }
.plan-badge {
  position: absolute; top: -12px; left: 28px; background: var(--grape); color: var(--on-accent);
  font-size: 11px; font-weight: 500; padding: 4px 12px; border-radius: 6px;
}
.plan-name { font-size: 15px; font-weight: 500; color: var(--slate); margin-bottom: 8px; }
.plan-price { font-family: var(--font-display); font-size: 34px; font-weight: 600; margin-bottom: 4px; }
.plan-price sup { font-size: 14px; font-weight: 400; color: var(--slate); }
.plan-desc { font-size: 13.5px; color: var(--slate); margin-bottom: 24px; min-height: 40px; }
.plan ul { margin-bottom: 28px; flex-grow: 1; }
.plan li {
  font-size: 14px; padding: 9px 0; border-top: 0.5px solid var(--hairline);
  display: flex; gap: 10px; align-items: flex-start;
}
.plan li:first-child { border-top: none; }
.check { color: var(--teal); flex-shrink: 0; }
.plan .btn { width: 100%; padding: 11px; }

.site-footer { border-top: 0.5px solid var(--hairline); padding: 40px 32px; text-align: center; background: var(--surface); }
.site-footer p { font-size: 13px; color: var(--slate); }
.site-footer .tag-line { font-size: 12px; color: var(--slate); margin-top: 6px; font-style: italic; }
.footer-links { display: flex; gap: 20px; justify-content: center; margin-bottom: 14px; font-size: 13px; }

/* The legal line, on both the marketing footer and the in-app one. Quieter
   than the tagline above it: it has to be present and findable, not read. */
.site-footer .footer-legal { font-size: 12px; color: var(--slate); margin-top: 14px; }
.site-footer .footer-fineprint { margin-top: 6px; max-width: 62ch; margin-left: auto; margin-right: auto; }
/* Inside the app the footer is a closing line, not a section of the page. */
.site-footer-app { padding: 28px 32px; background: transparent; }

/* --------------------------------------------------------- Review queue */

.review-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: start; }
.source-text {
  font-size: 14px; line-height: 1.7; white-space: pre-wrap;
  max-height: 640px; overflow-y: auto;
  padding: 16px; background: var(--ground); border-radius: var(--radius);
  border: 0.5px solid var(--hairline);
}
.review-actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* --------------------------------------------------------------- Utility */

.stack > * + * { margin-top: 16px; }
.stack-sm > * + * { margin-top: 8px; }
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.row-between { display: flex; gap: 12px; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.divider { height: 1px; background: var(--hairline); border: 0; margin: 24px 0; }
.inline-form { display: inline; }

@media (max-width: 980px) {
  .split, .split-reverse, .review-layout { grid-template-columns: 1fr; }
  .panel-grid.cols-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 820px) {
  .headline { font-size: 38px; }
  .feature-grid, .pricing-grid, .panel-grid.cols-2, .panel-grid.cols-3 { grid-template-columns: 1fr; }
  .marketing-nav .nav-links { display: none; }
  .app-nav-inner { height: auto; padding: 12px 16px; flex-wrap: wrap; gap: 12px; }
  .nav-links { overflow-x: auto; width: 100%; }
  .nav-search { flex: 1 1 100%; margin-left: 0; order: 3; }
  .page { padding: 24px 16px 60px; }
  .page-header { padding: 18px 18px; }
  .definition-list { grid-template-columns: 1fr; gap: 4px 0; }
  .definition-list dt { margin-top: 12px; }
}

@media print {
  .app-nav, .site-footer, .btn, .filters, .banner { display: none; }
  body { background: var(--surface); }
}

/* ==========================================================================
   Error pages
   ========================================================================== */

.error-page { display: flex; align-items: center; justify-content: center;
              min-height: 100vh; padding: 48px 24px; }
.error-card {
  max-width: 560px; width: 100%; background: var(--surface);
  border: 0.5px solid var(--hairline); border-radius: var(--radius-lg);
  padding: 40px 36px; text-align: center;
}
.error-code {
  font-size: 13px; letter-spacing: 0.1em; color: var(--slate);
  text-transform: uppercase; margin-bottom: 10px;
}
.error-card h1 { font-size: 23px; margin-bottom: 14px; }
.error-body p { color: var(--slate); font-size: 15px; margin-bottom: 12px; }
.error-body p.small { font-size: 13.5px; }
.error-actions { display: flex; gap: 12px; justify-content: center;
                 margin-top: 28px; flex-wrap: wrap; }
.error-path {
  margin-top: 24px; padding-top: 18px; border-top: 0.5px solid var(--hairline);
  font-size: 12px; color: var(--slate); word-break: break-all;
}

/* ==========================================================================
   Operations console
   --------------------------------------------------------------------------
   A distinct surface from the subscriber app: denser, sidebar-driven, and
   built around "what is the platform doing" rather than "what is my feed".
   Still bound by the same colour rule — amber and red mean severity, so the
   console uses Bark for simulation state and Ink for its own chrome.
   ========================================================================== */

.ops-shell { display: grid; grid-template-columns: 232px 1fr; min-height: 100vh; }

.ops-sidebar {
  background: var(--deep); color: #C6D2DC; padding: 20px 0 40px;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.ops-brand {
  display: flex; align-items: center; gap: 10px; padding: 0 20px 18px;
  font-family: var(--font-display); font-weight: 600; font-size: 16px; color: #fff;
  border-bottom: 1px solid rgba(255,255,255,0.08); margin-bottom: 14px;
}
.ops-brand:hover { text-decoration: none; }
.ops-brand svg circle { fill: #B98FC9; }
.ops-nav-group { padding: 12px 0 4px; }
.ops-nav-label {
  padding: 0 20px 6px; font-family: var(--font-mono); font-size: 10.5px;
  letter-spacing: 0.09em; text-transform: uppercase; color: #6E8091;
}
.ops-nav a {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  padding: 8px 20px; font-size: 14px; color: #C6D2DC;
}
.ops-nav a:hover { background: rgba(255,255,255,0.06); color: #fff; text-decoration: none; }
.ops-nav a.active { background: rgba(185,143,201,0.18); color: #fff; font-weight: 500;
                    box-shadow: inset 3px 0 0 #B98FC9; }
.ops-nav .count {
  font-family: var(--font-mono); font-size: 11px; padding: 1px 7px;
  border-radius: 10px; background: rgba(255,255,255,0.12); color: #fff;
}
.ops-nav .count-alert { background: #C9A8D6; color: #0C2A42; font-weight: 600; }
/* Which build the console is running, quietly, on every page. Fixed chrome
   colours like the rest of the sidebar, which deliberately does not theme. */
.ops-build { padding: 0 20px; margin-top: 18px; font-size: 11px; color: #7C8FA0;
             letter-spacing: .02em; }
.ops-build .mono { color: #A9BCCB; }

.ops-footer-link { padding: 16px 20px 0; margin-top: 14px;
                   border-top: 1px solid rgba(255,255,255,0.08); }
.ops-footer-link a { font-size: 13px; color: #8FA2B2; display: block; padding: 5px 0; }
.ops-footer-link a:hover { color: #fff; }

.ops-main { padding: 28px 32px 72px; max-width: 1400px; }
.ops-header { display: flex; justify-content: space-between; align-items: flex-start;
              gap: 20px; margin-bottom: 24px; flex-wrap: wrap; }
.ops-header h1 { font-size: 24px; margin-bottom: 4px; }
.ops-header p { color: var(--slate); font-size: 14px; max-width: 62ch; }

.ops-grid { display: grid; gap: 16px; }
.ops-grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ops-grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ops-grid.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.mode-card {
  background: var(--surface); border: 0.5px solid var(--hairline);
  border-radius: var(--radius); padding: 16px 18px;
}
.mode-card .mode-label { font-size: 13px; color: var(--slate); margin-bottom: 6px; }
.mode-state { font-family: var(--font-display); font-size: 17px; font-weight: 600; }
.mode-live { color: var(--teal); }
.mode-sim { color: var(--bark); }
.mode-card .mode-detail { font-size: 12.5px; color: var(--slate); margin-top: 6px; }
.mode-locked { font-size: 11px; color: var(--slate); font-style: italic; margin-top: 4px; }

.ops-alert {
  border-radius: var(--radius); padding: 14px 18px; font-size: 14px;
  background: var(--bark-tint); color: var(--bark); border: 0.5px solid var(--bark-line);
  margin-bottom: 16px;
}
.ops-alert ul { margin-top: 8px; }
.ops-alert li { padding: 3px 0; }
.ops-alert-quiet { background: var(--surface); color: var(--slate); border-color: var(--hairline); }

/* A withdrawn advisory's notice, at the top of its own page.

   Bark, not amber: amber and red belong to the severity badges and appear
   nowhere else, and a withdrawal is a state rather than a severity — the
   advisory may have been low. Bark is what this palette already uses for "the
   platform is telling you something about itself", which is exactly what this
   is. Its own class rather than borrowing `.ops-alert`, which is console
   chrome and would drift the day the console restyles. */
.withdrawn-notice {
  border-radius: var(--radius); padding: 14px 18px; font-size: 14px;
  background: var(--bark-tint); color: var(--bark); border: 0.5px solid var(--bark-line);
  margin-bottom: 20px;
}
.withdrawn-notice a { color: inherit; }

.ops-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.ops-table th {
  text-align: left; padding: 9px 12px; font-size: 11px; font-weight: 500;
  color: var(--slate); text-transform: uppercase; letter-spacing: 0.05em;
  border-bottom: 1px solid var(--hairline); white-space: nowrap;
}
.ops-table td { padding: 10px 12px; border-bottom: 0.5px solid var(--hairline);
                vertical-align: top; }
.ops-table tr:last-child td { border-bottom: none; }
.ops-table tbody tr:hover { background: var(--surface-hover); }
.ops-table .num { font-family: var(--font-mono); text-align: right; }

.state-dot { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }
.state-dot::before { content: ""; width: 7px; height: 7px; border-radius: 50%;
                     background: currentColor; flex-shrink: 0; }
.state-ok { color: var(--teal); }
.state-warn { color: var(--bark); }
.state-bad { color: var(--sev-high); }
.state-idle { color: var(--slate); }

.ops-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.ops-toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* A wide table scrolls inside its own box. Without this the whole console body
   scrolls sideways and the sidebar leaves the screen with it. */
.table-wrap { overflow-x: auto; }
.table-wrap .ops-table { min-width: 640px; }
.ops-json {
  font-family: var(--font-mono); font-size: 12px; line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
  background: var(--ground); border: 0.5px solid var(--hairline);
  border-radius: var(--radius); padding: 14px; max-height: 460px; overflow: auto;
}
.ops-switch {
  display: grid; grid-template-columns: auto 1fr; gap: 12px;
  padding: 16px 0; border-top: 0.5px solid var(--hairline);
}
.ops-switch:first-of-type { border-top: none; }
.ops-switch input { margin-top: 4px; width: 16px; height: 16px; }
.ops-switch label { font-weight: 500; margin-bottom: 4px; }
.ops-switch .switch-help { font-size: 13px; color: var(--slate); }
.ops-switch-locked { opacity: 0.6; }

@media (max-width: 1000px) {
  .ops-shell { grid-template-columns: 1fr; }
  .ops-sidebar { position: static; height: auto; }
  .ops-nav { display: flex; flex-wrap: wrap; gap: 4px; padding: 0 12px; }
  .ops-nav-group { padding: 6px 0; }
  .ops-nav-label { display: none; }
  .ops-grid.cols-3, .ops-grid.cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ops-main { padding: 20px 16px 48px; }
}
@media (max-width: 640px) {
  .ops-grid.cols-2, .ops-grid.cols-3, .ops-grid.cols-4 { grid-template-columns: 1fr; }
}

/* Sticky action bar for long settings forms. A submit button placed after a
   two-column grid sits off-screen whenever one column is much taller than the
   other, which reads to the user as "there is no save button". */
.ops-sticky-actions {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  gap: 16px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 24px;
  padding: 14px 20px;
  background: var(--surface);
  border: 0.5px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 -6px 18px -12px rgba(12, 42, 66, 0.45);
}

/* ==========================================================================
   Landing page — product previews and the single-plan pricing card
   ========================================================================== */

.hero-price { font-size: 14px; color: var(--slate); margin-top: -48px; margin-bottom: 72px; }
.section-tight { padding-top: 0; }
.section-intro {
  max-width: 62ch; margin: -44px auto 44px; text-align: center;
  font-size: 16px; color: var(--slate);
}

/* A framed shot of the real app, built from live data rather than an image. */
.app-shot {
  border: 0.5px solid var(--hairline); border-radius: var(--radius-lg);
  overflow: hidden; background: var(--surface);
}
.app-shot-chrome {
  display: flex; align-items: center; gap: 7px;
  padding: 11px 16px; background: var(--surface-2);
  border-bottom: 0.5px solid var(--hairline);
}
.app-shot-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--hairline-2); }
.app-shot-title { font-size: 11.5px; color: var(--slate); margin-left: 10px; }
.app-shot-body { display: grid; gap: 1px; background: var(--hairline); }
.shot-item { background: var(--surface); padding: 20px 24px; }
.shot-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 9px; }
.shot-title {
  font-family: var(--font-display); font-size: 16px; font-weight: 500;
  line-height: 1.35; margin-bottom: 7px;
}
.shot-summary { font-size: 14px; color: var(--slate); margin-bottom: 10px; }
/* The matched-products line inside a landing-page mockup. Teal, exactly as
   .feed-matches is in the app, because rule-based matching is the
   deterministic half of the pipeline and the mockup must not invent a visual
   language the real page does not use. Amber and red stay severity-only. */
/* The seat bands on a pricing card. Deliberately quiet: the headline price is
   what a buyer compares, and these answer the second question rather than
   competing with the first. Grape, never the severity ramp. */
.seat-bands { display: block; margin-top: 10px; border-top: 1px solid var(--hairline); padding-top: 8px; }
.seat-band { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; line-height: 1.9; }
.seat-band-range { color: var(--slate); }
.seat-band-rate { color: var(--grape); font-weight: 500; font-variant-numeric: tabular-nums; }

.shot-matches { margin-top: 10px; color: var(--teal); line-height: 1.5; }
.shot-matches .muted { color: var(--slate); }

.proof-row {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
  background: var(--hairline); border: 0.5px solid var(--hairline);
  border-radius: var(--radius-lg); overflow: hidden; margin-top: 24px;
}
.proof { background: var(--surface); padding: 22px 24px; text-align: center; }
.proof-value { font-family: var(--font-display); font-size: 30px; font-weight: 600; line-height: 1; }
.proof-label { font-size: 13px; color: var(--slate); margin-top: 7px; }

.split-showcase { display: grid; grid-template-columns: 1fr 1fr; gap: 56px; align-items: center; }
.showcase-title { font-size: 30px; margin-bottom: 16px; }
.showcase-copy { font-size: 16px; color: var(--slate); margin-bottom: 20px; }
.plain-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 26px; }
.plain-list li { position: relative; padding-left: 20px; font-size: 14.5px; color: var(--slate); }
.plain-list li::before {
  content: ""; position: absolute; left: 2px; top: 0.6em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--grape);
}

.mail-shot {
  border: 0.5px solid var(--hairline); border-radius: var(--radius-lg);
  background: var(--surface); overflow: hidden;
}
.mail-shot-head { padding: 20px 22px; border-bottom: 0.5px solid var(--hairline); }
.mail-shot-subject {
  font-family: var(--font-display); font-size: 17px; font-weight: 600; margin-top: 8px;
}
.mail-shot-item { padding: 16px 22px; border-bottom: 0.5px solid var(--hairline); }
.mail-shot-item:last-child { border-bottom: none; }
.mail-shot-title { font-size: 14.5px; font-weight: 500; margin: 8px 0 4px; line-height: 1.4; }

/* -------------------------------------------------------------- pricing */
/*
 * One card or several, from the same markup. A grid of one looks wrong, so a
 * single plan gets a narrower centred card instead of a full-width one.
 */

.plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: 20px;
  align-items: start;
}
.plan-grid-single { grid-template-columns: minmax(0, 620px); justify-content: center; }

.plan-card {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); padding: 32px;
  display: flex; flex-direction: column; height: 100%;
}
/* The grape border marks where a visitor is meant to start, not "better". */
.plan-card-featured { border: 2px solid var(--grape); }
.plan-card .plan-features { flex: 1 1 auto; }

.plan-card-head {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 20px; flex-wrap: wrap; margin-bottom: 24px;
}
/* `text-wrap: pretty` rather than a `max-width` alone. A 44ch cap on a
   two-sentence description leaves the browser breaking wherever it happens to
   run out, which reads as a hard line break somebody typed — reported as
   exactly that. `pretty` moves the ragged edge and forbids a one-word last
   line, and degrades to ordinary wrapping where it is not supported. */
.plan-card .plan-desc {
  min-height: 0; margin-bottom: 0; max-width: 48ch; text-wrap: pretty;
}

/* Bark, not amber: amber belongs to severity badges alone. */
.offer-flag {
  display: flex; flex-direction: column; align-items: flex-end; gap: 3px;
  padding: 9px 14px; border-radius: var(--radius);
  background: var(--bark-tint); border: 0.5px solid var(--bark-line);
}
.offer-flag-save { font-family: var(--font-display); font-weight: 600; font-size: 17px; color: var(--bark); }
.offer-flag-text { font-size: 12px; color: var(--bark); }

.price-options { display: grid; gap: 12px; margin-bottom: 26px; }
.price-option {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 16px 18px; border: 1px solid var(--hairline);
  border-radius: var(--radius); cursor: pointer; margin: 0; font-weight: 400;
}
.price-option:hover { border-color: var(--slate); }
.price-option input { margin-top: 5px; flex: 0 0 auto; }
.price-option:has(input:checked) { border-color: var(--grape); background: var(--grape-wash); }
.price-option-body { display: flex; flex-direction: column; gap: 3px; }
.price-amount { font-family: var(--font-display); font-size: 27px; font-weight: 600; line-height: 1.1; }
.price-unit { font-size: 14px; font-weight: 400; color: var(--slate); }
.price-note { font-size: 13.5px; color: var(--slate); }

.plan-features { display: flex; flex-direction: column; margin-bottom: 26px; }
.plan-features li {
  display: flex; gap: 10px; align-items: flex-start;
  font-size: 14.5px; padding: 10px 0; border-top: 0.5px solid var(--hairline);
}
.plan-features li:first-child { border-top: none; }
.plan-actions { display: flex; flex-direction: column; gap: 10px; }
.plan-finequiet { font-size: 13px; color: var(--slate); text-align: center; margin-top: 6px; }

@media (max-width: 900px) {
  .split-showcase { grid-template-columns: 1fr; gap: 32px; }
  .proof-row { grid-template-columns: 1fr; }
  .hero-price { margin-top: -32px; margin-bottom: 56px; }
  .section-intro { margin-top: -28px; }
}

/* ------------------------------------------------- Source documents */
/* A stored file is a snapshot of something that changes at its source, so the
   staleness note is part of the component rather than an afterthought. */
.doc-list { list-style: none; margin-top: 12px; display: flex; flex-direction: column; gap: 12px; }
.doc { padding: 12px; border: 0.5px solid var(--hairline); border-radius: 8px; background: var(--ground); }
.doc-stale { border-color: rgba(138, 86, 15, 0.35); background: var(--sev-medium-bg); }
.doc-head { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.doc-name { font-size: 12.5px; margin-top: 8px; word-break: break-all; }
.doc-warn { color: var(--sev-medium); }
.doc-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.doc-actions .btn { flex: 1 1 auto; }

/* --------------------------------------------- Grouped category pickers */
/* Forty categories in one alphabetical grid is a wall. Grouped, a subscriber
   scans to their own area and reads six. */
.category-group + .category-group { margin-top: 20px; }
.category-group-title {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--slate);
  padding-bottom: 6px;
  margin-bottom: 10px;
  border-bottom: 0.5px solid var(--hairline);
}

/* ------------------------------------------------------- Theme chooser */
/*
 * Each option previews itself rather than describing itself: the swatch is
 * painted from that theme's own tokens, so "Colorful" shows what colorful
 * actually looks like instead of asking the reader to imagine it.
 */
.theme-choices { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.theme-choice {
  display: flex; flex-direction: column; gap: 10px; align-items: flex-start;
  padding: 14px; margin: 0; font-weight: 400; cursor: pointer;
  border: 1px solid var(--hairline); border-radius: var(--radius);
  background: var(--surface);
}
.theme-choice:hover { border-color: var(--slate); }
.theme-choice:has(input:checked) { border-color: var(--grape); background: var(--grape-wash); }
.theme-choice input { margin: 0; }
.theme-choice-name { font-size: 14px; font-weight: 500; }

.theme-swatch {
  width: 100%; border-radius: 8px; overflow: hidden;
  border: 0.5px solid rgba(0, 0, 0, 0.12);
}
.theme-swatch-bar { display: block; height: 14px; }
.theme-swatch-body { display: block; padding: 9px; }
.theme-swatch-line { display: block; height: 4px; border-radius: 2px; margin-bottom: 5px; }
.theme-swatch-line.short { width: 55%; }
.theme-swatch-chip { display: block; height: 8px; width: 34%; border-radius: 4px; }

/* Painted from each theme's own values, not from the tokens in effect — a
   preview that changed with the current theme would preview nothing. */
.theme-choice-light .theme-swatch { background: #FFFFFF; }
.theme-choice-light .theme-swatch-bar { background: #0C2A42; }
.theme-choice-light .theme-swatch-line { background: #DCE2E8; }
.theme-choice-light .theme-swatch-chip { background: #5B2C6F; }

.theme-choice-dark .theme-swatch { background: #111F2B; }
.theme-choice-dark .theme-swatch-bar { background: #061019; }
.theme-choice-dark .theme-swatch-line { background: #27394A; }
.theme-choice-dark .theme-swatch-chip { background: #C0A0D2; }

.theme-choice-colorful .theme-swatch { background: #FFFFFF; }
.theme-choice-colorful .theme-swatch-bar { background: #1E0C2C; }
.theme-choice-colorful .theme-swatch-line { background: #E0D2EC; }
.theme-choice-colorful .theme-swatch-chip { background: #7A2E9E; }

/* ----------------------------------------------------- Rendered prose */
/*
 * Agent output arrives as light Markdown and is rendered by |richtext into a
 * fixed set of tags. Styled here so a bold run or a bullet list sits inside a
 * card without inheriting page-level heading spacing.
 */
.richtext p { margin-bottom: 10px; }
.richtext p:last-child { margin-bottom: 0; }
.richtext ul, .richtext ol { margin: 0 0 10px 18px; }
.richtext ul { list-style: disc; }
.richtext ol { list-style: decimal; }
.richtext li { padding: 2px 0; }
.richtext li:last-child { padding-bottom: 0; }
.richtext strong { font-weight: 600; }
.richtext code {
  font-family: var(--font-mono); font-size: 0.92em;
  background: var(--surface-2); padding: 1px 5px; border-radius: 4px;
}

/* ------------------------------------------------------------- Briefing */
/* The first thing on the overview, so it reads as prose rather than as a
   dashboard panel: a little more room, and the headline set at reading size.
   The grape rail marks the organization's own briefing apart from the shared
   one without introducing a hue — the same accent every other "this is yours"
   surface uses. */
.briefing { margin-bottom: 20px; }
.briefing-accent { border-left: 3px solid var(--grape); }
.briefing-headline { font-family: var(--font-display); font-size: 17px; line-height: 1.45; margin-top: 10px; }
.briefing-label {
  font-family: var(--font-mono); font-size: 11px; color: var(--slate);
  text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 2px;
}

/* Exposure rows on the overview: quiet, because being exposed to a theme is
   not a problem — it is the frame the problems arrive in. */
.exposure-row { padding: 8px 0; border-top: 0.5px solid var(--hairline); }
.exposure-row:first-child { border-top: 0; }

/*
 * Proportion bars and the obligations strip.
 *
 * Both take their colours from ramps that already exist — severity for what
 * was published, the lifecycle tones for components — so a segment here means
 * exactly what the badge it matches means elsewhere, and no new hue enters the
 * palette to say it.
 */
.mix-bar { display: flex; height: 12px; border-radius: 6px; overflow: hidden; margin-top: 12px; background: var(--surface-2); }
.mix-seg { display: block; height: 100%; }
.mix-seg + .mix-seg { border-left: 1px solid var(--surface); }
.mix-bar .sev-critical, .chart-legend .sev-critical { background: var(--sev-high); }
.mix-bar .sev-high, .chart-legend .sev-high { background: var(--sev-high); opacity: 0.66; }
.mix-bar .sev-medium, .chart-legend .sev-medium { background: var(--sev-medium); }
.mix-bar .sev-low, .chart-legend .sev-low { background: var(--sev-low); }
.mix-eol { background: var(--sev-high); }
.mix-abandoned { background: var(--sev-medium); }
.mix-eos { background: var(--sev-medium); opacity: 0.7; }
.mix-stale { background: var(--hairline-2); }
.mix-supported { background: var(--sev-low); }
.mix-unknown { background: var(--heat-1); }

.timeline { position: relative; height: 26px; margin-top: 14px; }
.timeline-rule { position: absolute; left: 0; right: 0; top: 12px; height: 1px; background: var(--hairline-2); }
.timeline-mark {
  position: absolute; top: 7px; width: 11px; height: 11px; border-radius: 50%;
  margin-left: -5px; background: var(--slate); border: 2px solid var(--surface);
}
.timeline-mark-open { background: var(--sev-high); }
.timeline-ends { display: flex; justify-content: space-between; font-family: var(--font-mono); font-size: 10px; color: var(--slate); }
.chart-legend .swatch-open { background: var(--sev-high); border-radius: 50%; }
.chart-legend .swatch-handled { background: var(--slate); border-radius: 50%; }

/*
 * The overview reads as three questions — what is happening, what to do, what
 * is coming — and these are the seams between them. Set as page furniture
 * rather than as another card title, so a reader scanning for the act they
 * want does not have to tell them apart from the cards inside it.
 */
/* The setup checklist. Grape rail rather than a severity colour: unfinished
   setup is an opportunity, not an alarm, and amber belongs to severity. */
.setup-card { border-left: 3px solid var(--grape); margin-bottom: 20px; }
.setup-steps { display: grid; gap: 10px; margin-top: 12px; }
.setup-step { display: flex; gap: 10px; align-items: baseline; font-size: 14px; }
.setup-step-done { color: var(--slate); }
.setup-tick { font-family: var(--font-mono); color: var(--slate); width: 14px; flex: none; }
.setup-step-done .setup-tick { color: var(--sev-low); }
.setup-label { font-weight: 500; }
.setup-step-done .setup-label { font-weight: 400; text-decoration: line-through; }

.section-heading {
  font-family: var(--font-display);
  font-size: 12px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--slate);
  margin: 32px 0 12px;
}
.section-heading:first-of-type { margin-top: 0; }

/*
 * A name in the briefing that is also a record here. Underlined rather than
 * coloured: these sit inside a paragraph, several to a sentence, and a run of
 * grape words reads as a navigation bar rather than as prose.
 */
.mention { color: inherit; text-decoration: underline; text-decoration-color: var(--hairline-2); text-underline-offset: 2px; }
.mention:hover { color: var(--grape); text-decoration-color: var(--grape); }

/* One recommendation, against the item it was written for. */
.action-row { padding: 10px 0; border-top: 0.5px solid var(--hairline); }
.action-row:first-child { border-top: 0; padding-top: 2px; }

/* Operator triage bands. Severity red is reused here for its own meaning —
   these are the findings that mean customers are affected right now — which is
   the only reuse the colour rule allows outside a badge. */
.ops-band { border: 0.5px solid var(--hairline); border-radius: var(--radius-lg); padding: 16px 18px; margin-bottom: 16px; background: var(--surface); }
.ops-band-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.ops-band-head .tiny { font-size: 11px; color: var(--slate); }
.ops-band-critical { border-left: 3px solid var(--sev-high); }
.ops-band-warning { border-left: 3px solid var(--sev-medium); }
.ops-band-clear { border-left: 3px solid var(--sev-low); color: var(--slate); font-size: 14px; }
.ops-finding { padding: 10px 0; border-top: 0.5px solid var(--hairline); }
.ops-finding:first-of-type { border-top: 0; }
.ops-finding-title { font-weight: 600; font-size: 14.5px; }
.ops-finding-detail { font-size: 13px; color: var(--slate); margin: 3px 0 6px; }
.ops-finding-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ops-finding-meta { font-size: 12px; color: var(--slate); margin-bottom: 6px; }

/* Silenced criticals. Slate, not red and not green: red would say nobody has
   seen it, which is the one thing that is no longer true, and green would say
   it is fixed, which is the one thing that is still false. Something still
   broken and taken on by a person is neither, and the palette already has a
   colour for "true, nothing to do about it this minute". */
.ops-band-silenced { border-left: 3px solid var(--slate); }

/* The silence control gets its own row under the finding rather than sharing one
   with the finding's link. The note is the field that needs the width — it is
   the part the next operator reads, and a duration without a reason answers
   "how long" while leaving "why" to whoever is on shift at three in the
   morning — and inputs here are `width: 100%` by default, so sharing a line
   squeezed it to a sliver and pushed the button off the card.

   Explicit `width: auto` on both controls: the global rule would otherwise win
   over flex-basis and give the select the whole row. */
.ops-silence-form {
  display: flex; align-items: center; gap: 8px;
  margin-top: 8px; padding-top: 8px; border-top: 0.5px dashed var(--hairline);
}
.ops-silence-form input[type="text"], .ops-silence-form select {
  width: auto; font-size: 12.5px; padding: 6px 8px;
}
.ops-silence-note { flex: 1 1 auto; min-width: 0; }
.ops-silence-for { flex: 0 0 auto; }
.ops-silence-form .btn { flex: 0 0 auto; }

/* ------------------------------------------------------------------ Charts */

/*
 * Inline SVG, sized by its container and coloured from the tokens above, so a
 * chart follows the theme like every other component and prints with the page.
 */
/* One, two or three, because the day decides how many there are. A single
   panel spanning the width would be a poster; it takes the left half and lets
   the page breathe. */
.chart-grid { display: grid; gap: 16px; margin-top: 16px; }
.chart-grid-1 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.chart-grid-1 > * { grid-column: 1; }
.chart-grid-2 { grid-template-columns: 1.15fr 1fr; }
.chart-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.chart-panel {
  background: var(--surface); border: 0.5px solid var(--hairline);
  border-radius: var(--radius); padding: 16px 18px;
}
.chart-panel h3 {
  font-family: var(--font-display); font-size: 13px; font-weight: 600;
  letter-spacing: 0.02em; margin-bottom: 2px;
}
.chart-panel .chart-note { font-size: 12px; color: var(--slate); }
.chart-svg { display: block; width: 100%; height: auto; margin-top: 10px; overflow: visible; }

.tile { stroke: var(--surface); stroke-width: 1.5; }
.tile-0 { fill: var(--heat-0); }
.tile-1 { fill: var(--heat-1); }
.tile-2 { fill: var(--heat-2); }
.tile-3 { fill: var(--heat-3); }
.tile-4 { fill: var(--heat-4); }
/* Not watched at all: an outline, so it reads as a gap in coverage rather than
   as a quiet month. The two are opposite findings. */
.tile-unwatched { fill: none; stroke: var(--heat-line); stroke-dasharray: 3 3; }
.tile-label { font-family: var(--font-mono); font-size: 9px; fill: var(--slate); }
.tile-label-on { fill: var(--on-accent); }
.tile-label-off { fill: var(--hairline-2); }

.radar-ring { fill: none; stroke: var(--heat-line); stroke-width: 0.5; }
.radar-spoke { stroke: var(--heat-line); stroke-width: 0.5; }
/* The themes this reader is exposed to, behind the data. */
.radar-spoke-exposed { stroke: var(--heat-2); stroke-width: 6; stroke-linecap: round; opacity: 0.35; }
/* The month is an outline; what arrived since the briefing was written is
   filled inside it. Two shapes against one scale, so the reader sees today
   against its own background rather than a number they have to remember. */
.radar-area { fill: none; stroke: var(--grape); stroke-width: 1.5; }
.radar-area-baseline { stroke: var(--heat-2); stroke-dasharray: 3 3; }
.radar-area-recent { fill: var(--grape); fill-opacity: 0.22; }
/* A regulator that moved inside that same window. */
.tile-fresh { fill: var(--grape); stroke: var(--surface); stroke-width: 1.5; }
.radar-vertex { fill: var(--grape); }
.radar-label { font-size: 9px; fill: var(--slate); }
.radar-label-exposed { fill: var(--ink); font-weight: 500; }

/* Two independent decisions over one selection, so they need separating from
   the status controls above them without becoming a second bar. */
/* Separates "what the list is of" from "what state it is in" — two groups of
   controls that narrow the same list and would otherwise read as one row of
   six. */
.tab-divider { width: 1px; align-self: stretch; background: var(--hairline); margin: 0 4px; }

.bulk-read-row { display: flex; align-items: center; gap: 8px; margin-top: 10px;
                 padding-top: 10px; border-top: 1px solid var(--hairline); }

/* The news feed's trend radar. Same geometry and same palette as the overview's,
   a different question: this reader's own feed, this week against its usual
   week, so an axis bursting past its outline *is* the finding.

   A surging axis is grape and heavier, never amber or red. A rise in regulatory
   activity is not a severity — reaching for the severity ramp here would put a
   red spike on a page whose whole argument is that most of what is published
   does not touch you. tests/test_themes.py enforces that by hue. */
.radar-vertex-hot { fill: var(--grape); stroke: var(--surface); stroke-width: 2; }
.radar-label-hot { fill: var(--ink); font-weight: 600; }

.radar-windows { display: flex; gap: 4px; }

.radar-row { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.radar-row .radar { flex: 1 1 340px; max-width: 420px; height: auto; }
.radar-legend { flex: 1 1 240px; min-width: 220px; }

.radar-key { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.radar-key li { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; color: var(--slate); }
.radar-key li.is-surging { color: var(--ink); font-weight: 500; }

/* Direction, not judgement. Up is grape because more activity is information
   rather than an emergency; down is quiet slate for the same reason. */
.trend-up { color: var(--grape); font-weight: 500; }
.trend-down { color: var(--slate); }
.stat-unit { font-size: 0.5em; margin-left: 2px; color: var(--slate); }

/* ---------------------------------------------------------------------------
   The daily briefing, set as an issue rather than a dashboard.

   One column with a measure, sections separated by space and a kicker instead
   of by card borders. A card says "here is a widget"; a rule and a kicker say
   "here is the next thing to read", which is what a page somebody opens at nine
   in the morning should be doing.

   Colour is the grape ramp doing structural work — the masthead rule, the
   kickers, the callout edge — plus teal where something is *handled*. Amber and
   red stay reserved for item severity badges, which tests/test_themes.py
   enforces by hue, so the severity chips in the list are the only warm thing on
   the page and therefore the thing the eye finds first. That is the point.
   --------------------------------------------------------------------------- */
.issue-head { margin-bottom: 34px; }

/* Explicit height, not `auto`. An SVG with a viewBox and `height: auto` is at
   the mercy of the layout it sits in — inside a grid row as tall as a six-line
   headline it stretched to 240px, turning a 74px mark into a wall of bars.
   `preserveAspectRatio: none` lets it fill the rail's width at a stated height
   rather than letterboxing itself into the middle of the column. */
/* The fortnight as a mark on the dateline: sparkline scale, where fourteen
   bars still read as fourteen days. Stretched across the measure they became a
   chunky bar chart and stopped being a mark at all. */
.issue-meta-mark { display: inline-flex; align-items: center; gap: 7px; }
.issue-meta-mark svg { display: block; width: 78px; height: 15px; }
.issue-ribbon-svg { display: block; width: 100%; height: 40px; }

.ribbon-bar { transition: opacity 120ms; }
.ribbon-bar.heat-0 { fill: var(--heat-0); }
.ribbon-bar.heat-1 { fill: var(--heat-1); }
.ribbon-bar.heat-2 { fill: var(--heat-2); }
.ribbon-bar.heat-3 { fill: var(--heat-3); }
.ribbon-bar.heat-4 { fill: var(--heat-4); }
/* Today gets an outline rather than a colour, so the ramp keeps meaning volume
   and nothing else. */
.ribbon-today { stroke: var(--grape); stroke-width: 1.5; }
/* The masthead rule: the one piece of pure decoration here, and it earns its
   place by making the top of the page look like the top of something. */
.issue-rule { height: 4px; border-radius: 2px; margin-bottom: 16px;
              background: linear-gradient(90deg, var(--grape) 0%, var(--heat-3) 45%,
                                          var(--heat-2) 70%, var(--heat-1) 100%); }
.issue-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
              font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
              color: var(--slate); }
.issue-date { font-weight: 600; color: var(--grape); }
/* A briefing headline is a sentence, not a label — the model writes twenty-odd
   words. Capped at 26ch that became six lines and a wall; the cap is the column
   now, with `balance` evening the last line. */
.issue-headline { font-family: var(--font-display); font-size: 27px; line-height: 1.22;
                  font-weight: 600; color: var(--ink); margin: 8px 0 0; max-width: 44ch;
                  text-wrap: balance; }
/* The kicker above the headline. Quiet by construction: it is the one line
   here written for tone rather than for fact, and setting it at headline weight
   would have two things competing to be the first thing read. */
.issue-opening { font-size: 12.5px; letter-spacing: 0.05em; font-weight: 600;
                 color: var(--grape); margin: 0; text-transform: none;
                 max-width: 52ch; }
/* Staff-only, and deliberately not styled as a warning: nothing is broken, a
   row is simply older than the page. Bark rather than the severity ramp. */
.issue-stale-note { font-size: 13px; line-height: 1.55; color: var(--slate);
                    border-left: 3px solid var(--bark); padding: 8px 12px;
                    margin: 0 0 14px; background: var(--surface-2);
                    border-radius: var(--radius); max-width: 62ch; }
.issue-lead { font-size: 16.5px; line-height: 1.68; color: var(--ink); max-width: 66ch;
              margin-bottom: 38px; }
.issue-lead p:first-child::first-letter { float: left; font-family: var(--font-display);
              font-size: 46px; line-height: 0.86; font-weight: 600; color: var(--grape);
              padding: 4px 8px 0 0; }

.issue-section { padding-top: 28px; margin-bottom: 30px; border-top: 0.5px solid var(--hairline); }
/* A section's own parts sit closer together than the gap to the next section,
   so the page reads as beats rather than as evenly spaced blocks. */
.issue-section .chart-panel { margin: 0; }
.issue-kicker { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
                font-weight: 600; color: var(--grape); margin-bottom: 16px; }
.issue-kicker-sm { font-size: 10.5px; letter-spacing: 0.07em; text-transform: uppercase;
                   font-weight: 600; color: var(--slate); margin-bottom: 5px; }
.issue-quiet { font-size: 14px; color: var(--slate); max-width: 66ch; margin: 0; }
.issue-more { display: inline-block; margin-top: 12px; font-size: 13px; font-weight: 500; }
.issue-window { margin-top: 16px; }

/* A decided row's state, where an undecided one has a tick box.
   A disabled empty checkbox says "not selected", which is the opposite of what
   it means — so a row that is done shows a tick and one that failed shows a
   mark. Teal for done, bark for needs-a-look: amber and red are the severity
   ramp and appear nowhere else. */
.bulk-state { display: inline-flex; align-items: center; justify-content: center;
              width: 16px; height: 16px; flex: none; font-size: 12px; line-height: 1;
              border-radius: 4px; }
.bulk-state-done { color: var(--teal); }
.bulk-state-stuck { color: var(--bark); font-weight: 700; }

/* ---------------------------------------------------------------------------
   The user guide.

   A reference somebody consults mid-task, so the contents stay on screen and
   each section answers one question. Prose measure rather than full width —
   this is the one page in the app that is mostly words.
   --------------------------------------------------------------------------- */
.guide { display: grid; grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
         gap: 40px; align-items: start; }
/* Below the nav, not behind it: at 24px the contents list slid under the bar as
   soon as the page scrolled. */
.guide-contents { position: sticky; top: calc(var(--nav-height) + 20px); }
.guide-contents ol { list-style: none; margin: 0; padding: 0; counter-reset: guide; }
.guide-contents li { counter-increment: guide; margin-bottom: 7px; font-size: 13.5px; }
.guide-contents a { color: var(--slate); text-decoration: none; }
.guide-contents a:hover { color: var(--grape); }

.guide-body { min-width: 0; max-width: 72ch; }
/* Scroll-margin, so an anchor lands under the sticky nav rather than behind it. */
.guide-section { padding-top: 26px; margin-bottom: 26px;
                 border-top: 0.5px solid var(--hairline); }
.guide-section:first-child { border-top: 0; padding-top: 0; }
.guide-section h2 { font-family: var(--font-display); font-size: 21px; font-weight: 600;
                    margin: 0 0 12px; }
.guide-section h3 { font-size: 15px; font-weight: 600; margin: 22px 0 8px; }
.guide-section p { font-size: 15px; line-height: 1.68; margin: 0 0 13px; }
.guide-list { margin: 0 0 13px; padding-left: 20px; font-size: 15px; line-height: 1.66; }
.guide-list li { margin-bottom: 8px; }

/* The status words, set as definitions because that is what they are. */
.guide-terms { margin: 0 0 14px; }
.guide-terms dt { font-weight: 600; font-size: 15px; margin-top: 14px; }
.guide-terms dd { margin: 4px 0 0; font-size: 14.5px; line-height: 1.6; color: var(--slate);
                  padding-left: 16px; border-left: 2px solid var(--hairline); }
.guide-tag { display: inline-block; margin-left: 6px; font-size: 11px; letter-spacing: 0.04em;
             text-transform: uppercase; color: var(--grape); }

/* An aside within a section: the thing that is easy to get wrong, or the
   boundary the product deliberately does not cross. */
.guide-note { padding: 12px 14px; border-radius: var(--radius); background: var(--surface-2);
              border-left: 3px solid var(--grape); font-size: 14px; line-height: 1.6; }

@media (max-width: 900px) {
  .guide { grid-template-columns: minmax(0, 1fr); gap: 24px; }
  .guide-contents { position: static; }
}

/* ---------------------------------------------------------------------------
   Key judgments, and the day's lead story.

   The two sections a briefing has and a dashboard does not: the things to take
   away, and the one item worth reading at length. Everything else on the page
   answers a standing question; these two are about today.
   --------------------------------------------------------------------------- */

/* Numbered, because they are ordered by importance and a bullet hides that. The
   numeral is set in the display face and in grape, so the list reads as an
   editorial device rather than as an ordered list of tasks. */
/* The kicker and the sparkline on one line: a sparkline is a mark on a
   sentence, not a section, so it sits beside the heading rather than under it. */
.judgments-head { display: flex; align-items: baseline; justify-content: space-between;
                  gap: 16px; flex-wrap: wrap; }
.judgments-head .issue-kicker { margin-bottom: 0; }
.judgment-spark { display: inline-flex; align-items: center; gap: 8px; }
.judgment-spark svg { display: block; width: 120px; height: 22px; }
.judgment-list { margin-top: 16px; list-style: none; margin: 0; padding: 0; counter-reset: judgment; }
.judgment { counter-increment: judgment; position: relative; padding-left: 34px;
            margin-bottom: 14px; font-size: 16px; line-height: 1.6;
            max-width: 68ch; }
.judgment::before { content: counter(judgment); position: absolute; left: 0; top: 1px;
                    font-family: var(--font-display); font-size: 15px; font-weight: 600;
                    color: var(--grape); }
.judgment:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------------------------
   Item briefs, at the weight the edition gives them.

   A brief is the unit a real briefing is made of: a headline saying what the
   item *means*, the record's own title underneath so the reader can see which
   advisory is being discussed, the prose, an inset where the day's data
   supports one, and the action. `full` is that; `compact` is a line in an
   "also today" list, which is what an edition does with items that are real
   but not today's story.
   --------------------------------------------------------------------------- */
.brief--full { border-left: 3px solid var(--grape); padding-left: 20px;
               margin-bottom: 30px; }
.brief-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.brief-headline { font-family: var(--font-display); font-size: 20px; line-height: 1.3;
                  font-weight: 600; margin: 0; max-width: 42ch; }
.brief-meta { margin-top: 7px; }
.brief-narrative { margin-top: 13px; font-size: 15.5px; line-height: 1.65; max-width: 68ch; }
.brief-action { margin-top: 14px; padding: 12px 14px; border-radius: var(--radius);
                background: var(--surface-2); font-size: 14px; line-height: 1.55;
                max-width: 68ch; }

/* Compact: one line, still severity-badged, still linked. An "also today" entry
   is a thing somebody may want to open, not a footnote about it. */
.brief--compact { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
                  padding: 9px 0; border-bottom: 0.5px solid var(--hairline);
                  font-size: 14px; }
.brief--compact:last-child { border-bottom: 0; }

/* The inset table. Bordered rather than ruled, because it is an object inside a
   passage rather than a section of the page — a reader should be able to see
   where the prose stops and the data starts. */
.brief-inset { margin-top: 16px; max-width: 68ch; border: 0.5px solid var(--hairline);
               border-radius: var(--radius); }
.inset-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.inset-table th { text-align: left; font-size: 11px; letter-spacing: 0.06em;
                  text-transform: uppercase; color: var(--slate); font-weight: 600;
                  padding: 10px 14px; background: var(--surface-2); }
.inset-table td { padding: 10px 14px; border-top: 0.5px solid var(--hairline); }

/* How much of the portfolio an advisory reached. Grape, not the severity ramp:
   a proportion is not a severity, and the badge beside it is the thing that
   means urgency. */
.brief-reach { margin-top: 16px; max-width: 44ch; }
.brief-reach-label { font-size: 13.5px; margin-bottom: 7px; }
.brief-reach-track { height: 8px; border-radius: 4px; background: var(--surface-2);
                     overflow: hidden; }
.brief-reach-fill { display: block; height: 100%; background: var(--grape); }

/* An advisory's own dates, inside the passage about it.

   Reuses `.timeline` rather than restating it, so a mark here and a mark in
   Looking Ahead are the same object — they mean the same thing and drawing them
   differently would suggest they did not. What differs is the axis: this one is
   drawn to the advisory's furthest date rather than to a fixed fortnight, which
   is why it carries a written key underneath instead of "today / +14d". */
.brief-timeline { margin-top: 16px; max-width: 60ch; }
.brief-timeline .timeline { margin-top: 10px; }
.brief-timeline-key { list-style: none; margin: 10px 0 0; padding: 0;
                      font-size: 13px; display: grid; gap: 5px; }
.brief-timeline-key li { display: flex; gap: 10px; align-items: baseline; }
.brief-timeline-when { font-family: var(--font-mono); font-size: 11.5px;
                       color: var(--slate); min-width: 5.5ch; }

/* How often this kind of exposure has arrived. Grape and small: it is a mark on
   a passage, not a section, and the same treatment as the sparkline beside the
   key judgments because they are the same kind of claim about different rows. */
.brief-trend { margin-top: 16px; display: inline-flex; align-items: center; gap: 8px; }
.brief-trend svg { display: block; width: 120px; height: 22px; }

/* The greeting is the platform's; the sentence after it is the agent's. */
.issue-greeting { color: var(--grape); font-weight: 700; }

/* What you may be asked. A definition list, because that is what it is: the
   question a reader will be put, and the answer they can give. */
.asked-list { margin: 0; }
.asked-question { font-weight: 600; font-size: 15px; margin-top: 16px; }
.asked-question:first-child { margin-top: 0; }
.asked-answer { margin: 6px 0 0; font-size: 14.5px; line-height: 1.6; color: var(--slate);
                max-width: 68ch; padding-left: 16px; border-left: 2px solid var(--hairline); }

/* Looking ahead: the prose and the dates side by side, since one says what they
   mean and the other says when. */
.issue-ahead-prose { font-size: 15px; line-height: 1.65; max-width: 68ch; }
.ahead-dates { list-style: none; margin: 16px 0 0; padding: 0; }
.ahead-dates li { display: flex; gap: 12px; align-items: baseline; padding: 7px 0;
                  border-top: 0.5px solid var(--hairline); font-size: 14px; }
.ahead-date { font-family: var(--font-display); font-weight: 600; min-width: 56px; }
.ahead-label { min-width: 0; }

/* A developing story leads with its one item, given the full measure before
   anything else on the page. */
.edition-lead .brief--full { border-left-width: 4px; }
.edition-lead .brief-headline { font-size: 25px; max-width: 36ch; }
.edition-lead .brief-narrative { font-size: 16.5px; }
/* A quiet day gives Looking Ahead the room the briefs would have had. */
.edition-ahead-wide .issue-ahead-prose { font-size: 16px; }

/* The lead story sits inside a ruled block rather than a card: a card would make
   it a widget beside the others, and this is the passage the page is built
   around. The rule is the same grape the masthead uses, so the two read as the
   top of one issue. */
.issue-lead-story { border-left: 3px solid var(--grape); padding-left: 20px; }
.lead-story-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.lead-story-title { font-family: var(--font-display); font-size: 22px; line-height: 1.28;
                    font-weight: 600; margin: 0; max-width: 40ch; }
.lead-story-title a { color: var(--ink); text-decoration: none; }
.lead-story-title a:hover { color: var(--grape); }
.lead-story-meta { margin-top: 8px; }
.lead-story-body { margin-top: 14px; font-size: 15.5px; line-height: 1.65; max-width: 68ch; }
/* The action under the item it is about, so a reader who stops here still knows
   what to do. It is deliberately not in the table below — one instruction, one
   place. */
.lead-story-action { margin-top: 14px; padding: 12px 14px; border-radius: var(--radius);
                     background: var(--surface-2); font-size: 14px; line-height: 1.55;
                     max-width: 68ch; }

/* The action list as a table: these are a work list rather than a passage, and
   stacked paragraphs read as more prose on a page that is mostly prose. */
.action-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.action-table th { text-align: left; font-size: 11px; letter-spacing: 0.06em;
                   text-transform: uppercase; color: var(--slate); font-weight: 600;
                   padding: 0 14px 8px 0; border-bottom: 0.5px solid var(--hairline); }
.action-table td { padding: 12px 14px 12px 0; vertical-align: top;
                   border-bottom: 0.5px solid var(--hairline); }
.action-table tr:last-child td { border-bottom: 0; }
/* Priority is the item's own severity in a word. It carries no colour of its
   own — the severity ramp belongs to the badges, and a second warm thing in the
   next column would compete with the one that means something. */
.action-priority { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase;
                   letter-spacing: 0.04em; color: var(--slate); }
.action-priority--critical, .action-priority--high { color: var(--ink); font-weight: 600; }

/* ---------------------------------------------------------------------------
   Beats, composed — not a rail.

   This page had every figure at one width down one edge, which is a dashboard
   however the prose beside it reads. Editorial layout's answer is older than
   the web: vary the scale and the side, let one image lead, and alternate dense
   passages with airy ones so the reader does not fatigue on the way down.

   The variation is not decoration, because it is not chosen here. A figure's
   *shape* decides what it can be — a radar is square and sits beside prose, a
   timeline is a strip and runs across the measure — and the day's *ranking*
   decides which block leads the issue and which strip gets the plate. Both come
   out of `charts.compose()`, both change when the day does, and neither changes
   when it does not.

   Four shapes, and a beat is exactly one of them.
   --------------------------------------------------------------------------- */
.beat { display: grid; gap: 34px 44px; align-items: start; }
.beat-prose { min-width: 0; }
.beat-prose > * + * { margin-top: 14px; }
.beat-prose > .issue-kicker + * { margin-top: 0; }
.beat-figure { min-width: 0; }

/* Airy. No figure, so the prose takes a measure and the rest of the line is
   white — which is the rest a page needs between two dense passages, not a gap
   waiting to be filled. Filling it is what produced the rail. */
.beat--full { grid-template-columns: minmax(0, 1fr); }
.beat--full .beat-prose { max-width: 72ch; }

/* Some beats are a list, not a paragraph, and a list has no measure to keep.
   Four figureless beats in a row left the same empty band down the right of the
   page four times, which is the rail again in negative — the research phrase is
   that a run of airy spreads "feels thin". These take the width and set their
   list in columns instead.

   `columns: 22rem` is a column *width*, not a count, so the same rule gives two
   columns across a full measure and one inside a beat that also has a figure —
   no breakpoint to keep in step, and no way for it to produce a 180px column of
   broken words. */
/* Specificity, not order: `.beat--full .beat-prose` is two classes deep and
   would keep winning the 72ch cap over a single-class override. The exact
   cascade collision the house rules warn about, and it fails silently — the
   list simply stays one column and looks like the rule was never written. */
.beat--full .beat-prose--wide, .beat-prose--wide { max-width: none; }
/* `display: block` is not tidying — multi-column is simply ignored on a flex
   container, so the list took the full width as one column and set a 1300px
   measure, which is worse than the 72ch it started at. */
.beat-prose--wide .exposure-list,
.beat-prose--wide .setup-list {
  display: block; columns: 28rem; column-gap: 44px;
}
.beat-prose--wide .exposure-list li,
.beat-prose--wide .setup-list li { break-inside: avoid; margin-bottom: 12px; }

/* Asymmetric on purpose: equal columns are for spreadsheets. The prose leads
   and the figure supports, which is also the reading order. */
.beat--right { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); }
.beat--left { grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr); }
/* Prose stays first in the document and moves second on screen — the eye
   crosses the measure, a screen reader still gets the words before the
   picture of them. */
.beat--left > .beat-figure { grid-column: 1; grid-row: 1; }
.beat--left > .beat-prose { grid-column: 2; grid-row: 1; }

/* The masthead is the same machinery: headline left, the day's lead figure
   right. Which figure that is changes daily, so the top of the page does. */
.beat--masthead { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
                  margin-top: 14px; }
/* When the day's strongest figure is a strip, the masthead is one column and
   the figure runs beneath the words across the full measure. A timeline in a
   480px column is the squeeze `PANEL_SHAPES` exists to prevent — so the shape
   changes the arrangement rather than disqualifying the figure. */
.beat--masthead-stacked { grid-template-columns: minmax(0, 1fr); gap: 20px 0; }
.beat--masthead-stacked .beat-figure-lead { grid-column: 1; }
.beat--masthead-stacked .beat-figure-lead .chart-svg { max-height: 120px; }

/* Capped, because the radar is square and a square in a 420px column is 420px
   tall — which pushed the lead paragraph most of a screen down and left the
   headline sitting over a hole. It is the largest figure in the issue; it is
   not a hero image. */
.beat-figure-lead .chart-svg { max-height: 210px; }
.beat-figure-lead .chart-panel { border-top-width: 2px; }

/* ---------------------------------------------------------------------------
   The motif.

   A flat abstract mark for the kind of story the day led with, drawn inline in
   the brand's own tokens for the same three reasons the charts are: no
   generation latency, nothing to fail or come back off-brand, and it follows
   the theme instead of being a picture of one theme baked into a file.

   It is deliberately *not* a chart and must not be mistaken for one, so it has
   no panel, no border and no caption — a mark on the page, sized by the day's
   edition and centred in whatever it is given. Amber and red never appear in
   it: those are the severity ramp, and a "signal" motif drawn in warning
   colours would make every vulnerability morning look like an emergency before
   the reader had read a word.
   --------------------------------------------------------------------------- */
.motif { display: block; margin: 0 auto; width: 100%; height: auto; }
.motif--large { max-width: 168px; }
.motif--medium { max-width: 112px; }
.motif--small { max-width: 64px; }
.beat-figure--motif { display: flex; align-items: center; justify-content: center;
                      padding: 8px 0 4px; }

/* ---------------------------------------------------------------------------
   Editions.

   The day's shape, applied to the masthead only. Everything below it is placed
   by `charts.compose()` from the same day's material, so an edition that also
   restyled the sections would be a second opinion about the same evidence —
   and the two would disagree on the mornings it mattered.

   Three real differences and no more. A developing story earns weight because
   one item dominates; a quiet day earns air because the honest thing to say is
   that little happened, and shouting it in 27px would be the manufactured
   urgency the whole mechanism exists to prevent. Standard and roundup are the
   default, which is what a default is for.
   --------------------------------------------------------------------------- */
.issue-head--developing_story .issue-rule { height: 6px; }
.issue-head--developing_story .issue-headline { font-size: 32px; max-width: 40ch; }
.issue-head--developing_story .motif--large { max-width: 192px; }

.issue-head--quiet .issue-rule { height: 2px; opacity: 0.7; }
.issue-head--quiet .issue-headline { font-size: 23px; }
.issue-head--quiet .issue-lead { margin-bottom: 46px; }
.issue-head--quiet .motif--large { max-width: 132px; }

/* A strip runs the measure, and is where the page takes a breath. */
.beat-strip { margin-top: 26px; }
/* Under a kicker, at caption scale. Full width it was a thousand pixels of red
   on the one page whose argument is that most of this does not touch you. */
.beat-strip--tight { margin-top: 12px; max-width: 480px; }
/* The issue's one big picture: the day's strongest strip, given room and ruled
   off top and bottom so it reads as a plate rather than as another row. */
.beat-strip--plate { margin-top: 30px; padding: 20px 0 22px;
                     border-top: 2px solid var(--grape);
                     border-bottom: 0.5px solid var(--hairline); }
.beat-strip--plate .chart-svg { max-height: 190px; }

/* A figure, not a card.

   Set editorially wherever it lands — a rule, a label, the graphic — and the
   bordered white box dropped. A box says "widget", and a newsletter has none.
   Applied by position rather than by the partials, so a new chart inherits it
   without anybody remembering to. */
.beat-figure .chart-panel,
.beat-strip .chart-panel {
  background: none; border: 0; border-radius: 0; margin: 0;
  border-top: 2px solid var(--grape); padding: 11px 0 0;
}
.beat-strip--plate .chart-panel { border-top: 0; padding-top: 0; }
.beat-figure .chart-panel h3,
.beat-strip .chart-panel h3 {
  font-family: var(--font-body); font-size: 10.5px; letter-spacing: 0.08em;
  text-transform: uppercase; font-weight: 600; color: var(--slate);
  margin: 0 0 6px;
}
.beat-figure .chart-panel .chart-note,
.beat-strip .chart-panel .chart-note {
  font-size: 11.5px; line-height: 1.5; color: var(--slate); margin: 6px 0 0;
}
.beat-strip .mix-key { margin-bottom: 0; }

.issue-list { list-style: none; margin: 0; padding: 0; display: flex;
              flex-direction: column; gap: 10px; }
.issue-list li { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
                 padding-bottom: 10px; border-bottom: 0.5px solid var(--hairline); }
.issue-list li:last-child { border-bottom: 0; padding-bottom: 0; }
.issue-link { font-family: var(--font-display); font-size: 15px; font-weight: 500;
              color: var(--ink); }
.issue-link:hover { color: var(--grape); }

/* The recommendation, set apart. A left edge rather than a box: it is an aside
   in the prose, not another widget. */
.issue-callout { border-left: 3px solid var(--grape); background: var(--heat-0);
                 padding: 12px 16px; border-radius: 0 var(--radius) var(--radius) 0;
                 margin-top: 18px; max-width: 70ch; }
.issue-actions { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }

.issue-dates { list-style: none; margin: 14px 0 0; padding: 0; display: flex;
               flex-direction: column; gap: 2px; }
.issue-dates li { display: flex; gap: 14px; align-items: baseline; padding: 8px 12px;
                  border-radius: var(--radius); border-left: 3px solid var(--hairline-2); }
.issue-dates li:nth-child(odd) { background: var(--surface-2); }
.issue-date-when { flex: none; width: 96px; font-weight: 600; font-size: 13px;
                   font-variant-numeric: tabular-nums; }
.issue-date-what { min-width: 0; font-size: 13.5px; }
.issue-due-count { margin: -6px 0 0; }

/* The shape of what is open, before the list of it. One bar, segments
   proportional. Severity is the one subject amber and red belong to, and this
   is a chart of severity. */
.mix-bar { position: relative; height: 10px; border-radius: 5px; overflow: hidden;
           background: var(--surface-2); margin-bottom: 8px; }
.mix-seg { position: absolute; top: 0; bottom: 0; }
/* Critical and high share a colour, as they do on every badge — the design
   guidance maps the ramp to exactly three, and inventing a fourth here would put
   a hue on the page that nothing else uses. The count beside each keeps them
   apart. */
.mix-seg.sev-critical, .mix-dot.sev-critical,
.mix-seg.sev-high, .mix-dot.sev-high { background: var(--sev-high); }
.mix-seg.sev-medium, .mix-dot.sev-medium { background: var(--sev-medium); }
.mix-seg.sev-low, .mix-dot.sev-low { background: var(--sev-low); }
.mix-key { display: flex; gap: 14px; flex-wrap: wrap; margin: 0 0 18px;
           font-size: 11.5px; color: var(--slate); }
.mix-key-item { display: inline-flex; align-items: center; gap: 5px; }
.mix-dot { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }

/* How much moved on each exposure axis. Ticks rather than a number: the list
   was seven lines of prose and a reader scanning it could not see which of the
   seven was actually busy. */
.exposure-head { display: flex; align-items: center; gap: 8px; }
.exposure-bar { display: inline-flex; gap: 2px; }
/* Open work by product category. The svg's height comes from the data, set
   inline, so a two-category portfolio draws a two-bar box rather than sitting
   in one built for six. The key is a list beside it rather than axis labels
   inside it — labels inside would set the width by the longest category name. */
.exposure-bars { margin: 16px 0 18px; display: flex; gap: 18px; align-items: flex-start;
                 flex-wrap: wrap; }
.exposure-bars svg { display: block; width: 260px; max-width: 100%; }
.exposure-bars .issue-kicker-sm { flex-basis: 100%; }
.exposure-bar-key { list-style: none; margin: 0; padding: 0; font-size: 12.5px; }
.exposure-bar-key li { display: flex; gap: 10px; justify-content: space-between;
                       height: 24px; align-items: center; }
.exposure-bar-name { color: var(--slate); }
.exposure-bar.heat-0 { fill: var(--heat-0); }
.exposure-bar.heat-1 { fill: var(--heat-1); }
.exposure-bar.heat-2 { fill: var(--heat-2); }
.exposure-bar.heat-3 { fill: var(--heat-3); }
.exposure-bar.heat-4 { fill: var(--heat-4); }
.issue-exposure-summary { font-size: 15px; line-height: 1.65; max-width: 68ch;
                          margin-bottom: 14px; }

.exposure-tick { width: 3px; height: 10px; border-radius: 1px; background: var(--heat-3); }

.exposure-list { list-style: none; margin: 0; padding: 0; display: flex;
                 flex-direction: column; gap: 8px; }
.exposure-list li { display: flex; flex-direction: column; gap: 1px; padding-left: 11px;
                    border-left: 2px solid var(--hairline); }
/* Exposed themes carry the accent; the rest stay quiet but stay *listed* —
   "we looked and it does not apply to you" is itself a finding. */
.exposure-list li.is-exposed { border-left-color: var(--grape); }
.exposure-list li.is-exposed strong { color: var(--ink); }
.exposure-list li:not(.is-exposed) strong { color: var(--slate); font-weight: 500; }

.issue-setup { border-top-color: var(--grape); }
.setup-list { list-style: none; margin: 0; padding: 0; display: flex;
              flex-direction: column; gap: 8px; }
.setup-list li { display: flex; flex-direction: column; gap: 1px; }
.setup-list .setup-step-done { opacity: 0.55; }
.setup-list .setup-step-label { text-decoration: line-through; }

/* The setup rail: one line above the page content, on every app page while
   anything is outstanding. Grape rather than a warm hue — unfinished setup is
   not a severity, and amber and red are reserved for the badges. */
.setup-rail {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 16px; margin-bottom: 20px;
  background: var(--surface); border: 0.5px solid var(--hairline);
  border-left: 3px solid var(--grape); border-radius: var(--radius);
  font-size: 13.5px;
}
.setup-rail-count {
  font-weight: 600; color: var(--grape); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.setup-rail-why { color: var(--slate); flex: 1 1 22ch; min-width: 0; }
.setup-rail-all { margin-left: auto; white-space: nowrap; }

/* The welcome page's map of the nav. A definition list in spirit: the name is
   the link, the reason sits under it, and "More" goes to the guide section that
   explains it properly rather than the top of a long page. */
.welcome-surfaces { list-style: none; margin: 0; padding: 0;
                    display: flex; flex-direction: column; gap: 14px; }
.welcome-surfaces li {
  display: grid; grid-template-columns: minmax(9rem, auto) 1fr auto;
  gap: 4px 14px; align-items: baseline;
}
.welcome-surface-name { font-weight: 600; }
.welcome-surface-why { color: var(--slate); font-size: 14px; }
.welcome-surface-more { white-space: nowrap; }
@media (max-width: 640px) {
  .welcome-surfaces li { grid-template-columns: 1fr auto; }
  .welcome-surface-why { grid-column: 1 / -1; }
}
.issue-footnote { color: var(--slate); }
.issue-footnote .btn { margin-top: 12px; }

/* Stacked well before the rail gets thin. At 900px the two columns still fit,
   and that is the trap: a 300px rail leaves the reading column about forty
   characters, which is a newspaper column's width for a paragraph that is not
   set as one. The figures go under the words they illustrate instead, side by
   side while there is room for two. */
@media (max-width: 1024px) {
  .beat, .beat--right, .beat--left, .beat--masthead {
    grid-template-columns: minmax(0, 1fr); gap: 24px;
  }
  /* Both halves back to one column, and the figure back under the words it
     illustrates — including the ones that were placed on the left, or they
     would read before the prose they belong to. */
  .beat--left > .beat-figure, .beat--left > .beat-prose {
    grid-column: 1; grid-row: auto;
  }
  .issue-headline { font-size: 24px; }
  .issue-date-when { width: auto; }
}

/* The activity strip. A shape answers "is anything happening" before any number
   does, and a flat line with thirty active sources is the finding. */
.spark { display: flex; align-items: flex-end; gap: 2px; height: 64px; margin-top: 12px; }
.spark-bar { flex: 1 1 auto; background: var(--heat-2); border-radius: 1px; min-height: 1px; }
.spark-bar:hover { background: var(--grape); }

.chart-legend { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin-top: 10px; font-size: 11px; color: var(--slate); }
.chart-legend .swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; vertical-align: -1px; margin-right: 4px; }
.chart-legend .swatch-outline { border: 1px dashed var(--heat-line); }
.chart-legend .swatch-outline-grape { border: 1px dashed var(--heat-2); }
.chart-legend .swatch-dot { border-radius: 50%; background: var(--grape); width: 8px; height: 8px; }

@media (max-width: 980px) {
  .chart-grid, .chart-grid-1, .chart-grid-2, .chart-grid-3 { grid-template-columns: 1fr; }
}
