/* base.css — Base rules, the .btn primitive and the .chip-top pill. Loaded before every component.
   Cascade position 2 of 18. Order is load-bearing and is asserted by
   verify-fixtures.mjs; index.html links these explicitly (never @import) because Caddy's
   try_files turns a missing file into a 200 text/html SPA shell.
   Owner after Step 1: shared, frozen after Step 1. */
* { box-sizing: border-box; margin: 0; }
html, body { height: 100%; }
body {
  background: var(--bg); color: var(--ink);
  font-family: 'Schibsted Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
  font-size: 15px; line-height: 1.4;
  display: grid; grid-template-rows: 56px minmax(0, 1fr);
  overflow: clip;
  min-width: 0;
}
html { overflow-x: hidden; }
h1, h2, h3 { font-family: Gabarito, ui-sans-serif, system-ui, sans-serif; font-weight: 800; letter-spacing: -.02em; }
button { font: inherit; color: inherit; cursor: pointer; }
[hidden] { display: none !important; }
/* item 80: the ring follows the SHAPE of what it surrounds. The hard-coded 6px drew
   square-ish corners around every 999px pill and around the 11px primary. */
:focus-visible { outline: 2px solid var(--sn-focus); outline-offset: 2px; border-radius: inherit; }

/* items 80 + 103: `:active` existed exactly ONCE in the whole stylesheet
   (`.runlog__bar:active { cursor: grabbing }`), so press-and-hold on "Prepare my model"
   — an action that takes minutes and that people press twice — produced no change at
   all. This is the app's only press rule, and REFERENCE-STUDY §18's "hover changes fill
   and nothing else" allows it precisely because a press is not a hover. */
:where(button, [role="button"], a, label, summary):active:not(:disabled) {
  transform: translateY(1px);
  filter: brightness(.94);
}

.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; }

/* item 146: the shipped block killed animations and left every TRANSITION running —
   measured with reduced motion on, `.cta` still ran 0.2s and `.chip` 0.18s. The JS side
   was already correct (auto-spin and all three scrollIntoView calls check the query),
   so this was a one-line gap in an otherwise conscientious implementation. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ══ The one spinner (item 92) ══════════════════════════════════════════════════════
   apps/web/shared.css:168-169 still declares a SECOND `@keyframes spin` under
   `.spin-slow`, and a `.sr` that does this file's `.sr-only` job. The dashboard does not
   link shared.css, so they cannot collide today — but they would the moment item 87
   unifies the two pages, and the collision is silent. Measured and recorded rather than
   fixed, because shared.css is the marketing site's and is out of scope. */
.spin {
  width: 30px; height: 30px; border-radius: 50%;
  border: 2px solid var(--line-strong); border-top-color: var(--sn-accent);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg) } }

/* ══ The one button base (items 89, 80, 103, 76, 139, 77b) ═════════════════════════
   Nine button idioms shipped, each implementing a different subset of hover /
   focus-visible / active / disabled, and no two agreeing. Everything below is ADDITIVE:
   no shipped selector is renamed here, so nothing repaints until a component section
   moves its idiom onto `.btn`. Sections may add variants; they may not redeclare the
   base's height, padding or state grammar.

   Minimum target is 32 x 32 CSS px, above WCAG 2.2's floor of 24, because the audit
   found eleven controls under 24 (items 77, 77b, 137). Icon-only controls are 32 x 32
   with the glyph at 16.

   Hover changes FILL and nothing else (REFERENCE-STUDY §18): no transform, no scale, no
   shadow growth. The press above is the single exception and it is a press. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sn-space-2);
  min-height: 32px; min-width: 32px; padding: 0 var(--sn-space-3);
  border: 1px solid var(--sn-line); border-radius: var(--sn-radius-sm);
  font: inherit; font-size: var(--sn-text-sm); font-weight: 600; line-height: 1;
  color: var(--sn-ink); background: var(--sn-surface-2);
  cursor: pointer; white-space: nowrap; text-decoration: none;
  transition: background-color var(--sn-dur-fast) var(--sn-ease-out),
              border-color var(--sn-dur-fast) var(--sn-ease-out),
              color var(--sn-dur-fast) var(--sn-ease-out);
}
.btn:hover:not(:disabled):not([aria-disabled="true"]) {
  border-color: var(--sn-line-strong); background: var(--sn-surface-2-alt);
}
/* Disabled NEVER means invisible. `opacity: .35` is banned here: composited, it took the
   primary CTA's label to 1.93:1 and the button to 2.06:1 against the page (item 76), on
   the single most important element in the empty state. Disabled is a colour and border
   change, and the label stays past 4.5:1. */
.btn:disabled, .btn[aria-disabled="true"] {
  color: var(--sn-ink-mute); background: var(--sn-surface-1);
  border-color: var(--sn-line); cursor: not-allowed;
}
.btn--primary {
  min-height: 44px; width: 100%; padding: 0 var(--sn-space-4);
  border: 0; border-radius: var(--sn-radius-md);
  background: var(--sn-accent); color: var(--sn-accent-ink);
  font-size: var(--sn-text-md); font-weight: 700;
}
.btn--primary:hover:not(:disabled) { background: var(--sn-accent-hover); }
.btn--primary:active:not(:disabled) { background: var(--sn-accent-press); }
/* MEASURED: label 7.23:1 on its own fill; the button reaches the 3:1-against-the-page
   target through the 1.5px outline, which is the construction `.cta.is-warn:disabled`
   already shipped and which the file simply never applied to green (item 76). */
.btn--primary:disabled, .btn--primary[aria-disabled="true"] {
  background: var(--sn-accent-disabled-bg); color: var(--sn-accent-disabled-ink);
  border: 0; box-shadow: inset 0 0 0 1.5px var(--sn-accent-disabled-edge);
  cursor: not-allowed;
}
.btn--secondary { /* the base as written */ }
.btn--ghost {
  background: none; border-color: transparent; color: var(--sn-ink-dim);
}
.btn--ghost:hover:not(:disabled):not([aria-disabled="true"]) {
  background: rgb(var(--sn-white-rgb) / .047058823529411764);
  border-color: var(--sn-line); color: var(--sn-ink);
}
.btn--ghost:disabled, .btn--ghost[aria-disabled="true"] {
  background: none; border-color: transparent; color: var(--sn-ink-mute);
}

/* ══ Loading is a STATE, not a fade (items 42, 139) ════════════════════════════════
   `#cpSave` and `#prepare` set `disabled = true` and relied on the 35% fade, so a click
   on "Save printer" was indistinguishable from a dead button for the whole round trip.
   A control that makes a request sets data-busy="true" AND aria-busy="true", swaps its
   label to the present participle, and shows the 14px spinner this rule draws. */
[data-busy="true"] { pointer-events: none; }
[data-busy="true"]::before {
  content: ''; width: 14px; height: 14px; flex: 0 0 auto;
  border-radius: 50%; border: 2px solid currentColor; border-top-color: transparent;
  animation: spin .8s linear infinite;
}

/* ══ The one top-bar pill (items 12, 13, 86) ═══════════════════════════════════════
   #creditsChip and #acctBtn are the same object and shipped as two: 28.766px against
   28.000px, with the account name on a baseline 1px off the credits label, because one
   used `.34em .7em` padding and the other `3px 10px 3px 3px`. px only, one height, one
   line-height. Sections (a) and (b) consume this and may NOT redeclare its height,
   padding, font-size or line-height. */
.chip-top {
  display: inline-flex; align-items: center; gap: var(--sn-space-1);
  height: 32px; padding: 0 var(--sn-space-3); box-sizing: border-box;
  border: 1px solid var(--sn-line); border-radius: var(--sn-radius-pill);
  background: var(--sn-surface-2);
  font-size: var(--sn-text-xs); line-height: 1.4; font-weight: 500;
  color: var(--sn-ink-dim); white-space: nowrap;
}
