/**
 * hub.css — Styling for the Skald landing page.
 *
 * Self-contained rather than importing an app's css/themes.css: the hub sits
 * one level above the apps and must render before any of them is chosen, so it
 * cannot depend on a particular app's folder still being there. The palette is
 * deliberately the same terracotta-on-cream family the apps use, so the hub
 * reads as the same product.
 *
 * The apps carry several named themes and a picker; the hub has exactly two
 * looks. Anything more is a second theming system to keep in sync for a page a
 * learner spends ten seconds on.
 */

/*
 * ── LIGHT AND DARK ─────────────────────────────────────────────────────────
 *
 * Two palettes, each declared exactly once, chosen by `data-theme` on <html>.
 *
 * The default is still whatever the OS asks for. That gets resolved into a
 * concrete `data-theme` before the first paint, by the inline script in
 * <head> — and doing it there rather than in a `prefers-color-scheme` media
 * query is precisely what keeps this to one dark palette. A media query cannot
 * also match an explicit choice, so a toggle built that way needs the same
 * twenty variables written twice, and the two copies drift.
 *
 * A browser with scripting off therefore gets light. That is not worth solving
 * here: the app table, the connection form and the key test are all
 * JavaScript, so such a browser has no page to read the palette of.
 *
 * `color-scheme` is not decoration — it is what makes the native controls
 * (text inputs, the provider <select>, scrollbars) follow along. Without it
 * they stay light inside a dark page.
 */
:root {
  color-scheme: light;

  --bg:            #FBF7F0;
  --bg-elevated:   #FFFFFF;
  --bg-subtle:     #F3EDE2;
  --text:          #2C2419;
  --text-muted:    #6E5F4F;
  --accent:        #C4553A;
  --accent-hover:  #B04A32;
  --accent-text:   #9E3E28;
  --on-accent:     #FFFFFF;
  --border:        #CFC4B2;
  --border-subtle: #E5DDD0;
  --ok:            #2A7244;
  --ok-bg:         #E6F4EC;
  --warn:          #A66D1B;
  --warn-bg:       #FFF3DB;
  --err:           #B83C22;
  --err-bg:        #FCE8E3;
  --shadow:        0 1px 3px rgba(44, 36, 25, 0.06), 0 4px 12px rgba(44, 36, 25, 0.07);
  --radius:        14px;
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:            #1A1713;
  --bg-elevated:   #241F1A;
  --bg-subtle:     #2E2822;
  --text:          #F2EBE0;
  --text-muted:    #B3A695;
  --accent:        #D9694E;
  --accent-hover:  #E67A5F;
  --accent-text:   #E8927B;
  --on-accent:     #1A1713;
  --border:        #443A31;
  --border-subtle: #332C26;
  --ok:            #6BBF8A;
  --ok-bg:         #1C3326;
  --warn:          #D9A441;
  --warn-bg:       #33280F;
  --err:           #E5765C;
  --err-bg:        #3A1E17;
  --shadow:        0 1px 3px rgba(0, 0, 0, 0.3), 0 4px 12px rgba(0, 0, 0, 0.25);
}

* { box-sizing: border-box; }

/* ── Language ────────────────────────────────────────────── */

/*
 * The page carries both languages in the DOM and hides one.
 *
 * The alternative — swapping text from a string table after load — means every
 * reader watches the page change language on every visit, and means the page is
 * blank to a browser with scripting off. This way the served HTML is already a
 * complete, correct page in whichever language was chosen; the inline script in
 * <head> only sets an attribute.
 *
 * `display` rather than `hidden`/`visibility`: hidden text must not be read by a
 * screen reader, must not be found by the browser's own Ctrl+F, and must not
 * leave a gap.
 */
/*
 * THREE FORMS SINCE 2026-09-09, and the rule is written out in full rather
 * than as `[lang]:not([lang="..."])`. The negation form is shorter and it is
 * also wrong here: it would hide every element carrying ANY other `lang`,
 * including the `lang="es"` and `lang="it"` spans the app list writes for
 * language names. Six pairs, named one by one, hide only what this page owns.
 */
:root[data-lang="nb"] [lang="nn"],
:root[data-lang="nb"] [lang="en"],
:root[data-lang="nn"] [lang="nb"],
:root[data-lang="nn"] [lang="en"],
:root[data-lang="en"] [lang="nb"],
:root[data-lang="en"] [lang="nn"] { display: none; }

/*
 * The switcher is the one exception. Every option carries the language of its
 * own label, so the rule above would otherwise hide exactly the ones the
 * reader came to click. It is a `<select>` since 2026-09-09 — four buttons in
 * one row measured 372 px against 350 available on a 390 px phone, and the
 * founder chose a dropdown labelled «Languages» over shortened labels.
 */
:root[data-lang] .lang-switch [lang] { display: revert; }

body {
  margin: 0;
  padding: 0 1.25rem 3rem;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

main, .hub-header, .hub-footer { max-width: 46rem; margin: 0 auto; }

/* ── Header ──────────────────────────────────────────────── */

.hub-header { padding: 1.25rem 0 2rem; text-align: center; }

/* Which language I read this in and which palette I read it on are the same
   kind of choice — how the page is presented to me — so they share one
   right-aligned row instead of being hunted for in two different corners. */
.header-controls {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 1.5rem;
}

.lang-switch { display: flex; gap: 0.4rem; }

/* One pill, defined once for both controls. They sit next to each other, so
   any difference between them would read as a mistake rather than a meaning. */
.lang-btn,
.theme-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.7rem;
  font-size: 0.875rem;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
}

.lang-btn:hover,
.theme-btn:hover { color: var(--text); background: var(--bg-subtle); }

/* aria-pressed is the state, so it drives the styling too — they cannot
   disagree the way a separate .active class eventually would. */
.lang-btn[aria-pressed="true"] {
  color: var(--text);
  background: var(--bg-elevated);
  border-color: var(--border);
  font-weight: 600;
}

/* The theme button never gets that selected look — it is an action, not one of
   two options on offer. The faint outline stops it reading as disabled beside
   the language pill that IS selected. */
.theme-btn { border-color: var(--border-subtle); }

/* Exactly one label is in the flow: the one naming where pressing would take
   you. The language rule further up then hides one of the two inside it. No
   script in either step, which is why the button is right on the first frame. */
.theme-label { display: inline-flex; align-items: center; gap: 0.4rem; }
:root[data-theme="dark"] .theme-to-dark { display: none; }
:root:not([data-theme="dark"]) .theme-to-light { display: none; }

.lang-flag,
.theme-icon { font-size: 1.05rem; line-height: 1; }

.wordmark {
  margin: 0;
  font-size: clamp(2.25rem, 7vw, 3rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--accent-text);
}

.tagline { margin: 0.5rem 0 0; color: var(--text-muted); font-size: 1.05rem; }

/* Smaller than the line above it, and deliberately not a warning box. This is a
   standing property of generated content rather than an alert about something
   that just went wrong, and a box with a dismiss affordance would teach the
   reader to stop seeing it — which is the opposite of what it is for. */
.tagline.caution { margin-top: 0.4rem; font-size: 0.9rem; }

/* ── Cards & sections ────────────────────────────────────── */

.card {
  background: var(--bg-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.5rem;
}

h2 { margin: 0 0 0.35rem; font-size: 1.15rem; letter-spacing: -0.01em; }

/*
 * The collapsed connection form. Borrows the chevron from .faq below rather
 * than inventing a second disclosure control - two ways of saying "this opens"
 * on one page is one too many.
 */
.panel > summary {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

.panel > summary::-webkit-details-marker { display: none; }

.panel > summary::before {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}

.panel[open] > summary::before { transform: rotate(45deg); }

.panel > summary:hover { color: var(--accent-text); }

/* The state, so the summary says something worth reading while closed. */
.panel-status {
  font-weight: 400;
  font-size: 0.9rem;
  color: var(--text-muted);
  /* A long model name must not push the chevron off a phone screen. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-intro { margin: 0 0 1.25rem; color: var(--text-muted); font-size: 0.925rem; }

section#apps, section#faq { margin-top: 2.5rem; }

a { color: var(--accent-text); }

/* ── Form ────────────────────────────────────────────────── */

.field { margin-bottom: 1.1rem; flex: 1 1 12rem; }

.field-row { display: flex; flex-wrap: wrap; gap: 1rem; }

label { display: block; margin-bottom: 0.35rem; font-size: 0.875rem; font-weight: 600; }

.optional { font-weight: 400; color: var(--text-muted); }

input[type="text"], input[type="password"], select {
  width: 100%;
  padding: 0.6rem 0.75rem;
  font: inherit;
  font-size: 0.95rem;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 9px;
}

input:focus-visible, select:focus-visible, button:focus-visible,
.app-link:focus-visible, .skip-link:focus {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.key-row { display: flex; gap: 0.5rem; }

/* The button beside a field in a .key-row keeps its words on one line. "Vis"
   never needed this; "Lås opp" and "Unlock" do, and a two-word button broken
   across two lines beside a single-line input reads as a rendering fault. */
.key-row > .btn,
.key-row > .btn-ghost,
.key-row > .btn-secondary { white-space: nowrap; }

/* The two boxes when a password is being chosen — side by side on a desktop,
   stacked on a phone, because two half-width password fields at 360px are two
   fields nobody can read the placeholder of. */
#passcode-form .key-row { flex-wrap: wrap; }
#passcode-form .key-row > input { flex: 1 1 12rem; }

/* Shown only when the chosen provider takes no key. The field itself always
   stays — some local servers are started with one, and hiding the box would
   make that setup impossible with nothing on screen to explain why. */
.if-key-optional { display: none; }
#connection-form.key-optional .if-key-optional { display: inline; }

/* The mixed-content notice. It explains rather than blocks: the local option
   stays selectable, because reading this is how most people find out the local
   path exists at all. */
.warn {
  margin: 0 0 1.1rem;
  padding: 0.7rem 0.85rem;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--warn);
  background: var(--warn-bg);
  border-radius: 9px;
}

.warn a { color: inherit; }

/* The provider table has four columns and cannot fit a phone. It scrolls inside
   its own box so the PAGE never scrolls sideways — a body that slides left and
   right makes the whole site feel broken, and the reader loses their place in
   text that has nothing to do with the table. `tabindex` because a scrollable
   region has to be reachable by keyboard, not only by swiping. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
}

.table-scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.hint { margin: 0.4rem 0 0; font-size: 0.825rem; color: var(--text-muted); }

.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin-top: 0.5rem; }

button { font: inherit; font-size: 0.925rem; border-radius: 9px; cursor: pointer; }

.btn, .btn-secondary, .btn-ghost { padding: 0.55rem 1.1rem; border: 1px solid transparent; }

/* A download is a link, not a <button> -- it navigates. But an <a> carrying a
   button class inherits the underline every other link here wants, which reads
   as a mis-styled link rather than a button. The shape is the affordance; the
   underline fights it. */
a.btn, a.btn-secondary, a.btn-ghost { text-decoration: none; }

.btn { background: var(--accent); color: var(--on-accent); font-weight: 600; }
.btn:hover { background: var(--accent-hover); }

.btn-secondary { background: transparent; color: var(--accent-text); border-color: var(--border); }
.btn-secondary:hover { background: var(--bg-subtle); }

.btn-ghost { background: var(--bg-subtle); color: var(--text-muted); border-color: var(--border-subtle); white-space: nowrap; }
.btn-ghost:hover { color: var(--text); }

button[disabled] { opacity: 0.55; cursor: default; }

.result { font-size: 0.875rem; font-weight: 600; }
.result.ok  { color: var(--ok); }
.result.err { color: var(--err); }

/* The link to the FAQ entry that explains a status code. Deliberately lighter
   than the message beside it: the sentence is the answer and this is the way to
   more of it, so it must not compete for the first read. It keeps the message's
   colour so the pair reads as one thing rather than as an unrelated link that
   happened to land next to an error. */
.result-help {
  font-weight: 400;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.15em;
  white-space: nowrap;
}

/* ── The model picker ────────────────────────────────────── */

/*
 * The list is IN THE FLOW, not floating over the page.
 *
 * A floating menu is the usual choice and it is the wrong one here. This field
 * is filled on a phone, with the keyboard up, and an absolutely-positioned list
 * under the input lands behind the keyboard — the reader sees nothing happen
 * and concludes the button is broken. In the flow it pushes the rest of the
 * panel down, stays above the keyboard, and scrolls with the page.
 *
 * The cost is a layout shift when it opens. That is the right trade for a
 * control whose whole job is to be looked at.
 */
.combo { position: relative; }

.combo-list {
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0.25rem;
  /* Roughly six rows. Enough to read as a list rather than as a hint, short
     enough that the field it belongs to stays on screen above it. */
  max-height: 15rem;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 9px;
  box-shadow: var(--shadow);
}

.combo-option {
  /* Measured at 375px: 44px tall, which is the smallest target a thumb hits
     reliably and the number Apple's own guidance gives. 0.65rem came out at 40
     and was raised after measuring rather than after looking.

     A model id is long and must NOT be truncated — a list of ids all reading
     `mistralai/mistral-…` would be unusable — so it wraps instead. */
  padding: 0.75rem 0.7rem;
  border-radius: 7px;
  font-size: 0.9rem;
  line-height: 1.35;
  word-break: break-word;
  cursor: pointer;
}

/* Hover is the mouse; .is-active is the keyboard, set by model-combobox.js and
   mirrored to aria-activedescendant. Same look, because they mean the same
   thing to the reader. */
.combo-option:hover,
.combo-option.is-active { background: var(--bg-subtle); color: var(--text); }

.combo-status { margin-top: 0.35rem; }

/* ── The app table ───────────────────────────────────────── */

.apps-refresh { margin: 0 0 1.25rem; }

.refresh-btn { display: inline-flex; align-items: center; gap: 0.45rem; }

/* inline-block, or `transform` below is silently ignored — it does not apply to
   an inline box, and a <span> is one. */
.refresh-icon { display: inline-block; font-size: 1.05rem; line-height: 1; }

/* Turns while the page is being re-fetched, so the button reads as working
   rather than as ignored. The reload that follows ends it by replacing the
   page — there is nothing to switch off. */
.refresh-btn[disabled] .refresh-icon { animation: refresh-spin 0.9s linear infinite; }

@keyframes refresh-spin { to { transform: rotate(360deg); } }

/* Respect a reader who has asked the system for less motion: they still get the
   disabled state, just without the spin. */
@media (prefers-reduced-motion: reduce) {
  .refresh-btn[disabled] .refresh-icon { animation: none; }
}

/* A table can always exceed a narrow screen, whatever the styling says. Let it
   scroll inside this box rather than making the whole page scroll sideways. */
.table-wrap {
  overflow-x: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* The second table in the apps section — the one for apps that are not language
   pairs. Without this the two sit flush, and a reader meets a second header row
   mid-table and reads it as part of the first: one table whose column heading
   inexplicably changes from "Språk" to "Tema" halfway down. The gap is what
   says these are two lists. */
.table-wrap + .table-wrap { margin-top: 1.25rem; }

.app-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}

.app-table th, .app-table td {
  padding: 0.45rem 1.1rem;
  text-align: left;
  vertical-align: baseline;
}

/* A rule between LANGUAGE groups, not between every row. The blank cells in
   column one are what tie a group together visually, and a line through each
   one would cut the group into unrelated strips. */
.app-table tbody tr.group-start th,
.app-table tbody tr.group-start td { border-top: 1px solid var(--border-subtle); }

.app-table tbody tr.group-start th { padding-top: 0.7rem; }
.app-table tbody tr.group-start td { padding-top: 0.7rem; }

.app-table thead th {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--bg-subtle);
  white-space: nowrap;
}

/* The language cell is a row header, not data — it names what the row is. */
.app-table tbody th {
  font-weight: 600;
  white-space: nowrap;
}

.app-flags { margin-right: 0.5rem; font-size: 1.15rem; line-height: 1; }

.app-link {
  display: inline-block;
  color: var(--accent-text);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.app-link:hover { border-bottom-color: var(--accent); }

@media (max-width: 30rem) {
  .app-table th, .app-table td { padding: 0.45rem 0.7rem; }
}

#connection-state { margin: 0 0 0.9rem; }

.badge {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  font-size: 0.75rem;
  font-weight: 600;
  border-radius: 999px;
}

.badge.connected { color: var(--ok);   background: var(--ok-bg); }
.badge.offline   { color: var(--warn); background: var(--warn-bg); }

.empty { color: var(--text-muted); font-size: 0.9rem; }

/* ── FAQ ─────────────────────────────────────────────────── */

.faq {
  background: var(--bg-elevated);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius);
  margin-bottom: 0.6rem;
}

.faq summary {
  padding: 0.9rem 1.1rem;
  font-weight: 600;
  cursor: pointer;
  /* The default triangle sits too far left and ignores the font size. */
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  border-radius: var(--radius);
}

.faq summary::-webkit-details-marker { display: none; }

.faq summary::before {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}

.faq[open] summary::before { transform: rotate(45deg); }

.faq summary:hover { color: var(--accent-text); }

.faq-body {
  padding: 0 1.1rem 1.1rem;
  font-size: 0.95rem;
  border-top: 1px solid var(--border-subtle);
  padding-top: 1rem;
}

.faq-body p { margin: 0 0 0.9rem; }
.faq-body p:last-child { margin-bottom: 0; }

/* Numbered steps. The counter is drawn rather than using the list marker so it
   can sit beside a heading and a screenshot without the text wrapping under it. */
.steps {
  list-style: none;
  counter-reset: step;
  margin: 1.4rem 0;
  padding: 0;
}

.steps > li {
  counter-increment: step;
  position: relative;
  padding-left: 2.6rem;
  margin-bottom: 1.6rem;
}

.steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 1.85rem;
  height: 1.85rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 0.875rem;
  font-weight: 700;
  border-radius: 50%;
}

.steps h3 {
  margin: 0.15rem 0 0.5rem;
  font-size: 1rem;
  letter-spacing: -0.01em;
}

/* Screenshots of the OpenRouter flow. Sized to the page rather than to the
   capture, so a wide screenshot never forces the page to scroll sideways. */
.steps figure {
  margin: 0.9rem 0 0;
}

.steps img {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--bg-subtle);
}

.steps figcaption {
  margin-top: 0.45rem;
  font-size: 0.825rem;
  color: var(--text-muted);
}

/* Underlined AND <strong>: the underline is what was asked for, the strong is
   what a screen reader needs to convey the same emphasis. */
.underline { text-decoration: underline; text-underline-offset: 0.15em; }

/* Provider comparison. Its own class rather than .app-table: three columns of
   prose that wraps, not two columns of links. */
.faq-table {
  width: 100%;
  margin: 0 0 1rem;
  border-collapse: collapse;
  font-size: 0.875rem;
}

.faq-table th, .faq-table td {
  padding: 0.45rem 0.6rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--border-subtle);
}

.faq-table thead th {
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}

.faq-table tbody th { font-weight: 600; white-space: nowrap; }
.faq-table tbody tr:last-child th,
.faq-table tbody tr:last-child td { border-bottom: 0; }

.faq-body ul { margin: 0 0 0.9rem; padding-left: 1.2rem; }
.faq-body li { margin-bottom: 0.3rem; }

.faq-body h3 {
  margin: 1.2rem 0 0.5rem;
  font-size: 0.925rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/*
 * A section inside an answer that something links to by name — the error codes,
 * and the per-provider blocks in the setup guide.
 *
 * Two things it needs that an ordinary h3 does not. It is a DESTINATION, so it
 * is styled as a heading a reader has just landed on rather than as a muted
 * label above a paragraph: no uppercase, because `500, 502, 503, 504` and
 * `mistral-small-latest` are not words and shouting them helps nobody. And it
 * needs `scroll-margin-top`, or an anchor lands with the heading welded to the
 * top edge of the screen and the answer looks like it starts mid-sentence.
 */
.faq-topic { scroll-margin-top: 1rem; }

.faq-topic > h3 {
  margin-top: 1.5rem;
  font-size: 1rem;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--text);
}

.faq-topic:first-of-type > h3 { margin-top: 0.6rem; }

/* Questions written to be copied into some other AI assistant. Styled as
   quotable blocks rather than bullets so it is obvious they are the text to
   take, not instructions about it. */
.ask { list-style: none; padding-left: 0; }

.ask li {
  padding: 0.55rem 0.8rem;
  margin-bottom: 0.4rem;
  background: var(--bg-subtle);
  border-left: 3px solid var(--border);
  border-radius: 0 7px 7px 0;
  font-size: 0.9rem;
}

.ask li::before { content: "\201C"; }
.ask li::after  { content: "\201D"; }

/* Both Norwegian forms take the angle quotes; English keeps the curly ones. */
:root[data-lang="nb"] .ask li::before,
:root[data-lang="nn"] .ask li::before { content: "\00AB"; }
:root[data-lang="nb"] .ask li::after,
:root[data-lang="nn"] .ask li::after  { content: "\00BB"; }

/* For the one thing in the walkthrough that is genuinely easy to get wrong. */
.callout {
  padding: 0.7rem 0.9rem;
  background: var(--warn-bg);
  color: var(--text);
  border-left: 3px solid var(--warn);
  border-radius: 0 9px 9px 0;
  font-size: 0.9rem;
}

code {
  padding: 0.1rem 0.35rem;
  background: var(--bg-subtle);
  border-radius: 5px;
  font-size: 0.875em;
}

/* ── Footer & a11y ───────────────────────────────────────── */

.hub-footer { margin-top: 3rem; text-align: center; color: var(--text-muted); font-size: 0.85rem; }

.sr-only {
  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;
  padding: 0.6rem 1rem;
  background: var(--accent); color: var(--on-accent);
  border-radius: 0 0 9px 0; text-decoration: none;
}

.skip-link:focus { left: 0; top: 0; }

@media (prefers-reduced-motion: reduce) {
  /* Kill movement, not geometry. A blanket `transform: none` also flattens the
     FAQ chevron, which is a static rotated square rather than an animation —
     the arrow would then point the same way open and closed. */
  * { transition: none !important; animation: none !important; }
}
