/* ============================================================================
   AceNotifier — design system.

   One idea holds this together: colour is reserved for meaning. The interface is
   almost monochrome, and green/amber/red appear only as state. A product whose
   job is to make you notice a failure cannot afford decorative red.

   Consequences, stated so they are not undone by accident:
   - Primary buttons are inverted (white on ink, ink on paper), not brand-coloured.
   - Borders do the separating, not shadows. Shadows only where something floats.
   - Radii are small: 6px. Large pills read as consumer software.
   - No gradients on text, no glow, no decorative icons, no emoji anywhere.
   - Motion is limited to state changes under 140ms. Nothing animates on scroll.
   ============================================================================ */

:root {
  /* Surfaces, back to front. Slightly cool ink rather than pure black. */
  --bg: #0b0c0e;
  --surface: #121417;
  --surface-2: #191c20;
  --surface-3: #22262b;
  --line: #23272d;
  --line-2: #31363d;

  --text: #eceef1;
  --muted: #969ca6;
  --dim: #6b727c;

  /* Primary action is an inversion of the page, not a brand colour. */
  --primary-bg: #ffffff;
  --primary-fg: #0b0c0e;
  --primary-bg-hover: #d8dade;

  --link: #7fa9f5;
  --focus: #5b8def;

  /* The only colours in the system that carry meaning. */
  --ok: #3fb950;
  --ok-bg: rgba(63, 185, 80, 0.12);
  --warn: #d29922;
  --warn-bg: rgba(210, 153, 34, 0.12);
  --danger: #f85149;
  --danger-bg: rgba(248, 81, 73, 0.12);

  --r-sm: 4px;
  --r: 6px;
  --r-lg: 10px;

  --shadow: 0 8px 28px rgba(0, 0, 0, 0.5);

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

  --page: 1100px;
  --header-h: 56px;

  /* One curve and two durations for the whole system. Interface motion is feedback, not
     decoration: long enough to be seen as a change of state, short enough that a pointer moving
     down a list never waits for it. The curve leaves fast and settles — a linear fade reads as
     lag, a bouncy one reads as a toy. */
  --ease: cubic-bezier(0.2, 0.6, 0.35, 1);
  --t-press: 90ms;
  --t: 150ms;
}

:root[data-theme="light"] {
  --bg: #ffffff;
  --surface: #ffffff;
  --surface-2: #f6f7f8;
  --surface-3: #eceef1;
  --line: #e3e5e8;
  --line-2: #d0d4d9;

  --text: #16181c;
  --muted: #5b6470;
  --dim: #858d99;

  --primary-bg: #16181c;
  --primary-fg: #ffffff;
  --primary-bg-hover: #33383f;

  --link: #0b5cd5;
  --focus: #0b5cd5;

  --ok: #1a7f37;
  --ok-bg: rgba(26, 127, 55, 0.1);
  --warn: #9a6700;
  --warn-bg: rgba(154, 103, 0, 0.1);
  --danger: #cf222e;
  --danger-bg: rgba(207, 34, 46, 0.1);

  --shadow: 0 8px 28px rgba(16, 22, 30, 0.12);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #ffffff;
    --surface: #ffffff;
    --surface-2: #f6f7f8;
    --surface-3: #eceef1;
    --line: #e3e5e8;
    --line-2: #d0d4d9;
    --text: #16181c;
    --muted: #5b6470;
    --dim: #858d99;
    --primary-bg: #16181c;
    --primary-fg: #ffffff;
    --primary-bg-hover: #33383f;
    --link: #0b5cd5;
    --focus: #0b5cd5;
    --ok: #1a7f37;
    --ok-bg: rgba(26, 127, 55, 0.1);
    --warn: #9a6700;
    --warn-bg: rgba(154, 103, 0, 0.1);
    --danger: #cf222e;
    --danger-bg: rgba(207, 34, 46, 0.1);
    --shadow: 0 8px 28px rgba(16, 22, 30, 0.12);
  }
}

/* ---------- Reset ---------- */

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

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

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font: 400 15px/1.6 var(--font);
  -webkit-font-smoothing: antialiased;
  /* Tabular figures everywhere: this interface is mostly numbers in columns. */
  font-variant-numeric: tabular-nums;
}

/* Display sizes carry tight tracking; body text does not. Weight and spacing do
   the hierarchy work, so the palette does not have to. */
h1, h2, h3, h4 { margin: 0; font-weight: 600; line-height: 1.15; }
h1 { font-size: clamp(32px, 4.4vw, 46px); letter-spacing: -0.028em; }
h2 { font-size: clamp(22px, 2.6vw, 28px); letter-spacing: -0.022em; }
h3 { font-size: 17px; letter-spacing: -0.012em; }
h4 { font-size: 15px; letter-spacing: -0.008em; }

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

img, svg { max-width: 100%; }
svg { display: block; }

code, kbd, pre, samp { font-family: var(--mono); font-size: 0.895em; }

code:not(pre code) {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 0.1em 0.38em;
  color: var(--text);
  word-break: break-word;
}

hr { border: none; border-top: 1px solid var(--line); margin: 28px 0; }

::selection { background: var(--focus); color: #fff; }

/* ---------- Layout ---------- */

.wrap { width: 100%; max-width: var(--page); margin: 0 auto; padding: 0 24px; }
.narrow { max-width: 760px; }
.section { padding: 76px 0; }
.section-sm { padding: 40px 0; }
.section-line { border-top: 1px solid var(--line); }

.muted { color: var(--muted); }
.dim { color: var(--dim); }
.mono { font-family: var(--mono); }
.nowrap { white-space: nowrap; }
.center { text-align: center; }
[hidden], .hidden { display: none !important; }

.row { display: flex; align-items: center; gap: 10px; }
.row-wrap { flex-wrap: wrap; }
.spacer { flex: 1 1 auto; }
.stack { display: flex; flex-direction: column; gap: 12px; }

/* The 1px gap belongs to .grid-ruled, where the container's background shows through the
   hairlines. Plain grids inherited it too, so cards on the console sat a single pixel apart —
   close enough to read as a rendering fault rather than a layout. Ruled grids reclaim 1px below;
   everything else gets a real gap.

   Cells are aligned to the start, not stretched: a short card next to a tall one was being pulled
   to the tall one's height and filled the difference with nothing. */
.grid { display: grid; gap: 18px; align-items: start; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.grid-gap { gap: 20px; }

/* A grid whose cells are separated by hairlines rather than gaps — denser and
   more like a technical document than a row of floating cards.

   The hairlines are the container's background showing through 1px gaps, which
   means an incomplete last row would leave visible empty boxes. Use the fixed
   .cols-N classes rather than auto-fit whenever the item count is known, so the
   rows always come out full. */
.grid-ruled {
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
  background: var(--line);
  /* The hairline IS the gap here, and cells must fill the row for it to run edge to edge. */
  gap: 1px;
  align-items: stretch;
}
.grid-ruled > * { background: var(--bg); padding: 22px; }

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

@media (max-width: 900px) {
  .cols-3, .cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .cols-2, .cols-3, .cols-4 { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Type helpers ---------- */

.lead { font-size: clamp(16px, 1.7vw, 18px); color: var(--muted); line-height: 1.6; }
.small { font-size: 13.5px; }
.tiny { font-size: 12.5px; }

.label {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--dim);
}

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  font: 500 14px/1 var(--font);
  padding: 10px 16px;
  border-radius: var(--r);
  border: 1px solid transparent;
  background: var(--primary-bg);
  color: var(--primary-fg);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
/* Hover is scoped away from disabled controls. A greyed-out button that still lights up under the
   pointer is worse than one that does nothing: it promises the click will land. The cursor stays
   `not-allowed`, so the control still answers — it just answers "no".
   The guard sits inside :where() so it contributes no specificity; without that, .btn:hover would
   outrank .btn-secondary:hover and every variant would inherit the primary hover colour. */
.btn:hover:where(:not(:disabled):not([aria-disabled="true"])) {
  background: var(--primary-bg-hover);
  text-decoration: none;
}
.btn:disabled, .btn[disabled], .btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* tabindex="-1" is excluded deliberately. It marks an element that JavaScript focuses on the
   user's behalf — the console moves focus to the view container after every navigation so a
   screen reader announces the new screen. That is routing, not a keyboard target, and painting a
   ring around it drew a blue rule across the width of every page: the container is taller than
   the window, so only its bottom edge landed on screen and read as a stray line. */
.btn:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.btn-lg { padding: 12px 22px; font-size: 15px; }
.btn-sm { padding: 6px 11px; font-size: 13px; }
.btn-block { width: 100%; }

.btn-secondary {
  background: transparent;
  color: var(--text);
  border-color: var(--line-2);
}
.btn-secondary:hover:where(:not(:disabled):not([aria-disabled="true"])) { background: var(--surface-2); border-color: var(--line-2); }

.btn-ghost { background: transparent; color: var(--muted); border-color: transparent; }
.btn-ghost:hover:where(:not(:disabled):not([aria-disabled="true"])) { background: var(--surface-2); color: var(--text); }

.btn-danger { background: transparent; color: var(--danger); border-color: var(--line-2); }
.btn-danger:hover:where(:not(:disabled):not([aria-disabled="true"])) { background: var(--danger-bg); border-color: var(--danger); }

.btn-icon {
  padding: 6px;
  min-width: 30px;
  min-height: 30px;
  background: transparent;
  color: var(--muted);
  border-color: transparent;
}
.btn-icon:hover:where(:not(:disabled):not([aria-disabled="true"])) { background: var(--surface-2); color: var(--text); }

/* Touch targets have to clear 40px on a phone even though the desktop button is
   deliberately compact. */
@media (pointer: coarse) {
  .btn { min-height: 40px; }
  .btn-icon { min-width: 40px; min-height: 40px; }
}

.btn .icon { width: 16px; height: 16px; flex-shrink: 0; }

/* ---------- Icons ---------- */

/* Line icons, 1.5px stroke, currentColor. Never decorative: an icon appears only
   where it identifies an action or a state. */
.icon {
  width: 18px;
  height: 18px;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  flex-shrink: 0;
}
.icon-sm { width: 15px; height: 15px; }
.icon-lg { width: 22px; height: 22px; }

/* ---------- Interaction ---------- */

/* Every control that responds to a pointer animates the same way, from here, once.
   The individual components already declare what changes on :hover; what they lacked was the
   transition itself, so a hover was an instant jump — the thing that makes an interface feel
   like a wireframe rather than a product.
   Listed by property rather than `all`: `all` animates layout properties too, and a width or a
   padding caught in a 150ms transition turns a re-render into a visible slide. */
a,
button,
summary,
select,
label[for],
[role="button"],
[tabindex]:not([tabindex="-1"]),
.btn,
.btn-icon,
.copy-btn,
.tab,
.chip,
.brand,
.navlink,
.linkbtn,
.grail-btn,
.menu-btn,
.skip-link,
.pick button,
table.data tbody tr {
  transition:
    background-color var(--t) var(--ease),
    border-color var(--t) var(--ease),
    color var(--t) var(--ease),
    opacity var(--t) var(--ease),
    box-shadow var(--t) var(--ease);
}

/* Icons are drawn in currentColor, so they inherit the parent's colour transition for free and
   must not run one of their own — two timers on the same visual change beat against each other. */
.icon { transition: none; }

/* Press feedback. One pixel down, faster than the hover it interrupts: the control has to feel
   like it took the click even when the work behind it takes a moment to answer. No scaling —
   a button that grows under the thumb reads as a toy, and on a phone the finger already hides it. */
.btn:active:not(:disabled):not([aria-disabled="true"]),
.btn-icon:active:not(:disabled),
.copy-btn:active:not(:disabled),
.grail-btn:active,
.navlink:active,
.pick button:active {
  transform: translateY(1px);
  transition-duration: var(--t-press);
}

/* Controls that had a cursor but no visible answer to the pointer. */
.pick button:hover { border-color: var(--line-2); color: var(--text); }
.skip-link:hover { background: var(--surface-2); }
.chip:hover { border-color: var(--line-2); }

/* A <summary> is a button that does not look like one — the FAQ questions and the docs table of
   contents are both this. The disclosure marker is the part that says "this opens", so that is
   the part that answers the pointer. */
details > summary:hover { color: var(--text); }
details > summary:hover::after { border-color: var(--text); color: var(--text); }
/* transform is in here for the disclosure chevron: it rotates when the section opens, and an
   instant flip is the one place in this interface where motion carries meaning rather than
   decorating it — it shows the panel came from the header you just clicked. */
details > summary::after {
  transition: border-color var(--t) var(--ease), color var(--t) var(--ease),
              transform var(--t) var(--ease);
}

/* ---------- Cards and panels ---------- */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 22px;
}
.card-tight { padding: 16px; }

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.panel-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line);
  font-weight: 600;
  font-size: 14.5px;
}
.panel-body { padding: 16px; }
.panel-foot {
  padding: 13px 16px;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
}

/* A note that needs to be read, not decorated. Left rule carries the severity. */
.note {
  border: 1px solid var(--line);
  border-left: 2px solid var(--muted);
  border-radius: var(--r);
  padding: 14px 16px;
  background: var(--surface);
  font-size: 14px;
}
.note-ok { border-left-color: var(--ok); }
.note-warn { border-left-color: var(--warn); }
.note-danger { border-left-color: var(--danger); }

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

.field { display: flex; flex-direction: column; gap: 5px; }
.field > label { font-size: 13px; font-weight: 500; color: var(--muted); }
.field .hint { font-size: 12.5px; color: var(--dim); line-height: 1.45; }
.field-row { display: flex; gap: 12px; flex-wrap: wrap; }
.field-row > .field { flex: 1 1 180px; }

input[type=text], input[type=email], input[type=password], input[type=number],
input[type=url], input[type=tel], input[type=search], input[type=time],
select, textarea {
  width: 100%;
  font: inherit;
  font-size: 14.5px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  padding: 9px 11px;
  transition: border-color 120ms ease;
}
input:hover, select:hover, textarea:hover { border-color: var(--muted); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--focus); }
input::placeholder, textarea::placeholder { color: var(--dim); }
input:disabled, select:disabled, textarea:disabled { opacity: 0.5; cursor: not-allowed; }

textarea { resize: vertical; min-height: 84px; line-height: 1.5; }

select {
  appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' stroke='%23969ca6' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
}

.check { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; font-size: 14px; }
.check input { width: auto; margin: 3px 0 0; accent-color: var(--focus); }

fieldset { border: none; margin: 0; padding: 0; }

/* ---------- Badges and state ---------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11.5px;
  font-weight: 500;
  padding: 2px 7px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-2);
  color: var(--muted);
  background: transparent;
  white-space: nowrap;
}
.badge-ok { color: var(--ok); border-color: transparent; background: var(--ok-bg); }
.badge-warn { color: var(--warn); border-color: transparent; background: var(--warn-bg); }
.badge-danger { color: var(--danger); border-color: transparent; background: var(--danger-bg); }

/* Emphasis without hue: for counts and "current" markers, where the point is
   "notice this" rather than "this is good or bad". Colour stays reserved for state. */
.badge-accent {
  color: var(--text);
  border-color: transparent;
  background: var(--surface-3);
  font-weight: 600;
}

.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dim); flex-shrink: 0; }
.dot-ok { background: var(--ok); }
.dot-warn { background: var(--warn); }
.dot-danger { background: var(--danger); }
.dot-lg { width: 9px; height: 9px; }

/* Ruled key/value list — the shape most of this product's data actually is. */
.kv { display: grid; grid-template-columns: auto 1fr; gap: 0; font-size: 14px; }
.kv > dt {
  color: var(--muted);
  padding: 9px 20px 9px 0;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.kv > dd {
  margin: 0;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.kv > dt:last-of-type, .kv > dd:last-of-type { border-bottom: none; }

/* ---------- Code ---------- */

.code {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.code-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 13px;
  border-bottom: 1px solid var(--line);
  font-size: 12.5px;
  color: var(--muted);
  background: var(--surface-2);
  font-family: var(--mono);
}
.code pre {
  margin: 0;
  padding: 14px 16px;
  overflow-x: auto;
  color: var(--text);
  font-size: 13px;
  line-height: 1.65;
  tab-size: 2;
}

/* Syntax colour stays close to the text colour: a code sample is content, not a
   light show. Only three tones, all legible in both themes. */
.code .tok-cmd { color: var(--text); font-weight: 600; }
.code .tok-flag { color: var(--muted); }
.code .tok-str { color: var(--ok); }
.code .tok-key { color: var(--link); }
.code .tok-comment { color: var(--dim); }

.copy-btn {
  position: absolute;
  top: 6px;
  right: 7px;
  font: 500 12px/1 var(--font);
  padding: 5px 9px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-2);
  background: var(--bg);
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity 120ms ease, color 120ms ease;
}
.code:hover .copy-btn, .copy-btn:focus-visible { opacity: 1; }
.copy-btn:hover { color: var(--text); }
.copy-btn.done { color: var(--ok); border-color: var(--ok); opacity: 1; }
@media (pointer: coarse) { .copy-btn { opacity: 1; } }

/* ---------- Tabs ---------- */

.tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  font: 500 14px/1 var(--font);
  padding: 11px 14px;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  transition: color 120ms ease, border-color 120ms ease;
}
.tab:hover { color: var(--text); }
.tab[aria-selected="true"], .tab.active { color: var(--text); border-bottom-color: var(--text); }

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

.table-scroll { overflow-x: auto; }
table.data { width: 100%; border-collapse: collapse; font-size: 14px; }
table.data th {
  text-align: left;
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--dim);
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
table.data td {
  padding: 11px 12px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
table.data tbody tr:last-child td { border-bottom: none; }
table.data tbody tr:hover { background: var(--surface-2); }
table.data td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ---------- Header and footer ---------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.site-header .wrap { display: flex; align-items: center; gap: 20px; }

/* Typographic wordmark: two weights, no gradient, no coloured tile. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  color: var(--text);
  letter-spacing: -0.015em;
  white-space: nowrap;
}
.brand:hover { text-decoration: none; }
.brand b { font-weight: 650; }
.brand span { font-weight: 400; color: var(--muted); }
.brand-mark { width: 15px; height: 15px; color: var(--text); flex-shrink: 0; }

.nav { display: flex; align-items: center; gap: 2px; }
.nav a {
  padding: 6px 10px;
  border-radius: var(--r-sm);
  color: var(--muted);
  font-size: 14px;
  font-weight: 450;
}
.nav a:hover { color: var(--text); text-decoration: none; }
.nav a[aria-current="page"] { color: var(--text); }

.site-footer {
  border-top: 1px solid var(--line);
  padding: 36px 0 44px;
  color: var(--muted);
  font-size: 13.5px;
}
.site-footer a { color: var(--muted); }
.site-footer a:hover { color: var(--text); }

/* ---------- Toast and modal ---------- */

.toast-host {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  pointer-events: none;
  width: min(92vw, 420px);
}
.toast {
  background: var(--surface-3);
  border: 1px solid var(--line-2);
  border-left: 2px solid var(--muted);
  color: var(--text);
  padding: 10px 14px;
  border-radius: var(--r);
  box-shadow: var(--shadow);
  font-size: 14px;
  width: 100%;
}
.toast-ok { border-left-color: var(--ok); }
.toast-err { border-left-color: var(--danger); }

.modal-host {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(4, 6, 9, 0.6);
}
.modal {
  width: 100%;
  max-width: 480px;
  max-height: 86vh;
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
}
.modal-wide { max-width: 680px; }
.modal-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 20px 22px 0;
}
.modal-head h3 { flex: 1; }
.modal-body { padding: 16px 22px; }
.modal-foot {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  padding: 4px 22px 22px;
}

/* ---------- Misc ---------- */

.empty {
  text-align: center;
  padding: 48px 24px;
  color: var(--muted);
  font-size: 14px;
}
.empty h4 { margin-bottom: 6px; color: var(--text); }
.empty p { max-width: 420px; margin-left: auto; margin-right: auto; }

.empty-title { color: var(--text); font-weight: 600; margin-bottom: 6px; }
.empty-hint { max-width: 460px; margin: 0 auto 14px; }

/* Пустая панель внутри экрана: та же мысль, но без центрирования и без воздуха на треть
   экрана. Она соседствует с другими блоками, а не заменяет собой всё. */
/* Сбрасываются оба абзаца, а не только подсказка: `.empty p` центрирует блок автоматическими
   полями, и заголовок оставался посередине узкой колонки даже при text-align: left. */
.empty-inline { padding: 18px; text-align: left; }
.empty-inline p { max-width: 62ch; margin-left: 0; margin-right: 0; }
.empty-inline .empty-hint { margin-bottom: 0; }

.spinner {
  width: 15px;
  height: 15px;
  border: 1.5px solid var(--line-2);
  border-top-color: var(--text);
  border-radius: 50%;
  animation: spin 640ms linear infinite;
  display: inline-block;
  vertical-align: -2px;
}
@keyframes spin { to { transform: rotate(360deg); } }

.bar { height: 4px; border-radius: 2px; background: var(--surface-3); overflow: hidden; }
.bar > i { display: block; height: 100%; background: var(--muted); }
.bar > i.warn { background: var(--warn); }
.bar > i.danger { background: var(--danger); }

.skeleton {
  background: var(--surface-2);
  border-radius: var(--r-sm);
  animation: fade 1.1s ease-in-out infinite alternate;
}
@keyframes fade { to { opacity: 0.55; } }

details > summary { cursor: pointer; list-style: none; }
details > summary::-webkit-details-marker { display: none; }

/* Alerts get read during incidents. Anyone who has asked for less motion has
   asked for a good reason. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

@media (max-width: 760px) {
  .section { padding: 48px 0; }
  .wrap { padding: 0 18px; }
  .card { padding: 18px; }
  .grid-ruled > * { padding: 18px; }

  /* Footer and nav links are 16px of text on desktop, which is a hard target to hit with a
     thumb. Padding rather than font-size: the type stays right, the tap area gets usable. */
  .site-footer a,
  .foot-grid li a { display: inline-block; padding: 7px 0; }
  .site-footer li { padding: 0; }
}
