/* =============================================================================
   GeoVerio Design System — components.css
   Version 1.0.0 · "Ember"
   -----------------------------------------------------------------------------
   Reference implementation of every component in the specification.
   References ONLY --gv-* semantic tokens. Zero colour literals, zero magic px.
   Load after tokens.css and base.css.

   Every component below was designed against a specific defect found in the
   2026-07-28 audit. Those are marked  ▸ FIXES <ID>.
   ============================================================================= */


/* =============================================================================
   BUTTON
   -----------------------------------------------------------------------------
   Six variants × three sizes. ONE primary treatment product-wide.

   The primary is a NEUTRAL fill, not an accent fill. That is deliberate:
   white-on-Ember tops out at 3.68:1 and can never reach AA, whereas
   neutral-on-canvas is 18.4:1. Ember earns its prominence from being the only
   saturated thing on the page, not from being the button colour.
   ▸ FIXES CT-01 (2.83:1 primary in dark), CB-01 (four primary treatments),
     CB-02 (indigo halo), CB-03 (three button heights).
   ============================================================================= */
.gv-btn {
  --_bg: transparent;
  --_fg: var(--gv-fg);
  --_bd: transparent;
  --_shadow: var(--gv-shadow-0);

  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--gv-space-2);
  height: var(--gv-btn-h, 2.25rem);            /* 36px */
  padding-inline: var(--gv-btn-px, var(--gv-space-4));
  border-radius: var(--gv-radius-md);
  border: var(--gv-border-width) solid var(--_bd);
  background: var(--_bg);
  color: var(--_fg);
  box-shadow: var(--_shadow);
  font-size: var(--gv-btn-fs, var(--gv-text-md));
  font-weight: var(--gv-weight-medium);
  letter-spacing: var(--gv-tracking-snug);
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  user-select: none;
  transition:
    background-color var(--gv-duration-fast) var(--gv-ease-standard),
    border-color     var(--gv-duration-fast) var(--gv-ease-standard),
    color            var(--gv-duration-fast) var(--gv-ease-standard),
    box-shadow       var(--gv-duration-fast) var(--gv-ease-standard),
    transform        var(--gv-duration-instant) var(--gv-ease-standard);
}
/* The hit area is expanded to the comfortable target without changing the
   painted box — so a 36px button is still a 44px tap target. ▸ FIXES MO-05 */
.gv-btn::after {
  content: ""; position: absolute; inset: 50% 0 auto 0;
  height: var(--gv-target-comfortable); transform: translateY(-50%);
}
.gv-btn { position: relative; }
.gv-btn:active { transform: translateY(0.5px); }
.gv-btn:focus-visible { box-shadow: var(--gv-focus-ring); }
.gv-btn[disabled], .gv-btn[aria-disabled="true"] {
  opacity: var(--gv-opacity-disabled); cursor: not-allowed; transform: none;
}

/* Sizes */
.gv-btn--sm { --gv-btn-h: 1.875rem; --gv-btn-px: var(--gv-space-3); --gv-btn-fs: var(--gv-text-sm); }
.gv-btn--lg { --gv-btn-h: 2.75rem;  --gv-btn-px: var(--gv-space-6); --gv-btn-fs: var(--gv-text-base); }

/* Variant — primary. The only high-emphasis action. One per view. */
.gv-btn--primary { --_bg: var(--gv-fg); --_fg: var(--gv-fg-inverse); --_shadow: var(--gv-shadow-1); }
.gv-btn--primary:hover { --_bg: color-mix(in srgb, var(--gv-fg) 88%, var(--gv-bg)); }
.gv-btn--primary:active { --_bg: color-mix(in srgb, var(--gv-fg) 80%, var(--gv-bg)); }

/* Variant — accent. Reserved for the ONE conversion CTA on a marketing page.
   Uses --gv-accent-solid (Ember 700 / Ember 400), which is contrast-validated
   under a label; --gv-accent itself is graphics-only and must never sit under text. */
.gv-btn--accent { --_bg: var(--gv-accent-solid); --_fg: var(--gv-fg-on-accent); --_shadow: var(--gv-shadow-1); }
.gv-btn--accent:hover { --_bg: color-mix(in srgb, var(--gv-accent-solid) 88%, var(--gv-fg)); }

/* Variant — secondary. The workhorse. */
.gv-btn--secondary { --_bg: var(--gv-surface); --_fg: var(--gv-fg); --_bd: var(--gv-border-strong); --_shadow: var(--gv-shadow-1); }
.gv-btn--secondary:hover { --_bg: var(--gv-surface-hover); --_bd: var(--gv-border-control); }
.gv-btn--secondary:active { --_bg: var(--gv-surface-active); }

/* Variant — ghost. Toolbars, table row actions, dense UI. */
.gv-btn--ghost { --_fg: var(--gv-fg-secondary); }
.gv-btn--ghost:hover { --_bg: var(--gv-surface-hover); --_fg: var(--gv-fg); }

/* Variant — danger. Outlined, never a solid red fill.
   In a red-orange brand a solid red button is indistinguishable from the CTA;
   destructive intent is carried by the outline + label + a confirm step. ▸ §4.2 */
.gv-btn--danger { --_fg: var(--gv-danger-ink); --_bd: var(--gv-danger-border); --_bg: var(--gv-surface); }
.gv-btn--danger:hover { --_bg: var(--gv-danger-subtle); --_bd: var(--gv-danger); }

/* Variant — link */
.gv-btn--link { --_fg: var(--gv-accent-ink); height: auto; padding: 0; }
.gv-btn--link:hover { text-decoration: underline; text-underline-offset: 0.18em; }

/* Icon-only. Square, and always requires an aria-label. */
.gv-btn--icon { width: var(--gv-btn-h); padding-inline: 0; }

/* Loading. The button MUST NOT be disabled while focused — disabling drops
   focus to <body>. Use aria-busy + pointer-events instead. ▸ FIXES A11Y-06 */
.gv-btn[aria-busy="true"] { pointer-events: none; }
.gv-btn[aria-busy="true"] .gv-btn__label { opacity: 0.55; }
.gv-spinner {
  width: 1em; height: 1em; flex: none;
  border: 2px solid currentColor; border-right-color: transparent;
  border-radius: var(--gv-radius-full); opacity: 0.9;
  animation: gv-spin var(--gv-duration-lazy) linear infinite;
}
@keyframes gv-spin { to { transform: rotate(360deg); } }

/* Button group */
.gv-btn-group { display: inline-flex; }
.gv-btn-group .gv-btn { border-radius: 0; margin-inline-start: calc(-1 * var(--gv-border-width)); }
.gv-btn-group .gv-btn:first-child { border-start-start-radius: var(--gv-radius-md); border-end-start-radius: var(--gv-radius-md); margin-inline-start: 0; }
.gv-btn-group .gv-btn:last-child { border-start-end-radius: var(--gv-radius-md); border-end-end-radius: var(--gv-radius-md); }
.gv-btn-group .gv-btn:focus-visible { z-index: var(--gv-z-docked); }


/* =============================================================================
   INPUT / FIELD
   -----------------------------------------------------------------------------
   Every field is a <label> + control + optional hint + optional error, and the
   hint and error are ALWAYS wired with aria-describedby. In the audited product
   `aria-describedby` appeared zero times. ▸ FIXES A11Y-09, CT-05
   ============================================================================= */
.gv-field { display: flex; flex-direction: column; gap: var(--gv-space-2); }
.gv-field__label {
  font-size: var(--gv-text-md); font-weight: var(--gv-weight-medium);
  color: var(--gv-fg); display: flex; align-items: center; gap: var(--gv-space-2);
}
.gv-field__optional { font-weight: var(--gv-weight-regular); color: var(--gv-fg-muted); font-size: var(--gv-text-sm); }
.gv-field__required { color: var(--gv-danger-ink); }
.gv-field__hint { font-size: var(--gv-text-sm); line-height: var(--gv-leading-sm); color: var(--gv-fg-secondary); }
.gv-field__error {
  display: flex; align-items: flex-start; gap: var(--gv-space-2);
  font-size: var(--gv-text-sm); line-height: var(--gv-leading-sm); color: var(--gv-danger-ink);
}
.gv-field__error .gv-icon { margin-top: 0.15em; }

.gv-input, .gv-select, .gv-textarea {
  width: 100%;
  min-height: var(--gv-input-h, 2.25rem);
  padding: var(--gv-space-2) var(--gv-space-3);
  background: var(--gv-surface);
  color: var(--gv-fg);
  border: var(--gv-border-width) solid var(--gv-border-control);  /* 3:1 boundary */
  border-radius: var(--gv-radius-md);
  font-size: var(--gv-text-md);
  line-height: var(--gv-leading-md);
  transition: border-color var(--gv-duration-fast) var(--gv-ease-standard),
              box-shadow  var(--gv-duration-fast) var(--gv-ease-standard);
}
.gv-input::placeholder, .gv-textarea::placeholder { color: var(--gv-fg-subtle); }
.gv-input:hover, .gv-select:hover, .gv-textarea:hover { border-color: var(--gv-fg-subtle); }
.gv-input:focus-visible, .gv-select:focus-visible, .gv-textarea:focus-visible {
  border-color: var(--gv-border-accent);
  box-shadow: var(--gv-focus-ring);
}
.gv-input[aria-invalid="true"], .gv-select[aria-invalid="true"], .gv-textarea[aria-invalid="true"] { border-color: var(--gv-danger); }
.gv-input[disabled], .gv-select[disabled], .gv-textarea[disabled] {
  background: var(--gv-bg-subtle); color: var(--gv-fg-muted);
  opacity: 1; cursor: not-allowed;
}
.gv-input[readonly] { background: var(--gv-bg-subtle); }
.gv-textarea { min-height: 5.5rem; resize: vertical; line-height: var(--gv-leading-base); }
.gv-input--sm { --gv-input-h: 1.875rem; font-size: var(--gv-text-sm); }
.gv-input--lg { --gv-input-h: 2.75rem;  font-size: var(--gv-text-base); }
.gv-input--mono { font-family: var(--gv-font-mono); letter-spacing: 0.02em; }

.gv-select {
  appearance: none; padding-inline-end: var(--gv-space-8); cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right var(--gv-space-4) center, right var(--gv-space-3) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

/* Input with leading / trailing adornment */
.gv-input-group { display: flex; align-items: stretch; }
.gv-input-group__addon {
  display: inline-flex; align-items: center; padding-inline: var(--gv-space-3);
  background: var(--gv-bg-subtle); color: var(--gv-fg-muted);
  border: var(--gv-border-width) solid var(--gv-border-control);
  font-family: var(--gv-font-mono); font-size: var(--gv-text-sm);
}
.gv-input-group__addon:first-child { border-inline-end: none; border-start-start-radius: var(--gv-radius-md); border-end-start-radius: var(--gv-radius-md); }
.gv-input-group__addon:last-child  { border-inline-start: none; border-start-end-radius: var(--gv-radius-md); border-end-end-radius: var(--gv-radius-md); }
.gv-input-group__addon + .gv-input { border-start-start-radius: 0; border-end-start-radius: 0; }
.gv-input-group .gv-input:has(+ .gv-input-group__addon) { border-start-end-radius: 0; border-end-end-radius: 0; }

/* Checkbox & radio — 20px painted, 44px hit area. The audited checkbox was
   16×16, below even the WCAG 2.2 AA floor. ▸ FIXES MO-05 */
.gv-check { display: flex; align-items: flex-start; gap: var(--gv-space-3); cursor: pointer; padding-block: var(--gv-space-2); }
.gv-check input[type="checkbox"], .gv-check input[type="radio"] {
  appearance: none; flex: none;
  width: 1.25rem; height: 1.25rem; margin: 0;
  border: var(--gv-border-width-thick) solid var(--gv-border-control);
  border-radius: var(--gv-radius-xs);
  background: var(--gv-surface);
  cursor: pointer;
  transition: background-color var(--gv-duration-fast) var(--gv-ease-standard),
              border-color var(--gv-duration-fast) var(--gv-ease-standard);
}
.gv-check input[type="radio"] { border-radius: var(--gv-radius-full); }
.gv-check input:checked { background: var(--gv-accent-solid); border-color: var(--gv-accent-solid); }
.gv-check input[type="checkbox"]:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-size: 0.8rem; background-position: center; background-repeat: no-repeat;
}
.gv-check input[type="radio"]:checked { box-shadow: inset 0 0 0 3px var(--gv-surface); }
.gv-check input:focus-visible { box-shadow: var(--gv-focus-ring); }
.gv-check__text { font-size: var(--gv-text-md); line-height: 1.25rem; }

/* Switch */
.gv-switch { display: inline-flex; align-items: center; gap: var(--gv-space-3); cursor: pointer; }
.gv-switch input { appearance: none; margin: 0; width: 2.25rem; height: 1.25rem; flex: none;
  background: var(--gv-border-control); border-radius: var(--gv-radius-full); position: relative; cursor: pointer;
  transition: background-color var(--gv-duration-fast) var(--gv-ease-standard); }
.gv-switch input::after { content: ""; position: absolute; inset-block: 3px; inset-inline-start: 3px;
  width: 0.875rem; background: var(--gv-surface); border-radius: var(--gv-radius-full);
  transition: translate var(--gv-duration-fast) var(--gv-ease-standard); }
.gv-switch input:checked { background: var(--gv-accent-solid); }
.gv-switch input:checked::after { translate: 1rem 0; }
.gv-switch input:focus-visible { box-shadow: var(--gv-focus-ring); }


/* =============================================================================
   CARD
   ============================================================================= */
.gv-card {
  background: var(--gv-surface);
  border: var(--gv-border-width) solid var(--gv-border);
  border-radius: var(--gv-radius-lg);
  box-shadow: var(--gv-highlight);
  overflow: hidden;
}
.gv-card__header {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--gv-space-4);
  padding: var(--gv-space-4) var(--gv-space-5);
  border-bottom: var(--gv-border-width) solid var(--gv-border);
}
.gv-card__title { font-size: var(--gv-text-lg); line-height: var(--gv-leading-lg); font-weight: var(--gv-weight-semibold); letter-spacing: var(--gv-tracking-snug); }
.gv-card__subtitle { font-size: var(--gv-text-sm); color: var(--gv-fg-secondary); margin-top: var(--gv-space-1); }
.gv-card__body { padding: var(--gv-space-5); }
.gv-card__footer { padding: var(--gv-space-4) var(--gv-space-5); border-top: var(--gv-border-width) solid var(--gv-border); background: var(--gv-bg-subtle); }

.gv-card--raised { box-shadow: var(--gv-shadow-2), var(--gv-highlight); }
.gv-card--flat   { box-shadow: none; background: var(--gv-bg-subtle); border-color: transparent; }
.gv-card--interactive {
  transition: border-color var(--gv-duration-base) var(--gv-ease-standard),
              box-shadow   var(--gv-duration-base) var(--gv-ease-standard),
              transform    var(--gv-duration-base) var(--gv-ease-out);
}
.gv-card--interactive:hover { border-color: var(--gv-border-strong); box-shadow: var(--gv-shadow-2), var(--gv-highlight); transform: translateY(-1px); }
.gv-card--interactive:focus-within { box-shadow: var(--gv-focus-ring); }
/* A whole-card link. The card is ONE link — never a link inside a link. ▸ FIXES LP-05 */
.gv-card__link::after { content: ""; position: absolute; inset: 0; }
.gv-card--interactive { position: relative; }

/* Feature card with an accent icon tile */
.gv-card__icon {
  width: 2.25rem; height: 2.25rem; display: grid; place-items: center;
  border-radius: var(--gv-radius-md);
  background: var(--gv-accent-muted); color: var(--gv-accent-ink);
}

/* KPI / stat tile — always pairs an allowance with its consumption. ▸ FIXES DB-01 */
.gv-kpi { padding: var(--gv-space-4) var(--gv-space-5); }
.gv-kpi__label { font-size: var(--gv-text-xs); font-weight: var(--gv-weight-medium); letter-spacing: var(--gv-tracking-wide); text-transform: uppercase; color: var(--gv-fg-muted); }
.gv-kpi__value { font-size: var(--gv-text-3xl); line-height: var(--gv-leading-3xl); font-weight: var(--gv-weight-semibold); letter-spacing: var(--gv-tracking-tight); margin-top: var(--gv-space-2); font-variant-numeric: tabular-nums; }
.gv-kpi__hint { font-size: var(--gv-text-sm); color: var(--gv-fg-secondary); margin-top: var(--gv-space-1); }
.gv-meter { height: 6px; border-radius: var(--gv-radius-full); background: var(--gv-bg-inset); overflow: hidden; margin-top: var(--gv-space-3); }
.gv-meter__fill { height: 100%; background: var(--gv-accent); border-radius: inherit; }
.gv-meter--warning .gv-meter__fill { background: var(--gv-warning); }
.gv-meter--danger  .gv-meter__fill { background: var(--gv-danger); }


/* =============================================================================
   TABLE
   -----------------------------------------------------------------------------
   The wrapper scrolls; the table NEVER re-flows into blocks. The audited
   dashboard set `.table thead { display: none }` below 860px, which removed
   every columnheader node from the accessibility tree.
   ▸ FIXES A11Y-03. The wrapper is focusable and named. ▸ FIXES LY-02
   ============================================================================= */
.gv-table-wrap {
  overflow-x: auto;
  border: var(--gv-border-width) solid var(--gv-border);
  border-radius: var(--gv-radius-lg);
  background: var(--gv-surface);
  -webkit-overflow-scrolling: touch;
}
.gv-table-wrap:focus-visible { box-shadow: var(--gv-focus-ring); }
.gv-table { width: 100%; min-width: 34rem; border-collapse: collapse; font-size: var(--gv-text-md); }
.gv-table th, .gv-table td { padding: var(--gv-space-3) var(--gv-space-4); text-align: start; vertical-align: middle; }
.gv-table thead th {
  position: sticky; top: 0; z-index: var(--gv-z-docked);
  background: var(--gv-bg-subtle);
  font-size: var(--gv-text-xs); font-weight: var(--gv-weight-semibold);
  letter-spacing: var(--gv-tracking-wide); text-transform: uppercase;
  color: var(--gv-fg-secondary);            /* 4.5:1, not the old 2.88:1 muted */
  border-bottom: var(--gv-border-width) solid var(--gv-border);
  white-space: nowrap;
}
.gv-table tbody tr + tr td { border-top: var(--gv-border-width) solid var(--gv-border); }
.gv-table tbody tr:hover td { background: var(--gv-surface-hover); }
.gv-table--dense th, .gv-table--dense td { padding: var(--gv-space-2) var(--gv-space-3); }
.gv-table__num { text-align: end; font-variant-numeric: tabular-nums; font-family: var(--gv-font-mono); font-size: var(--gv-text-sm); }
.gv-table__actions { width: 1%; white-space: nowrap; text-align: end; }
/* An actions column still needs an accessible name — use a visually hidden th,
   never an empty one. ▸ FIXES A11Y (empty columnheader) */


/* =============================================================================
   CODE BLOCK
   -----------------------------------------------------------------------------
   Scrolls, never clips. The audited home page capped code at 320px inside
   overflow:hidden with no fade, so every sample ended mid-object.
   ▸ FIXES LY-02, MO-02
   ============================================================================= */
.gv-code {
  border: var(--gv-border-width) solid var(--gv-code-border);
  border-radius: var(--gv-radius-lg);
  background: var(--gv-code-bg);
  overflow: hidden;
}
.gv-code__head {
  display: flex; align-items: center; gap: var(--gv-space-2);
  padding: var(--gv-space-2) var(--gv-space-3);
  border-bottom: var(--gv-border-width) solid var(--gv-code-border);
  background: color-mix(in srgb, var(--gv-code-bg) 60%, var(--gv-bg));
}
.gv-code__file { font-family: var(--gv-font-mono); font-size: var(--gv-text-xs); color: var(--gv-fg-secondary); }
.gv-code__tabs { display: flex; gap: var(--gv-space-1); overflow-x: auto; scrollbar-width: none; }
.gv-code__tabs::-webkit-scrollbar { display: none; }
.gv-code__tab {
  padding: var(--gv-space-1) var(--gv-space-3); min-height: var(--gv-target-min);
  border-radius: var(--gv-radius-sm);
  font-family: var(--gv-font-mono); font-size: var(--gv-text-xs);
  color: var(--gv-fg-muted); white-space: nowrap;
}
.gv-code__tab[aria-selected="true"] { background: var(--gv-surface-active); color: var(--gv-fg); }
.gv-code__tab:focus-visible { box-shadow: var(--gv-focus-ring); }
.gv-code__actions { margin-inline-start: auto; display: flex; gap: var(--gv-space-1); }
.gv-code__body { overflow: auto; max-height: var(--gv-code-max, 28rem); }
.gv-code pre {
  margin: 0; padding: var(--gv-space-4);
  font-family: var(--gv-font-mono);
  font-size: var(--gv-text-sm); line-height: 1.65;
  color: var(--gv-code-fg);
  tab-size: 2;
}
.gv-code--sm { --gv-code-max: 16rem; }
/* Syntax tokens — one palette, both modes, all validated ≥4.5:1 */
.gv-t-key  { color: var(--gv-code-key); }
.gv-t-str  { color: var(--gv-code-str); }
.gv-t-num  { color: var(--gv-code-num); }
.gv-t-bool { color: var(--gv-code-bool); }
.gv-t-null { color: var(--gv-code-null); }
.gv-t-com  { color: var(--gv-code-comment); font-style: italic; }
.gv-t-punc { color: var(--gv-code-punc); }
/* Inline code */
:where(p, li, td, .gv-prose) > code {
  font-family: var(--gv-font-mono); font-size: 0.88em;
  padding: 0.1em 0.34em; border-radius: var(--gv-radius-sm);
  background: var(--gv-bg-subtle); border: var(--gv-border-width) solid var(--gv-border);
  white-space: nowrap;
}


/* =============================================================================
   ALERT / BANNER
   -----------------------------------------------------------------------------
   Four intents. Colour is never the only signal: every intent ships an icon
   and a text label. ▸ WCAG 1.4.1
   ============================================================================= */
.gv-alert {
  display: flex; gap: var(--gv-space-3);
  padding: var(--gv-space-4);
  border: var(--gv-border-width) solid var(--gv-alert-bd, var(--gv-border));
  border-radius: var(--gv-radius-lg);
  background: var(--gv-alert-bg, var(--gv-bg-subtle));
  color: var(--gv-fg);
  font-size: var(--gv-text-md);
}
.gv-alert__icon { color: var(--gv-alert-fg, var(--gv-fg-secondary)); flex: none; margin-top: 0.1em; }
.gv-alert__body { flex: 1; min-width: 0; }
.gv-alert__title { font-weight: var(--gv-weight-semibold); color: var(--gv-alert-fg, var(--gv-fg)); }
.gv-alert__text { color: var(--gv-fg-secondary); margin-top: var(--gv-space-1); }
.gv-alert__actions { margin-top: var(--gv-space-3); display: flex; gap: var(--gv-space-2); }
.gv-alert__close { margin-inline-start: auto; flex: none; color: var(--gv-fg-muted); }

.gv-alert--info    { --gv-alert-bg: var(--gv-info-subtle);    --gv-alert-bd: var(--gv-info-border);    --gv-alert-fg: var(--gv-info-ink); }
.gv-alert--success { --gv-alert-bg: var(--gv-success-subtle); --gv-alert-bd: var(--gv-success-border); --gv-alert-fg: var(--gv-success-ink); }
.gv-alert--warning { --gv-alert-bg: var(--gv-warning-subtle); --gv-alert-bd: var(--gv-warning-border); --gv-alert-fg: var(--gv-warning-ink); }
.gv-alert--danger  { --gv-alert-bg: var(--gv-danger-subtle);  --gv-alert-bd: var(--gv-danger-border);  --gv-alert-fg: var(--gv-danger-ink); }
/* Page-width banner variant */
.gv-alert--banner { border-radius: 0; border-inline: none; }

/* Toast. Timer pauses on hover AND on focus, and never auto-dismisses a
   danger toast. ▸ FIXES A11Y-11 */
.gv-toast {
  display: flex; gap: var(--gv-space-3); align-items: flex-start;
  padding: var(--gv-space-3) var(--gv-space-4);
  background: var(--gv-surface-raised);
  border: var(--gv-border-width) solid var(--gv-border-strong);
  border-radius: var(--gv-radius-lg);
  box-shadow: var(--gv-shadow-4), var(--gv-highlight);
  max-width: 26rem;
}
.gv-toasts { position: fixed; inset-block-end: var(--gv-space-6); inset-inline-end: var(--gv-space-6);
  z-index: var(--gv-z-toast); display: flex; flex-direction: column; gap: var(--gv-space-3); }


/* =============================================================================
   BADGE
   -----------------------------------------------------------------------------
   3 styles × 6 intents × 2 sizes. Soft is the default; solid is reserved for
   counts and one-off emphasis; dot is for live/status indicators.
   ============================================================================= */
.gv-badge {
  display: inline-flex; align-items: center; gap: var(--gv-space-1);
  padding: 0.15rem var(--gv-space-2);
  border-radius: var(--gv-radius-full);
  border: var(--gv-border-width) solid transparent;
  font-size: var(--gv-text-xs); line-height: 1.2;
  font-weight: var(--gv-weight-medium);
  letter-spacing: var(--gv-tracking-none);
  white-space: nowrap;
  background: var(--gv-badge-bg, var(--gv-bg-inset));
  color: var(--gv-badge-fg, var(--gv-fg-secondary));
  border-color: var(--gv-badge-bd, transparent);
}
.gv-badge--sm { font-size: var(--gv-text-2xs); padding: 0.1rem var(--gv-space-1); }
.gv-badge--square { border-radius: var(--gv-radius-sm); font-family: var(--gv-font-mono); }

.gv-badge--neutral { --gv-badge-bg: var(--gv-bg-inset);       --gv-badge-fg: var(--gv-fg-secondary); }
.gv-badge--accent  { --gv-badge-bg: var(--gv-accent-subtle);  --gv-badge-fg: var(--gv-accent-ink);  --gv-badge-bd: var(--gv-accent-border); }
.gv-badge--success { --gv-badge-bg: var(--gv-success-subtle); --gv-badge-fg: var(--gv-success-ink); --gv-badge-bd: var(--gv-success-border); }
.gv-badge--warning { --gv-badge-bg: var(--gv-warning-subtle); --gv-badge-fg: var(--gv-warning-ink); --gv-badge-bd: var(--gv-warning-border); }
.gv-badge--danger  { --gv-badge-bg: var(--gv-danger-subtle);  --gv-badge-fg: var(--gv-danger-ink);  --gv-badge-bd: var(--gv-danger-border); }
.gv-badge--info    { --gv-badge-bg: var(--gv-info-subtle);    --gv-badge-fg: var(--gv-info-ink);    --gv-badge-bd: var(--gv-info-border); }

.gv-badge--solid.gv-badge--neutral { --gv-badge-bg: var(--gv-fg);            --gv-badge-fg: var(--gv-fg-inverse); }
.gv-badge--solid.gv-badge--accent  { --gv-badge-bg: var(--gv-accent-solid);  --gv-badge-fg: var(--gv-fg-on-accent); }
.gv-badge--solid.gv-badge--danger  { --gv-badge-bg: var(--gv-danger);        --gv-badge-fg: var(--gv-fg-on-solid); }

.gv-badge--outline { --gv-badge-bg: transparent; --gv-badge-bd: var(--gv-border-strong); --gv-badge-fg: var(--gv-fg-secondary); }

.gv-badge__dot { width: 0.4rem; height: 0.4rem; border-radius: var(--gv-radius-full); background: currentColor; flex: none; }
/* HTTP method pills for docs */
.gv-method { font-family: var(--gv-font-mono); font-weight: var(--gv-weight-semibold); letter-spacing: 0.03em; }
.gv-method--get    { --gv-badge-bg: var(--gv-success-subtle); --gv-badge-fg: var(--gv-success-ink); }
.gv-method--post   { --gv-badge-bg: var(--gv-info-subtle);    --gv-badge-fg: var(--gv-info-ink); }
.gv-method--delete { --gv-badge-bg: var(--gv-danger-subtle);  --gv-badge-fg: var(--gv-danger-ink); }


/* =============================================================================
   SKELETON
   -----------------------------------------------------------------------------
   A skeleton must mirror the shape of what is loading, be marked aria-hidden,
   and sit next to a visually hidden live region announcing "Loading …".
   The sheen animation is suppressed under reduced-motion.
   ============================================================================= */
.gv-skel {
  background: var(--gv-skeleton-base);
  border-radius: var(--gv-radius-sm);
  position: relative; overflow: hidden;
}
.gv-skel::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--gv-skeleton-sheen), transparent);
  translate: -100% 0;
  animation: gv-sheen 1.4s var(--gv-ease-standard) infinite;
}
@keyframes gv-sheen { to { translate: 100% 0; } }
@media (prefers-reduced-motion: reduce) { .gv-skel::after { animation: none; opacity: 0.5; } }

.gv-skel--text  { height: 0.75rem; border-radius: var(--gv-radius-full); }
.gv-skel--title { height: 1.125rem; border-radius: var(--gv-radius-full); }
.gv-skel--circle{ border-radius: var(--gv-radius-full); }
.gv-skel--block { height: 8rem; border-radius: var(--gv-radius-lg); }
.gv-skel-lines > * + * { margin-top: var(--gv-space-2); }
.gv-skel-lines > :last-child { width: 62%; }


/* =============================================================================
   EMPTY STATE
   -----------------------------------------------------------------------------
   Icon + title + one sentence of why + exactly one primary action.
   The primary action must be the NEXT thing the user should do, which for a
   brand-new account is never "Upgrade plan". ▸ FIXES DB-03
   ============================================================================= */
.gv-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--gv-space-3);
  padding: var(--gv-space-12) var(--gv-space-6);
}
.gv-empty__icon {
  width: 3rem; height: 3rem; display: grid; place-items: center;
  border-radius: var(--gv-radius-xl);
  background: var(--gv-bg-inset); color: var(--gv-fg-muted);
  margin-bottom: var(--gv-space-1);
}
.gv-empty__title { font-size: var(--gv-text-lg); font-weight: var(--gv-weight-semibold); letter-spacing: var(--gv-tracking-snug); }
.gv-empty__text { font-size: var(--gv-text-md); color: var(--gv-fg-secondary); max-width: 42ch; }
.gv-empty__actions { margin-top: var(--gv-space-2); display: flex; gap: var(--gv-space-2); flex-wrap: wrap; justify-content: center; }
.gv-empty--sm { padding: var(--gv-space-8) var(--gv-space-4); }
.gv-empty--sm .gv-empty__icon { width: 2.25rem; height: 2.25rem; border-radius: var(--gv-radius-lg); }
.gv-empty--page { padding: var(--gv-space-20) var(--gv-space-6); }
.gv-empty--page .gv-empty__icon { width: 4rem; height: 4rem; }


/* =============================================================================
   SUPPORTING PATTERNS
   ============================================================================= */
/* Tabs — a real tablist with roving tabindex expected from the JS layer. */
.gv-tabs { display: flex; gap: var(--gv-space-1); border-bottom: var(--gv-border-width) solid var(--gv-border); overflow-x: auto; }
.gv-tab {
  padding: var(--gv-space-3) var(--gv-space-3); min-height: var(--gv-target-comfortable);
  color: var(--gv-fg-secondary); font-size: var(--gv-text-md); font-weight: var(--gv-weight-medium);
  border-bottom: var(--gv-border-width-thick) solid transparent; margin-bottom: calc(-1 * var(--gv-border-width));
  white-space: nowrap;
}
.gv-tab:hover { color: var(--gv-fg); }
.gv-tab[aria-selected="true"] { color: var(--gv-fg); border-bottom-color: var(--gv-accent); }
.gv-tab:focus-visible { box-shadow: var(--gv-focus-ring); border-radius: var(--gv-radius-md); }

/* Segmented control */
.gv-segment { display: inline-flex; padding: var(--gv-space-0-5); gap: var(--gv-space-0-5);
  background: var(--gv-bg-inset); border-radius: var(--gv-radius-md); }
.gv-segment__item { padding: var(--gv-space-1) var(--gv-space-3); min-height: 1.75rem; border-radius: calc(var(--gv-radius-md) - 2px);
  font-size: var(--gv-text-sm); font-weight: var(--gv-weight-medium); color: var(--gv-fg-secondary); }
.gv-segment__item[aria-pressed="true"] { background: var(--gv-surface); color: var(--gv-fg); box-shadow: var(--gv-shadow-1); }

/* Copy button — pairs with a visually hidden live region so the confirmation
   is announced, not just drawn. ▸ FIXES A11Y-12 */
.gv-copy { display: inline-flex; align-items: center; gap: var(--gv-space-1); }

/* Live region helper */
.gv-live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* Key / secret reveal box */
.gv-secret {
  display: flex; align-items: center; gap: var(--gv-space-3);
  padding: var(--gv-space-3) var(--gv-space-4);
  background: var(--gv-code-bg); border: var(--gv-border-width) dashed var(--gv-border-strong);
  border-radius: var(--gv-radius-md);
  font-family: var(--gv-font-mono); font-size: var(--gv-text-sm);
  word-break: break-all;
}

/* Definition list used for response fields */
.gv-dl { display: grid; gap: var(--gv-space-4); }
.gv-dl__row { display: grid; gap: var(--gv-space-1); padding-bottom: var(--gv-space-4); border-bottom: var(--gv-border-width) solid var(--gv-border); }
.gv-dl__term { display: flex; align-items: center; gap: var(--gv-space-2); font-family: var(--gv-font-mono); font-size: var(--gv-text-sm); color: var(--gv-fg); }
.gv-dl__type { font-size: var(--gv-text-2xs); color: var(--gv-fg-muted); }
.gv-dl__desc { font-size: var(--gv-text-md); color: var(--gv-fg-secondary); }


/* =============================================================================
   Atlas V2 — loading additions from the brand pack (loading.css), re-expressed
   on system tokens. The spinner and skeleton already existed above; the pack
   contributes the progress bar, indeterminate progress, dots and logo loader.
   Rules: progress bars for measurable work, indeterminate for unknown
   duration, dots for compact inline waits, the logo loader ONLY for branded
   full-page/app initialisation. Mark decorative instances aria-hidden; give
   meaningful ones role="status".
   ========================================================================== */
.gv-progress { height: 8px; overflow: hidden; border-radius: var(--gv-radius-full); background: var(--gv-skeleton-base); }
.gv-progress > span { display: block; height: 100%; width: var(--progress, 0%); border-radius: inherit; background: var(--gv-brand-gradient); transition: width var(--gv-duration-slow) var(--gv-ease-out, ease); }
.gv-progress--indeterminate > span { width: 38%; animation: gv-progress-slide 1.25s ease-in-out infinite; }

.gv-dots { display: inline-flex; gap: var(--gv-space-1); }
.gv-dots i { width: 6px; height: 6px; border-radius: var(--gv-radius-full); background: var(--gv-accent); animation: gv-dot-pulse 1.2s ease-in-out infinite; }
.gv-dots i:nth-child(2) { animation-delay: .15s; }
.gv-dots i:nth-child(3) { animation-delay: .3s; }

.gv-loader-logo { display: block; width: 56px; height: 56px; margin-inline: auto; animation: gv-logo-pulse 1.4s ease-in-out infinite; }

@keyframes gv-progress-slide { 0% { transform: translateX(-110%); } 100% { transform: translateX(270%); } }
@keyframes gv-dot-pulse { 0%, 100% { opacity: .35; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-2px); } }
@keyframes gv-logo-pulse { 50% { opacity: .42; } }

@media (prefers-reduced-motion: reduce) {
  .gv-progress--indeterminate > span, .gv-dots i, .gv-loader-logo { animation: none; }
  .gv-progress--indeterminate > span { width: 100%; opacity: .5; }
}

/* =============================================================================
   DIALOG
   Replaces window.confirm(). Built on <dialog>, so showModal() supplies the
   focus trap, the inert background and Escape — this file only dresses it.
   The destructive confirm reuses .gv-btn--danger, which is OUTLINED by system
   rule: a filled rose button beside a filled ember button reads as two equal
   primary actions, and rose sits ~15deg from the brand hue.
   ========================================================================== */
.gv-dialog {
  width: min(30rem, calc(100vw - 2 * var(--gv-space-5)));
  /* The global reset zeroes margins, which kills <dialog>'s own centering —
     showModal() relies on `margin: auto` in the top layer. */
  margin: auto;
  padding: 0;
  border: var(--gv-border-width) solid var(--gv-border);
  border-radius: var(--gv-radius-xl);
  background: var(--gv-surface-raised);
  color: var(--gv-fg);
  box-shadow: var(--gv-shadow-5);
}
.gv-dialog::backdrop {
  background: rgba(10, 9, 8, 0.55);
  backdrop-filter: blur(var(--gv-blur-sm));
}
.gv-dialog__body { padding: var(--gv-space-6) var(--gv-space-6) var(--gv-space-5); }
.gv-dialog__title {
  margin: 0;
  font-size: var(--gv-text-xl); line-height: var(--gv-leading-xl);
  font-weight: var(--gv-weight-semibold); letter-spacing: var(--gv-tracking-tight);
}
.gv-dialog__text {
  margin: var(--gv-space-3) 0 0;
  font-size: var(--gv-text-md); line-height: var(--gv-leading-md);
  color: var(--gv-fg-secondary);
}
.gv-dialog__actions {
  display: flex; justify-content: flex-end; gap: var(--gv-space-3);
  padding: var(--gv-space-4) var(--gv-space-6);
  border-block-start: var(--gv-border-width) solid var(--gv-border);
  background: var(--gv-bg-subtle);
  border-radius: 0 0 calc(var(--gv-radius-xl) - 1px) calc(var(--gv-radius-xl) - 1px);
}
@media (max-width: 480px) {
  .gv-dialog__actions { flex-direction: column-reverse; }
  .gv-dialog__actions > .gv-btn { width: 100%; }
}
@media (prefers-reduced-motion: no-preference) {
  .gv-dialog[open] { animation: gv-dialog-in var(--gv-duration-base) var(--gv-ease-out, ease-out); }
  @keyframes gv-dialog-in {
    from { opacity: 0; transform: translateY(6px) scale(0.99); }
  }
}

/* =============================================================================
   OTP — segmented one-time-code field
   One character per cell, grouped 000 - 000. The cells are the interface; the
   real <input> behind them keeps the value, so the form posts unchanged and
   the screen still works with JavaScript off (it stays a single field).
   ========================================================================== */
.gv-otp {
  display: flex; align-items: center; justify-content: center;
  gap: var(--gv-space-2);
  margin-block-start: var(--gv-space-1);
}
.gv-otp__cell {
  width: 3rem; height: 3.5rem;
  padding: 0;
  text-align: center;
  font-family: var(--gv-font-mono);
  font-size: var(--gv-text-2xl); line-height: 1;
  font-weight: var(--gv-weight-medium);
  font-variant-numeric: tabular-nums;
  color: var(--gv-fg);
  background: var(--gv-surface);
  border: var(--gv-border-width) solid var(--gv-border-strong, var(--gv-border));
  border-radius: var(--gv-radius-md);
  transition: border-color var(--gv-duration-fast) ease, box-shadow var(--gv-duration-fast) ease;
  caret-color: var(--gv-accent);
}
.gv-otp__cell:hover { border-color: var(--gv-border-hover, var(--gv-fg-muted)); }
.gv-otp__cell:focus {
  outline: none;
  border-color: var(--gv-accent);
  box-shadow: var(--gv-focus-ring);
}
/* A filled cell reads as done without relying on colour alone: the value is
   the signal, the border only reinforces it. */
.gv-otp__cell:not(:placeholder-shown) { border-color: var(--gv-fg-muted); }
.gv-otp__dash {
  width: 0.75rem; height: var(--gv-border-width);
  background: var(--gv-border-strong, var(--gv-border));
  flex: none;
}
.gv-otp__alt {
  display: block; margin: var(--gv-space-4) auto 0;
  padding: var(--gv-space-1) var(--gv-space-2);
  border: 0; background: none; cursor: pointer;
  font: inherit; font-size: var(--gv-text-sm);
  color: var(--gv-fg-secondary); text-decoration: underline; text-underline-offset: 0.18em;
  border-radius: var(--gv-radius-sm);
}
.gv-otp__alt:hover { color: var(--gv-fg); }
.gv-otp__alt:focus-visible { outline: none; box-shadow: var(--gv-focus-ring); }

@media (max-width: 400px) {
  .gv-otp { gap: var(--gv-space-1); }
  .gv-otp__cell { width: 2.5rem; height: 3rem; font-size: var(--gv-text-xl); }
}

/* =============================================================================
   CAPTCHA
   The shared _captcha.twig widget ships engine-agnostic hooks; this gives them
   a home in the auth card. The min-height reserves the vendor iframe's box
   (Turnstile is 300x65) so the card does not jump when it finishes loading.
   ========================================================================== */
.gv-captcha { margin-block: var(--gv-space-4); }
.gv-captcha__frame {
  display: flex; justify-content: center;
  min-height: 4.0625rem; /* 65px — the vendor widget's own height */
}
/* The built-in engine is a question and a text answer: it gets the same field
   furniture as every other input on the form rather than browser defaults. */
.gv-captcha__label {
  display: block;
  margin-block-end: var(--gv-space-2);
  font-size: var(--gv-text-md); font-weight: var(--gv-weight-medium);
}
