/* ======================================================================
   GlideAPI — Site-wide UI polish layer (2026-08-24 elevation pass)
   Loads LAST in the cascade on every page. Three jobs:
   1. Style every state that was left unstyled (portal error state).
   2. Normalize element sizing/spacing to one rhythm across surfaces.
   3. Refine landing/models visual details toward DESIGN.md restraint.
   Nothing here changes copy or behavior.
   ====================================================================== */

/* ── 0 · The type foundation ─────────────────────────────────────────
   Both faces are served from our own origin. Until 2026-08-30 every page
   carried two <link rel="preconnect"> tags and a render-blocking stylesheet
   from fonts.googleapis.com, which cost a visitor two extra DNS + TCP + TLS
   handshakes and made text paint wait on a three-deep chain: our HTML, then
   Google's CSS on a new connection, then the woff2 on a second new one.
   Self-hosted it is two deep and both hops are on the connection that is
   already open, because each page preloads the latin face directly.

   These rules live in THIS sheet for one reason: it is the only stylesheet all
   sixteen pages load, so declaring the faces here costs no extra request on any
   of them. That is an invariant, not a coincidence - test/typography.test.js
   fails if a page stops loading polish.css or starts declaring the faces
   somewhere else. A "polish" layer is an odd home for a foundation; a sixteenth
   duplicated @font-face block, or a new stylesheet on every page's critical
   path, would be worse.

   Only latin and latin-ext are vendored. The product copy is English, and a
   character outside those ranges falls back to the stack's next family rather
   than failing. Bytes are byte-identical to what Google serves for Geist v5 /
   Geist Mono v6 - re-run scripts/dev-vendor-fonts.mjs to confirm.
   Geist and Geist Mono are SIL OFL 1.1 (Vercel), which permits self-hosting. */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/assets/fonts/geist-v5-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/assets/fonts/geist-v5-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Geist Mono';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/geist-mono-v6-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist Mono';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/geist-mono-v6-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── 1 · Portal error state — moved out of this file ─────────────────
   This layer used to own .portal-error-state. It no longer does. polish.css
   loads after portal/style.css (index.html:26 then :30), so the rules here were
   overriding the console's own error-state treatment on display, padding,
   alignment and the ::before glyph, leaving that treatment half-applied - the
   exact split-ownership problem the CONSOLE DEPTH SYSTEM block was written to
   end. The single owner is now public/portal/style.css, which is also the only
   stylesheet that has the --c-* planes the rest of the console is built from.
   The 44px alert glyph survives there, redrawn as a mask so it takes its colour
   from --danger instead of a hard-coded #ef4444.
   test/portalStateStyles.test.js locks the surface as styled either way. */

/* ── 2 · Logo scale consistency (sidebar lockups were mismatched) ───── */
.portal-sidebar .logo-mark,
.admin-brand .logo-mark {
  width: 148px;
  height: 36px;
  display: flex;
  align-items: center;
}

.portal-sidebar .logo-mark img,
.admin-brand .logo-mark img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* ── 3 · Button rhythm — one height system everywhere ───────────────── */
/* `transform` joined this list on 2026-09-01. Every sheet on the site states a
   `.btn` transition - brand-system, models.css, portal/style.css, admin and this
   one - and this one is loaded last on all of them, so this is the list that
   renders. It named four properties and not the fifth, which meant a button that
   moves under the pointer snapped to its new position instead of travelling
   there: landing.html's -1px hover lift and +2px press, and admin's -2px/-2px
   nudge, were both instantaneous. Nothing else on the site transforms a `.btn`,
   so naming it here is inert everywhere it does not apply. */
.btn { transition: box-shadow .15s ease, background-color .15s ease, border-color .15s ease, color .15s ease, transform .18s cubic-bezier(.34, 1.42, .64, 1); }

/* Removed 2026-09-01:
     .btn-primary,
     .btn.btn-primary:not(:disabled):hover { box-shadow: none; }
   It was doing nothing on sixteen of the seventeen pages that download this file
   and one wrong thing on the seventeenth. brand-system.css already flattens
   `.btn` with `box-shadow: none !important`, which outranks this rule and covers
   every page that loads it - so on those pages the pair was pure duplication.
   Four pages take polish.css without brand-system.css: portal/index.html and
   docs.html have no `.btn-primary` shadow for it to erase, verification.html
   spells its buttons `.button primary` and never matched, and landing.html is
   the one page that authors a primary key WITH depth - a 2px socket and a
   contact shadow, stated in its own <style> block, which this rule silently
   deleted because it loads later at the same specificity. The key had been
   drawing flat since the day the socket was written. */

button:focus-visible,
a:focus-visible,
[role="button"]:focus-visible {
  /* Fallback was #8b5cf6, a violet from before the accent was blue. It only
     paints if --accent goes missing, and a focus ring is the last place that
     should surprise anyone with an off-palette colour. #1f53db is what
     oklch(0.505 0.184 258) resolves to in sRGB. */
  outline: 2px solid var(--accent, #1f53db);
  outline-offset: 2px;
}

/* ── 4 · Sidebar footer de-cruft (portal) ──────────────────────────── */
.sidebar-resources {
  gap: 2px !important;
}
.sidebar-resources button,
.sidebar-resources a {
  padding: 7px 8px !important;
  border-radius: var(--radius-sm, 6px) !important;
  transition: background-color .15s ease, color .15s ease;
}
.sidebar-resources button:hover,
.sidebar-resources a:hover {
  background: var(--surface-hover, rgba(255,255,255,.06));
  color: var(--text, inherit) !important;
}

/* Balance card: sit flush with sidebar rhythm instead of floating */
.sidebar-balance {
  border-radius: var(--radius-md, 8px) !important;
}

/* ── 5 · Models page fixes ──────────────────────────────────────────── */
/* Empty/loading/error state: make it read as an intentional card.
   #modelsList is the public catalogue's grid (models.html:111); models.js:334
   renders .state into it. The portal's list shares the id but renders
   .empty-state, so these two rules only reach the public page. */
#modelsList .state {
  border: 1px dashed var(--border-strong, #315b66);
  border-radius: var(--radius-md, 8px);
  background: var(--bg-subtle, transparent);
}
#modelsList .state svg {
  opacity: .55;
}

/* Removed here, 2026-08-26:
   - `.model-toolbar, .catalog-toolbar { align-items: center }`. The comment on it
     read "count chip belongs at the end of the toolbar", which is horizontal
     placement the rule never touched; `justify-self` does that, and the portal
     toolbar now says so itself (portal/style.css:1402). What the rule did do was
     centre the chip vertically, and because this sheet loads after
     portal/style.css it silently beat that toolbar's own `align-items: end` -
     measured: chip bottom 274 against the search and filter at 295. The public
     catalogue had already had to re-assert `align-items: end` in an inline
     <style> after this link (models.html:35), so nothing was relying on it.
   - `.models-hero h1, .models-header h1 { ... }` and
     `.model-search input, .catalog-search input { height: 100% }`. No element in
     public/ carries `models-hero`, `models-header`, `model-search` or
     `catalog-search`; the live classes are `model-search-group` (portal) and
     `status-model-search` (status page), which neither selector matches. */

/* ── 6 · Landing refinements — moved out of this sheet on 2026-08-27 ─────
   Four rules lived here. Two were landing-only and are now stated in
   landing.html beside the elements they style:

     .hero-cta .btn-secondary (+ :hover)   the neutral-glass fill and rim the
                                           hero's second button needs on a
                                           field the accent bloom already lights
     .route-joint                          opacity .5 on the two connectors

   Two matched nothing anywhere in public/ and were deleted rather than moved:

     .pagefoot        landing.html does author `<footer class="pagefoot">`, but
                      shared-footer.js reassigns that element's className to
                      `glide-site-footer` and rewrites its innerHTML during
                      parse, so the class is gone before first paint. Measured
                      in a settled DOM (fonts ready, 600ms): one <footer>,
                      class `glide-site-footer`, `.pagefoot` 0, `.foot-grid` 0.
                      The footer's real top seam comes from shared-footer.css.
     .promo-banner,   zero occurrences of either class across all 18 pages in
     .topnav-promo    public/, and none in any stylesheet or in the scripts
                      that build markup. Never painted on any page.

   The `.hero-actions .btn-secondary` half of the secondary-button pair went the
   same way: verification.html is the only page carrying `.hero-actions`, and
   that div holds `<a class="button primary">` and `<a class="text-action">` -
   there is no `btn-secondary` in the file at all. */

/* ── 7 · Universal spacing sanity ──────────────────────────────────── */
/* Prevent any page from horizontal scroll on tiny screens */
html, body { overflow-x: clip; }

/* ── 8 · Landing rhythm & contrast elevation (2026-08-24) ───────────── */

/* (Removed: flat `background: oklch(0.1xx 0.01x 264)` overrides for
   .section.field-sunk / .field-tint / .field-ink. They were added to widen the
   tonal separation between bands, but they did it by REPLACING the landing
   page's own --field-* tokens with near-neutral values - chroma 0.010–0.012
   against the tokens' 0.017–0.062 - and the .field-tint override also erased
   the accent radial gradient that band is built on. Net effect across the whole
   page was a desaturated grey wash. The separation is now achieved inside
   landing.html by spreading the --field-* tokens' L channel, which keeps the
   chroma. The edge separators below are kept: they were the part that worked. */

/* Kill fractional inline paddings (112.56px etc.) that JS scroll code wrote
   into the markup — they made vertical rhythm look random. Round to grid: */
/* (Removed: a blanket `.section[style*="padding"] { padding-block: … !important }`
   used to live here to round off the visual editor's leftover inline values
   (117.42px, 112.56px, 70.56px). Those inline attributes are now gone from the
   markup, so the stylesheet is the single source of truth. The blanket rule
   also overrode the marquee band's deliberate `padding: 0` and inflated it to
   ~100px top and bottom, which read as broken spacing.) */


/* Dim capability text was illegible. The marquee band now owns its own type and
   contrast in landing.html (13px / .14em / oklch 0.800), so the !important
   override that used to live here is gone - it was fighting the page for
   control of the same three properties. */

/* `.deco-rules::after { opacity: .5 }` also lived here. The blueprint lattice is
   a landing-page decoration (three sections carry `deco-rules`, all of them in
   landing.html), and the pseudo-element it dims is declared in landing.html, so
   the two halves of one effect were in two files. Folded into that rule on
   2026-08-27; the measured opacity is unchanged at 0.5.

   These two are site-wide and stay: shared-footer.js injects
   `.glide-site-footer` into every page, and this is the only rule in the repo
   that colours a generic footer link.

   Known defect, left for the footer pass rather than patched here: this rule and
   shared-footer.css's `.glide-site-footer__nav a` have identical specificity
   (0,1,1), so which one colours a nav link depends on which sheet the page links
   last - and the pages disagree. Measured at 1440: landing.html links
   shared-footer.css after this sheet and renders nav links at
   oklch(0.688 0.022 259); the other fifteen link it before and render
   oklch(0.760 0.012 262). Both clear AA on the footer's oklch(0.132 0.028 260)
   band (6.70:1 and 9.28:1), so this is a consistency defect, not a legibility
   one, and fixing it means deciding one colour for the whole footer. */
.glide-site-footer a { color: oklch(0.760 0.012 262); }
.glide-site-footer a:hover { color: oklch(0.940 0.004 262); }

/* An earlier attempt at "give the hero diagram real depth" lived here as
   `.hero .term, .hero [class*="console"]`. It never applied: the hero holds
   one panel, `.panel.route`, and carries neither a `term` class nor anything
   with "console" in its class list, so the shadow was written, shipped and
   never painted - which is presumably why it was written a second time as
   section 10's `.hero .panel.route`. The working version now lives in
   landing.html with the rest of the hero. Deleted 2026-08-27. */

/* ── 9 · Landing rhythm v2 — moved out of this sheet on 2026-08-27 ────────
   Six rules lived here. Three were landing-only and, worse, were each fighting a
   rule in landing.html for the same property - so the page's own declaration was
   dead and a reader editing it saw nothing change. All three are now stated once,
   in landing.html, at the value that was actually rendering:

     .section.field-sunk / .field-tint / .field-ink   the 1px top+bottom edge
       lines on the three banded plates. Folded into the plate system beside the
       `::before` seam they work with. `--border-2` is redefined inside
       `.field-ink`, so the ink band's line resolves to `--ink-border-2` - which
       is order-independent, and measured identical after the move:
       oklch(0.376 0.034 259) on ink, oklch(0.400 0.032 259) on sunk and tint.

     .section:not(.marquee-band):not(.status-strip) { padding-block: … !important }
       landing.html's own `.section` said `clamp(76px, 8.4vw, 132px)`; this said
       `clamp(64px, 5.5vw, 104px) !important` and won, so every section on the
       page rendered 79.2px at 1440 while the page claimed 120.96px. The clamp
       that renders is now the one landing.html states, with no `!important`:
       `.section.marquee-band { padding-block: 0 }` is two classes to one and
       keeps winning on specificity alone, which is what the `:not()` guard was
       standing in for. `:not(.status-strip)` guarded nothing - measured, zero
       elements match `.section.status-strip`; the strip is a <section> that
       carries `status-strip field-ink` and never the `section` class.

     .status-strip { padding-block: … !important }   same story: the page said
       `clamp(42px, 4.6vw, 68px)`, this said `clamp(28px, 3vw, 48px) !important`,
       and 43.2px is what rendered at 1440. Now stated once, in landing.html.

   One was redundant and is deleted: `.status-strip .status-inner
   { align-items: center }` - landing.html:1928 already says exactly that.

   Two were entirely dead, every selector in them matching nothing in a settled
   DOM, and are deleted rather than moved:

     .status-strip .meta-label        `.meta-label` exists only in
                                     portal/index.html, never inside the strip
     [class*="capability"] span       no class anywhere in public/ contains
                                     "capability" - the marquee section's
                                     data-od-id does, and that is not a class
     .deco-rules .marquee-track span  the marquee lives in `.section.marquee-band`,
                                     which is not a `.deco-rules` descendant, so
                                     this never reached it. landing.html:2117's
                                     comment already claimed this override was
                                     gone; it was right about the effect and
                                     wrong about the file.
     .status-strip strong            the strip contains no <strong>
     .status-strip [class*="value"]  and no class containing "value"

   Measured live counts for all five: 0, 0, 0, 0, 0.

   What stays is the one rule here that is not about the landing page: */

/* Consistent focus treatment on all interactive rows/cards. `.key-row` is built
   by portal/app.js and admin/app.js, `.model-row` by admin/app.js - neither
   appears in landing.html. */
.key-row:focus-within,
.model-row:focus-within {
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, #1f53db) 35%, transparent);
  border-radius: var(--radius-sm, 6px);
}

/* ── 10 · Hero elevation — moved out of this sheet on 2026-08-27 ──────────
   This section styled the landing hero: `.hero h1` (stated three times, with
   a `text-wrap: balance` that a later rule contradicted and a `font-size`
   that a later rule made dead), `.hero h1 .num.mint`, `.hero .lead` (with
   `!important` on two properties, used only to beat an inline `style`
   attribute in the markup), `.hero-cta` and its two buttons, `.trust-strip`
   and its parts (two of whose three declarations never rendered, because the
   same inline-attribute problem applied to the <ul>), `.hero .panel.route`
   plus its accent hairline, and a `@media (min-width: 921px)` override of
   `.hero-split`'s columns.

   Every one of those selectors matched `public/landing.html` and nothing
   else: the other sixteen pages that link this sheet use `page-hero`,
   `docs-hero`, `model-hero`, `verification-hero` or `wallet-hero`, never the
   bare class `hero`. So the rules now live in landing.html's own hero block,
   stated once each, on the page's tokens, with no `!important` and with the
   three inline `style` attributes removed so the sheet owns the values.

   Two selectors were dropped rather than moved, having matched nothing:
   `.hero .route .provider-logos img` and `.hero .route [class*="provider"] img`
   - the marks row is `.route-marks`, which landing.html already sizes at
   26px. */


/* ── 11 · Scrollbars, site-wide (2026-08-28) ───────────────────────────
   Reported as "the scroll bars looks so dumb ... make sure like every scrolll
   bar look better", pointing at the console's request log: a chunky grey page
   channel with a square thumb, and a second one of the same weight under the
   table. That was the browser default on every surface that had not hidden its
   bars outright.

   ONE MECHANISM, deliberately. When `scrollbar-width` or `scrollbar-color` is set
   on an element, Chromium and Safari 18.2+ stop applying every
   `::-webkit-scrollbar` rule to it. Declaring both on the same scroller therefore
   does not layer two treatments - it silently discards the pseudo-element one. So
   the standard properties are the treatment, and the `@supports not` block below
   is a fallback rather than a duplicate: it is the only thing Safari before 18.2
   understands, and it is inert everywhere the standard properties land.

   The two properties do NOT behave the same way, which decides the shape of this
   block. `scrollbar-color` inherits, so one declaration on `html` reaches every
   scroller on the page. `scrollbar-width` does not inherit - measured on
   :3210, where `html` computed `thin` and `body` computed `auto` underneath it -
   so the width has to be handed to descendants explicitly. That is the second
   rule below, and it is why the request log's table scroller, the activity feeds,
   `pre` blocks and the docs nav all get the treatment without being named.
   Chromium draws `thin` as a rounded overlay-style pill, which is the look this
   asks for; the width is the platform's, so it stays consistent with the rest of
   the user's system.

   The thumb colour is resolved through a fallback chain because this sheet loads
   on seventeen pages that do not all share one palette: `--border-2` is the
   canonical token (brand-system.css, docs.css, verification.css), the console and
   the admin panel call the same step `--border-strong`, and the landing page's
   glide skin declares neither - hence the literal, which is `--border-2` at
   oklch(0.400 0.032 259) written in a form every engine parses.

   Three chip strips hide a bar on purpose and still do, because a real selector
   beats the zero-scoring one below: `.admin-tabs`, `.finance-coverage` and
   `.os-tabs-list`, where a bar would sit on top of the chips. glide-skin.css hides
   the root bar for an edge-to-edge hero page, but no page links that sheet, so the
   landing page takes the thin bar like every other page - measured on :3210, page
   gutter 10px, no horizontal overflow at 717px. The docs page's `html` used to
   hide too and no longer does: hiding it cost the reader any indication of where
   they were in a long document, and the only reason it was hidden was that the
   default bar is a 15px grey channel. */
html {
  scrollbar-width: thin;
  scrollbar-color: var(--border-2, var(--border-strong, oklch(0.400 0.032 259))) transparent;
}
/* `scrollbar-width` is not inherited, so this hands it to every element under
   `body` - and to `body`, which is the scroller on the pages that scroll it
   instead of the root. `:where()` scores 0-0-0, so a strip that hides its own bar
   with a plain class selector still wins, and no page has to fight this rule. */
:where(body, body *) {
  scrollbar-width: thin;
}
@supports not (scrollbar-width: thin) {
  ::-webkit-scrollbar { width: 11px; height: 11px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb {
    /* A transparent border plus `background-clip: padding-box` insets the thumb
       inside its gutter, so it reads as a floating pill instead of filling a
       channel - the same shape Chromium's `thin` draws natively. */
    border: 3px solid transparent;
    border-radius: 8px;
    background-clip: padding-box;
    background-color: var(--border-2, var(--border-strong, oklch(0.400 0.032 259)));
  }
  ::-webkit-scrollbar-thumb:hover {
    background-color: var(--border-3, var(--border-strong, oklch(0.492 0.032 258)));
  }
  ::-webkit-scrollbar-corner { background: transparent; }
}

/* ── 12 · Skip link — WCAG 2.2 SC 2.4.1 (Bypass Blocks, level A) ─────
   Every page in this build opens with the same navigation block, and no page had
   a way past it: a keyboard or screen-reader user walked the whole row before
   reaching the page's own content, on every page, and the docs sidebar is up to
   fifteen stops on its own. This link is the first focusable node in the
   document and stays invisible until it takes focus.

   It is clipped rather than hidden. `display: none` and `visibility: hidden`
   both remove an element from the focus order, which is the one thing this
   element exists to be in - so the resting state is a 1px clipped box that is
   still focusable, and focus restores it to full size.

   `position: fixed` in both states: an absolute link inside a page that has
   already been scrolled would reveal itself off-screen. The target carries
   tabindex="-1" so the browser moves focus into it instead of only scrolling to
   it (Safari and Firefox otherwise leave focus on the link, and the next Tab
   walks straight back into the nav). Its focus ring is suppressed because it is
   a whole page region, not a control - the ring would outline the viewport. */
.skip-link {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 2000;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border-radius: 10px;
  background: var(--accent, #3d7dfb);
  color: #fff;
  font: 600 14px/1.2 var(--font-sans, ui-sans-serif, system-ui, sans-serif);
  text-decoration: none;
}
.skip-link:focus {
  width: auto;
  height: auto;
  padding: 11px 16px;
  overflow: visible;
  clip-path: none;
  outline: 2px solid #fff;
  outline-offset: 2px;
}
main[tabindex="-1"]:focus { outline: none; }
