:root {
  --csp-scheme: light;
  --csp-color-canvas: #F7F7FB;
  --csp-color-surface: #FFFFFF;
  --csp-color-surface-raised: #FFFFFF;
  --csp-color-surface-sunken: #F7F5FF;
  --csp-color-surface-muted: #F0EEF8;
  --csp-color-border: #E6E4F0;
  --csp-color-border-strong: #D9D4EE;
  --csp-color-edge-light: hsl(0 0% 100% / 0);
  --csp-color-text-primary: #16152B;
  --csp-color-text-secondary: #45435E;
  --csp-color-text-muted: #5E5C78;
  --csp-color-text-inverse: #FFFFFF;
  --csp-color-accent: #5B3FE0;
  --csp-color-accent-strong: #3A22B8;
  --csp-color-accent-subtle: #EEE9FF;
  --csp-color-accent-border: #C9BEFF;
  --csp-color-accent-wash: #E6E0FF;
  --csp-color-accent-wash-strong: #DCD4FF;
  --csp-color-accent-text: #FFFFFF;
  --csp-color-accent-ink: #5B3FE0;
  --csp-color-caution-surface: #FFF4D6;
  --csp-color-caution-border: #F5C26B;
  --csp-color-caution-text: #6B4A00;
  --csp-color-critical-surface: #FFE1E6;
  --csp-color-critical-border: #F4B3C0;
  --csp-color-critical-text: #B0192F;
  --csp-color-focus-ring: #7B5CFF;
  --csp-color-placeholder-underline: rgba(22, 21, 43, 0.4);
  --csp-color-plot-ground: hsl(255 28% 9%);
  --csp-color-plot-ink: hsl(255 20% 96%);
  --csp-color-plot-ink-muted: hsl(255 12% 72%);
  --csp-color-plot-series-one: hsl(265 85% 78%);
  --csp-color-plot-series-two: hsl(152 68% 60%);
  --csp-color-plot-series-three: hsl(355 90% 74%);
  --csp-color-text-faint: #8A88A3;
  --csp-color-text-colon: #B9B3D6;
  --csp-color-border-control: #E0DCF0;
  --csp-color-border-radio: #C9C4E0;
  --csp-color-divider: #EEEBF7;
  --csp-color-seat-empty: #E7E4F4;
  --csp-color-accent-bright: #7B5CFF;
  --csp-color-accent-mid: #A48CFF;
  --csp-color-lavender: #E6E0FF;
  --csp-color-lavender-soft: #EEE9FF;
  --csp-color-inset: #F7F5FF;
  --csp-color-inset-strong: #F3F0FF;
  --csp-color-inset-feed: #F8F7FC;
  --csp-color-success: #0E9F5E;
  --csp-color-success-text: #0B7A45;
  --csp-color-success-surface: #DDF8E9;
  --csp-color-flash-up-surface: #C9F7DD;
  --csp-color-flash-up-text: #0B7A45;
  --csp-color-flash-down-surface: #FFD3DA;
  --csp-color-flash-down-text: #B0192F;
  --csp-color-urgent: #E0344F;
  --csp-color-fire: #E8340C;
  --csp-color-fire-text: #B0360C;
  --csp-color-fire-chip-surface: #FFE8D6;
  --csp-color-fire-chip-text: #9A3A00;
  --csp-color-sizzle-surface: #FFF6EC;
  --csp-color-heat-track: #FFE3CC;
  --csp-color-yellow: #FFD23F;
  --csp-color-yellow-surface: #FFF9E0;
  --csp-color-gold: #FFE070;
  --csp-color-pink: #FF4FC3;
  --csp-color-pink-surface: #FFE8F6;
  --csp-color-pink-text: #B0197A;
  --csp-color-yours-surface: #FFE3F4;
  --csp-color-yours-text: #9C1470;
  --csp-color-split-creator: #D6338F;
  --csp-color-split-creator-stripe: #F2A7CF;
  --csp-color-split-referral: #C27A1A;
  --csp-color-split-team: #0E9488;
  --csp-color-split-boost: #3A7BD5;
  --csp-color-tile-ink: #16152B;
  --csp-color-tile-ink-text: #FFFFFF;
  --csp-color-bubble-surface: #16152B;
  --csp-color-bubble-text: #FFFFFF;
  --csp-color-grid-line: rgba(30, 30, 80, 0.06);
  --csp-color-night: #16152B;
  --csp-color-night-raised: #1F1D3A;
  --csp-color-night-border: #2F2C55;
  --csp-color-night-hover: #24223F;
  --csp-color-night-track: #2A2750;
  --csp-color-night-chevron: #1D1B36;
  --csp-color-night-chevron-border: #34315A;
  --csp-color-night-text: #F4F2FF;
  --csp-color-night-body: #E4E1FA;
  --csp-color-night-nav: #D9D6F2;
  --csp-color-night-muted: #A9A5D6;
  --csp-color-night-mint: #8FF0B0;
  --csp-color-night-mint-border: #2E6B4B;
  --csp-fx-card-radius: var(--csp-radius-lg);
  --csp-fx-control-radius: var(--csp-radius-md);
  --csp-fx-art-radius: var(--csp-radius-lg);
  --csp-fx-border-width: var(--csp-size-border-width);
  --csp-fx-border-image: none;
  --csp-fx-border-color: var(--csp-color-border);
  --csp-fx-glow: 0 0 0 0 transparent;
  --csp-fx-glow-hover: 0 0 0 0 transparent;
  --csp-fx-inner-light: var(--csp-shadow-sm);
  --csp-fx-surface-image: none;
  --csp-fx-canvas-image: none;
  --csp-fx-sheen: none;
  --csp-fx-art-rendering: auto;
  --csp-fx-duration: var(--csp-duration-fast);
  --csp-fx-ease: var(--csp-easing-standard);
  --csp-fx-lift: 0px;
  --csp-fx-press: 1;
  --csp-fx-hover-scale: 1;
  --csp-fx-edge-width: var(--csp-fx-border-width);
  --csp-fx-grid-step: 24px;
  --csp-fx-body-family: var(--csp-font-body);
  --csp-fx-mono-family: var(--csp-font-mono);
  --csp-fx-display-family: var(--csp-font-body);
  --csp-fx-heading-weight: 600;
  --csp-fx-display-tracking: var(--csp-type-display-tracking);
  --csp-fx-label-transform: uppercase;
  --csp-fx-label-tracking: var(--csp-type-label-tracking);
  --csp-fx-label-weight: var(--csp-type-label-weight);
  --csp-fx-control-weight: var(--csp-type-heading-sm-weight);
  --csp-space-0: 0px;
  --csp-space-1: 4px;
  --csp-space-2: 8px;
  --csp-space-3: 12px;
  --csp-space-4: 16px;
  --csp-space-5: 20px;
  --csp-space-6: 24px;
  --csp-space-8: 32px;
  --csp-space-10: 40px;
  --csp-space-12: 48px;
  --csp-space-16: 64px;
  --csp-space-20: 80px;
  --csp-radius-sm: 4px;
  --csp-radius-md: 8px;
  --csp-radius-lg: 12px;
  --csp-radius-pill: 999px;
  --csp-shadow-none: none;
  --csp-shadow-sm: inset 0 1px 0 var(--csp-color-edge-light);
  --csp-shadow-md: 0 24px 48px hsl(0 0% 0% / 0.28), inset 0 1px 0 var(--csp-color-edge-light);
  --csp-size-avatar-sm: 20px;
  --csp-size-avatar-md: 30px;
  --csp-size-avatar-lg: 40px;
  --csp-size-avatar-xl: 96px;
  --csp-size-brand-mark: 80px;
  --csp-size-container-max: 1140px;
  --csp-size-prose-max: 640px;
  --csp-size-modal-max: 688px;
  --csp-size-modal-steady: 780px;
  --csp-size-modal-wide: 880px;
  --csp-size-card-min: 280px;
  --csp-size-tile-min: 240px;
  --csp-size-control-height: 40px;
  --csp-size-control-height-sm: 36px;
  --csp-size-progress-height: 4px;
  --csp-size-sparkline-inline: 72px;
  --csp-size-table-wide: 760px;
  --csp-size-border-width: 1px;
  --csp-size-nav-icon: 36px;
  --csp-size-header-height: 72px;
  --csp-size-header-height-compact: 56px;
  --csp-size-tab-marker: 2px;
  --csp-size-page-dot: 8px;
  --csp-size-plot-height: 160px;
  --csp-size-chart-height: 280px;
  --csp-size-collection-chart-height: 128px;
  --csp-size-dropdown-max: 320px;
  --csp-size-plot-stroke: 2px;
  --csp-size-plot-mark: 8px;
  --csp-duration-fast: 140ms;
  --csp-duration-base: 220ms;
  --csp-easing-standard: cubic-bezier(0.2, 0, 0, 1);
  --csp-font-body: var(--font-body), Geist, Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --csp-font-mono: var(--font-mono), 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --csp-font-display: var(--font-display), 'Archivo Black', 'Arial Black', Impact, sans-serif;
  --csp-font-pixel: var(--font-pixel), Silkscreen, 'Courier New', ui-monospace, monospace;
  --csp-font-grotesque: var(--font-grotesque), 'Bricolage Grotesque', system-ui, -apple-system, sans-serif;
  --csp-font-plex-mono: var(--font-plex-mono), 'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace;
  --csp-tone-primary: var(--csp-color-text-primary);
  --csp-tone-secondary: var(--csp-color-text-secondary);
  --csp-tone-muted: var(--csp-color-text-muted);
  --csp-tone-accent: var(--csp-color-accent-ink);
  --csp-tone-caution: var(--csp-color-caution-text);
  --csp-tone-critical: var(--csp-color-critical-text);
  --csp-tone-inverse: var(--csp-color-text-inverse);
  --csp-tone-on-plot: var(--csp-color-plot-ink);
  --csp-tone-on-plot-muted: var(--csp-color-plot-ink-muted);
  --csp-tone-success: var(--csp-color-success-text);
  --csp-tone-fire: var(--csp-color-fire-text);
  --csp-tone-urgent: var(--csp-color-urgent);
  --csp-tone-faint: var(--csp-color-text-faint);
  --csp-tone-pink: var(--csp-color-pink-text);
  --csp-type-display-family: var(--csp-fx-body-family);
  --csp-type-display-size: clamp(32px, 6.5vw, 52px);
  --csp-type-display-line: clamp(34px, 6.8vw, 54px);
  --csp-type-display-weight: 600;
  --csp-type-display-tracking: -0.03em;
  --csp-type-heading-lg-family: var(--csp-fx-body-family);
  --csp-type-heading-lg-size: 28px;
  --csp-type-heading-lg-line: 34px;
  --csp-type-heading-lg-weight: 600;
  --csp-type-heading-lg-tracking: -0.02em;
  --csp-type-heading-md-family: var(--csp-fx-body-family);
  --csp-type-heading-md-size: 18px;
  --csp-type-heading-md-line: 24px;
  --csp-type-heading-md-weight: 600;
  --csp-type-heading-md-tracking: -0.015em;
  --csp-type-heading-sm-family: var(--csp-fx-body-family);
  --csp-type-heading-sm-size: 14px;
  --csp-type-heading-sm-line: 20px;
  --csp-type-heading-sm-weight: 500;
  --csp-type-heading-sm-tracking: 0em;
  --csp-type-metric-family: var(--csp-fx-mono-family);
  --csp-type-metric-size: 28px;
  --csp-type-metric-line: 32px;
  --csp-type-metric-weight: 500;
  --csp-type-metric-tracking: -0.02em;
  --csp-type-metric-sm-family: var(--csp-fx-mono-family);
  --csp-type-metric-sm-size: 15px;
  --csp-type-metric-sm-line: 20px;
  --csp-type-metric-sm-weight: 500;
  --csp-type-metric-sm-tracking: -0.01em;
  --csp-type-body-family: var(--csp-fx-body-family);
  --csp-type-body-size: 14px;
  --csp-type-body-line: 22px;
  --csp-type-body-weight: 400;
  --csp-type-body-tracking: 0em;
  --csp-type-body-sm-family: var(--csp-fx-body-family);
  --csp-type-body-sm-size: 12px;
  --csp-type-body-sm-line: 18px;
  --csp-type-body-sm-weight: 400;
  --csp-type-body-sm-tracking: 0em;
  --csp-type-code-family: var(--csp-fx-mono-family);
  --csp-type-code-size: 12px;
  --csp-type-code-line: 18px;
  --csp-type-code-weight: 400;
  --csp-type-code-tracking: 0em;
  --csp-type-label-family: var(--csp-fx-body-family);
  --csp-type-label-size: 11px;
  --csp-type-label-line: 14px;
  --csp-type-label-weight: 500;
  --csp-type-label-tracking: 0.04em;
  --csp-type-display-l-family: var(--csp-fx-body-family);
  --csp-type-display-l-size: clamp(40px, 7vw, 66px);
  --csp-type-display-l-line: 0.95;
  --csp-type-display-l-weight: 800;
  --csp-type-display-l-tracking: -0.045em;
  --csp-type-lede-family: var(--csp-fx-body-family);
  --csp-type-lede-size: 18px;
  --csp-type-lede-line: 1.45;
  --csp-type-lede-weight: 400;
  --csp-type-lede-tracking: 0em;
  --csp-type-card-title-family: var(--csp-fx-body-family);
  --csp-type-card-title-size: 22px;
  --csp-type-card-title-line: 1.15;
  --csp-type-card-title-weight: 800;
  --csp-type-card-title-tracking: -0.02em;
  --csp-type-token-symbol-family: var(--csp-fx-body-family);
  --csp-type-token-symbol-size: 20px;
  --csp-type-token-symbol-line: 1.15;
  --csp-type-token-symbol-weight: 800;
  --csp-type-token-symbol-tracking: -0.01em;
  --csp-type-body-lg-family: var(--csp-fx-body-family);
  --csp-type-body-lg-size: 15px;
  --csp-type-body-lg-line: 1.45;
  --csp-type-body-lg-weight: 400;
  --csp-type-body-lg-tracking: 0em;
  --csp-type-meta-family: var(--csp-fx-body-family);
  --csp-type-meta-size: 13px;
  --csp-type-meta-line: 1.35;
  --csp-type-meta-weight: 400;
  --csp-type-meta-tracking: 0em;
  --csp-type-quote-family: var(--csp-fx-body-family);
  --csp-type-quote-size: 17px;
  --csp-type-quote-line: 1.45;
  --csp-type-quote-weight: 400;
  --csp-type-quote-tracking: 0em;
  --csp-type-figure-xl-family: var(--csp-fx-mono-family);
  --csp-type-figure-xl-size: 44px;
  --csp-type-figure-xl-line: 1;
  --csp-type-figure-xl-weight: 700;
  --csp-type-figure-xl-tracking: 0em;
  --csp-type-figure-lg-family: var(--csp-fx-mono-family);
  --csp-type-figure-lg-size: 36px;
  --csp-type-figure-lg-line: 1;
  --csp-type-figure-lg-weight: 700;
  --csp-type-figure-lg-tracking: 0em;
  --csp-type-figure-md-family: var(--csp-fx-mono-family);
  --csp-type-figure-md-size: 28px;
  --csp-type-figure-md-line: 1.1;
  --csp-type-figure-md-weight: 700;
  --csp-type-figure-md-tracking: 0em;
  --csp-type-figure-sm-family: var(--csp-fx-mono-family);
  --csp-type-figure-sm-size: 22px;
  --csp-type-figure-sm-line: 1.1;
  --csp-type-figure-sm-weight: 700;
  --csp-type-figure-sm-tracking: 0em;
  --csp-type-figure-xs-family: var(--csp-fx-mono-family);
  --csp-type-figure-xs-size: 15px;
  --csp-type-figure-xs-line: 1.3;
  --csp-type-figure-xs-weight: 700;
  --csp-type-figure-xs-tracking: 0em;
  --csp-type-number-sm-family: var(--csp-fx-mono-family);
  --csp-type-number-sm-size: 12px;
  --csp-type-number-sm-line: 1.3;
  --csp-type-number-sm-weight: 700;
  --csp-type-number-sm-tracking: 0em;
}
.csp-t-display{
  font-family: var(--csp-type-display-family);
  font-size: var(--csp-type-display-size);
  line-height: var(--csp-type-display-line);
  font-weight: var(--csp-type-display-weight);
  letter-spacing: var(--csp-type-display-tracking);
}
.csp-t-heading-lg{
  font-family: var(--csp-type-heading-lg-family);
  font-size: var(--csp-type-heading-lg-size);
  line-height: var(--csp-type-heading-lg-line);
  font-weight: var(--csp-type-heading-lg-weight);
  letter-spacing: var(--csp-type-heading-lg-tracking);
}
.csp-t-heading-md{
  font-family: var(--csp-type-heading-md-family);
  font-size: var(--csp-type-heading-md-size);
  line-height: var(--csp-type-heading-md-line);
  font-weight: var(--csp-type-heading-md-weight);
  letter-spacing: var(--csp-type-heading-md-tracking);
}
.csp-t-heading-sm{
  font-family: var(--csp-type-heading-sm-family);
  font-size: var(--csp-type-heading-sm-size);
  line-height: var(--csp-type-heading-sm-line);
  font-weight: var(--csp-type-heading-sm-weight);
  letter-spacing: var(--csp-type-heading-sm-tracking);
}
.csp-t-metric{
  font-family: var(--csp-type-metric-family);
  font-size: var(--csp-type-metric-size);
  line-height: var(--csp-type-metric-line);
  font-weight: var(--csp-type-metric-weight);
  letter-spacing: var(--csp-type-metric-tracking);
}
.csp-t-metric-sm{
  font-family: var(--csp-type-metric-sm-family);
  font-size: var(--csp-type-metric-sm-size);
  line-height: var(--csp-type-metric-sm-line);
  font-weight: var(--csp-type-metric-sm-weight);
  letter-spacing: var(--csp-type-metric-sm-tracking);
}
.csp-t-body{
  font-family: var(--csp-type-body-family);
  font-size: var(--csp-type-body-size);
  line-height: var(--csp-type-body-line);
  font-weight: var(--csp-type-body-weight);
  letter-spacing: var(--csp-type-body-tracking);
}
.csp-t-body-sm{
  font-family: var(--csp-type-body-sm-family);
  font-size: var(--csp-type-body-sm-size);
  line-height: var(--csp-type-body-sm-line);
  font-weight: var(--csp-type-body-sm-weight);
  letter-spacing: var(--csp-type-body-sm-tracking);
}
.csp-t-code{
  font-family: var(--csp-type-code-family);
  font-size: var(--csp-type-code-size);
  line-height: var(--csp-type-code-line);
  font-weight: var(--csp-type-code-weight);
  letter-spacing: var(--csp-type-code-tracking);
}
.csp-t-label{
  font-family: var(--csp-type-label-family);
  font-size: var(--csp-type-label-size);
  line-height: var(--csp-type-label-line);
  font-weight: var(--csp-type-label-weight);
  letter-spacing: var(--csp-type-label-tracking);
}
.csp-t-display-l{
  font-family: var(--csp-type-display-l-family);
  font-size: var(--csp-type-display-l-size);
  line-height: var(--csp-type-display-l-line);
  font-weight: var(--csp-type-display-l-weight);
  letter-spacing: var(--csp-type-display-l-tracking);
}
.csp-t-lede{
  font-family: var(--csp-type-lede-family);
  font-size: var(--csp-type-lede-size);
  line-height: var(--csp-type-lede-line);
  font-weight: var(--csp-type-lede-weight);
  letter-spacing: var(--csp-type-lede-tracking);
}
.csp-t-card-title{
  font-family: var(--csp-type-card-title-family);
  font-size: var(--csp-type-card-title-size);
  line-height: var(--csp-type-card-title-line);
  font-weight: var(--csp-type-card-title-weight);
  letter-spacing: var(--csp-type-card-title-tracking);
}
.csp-t-token-symbol{
  font-family: var(--csp-type-token-symbol-family);
  font-size: var(--csp-type-token-symbol-size);
  line-height: var(--csp-type-token-symbol-line);
  font-weight: var(--csp-type-token-symbol-weight);
  letter-spacing: var(--csp-type-token-symbol-tracking);
}
.csp-t-body-lg{
  font-family: var(--csp-type-body-lg-family);
  font-size: var(--csp-type-body-lg-size);
  line-height: var(--csp-type-body-lg-line);
  font-weight: var(--csp-type-body-lg-weight);
  letter-spacing: var(--csp-type-body-lg-tracking);
}
.csp-t-meta{
  font-family: var(--csp-type-meta-family);
  font-size: var(--csp-type-meta-size);
  line-height: var(--csp-type-meta-line);
  font-weight: var(--csp-type-meta-weight);
  letter-spacing: var(--csp-type-meta-tracking);
}
.csp-t-quote{
  font-family: var(--csp-type-quote-family);
  font-size: var(--csp-type-quote-size);
  line-height: var(--csp-type-quote-line);
  font-weight: var(--csp-type-quote-weight);
  letter-spacing: var(--csp-type-quote-tracking);
}
.csp-t-figure-xl{
  font-family: var(--csp-type-figure-xl-family);
  font-size: var(--csp-type-figure-xl-size);
  line-height: var(--csp-type-figure-xl-line);
  font-weight: var(--csp-type-figure-xl-weight);
  letter-spacing: var(--csp-type-figure-xl-tracking);
}
.csp-t-figure-lg{
  font-family: var(--csp-type-figure-lg-family);
  font-size: var(--csp-type-figure-lg-size);
  line-height: var(--csp-type-figure-lg-line);
  font-weight: var(--csp-type-figure-lg-weight);
  letter-spacing: var(--csp-type-figure-lg-tracking);
}
.csp-t-figure-md{
  font-family: var(--csp-type-figure-md-family);
  font-size: var(--csp-type-figure-md-size);
  line-height: var(--csp-type-figure-md-line);
  font-weight: var(--csp-type-figure-md-weight);
  letter-spacing: var(--csp-type-figure-md-tracking);
}
.csp-t-figure-sm{
  font-family: var(--csp-type-figure-sm-family);
  font-size: var(--csp-type-figure-sm-size);
  line-height: var(--csp-type-figure-sm-line);
  font-weight: var(--csp-type-figure-sm-weight);
  letter-spacing: var(--csp-type-figure-sm-tracking);
}
.csp-t-figure-xs{
  font-family: var(--csp-type-figure-xs-family);
  font-size: var(--csp-type-figure-xs-size);
  line-height: var(--csp-type-figure-xs-line);
  font-weight: var(--csp-type-figure-xs-weight);
  letter-spacing: var(--csp-type-figure-xs-tracking);
}
.csp-t-number-sm{
  font-family: var(--csp-type-number-sm-family);
  font-size: var(--csp-type-number-sm-size);
  line-height: var(--csp-type-number-sm-line);
  font-weight: var(--csp-type-number-sm-weight);
  letter-spacing: var(--csp-type-number-sm-tracking);
}
.csp-c-primary{ color: var(--csp-tone-primary); }
.csp-c-secondary{ color: var(--csp-tone-secondary); }
.csp-c-muted{ color: var(--csp-tone-muted); }
.csp-c-accent{ color: var(--csp-tone-accent); }
.csp-c-caution{ color: var(--csp-tone-caution); }
.csp-c-critical{ color: var(--csp-tone-critical); }
.csp-c-inverse{ color: var(--csp-tone-inverse); }
.csp-c-on-plot{ color: var(--csp-tone-on-plot); }
.csp-c-on-plot-muted{ color: var(--csp-tone-on-plot-muted); }
.csp-c-success{ color: var(--csp-tone-success); }
.csp-c-fire{ color: var(--csp-tone-fire); }
.csp-c-urgent{ color: var(--csp-tone-urgent); }
.csp-c-faint{ color: var(--csp-tone-faint); }
.csp-c-pink{ color: var(--csp-tone-pink); }

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
/*
 * Declares the ground to the browser itself, so native scrollbars, form controls and the paint
 * before our stylesheet lands all match. Without it a dark page's first frame flashes white.
 *
 * It reads the PALETTE's polarity rather than saying `dark`: a light palette that still declared
 * a dark scheme would get white-on-dark form controls and a dark scrollbar down the side of a
 * white page, and nothing in this repo measures either of those.
 */
html { color-scheme: var(--csp-scheme); }
::selection { background: var(--csp-color-accent-subtle); color: var(--csp-color-text-primary); }
body {
  background: var(--csp-color-canvas);
  color: var(--csp-color-text-primary);
  /*
   * The FACE token, not the fx role. The fx block is declared on the shell root (the element that
   * carries data-skin, one level down), so a var() here would resolve to the root default whatever
   * skin is on — reading as skinnable while being nothing of the kind. Every piece of text on the
   * page wears a Text variant, so this only ever styles what the design audit already forbids.
   */
  font-family: var(--csp-font-body);
  /*
   * The document's base size is a TOKEN, not the browser's 16px default. Anything that renders
   * without a Text variant inherits from here, so leaving it unset put an undeclared size on the
   * page — invisible until the scale stopped happening to contain 16px, and caught by the probe's
   * font-size check the moment it did.
   */
  font-size: var(--csp-type-body-size);
  line-height: var(--csp-type-body-line);
  -webkit-font-smoothing: antialiased;
  /*
   * Deliberately NO overflow-x:hidden here. It would clip a sideways-overflowing child instead of
   * fixing it, and it would hide the failure from scripts/explore.ts. Layout is responsible
   * for fitting; the probe is allowed to see the truth.
   */
}
img {
  max-width: 100%;
  display: block;
  /*
   * How a raster is resampled when it is not shown at its natural size. `auto` is the browser's
   * smoothing; a skin whose art is genuine pixel art sets `pixelated` so upscaling steps rather
   * than blurs.
   *
   * ⚠️ Every image this product ships today is a smooth SVG, which a browser rasterises AT its
   * layout size — so there is nothing to resample and this changes nothing you can see. It is the
   * hook that makes real pixel art render correctly when there is any, and it is deliberately on
   * the element rather than on a class so no primitive's markup had to change to gain it.
   */
  image-rendering: var(--csp-fx-art-rendering);
}

/*
 * The page, and the one element that carries `data-skin`.
 *
 * It is the shell's root rather than <html> because <html> is rendered by the web app's root
 * layout, which cannot read searchParams — see the spec's approach note (b). Everything on the
 * page inherits the fx custom properties from here, and this box is the full viewport, so the
 * skin's backdrop still covers it.
 *
 * background-color plus background-image, never the shorthand: the shorthand would reset the image
 * a skin sets, and the contrast audit composites background COLOURS, so keeping the colour in its
 * own longhand is what keeps that measurement reading the same value it always did.
 */
.csp-page {
  min-height: 100dvh;
  background-color: var(--csp-color-canvas);
  background-image: var(--csp-fx-canvas-image);
  /*
   * ⚠️ NO `background-attachment: fixed` here, and this is the reason.
   *
   * `fixed` paints a background against the VIEWPORT rather than against the element, so on any
   * page taller than the window the skin's canvas treatment covered the first screen and stopped:
   * a hard horizontal seam at exactly the fold, wash above and bare canvas below. Measured on the
   * light palette that step was 189 units of 8-bit colour, and on the dark ones 13 — the owner
   * spotted it by eye in a screenshot, which is the part worth remembering, because three separate
   * probes had looked straight at it and reported green.
   *
   * `scripts/canvas-continuity.ts` is what would have caught it, and `dev.bat probe skins` now
   * asserts it: the page's own ground is sampled down the whole document and has to be continuous.
   *
   * A skin whose canvas treatment WANTS to be the size of a screen says so in `vh` — see vault's
   * top glow. That keeps the shape without handing the painting area to the viewport.
   */
}
.csp-page[data-lab-grid="on"]:has(.csp-hero) {
  background-image:
    linear-gradient(to bottom, var(--csp-color-canvas) 0px, var(--csp-color-canvas) calc(1.17 * min(100vw, var(--csp-size-container-max))), transparent calc(calc(1.17 * min(100vw, var(--csp-size-container-max))) + 160px)),
    var(--csp-fx-canvas-image);
}
@media (min-width: 768px) {
.csp-page[data-lab-grid="on"]:has(.csp-hero) {
  background-image:
    linear-gradient(to bottom, var(--csp-color-canvas) 0px, var(--csp-color-canvas) 620px, transparent calc(620px + 160px)),
    var(--csp-fx-canvas-image);
}
}
.csp-container {
  width: 100%;
  padding-inline: var(--csp-space-4);
}
@media (min-width: 768px) {
  .csp-container { padding-inline: var(--csp-space-8); }
}

.csp-stack { display: flex; min-width: 0; }
/*
 * Stack as="ul" is how a semantic list is built here, and a browser's own list chrome — bullets
 * plus a 40px indent nobody chose — is not part of this system. The spacing utilities carry a
 * doubled class so they outrank this element-qualified rule, which is what lets a list-shaped
 * Stack still ask for padding and get it.
 */
ul.csp-stack, ol.csp-stack { list-style: none; margin: 0; padding: 0; }
/*
 * The one list that keeps its markers (Stack marked) -- a document's bulleted or numbered list,
 * where the number is part of the text. Doubled class so it outranks the element-qualified reset
 * above, and the indent is the marker's room; the items are Text as="li", which are list items
 * and not flex boxes, so the browser draws the marker itself.
 */
ul.csp-stack.csp-list-marked { list-style: disc; padding-inline-start: var(--csp-space-5); }
ol.csp-stack.csp-list-marked { list-style: decimal; padding-inline-start: var(--csp-space-5); }
.csp-stack-row { flex-direction: row; }
.csp-stack-column { flex-direction: column; }
.csp-wrap { flex-wrap: wrap; }
.csp-grow { flex: 1 1 auto; min-width: 0; }
.csp-align-start { align-items: flex-start; }
.csp-align-center { align-items: center; }
.csp-align-end { align-items: flex-end; }
.csp-align-stretch { align-items: stretch; }
.csp-align-baseline { align-items: baseline; }
.csp-justify-start { justify-content: flex-start; }
.csp-justify-center { justify-content: center; }
.csp-justify-between { justify-content: space-between; }
.csp-justify-end { justify-content: flex-end; }

.csp-text { margin: 0; min-width: 0; }
/*
 * Text the owner edits in place (EditableText). It is the SAME element with the same classes as the
 * public one -- these rules hang off the attribute, never a class, so the class list stays
 * identical and the owner-page test can compare the two. Nothing here draws a box: a text cursor,
 * the focus ring every control gets, and a little width for an empty one so its caret has somewhere
 * to be.
 */
.csp-text[contenteditable] { cursor: text; min-width: var(--csp-space-8); }
.csp-text[contenteditable]:focus-visible {
  outline: 2px solid var(--csp-color-focus-ring);
  outline-offset: 2px;
  border-radius: var(--csp-radius-sm);
}
/*
 * The type details a skin owns. They are written here, AFTER the generated variant rules, so they
 * override at equal specificity by source order rather than by a doubled class.
 *
 * The heading weight covers display / headingLg / headingMd and stops there. Those three are 600
 * today; headingSm and metric are 500, so one token cannot stay inert over all five, and moving the
 * scale is not a skin's job.
 *
 * The DISPLAY FACE covers display and headingLg — the two sizes a direction sets its headline in,
 * and the pair that reads as a voice rather than as a scale. It stops before headingMd, which is a
 * card title: a heavy grotesque or a bitmap face at 18px inside a tile is a legibility problem, not
 * a direction. The default resolves to the body face, so with no skin this changes nothing.
 */
.csp-uppercase { text-transform: var(--csp-fx-label-transform); }
.csp-t-label {
  letter-spacing: var(--csp-fx-label-tracking);
  font-weight: var(--csp-fx-label-weight);
}
.csp-t-display,
.csp-t-heading-lg,
.csp-t-heading-md { font-weight: var(--csp-fx-heading-weight); }
/*
 * A bold run inside running text (Text as="strong"): the variant's own size and line, at the
 * heading weight. Element-qualified so it outranks the variant class that sets the body weight.
 */
strong.csp-text { font-weight: var(--csp-fx-heading-weight); }
.csp-t-display,
.csp-t-heading-lg { font-family: var(--csp-fx-display-family); }
.csp-t-display { letter-spacing: var(--csp-fx-display-tracking); }
.csp-numeric { font-variant-numeric: tabular-nums; }
.csp-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * THE GRADIENT-BORDER LAYER, and the one piece of machinery every skin's border treatment hangs
 * off. It is written once and worn by the card, the two tiles and the buttons.
 *
 * border-image cannot do this: it ignores border-radius, so a gradient border drawn with it comes
 * out square on a rounded box. The way that works is a pseudo-element the size of the box, filled
 * with the gradient, and masked so only its OWN border-width ring survives — content-box subtracted
 * from padding-box.
 *
 * It is inert while --fx-border-image is `none`: `background: none` paints nothing, so the
 * element renders exactly as it did before the layer existed. Both the prefixed and the standard
 * mask-composite are set, because the shorthand -webkit-mask sets -webkit-mask-composite and the
 * unprefixed mask sets mask-composite, and the two spell the same operation differently.
 */
.csp-card::before,
.csp-nft::before,
.csp-corp::before,
.csp-button-primary::before,
.csp-button-secondary::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  padding: var(--csp-fx-edge-width);
  background: var(--csp-fx-border-image);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
  transition: opacity var(--csp-fx-duration) var(--csp-fx-ease);
}

/*
 * position: relative is here for the ::before above, and it is safe: nothing inside a card is
 * positioned with offsets. The one absolutely-positioned thing that ever lands in one is
 * .csp-sr-only, which sets no offsets and therefore stays at its static position whatever its
 * containing block is — the bug that rule once caused was a caption's static position, not its
 * containing block. See the note on .csp-table-scroll.
 */
.csp-card {
  /* The pivot every hover transform above depends on. Never a corner or an edge. */
  transform-origin: center;
  position: relative;
  background-color: var(--csp-color-surface);
  background-image: var(--csp-fx-surface-image);
  border: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  border-radius: var(--csp-fx-card-radius);
  box-shadow: var(--csp-fx-inner-light), var(--csp-fx-glow);
  display: flex;
  flex-direction: column;
  min-width: 0;
  /*
   * So a full-bleed media band clips to the card's own radius -- the same reason .csp-coll has it,
   * and the alternative is a square-cornered picture sitting proud of a rounded panel.
   *
   * Almost nothing in a card is positioned outside its box: .csp-sr-only sets no offsets, and every
   * other absolutely-positioned primitive (the checkbox input, the toggle knob, the nft overlays)
   * is inside a relative parent of its own. The one thing this DOES clip is an outline drawn
   * outward at the card's very edge, which is why .csp-card-link turns its ring inward below.
   *
   * ⚠️ There is ONE exception and it is declared, not discovered: a card holding an .csp-info mark
   * un-clips itself, because that mark's tooltip is painted outside the card on purpose. See that
   * rule for why it is safe there and nowhere else.
   */
  overflow: hidden;
  transition: transform var(--csp-fx-duration) var(--csp-fx-ease),
    box-shadow var(--csp-fx-duration) var(--csp-fx-ease),
    border-radius var(--csp-fx-duration) var(--csp-fx-ease),
    border-color var(--csp-fx-duration) var(--csp-fx-ease);
}
/*
 * THE LIGHT changes on every card, and only the light. See `.csp-card-lift` for the movement.
 */
.csp-card:hover {
  box-shadow: var(--csp-fx-inner-light), var(--csp-fx-glow-hover);
}
/*
 * ⭐ THE CARD THAT GROWS UNDER THE POINTER, and it is the one that is actually CLICKABLE.
 *
 * This used to be the other way round: every card lifted and scaled, and `Card motion="still"`
 * was the per-call opt-out. Which means the default promised a click on a leaderboard, a summary
 * panel, a stat tile, a loading skeleton and an error message, and the four "your position" tiles
 * on the dashboard — which ARE links — had been stilled by hand and did not move at all. The owner:
 * *"only clickable things should grow on hover"*. So the affordance is DERIVED rather than asked
 * for: `Card` puts this class on when it was given an `href`, and on nothing else, and there is
 * no prop left to get backwards.
 *
 * The light still changes on both, above: a panel may answer the pointer, it just may not promise
 * a click it cannot honour.
 */
.csp-card-lift:hover {
  transform: translateY(var(--csp-fx-lift)) scale(var(--csp-fx-hover-scale));
}

/*
 * The FILLED panel -- `Card tone="accent"`. A card that is itself the primary way on, rather than
 * a card with a button in it.
 *
 * The ink comes with the fill and is applied to every piece of text inside, not just to the card's
 * own `color`: a `Text` sets its tone class on itself, so an unqualified `color` here would be
 * overridden by every child and the caller would have to switch each one by hand -- which is the
 * kind of thing that gets half-done and ships a dark label on a saturated ground.
 * `accentText` is the palette's answer to "what reads ON the accent", per polarity.
 *
 * The secondary step is kept as OPACITY rather than as a second colour, because there is no token
 * for "the quieter version of the ink that reads on the accent" and inventing one would be a colour
 * with no home in `palettes.ts`.
 *
 * `background-image: none` drops the skin's surface texture: it is a veil tuned for the surface
 * colour and reads as dirt over a saturated fill.
 */
.csp-card-accent {
  background-color: var(--csp-color-accent);
  background-image: none;
  border-color: var(--csp-color-accent);
}
.csp-card-accent,
.csp-card-accent .csp-text { color: var(--csp-color-accent-text); }
.csp-card-accent .csp-c-secondary,
.csp-card-accent .csp-c-muted { opacity: 0.85; }

/*
 * The picture band and the padded body under it. Both exist only on a card that was GIVEN a media
 * or an href; a plain card still holds its children directly and carries the padding itself, which
 * is what keeps every screen this redesign has not been near rendering exactly as it did.
 *
 * The slot's child must not draw a corner of its own. The band runs edge to edge and the card
 * rounds its top two corners already, so a picture rounded on all four sits notched inside it --
 * which is what .csp-art-image, the thing callers actually pass, would otherwise do.
 */
.csp-card-media { min-width: 0; }
.csp-card-media > * { border-radius: 0; }
.csp-card-body { display: flex; flex-direction: column; min-width: 0; }

/*
 * The whole card as one link.
 *
 * It fills the card, so the target is the card and not the words in it, and it takes the inherited
 * colour rather than the link ink: a headline that turned accent-coloured because its card became
 * clickable is a different design, not the same one made navigable.
 *
 * The ring is turned INWARD. It is the palette's ring -- same colour, same 2px, drawn by the one
 * :focus-visible rule every other control uses -- but this element's border box is the card's inner
 * edge, and the card clips its overflow, so the shared outward offset would paint the ring exactly
 * where it is cut off. A keyboard user would see nothing at all.
 */
.csp-card-link {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}
.csp-card-link:focus-visible { outline-offset: calc(var(--csp-space-1) * -1); }
/*
 * The whole card as one disclosure BUTTON -- `Card onPress`. It wears .csp-card-link for the fill,
 * the inherited colour and the inward ring, and this only takes away what a <button> draws for
 * itself: the UA's border, fill, padding and font, and its centred text. The card's own words stay
 * where a card's words are.
 */
.csp-card-button {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  text-align: start;
  cursor: pointer;
}
/* The card that is OPEN says so on its own edge, since the region it opened sits below the grid. */
.csp-card:has(> .csp-card-button[aria-expanded='true']) {
  border-color: var(--csp-color-accent-border);
}

/*
 * The modal dialog. See primitives/overlay.tsx.
 *
 * Everything the UA draws on a <dialog> is undone — the 1px border, the white fill, the 1em
 * padding — and what replaces it is a WIDTH, not a page. The panel inside is an ordinary
 * .csp-card, so the dialog element itself paints nothing.
 *
 * ## Centring is the browser's, and it only works if the box is not full-size
 *
 * A modal <dialog> is in the top layer at `position: fixed; inset: 0` with `margin: auto`, which
 * centres it in both axes the moment it is narrower and shorter than the viewport. That is the
 * whole mechanism, and it is why the dashboard's introduction was not centred before this: it set
 * `width: 100%; height: 100%` on the element, which left `margin: auto` nothing to distribute and
 * turned a dialog into a repaint of the page.
 *
 * (!) `NftDetailDialog` on the mint page still carries that same inline reset and is still
 * full-bleed. It is a different page and was out of scope; moving it onto `Modal` is a small,
 * separate change and this rule is already what it needs.
 *
 * `max-height` is the other half: a dialog is never taller than the screen. What SCROLLS inside it
 * is the body, not the box — see the three rules under it.
 */
.csp-modal {
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  /*
   * The UA's own max-width is calc(100% - 6px - 2em), so the box already has a gutter on a narrow
   * screen; this is the ceiling on a wide one.
   */
  width: min(var(--csp-size-modal-max), 100%);
  max-height: calc(100% - 2 * var(--csp-space-8));
  /*
   * The box itself does not scroll: .csp-modal-body does. Scrolling HERE is what put the dashboard
   * introduction's Back / Next / Close row off the bottom of the screen on its second page — the
   * footer was inside the scroller, so the only way to reach the controls was to scroll the thing
   * you were trying to close.
   */
  overflow: hidden;
  /*
   * A wheel anywhere over this box stops here rather than scrolling the page behind the scrim.
   *
   * It lived on `.csp-modal-body` alone, which was fine while that box was unconditionally a
   * scroller: everything a pointer could be over was inside it. Now it is only a scroller when it
   * has something to scroll, so the same gesture over a short dialog chained straight through —
   * measured with the wallet picker open, a 400px wheel over the panel moved the page behind it by
   * exactly 400. `hidden` makes this element a scroll container with nothing to scroll, which is
   * the thing `contain` needs to be on, and it covers the footer and the panel's padding too — a
   * wheel over those chained before this line existed.
   */
  overscroll-behavior: contain;
}
/*
 * ⚠️ Scoped to [open], and that is load-bearing rather than tidy.
 *
 * The UA sheet is what makes a closed <dialog> `display: none`, and an author `display: flex` on
 * the element beats it at every specificity — so an unscoped rule here would leave every closed
 * dialog on this site visible, with its contents in the tab order. [open] is the attribute
 * `showModal()` sets and `close()` removes, so this matches exactly when the box is on screen.
 *
 * A column, because the panel inside has to be able to give its leftover height to the body and
 * keep the footer at its own height whatever the body does.
 */
dialog.csp-modal[open] {
  display: flex;
  flex-direction: column;
}
/*
 * The panel takes the dialog's height rather than its content's, so the footer lands on the box's
 * bottom edge instead of wherever the words happened to stop. `min-height: 0` is what lets it
 * shrink past its own content — the same declaration `.csp-fill` carries and for the same reason.
 */
.csp-modal > .csp-card {
  flex: 1 1 auto;
  min-height: 0;
}
/*
 * The scrolling half — and it is only a SCROLLER when its content does not fit.
 *
 * ## ⭐ Why `overflow-y: auto` is not unconditional any more
 *
 * A scroll container clips its own children at its padding box whether or not anything is
 * scrolling, so every drop shadow inside this box was cut off at the box's edge. The wallet picker
 * is where the owner saw it: two buttons that fit easily, the last one's border box ending at
 * exactly the same y as the body's clip, and vault's `0 14px 34px` glow — which reaches 48px below
 * a control — sliced away entirely. Measured at 1280x900: button bottom 475, body clip bottom 475,
 * and 84px of unused room below it inside the panel (the footer's gap, the footer, and the card's
 * own padding). His words were "only put them in a scroll area if they need to scroll, and like
 * everything on the app we need to make sure the button shadows do not get cropped".
 *
 * ⚠️ Neither CSS answer works on THIS box, and both are worth naming so they are not tried again:
 *
 *  - **`overflow-clip-margin`** only has an effect on an axis whose `overflow` is `clip`, and the
 *    cropped edge here is the bottom — the axis that has to stay scrollable. It can widen the
 *    sides and nothing else.
 *  - **Padding inside the scroller** needs a LENGTH for how far a glow reaches, which is a
 *    per-skin magic number this file already refused once for the same defect — see the ⭐ note on
 *    `.csp-header-bar`, where the glow was dropped rather than allowed for. It also puts the room
 *    INSIDE the scrollport, so tall content scrolls through it and shows under the footer.
 *
 * So the answer is neither: the box stops being a scroller when it has nothing to scroll. `Modal`
 * measures its content against its box and sets `data-scroll`; see `primitives/overlay.tsx` for
 * why that decision cannot oscillate. Attribute absent means "scroller", because that is the safe
 * half of the answer to render before anything has been measured.
 *
 * The tall case is untouched, and every reason under it still holds: the BODY scrolls and the box
 * never does, so the footer under it stays pinned and reachable. `overscroll-behavior` keeps a
 * scroll that reaches either end from becoming a page scroll behind the scrim — and the same
 * declaration is on `.csp-modal` for the gesture that never reaches a scroller at all, which is
 * every gesture over a dialog whose content fits.
 */
.csp-modal-body {
  flex: 1 1 auto;
  min-height: 0;
}
.csp-modal-body:not([data-scroll='false']) {
  overflow-y: auto;
  overscroll-behavior: contain;
}
/*
 * A steady box holds a height its shorter page does not need, so the leftover room is DISTRIBUTED
 * around that page's content rather than all left under it. The introduction's page one is a badge,
 * a headline and two lines in a box sized for page two's three beats; against a pinned footer, all
 * of the slack in one lump at the bottom reads as a box with something missing from it.
 *
 * ⚠️ `safe` is load-bearing and is not decoration. A centred flex column whose content is TALLER
 * than the box overflows in BOTH directions, and the overflow above the top edge is unreachable —
 * no scrollbar reaches it, so the first lines of the content are simply gone. That is the classic
 * way this rule is written wrong, and it is not hypothetical here: page two on a phone is 1118px of
 * content in a 593px body. `safe` falls back to `start` exactly when that happens, so the short
 * page centres and the tall page scrolls from its first line as before.
 *
 * A browser without `safe` drops the whole declaration and gets `flex-start`, which is what this
 * box did before — the degradation is the old behaviour, not a broken one.
 *
 * Scoped to the steady box on purpose: a dialog sized by its own content has no slack to
 * distribute, so this would be a no-op there and a surprise the day one of them grows a fixed
 * height for some other reason.
 */
.csp-modal-steady .csp-modal-body {
  display: flex;
  flex-direction: column;
  justify-content: safe center;
}
/*
 * The chrome. It never scrolls and never shrinks: it is the way out of the box.
 *
 * The space above it is HERE rather than in the caller's markup, because the two regions belong to
 * this component and a gap that only exists when there is a footer cannot be expressed on a body
 * that does not know whether there is one.
 */
.csp-modal-footer {
  flex: 0 0 auto;
  padding-top: var(--csp-space-5);
}
/*
 * ONE height, whichever page is showing — see size.modalSteady for why a dialog would want that.
 * The max-height above still wins on a short window, so this never pushes the box off the screen.
 */
.csp-modal-steady { height: var(--csp-size-modal-steady); }
/* The wide box — the mint wizard's. Still capped at the viewport by the UA's own max-width. */
.csp-modal-wide { width: min(var(--csp-size-modal-wide), 100%); }
/*
 * The scrim. A DIM rather than a palette colour: darkening is what reads as "the page is behind
 * this" on a light ground and on a dark one alike, where any tone from the ramp would be a wash of
 * the wrong polarity on one of them. The page stays visible through it, which is the difference
 * between interrupting somebody and replacing what they were looking at.
 */
.csp-modal::backdrop {
  background: hsl(0 0% 0% / 0.55);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}

/*
 * The toast. See primitives/overlay.tsx.
 *
 * Every declaration here is about the box being UNABLE to affect the page: `position: fixed` takes
 * it out of flow entirely, so the block it replaced — a stack of sentences under the header's
 * divider — can no longer push the page down when a wallet has something to say. That shift is the
 * whole defect this exists to make unrepeatable, and it is a positioning rule, not a look: the
 * panel inside is an ordinary .csp-card, so this element paints nothing.
 *
 * ## TOP and centred, over the header
 *
 * It was pinned to the bottom-right corner for one spec, alongside the mascot. The owner, on a
 * caution that sat there: *"if this is a toast with error messages or anything that might actually
 * be useful it should be at the top of the screen and should be closable."* So it is at the top,
 * horizontally centred — which puts it OVER the header bar, deliberately: every sentence this box
 * carries is about the wallet control in that bar, and `position: fixed` means covering it costs
 * the page nothing. The X that came with the same instruction is `.csp-toast-close` below.
 *
 * ## The inset and the width
 *
 * The inset is the same `space-4` it always was, and the 24rem ceiling is the same literal: it is
 * the width of one floating box with one home, not a number two rules have to agree about.
 *
 *  - `env(safe-area-inset-*)` on each offset, exactly as `.csp-speech` does and for the reason its
 *    own note gives, so an iPhone's notch and rounded corners do not sit on top of the panel.
 *  - **left AND right offsets with `margin-inline: auto`, not `left: 50%` + a transform.** Centring
 *    a box inside its two offsets is what keeps the cap from ever overhanging one of them, and it
 *    avoids a percentage width entirely — 100vw includes the classic scrollbar and the fixed
 *    containing block does not, which is the trap the old rule needed a percent to dodge.
 *  - `max-width`, so below 24rem the panel simply takes the strip between the two gutters: never
 *    the whole screen and never wider than it, on a 320px phone included.
 *
 * ## 50: above the mascot, and under every dialog
 *
 * `.csp-speech` is at 40 and is fixed too, so the two CAN overlap on the two pages the mascot
 * appears on — less often now they are at opposite ends of the viewport, but a short window and a
 * tall bubble still meet. Equal z-indexes would leave that to source order, which is the kind of
 * correct-by-accident this sheet has been bitten by before; 50 decides it once, and it decides it
 * the right way round — a caution about somebody's wallet must not be behind a cartoon. The top
 * layer a <dialog> renders into is above every z-index there is, so a modal is never behind a
 * toast either.
 */
.csp-toast {
  position: fixed;
  z-index: 50;
  top: calc(var(--csp-space-4) + env(safe-area-inset-top));
  left: calc(var(--csp-space-4) + env(safe-area-inset-left));
  right: calc(var(--csp-space-4) + env(safe-area-inset-right));
  margin-inline: auto;
  max-width: 24rem;
}
/*
 * ⭐ The column the X sits in, RESERVED, so no sentence runs underneath the glyph.
 *
 * The arithmetic, because a number chosen by eye here is the exact defect .csp-modal-close's note
 * records: the button is `control-height-sm` (36px) wide with its right edge `space-2` (8px) inside
 * the card, so its left edge is 44px in, while the card's content edge is `space-4` (16px) in. The
 * overlap to clear is 44 - 16 = 28px, and one `control-height-sm` of padding clears it with 8px of
 * gap left over. One token, not a literal, and it is the same token that sets the width it is
 * clearing.
 *
 * The button itself is `.csp-toast-close`, which is NOT here: it wears `.csp-button` and so has to
 * be declared after it. Its own note says why.
 */
.csp-toast-body {
  padding-inline-end: var(--csp-size-control-height-sm);
}

/*
 * The docked panel. See primitives/dock.tsx.
 *
 * The toast's own arrangement, at the BOTTOM: fixed, centred between a left and a right offset
 * with margin-inline auto (never left: 50% and a transform -- 100vw includes the classic scrollbar
 * and the fixed containing block does not), env(safe-area-inset-*) on every offset, and a width
 * cap. 26rem, which is the bubble's: the two are at the same edge of the viewport, the bubble at
 * the right and this in the middle, and at that width they touch at 1280px rather than overlap.
 *
 * ## 45: above the bubble, below the toast
 *
 * The bubble is at 40 and the toast at 50. Below 1280px the two bottom boxes overlap, and equal
 * z-indexes would leave that to source order; 45 decides it once and the right way round -- the
 * control the page exists for over the cartoon commenting on it, and a caution about somebody's
 * wallet over both. The top layer a <dialog> renders into is above every z-index there is.
 *
 * The reserve is sized inline, off a measurement -- see the component for why it is not a clamp.
 */
.csp-dock {
  position: fixed;
  z-index: 45;
  left: calc(var(--csp-space-4) + env(safe-area-inset-left));
  right: calc(var(--csp-space-4) + env(safe-area-inset-right));
  bottom: calc(var(--csp-space-4) + env(safe-area-inset-bottom));
  margin-inline: auto;
  max-width: 26rem;
}
.csp-dock-reserve {
  width: 100%;
}

.csp-grid {
  display: grid;
  /* Also used as a UL so the card list is a list to assistive tech; the bullets are not wanted. */
  list-style: none;
  margin: 0;
  padding: 0;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--csp-size-card-min), 100%), 1fr));
  gap: var(--csp-space-4);
}
/*
 * The tighter column. Same rule, one token over -- see size.tileMin for what the swap actually buys
 * and why the container, not this number, is what decides the column count.
 */
.csp-grid-tile {
  grid-template-columns: repeat(auto-fill, minmax(min(var(--csp-size-tile-min), 100%), 1fr));
}
/*
 * The FIXED count -- `CardGrid columns="three"`. See the prop's own note for when a grid should
 * stop asking the container.
 *
 * minmax(0, 1fr) rather than 1fr: a bare 1fr is min-content-floored, so a long unbroken word in one
 * column would push the row wider than the box and drag the page sideways -- the failure
 * .csp-mosaic guards the same way, and the one `probe dashboard` watches the document for.
 *
 * Scoped to the breakpoint on exactly .csp-mosaic's reasoning: below it the base rule's auto-fill
 * already gives one column on a phone, and three columns of a 356px dialog is not a row, it is
 * three slivers.
 */
@media (min-width: 768px) {
  .csp-grid-three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/*
 * WHOLE ROWS ONLY -- `CardGrid columns="three" wholeRows`. The owner: *"always show or hide these
 * such that they don't have one hanging off onto the next row"*. A card that would start a row it
 * cannot fill is not drawn, at whatever column count the grid has at that width.
 *
 * ⭐ The column count and the hiding are declared under the SAME condition at every width, so the
 * two cannot disagree: below the breakpoint the grid is one column, and two once its own box
 * (`.csp-grid-rows`, a size container) is wide enough for two card minimums and a gap -- what the
 * base rule's auto-fill gave it; from the breakpoint up it is `grid-three`'s three, and FOUR once
 * the box holds four card minimums (the owner: *"if 4 makes it better, do that"*).
 * Which cards those are is `WHOLE_ROW_OVERHANG`, above.
 */
.csp-grid-rows { container-type: inline-size; }
@media (width < 768px) {
  .csp-grid-whole { grid-template-columns: minmax(0, 1fr); }
  @container (min-width: 576px) {
    .csp-grid-whole { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .csp-grid-whole > :nth-child(2n + 1):last-child { display: none; }
  }
}
@media (min-width: 768px) {
  @container (width < 1168px) {
    .csp-grid-whole > :nth-child(3n + 1):last-child, .csp-grid-whole > :nth-child(3n + 1):nth-last-child(2), .csp-grid-whole > :nth-child(3n + 1):nth-last-child(2) + * { display: none; }
  }
  @container (min-width: 1168px) {
    .csp-grid-whole { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    .csp-grid-whole > :nth-child(4n + 1):last-child, .csp-grid-whole > :nth-child(4n + 1):nth-last-child(2), .csp-grid-whole > :nth-child(4n + 1):nth-last-child(2) + *, .csp-grid-whole > :nth-child(4n + 1):nth-last-child(3), .csp-grid-whole > :nth-child(4n + 1):nth-last-child(3) + *, .csp-grid-whole > :nth-child(4n + 1):nth-last-child(3) + * + * { display: none; }
  }
}
/*
 * The FIXED PAIR -- `CardGrid columns="two"`. Two columns at every width, including a phone.
 *
 * It is for a grid whose COUNT is the content: the four reference-image slots are a 2x2 because
 * there are four of them and a creator is looking at the set, not at a list that happens to have
 * four entries. Asking the container would give one column on a narrow screen and break the shape
 * the owner asked for -- "let's just show where all of the images will go in a 2x2 grid".
 *
 * Unscoped by breakpoint for that reason, unlike .csp-grid-three above: two columns of a phone is
 * still two columns, and a 2x2 that becomes a 1x4 is a different thing.
 */
.csp-grid-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/*
 * ONE EVEN ROW -- `CardGrid columns="even"`. Every child on a single row of equal columns from the
 * breakpoint up, whatever their number, and two to a row on a phone. For a strip of headline figures
 * read across as a set; minmax(0, 1fr) for the long-word reason given on .csp-grid-three.
 */
.csp-grid-even { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 768px) {
  .csp-grid-even { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
}

/*
 * The rail. One row, never wrapping, scrolling sideways inside itself.
 *
 * It is the third box in this file that absorbs its own horizontal overflow, and the reason is the
 * one written two hundred lines up on body: there is no overflow-x: hidden on the document, so a
 * row wider than the viewport drags the whole page sideways unless the row itself contains it.
 * overscroll-behavior-x: contain is what stops a swipe that reaches the end of the rail from
 * becoming a back gesture or a page pan.
 *
 * The scrollbar is NOT hidden here, and that is the difference from the header bar. The bar is
 * exactly as tall as the tabs in it and a classic scrollbar would be drawn over the underline it
 * exists to reveal; a rail has room under its cards, and the bar is the only thing that says there
 * are more of them off the right edge. padding-bottom is what gives it that room -- without it the
 * scrollbar overlaps the cards it belongs to.
 *
 * Items take the tile column's width and do not shrink: a rail whose cards squeeze to fit is a grid
 * with extra steps. scroll-snap-align on each of them means a flick lands on a card rather than
 * halfway through one; proximity rather than mandatory, so a deliberate small drag is not fought.
 *
 * ⚠️ A scroll container clips its own children at its padding box, which is the same fact spec 102
 * recorded about the header bar -- so a card's glow is cut where it crosses the rail's edge. The
 * padding below is what buys it room vertically. Horizontally there is nothing to buy: the first
 * and last cards sit at the ends of a strip that has to start and end somewhere.
 */
.csp-carousel {
  /*
   * Load-bearing, and the same trap .csp-table-scroll and .csp-header-bar both record: .csp-sr-only
   * is position: absolute with no offsets, and with no positioned ancestor its containing block is
   * the document -- so a visually hidden label inside this rail would not be clipped by it and
   * would drag the page sideways instead.
   */
  position: relative;
  display: flex;
  flex-direction: row;
  gap: var(--csp-space-4);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding: 0 0 var(--csp-space-3);
  /* Also used as a UL, so the card list is a list to assistive tech. Same reason .csp-grid is. */
  list-style: none;
  margin: 0;
}
:where(.csp-carousel) > * {
  flex: none;
  width: var(--csp-size-tile-min);
  scroll-snap-align: start;
}

/*
 * "Take the height this column has left, and be allowed to be shorter than your own contents."
 *
 * min-height: 0 is the whole of it. Without that declaration a flex item in a column refuses to go
 * below its min-content height, so a long list makes its column taller instead of giving -- which
 * is precisely how one column of a two-column layout ends up setting the height of the page.
 *
 * .csp-clip is deliberately a SEPARATE class rather than part of this one. overflow: hidden on a
 * box clips its descendants, and a card's glow is painted outside the card's border box -- so
 * folding the clip into fill would crop the glow of every card inside a filled section. The box
 * that hides the overflow is the box that holds the rows, not the section around it.
 */
.csp-fill { flex: 1 1 auto; min-height: 0; }
.csp-clip { overflow: hidden; }
/*
 * The stop that keeps "fill the column" from meaning "become a box of mostly nothing".
 *
 * A five-row board beside eight corp cards fills to the height of the cards, which is several
 * screens. The panel must still not make the page taller than the column beside it -- that is what
 * fill buys -- so what it needs is a ceiling, and one viewport is the ceiling a reader can see.
 * 100dvh rather than 100vh: on a phone the browser chrome moves, and dvh is the box that is really
 * there. The same unit .csp-page already uses for the canvas, and for the same reason.
 */
.csp-max-h-screen { max-height: 100dvh; }
/*
 * ⛔ There is NO floor class here, and there must not be one again.
 *
 * `.csp-min-h-list` was a minimum height for a filling panel, so that a board beside a short
 * column still showed about eighteen rows. The owner rejected it twice in the same words: a panel
 * sizes to whatever is beside it and clips the rest, and it may not make the page taller to show
 * more rows. A floor is what made the dashboard's board 315px taller than the column it sits next
 * to. Deleted with `size.listPanelMin` and `Stack`'s `minHeight` prop.
 */

.csp-divider {
  border: 0;
  border-top: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  margin: 0;
  width: 100%;
}

.csp-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--csp-space-1);
  border-radius: var(--csp-radius-pill);
  border: var(--csp-fx-border-width) solid transparent;
  padding: var(--csp-space-1) var(--csp-space-2);
  white-space: nowrap;
  max-width: 100%;
}
.csp-badge-neutral {
  background: var(--csp-color-surface-muted);
  border-color: var(--csp-color-border);
  color: var(--csp-color-text-secondary);
}
.csp-badge-accent {
  background: var(--csp-color-accent-subtle);
  border-color: var(--csp-color-accent-border);
  color: var(--csp-color-accent-strong);
}
.csp-badge-caution {
  background: var(--csp-color-caution-surface);
  border-color: var(--csp-color-caution-border);
  color: var(--csp-color-caution-text);
}
.csp-badge-critical {
  background: var(--csp-color-critical-surface);
  border-color: var(--csp-color-critical-border);
  color: var(--csp-color-critical-text);
}

.csp-button {
  /* The pivot every hover transform above depends on. Never a corner or an edge. */
  transform-origin: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--csp-space-2);
  min-height: var(--csp-size-control-height);
  /*
   * Chromium's UA stylesheet gives a button padding of 1px 6px. Setting only padding-inline leaves
   * that 1px block padding in place — a value from nobody's design system sitting in ours, which is
   * what the probe's spacing check caught. Height comes from min-height and centring, so zero it.
   */
  padding-block: 0;
  padding-inline: var(--csp-space-4);
  border-radius: var(--csp-fx-control-radius);
  border: var(--csp-fx-border-width) solid transparent;
  font-family: var(--csp-fx-body-family);
  font-size: var(--csp-type-heading-sm-size);
  line-height: var(--csp-type-heading-sm-line);
  font-weight: var(--csp-fx-control-weight);
  cursor: pointer;
  position: relative;
  box-shadow: var(--csp-fx-glow);
  transition: background-color var(--csp-fx-duration) var(--csp-fx-ease),
    border-color var(--csp-fx-duration) var(--csp-fx-ease),
    box-shadow var(--csp-fx-duration) var(--csp-fx-ease),
    transform var(--csp-fx-duration) var(--csp-fx-ease),
    color var(--csp-fx-duration) var(--csp-fx-ease);
  white-space: nowrap;
}
/*
 * (!) A HOVER TRANSFORM MUST NOT PULL AN EDGE TOWARDS THE CURSOR.
 *
 * A transformed element is hit-tested in transformed space, so its hover target moves with it. Scale
 * from the centre is always safe — every edge travels outward, and a cursor already inside stays
 * inside. A translate is not: it moves the whole box one way, and the trailing edge chases the
 * cursor. If it catches it, the element un-hovers, snaps back, re-hovers, and oscillates.
 *
 * Whether it catches depends on the element's HEIGHT, because the two effects fight: scale pushes
 * the bottom edge down by height/2 x (scale - 1), and the lift pulls it up by the lift. On a 350px
 * card at 1.03 that is 5.3px down against 4px up — net outward, safe. On a 40px button it is 0.6px
 * against the same 4px — the bottom edge retreats 3.4px into the pointer, and it jitters.
 *
 * So the button scales and does not lift. The shells below keep both, because at card heights the
 * scale always wins. Anything short and hoverable added later belongs in this rule, not that one.
 *
 * ⭐ WHICH IS WHY THE HEADER'S TWO CONTROLS ARE IN THIS SELECTOR LIST rather than in a rule of
 * their own. The owner: *"the logo that takes you to the landing page and all of the buttons should
 * behave like buttons. that means bouncing on hover etc."* The link home held a picture and moved
 * like text; the nav tabs are marks with no words now, so a tab that did not answer the pointer
 * would be an icon nobody could tell was a control. Both are short and hoverable, which is exactly
 * the paragraph above — they scale from the centre and they do not lift.
 *
 * HOW MUCH they scale is `fx.hoverScale`, which is the point of there being one number: the
 * shipped default is `DEFAULT_THEME` = `vault` (themes.ts) and vault sets `1.03`, so all of
 * these grow by the same 3% on the same curve. Measured on the running app at 1280px: the link
 * home, a nav tab and a `Button` all compute `matrix(1.03, 0, 0, 1.03, 0, 0)` on hover and
 * `none` at rest.
 *
 * ⚠️ `fx.hoverScale` itself — the value `base` leaves at `1` — is NOT this rule's to retune.
 * It is also what `.csp-card-lift`, `.csp-nft` and `.csp-coll` read.
 */
.csp-button:hover:not(:disabled):not([aria-disabled='true']),
.csp-link-brand:hover,
.csp-link-nav-stacked:hover {
  transform: scale(var(--csp-fx-hover-scale));
  box-shadow: var(--csp-fx-glow-hover);
}
/*
 * The press. `scale(1)` by default, so with no skin this is a no-op that only ever computes while
 * a finger is down — nothing a probe or a screenshot of an idle page can see.
 */
.csp-button:active:not(:disabled):not([aria-disabled='true']) {
  transform: translateY(var(--csp-fx-lift)) scale(var(--csp-fx-press));
}
/*
 * ⛔ THE PRESS MUST NOT LOSE THE CLICK. The rule above moves the button while the finger is down —
 * vault lifts it 4px and shrinks it 2%, another skin drops it 2px — and a transformed element is
 * hit-tested where it now IS. A pointer that went down in the bottom 4px of a 36px tab was outside
 * the button at mouse-up, the browser dispatched the click to the parent, and nothing happened.
 * Measured on the leaderboard tabs at 1280px: pressing 90% of the way down registered nothing,
 * 80% registered. The owner: *"it appears to click but there's some state lock … so it does not
 * register."* There was no lock; the button had moved out from under the pointer.
 *
 * So while it is pressed, an invisible pseudo-element extends the hit area 8px past every edge —
 * more than any skin's lift plus its shrink — and the click lands on the button however it moves.
 * `.csp-button` is `position: relative` already. Present only during :active, so it covers
 * nothing while nobody is pressing.
 */
.csp-button:active:not(:disabled):not([aria-disabled='true'])::after {
  content: '';
  position: absolute;
  inset: -8px;
}
/*
 * ⛔ DISABLED IS STATED, NEVER FADED — for every variant, and for the label FileButton wears.
 *
 * It was `opacity: 0.55` on the whole element, with the filled variant carved out. The owner's
 * rule has no carve-out either way: *"no button in the entire app should be [transparent]"* — and
 * a faded button is a button the page shows through. So a disabled control STATES its look from
 * solid tokens, the way `.csp-toggle:disabled` flattens its switch rather than fading it: the
 * recessed well every input sits in, the plain hairline, the muted ink (which clears 4.5:1 on
 * every ground — `TEXT_RAMP` in palettes.ts) and no glow. Flat and sunk reads as "off"; faded
 * reads as "failing". Both fills are composited opaque by `solidBackgrounds`, which
 * `solid-surfaces.test.ts` holds over every palette.
 *
 * The filled variant used to state its own look here because fading it washed near-white ink to
 * 2.2:1 on a light ground; one stated look for all three is that fix, generalised.
 *
 * WCAG 1.4.3 exempts inactive components. This deliberately does not take the exemption: a disabled
 * control still has to be readable to be understood as disabled rather than as broken, and the
 * rendered-pixel probes measure it like any other text.
 *
 * `aria-disabled` is how a control that is not a <button> says it — FileButton's label, and a
 * social-row anchor with nowhere to go — so the same look keys on it, and the hover and press rules
 * above leave it alone exactly as they leave `:disabled` alone.
 */
.csp-button:disabled,
.csp-button[aria-disabled='true'],
.csp-file-button[aria-disabled='true'] {
  cursor: default;
  background: var(--csp-color-surface-sunken);
  border-color: var(--csp-color-border);
  color: var(--csp-color-text-muted);
  box-shadow: none;
}
.csp-button-sm {
  min-height: var(--csp-size-control-height-sm);
  padding-inline: var(--csp-space-3);
  font-size: var(--csp-type-body-sm-size);
  line-height: var(--csp-type-body-sm-line);
}
/*
 * `Button justify="start"` — a full-width ROW rather than a centred button. See ButtonProps
 * for when a list of controls wants to be one.
 *
 * It restates a declaration the generic .csp-justify-start utility already carries rather
 * than reusing it, and that is the cascade rather than an oversight: that utility sits with the
 * Stack alignment classes, hundreds of lines ABOVE .csp-button, so at equal specificity the
 * button's own `justify-content: center` beats it. This rule is written after it.
 */
.csp-button-start { justify-content: flex-start; }
/*
 * The button whose whole content is a glyph — `IconButton`, worn today by the mascot's two
 * bubble controls and by `Modal`'s corner X. It wears the ghost button's own classes and this
 * only makes it SQUARE, at the compact control height, so it reads as chrome rather than as a
 * labelled button. Later in source order than .csp-button-sm, which is what lets the zeroed
 * inline padding win without a doubled selector.
 */
.csp-icon-button {
  width: var(--csp-size-control-height-sm);
  padding-inline: 0;
}
.csp-icon-button svg {
  width: var(--csp-space-4);
  height: var(--csp-space-4);
  display: block;
}
/*
 * The form around the header's sun/moon switch — `ThemeSwitch`, the one `<form>` in the
 * product. A form is a block, and a block holding an inline-block button puts that button on a
 * line box with the strut's descender under it; flex is what makes the form exactly the button's
 * own box, so it centres on the header row like the wallet control beside it.
 */
.csp-theme-switch { display: flex; }
/*
 * That icon button in the corner of a dialog — `Modal closeLabel`, and only the dialogs that
 * ask for one have it.
 *
 * OVER the panel rather than in it, so the body's width is the body's width: a way out that took a
 * column would narrow every row under it by the width of one button, and the rows in a picker are
 * the panel's content width or they are not rows. `.csp-card` is already
 * `position: relative`, which is the box these offsets are against.
 *
 * ## ⭐ The offsets are a CORNER, and the collision they are chosen to miss
 *
 * ⚠️ It is anchored in the panel's padding gutter and NOT flush with the body's content edges,
 * which is where it was written first, at MODAL_PADDING. That looked tidier and put fourteen pixels
 * of this button's box on top of the row underneath it: measured in the wallet picker at 1280x900,
 * the button ran 25..61px down the card while the first row began at 47. The GLYPH cleared it, so
 * nothing looked wrong; what was wrong is that the row is positioned too and comes later in the
 * DOM, so it paints over this button — the bottom of a control that dismisses the dialog
 * would have connected a wallet instead.
 *
 * At `space-2` the button ends at 45 against that same 47, which is clear, and clear in the
 * direction that holds: anything that makes the heading above a list taller pushes the first row
 * further DOWN, and nothing in the panel can push it up. `z-index` is the belt to that brace:
 * a dialog whose content starts flush at the top with no heading has nothing between it and this
 * button, and the control drawn deliberately on top is the one that should answer a press in its
 * own box. (The edge layer every card paints is `z-index: 0` and takes no pointer events, so this
 * sits above it and over nothing else.)
 *
 * ⚠️ It is written HERE rather than up with the other .csp-modal rules because of the
 * cascade: .csp-button sets `position: relative` for its own sake, and at equal specificity
 * the later rule wins. Written in the modal block it would have been inert, and inert in a way that
 * looks exactly like a typo in the offsets. css.test.ts asserts the order.
 *
 * The card CLIPS, so a ring drawn outward at its very edge would be cut off. The shared ring is
 * 2px at a 2px offset, so this reaches 4px past the button and `space-2` leaves twice that.
 */
.csp-modal-close {
  position: absolute;
  top: var(--csp-space-2);
  right: var(--csp-space-2);
  z-index: 1;
}
/*
 * `Toast`'s X, and the same arrangement for the same reason: over the panel's padding gutter rather
 * than in the body, so the body's width is the body's width. `.csp-card` is already
 * `position: relative`, which is the box these offsets are against.
 *
 * ## ⚠️ IT HAS TO LIVE HERE, after .csp-button — it was written up beside .csp-toast first
 *
 * `.csp-button` sets `position: relative` and this selector has exactly its specificity, so
 * declared earlier in the sheet it LOSES and the button stays in flow. Measured: the X rendered as
 * the first row of the card at x=457 in a box whose right edge was 832, pushing the panel to 132px
 * tall and sitting 8px on top of the sentence. Nothing errored and the glyph looked plausible —
 * which is the whole trap. Anything that wears `.csp-button` and then repositions itself
 * belongs below that rule.
 *
 * Not merged with .csp-modal-close, because the two are anchored against different paddings —
 * the dialog's is MODAL_PADDING and the toast's card is one `space-4` step — so the collision each
 * has to miss is a different one. See the note above for what happens when those are chosen by eye.
 */
.csp-toast-close {
  position: absolute;
  top: var(--csp-space-2);
  right: var(--csp-space-2);
  z-index: 1;
}
.csp-button-primary {
  background: var(--csp-color-accent);
  border-color: var(--csp-color-accent);
  color: var(--csp-color-accent-text);
}
.csp-button-primary:hover:not(:disabled) {
  background: var(--csp-color-accent-strong);
  border-color: var(--csp-color-accent-strong);
}
/*
 * THE SECONDARY CTA, and it is no longer the colour of the page.
 *
 * It filled with `surface`, which on a light palette IS the paper — `daylight`'s two panel roles
 * resolve to the canvas (see the ladder in palettes.ts), so the shipping product's every secondary
 * button and secondary-variant Link was white on white, findable only by its hairline. The owner
 * asked for a very light tint of the accent instead, a little stronger under the pointer, which is
 * what `accentWash` / `accentWashStrong` are — every palette derives its own pair, so this is one
 * rule rather than a colour that is right on one ground.
 */
/*
 * FileButton -- a label wearing the button classes, over an input nobody sees.
 *
 * The label is not a <button>, so :disabled never matches it and the button rules' disabled
 * treatment cannot reach it. aria-disabled is what it sets instead: its LOOK is the stated one
 * every disabled button gets (the rule above lists it), and this adds pointer-events off so the
 * hidden input cannot be reached by a click either. The input itself is still disabled, which is
 * what actually stops it.
 */
/*
 * An animated ellipsis for a control that is working -- three dots that fill in and start again.
 *
 * On a ::after so the button's own label stays one string: the caller writes "Generating" with no
 * dots, and this adds them. Written as content steps rather than an opacity fade because the dots
 * must not change the button's WIDTH as they animate -- so the pseudo-element is a fixed 1.5ch and
 * the text inside it is what changes.
 *
 * prefers-reduced-motion gets the three dots, still. The information is "this is working", and a
 * static "..." says that without moving.
 */
.csp-ellipsis::after {
  content: '';
  display: inline-block;
  width: 1.5ch;
  text-align: left;
  animation: csp-ellipsis-steps 1.2s steps(1, end) infinite;
}
@keyframes csp-ellipsis-steps {
  0%   { content: '.'; }
  33%  { content: '..'; }
  66%  { content: '...'; }
  100% { content: '.'; }
}
@media (prefers-reduced-motion: reduce) {
  .csp-ellipsis::after { content: '...'; animation: none; }
}
.csp-file-button[aria-disabled='true'] { pointer-events: none; }
/*
 * A picture that IS the file control (PictureButton): a button with no chrome of its own, the picture
 * inside it edge to edge. The focus ring is the shared rule below; the box's own radius is the
 * picture's, so the ring follows the picture. Its ground is the one the public `Avatar` paints
 * under the same picture — not `none`: a button on this product is never a hole in the page, and
 * a picture with alpha in it (a coin on transparency) shows what is under it.
 */
.csp-picture-button {
  display: block;
  width: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  background: var(--csp-color-surface-muted);
  color: inherit;
  font: inherit;
  cursor: pointer;
  border-radius: var(--csp-fx-art-radius);
}
.csp-picture-button:disabled { cursor: default; }
/*
 * ⭐ THE EDIT BUTTON THAT APPEARS ON HOVER — `EditHost`, around every element the owner edits in
 * place. The owner: *"they need some edit button that appears on hover."*
 *
 * The pencil is an `IconButton` (the ghost chip) at the host's top-right corner, and it is NOT
 * painted until the pointer is over the host or focus is inside it — the element's own focus
 * reveals it, so a keyboard reaches it with the next Tab; on a device with no hover it is simply
 * always there, as `.csp-nft-action` is. `visibility`, not `opacity`: a button on this product is
 * never painted translucent, so it is either not painted or painted whole.
 *
 * One placement for every host. What differs is whether the host makes ROOM: a picture or a mark
 * is a box the chip can sit on, and text is not — a pencil over the end of a heading is a pencil
 * over its last letters. So the text host (`-beside`) shrinks to its own words and reserves the
 * chip's width past them, capped at the column, and the chip sits in that room.
 */
.csp-edit-host { position: relative; }
.csp-edit-host-beside {
  width: fit-content;
  max-width: 100%;
  padding-inline-end: calc(var(--csp-size-control-height-sm) + var(--csp-space-2));
}
.csp-edit-button {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
  z-index: 1;
  visibility: hidden;
}
.csp-edit-host:hover > .csp-edit-button,
.csp-edit-host:focus-within > .csp-edit-button { visibility: visible; }
@media (hover: none) {
  .csp-edit-button { visibility: visible; }
}
/*
 * ABOVE — a mark on the links row, which is the pencil's own size: the pencil stands on the mark's
 * top edge, centred over it, and never over it (the owner: *"make it 'y above'"*). Flush, with no
 * gap, so a pointer travelling up from the mark is inside the pencil before it has left the host
 * and the reveal holds. Centred by insets and auto margins, not a transform: the button's own
 * hover moves it by transform, and a second one here would be overwritten.
 *
 * On a device with no hover the pencil is always painted, so there the host keeps the pencil's
 * height clear above the mark rather than letting it sit on whatever is above the row.
 */
.csp-edit-host-above > .csp-edit-button {
  inset-block-start: auto;
  inset-block-end: 100%;
  inset-inline: 0;
  width: var(--csp-size-control-height-sm);
  margin-inline: auto;
}
@media (hover: none) {
  .csp-edit-host-above { margin-block-start: var(--csp-size-control-height-sm); }
}
/*
 * The field that stands where the pencil stood while a mark's link is being edited — the field's
 * own look (`.csp-field`'s ground, border and radius), floating: ABSOLUTE above the mark, so the
 * row under it never wraps or moves, and over the page with the dropped panel's shadow and index.
 * One line: a long URL scrolls inside it rather than growing it.
 */
.csp-edit-host > .csp-edit-overhead {
  position: absolute;
  z-index: 10;
  inset-block-end: 100%;
  inset-inline-start: 0;
  width: min(24rem, calc(100vw - 2 * var(--csp-space-4)));
  padding: var(--csp-space-2) var(--csp-space-3);
  border-radius: var(--csp-fx-control-radius);
  border: var(--csp-fx-border-width) solid var(--csp-color-border-strong);
  background-color: var(--csp-color-surface-sunken);
  background-image: var(--csp-fx-surface-image);
  box-shadow: var(--csp-shadow-md);
}
.csp-edit-overhead > * {
  display: block;
  white-space: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}
/*
 * Out of sight, still in the tab order. NOT display:none and NOT visibility:hidden -- both remove
 * it from the tab order, and it is the only thing that can open a file dialog, so that would leave
 * a keyboard user unable to pick a file at all.
 */
.csp-file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.csp-button-secondary {
  background: var(--csp-color-accent-wash);
  border-color: var(--csp-color-border-strong);
  color: var(--csp-color-text-primary);
}
.csp-button-secondary:hover:not(:disabled) { background: var(--csp-color-accent-wash-strong); }
/*
 * ⛔ **THE QUIETEST BUTTON, AND IT IS NOT A HOLE IN THE PAGE.**
 *
 * It was `background: transparent; border-color: transparent` — nothing painted at all, so the
 * page's own canvas ran straight through it and the only thing saying "button" was the label. The
 * owner, on the launch wizard's Start over: *"the 'start over' button is transparent. no button in
 * the app should be transparent under any circumstances — fix all instances."*
 *
 * So it has a ground and an edge, and the ladder between the three variants is what keeps it the
 * quietest of them rather than a second secondary:
 *
 *  - **primary** — `accent` inside `accent`. The page's loudest thing.
 *  - **secondary** — `accent-wash` inside `border-strong`. Tinted, and edged hard.
 *  - **ghost** — `surface-muted` inside the plain `border` hairline. A neutral chip, visibly a
 *    control and claiming nothing; hover lifts it one step of that same ladder
 *    (`surface-raised`, `border-strong`) so the pointer gets an answer that is still not a tint.
 *
 * Both grounds are composited opaque by `solidBackgrounds`, which is the guarantee
 * `solid-surfaces.test.ts` holds over every palette — so neither state can go translucent again
 * because a palette was added.
 *
 * ⚠️ **`IconButton`, the palette's `Tabs`, `Modal`'s corner X and the social links all wear these
 * classes deliberately**, so every one of them gains the same chip. That is the intent rather than
 * a side effect: they are buttons, and the rule above has no exceptions — the owner's row used to
 * dim a mark whose link is not given yet, and that went for the same reason.
 */
.csp-button-ghost {
  background: var(--csp-color-surface-muted);
  border-color: var(--csp-color-border);
  color: var(--csp-color-text-secondary);
}
.csp-button-ghost:hover:not(:disabled):not([aria-disabled='true']) {
  background: var(--csp-color-surface-raised);
  border-color: var(--csp-color-border-strong);
  color: var(--csp-color-text-primary);
}
.csp-button[aria-pressed='true'].csp-button-ghost,
.csp-button[aria-pressed='true'].csp-button-secondary {
  background: var(--csp-color-accent-subtle);
  border-color: var(--csp-color-accent-border);
  color: var(--csp-color-accent-strong);
}

/*
 * ⭐ THE OPTION CARD — `OptionCard`, one choice of a few, each the size of a panel (spec 272: the
 * mint's fee-share dial, three of these in a row).
 *
 * It is the ghost chip's ladder at a card's scale. At rest it is the ghost button's ground inside
 * the card's edge and radius, with the card's light; under the pointer it climbs one step
 * (`surface-raised`, `border-strong`) AND lifts and scales as a clickable card does — at this
 * height the scale beats the lift, so the trailing edge never chases the cursor (see the note on
 * `.csp-button:hover`). The press is the button's press.
 *
 * ⭐ THE CHOSEN ONE IS FILLED. Not the pressed chip's 14% tint inside a hairline — that reads as
 * "hovered" at card size — but the accent itself with the ink that reads on it, exactly what
 * `.csp-button-primary` and `.csp-card-accent` paint. Three cards side by side, one of them the
 * loudest colour on the page, is a state that cannot be misread at a glance. The skin's surface
 * texture comes off the filled card for the reason `.csp-card-accent` drops it: a veil tuned for
 * the surface reads as dirt over a saturated fill.
 *
 * The label is laid out at the START and wraps: a card is wide enough that a centred short label
 * floats, and an option's words are read like a row's, from the leading edge.
 */
.csp-option-card {
  transform-origin: center;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  width: 100%;
  min-height: calc(var(--csp-size-control-height) * 2);
  padding: var(--csp-space-4);
  border-radius: var(--csp-fx-card-radius);
  border: var(--csp-fx-border-width) solid var(--csp-color-border);
  background-color: var(--csp-color-surface-muted);
  background-image: var(--csp-fx-surface-image);
  color: var(--csp-color-text-secondary);
  font-family: var(--csp-fx-body-family);
  font-size: var(--csp-type-heading-sm-size);
  line-height: var(--csp-type-heading-sm-line);
  font-weight: var(--csp-fx-control-weight);
  text-align: start;
  cursor: pointer;
  box-shadow: var(--csp-fx-inner-light), var(--csp-fx-glow);
  transition: background-color var(--csp-fx-duration) var(--csp-fx-ease),
    border-color var(--csp-fx-duration) var(--csp-fx-ease),
    box-shadow var(--csp-fx-duration) var(--csp-fx-ease),
    transform var(--csp-fx-duration) var(--csp-fx-ease),
    color var(--csp-fx-duration) var(--csp-fx-ease);
}
.csp-option-card:hover {
  background-color: var(--csp-color-surface-raised);
  border-color: var(--csp-color-border-strong);
  color: var(--csp-color-text-primary);
  transform: translateY(var(--csp-fx-lift)) scale(var(--csp-fx-hover-scale));
  box-shadow: var(--csp-fx-inner-light), var(--csp-fx-glow-hover);
}
.csp-option-card:active {
  transform: translateY(var(--csp-fx-lift)) scale(var(--csp-fx-press));
}
.csp-option-card[aria-pressed='true'],
.csp-option-card[aria-pressed='true']:hover {
  background-color: var(--csp-color-accent);
  background-image: none;
  border-color: var(--csp-color-accent);
  color: var(--csp-color-accent-text);
}

.csp-field {
  width: 100%;
  min-height: var(--csp-size-control-height);
  padding: var(--csp-space-2) var(--csp-space-3);
  border-radius: var(--csp-fx-control-radius);
  border: var(--csp-fx-border-width) solid var(--csp-color-border-strong);
  background-color: var(--csp-color-surface-sunken);
  background-image: var(--csp-fx-surface-image);
  transition: border-color var(--csp-fx-duration) var(--csp-fx-ease),
    box-shadow var(--csp-fx-duration) var(--csp-fx-ease);
  color: var(--csp-color-text-primary);
  font-family: var(--csp-fx-body-family);
  font-size: var(--csp-type-body-size);
  line-height: var(--csp-type-body-line);
}
.csp-field::placeholder { color: var(--csp-color-text-muted); }
/* An invalid field is marked by its border AND by the message beside it, never by colour alone. */
.csp-field-invalid { border-color: var(--csp-color-critical-border); }

/*
 * THE DROPPED PANEL — one box under two hosts, `Combobox` and `Menu` (`primitives/dropdown.tsx`).
 *
 * The host is the positioning context; the panel hangs off its bottom edge at the host's own
 * width, one step below it. ABSOLUTE so opening it moves nothing on the page — a list that
 * reflowed the table under it every time the box took focus would be a table that jumps under
 * the cursor. Capped at `size.dropdownMax` and scrolling past it.
 *
 * `inset-block-start: 100%` is load-bearing and not tidiness. Left to its static position the
 * panel lands where the host's layout would have put a normal child — under the box in the
 * picker, whose host is a block, but at the START of the row in the menu, whose host is inline-flex:
 * measured, the menu's panel opened over its own trigger, four pixels down from its top edge.
 *
 * It is a card's ground and a card's edge — the same surface, image, border and radius — with the
 * palette's cast shadow, because it OVERLAPS the page rather than sitting in it. `z-index` for
 * the same reason: a later sibling that is itself positioned (the table's card) would otherwise
 * paint over it. 10 clears every in-page layer (the card edge and the tooltip are 1) and stays
 * under the sticky header's 30 and everything floating above that.
 *
 * A row is a real button with no button chrome: full width, packed to the leading edge, the
 * control radius, and a ground only when chosen or under the pointer. The chosen row's ground is
 * the ACCENT wash the pressed ghost button wears, so "this is picked" reads the same here as it
 * does on a filter chip.
 */
.csp-combobox,
.csp-menu { position: relative; width: 100%; }
.csp-menu { width: auto; display: inline-flex; }
.csp-dropdown {
  position: absolute;
  z-index: 10;
  inset-block-start: 100%;
  inset-inline-start: 0;
  min-width: 100%;
  margin-block-start: var(--csp-space-1);
  max-height: var(--csp-size-dropdown-max);
  overflow-y: auto;
  padding: var(--csp-space-2);
  border-radius: var(--csp-fx-card-radius);
  border: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  background-color: var(--csp-color-surface);
  background-image: var(--csp-fx-surface-image);
  box-shadow: var(--csp-shadow-md);
}
.csp-dropdown-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--csp-space-2) var(--csp-space-3);
  border: 0;
  border-radius: var(--csp-fx-control-radius);
  background: transparent;
  color: var(--csp-color-text-primary);
  font: inherit;
  white-space: nowrap;
  cursor: pointer;
}
.csp-dropdown-option:hover { background: var(--csp-color-surface-muted); }
.csp-dropdown-option[aria-selected='true'],
.csp-dropdown-option[aria-checked='true'] {
  background: var(--csp-color-accent-subtle);
  color: var(--csp-color-accent-strong);
}
.csp-dropdown-option:disabled { cursor: default; color: var(--csp-color-text-muted); }
/* The tick beside a checkbox item, one label-em wide so the words line up whether or not it shows. */
.csp-dropdown-check {
  display: inline-block;
  width: 1em;
  text-align: center;
  color: var(--csp-color-accent-ink);
}

/*
 * Links.
 *
 * A link carries no colour of its own at the base level, so a link wearing the button classes keeps
 * the button's colour. The variants below add one. The focus rule underneath is the reason this
 * primitive exists at all: the browser's default ring computes to 1px, and the probe's focus check
 * asks for two.
 */
.csp-link { text-decoration: none; border-radius: var(--csp-radius-sm); }
/*
 * THE UNDERLINE TAB, and it is ONE style worn by two places: the header's primary nav and the
 * Holdings screen's two sections. Restyle B, which is the one the owner picked.
 *
 * That sharing is the point of the class rather than a coincidence of it — standardising the tab
 * treatment means changing it HERE and having both move. Whatever is out of scope stays out: the
 * palette's `Tabs` primitive is ghost buttons with `aria-pressed` because it filters a list in
 * place, and the creator wizard's stepper is a progress indicator. Neither is a tab group and
 * neither wears this.
 *
 * The marker is an `::after` rather than a `border-bottom`: it is painted OUT of flow, so it
 * cannot take 2px off the content box of a tab whose height has to add up to `headerHeight`
 * exactly. `bottom: 0` is what seats it on the bar's bottom edge, because a stacked tab is the
 * full height of the bar — see `.csp-link-nav-stacked` below.
 *
 * `white-space: nowrap`: a tab label that wraps is a tab two lines tall, which in the header is
 * the exact failure spec 101 exists to remove.
 */
.csp-link-nav {
  /* The pivot the shared hover transform depends on. Never a corner or an edge. */
  transform-origin: center;
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: var(--csp-size-control-height-sm);
  padding-inline: var(--csp-space-2);
  color: var(--csp-color-text-secondary);
  white-space: nowrap;
  transition: transform var(--csp-fx-duration) var(--csp-fx-ease),
    color var(--csp-fx-duration) var(--csp-fx-ease);
}
.csp-link-nav:hover { color: var(--csp-color-text-primary); }
.csp-link-nav::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: var(--csp-size-tab-marker);
  background-color: transparent;
}
/*
 * Both of these are the accent AS TEXT, so both take the INK role rather than the fill. The rule
 * for telling the two apart is one line: a `color:` is the ink, a background, a border, a shadow
 * or a fill is the accent. See `accentInk` in palettes.ts for why they are different colours on a
 * light ground and the same one on a dark ground.
 */
.csp-link-nav[aria-current='page'] { color: var(--csp-color-accent-ink); }
/* The marker is a FILL by that same rule — a background, not a colour on text. */
.csp-link-nav[aria-current='page']::after { background-color: var(--csp-color-accent); }
/*
 * THE PLAIN NAV LINK — `Link variant="navPlain"`: primary navigation for a header whose tabs sit
 * one row down. The admin panel's top bar (2026-09-19): its group rail wore `navStacked` beside
 * the active group's `nav` sections in the same row, and two levels of underline in one line
 * read as one flat list of seven tabs.
 *
 * So this is the tab with the underline taken away and the current state moved into the TEXT: the
 * resting tone is the tab's (`text-secondary`), hover and current are the primary ink, and the
 * current one is also set a weight up. Tone alone is how Vercel's project chrome tells the active
 * item; weight is GitHub's; a text link with no underline needs both to be read at a glance next
 * to a row of tabs that HAVE one — and on `daylight` the two text tones are close.
 *
 * ⚠️ The weight is the TYPE SCALE's heading weight (`type-heading-md-weight`, 600) and NOT
 * `fx-heading-weight`, the token a bold run takes. `vault` — the product — sets that fx
 * token to 400, because its display face has no real bold, and under it the current link computed
 * LIGHTER than its neighbours at 500 (measured on the panel, 2026-09-19). A weight that says
 * "current" has to be heavier than the resting one whatever the skin, and the scale's is.
 *
 * Same box as `.csp-link-nav` — the compact control height, the same inline padding — so the two
 * tiers are spaced as one system and a plain link is still a tap target. No `::after`, no
 * hover scale, no `position: relative`: there is nothing to seat and nothing to slide.
 */
.csp-link-nav-plain {
  display: inline-flex;
  align-items: center;
  min-height: var(--csp-size-control-height-sm);
  padding-inline: var(--csp-space-2);
  color: var(--csp-color-text-secondary);
  white-space: nowrap;
  transition: color var(--csp-fx-duration) var(--csp-fx-ease);
}
.csp-link-nav-plain:hover { color: var(--csp-color-text-primary); }
.csp-link-nav-plain[aria-current='page'] {
  color: var(--csp-color-text-primary);
  font-weight: var(--csp-type-heading-md-weight);
}
/*
 * THE MARKER THAT SLIDES — one per row, over whichever tab is current (spec 102).
 *
 * The per-tab `::after` above is the marker with no JavaScript, and it cannot move: a marker that
 * belongs to a tab has nowhere to travel from. This one belongs to the ROW, so it can. The component
 * that seats it is `primitives/tab-marker.tsx`, and that file carries the reasoning.
 *
 * ## Three rules, and each of them is load-bearing
 *
 *  1. A row that CONTAINS one becomes the positioning context. `Stack` takes semantic props only —
 *     no `className`, no `style` — so the row cannot be told to be `relative` from the app's
 *     side, and `:has()` is how the marker asks for what it needs without the caller knowing.
 *     Applied from the first paint, before hydration, so nothing shifts when the marker appears.
 *  2. It is `display: none` until `data-on`, which ONLY the component sets. With no JavaScript
 *     it is an empty hidden span and the per-tab markers are untouched — the no-JS promise the shell
 *     and `HoldingsTabs` both make.
 *  3. Once it IS on, the current tab's own `::after` goes transparent. Otherwise the destination
 *     is already painted before the slide arrives, and what a reader sees is a second underline
 *     sliding towards a marker that beat it there.
 *
 * `left`, `width` and `bottom` all arrive as inline styles from the measurement — there is no
 * length here, because the answer is wherever the current tab happens to be.
 */
.csp-stack:has(> .csp-tab-marker) { position: relative; }
.csp-tab-marker {
  display: none;
  position: absolute;
  height: var(--csp-size-tab-marker);
  background-color: var(--csp-color-accent);
}
.csp-tab-marker[data-on] { display: block; }
.csp-stack:has(> .csp-tab-marker[data-on])
  .csp-link-nav[aria-current='page']::after {
  background-color: transparent;
}
/*
 * THE HEADER TAB — A MARK, AND NOTHING ELSE PAINTED.
 *
 * It used to be a mark stacked ON its label. The owner asked for the words to go: *"make the page
 * buttons (explore/dashboard/etc.) just show the icon and highlight and show the tab name as a sort
 * of tooltip on hover."* So the label is still inside the link — it is the link's accessible name
 * and it is what the tooltip paints — but it is out of flow, and what is left in the column is the
 * mark. See `.csp-tooltip` below.
 *
 * It is the full height of the bar (`align-self: stretch` inside a stretched row), which is what
 * puts its underline on the bar's own bottom edge rather than floating somewhere inside it, and
 * what gives the tooltip a box to sit at the bottom of. `padding-block` is one of the three terms
 * of `size.headerHeight`'s sum, read from the same token NAME the sum reads — see
 * `HEADER_PADDING_Y` in tokens.ts. A literal here is how a bar ends up a step taller than the tab
 * it was sized for.
 *
 * ⚠️ `NAV_STACK_GAP` is no longer a `gap` on this rule, and it must not come back as one: with
 * the label out of flow the column has a single child, so a gap would describe a relationship that
 * no longer exists. It is still the second term of `size.headerHeight` and still 0, which is why
 * the bar's height did not move when the words did.
 *
 * Holdings' tabs deliberately do NOT wear this: they are label-only, so there is nothing to hide.
 */
.csp-link-nav-stacked {
  flex-direction: column;
  justify-content: center;
  align-self: stretch;
  padding-block: var(--csp-space-2);
}
/*
 * THE TOOLTIP — the label, painted only under the pointer or under focus.
 *
 * ## At rest it is hidden the way VisuallyHidden hides things, and that is load-bearing
 *
 * Clipped to a pixel, NOT `display: none` and NOT `visibility: hidden`. Either of those takes
 * the text out of the accessibility tree, and this text is the link's ONLY accessible name now that
 * the words are not painted — four nav items called nothing at all, which no visual check would
 * ever notice. The declarations are `.csp-sr-only`'s, and they are repeated rather than shared
 * because this element leaves that state and that one never does.
 *
 * ## It answers a KEYBOARD as well as a pointer
 *
 * `:focus-visible` beside `:hover`, on the parent. A tooltip that only a mouse can summon is
 * a tooltip a keyboard user does not have.
 *
 * ## Why it sits INSIDE the bar rather than hanging below it
 *
 * `.csp-header-bar` is a horizontal scroll strip, and a scroll container clips its own children
 * on BOTH axes — the same fact that cost the connect button its cast shadow (spec 102, recorded on
 * that rule). Nothing painted outside the bar's box survives, whatever it is positioned against. So
 * this sits on the bar's own bottom edge, in the band of space the label used to occupy: the tab is
 * the full height of the bar, the mark is centred in it, and `bottom` clears the underline so a
 * current tab's marker is not covered by its own tooltip.
 *
 * Its height is the label's line box and nothing else — no block padding — which is what keeps it
 * clear of the mark above it without a length nobody can justify.
 */
.csp-link-nav-stacked:has(> .csp-tooltip),
.csp-infotip:has(> .csp-tooltip),
.csp-info:has(> .csp-tooltip) { position: relative; }
.csp-tooltip {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/*
 * SHOWN — everything the three hosts agree about. What differs between them is the offsets, in the
 * rules below; a fourth host adds itself to this list rather than restating the treatment.
 */
.csp-tooltip[data-tip-open],
.csp-link-nav-stacked:hover > .csp-tooltip,
.csp-link-nav-stacked:focus-visible > .csp-tooltip,
.csp-infotip:hover > .csp-tooltip,
.csp-infotip:focus-visible > .csp-tooltip,
.csp-info:hover > .csp-tooltip,
.csp-info:focus-visible > .csp-tooltip {
  width: auto;
  height: auto;
  margin: 0;
  overflow: visible;
  clip-path: none;
  padding-block: 0;
  padding-inline: var(--csp-space-2);
  border-radius: var(--csp-radius-pill);
  background-color: var(--csp-color-surface-muted);
  color: var(--csp-color-text-primary);
  font-size: var(--csp-type-label-size);
  line-height: var(--csp-type-label-line);
  font-weight: var(--csp-fx-label-weight);
  letter-spacing: var(--csp-fx-label-tracking);
  pointer-events: none;
}
/* The tab's placement: on the bar's own bottom edge, centred under the mark. */
.csp-link-nav-stacked:hover > .csp-tooltip,
.csp-link-nav-stacked:focus-visible > .csp-tooltip {
  inset-block-end: var(--csp-size-tab-marker);
  inset-inline-start: 50%;
  transform: translateX(-50%);
}
/*
 * The "i"'s placement: ABOVE itself, and aligned to its leading edge rather than centred on it.
 * Centred is right for a tab in a row of equals; this mark sits at the end of a tile's name, where a
 * centred label of a dozen words would hang off the card's inline edge on the narrow column.
 */
.csp-infotip:hover > .csp-tooltip,
.csp-infotip:focus-visible > .csp-tooltip,
.csp-info:hover > .csp-tooltip,
.csp-info:focus-visible > .csp-tooltip {
  inset-block-end: 100%;
  inset-inline-start: 0;
  margin-block-end: var(--csp-space-1);
}
/*
 * THE THIRD HOST'S ONE DIFFERENCE: it carries an EXPLANATION, so it wraps.
 *
 * The other two hosts label a control — a tab's name, a link's destination — and a label is a few
 * words that must not break, which is what `white-space: nowrap` on the hidden state buys them.
 * What this one holds is prose, and prose in a nowrap pill is one line as wide as the sentence: the
 * terms block above a Confirm button would be several thousand pixels of it, off the side of the
 * page, with no way to read the end.
 *
 * So it wraps, to the system's own reading measure, and takes the block padding and the surface
 * treatment a paragraph needs — the same surface, the same ink, the same type scale as the pill
 * above, because it is the same tooltip. `z-index` because this one overlaps content rather than
 * sitting in a gap left for it: a pill above a stat tile's first line has nothing under it, and a
 * paragraph above a button covers whatever the button sits over.
 *
 * ⚠️ The width is a CEILING and not a width. `max-width` alone would let a two-word note render as
 * a box the width of the measure; `width: max-content` sizes it to the words and the ceiling stops
 * it at the measure. `min()` keeps it inside a phone.
 */
.csp-infotip > .csp-tooltip[data-tip-open],
.csp-infotip:hover > .csp-tooltip,
.csp-infotip:focus-visible > .csp-tooltip {
  white-space: normal;
  width: max-content;
  max-width: min(var(--csp-size-prose-max), 80vw);
  padding-block: var(--csp-space-3);
  padding-inline: var(--csp-space-4);
  border: 1px solid var(--csp-color-border);
  border-radius: var(--csp-radius-md);
  box-shadow: var(--csp-shadow-md);
  font-size: var(--csp-type-body-sm-size);
  line-height: var(--csp-type-body-sm-line);
  font-weight: normal;
  letter-spacing: normal;
  z-index: 1;
}
/*
 * ⚠️ AND THE CARD AROUND IT STOPS CLIPPING — without this rule the tooltip does not exist.
 *
 * `.csp-card` is `overflow: hidden` so a full-bleed media band takes the card's own radius, and this
 * mark sits on the FIRST line of a stat tile: a label painted above it is painted outside the card,
 * where a clipping box cuts it off entirely. That failure is invisible to every check that reads the
 * DOM — the element is there, it has a box, and none of it is drawn.
 *
 * Un-clipping is safe for exactly the cards this matches and no others. A card holding one of these
 * holds no full-bleed media — a stat tile is a name, a figure and a line of prose, and the mint's
 * order panel is figures and controls — so there is no band for a radius to square off. The selector
 * says so rather than a comment asking anyone to remember it, and it is here — after `.csp-card` —
 * because source order is what settles the conflict.
 */
/*
 * ⭐ A LIFTED NOTE — the "i"'s, since 2026-09-24 (`tip-live.tsx`, `TipHost`). It is a
 * `popover`, which the browser's own sheet hides with `display: none` while it is closed — and
 * that would take the words out of the accessibility tree, where an `InfoLink`'s label is the
 * link's only name. So it is displayed, and `.csp-tooltip` above clips it to a pixel exactly as
 * before. Open, it is in the top layer and placed by the host, and nothing around it clips it.
 */
.csp-tooltip[popover] { display: block; }
.csp-tip-host { display: inline-flex; min-width: 0; max-width: 100%; }
/* `.csp-info` is LAST in the list on purpose: `css.test.ts` finds a rule by the selector
   that ENDS it, and that test is what holds this rule after the one it overturns. */
.csp-card:has(.csp-infotip),
.csp-card:has(.csp-info) { overflow: visible; }
/*
 * The explanatory host's own box. It shrink-wraps whatever it is hosting — the "i" below, or the
 * caller's own content — and gives the absolutely-positioned label something with a box to sit
 * against. Before `.csp-info`, so a host that is BOTH still takes the mark's size from that rule.
 */
.csp-infotip { display: inline-flex; min-width: 0; max-width: 100%; }
/*
 * THE "i" ITSELF — a ringed mark the size of the label beside it.
 *
 * It is an anchor (`.csp-link` too), so the focus ring, the cursor and the router interception all
 * come from there; this rule is the shape and nothing else. `flex: none` because it sits in a row
 * with a tile's name, which wraps.
 */
.csp-info {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: var(--csp-space-4);
  height: var(--csp-space-4);
  border: 1px solid var(--csp-color-border);
  border-radius: var(--csp-radius-pill);
  color: var(--csp-color-text-secondary);
  text-decoration: none;
}
.csp-info:hover {
  border-color: var(--csp-color-text-primary);
  color: var(--csp-color-text-primary);
}
.csp-info-mark {
  font-size: var(--csp-type-label-size);
  line-height: 1;
  font-weight: var(--csp-fx-label-weight);
}
/*
 * A VALUE THAT COPIES ITSELF — see `primitives/copy.tsx` for why the value is the button.
 *
 * Every property here exists to undo something a `<button>` does by default and nothing else: the
 * UA's own background, border, padding, font and centre alignment, and the `user-select: none` that
 * would make the refusal path's remedy — leaving the value selected to be copied by hand — do
 * nothing. The focus ring comes from the shared `:where()` list at the foot of this sheet.
 *
 * `overflow-wrap: anywhere` because the values these hold are addresses: forty-two characters with
 * no break opportunity in them, which on a phone is wider than the column.
 */
.csp-copy {
  display: inline-flex;
  align-items: center;
  gap: var(--csp-space-2);
  max-width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: start;
  color: var(--csp-color-text-secondary);
  cursor: pointer;
}
.csp-copy:hover { color: var(--csp-color-text-primary); }
.csp-copy-value {
  min-width: 0;
  overflow-wrap: anywhere;
  user-select: text;
}
.csp-copy-mark {
  display: inline-flex;
  flex: none;
  width: var(--csp-space-4);
  height: var(--csp-space-4);
}
.csp-copy-mark svg { width: 100%; height: 100%; display: block; }
/* The tick, once the value is on the clipboard. The only state that changes the mark's colour. */
.csp-copy[data-copy='copied'] .csp-copy-mark { color: var(--csp-color-accent); }
/*
 * A refusal. The value is left SELECTED by the component, which is the remedy; this says so in the
 * mark's colour rather than in a sentence, because the words on this surface are Product's.
 */
.csp-copy[data-copy='failed'] .csp-copy-mark {
  color: var(--csp-color-critical-text);
}
/*
 * The ring INSIDE the box, for this one variant. The header bar is a scroll strip (see the rule
 * below) and a scroll container clips to its padding box, so a ring drawn 2px outside a tab that is
 * exactly the bar's height loses its top and bottom edges. Same width, same colour, still two
 * pixels — only which side of the edge it is drawn on changes.
 */
.csp-link-nav-stacked:focus-visible { outline-offset: -2px; }
/*
 * THE LINK HOME. It holds the brand mark and no label, so it is not a tab and must not wear a
 * tab's underline — which is what it did until spec 101, by sharing `.csp-link-nav` with the nav
 * items.
 *
 * Splitting it off also retires a wart: `.csp-nav-icon` carried a `margin-inline-end` instead
 * of a `gap` PRECISELY because this link shared the class and a gap there would have described a
 * relationship it does not have. It does not share the class any more, so the space lives on the
 * tab, where it belongs.
 *
 * It still needs a `color`, even holding a picture. `.csp-link` declares none, so splitting
 * this off `.csp-link-nav` left the anchor on the BROWSER's default link blue — measured
 * `rgb(0, 0, 238)` on the rendered page, which is not a colour this palette owns. Nothing paints
 * it while the artwork loads; it is the mark's ALT TEXT that renders when the file is missing, and
 * that is not a rare state — every served image is derived from the sibling art checkout, so on a
 * machine without it this link is the site's name in words on the palette's own ground.
 */
.csp-link-brand {
  /* The pivot the shared hover transform depends on. Never a corner or an edge. */
  transform-origin: center;
  display: inline-flex;
  align-items: center;
  color: var(--csp-color-text-secondary);
  transition: transform var(--csp-fx-duration) var(--csp-fx-ease),
    color var(--csp-fx-duration) var(--csp-fx-ease);
}
/*
 * THE NAV ITEM'S ICON — a MASK, not an <img>, and the mechanism is the requirement rather than an
 * implementation detail.
 *
 * The served file is WHITE ink on transparency and it STAYS white. The colour is painted through
 * its alpha here, so it arrives at render time instead of being baked into the pixels. That matters
 * because there are five palettes and the default (`daylight`) is a WHITE canvas while
 * `obsidian` is near-black: a colour baked into a PNG is correct on exactly one ground and wrong
 * the moment the theme changes, and it would put a second copy of a palette colour in the tree for
 * that colour to drift away from. One white master, masked, is right on all five and follows
 * whatever the palette does next.
 *
 * ⭐ **The paint is `currentColor`, so the mark is EXACTLY the colour of its own label.** One
 * declaration, not two states enumerated: the accent ink when the tab is current, the resting text
 * colour when it is not, whatever either of those is retuned to, and on hover it follows the label
 * there too. Spec 102, on the owner's eye — a violet mark over a dark label read as two things
 * stacked rather than one item.
 *
 * That replaces a hard `--color-accent`, which was the FILL role by this file's own rule three
 * comments up (a `color:` is the ink; a background, a border, a shadow or a fill is the accent).
 * The rule still holds and this is still a background-color — it just takes its value from the ink
 * beside it now, because MATCHING THE LABEL is the requirement and a second colour token here
 * would be a copy for the label's colour to drift away from.
 *
 * Both the prefixed and the unprefixed mask longhands are set, exactly as the gradient-border layer
 * above sets both — the mask IMAGE is the only part that differs per icon and comes in as an inline
 * style from `NavIcon`, which is why it is absent here.
 *
 * (!) A SQUARE PX BOX, from `size.navIcon`, and no longer a ratio of the label's font size. The
 * mark sits ABOVE its label now, so what it stays in proportion to is the bar rather than the text
 * beside it — and `size.headerHeight` is derived from this box, which an `em` resolved against
 * an inherited font size could not have given it. The space under the mark is the stacked tab's own
 * `gap`, not a margin here.
 *
 * (!) **The set is NOT optically one size, whatever the recipe says.**
 * `art/masters/icon-px-header-set.md` reports all four as "ink at 80.1% of the canvas"; 80.1% is
 * the frame `art.ts icon` FITTED them into, and in three of the four that frame is filled out to
 * its edge by a near-transparent matte — 40-49% of the canvas at alpha 1-10 — rather than by ink.
 * Measured ink boxes, as a share of the 512px master:
 *
 *     explore 57.8% x 60.2%   holdings 53.9% x 66.0%
 *     leaderboard 44.1% x 40.2%   creator/launch 37.9% x 59.8%
 *
 * So `explore` is the WIDEST-drawn of the four and `creator` the narrowest — a ~1.5x spread a
 * reader sees as inconsistent weight along the nav. Nothing here compensates for it: a per-icon
 * size fudge is exactly the kind of number this file exists not to carry, and redrawing to a common
 * ink box is an Artist job on the masters. Recorded so the next person measures rather than trusts.
 *
 * (!) That matte is not free, because these files are read as MASKS: alpha the eye ignores in a
 * white PNG still paints. It is a faint plate behind three of the four icons, and it is a property
 * of the masters rather than of this rule. The box grew with spec 101 and again with 102, so the
 * plate grew twice — and because the ink boxes above are only 38-60% of the frame, a mark can read
 * as sitting further from its label than the stacked tab's own `gap` says it does. That distance
 * is the ARTWORK. There is no fudge here for it, deliberately.
 *
 * (!) No `image-rendering: pixelated` even though the artwork is pixel art. The served file is 2x
 * the box, so the browser is scaling DOWN, and nearest-neighbour on a downscale drops rows of ink
 * rather than preserving them. The pipeline's own resample is the right one to leave in charge.
 */
.csp-nav-icon {
  flex: none;
  width: var(--csp-size-nav-icon);
  height: var(--csp-size-nav-icon);
  background-color: currentColor;
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
}
/*
 * ⭐ THE BAND — full-bleed, opaque, ruled along its bottom edge.
 *
 * The bar had a fill of its own for one change, and it was on the wrong element. `AppShell`
 * rendered `Container as="header"`, so the painted box WAS the reading column: the ground stopped
 * at `size.containerMax`, the rule under it stopped there too, and the row sat flush against its
 * own paint because a container's gutter is padding on the element the app was painting. The
 * owner: *"it's not white and it doesn't stretch along the whole page … there's also no left/right
 * margin."* Three faults, one structure, and this rule plus `SiteHeader` is the whole of the fix:
 * the BAND spans the page and the CONTENT is contained, which is also what lines the wordmark up
 * with the first heading below it.
 *
 * `surface` and not `surfaceMuted`. Muted was chosen when the fill's only job was to stop the
 * graph paper, and on a light palette it is a 4% veil of the navy ink — `rgb(246,246,248)`, which
 * is grey, and the owner said so. `surface` is the PAPER on a light ground and a panel step above
 * the canvas on a dark one; both are opaque (`solidBackgrounds`, tokens.ts), so the paper still
 * stops here, and what separates the band from the page is the hairline below rather than a tint.
 *
 * The rule is a BORDER rather than a `Divider` element inside the container, for the same reason
 * the fill moved: a divider in the reading column is 1140px of line under an edge-to-edge bar.
 *
 * ⚠️ Its colour is the PALETTE's hairline (`color-border`) and NOT the skin's
 * (`fx-border-color`), which every other bordered box in this file takes. Measured on the shipped
 * default: `base`'s `borderColor` is `litInk(...)`, which is `transparent` on a light ground —
 * a deliberate decision for CARDS, where a white page separates panels by their shadow instead
 * (skins.ts records the audit). It is the wrong decision for this element, and lethally so now that
 * the fill is `surface`: white band, white page, no line, no bar. The `Divider` that used to sit
 * here drew nothing on `daylight` for exactly that reason and nobody noticed, because the grey
 * fill was doing the separating. A band is chrome rather than a panel, and its edge is the
 * palette's, which is a real line on both grounds.
 *
 * No `position: relative` and no glow suppression here — both belong to the scrolling row, and
 * both are still on it. This element does not scroll and therefore clips nothing.
 */
.csp-site-header {
  background-color: var(--csp-color-surface);
  border-bottom: var(--csp-fx-border-width) solid var(--csp-color-border);
}
/*
 * THE BAND THAT STAYS — `SiteHeader sticky`, the admin panel's header (the 2026-09-19 audit,
 * fix 5). The site's own band scrolls away, by the owner's choice, and this class is opt-in so
 * that stays true: a band with no class is exactly what it was.
 *
 * `z-index` because the page under it is cards, and a card is a stacking context of its own
 * (`.csp-card` is `z-index: 0` for its edge layer) — a sticky band with no index would slide
 * UNDER the first card that scrolled up to it. 30 puts it above every in-page layer (the tooltip
 * and the card's edge are 1) and below the floating ones: the speech bubble at 40, the action dock
 * at 45 and the toast at 50, which must all still cover the chrome; a dialog's top layer is above
 * every index there is.
 */
.csp-site-header-sticky {
  position: sticky;
  top: 0;
  z-index: 30;
}
/*
 * THE HEADER BAR — a row with a DECLARED height (spec 101).
 *
 * ## Why a rule rather than a Stack
 *
 * The header used to be a `Stack` with `wrap`, which is `display: flex` and no height, so it
 * was as tall as its tallest child and grew a second line whenever the wallet had something extra
 * to say. It said something extra in the connected-but-not-signed-in state at EVERY width, 1440
 * included, and the item that dropped to line two was the last child — the grid switch. A bar with
 * no height is not a bar. `Container` and `Stack` express no height and feature code may not
 * pass a `className`, so this is the one place the height can be stated.
 *
 * ## How the four children behave
 *
 * `align-items: stretch` gives every child the bar's full height, which is what lets a tab seat
 * its underline on the bar's bottom edge. Each child then centres its own content: the link home is
 * inline-flex with `align-items: center`, the controls are a Stack with `align="center"`, and
 * only the stacked tab actually uses the whole height. (There were four children until the owner
 * moved the grid switch out; there are three.)
 *
 * **Nothing in it shrinks, and the BAR is what gives.** Every child holds its measured width — a
 * control squeezed below the control-height token is a tap target nobody can hit, a squashed nav is
 * a scroll strip with one tab visible in it, and a shrunk brand mark is a smaller link home for no
 * reason. So the row stays its natural width and the bar scrolls SIDEWAYS when the viewport is
 * narrower than it. That is the phone answer, and it is the whole phone answer: one line, nothing
 * hidden, nothing wrapped, nothing behind a tap a stranger would not think to make.
 *
 * ⚠️ **It is the BAR that scrolls, not the nav alone, and that is a measurement rather than a
 * preference.** Spec 101 expected the nav's own strip to absorb the narrow case. It cannot: at
 * 390px the wallet controls plus the grid switch alone want 494-655px depending on the wallet's
 * state (measured, `probe wallet`), so a nav shrunk to nothing still leaves the document
 * scrolling sideways — and `body` deliberately refuses `overflow-x: hidden` two hundred lines
 * up, so there is nothing else to absorb it. Hiding the switch below the breakpoint was the other
 * way out and the owner ruled it out by name. One strip, one line, everything reachable.
 *
 * ⚠️ AMENDMENT: the grid switch is not in this row any more — the owner moved it into the web
 * shell's development panel — so the 494-655px above is a record of the measurement that settled
 * the question, not of what the row wants today. The answer is unchanged and is not a function of
 * that number: the bar scrolls, nothing in it shrinks, and nothing is hidden.
 *
 * `:where()` on the parent so the child rule carries ZERO specificity from the bar's own class. A
 * child that asks for something else through its own utility class — `.csp-grow`, say — still
 * wins, which is how a semantic prop stays the way a caller expresses layout.
 *
 * `position: relative` is load-bearing, not tidiness, and it is the SAME trap
 * `.csp-table-scroll` records further down this file. `.csp-sr-only` is `position: absolute`,
 * and with no positioned ancestor its containing block is the initial one — the document — so a
 * VisuallyHidden label inside this bar is NOT clipped by the bar and drags the whole page sideways
 * instead. Measured at a 390px viewport with the grid switch's own action label in it:
 * documentElement.scrollWidth was 462 against a body scrollWidth of 390, which is exactly how
 * invisible this failure is from the outside. One line, and it is that one.
 *
 * The scrollbar is hidden deliberately, and it is the one thing here worth arguing with. A classic
 * Chromium scrollbar is ~15px tall and it would be drawn INSIDE a bar that is exactly the height of
 * the tabs in it — over the underline it exists to reveal. On a phone, which is the width this is
 * for, the scrollbar is an overlay and there is nothing to hide. The bar is reachable by keyboard
 * and it is in the focus-ring list below, which is the half that actually matters.
 *
 * ## ⭐ NOTHING IN THE BAR CASTS A GLOW, and that is spec 102's answer to the cropped shadows
 *
 * A scroll container clips its OWN children at its padding box. That is not something a wrapper can
 * fix — an inner scrolling element inside a non-clipping outer one clips exactly the same children —
 * so as long as the bar scrolls, anything painted outside a child's border box is cut at the bar's
 * edge. Measured on the shipped default (`vault` on `daylight`): the connect button carries
 * `0 14px 34px` and reaches 48px below its own bottom edge, of which 32px is sliced off in a hard
 * horizontal line. That line is what the owner reported, and it is why he asked for a rule under the
 * bar to explain it.
 *
 * So the glow goes, inside the bar only. One declaration, no structure, and no allowance number:
 * the alternative was a transparent box hanging ~64px over the top of every page, sized by whichever
 * skin declares the largest glow (vault's hover reaches 74px on its own) — a per-skin magic number,
 * which is the exact kind of value this file exists not to carry.
 *
 * The consequence is deliberate. A fixed bar with a hard rule under it is CHROME, and chrome does
 * not lift off the page the way a card does. `0 0 0 0 transparent` and not `none`, for the
 * reason `fx.glow` itself records: these are composed into a `box-shadow` LIST beside
 * `innerLight`, and one `none` in a list invalidates the whole list. The inset hairline on a
 * control therefore survives — it is inside the box, so it was never what cropped.
 */
.csp-header-bar {
  --csp-fx-glow: 0 0 0 0 transparent;
  --csp-fx-glow-hover: 0 0 0 0 transparent;
  position: relative;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  justify-content: space-between;
  gap: var(--csp-space-3);
  height: var(--csp-size-header-height);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.csp-header-bar::-webkit-scrollbar { display: none; }
:where(.csp-header-bar) > * { flex: none; }
/*
 * THE COMPACT BAR — `HeaderBar size="compact"`. The same row with the shorter of the two declared
 * heights, for a header whose tabs are words rather than marks: `size.headerHeightCompact` is the
 * control-plus-padding term of the full height's sum with the stacked mark left out. Everything
 * else about the row — the stretch, the scroll, the glow suppression — is the rule above.
 */
.csp-header-bar-compact { height: var(--csp-size-header-height-compact); }
/*
 * THE TAB ROW — `HeaderBar size="tabs"`: the second row of a two-tier header, holding the
 * underlined section tabs and nothing else (the admin panel, 2026-09-19). The same strip — it
 * stretches its children so a tab's underline seats on its bottom edge, it scrolls sideways at a
 * phone width rather than wrapping, and it casts no glow — at the height of a `nav` tab itself:
 * `size.controlHeightSm` is that tab's `min-height`, so the row is as tall as its tabs and
 * not a pixel taller, and it carries no vertical padding of its own. The rule under it is the
 * band's (`.csp-site-header`), which is the row's bottom edge when it is the band's last child.
 */
.csp-header-bar-tabs { height: var(--csp-size-control-height-sm); }
.csp-link-inline {
  color: var(--csp-color-accent-ink);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
/* Element+class, so it beats the variant colour rules without needing !important. */
a.csp-button { text-decoration: none; }
/* The same statement for the link-shaped switch: ToggleLink is an anchor in Toggle's classes. */
a.csp-toggle { text-decoration: none; }

/*
 * A scrollable strip is focusable whether we ask for it or not: Chromium puts a scroll container in
 * the tab order so a keyboard user can actually scroll it, and hands it the browser's default ring,
 * which computes to 1px. So every one of ours is listed here with the controls — otherwise the
 * elements a keyboard user MUST reach on a phone are the ones wearing a ring we did not draw. There
 * are FOUR: a wide table's strip, the header bar, the dashboard's rail of live corps, and a
 * modal's body — the same answer to the same problem in four places.
 *
 * The fourth joined the list the day the dialog stopped scrolling as one piece: a box that scrolls
 * its own content under a pinned footer IS a scroll container, and on a phone the introduction's
 * second page really does overflow it.
 */
:where(
  .csp-button,
  .csp-copy,
  .csp-field,
  .csp-link,
  .csp-card-link,
  .csp-picture-button,
  /* The option card: a button wearing none of `.csp-button`'s classes, like the dropped row below. */
  .csp-option-card,
  /* The explanatory tooltip's host. Not a link and not a control, so it is in no other list here —
     and it is reachable by keyboard, which is the whole reason its label answers :focus-visible. */
  .csp-infotip,
  .csp-table-scroll,
  .csp-header-bar,
  .csp-carousel,
  .csp-modal-body,
  .csp-checkbox-input,
  .csp-toggle,
  /* A dropped panel's row: a button wearing none of `.csp-button`'s classes, so named here. */
  .csp-dropdown-option
):focus-visible {
  outline: 2px solid var(--csp-color-focus-ring);
  outline-offset: 2px;
}

/*
 * The verified mark. Its own element rather than a glyph in the label, so the name stays truncatable
 * and the mark keeps its accessible name when the text beside it is cut off.
 */
.csp-verified {
  display: inline-flex;
  flex: none;
  width: var(--csp-space-4);
  height: var(--csp-space-4);
  /*
   * The FILL, and the one `color:` on this page that is not the ink role.
   *
   * It carries no glyph: the mark is a filled disc (fill: currentColor, in nft.tsx) with the tick
   * cut into it in --color-accent-text, so this property is a conduit to a fill and the pair that
   * has to read is tick-on-disc, which is the accent's own guarantee. Handing it the ink would
   * darken the disc under a tick chosen for the fill.
   */
  color: var(--csp-color-accent);
}
.csp-verified svg { width: 100%; height: 100%; display: block; }

.csp-progress-track {
  width: 100%;
  height: var(--csp-size-progress-height);
  border-radius: var(--csp-radius-pill);
  background: var(--csp-color-surface-muted);
  overflow: hidden;
}
/*
 * (!) `display: block` is load-bearing, not tidiness. A <span> is an inline box, and width and
 * height do not apply to one -- so this painted a 0x0 nothing at every value and every progress bar
 * in the product read as an empty track. See ProgressBar's own note.
 *
 * min-width on the STARTED modifier alone: a bar at 0% must stay empty, and a bar at 2.4% must not
 * be invisible because the track happens to be narrow. One track height is a square of accent -- the
 * smallest mark the pill radius can still draw.
 */
.csp-progress-fill {
  display: block;
  height: 100%;
  border-radius: var(--csp-radius-pill);
  background: var(--csp-color-accent);
}
.csp-progress-fill-started { min-width: var(--csp-size-progress-height); }
/* A bar measured against a LIMIT wears its verdict (`ProgressBar tone`); the default stays accent. */
.csp-progress-fill-critical { background: var(--csp-color-critical-text); }
.csp-progress-fill-caution { background: var(--csp-color-caution-text); }
.csp-progress-fill-success { background: var(--csp-color-success-text); }

/*
 * THE STATUS MARK -- `primitives/status.tsx`. One label-sized square per state; the drawing is
 * `currentColor`, so the tone below is the whole of its colour. `flex: none` because it sits at
 * the start of a row whose text may be squeezed, and a squeezed mark is an unreadable one.
 *
 * Each state takes the INK of its role, never its surface: the mark is a small solid shape on
 * whatever ground the row has, and the inks are the steps each palette guarantees read on it.
 */
.csp-status {
  display: inline-flex;
  flex: none;
  width: var(--csp-space-4);
  height: var(--csp-space-4);
  vertical-align: middle;
}
.csp-status svg { width: 100%; height: 100%; display: block; }
.csp-status-critical { color: var(--csp-color-critical-text); }
.csp-status-caution { color: var(--csp-color-caution-text); }
.csp-status-unknown { color: var(--csp-color-accent-ink); }
.csp-status-ok { color: var(--csp-color-success-text); }
/* The critical mark's "!" is cut out of its fill in the ground colour, so it reads as one sign. */
.csp-status-knock { stroke: var(--csp-color-surface); stroke-width: 1.75; }
.csp-status-knock-dot { fill: var(--csp-color-surface); stroke: none; }

/*
 * A LINE PLOT — `primitives/plot.tsx`. The plot's own ground, which is the one surface here that
 * does not flip with the palette (see `plotGround` in tokens.ts), at the card's radius so it sits
 * in a card the way the corp page's chart does. The svg is the box's width and `size.plotHeight`
 * tall, and its drawing space is stretched to that box; every stroke below is non-scaling, so
 * these widths are the widths that paint. `overflow: visible` so a mark on the very last column
 * is not clipped by its own box.
 *
 * The series is drawn in `plotSeriesOne` — the first of the three the chart's key names — and the
 * mark on the latest reading is the same ink. A zero-length round-capped stroke is how the mark is
 * a disc at any stretch; its width is the disc's diameter. A comparison series, when the plot has
 * one, is the second ink at the same weight, drawn UNDER the series it is read against.
 */
.csp-plot {
  padding: var(--csp-space-4);
  border-radius: var(--csp-fx-card-radius);
  background: var(--csp-color-plot-ground);
}
.csp-plot-svg {
  display: block;
  width: 100%;
  height: var(--csp-size-plot-height);
  overflow: visible;
}
.csp-plot-axis {
  stroke: var(--csp-color-plot-ink-muted);
  stroke-opacity: 0.55;
  stroke-width: var(--csp-size-border-width);
}
.csp-plot-line {
  fill: none;
  stroke: var(--csp-color-plot-series-one);
  stroke-width: var(--csp-size-plot-stroke);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.csp-plot-line-comparison {
  fill: none;
  stroke: var(--csp-color-plot-series-two);
  stroke-width: var(--csp-size-plot-stroke);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.csp-plot-mark {
  stroke: var(--csp-color-plot-series-one);
  stroke-width: var(--csp-size-plot-mark);
  stroke-linecap: round;
}

.csp-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  overflow: hidden;
  background: var(--csp-color-surface-muted);
  border: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  color: var(--csp-color-text-secondary);
  border-radius: var(--csp-radius-pill);
}
.csp-avatar-sm { width: var(--csp-size-avatar-sm); height: var(--csp-size-avatar-sm); }
.csp-avatar-md { width: var(--csp-size-avatar-md); height: var(--csp-size-avatar-md); }
.csp-avatar-lg { width: var(--csp-size-avatar-lg); height: var(--csp-size-avatar-lg); }
.csp-avatar-xl { width: var(--csp-size-avatar-xl); height: var(--csp-size-avatar-xl); }
.csp-avatar-square { border-radius: var(--csp-fx-control-radius); }
.csp-avatar img { width: 100%; height: 100%; object-fit: cover; }
.csp-avatar-monogram {
  font-size: var(--csp-type-label-size);
  font-weight: var(--csp-type-label-weight);
  letter-spacing: 0;
}

/*
 * The brand mark. Height from the token, width from the artwork -- see BrandMark for why only one
 * axis is set. flex: none so it keeps its size in the header's wrapping row rather than being
 * squeezed by a long nav.
 */
.csp-brand-mark {
  display: block;
  height: var(--csp-size-brand-mark);
  width: auto;
  flex: none;
}

/*
 * Data tables — the leaderboard, the referral list, the fee history.
 *
 * Cells never wrap; the table scrolls sideways INSIDE its own strip instead. That is what keeps a
 * five-column table off a 390px phone's horizontal scrollbar, and the overflow probe knows the
 * difference: a node inside a deliberately scrollable strip is fine, a node whose scroll parent is
 * the document is a bug.
 *
 * position: relative on the strip is load-bearing, not tidiness. (And no backticks anywhere in
 * these comments: they live inside a template literal, and one would end the string.)
 *
 * Table renders its accessible name as a VisuallyHidden span inside a caption, and
 * .csp-sr-only is position: absolute. With no positioned ancestor its containing block was
 * the document, so the hidden span was NOT clipped by this strip -- a caption is centre-aligned,
 * and on a table wider than the viewport its static position lands past the right edge and drags
 * the whole document sideways. Measured on a 1137px fee ledger in a 358px strip:
 * documentScrollWidth went from 390 to 584 at a 390px viewport. One line, and it is this one.
 */
/*
 * overflow-y: hidden is NOT redundant beside overflow-x: auto. Setting one axis to anything but
 * visible computes the other to auto, so this strip grew a VERTICAL scrollbar the moment it sat in
 * a height-capped box -- which is the dashboard's leaderboard, and the scrolling the owner asked
 * twice to be rid of. A table strip scrolls sideways and only sideways; a panel that has been given
 * less height than its rows need clips them, and puts its way through underneath.
 *
 * It is inert anywhere the strip is free to be as tall as its table, which is every other caller.
 */
.csp-table-scroll {
  position: relative;
  width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
}
.csp-table { width: 100%; border-collapse: collapse; }
/*
 * A table whose columns are told their widths (`TableColumn.width`) lays them out from those
 * widths and not from its contents, so two tables that swap places on one page — the leaderboard's
 * three boards — keep every column edge where it was. Auto layout re-measured the columns on each
 * switch: the rank column went 182 → 173 → 192px with the widest rank on the board, and every cell
 * jumped sideways. The owner: *"changing the leaderboard changes the spacing … unnecessarily."*
 */
.csp-table-fixed { table-layout: fixed; }
.csp-table th,
.csp-table td {
  /* The vertical step is a token, so a cell's padding and the rows above it cannot drift apart. */
  padding: var(--csp-space-3) var(--csp-space-4);
  text-align: left;
  vertical-align: middle;
  white-space: nowrap;
  border-bottom: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
}
.csp-table thead th { background: var(--csp-color-surface-muted); }
.csp-table tbody tr:last-child td { border-bottom: 0; }
/*
 * THE DENSE TABLE — `Table density="compact"` (the 2026-09-19 panel audit, fix 7). One step less
 * padding on each axis: the admin panel's rows were 49px on 14px type, and a table an operator
 * scans down wants ~36. The site's own tables are untouched — this is opt-in, and it changes the
 * padding and nothing else, so a compact row's rule and hairline are exactly a regular row's.
 */
.csp-table-compact th,
.csp-table-compact td {
  padding: var(--csp-space-2) var(--csp-space-3);
}
.csp-table-end { text-align: right; }
.csp-table-span { text-align: center; white-space: normal; }
/* Table wrap: prose cells. Sentences wrap and a row reads from its top edge, not its middle. */
.csp-table-wrap th,
.csp-table-wrap td { white-space: normal; vertical-align: top; }
.csp-table-row-highlight { background: var(--csp-color-accent-subtle); }
.csp-table-row-caution { background: var(--csp-color-caution-surface); }
/*
 * A column whose text stops at its edge (`TableColumn.truncate`). The cell is the box that clips,
 * so the ellipsis lands on its inline content whatever primitive rendered it; it needs the fixed
 * layout a `width` switches on, because an auto-layout cell grows to its text before it clips it.
 */
.csp-table-truncate { overflow: hidden; text-overflow: ellipsis; }
/* `Table scrollBelow="wide"`: narrower than this, the strip above scrolls instead of the columns colliding. */
.csp-table-min-wide { min-width: var(--csp-size-table-wide); }

/*
 * A callout says one thing that must be read before an action — above all "this is permanent".
 * Product's rule is that irreversibility is stated BEFORE the button, in plain words, so the
 * component is deliberately loud and deliberately not a footnote.
 */
.csp-callout {
  display: flex;
  flex-direction: column;
  gap: var(--csp-space-2);
  min-width: 0;
  padding: var(--csp-space-4);
  border-radius: var(--csp-fx-card-radius);
  border: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  background-color: var(--csp-color-surface);
  background-image: var(--csp-fx-surface-image);
  /*
   * NO box-shadow here, deliberately, and this is the one place the fx layer had to be talked out
   * of a default.
   *
   * A card carries an inset hairline in the base sheet, so routing it through --fx-inner-light
   * reproduces what was already painted. A callout never had one — and the fx default resolves to
   * that same hairline, so borrowing the card's line would have put a white top edge on every
   * callout on /create, every mint page, the activation page and the creator flow. Nothing measures
   * box-shadow, so it would have shipped silently.
   *
   * (No slug wildcard written out above: a star-slash inside a block comment ends the comment, and
   * this file is one long template literal where that produces a syntax error two hundred lines
   * later. The same trap as the no-backticks note further up.)
   *
   * A skin that wants a lit callout opts in from its own sheet: litCallout, in skins.ts.
   */
}
.csp-callout-accent {
  border-color: var(--csp-color-accent-border);
  background: var(--csp-color-accent-subtle);
}
.csp-callout-caution {
  border-color: var(--csp-color-caution-border);
  background: var(--csp-color-caution-surface);
}
.csp-callout-critical {
  border-color: var(--csp-color-critical-border);
  background: var(--csp-color-critical-surface);
}

/** Multi-step flows: sign-up and the creator wizard. One row of pills, wrapping on a phone. */
.csp-stepper {
  display: flex;
  flex-wrap: wrap;
  gap: var(--csp-space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}
.csp-step {
  display: inline-flex;
  align-items: center;
  gap: var(--csp-space-2);
  min-height: var(--csp-size-control-height-sm);
  padding-inline: var(--csp-space-3);
  border-radius: var(--csp-radius-pill);
  border: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  background-color: var(--csp-color-surface);
  background-image: var(--csp-fx-surface-image);
  color: var(--csp-color-text-muted);
}
.csp-step-done { color: var(--csp-color-text-secondary); }
.csp-step-current {
  border-color: var(--csp-color-accent-border);
  background: var(--csp-color-accent-subtle);
  color: var(--csp-color-accent-strong);
}

/*
 * The other end of the same idea: where you are, in DOTS, for a flow short enough that naming its
 * steps would say more than the reader wants. The introduction dialog's footer is two of them.
 *
 * The dots carry no text, so the GROUP is named by its `aria-label` and each dot is `aria-hidden`
 * — a screen reader gets one sentence instead of two bullets it cannot tell apart.
 */
.csp-dots {
  display: flex;
  align-items: center;
  gap: var(--csp-space-2);
}
.csp-dot {
  width: var(--csp-size-page-dot);
  height: var(--csp-size-page-dot);
  border-radius: var(--csp-radius-pill);
  background-color: var(--csp-color-border-strong);
}
.csp-dot-current { background-color: var(--csp-color-accent); }

/*
 * The art that does not exist yet. Product's wireframes carry a labelled box for every image, and
 * so does this — a dashed well that says what belongs there, never a grey rectangle pretending to
 * be a picture. Ratios are unitless, so no size token is invented for it.
 */
.csp-art {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  overflow: hidden;
  padding: var(--csp-space-4);
  text-align: center;
  border-radius: var(--csp-fx-art-radius);
  border: var(--csp-fx-border-width) dashed var(--csp-color-border-strong);
  background: var(--csp-color-surface-sunken);
}

/*
 * The art that DOES exist. Same ratio classes as the well above, so the two are interchangeable in
 * a slot, and no dashed border and no sunken ground: those two say "undecided", and a picture that
 * wore them would read as a picture inside a placeholder.
 *
 * object-fit: cover, centred -- the same crop .csp-avatar img and .csp-nft-art img take.
 * The ratio is the caller's, so the box is never the file's shape, and the crop is the point: a slot
 * that has to line up in a grid with its neighbours is a fixed shape whatever the file's shape is.
 * The hero is the opposite case and has its own rule below (.csp-hero-art), which keeps the
 * file's aspect and crops nothing. display: block on the image kills the inline baseline gap that
 * would otherwise show as a sliver of the box under the picture.
 *
 * (No backticks in this file's comments: it IS a template literal, and one terminates the string.)
 */
.csp-art-image {
  width: 100%;
  overflow: hidden;
  border-radius: var(--csp-fx-art-radius);
}
/*
 * img AND video, on one rule rather than two. A ratio box holds a picture or a clip, and which one
 * a reader gets is decided at run time by prefers-reduced-motion (ArtVideo swaps the still in) --
 * so the two must be laid out identically or the card's artwork would move the moment the clip
 * arrives. One selector is what makes that structural instead of a pair to keep in step.
 */
.csp-art-image img,
.csp-art-image video { display: block; width: 100%; height: 100%; object-fit: cover; }
/*
 * fit="contain" -- the WHOLE picture inside the box, letterboxed rather than cropped.
 *
 * The note on ArtImage argued against exactly this and it was right for artwork slots: a cover crop
 * is what makes a row of differently shaped pictures line up. The owner overruled it for the two
 * slots where the point is to SEE THE WHOLE FILE -- a reference image you are about to draw from,
 * and the preview of one item -- "should be sized to fit all part of the image - in a square
 * aspect". A crop there hides the thing being judged.
 *
 * ⛔ It is opt-in and stays opt-in. Every card, tile and hero keeps the cover, because a grid of
 * contained pictures is a grid of different-sized pictures floating in bars.
 */
.csp-art-fit-contain img,
.csp-art-fit-contain video { object-fit: contain; }
/*
 * .csp-art-thumb is GONE, with ArtImage's `size` prop and size.artThumb. It pinned a picture
 * to 64px beside its words, for the dashboard introduction's three beats, and that dialog shows
 * those beats' own clips at the column's full width now. A rule whose only caller has left is a
 * width nothing sets -- deleted rather than kept for a case that no longer happens, on exactly the
 * reasoning that removed .csp-hero-panel below.
 */

/*
 * Text ON the artwork -- ArtOverlay. The picture at the top at ITS OWN shape, the words over its
 * lower part, and a gradient between them.
 *
 * ## The picture is CROPPED BY NOTHING, and that is the whole reason this box changed shape
 *
 * It used to fill the box: position: absolute; inset: 0 on the media layer, object-fit: cover, and
 * a box whose height came from the flex parent. That crops -- 74px off the width of a 576x528 hero
 * squeezed into a 638x653 dialog, which is the lettering down its left and right edges -- and the
 * owner's ask was the opposite: "I was thinking it could be a way to keep from cropping the image
 * so much... show more of the image and have the text sort of overlapping that."
 *
 * So the media is a grid item at width: 100%, height: auto. Its height is the FILE's, there is no
 * aspect-ratio written down anywhere (the masters are generated art and their shape is theirs to
 * change), and nothing is ever cut off any edge.
 *
 * ## The two items share ONE grid cell, and the box is the taller of them
 *
 * grid-area: 1 / 1 on both, the picture aligned to the top and the words to the bottom. So the row
 * -- and therefore this box -- is max(picture, words), which is the whole placement rule and it
 * needs no number:
 *
 *  - a picture TALLER than its words (the dialog's first page: 585px of hero against 246px of
 *    pitch) gives a box the picture's own height, with the words sitting in its foot;
 *  - words TALLER than their picture (the dialog's three beats: a 200px square against 524px of
 *    heading and paragraph) give a box the words' height, with the picture across its top and the
 *    words running on past it, over the panel's own ground.
 *
 * There is deliberately no aspect-ratio and no height on this box. A ratio box that is also
 * overflow: hidden has an automatic minimum size of ZERO -- min-height: auto resolves to 0 on a
 * scroll container -- so the ratio would win over the copy and the copy would be silently clipped.
 * That is not a hypothetical: an early build put the three beats in 202px squares and cut the top
 * off every paragraph, with nothing in the markup or the type-checker to say so.
 *
 * flex: 0 0 auto is for the flex parent it sits in (the dialog's body): it takes its own height and
 * neither grows into the room a steady box has spare nor shrinks below its own content. Growing is
 * what made it crop; the leftover room is the parent's to distribute, and .csp-modal-body centres
 * it.
 *
 * ## Three LENGTHS, and every one of them is measured from the top of the TEXT block
 *
 * A percentage would land somewhere different on every one of these boxes, because the copy sets
 * their heights and copy is different lengths. So:
 *
 *  - --art-over-ride is how far the first glyph rides UP onto the picture past where the fade
 *    finishes. Zero by default, which is the first page: the words are shorter than the picture, so
 *    the grid has already put them in its foot and they overlap it by their own height.
 *  - --art-over-ramp is the distance the picture has to dissolve over, and it is the LAST stretch
 *    before the first glyph. Everything above it is untouched artwork -- which is what keeps a
 *    scrim off the top of a 200px square whose words are 500px long. The gradient holds transparent
 *    for --art-over-ride, then runs to the scrim over --art-over-ramp, and padding-top is the sum
 *    of the two, so the first glyph starts exactly where the ramp finishes.
 *  - --art-over-scrim is what the picture fades INTO, and it is deliberately NOT opaque. The words
 *    are meant to sit on the artwork and the artwork is meant to stay visible through them; the
 *    scrim is what buys the contrast that makes both true at once. See below.
 *
 * background-origin is padding-box by default and there is no border here, so those offsets are
 * measured from this element's own top edge.
 *
 * ## The scrim's alpha is a MEASUREMENT, not a taste value
 *
 * Text over a picture cannot be contrast-audited by dom-checks.ts -- it composites background
 * COLOURS up the ancestor chain and cannot see a photograph, and it walks straight past this
 * gradient to the opaque panel behind it. So this pair is measured from RENDERED PIXELS instead,
 * the way scripts/pixel-contrast.ts does it, at five phases of every clip on the page, keeping each
 * text node's WORST reading.
 *
 * At 80% the dashboard introduction reports 0 pairs under AA on both of its pages. The tightest is
 * the beats' 11px accent step number, which is the smallest type over the busiest artwork: 7.32
 * against a 4.5 bar on the favourable bound, 5.64 on the median. The pitch laid over the hero -- the
 * one place where a whole paragraph sits on the picture -- reads 11.85 favourable and 10.61 median.
 *
 * That headroom is what the alpha buys and it is not spare change to spend on a thinner scrim: the
 * clips are pixel art whose lower thirds carry black ticker tiles and navy lettering, so the ground
 * under a given glyph depends on WHEN you look, which is why the reading is the worst of five
 * phases rather than of one screenshot.
 *
 * Mixed off --csp-color-canvas rather than --csp-color-surface because canvas is the one ground
 * token that is OPAQUE in every palette. surface is a veil (hsl(... / 0.05)) on the four dark ones,
 * so a scrim built from it would be ~5% of nothing over the artwork and the words would be sitting
 * on bare pixel art -- readable on the light palette this ships as the default and unreadable the
 * moment ?palette= names another.
 */
.csp-art-over {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  flex: 0 0 auto;
  width: 100%;
  min-width: 0;
  overflow: hidden;
  border-radius: var(--csp-fx-art-radius);
  --csp-art-over-ride: 0px;
  --csp-art-over-ramp: var(--csp-space-16);
  --csp-art-over-inset: var(--csp-space-5);
  --csp-art-over-scrim: color-mix(in srgb, var(--csp-color-canvas) 80%, transparent);
}
/*
 * The picture, at its own proportion, across the top of the cell. height: auto is the declaration
 * that does the work -- it is what makes the box the FILE's shape rather than the file the box's.
 * The radius is this box's, so a media element that carries one of its own (every ratio box does)
 * does not draw a second rounded corner inside a rounded corner.
 */
.csp-art-over-media { grid-area: 1 / 1; align-self: start; min-width: 0; }
.csp-art-over-media > * {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 0;
}
.csp-art-over-text {
  grid-area: 1 / 1;
  align-self: end;
  padding: var(--csp-art-over-inset);
  padding-top: calc(var(--csp-art-over-ride) + var(--csp-art-over-ramp));
  background-image: linear-gradient(
    to bottom,
    transparent 0,
    transparent var(--csp-art-over-ride),
    var(--csp-art-over-scrim)
      calc(var(--csp-art-over-ride) + var(--csp-art-over-ramp))
  );
}
/*
 * The words are TALLER than the picture here, so the grid has already stacked them from this box's
 * top and they would run over the whole square unless something said where to start. The ride is
 * that: space-20 of untouched artwork, then the ramp, then the first glyph -- which puts the words
 * on the picture's bottom 56px and leaves its top 80px clean.
 *
 * The radius goes with it. This variant is a full-bleed panel: the card it fills carries padding 0
 * and clips to its OWN radius, so a second one here would be a rounded rectangle inside a rounded
 * rectangle with a hairline of card between them.
 *
 * And the inset comes down a step, to the one a Card's own padding uses. The point of the full
 * bleed is that the PICTURE gets the width the card's padding used to take; the words are supposed
 * to wrap to exactly the measure they wrapped to before it, and at space-5 they did not -- they
 * came out narrower than the padded card's and the tallest beat gained a line. Same step, so the
 * inset simply moved from the card to here.
 */
.csp-art-over-foot {
  border-radius: 0;
  --csp-art-over-ride: var(--csp-space-20);
  --csp-art-over-inset: var(--csp-space-4);
}

/*
 * The hero: text BESIDE the picture, in two grid cells that never touch.
 *
 * It used to be ONE cell holding two layers, and that single decision forced both of the defects
 * the owner reported. Text sitting on the picture meant the picture had to fill a band, so it was
 * cropped to a ratio; and text on a picture cannot be contrast-audited (the audit composites
 * background COLOURS up the ancestor chain and cannot see a photograph), so the text needed an
 * opaque panel under it. Two cells, and both workarounds stop being needed at once: the picture is
 * shown whole because nothing has to sit on it, and the headline is measured against the page's own
 * background because that is what is now behind it.
 *
 * .csp-hero-panel is GONE, not made transparent. Its only reason to exist was text-over-
 * picture; a scrim nobody can see, kept for a case that no longer happens, is worse than no scrim.
 *
 * Below md the two cells stack into one column and the DOM order is what renders: art first, then
 * text. That order is the reference's and it is also the right one -- an illustration is what says
 * what a page is at a glance.
 *
 * At md the columns appear, and PLACEMENT is what moves the art rather than DOM order, so the
 * markup reads the same whichever side the caller picks. align-items: center is the reference's
 * alignment: the headline block sits against the MIDDLE of the illustration, not its top.
 */
.csp-hero {
  display: grid;
  width: 100%;
  min-width: 0;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--csp-space-6);
}
.csp-hero-media { min-width: 0; }
.csp-hero-body { min-width: 0; display: flex; align-items: center; }
@media (min-width: 768px) {
  .csp-hero {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    gap: var(--csp-space-10);
  }
  .csp-hero-body { grid-row: 1; grid-column: 1; }
  .csp-hero-media { grid-row: 1; grid-column: 2; }
  .csp-hero-art-left .csp-hero-body { grid-column: 2; }
  .csp-hero-art-left .csp-hero-media { grid-column: 1; }
}

/*
 * The hero's picture, shown WHOLE.
 *
 * No ratio box: width follows the column, height follows the file, so the rendered box is always the
 * artwork's own aspect and there is nothing to crop and nothing to letterbox. That is why this is
 * HeroArt and not a mode of ArtImage -- ArtImage's shape IS the ratio box, and contain inside one
 * would be the letterbox rather than the fix.
 *
 * object-fit: contain does nothing while height is auto, and it is declared anyway: it is the
 * standing instruction for what to do if either axis is ever constrained -- fit the whole picture,
 * never cut it -- and it is the exact opposite of the cover on .csp-art-image above.
 *
 * display: block kills the inline baseline gap that would show as a sliver under the picture.
 */
.csp-hero-art {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
  border-radius: var(--csp-fx-art-radius);
}

/*
 * TWO TOKENS, TETHERED — one figure rather than two cards beside each other.
 *
 * The difference is the rope. Two cards state that a page knows about two tokens; this states that
 * one is anchored to the other, which is the whole premise of a corp and the reason the mint
 * page opens on it. So the rope is drawn BETWEEN the marks, at their own centre line, and the
 * chain link sits on it — the link is what makes the line read as a tether rather than as a rule
 * somebody forgot to remove.
 *
 * ## Why the rope is a pseudo-element and the link is an SVG
 *
 * The rope has to span whatever the flex row leaves between two marks, which is a length nothing
 * knows until it is laid out; `inset-inline: 0` on an absolute child is the one way to say "all of
 * it" without a number. The LINK cannot stretch — a chain link scaled on one axis is an oval — so it
 * is a fixed-aspect drawing centred on the rope, and `preserveAspectRatio` keeps it round at every
 * width. The two are the same colour and read as one figure.
 *
 * ## The marks are the size of a subject, not of a portrait
 *
 * `size.avatarXl` — the size this system already shows a corp's own mark at on the page
 * ABOUT that corp. The three smaller avatar sizes are portraits inside a line of text, and
 * this figure is the thing the page is about.
 *
 * ## The basis is the MARK's width, not a column's
 *
 * `flex-basis: size.avatarXl` on each end. A basis wide enough to hold an address on one line would
 * wrap this figure into three rows inside the hero's own column, which is about 520px on a desktop —
 * so the ends start at the width of the mark itself, grow into whatever the row has, and the address
 * under them wraps instead (`overflow-wrap: anywhere`: forty-two characters with no break
 * opportunity in them, the same value `.csp-copy-value` carries for the same strings). Narrower than
 * the three of them together and the row wraps to mark / rope / mark, which still reads as a tether.
 */
.csp-tether {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: var(--csp-space-4);
  width: 100%;
  min-width: 0;
}
.csp-tether-end {
  display: flex;
  flex: 1 1 var(--csp-size-avatar-xl);
  flex-direction: column;
  align-items: center;
  gap: var(--csp-space-3);
  min-width: 0;
  text-align: center;
  overflow-wrap: anywhere;
}
/*
 * The mark. A ring and a surface under it, so a token with no artwork behind its URL is a disc
 * rather than a broken-image glyph — the same failure `CSuiteArt` guards at the app's end.
 */
.csp-tether-disc {
  display: block;
  flex: none;
  width: var(--csp-size-avatar-xl);
  height: var(--csp-size-avatar-xl);
  overflow: hidden;
  border: 1px solid var(--csp-color-border);
  border-radius: var(--csp-radius-pill);
  background-color: var(--csp-color-surface-muted);
}
.csp-tether-disc img { display: block; width: 100%; height: 100%; object-fit: cover; }
.csp-tether-link {
  position: relative;
  display: flex;
  flex: 1 1 var(--csp-space-10);
  align-items: center;
  justify-content: center;
  align-self: flex-start;
  height: var(--csp-size-avatar-xl);
  min-width: var(--csp-space-10);
  color: var(--csp-color-border);
}
/* The rope: the full span the row leaves, on the marks' own centre line. */
.csp-tether-link::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(50% - 1px);
  height: 2px;
  background-color: currentColor;
}
.csp-tether-link svg {
  position: relative;
  display: block;
  height: var(--csp-space-6);
  width: auto;
}

/*
 * The banner header: a wide band, the corp's mark overlapping its lower edge, the title block
 * under both. The shape a marketplace profile page has, and the shape /c/[slug] opens on now.
 *
 * ## The band holds no picture, and it is not a placeholder either
 *
 * There is no wide artwork anywhere in this product and no field on a corp to put one in, so
 * the band is painted from the surface and skin tokens the way every other panel is -- not a dashed
 * well saying a decision is outstanding, and emphatically NOT the square corp SVG stretched
 * across three times its width. BannerHeader carries a media slot for the day real banner art
 * exists; nothing passes it today. See that component for the whole of that decision.
 *
 * ## Why a negative margin and not absolute positioning
 *
 * Same technique, same reason, as the note on .csp-coll-avatar: the overlap has to survive an
 * ancestor that clips to its own radius, and an absolutely-positioned mark gets clipped along with
 * the band. In normal flow the mark also still claims its own height, so the title block under it
 * cannot collide with it at any width.
 *
 * ## One column, at every width
 *
 * A 96px mark with a display-size corp name beside it wraps on a phone whatever a breakpoint
 * says, so there is no second arrangement to keep true: mark, then title block, always. This block
 * is also the one thing on the corp screens that is full container width -- the prose under it
 * stays capped, and widening it was never part of showing the artwork.
 */
.csp-banner { display: flex; flex-direction: column; width: 100%; min-width: 0; }
.csp-banner-band {
  width: 100%;
  /* border-box globally (see the reset), so the MEASURED box is 3:1 and not 3:1 plus two borders. */
  aspect-ratio: 3 / 1;
  overflow: hidden;
  border-radius: var(--csp-fx-art-radius);
  border: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  background-color: var(--csp-color-surface-sunken);
  background-image: var(--csp-fx-surface-image);
}
/* The media slot fills the band, because the band's shape is the one the caller asked for. */
.csp-banner-band > * { width: 100%; height: 100%; }
.csp-banner-lead {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--csp-space-4);
  min-width: 0;
}
.csp-banner-mark { margin-top: calc(var(--csp-size-avatar-xl) * -0.5); }
.csp-banner-body { width: 100%; min-width: 0; }

/*
 * THE GRADIENT OUTLINE round one of the two pictures -- BannerHeader's `outlined`, and the launch
 * step's answer to "which picture does this panel of controls belong to". A panel points at its
 * picture; under the pointer the picture says so back.
 *
 * ## Why it is not the gradient-border layer above
 *
 * Same technique -- a pseudo-element filled with a gradient and masked down to its own ring, which
 * is the only way to get a gradient edge that follows a border-radius. NOT the same paint: that
 * layer draws `--fx-border-image`, which is `none` on every skin but three, so a highlight built
 * on it would be invisible on the skin this feature actually ships on. This one is the accent, so it
 * is there whatever the skin sets.
 *
 * ::after, not ::before -- the band and the mark both hold a picture that fills them, and a ::before
 * underneath it would be covered. It is drawn INSIDE the box for the same reason the overlap is a
 * negative margin: the band clips to its own radius, so a ring outside it would be cut off.
 *
 * The radius is per part rather than inherited, because the two boxes are rounded differently and
 * neither radius lives on the element this class is on.
 */
.csp-banner-band,
.csp-banner-mark { position: relative; }
.csp-banner-lit::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  /* 2px, the literal the focus ring above is drawn at: this is the same job -- an edge that has to
     read at a glance against a photograph, which one border-width does not. */
  padding: 2px;
  background: linear-gradient(
    135deg,
    var(--csp-color-accent) 0%,
    var(--csp-color-accent-strong) 50%,
    var(--csp-color-accent) 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
.csp-banner-band.csp-banner-lit::after {
  border-radius: var(--csp-fx-art-radius);
}
.csp-banner-mark.csp-banner-lit::after {
  border-radius: var(--csp-fx-control-radius);
}

/*
 * THE HEADER OVER ITS BAND -- BannerHeader's `layered` (the owner: "There's currently a LOT of space
 * taken by the banner and we should use that space to contain all of the other stuff over it"). The
 * five things the stacked header put under the band -- mark, chips, name, links, description -- in
 * one stack at the band's bottom-left, on the picture itself, behind a scrim. Four arrangements
 * were drawn; the owner picked this one ("a looks great, let's lock that in").
 *
 * ## The box is max(band, content), and the band is never taller than the stacked header's
 *
 * The container is a one-cell grid: a ::before spacer at the band's 3:1 sets the FLOOR, and the lead
 * sits in the same cell, so the box is the taller of the two. At a desktop width the 3:1 band is
 * the taller and the header is exactly the height the band alone used to be -- nothing under it any
 * more. On a phone the content is the taller and the band simply grows to hold it; the picture is
 * absolutely positioned behind, object-fit: cover, so it fills whatever the cell came out at.
 *
 * ## The scrim is the same measurement ArtOverlay's is
 *
 * --banner-over-scrim is --art-over-scrim's mix, canvas at 80%, for the reason written there: canvas
 * is the one ground token that is opaque in every palette, so the words sit on the theme's own
 * ground fading into the picture, dark on the dark palettes and light on the light one, and the
 * text tones keep the contrast the audit measured for them. It rises from the bottom, where the
 * text is, and is gone by the band's top tenth -- --banner-over-veil-rising. The other shape it
 * can take, --banner-over-veil-full, is the same mix over the whole band, for the one arrangement
 * that puts words at the band's top (the straddle on a phone, below). --banner-over-veil is which
 * of the two the band wears, and each gradient is written once.
 *
 * ## The lead lets the pointer through
 *
 * On the owner's page the band IS a control (PictureButton: press the banner to swap it), and the
 * lead is a box the size of the whole band with its content in one corner. So the lead itself takes
 * no pointer events and its three children take them back -- the empty part of the band is the
 * band's, the mark and the words are their own.
 *
 * ## One column, at every width
 *
 * A 96px mark with a display-size name beside it wraps on a phone whatever a breakpoint says, so
 * this is one stack at every width, as the stacked header is; the phone's is simply taller.
 */
.csp-banner-over {
  --csp-banner-over-scrim: color-mix(in srgb, var(--csp-color-canvas) 80%, transparent);
  --csp-banner-over-veil-rising: linear-gradient(
    to top,
    var(--csp-banner-over-scrim) 0,
    var(--csp-banner-over-scrim) 35%,
    transparent 90%
  );
  --csp-banner-over-veil-full: linear-gradient(var(--csp-banner-over-scrim), var(--csp-banner-over-scrim));
  --csp-banner-over-veil: var(--csp-banner-over-veil-rising);
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  width: 100%;
  min-width: 0;
}
.csp-banner-over::before {
  content: '';
  grid-area: 1 / 1;
  width: 100%;
  aspect-ratio: 3 / 1;
}
.csp-banner-over-band {
  /*
   * A grid item in the lead's cell, not `absolute; inset: 0`: the straddle arrangement adds a
   * second row under the cell (.csp-banner-under), and an absolute band would have painted across
   * that row too. The item stretches to the cell -- max(spacer, lead) -- and paints exactly that.
   */
  grid-area: 1 / 1;
  position: relative;
  overflow: hidden;
  border-radius: var(--csp-fx-art-radius);
  border: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  background-color: var(--csp-color-surface-sunken);
  background-image: var(--csp-fx-surface-image);
}
.csp-banner-over-band > * { width: 100%; height: 100%; border-radius: 0; }
.csp-banner-over-band::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--csp-banner-over-veil);
}
.csp-banner-over-lead {
  grid-area: 1 / 1;
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: var(--csp-space-4);
  padding: var(--csp-space-6);
  min-width: 0;
  pointer-events: none;
}
.csp-banner-over-lead > * { pointer-events: auto; }
.csp-banner-over-body,
.csp-banner-over-desc { min-width: 0; max-width: 100%; }
/* flex, so the inline avatar inside claims no baseline gap and the box is exactly the mark's. */
.csp-banner-over-mark { display: flex; }
.csp-banner-over-desc:empty { display: none; }

/*
 * THE MARK OVER THE BAND'S EDGE, AND THE ROW UNDER THE BAND -- BannerHeader's `arrangement:
 * 'straddle'` and `joined` (the owner: "the pfp there and the earning stats more to the right of
 * the pfp and stuff … left to right along the bottom of the border. Then … have the graph section
 * come out of the bottom of the banner, again no rounding on the conjoining angles"; of the three
 * arrangements drawn for him, "B dark is the best").
 *
 * ## The mark hangs over the band's edge, at every width
 *
 * The lead stays the one-cell column it is for the stacked header, with the identity -- mark,
 * title block and tiles, one flex row -- as its only child. The mark's margin box is aligned to
 * the lead's content bottom; a negative bottom margin of half the mark plus the lead's padding puts
 * its centre exactly on the band's edge and its lower half over the row below. The lead is z-index
 * 1 already, so it paints over that row. The vertical padding is --banner-over-pad (the inline
 * one --banner-over-pad-x), because the phone's are smaller (below) and the mark's overhang is
 * computed from whichever is in force.
 *
 * ## A desktop: the title block low beside the mark, the tiles against the far edge
 *
 * The title block takes a negative bottom margin of its own, so its last line ends --space-3 above
 * the band's edge rather than the lead's full --space-6 (the owner: "the display name/username
 * should be a bit lower -- closer to the bottom of the banner"). The lead's padding is left alone:
 * it is the corp page's too, and the stacked header's words sit where they always did.
 *
 * .csp-banner-over-figures is pushed to the row's far end with an auto margin and its tiles are
 * --space-12 apart (the owner: "right-align the group against the banner's right edge, with
 * liberal spacing BETWEEN the tiles ... the name block staying left" -- and, before that, "header
 * then value below it, with the value larger"). Each tile is a column of two lines, the figure at
 * the metric size under a label, sharing the title block's negative bottom margin so the two sit
 * on one baseline low in the band.
 *
 * ## A phone: the band is still exactly 3:1, the names top-left, the tiles down the right
 *
 * The owner: "do NOT move the picture and do NOT crop the banner -- the banner keeps its 3:1 box
 * exactly as on desktop and the picture keeps straddling the banner's bottom edge at the left
 * exactly as on desktop. What changes on phone is only: the name + X handle go to the banner's
 * TOP-LEFT, and the Rank / Seats / Created tiles go down the banner's RIGHT side." This is the
 * BASE rule, mobile first like the rest of the sheet, and the desktop block below overrides it:
 *
 *  - the lead is given the band's own 3:1 and no content-based minimum, so it is exactly the
 *    spacer's height whatever it holds: nothing in it can make the band taller and crop the
 *    picture. (Not `position: absolute; inset: 0` -- the grid has a second row under the band and
 *    an absolute lead spanned both, which put the mark at the bottom of the plot's top row.)
 *  - the mark is taken out of the row (absolute, against the lead's left padding, its centre on
 *    the band's bottom edge) so it overhangs exactly as on a desktop while the title block, first
 *    in the row now, sits in the band's actual top-left corner at the band's content padding --
 *    above the picture, not beside where it would have been (the owner: "pin the name block to the
 *    banner's actual top-left corner");
 *  - the tiles are a COLUMN pushed to the row's far end with an auto margin, each tile
 *    right-aligned. Three tiles at the metric step are 174px tall and a 3:1 band at 375px is 114px,
 *    so on a phone the figure takes the metric-sm step -- the type tokens, not a second copy of the
 *    numbers -- and the tiles stack with no gap: 3 x (14 + 20) = 102px between --space-1-and-a-half
 *    of padding above and below is the 114px band exactly, nothing past its edge. The lead's
 *    inline padding stays --space-6, the desktop's, so the handle and the mark keep the inset
 *    from the band's side they have there;
 *  - the scrim covers the whole band at one strength rather than rising from its bottom, because
 *    words are at the band's top now and a gradient that is gone by the top tenth would put the
 *    handle on the bare picture.
 *
 * ## The row under the band is the chart panel's top row
 *
 * .csp-banner-under is the grid's second row and leaves the mark's lower half its room: above
 * `md` an inline-start padding of the mark's width, below it a top padding of the mark's lower
 * half, so what is on the row goes beside the mark on a desktop and under it on a phone -- no
 * overlap at either width, and no measurement. -foot leads the row, -aside is pushed to the far end
 * with an auto margin and -controls follows it --space-4 on (the row's gap: the figures on -aside
 * are --space-8 apart, "twice as much as between the earned stuff and the buttons"); an empty slot
 * takes no room. On a phone the row is a column: -aside keeps its line and -controls takes the line
 * under it at the full width, every button in it an equal share -- the switch's three choices as
 * three equal buttons, never a wrapped row of chips.
 *
 * It is painted as the chart's own ground, and the text on it takes the plot's ink tones the way
 * .csp-card-accent recolours its text: the row IS the chart panel's top row when a chart frame is
 * joined beneath it, which is what tokens.ts's "nothing outside a plot" rule is about. The accent
 * figure takes the line's own colour (series one), which is the figure the line is of. Its bottom
 * padding is 0: the frame joined under it carries its own top padding, and two paddings would be
 * a gap in what should read as one box.
 *
 * ## joined -- the band's bottom is a join, not an edge
 *
 * Square bottom corners and no bottom border, the mirror of .csp-banner-flush's top. The block
 * under the header squares its own top; this rule knows nothing about it.
 */
.csp-banner-joined .csp-banner-over-band {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom-width: 0;
}
.csp-banner-over-straddle {
  --csp-banner-over-pad: calc(var(--csp-space-3) / 2);
  --csp-banner-over-pad-x: var(--csp-space-6);
  --csp-banner-over-veil: var(--csp-banner-over-veil-full);
}
.csp-banner-over-straddle .csp-banner-over-lead {
  aspect-ratio: 3 / 1;
  min-height: 0;
  justify-content: flex-start;
  padding: var(--csp-banner-over-pad) var(--csp-banner-over-pad-x);
}
.csp-banner-over-id {
  display: flex;
  align-items: flex-start;
  gap: var(--csp-space-4);
  min-width: 0;
  max-width: 100%;
  width: 100%;
  flex: 1 1 auto;
}
.csp-banner-over-straddle .csp-banner-over-mark {
  position: absolute;
  left: var(--csp-banner-over-pad-x);
  bottom: calc(-1 * var(--csp-size-avatar-xl) / 2);
}
.csp-banner-over-figures {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0;
  margin-inline-start: auto;
  min-width: 0;
}
.csp-banner-over-figure {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  min-width: 0;
}
.csp-banner-over-figure > .csp-t-metric {
  font-size: var(--csp-type-metric-sm-size);
  line-height: var(--csp-type-metric-sm-line);
  letter-spacing: var(--csp-type-metric-sm-tracking);
}
.csp-banner-under {
  grid-area: 2 / 1;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--csp-space-4);
  min-width: 0;
  padding: var(--csp-space-4) var(--csp-space-6) 0;
  padding-top: calc(var(--csp-size-avatar-xl) / 2 + var(--csp-space-4));
  background-color: var(--csp-color-plot-ground);
}
.csp-banner-under-foot,
.csp-banner-under-aside,
.csp-banner-under-controls { min-width: 0; }
.csp-banner-under-foot:empty,
.csp-banner-under-aside:empty,
.csp-banner-under-controls:empty { display: none; }
/* Every button on the controls' line an equal share of it. The switch is a row Stack of buttons. */
.csp-banner-under-controls > * { width: 100%; }
.csp-banner-under-controls .csp-button { flex: 1 1 0; min-width: 0; }
@media (min-width: 768px) {
  .csp-banner-over-straddle {
    --csp-banner-over-pad: var(--csp-space-6);
    --csp-banner-over-pad-x: var(--csp-space-6);
    --csp-banner-over-veil: var(--csp-banner-over-veil-rising);
  }
  .csp-banner-over-straddle .csp-banner-over-lead {
    aspect-ratio: auto;
    min-height: auto;
    justify-content: flex-end;
  }
  .csp-banner-over-id { align-items: flex-end; flex: 0 1 auto; }
  .csp-banner-over-straddle .csp-banner-over-mark {
    position: static;
    margin-bottom: calc(-1 * (var(--csp-banner-over-pad) + var(--csp-size-avatar-xl) / 2));
  }
  .csp-banner-over-straddle .csp-banner-over-body,
  .csp-banner-over-straddle .csp-banner-over-figures {
    margin-bottom: calc(var(--csp-space-3) - var(--csp-banner-over-pad));
  }
  .csp-banner-over-figures {
    flex-direction: row;
    align-items: flex-end;
    gap: var(--csp-space-12);
  }
  .csp-banner-over-figure { align-items: flex-start; gap: var(--csp-space-1); }
  .csp-banner-over-figure > .csp-t-metric {
    font-size: var(--csp-type-metric-size);
    line-height: var(--csp-type-metric-line);
    letter-spacing: var(--csp-type-metric-tracking);
  }
  .csp-banner-under {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    padding-top: var(--csp-space-4);
    padding-inline-start: calc(var(--csp-space-6) + var(--csp-size-avatar-xl) + var(--csp-space-4));
  }
  .csp-banner-under-aside { margin-inline-start: auto; }
  .csp-banner-under-controls > * { width: auto; }
  .csp-banner-under-controls .csp-button { flex: 0 0 auto; }
}
.csp-banner-under .csp-text { color: var(--csp-color-plot-ink); }
.csp-banner-under .csp-c-secondary,
.csp-banner-under .csp-c-muted { color: var(--csp-color-plot-ink-muted); }
.csp-banner-under .csp-c-accent { color: var(--csp-color-plot-series-one); }

/*
 * A BACKDROP (Backdrop): a picture that fills its box and is looked THROUGH rather than at -- the profile header's ground when the account has no banner, under
 * BannerHeader's scrim. Absolutely positioned inside its own box so the box's size is the parent's
 * (the band's), never the picture's.
 *
 * With -blur the picture is drawn 2x the blur radius PAST every edge of the box and blurred by that
 * radius. A blur fades into transparency over about its radius at the picture's edge, so a picture
 * blurred at the box's exact size shows a soft border of the ground behind it; drawing it larger
 * puts that fringe outside the box, where overflow: hidden throws it away.
 */
.csp-backdrop {
  --csp-backdrop-blur: 28px;
  position: relative;
  overflow: hidden;
  width: 100%;
  height: 100%;
}
.csp-backdrop img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Explicit width and height: an absolutely positioned img with both auto takes its own size, not
   the inset's, so the enlargement has to be spelled as a size and an offset. */
.csp-backdrop-blur img {
  top: calc(var(--csp-backdrop-blur) * -2);
  left: calc(var(--csp-backdrop-blur) * -2);
  width: calc(100% + var(--csp-backdrop-blur) * 4);
  /* The reset caps every img at its box; this one is meant to overflow it. */
  max-width: none;
  height: calc(100% + var(--csp-backdrop-blur) * 4);
  filter: blur(var(--csp-backdrop-blur));
}

/*
 * THE BAND FLUSH TO THE TOP OF THE PAGE -- BannerHeader's `flush` (the owner: "on pages with a
 * banner at the top (the profile and the corp page), have the banner go straight to the top of the
 * page with no rounding on the top corners").
 *
 * Two halves. The band's own: its top corners are square and its top border is gone, because the
 * line above it is now the site header's rule and a second hairline pressed against that one would
 * read as a thicker line across the column alone. The bottom corners and the bottom border are the
 * card idiom's, untouched.
 *
 * The page's half: the shell's `main` puts every screen inside one Stack with padding-block, and
 * that padding is the gutter between the header's rule and the first thing on a page. For a page
 * that opens on a flush band the gutter goes -- at the top only; the bottom half stays. The main is
 * not told which pages those are: it asks, with `:has()`, the way `.csp-page:has(.csp-hero)` asks
 * whether the page it paints has a hero (see HERO_SOLID_CANVAS). The header declares `flush` and
 * the shell honours it, so there is one declaration and no table of routes to keep in step. Not
 * "a flush header as the screen's first child": how deep the header sits differs per screen (the
 * corp page's is its root Stack's first child, the profile's is inside a section), and a selector
 * that spelled the depth would be a table of routes by another name. That `flush` is only ever
 * declared by a header that OPENS a page is the prop's own contract.
 *
 * Specificity: the shell's gutter is a spacing utility, doubled to (0,2,0) so it beats the list
 * reset (see spacingRules). `:has()` takes the specificity of its most specific argument, so this
 * selector is (0,3,1) and wins on specificity rather than on order -- which matters, because the
 * spacing utilities are appended LAST.
 *
 * The band's width is the content column's. The shell has one full-bleed element, the site header
 * (`.csp-site-header`: "the BAND spans the page and the CONTENT is contained"); everything under it
 * is inside a `Container`, and this band is no exception.
 */
.csp-banner-flush .csp-banner-over-band {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  border-top-width: 0;
}
main.csp-container > .csp-stack:has(.csp-banner-flush) {
  padding-block-start: 0;
}

.csp-art-square { aspect-ratio: 1 / 1; }
.csp-art-wide { aspect-ratio: 16 / 9; }
.csp-art-tall { aspect-ratio: 3 / 4; }
.csp-art-banner { aspect-ratio: 3 / 1; }

.csp-w-full { width: 100%; }
.csp-max-prose { max-width: var(--csp-size-prose-max); }
.csp-w-three-quarters { width: 75%; }
.csp-w-half { width: 50%; }
.csp-w-third { width: 33%; }

/*
 * The speech bubble, and the character standing behind it. See primitives/speech.tsx.
 *
 * The whole look lives here rather than in the feature that mounts it, because layout primitives
 * take semantic props only — no className and no style objects — and a mascot is not an excuse to
 * open that door. Feature code hands this component a name, a line and a portrait URL; every size,
 * colour, layer and curve below is the design system's business.
 *
 * ## Sizing is fluid, off ONE custom property
 *
 * The owner asked for a character who adapts to the screen "in an intelligent way", and explicitly
 * not for a fixed size or a breakpoint jump. So there is exactly one number that scales —
 * --csp-speech-portrait — and everything that has to stay in proportion with him, above all
 * the tuck, is DERIVED from it with calc(). There is no media query in this block on purpose: add
 * one and the two halves of him start disagreeing at the boundary.
 *
 * ## The bubble is UNDER THE SKIN
 *
 * Its shape and its light come from the fx layer — the card radius, the skin's hairline, the skin's
 * glow over its inner light — exactly as every card, tile and callout on the page does. It used to
 * carry its own 16px radius, its own accent border and an accent glow halo behind it, which read as
 * a bubble from the old system sitting under vault-shaped speaker and close buttons.
 *
 * What is NOT the skin's business is the panel's MATERIAL: --csp-speech-panel-bg and the
 * backdrop blur are what make this a bubble rather than a card, and they stay here.
 *
 * A skin that declares no glow therefore leaves the bubble with none. That is the consequence of
 * putting it on the same footing as every other surface, and it is intended.
 *
 * ## The layer order is load-bearing
 *
 *   portrait  z 10  bottom third tucked behind the panel, nothing clipped
 *   panel     z 20
 *   tail      z 20
 *
 * .csp-speech-bubble deliberately does NOT create a stacking context (position: relative and
 * no z-index of its own), which is what lets the portrait outside it compare its z-index against
 * the panel inside it. Give it a z-index and the portrait falls behind the page.
 *
 * Nothing clips the portrait, because its drop-shadow follows the artwork's own alpha and a clip
 * would cut the glow off square.
 */

/*
 * Floor 104px: legible on a 390px phone without covering the page. Ceiling 208px: past that he
 * stops being a mascot in the corner and starts being a second column.
 *
 * Declared for the bubble AND for the space it reserves below, because the reservation IS his
 * footprint and has to be the same number. One declaration, two selectors — never two clamps.
 */
.csp-speech,
.csp-speech-reserve {
  --csp-speech-portrait: clamp(104px, 20vw, 208px);
}

.csp-speech {
  /*
   * Declared once and read by BOTH the panel and its tail. The tip renders darker than the bubble
   * it grows out of the moment those two are written separately and one of them drifts, so "the
   * fill matches exactly" is structural here rather than a coincidence two rules keep up.
   */
  --csp-speech-panel-bg: color-mix(in srgb, var(--csp-color-canvas) 95%, transparent);
  position: fixed;
  /* Above the page, below the top layer a <dialog> renders into. */
  z-index: 40;
  right: calc(var(--csp-space-4) + env(safe-area-inset-right));
  bottom: calc(var(--csp-space-4) + env(safe-area-inset-bottom));
  /*
   * Percent, NOT vw: 100vw includes the classic scrollbar and the fixed containing block does not,
   * so a vw-based width plus a right offset overhangs the left edge by the scrollbar's width on
   * every desktop browser that still draws one. This resolves against the same box the offsets do.
   */
  width: min(26rem, calc(100% - 2 * var(--csp-space-4)));
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  /*
   * He must never eat a click meant for the page behind him. This is on the WRAPPER only — a
   * pointer-events: none panel would swallow its own speaker and close buttons.
   */
  pointer-events: none;
  animation: csp-speech-in var(--csp-duration-base) var(--csp-easing-standard) both;
}
@keyframes csp-speech-in {
  from { opacity: 0; transform: translateY(var(--csp-space-3)) scale(0.96); }
  to { opacity: 1; transform: none; }
}

.csp-speech-portrait {
  position: relative;
  z-index: 10;
  width: var(--csp-speech-portrait);
  height: var(--csp-speech-portrait);
  /*
   * The tuck: a third of his height overlaps the panel at EVERY size, because it is derived from
   * the same property his size is. That overlap is what makes him read as standing behind the
   * bubble rather than sitting on top of it.
   */
  margin-bottom: calc(var(--csp-speech-portrait) / -3);
  filter: drop-shadow(0 0 14px color-mix(in srgb, var(--csp-color-accent) 50%, transparent));
  pointer-events: none;
}

.csp-speech-bubble { position: relative; width: 100%; min-width: 0; }

.csp-speech-panel {
  position: relative;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: var(--csp-space-2);
  min-width: 0;
  padding: var(--csp-space-4);
  /* The skin's shape and the skin's light — see the note above. */
  border-radius: var(--csp-fx-card-radius);
  border: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  box-shadow: var(--csp-fx-inner-light), var(--csp-fx-glow);
  background: var(--csp-speech-panel-bg);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  /* Takes its own clicks back from the wrapper. It is also what makes a tap finish the line. */
  pointer-events: auto;
}

/*
 * The tail takes the panel's MATERIAL and nothing else, and that is deliberate rather than an
 * omission: it is a clipped triangle, so a radius would eat its tip, a border would draw three
 * edges the clip then cuts through, and a box-shadow is clipped away with the rest of the box. Its
 * geometry is pinned to the PORTRAIT rather than to the radius, which is what makes it survive a
 * skin whose corners are rounder — measured at both viewports, the tail sits at least 44px in from
 * the panel's right edge and the roundest skin (bloom) curves 20px of it.
 */
.csp-speech-tail {
  position: absolute;
  z-index: 20;
  top: 100%;
  /* Centred under the portrait, so the tail is on the side he is standing on at every size. */
  right: calc(var(--csp-speech-portrait) / 2 - var(--csp-space-2));
  width: var(--csp-space-4);
  height: var(--csp-space-2);
  background: var(--csp-speech-panel-bg);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  /*
   * The visible half of a square rotated 45 degrees, expressed as a CLIP rather than drawn as a
   * rotation. The panel is translucent, so the half a rotated square would tuck under it would show
   * through as a darker wedge behind the first line of text; a CSS-border triangle has exactly the
   * same inner half. Clipping removes it, and the two lower edges are still at 45 degrees.
   */
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  pointer-events: none;
}

/*
 * The untyped remainder of the line: present in the DOM from the first frame, and INVISIBLE rather
 * than absent.
 *
 * Rendering a growing prefix reflows on nearly every character on a bubble this narrow — the last
 * word jumps to the next line, the bubble gets taller mid-sentence, and the portrait is shoved
 * around while you are reading it. With the whole line present the bubble is at its final height
 * immediately and nothing moves. It is visibility: hidden and not opacity: 0, so the text is out
 * of the accessibility tree as well as off the screen. (And no backticks in any comment in this
 * template literal: one would end the string.)
 */
.csp-speech-rest { visibility: hidden; }

/*
 * THE FOOTPRINT HE STANDS IN, kept clear at the end of the page.
 *
 * "Without covering the page" is what the clamp above promises, and until this rule he did not keep
 * it: he is fixed to the bottom-right corner and his panel takes its own clicks back, so whatever
 * the page ends with lands underneath him and cannot be tapped. On the landing page that was the
 * "Create account" button, and it cleared him by 14px — luck, not design. Spec 65 deleted the
 * footer, took 155px off every page, and spent that 14px.
 *
 * The fix belongs to him rather than to the pages he sits on: a spacer in the shell would be a
 * second place that has to know how tall he is, and it drifts the first time the clamp is retuned.
 *
 * ## Why twice the portrait
 *
 * His footprint is the two thirds of the portrait the tuck leaves above the bubble, plus the bubble,
 * plus the space-4 he keeps off the bottom edge. The bubble's height is the one part CSS cannot
 * read — it depends on the line and on how narrowly it wraps — so the portrait stands in for it,
 * which is tight where he is smallest and generous where he is not. Measured against the tallest of
 * his seven lines, whose panel is 122px at every width:
 *
 *   390px   portrait 104  footprint 69 + 122 + 16 = 207   reserved 208
 *   640px   portrait 128  footprint 85 + 122 + 16 = 223   reserved 256
 *   1139px  portrait 208  footprint 139 + 122 + 16 = 277  reserved 416
 *
 * Always over, never under, and it moves with the clamp rather than with a number typed in here. The
 * slack at the top of the range is not empty page: it is the corner he is standing in, and the
 * alternative is a second expression here that has to know how tall his bubble is. A line long
 * enough to wrap the bubble past a portrait's height would eat the margin at 390px, where there is
 * only 1px of it; probe mascot's clearance assertion is what fails when it does, which is why that
 * assertion is the guard here rather than a comment asking nobody to write a long line.
 *
 * ## Why it stops where the content column does
 *
 * It stops at size.containerMax, and that number is the answer rather than a breakpoint because it
 * is the width at which the page's content stops growing towards him. BELOW it the column is the
 * viewport, so it keeps reaching further right while his dock stays 26rem wide — the two overlap,
 * and whatever the page ends with ends under him. AT and above it the column is capped and centred,
 * so it stops short of his corner and every further pixel of viewport widens the gap. Measured on
 * the landing page at its maximum scroll — no text and no control intersects his panel or his
 * portrait at any of these:
 *
 *   1139px  reserved 416px   the page grows by that plus its own gap
 *   1140px  not reserved     his panel starts at 708 and the column is capped, so nothing reaches it
 *   1280px, 1440px  not reserved, and the document is the same height it was before this rule
 *
 * (!) The system's one breakpoint, 768px, is NOT the line here, and it was tried first.
 * He still spans the column well past it: at 768px his panel starts at 336 while the account
 * prompt's paragraph runs to 632, which is covered — measured, with this rule already in place and
 * gated on breakpoint.md. A gate has to be the width where the geometry changes, not the nearest
 * existing number to it.
 *
 * (!) It is a HEIGHT and not padding on the page, deliberately. Every padding and gap on the page
 * is audited against the 4px token scale, and rightly: this is an artwork's footprint, not spacing
 * between two things, and it has no business claiming a step on that scale.
 */
.csp-speech-reserve {
  height: calc(var(--csp-speech-portrait) * 2);
}
@media (min-width: 1140px) {
  /* Not height: 0 — a zero-height flex item still takes its parent's gap. */
  .csp-speech-reserve { display: none; }
}

/*
 * The speaker and close buttons are .csp-icon-button and nothing else — the square shape
 * moved there when `Modal`'s corner X grew one too. All that is left here is that neither may
 * stretch or shrink inside the bubble's header row.
 */
.csp-speech-action { flex: none; }

.csp-skeleton {
  display: block;
  background: var(--csp-color-surface-muted);
  border-radius: var(--csp-radius-sm);
  animation: csp-pulse 1.4s ease-in-out infinite;
}
@keyframes csp-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/*
 * The loading icon. See primitives/display.tsx -> Spinner.
 *
 * A ring the strong border's colour with one quarter of it in the accent, turning. space-6
 * across, because it sits beside a label at the label size and anything smaller reads as a dot;
 * the ring is half a space-1 thick, which is the hairline scaled to the ring. One literal, the
 * turn's period, for the same reason .csp-skeleton's pulse has one: a rate is not a length and the
 * scale has no step for it. The universal reduce block below stops the turn with everything else.
 */
.csp-spinner {
  display: inline-block;
  flex: none;
  width: var(--csp-space-6);
  height: var(--csp-space-6);
  border-radius: var(--csp-radius-pill);
  border: calc(var(--csp-space-1) / 2) solid var(--csp-color-border-strong);
  border-top-color: var(--csp-color-accent);
  animation: csp-spin 0.9s linear infinite;
}
@keyframes csp-spin {
  to { transform: rotate(360deg); }
}
/*
 * ONE reduced-motion rule, for the whole system.
 *
 * It replaced two narrow ones (.csp-skeleton, .csp-button) when the skins landed. A skin is a
 * stylesheet somebody else writes, so per-rule opt-outs make "no motion means no motion" a promise
 * each new skin has to remember — and the fourth one forgets. This is the whole page, pseudo
 * elements included, at a specificity nothing can outrank.
 *
 * Written as the two longhand-collapsing shorthands on purpose: the probe asserts
 * animation-name: none and transition-duration: 0s, and only `animation`/`transition` reset
 * every longhand a skin might have set (delay and iteration-count included).
 *
 * ## What this replaced, and why nothing was lost
 *
 * Three named rules used to live here: .csp-skeleton's pulse, .csp-button's transition, and the
 * mascot's arrival (.csp-speech, added by the hiring-manager spec). The universal selector covers
 * all three and every pseudo-element besides, so each is subsumed rather than dropped — verified by
 * probe skins, which walks every element AND its ::before/::after and requires animation-name to be
 * none and every transition-duration to be 0s.
 *
 * The mascot's OTHER half is not CSS and is unaffected: SpeechBubble reads the same media query in
 * JavaScript and hands each line over whole instead of typing it. An instant line inside an
 * animated bubble is the jarring version of the same setting, which is why both halves go.
 */
/*
 * Something ARRIVING: it fades up into place instead of appearing.
 *
 * For a panel whose contents fill in while somebody works rather than all at once — the launch
 * flow's preview rail, where a picture, a name and a row of traits each land at the moment the
 * creator decides them. Without it every one of them pops, and a panel that pops five times while
 * you fill in a form reads as broken rather than live.
 *
 * ⚠️ It runs ON MOUNT and only on mount, which is the whole reason it is a keyframe and not a
 * transition: the elements it is for do not change, they appear. Putting it on something that is
 * always rendered animates it once on page load and never again, which is not what it is for.
 *
 * The duration and the curve are the system's own `--fx-` tokens, so this moves like everything
 * else does, and the universal reduce block below turns it off with the rest.
 */
.csp-rise-in {
  animation: csp-rise-in var(--csp-fx-duration) var(--csp-fx-ease) both;
}

@keyframes csp-rise-in {
  from { opacity: 0; transform: translateY(0.25rem); }
  to { opacity: 1; transform: none; }
}

/*
 * Something REVEALED: a picture that was being made bursts into place (spec 190).
 *
 * For the mint page's reveal card, the moment a token's draw lands: the box pops up from small and
 * over-bright to its own size and light, and a ring of the accent flashes out from its edge and
 * fades. It is the flashy half of "reveal the NFT in a flashy way", and the owner tunes it by eye
 * — every figure here is a token or a multiple of one, so a skin or a palette swap moves it with
 * everything else. The ring is the accent at the same radius as the art it wraps, so it reads as
 * the picture's own light and not a box drawn around it.
 *
 * Like .csp-rise-in it runs ON MOUNT and only on mount: the caller mounts it around the picture
 * when the picture arrives, and never around the placeholder before it. The universal reduce block
 * below turns it off with the rest, and a reader who asked for no motion sees the picture land.
 */
.csp-reveal {
  display: block;
  border-radius: var(--csp-fx-art-radius);
  animation:
    csp-reveal-pop calc(var(--csp-duration-base) * 3) var(--csp-easing-standard) both,
    csp-reveal-ring calc(var(--csp-duration-base) * 5) var(--csp-easing-standard) both;
}

@keyframes csp-reveal-pop {
  from { opacity: 0; transform: scale(0.6); filter: brightness(2.4) saturate(0.2); }
  60% { opacity: 1; transform: scale(1.06); filter: brightness(1.35) saturate(1.2); }
  to { opacity: 1; transform: none; filter: none; }
}

@keyframes csp-reveal-ring {
  from { box-shadow: 0 0 0 0 var(--csp-color-accent); }
  to { box-shadow: 0 0 0 var(--csp-space-6) transparent; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
}

/*
 * Placeholder data. Product's rule for this page is "no numbers we don't have", and their
 * wireframes mark every invented figure. Marking it in the system rather than per-feature is what
 * stops a demo quietly becoming a page that lies. The mark is a dotted underline, so it survives
 * greyscale, and the text still carries its own tone.
 */
.csp-placeholder {
  text-decoration: underline dotted var(--csp-color-placeholder-underline);
  text-underline-offset: 0.28em;
  text-decoration-thickness: 1px;
}

/* ---------------------------------------------------------------------------------------------
 * Checkbox and toggle.
 *
 * Both are real controls wearing a drawn face, never a div pretending. The checkbox IS an
 * <input type="checkbox"> — so `indeterminate`, the space bar, form association and the
 * accessibility tree all come free — sized to the 36px control floor, made transparent with
 * appearance: none, and laid OVER the box it looks like. That order is deliberate: the ring the
 * focus-walk probe reads belongs to the real control, and a box drawn on a sibling would have moved
 * it onto something the keyboard never focuses.
 * ------------------------------------------------------------------------------------------- */
.csp-checkbox {
  position: relative;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: var(--csp-size-control-height-sm);
  height: var(--csp-size-control-height-sm);
}
.csp-checkbox-input {
  appearance: none;
  -webkit-appearance: none;
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: var(--csp-fx-control-radius);
  cursor: pointer;
  /*
   * A form control does NOT inherit the page's font, and this one had none of its own — so it
   * rendered in the user agent's default, which is 13.333px Arial in Chromium. Nothing shows: the
   * input is transparent and empty, and the tick is drawn on the sibling box. But the design-system
   * audit reads computed styles off every element and correctly reported a font size outside the
   * type scale and a face nobody declared, on the first page to use a checkbox.
   *
   * Stated the way the text field above states it, because it is the same fact about the same kind
   * of element and two spellings of it would be two things to keep true.
   */
  font-family: var(--csp-fx-body-family);
  font-size: var(--csp-type-body-size);
}
.csp-checkbox-input:disabled { cursor: default; }
.csp-checkbox-box {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--csp-space-5);
  height: var(--csp-space-5);
  border-radius: var(--csp-fx-control-radius);
  border: var(--csp-fx-border-width) solid var(--csp-color-border-strong);
  background-color: var(--csp-color-surface-sunken);
  background-image: var(--csp-fx-surface-image);
  transition: background-color var(--csp-fx-duration) var(--csp-fx-ease),
    border-color var(--csp-fx-duration) var(--csp-fx-ease),
    box-shadow var(--csp-fx-duration) var(--csp-fx-ease);
}
/* The tick: a box with two of its four borders, turned 45 degrees. */
.csp-checkbox-box::after {
  content: '';
  width: 5px;
  height: 9px;
  margin-top: -2px;
  border: 2px solid var(--csp-color-accent-text);
  border-top: 0;
  border-left: 0;
  transform: rotate(45deg) scale(0);
  transition: transform var(--csp-fx-duration) var(--csp-fx-ease);
}
.csp-checkbox-input:checked + .csp-checkbox-box,
.csp-checkbox-input:indeterminate + .csp-checkbox-box {
  background-color: var(--csp-color-accent);
  border-color: var(--csp-color-accent);
  box-shadow: var(--csp-fx-glow);
}
.csp-checkbox-input:checked + .csp-checkbox-box::after { transform: rotate(45deg) scale(1); }
/* Indeterminate is the same glyph flattened to a bar, so the two states share one element. */
.csp-checkbox-input:indeterminate + .csp-checkbox-box::after {
  width: 10px;
  height: 0;
  margin-top: 0;
  border-width: 0 0 2px 0;
  transform: none;
}
.csp-checkbox-input:disabled + .csp-checkbox-box { opacity: 0.55; }
/* ---------------------------------------------------------------------------------------------
 * A CHECKBOX THAT IS ITS OWN LEGEND SWATCH.
 *
 * `Checkbox`'s `tint` sets `--csp-checkbox-tint` to a palette colour custom property, and
 * these three rules are everything that reads it: the box is OUTLINED in it when off, FILLED with
 * it when on, and the label is written in it.
 *
 * All three are the opposite of what an ordinary checkbox does, and all three are the point. A row
 * of these IS the key to a chart, so the colour has to say which line each switch owns whether or
 * not anything is switched on — the corp chart's three series are disabled on every
 * corp with no history, and a key with no colours in it is what the owner saw.
 *
 * The label rule reaches THROUGH `Text`'s own tone class on purpose: (0,3,0) beats
 * `.csp-c-secondary`'s (0,1,0), so a tinted checkbox needs no second prop to colour its
 * words. A disabled one is dimmed by `.csp-checkbox-label-disabled` rather than muted,
 * because muting it would take the colour back off.
 * ------------------------------------------------------------------------------------------- */
.csp-checkbox-tinted .csp-checkbox-box { border-color: var(--csp-checkbox-tint); }
.csp-checkbox-tinted .csp-checkbox-input:checked + .csp-checkbox-box,
.csp-checkbox-tinted .csp-checkbox-input:indeterminate + .csp-checkbox-box {
  background-color: var(--csp-checkbox-tint);
  border-color: var(--csp-checkbox-tint);
}
.csp-checkbox-label-tinted .csp-text { color: var(--csp-checkbox-tint); }
/*
 * A TINTED label keeps its colour when it is disabled, and is dimmed instead. Muting it is how
 * every other checkbox says disabled, and it is wrong for one whose colour is information.
 */
.csp-checkbox-label-disabled { opacity: 0.55; }
/*
 * A TONED checkbox keeps its colour when it is disabled, and is dimmed instead.
 *
 * An ordinary disabled checkbox says so by going muted, and that is right for a control whose
 * colour means nothing. It is wrong for a row of them that is also a KEY: the corp chart's
 * three series are all disabled on every corp with no history yet, so muting them left the
 * key with no colours in it at all — which is exactly what the owner saw and said.
 *
 * So `Checkbox` keeps the tone on the label and dims it here instead. The box beside it is dimmed
 * by the rule above.
 */
.csp-checkbox-label-disabled { opacity: 0.55; }

.csp-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--csp-space-2);
  min-height: var(--csp-size-control-height-sm);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--csp-color-text-secondary);
  font-family: var(--csp-fx-body-family);
  /*
   * ⚠️ The SIZE, and it is not decoration. A <button> with no font-size of its own takes the
   * browser's FORM-CONTROL default, which is 13.333px in Chromium and on no type scale this product
   * has — the same trap the note on `body`'s own font-size describes, one element down. It went
   * unnoticed while the only Toggle in the tree was on the style lab, which no probe walks; the
   * first one on a real page (the corp chart's series switches, spec 162) failed `probe
   * corp`'s design-system audit at once, at both widths, with "strays [13.33]".
   *
   * Both children wear a Text variant and set their own size, so nothing visible moves. What
   * changes is that the BUTTON itself measures as a size the scale contains.
   */
  font-size: var(--csp-type-body-size);
  cursor: pointer;
}
/*
 * DISABLED reads as DELIBERATELY OFF, never as broken.
 *
 * ⚠️ It was `opacity: 0.55` on the whole control, which took the LABEL down with the switch. The
 * first place that showed was the corp chart's three series switches over a corp
 * nothing has been measured on: three half-faded words above an empty box, which the owner read as
 * the plot having failed to load rather than as three series with nothing to draw.
 *
 * So the label keeps its full contrast — the component already gives it `tone="muted"`, which is
 * the design system's word for "quiet", and quiet twice over is illegible — and only the SWITCH is
 * quietened. It is quietened by FLATTENING it rather than by fading it: no surface image, no inner
 * light, no glow, the hairline border instead of the strong one, and a muted knob. A flat switch
 * parked at the off position is a control that is off; a faded one is a control that is failing.
 */
.csp-toggle:disabled { cursor: default; }
.csp-toggle-track {
  position: relative;
  display: inline-block;
  flex: none;
  width: var(--csp-space-12);
  height: var(--csp-space-6);
  border-radius: var(--csp-fx-control-radius);
  border: var(--csp-fx-border-width) solid var(--csp-color-border-strong);
  background-color: var(--csp-color-surface-sunken);
  background-image: var(--csp-fx-surface-image);
  box-shadow: var(--csp-fx-inner-light);
  transition: background-color var(--csp-fx-duration) var(--csp-fx-ease),
    border-color var(--csp-fx-duration) var(--csp-fx-ease),
    box-shadow var(--csp-fx-duration) var(--csp-fx-ease);
}
.csp-toggle-knob {
  position: absolute;
  top: 50%;
  left: 2px;
  width: 16px;
  height: 16px;
  margin-top: -8px;
  /*
   * The knob takes the control radius too, not a pill. At 16px square every skin's value except
   * foil's 4px resolves to a circle anyway, so this costs nothing and it is what makes foil's
   * switch a squared-off slider rather than the same switch in a different colour.
   */
  border-radius: var(--csp-fx-control-radius);
  background: var(--csp-color-text-secondary);
  transition: left var(--csp-fx-duration) var(--csp-fx-ease),
    width var(--csp-fx-duration) var(--csp-fx-ease),
    background-color var(--csp-fx-duration) var(--csp-fx-ease);
}
/*
 * The "on" appearance hangs off data-on, NOT off aria-checked.
 *
 * Two controls wear these classes and only one of them is a switch. Toggle is a button and says
 * role="switch" with aria-checked, which is honest. ToggleLink is an anchor that navigates to the
 * same page with a parameter flipped; it must NOT claim checkbox semantics, because a screen
 * reader would announce a switch and a keyboard user would reach for Space, which an anchor does
 * not answer. So the visual state gets its own attribute and the semantics get theirs, and neither
 * is forced to stand in for the other.
 */
.csp-toggle[data-on='true'] .csp-toggle-track {
  background-color: var(--csp-color-accent-subtle);
  border-color: var(--csp-color-accent-border);
  box-shadow: var(--csp-fx-inner-light), var(--csp-fx-glow);
}
.csp-toggle[data-on='true'] .csp-toggle-knob {
  left: calc(100% - 18px);
  background: var(--csp-color-accent);
}
/*
 * The flat switch, declared AFTER the "on" appearance on purpose: both selectors are three
 * components wide, so order is what decides, and a control that cannot be pressed must not be able
 * to render as lit. See the disabled note above for why this is flattened rather than faded.
 */
.csp-toggle:disabled .csp-toggle-track {
  background-image: none;
  box-shadow: none;
  border-color: var(--csp-color-border);
}
.csp-toggle:disabled .csp-toggle-knob {
  background: var(--csp-color-text-muted);
}

/* ---------------------------------------------------------------------------------------------
 * The two blocks this product is actually made of.
 *
 * Their own namespaces (csp-nft-*, csp-coll-*) exist so a skin has something specific to target
 * without reaching into a layout primitive that every other screen shares.
 *
 * --csp-px / --csp-py are declared here with a sensible middle, so the tile renders correctly
 * server-side and on a touch device, where `Interactive` deliberately attaches no listener and
 * never writes them.
 * ------------------------------------------------------------------------------------------- */
.csp-nft, .csp-coll {
  /* The pivot every hover transform above depends on. Never a corner or an edge. */
  transform-origin: center;
  --csp-px: 0.5;
  --csp-py: 0.5;
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background-color: var(--csp-color-surface);
  background-image: var(--csp-fx-surface-image);
  border: var(--csp-fx-border-width) solid var(--csp-fx-border-color);
  border-radius: var(--csp-fx-card-radius);
  box-shadow: var(--csp-fx-inner-light), var(--csp-fx-glow);
  transition: transform var(--csp-fx-duration) var(--csp-fx-ease),
    box-shadow var(--csp-fx-duration) var(--csp-fx-ease),
    border-radius var(--csp-fx-duration) var(--csp-fx-ease),
    border-color var(--csp-fx-duration) var(--csp-fx-ease);
}
.csp-nft:hover, .csp-coll:hover {
  transform: translateY(var(--csp-fx-lift)) scale(var(--csp-fx-hover-scale));
  box-shadow: var(--csp-fx-inner-light), var(--csp-fx-glow-hover);
}

.csp-nft { gap: var(--csp-space-3); padding: var(--csp-space-3); }
.csp-nft-art {
  position: relative;
  /* Contains the overlays' blend modes: without it a screen blend reaches the page's backdrop. */
  isolation: isolate;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--csp-fx-art-radius);
  background: var(--csp-color-surface-sunken);
}
.csp-nft-art img { width: 100%; height: 100%; object-fit: cover; }
/*
 * Two overlays over the art, both inert until a skin fills them: `sheen` is the token-driven layer
 * that tracks the cursor, `grain` is the second one a skin paints its own texture into. Two rather
 * than one because the two move at different rates, and one element cannot carry two backgrounds
 * at two positions.
 */
.csp-nft-sheen, .csp-nft-grain {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--csp-fx-duration) var(--csp-fx-ease);
}
.csp-nft-sheen {
  background-image: var(--csp-fx-sheen);
  background-size: 250% 250%;
  background-position: calc(var(--csp-px) * 100%) calc(var(--csp-py) * 100%);
  mix-blend-mode: screen;
}
.csp-nft:hover .csp-nft-sheen, .csp-nft:hover .csp-nft-grain { opacity: 1; }
/*
 * The action is in the DOM at all times and revealed by hover OR focus-within, so a keyboard user
 * reaches it in the same tab order a mouse user finds it in. On a device with no hover it is simply
 * always there — a control nobody can reveal is a control nobody has.
 */
.csp-nft-action {
  position: absolute;
  inset-inline: var(--csp-space-2);
  bottom: var(--csp-space-2);
  display: flex;
  justify-content: center;
  opacity: 0;
  transform: translateY(var(--csp-space-2));
  transition: opacity var(--csp-fx-duration) var(--csp-fx-ease),
    transform var(--csp-fx-duration) var(--csp-fx-ease);
}
.csp-nft:hover .csp-nft-action,
.csp-nft:focus-within .csp-nft-action { opacity: 1; transform: translateY(0); }
@media (hover: none) {
  .csp-nft-action { opacity: 1; transform: none; }
}
.csp-nft-row {
  display: flex;
  align-items: center;
  gap: var(--csp-space-2);
  min-width: 0;
}
.csp-nft-figures {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--csp-space-3);
  min-width: 0;
}

.csp-coll { overflow: hidden; }
.csp-coll-banner {
  position: relative;
  aspect-ratio: 16 / 5;
  overflow: hidden;
  background: var(--csp-color-surface-sunken);
}
.csp-coll-banner img { width: 100%; height: 100%; object-fit: cover; }
.csp-coll-body {
  display: flex;
  flex-direction: column;
  gap: var(--csp-space-4);
  padding: var(--csp-space-4);
}
/*
 * The avatar overlaps the banner's lower edge by half of itself, done with a negative margin in
 * normal flow rather than with absolute positioning — the card clips to its own radius, and an
 * absolutely-positioned avatar would be clipped along with the banner image.
 */
.csp-coll-avatar { margin-top: calc(var(--csp-space-8) * -1); }
.csp-coll-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--csp-space-3);
}
@media (min-width: 768px) {
  .csp-coll-stats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ---------------------------------------------------------------------------------------------
 * The composition grid.
 *
 * Twelve columns above the breakpoint, one below it — a phone has no room for an uneven
 * composition, and a 3-of-12 block on a 390px screen is not a design, it is a sliver.
 * ------------------------------------------------------------------------------------------- */
.csp-mosaic {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--csp-space-6);
  list-style: none;
  margin: 0;
  padding: 0;
}
.csp-mosaic-item { display: flex; flex-direction: column; gap: var(--csp-space-3); min-width: 0; }
@media (min-width: 768px) {
  .csp-mosaic { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .csp-col-1 { grid-column: span 1; }
  .csp-col-2 { grid-column: span 2; }
  .csp-col-3 { grid-column: span 3; }
  .csp-col-4 { grid-column: span 4; }
  .csp-col-5 { grid-column: span 5; }
  .csp-col-6 { grid-column: span 6; }
  .csp-col-7 { grid-column: span 7; }
  .csp-col-8 { grid-column: span 8; }
  .csp-col-9 { grid-column: span 9; }
  .csp-col-10 { grid-column: span 10; }
  .csp-col-11 { grid-column: span 11; }
  .csp-col-12 { grid-column: span 12; }
  .csp-row-1 { grid-row: span 1; }
  .csp-row-2 { grid-row: span 2; }
  /*
   * A filling column does not get a vote on how tall the row is.
   *
   * Above the breakpoint the items are side by side, so the grid row is as tall as the tallest of
   * them and every item stretches to it. flex-basis: 0 takes the filling child's own content out of
   * that maximum: the row is sized by the OTHER column, this one is handed whatever that came to,
   * and .csp-clip hides the rest. That is what "it stretches to fill and clips rather than setting
   * the page's height" actually compiles to.
   *
   * Scoped to the breakpoint on purpose. Below it the mosaic is ONE column, so every item is its
   * own row, and a basis of zero there would leave nothing for the row to be sized by — a panel
   * collapsed to nothing on a phone, which is the width most of this traffic arrives at.
   */
  .csp-mosaic-item > .csp-fill { flex-basis: 0; }
}

.csp-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


:root {
  --csp-b2-iri: linear-gradient(115deg, #FF8AD8, #A48CFF, #6FD6FF, #8FF0B0, #FFE070, #FF8AD8);
  --csp-b2-iri-fill: linear-gradient(115deg, #FF8AD8, #A48CFF, #6FD6FF, #8FF0B0, #FFE070);
  --csp-b2-itext: linear-gradient(100deg, #FF4FC3, #7B5CFF, #1FB6FF, #22C57A, #FF4FC3);
  --csp-b2-sizzle: linear-gradient(115deg, #FF2E00, #FF8A00, #FFD60A, #FF4FC3, #FF2E00);
  --csp-b2-heat: linear-gradient(90deg, #FF3D00, #FF8A00, #FFD60A);
  --csp-b2-conic: conic-gradient(#FF8AD8, #A48CFF, #6FD6FF, #8FF0B0, #FFE070, #FF8AD8);
  --csp-b2-iri-bar: linear-gradient(90deg, #FF8AD8, #A48CFF, #6FD6FF, #8FF0B0);
}

/* ------------------------------------------------------------------------------------------ *
 * Keyframes. Every one is transform or opacity unless its note says otherwise.
 * ------------------------------------------------------------------------------------------ */
@keyframes csp-flow { from { transform: translateX(0); } to { transform: translateX(-66.6667%); } }
@keyframes csp-heat { 0%, 100% { opacity: 0; } 35% { opacity: 1; } 60% { opacity: 0.55; } }
@keyframes csp-ping { from { transform: scale(1); opacity: 1; } to { transform: scale(3); opacity: 0; } }
@keyframes csp-blink { 50% { opacity: 0.2; } }
@keyframes csp-blink-hard { 50% { opacity: 0; } }
@keyframes csp-urgent { to { transform: scale(1.05); } }
@keyframes csp-ember-dot { 50% { transform: scale(1.12); } }
@keyframes csp-ember-lit { 50% { opacity: 1; } }
@keyframes csp-seat-new0 { 0% { transform: scale(2.2); background: var(--csp-color-pink); } 100% { transform: scale(1); } }
@keyframes csp-seat-new1 { 0% { transform: scale(2.2); background: var(--csp-color-pink); } 100% { transform: scale(1); } }
@keyframes csp-shake { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(-16deg); } 75% { transform: rotate(16deg); } }
@keyframes csp-wig { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg) scale(1.06); } }
@keyframes csp-confetti {
  0% { transform: translate(-50%, -50%) scale(0.3); opacity: 1; }
  75% { opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) rotate(var(--rot)) scale(1.15); opacity: 0; }
}
@keyframes csp-pop0 { from { opacity: 0; transform: translateX(-14px); background: var(--csp-color-lavender-soft); } to { opacity: 1; transform: none; } }
@keyframes csp-pop1 { from { opacity: 0; transform: translateX(-14px); background: var(--csp-color-lavender-soft); } to { opacity: 1; transform: none; } }
@keyframes csp-drop0 { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
@keyframes csp-drop1 { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
@keyframes csp-swap0 { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
@keyframes csp-swap1 { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
@keyframes csp-caret { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }
@keyframes csp-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
@keyframes csp-coin-rise {
  0% { transform: translate(0, 0) scale(0.7); opacity: 0; }
  12% { opacity: 1; }
  100% { transform: translate(var(--cx), var(--rise, -300px)) scale(1.1); opacity: 0; }
}
@keyframes csp-spin3d { 0%, 100% { transform: scaleX(1); } 50% { transform: scaleX(0.25); } }
@keyframes csp-rumble {
  0%, 100% { transform: translate(0, 0); }
  25% { transform: translate(-2px, 1px); }
  50% { transform: translate(2px, -1px); }
  75% { transform: translate(-1px, -2px); }
}
@keyframes csp-flick { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(1.12); } }
@keyframes csp-rise {
  0% { transform: translate(0, 0); opacity: 0; }
  15% { opacity: 1; }
  100% { transform: translate(var(--ex), -170px); opacity: 0; }
}
@keyframes csp-rise-lit { 0%, 30% { opacity: 0; } 100% { opacity: 1; } }
@keyframes csp-shimmer { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
@keyframes csp-stamp {
  0% { transform: translate(-50%, -50%) rotate(-12deg) scale(3); opacity: 0; }
  35% { transform: translate(-50%, -50%) rotate(-8deg) scale(0.95); opacity: 1; }
  45% { transform: translate(-50%, -50%) rotate(-8deg) scale(1.05); }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -50%) rotate(-8deg) scale(1); }
}
@keyframes csp-twinkle { 0%, 100% { opacity: 0.2; } 50% { opacity: 1; } }
@keyframes csp-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* A figure's flash is a BACKGROUND under its digits — see the note at the top of this file. */
@keyframes csp-flash-up0 { 0% { background: var(--csp-color-flash-up-surface); color: var(--csp-color-flash-up-text); } 100% { background: transparent; } }
@keyframes csp-flash-up1 { 0% { background: var(--csp-color-flash-up-surface); color: var(--csp-color-flash-up-text); } 100% { background: transparent; } }
@keyframes csp-flash-down0 { 0% { background: var(--csp-color-flash-down-surface); color: var(--csp-color-flash-down-text); } 100% { background: transparent; } }
@keyframes csp-flash-down1 { 0% { background: var(--csp-color-flash-down-surface); color: var(--csp-color-flash-down-text); } 100% { background: transparent; } }
/* The headline's gradient text — the one looping paint, and why is at the top of this file. */
@keyframes csp-itext { from { background-position: 0% 50%; } to { background-position: 100% 50%; } }

/* ------------------------------------------------------------------------------------------ *
 * Type the B2 components share.
 * ------------------------------------------------------------------------------------------ */
.csp-mono { font-family: var(--csp-fx-mono-family); font-variant-numeric: tabular-nums; }
/* Every figure aligns: each mono step of the type scale takes tabular figures, not only the mono class. */
.csp-t-metric, .csp-t-metric-sm, .csp-t-code, .csp-t-figure-xl, .csp-t-figure-lg, .csp-t-figure-md, .csp-t-figure-sm, .csp-t-figure-xs, .csp-t-number-sm { font-variant-numeric: tabular-nums; }
.csp-itext {
  background: var(--csp-b2-itext);
  background-size: 250% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: csp-itext 4s linear infinite alternate;
}
.csp-eyebrow {
  margin: 0;
  font-family: var(--csp-fx-body-family);
  font-size: 15px;
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--csp-color-accent-ink);
}
.csp-eyebrow-sm { font-size: 13px; }
.csp-eyebrow-xs { font-size: 11px; letter-spacing: 0.1em; }
.csp-eyebrow-muted { color: var(--csp-color-text-muted); }
.csp-eyebrow-ink { color: var(--csp-color-text-primary); }
.csp-eyebrow-fire { color: var(--csp-color-fire-text); }
.csp-eyebrow-pink { color: var(--csp-color-pink-text); }

/* ------------------------------------------------------------------------------------------ *
 * THE RING — the iri card, the countdown tile, and (in fire) the sizzle card.
 * ------------------------------------------------------------------------------------------ */
.csp-ring {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: var(--csp-ring-radius, 22px);
  padding: var(--csp-ring-width, 2px);
  color: var(--csp-color-text-primary);
  min-width: 0;
}
.csp-ring::before { content: '';
  position: absolute;
  inset: auto;
  top: -100%;
  left: 0;
  width: 300%;
  height: 300%;
  padding: 0;
  z-index: -2;
  border-radius: 0;
  -webkit-mask: none;
  mask: none;
  opacity: 1;
  background: var(--csp-ring-gradient, var(--csp-b2-iri));
  animation: csp-flow var(--csp-ring-duration, 5s) var(--csp-ring-ease, ease-in-out) infinite alternate;
  will-change: transform;
  pointer-events: none; }
.csp-ring::after { content: '';
  position: absolute;
  inset: var(--csp-ring-width, 2px);
  z-index: -1;
  border-radius: calc(var(--csp-ring-radius, 22px) - var(--csp-ring-width, 2px));
  background: var(--csp-ring-panel, var(--csp-color-surface));
  pointer-events: none; }
.csp-ring-body { position: relative; height: 100%; min-width: 0; display: flex; flex-direction: column; }
/* A face that runs edge to edge (`Ring bleed`): clipped to the panel's own rounded corners. */
.csp-ring-bleed > .csp-ring-body {
  border-radius: calc(var(--csp-ring-radius, 22px) - var(--csp-ring-width, 2px));
  overflow: hidden;
}
.csp-ring-fire {
  --csp-ring-gradient: var(--csp-b2-sizzle);
  --csp-ring-duration: 1.3s;
  --csp-ring-ease: linear;
  --csp-ring-width: 3px;
  --csp-ring-panel: var(--csp-color-sizzle-surface);
}
.csp-ring-ink { --csp-ring-panel: var(--csp-color-tile-ink); color: var(--csp-color-tile-ink-text); }
.csp-ring-pad { padding: calc(var(--csp-ring-width, 2px) + 20px); }
.csp-ring-pad-sm { padding: calc(var(--csp-ring-width, 2px) + 16px); }
.csp-ring-pad-lg { padding: calc(var(--csp-ring-width, 2px) + 28px); }
@media (max-width: 639.98px) {
  .csp-ring-pad-lg { padding: calc(var(--csp-ring-width, 2px) + 16px); }
}
/* A filled control wearing the moving gradient — the current nav item. No panel, all gradient. */
.csp-flow-fill { position: relative; isolation: isolate; overflow: hidden; }
.csp-flow-fill::before {
  content: '';
  position: absolute;
  inset: auto;
  top: -100%;
  left: 0;
  width: 300%;
  height: 300%;
  padding: 0;
  z-index: -2;
  border-radius: 0;
  -webkit-mask: none;
  mask: none;
  opacity: 1;
  background: var(--csp-ring-gradient, var(--csp-b2-iri));
  animation: csp-flow var(--csp-ring-duration, 5s) var(--csp-ring-ease, ease-in-out) infinite alternate;
  will-change: transform;
  pointer-events: none;
  --csp-ring-gradient: var(--csp-b2-iri-fill);
  --csp-ring-duration: 4s;
}

/* Lift: the design's hover on a card that is a link, and on the sizzle card that holds one. */
.csp-lift { transition: transform 0.2s ease; }
.csp-lift:hover { transform: translateY(-5px) rotate(-0.4deg); }

/* ------------------------------------------------------------------------------------------ *
 * THE SIZZLE — a mint past its minimum. Glow, flames, embers, the stamp.
 * ------------------------------------------------------------------------------------------ */
.csp-sizzle { position: relative; border-radius: 22px; display: flex; flex-direction: column; }
.csp-sizzle > .csp-ring { z-index: 1; flex: 1 1 auto; }
/*
 * A card whose body is a link (`SizzleCard href`): the link lies over the whole card, and every
 * button on the card is lifted above it, so a press on a button is the button's and a press
 * anywhere else is the link's.
 * `:not(dialog *)`: a pop-up the card opens is rendered inside it (`OpenMintWizard`), and its
 * corner X is positioned by `.csp-modal-close` — lifted by this rule it fell into the flow, top left.
 */
.csp-card-link { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }
.csp-card-link ~ * button:not(dialog *) { position: relative; z-index: 2; }
/*
 * The flames are 44px tall above the card. A sizzling card drops by exactly that much in its own
 * cell, so the flames stand inside the row's height and no card that is not on fire carries a band
 * for them. (`seat-grid-compact` is what the card gives back for it.)
 */
.csp-sizzle-hot { box-shadow: 0 18px 40px -14px rgba(255, 80, 0, 0.55); margin-top: 44px; }
.csp-sizzle-hot::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: 0 0 26px 3px rgba(255, 138, 0, 0.55), 0 24px 54px -10px rgba(255, 60, 0, 0.7);
  opacity: 0;
  animation: csp-heat 1.2s ease-in-out infinite;
}
.csp-flames {
  position: absolute;
  left: 18px;
  right: 18px;
  top: -44px;
  height: 48px;
  pointer-events: none;
  z-index: 0;
  transform-origin: bottom;
  animation: csp-flick 0.5s ease-in-out infinite;
  background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 12' shape-rendering='crispEdges'><path fill='%23E8340C' d='M2 0h1v1h-1zM2 1h1v1h-1zM6 1h1v1h-1zM2 2h1v1h-1zM6 2h1v1h-1zM1 3h1v1h-1zM6 3h1v1h-1zM7 3h1v1h-1zM1 4h1v1h-1zM3 4h1v1h-1zM7 4h1v1h-1zM3 5h1v1h-1zM5 5h1v1h-1zM0 6h1v1h-1zM8 6h1v1h-1zM4 7h1v1h-1zM9 8h1v1h-1z'/><path fill='%23FF8A00' d='M2 3h1v1h-1zM2 4h1v1h-1zM6 4h1v1h-1zM1 5h1v1h-1zM2 5h1v1h-1zM6 5h1v1h-1zM7 5h1v1h-1zM1 6h1v1h-1zM3 6h1v1h-1zM5 6h1v1h-1zM6 6h1v1h-1zM7 6h1v1h-1zM0 7h1v1h-1zM1 7h1v1h-1zM3 7h1v1h-1zM5 7h1v1h-1zM7 7h1v1h-1zM8 7h1v1h-1zM0 8h1v1h-1zM4 8h1v1h-1zM5 8h1v1h-1zM8 8h1v1h-1zM9 9h1v1h-1z'/><path fill='%23FFD60A' d='M2 6h1v1h-1zM2 7h1v1h-1zM6 7h1v1h-1zM1 8h1v1h-1zM2 8h1v1h-1zM3 8h1v1h-1zM6 8h1v1h-1zM7 8h1v1h-1zM0 9h1v1h-1zM1 9h1v1h-1zM2 9h1v1h-1zM3 9h1v1h-1zM4 9h1v1h-1zM5 9h1v1h-1zM6 9h1v1h-1zM7 9h1v1h-1zM8 9h1v1h-1zM0 10h1v1h-1zM1 10h1v1h-1zM2 10h1v1h-1zM7 10h1v1h-1zM8 10h1v1h-1zM9 10h1v1h-1zM0 11h1v1h-1zM1 11h1v1h-1zM2 11h1v1h-1zM7 11h1v1h-1zM8 11h1v1h-1zM9 11h1v1h-1z'/><path fill='%23FFF3B0' d='M3 10h1v1h-1zM4 10h1v1h-1zM5 10h1v1h-1zM6 10h1v1h-1zM3 11h1v1h-1zM4 11h1v1h-1zM5 11h1v1h-1zM6 11h1v1h-1z'/></svg>") left bottom / 40px 48px repeat-x;
}
.csp-flames::after {
  content: '';
  position: absolute;
  inset: 0;
  background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 12' shape-rendering='crispEdges'><path fill='%23E8340C' d='M7 0h1v1h-1zM3 1h1v1h-1zM7 1h1v1h-1zM3 2h1v1h-1zM7 2h1v1h-1zM2 3h1v1h-1zM3 3h1v1h-1zM0 4h1v1h-1zM2 4h1v1h-1zM6 4h1v1h-1zM8 4h1v1h-1zM0 5h1v1h-1zM4 5h1v1h-1zM6 5h1v1h-1zM8 5h1v1h-1zM1 6h1v1h-1zM9 6h1v1h-1zM5 7h1v1h-1z'/><path fill='%23FF8A00' d='M7 3h1v1h-1zM3 4h1v1h-1zM7 4h1v1h-1zM2 5h1v1h-1zM3 5h1v1h-1zM7 5h1v1h-1zM0 6h1v1h-1zM2 6h1v1h-1zM3 6h1v1h-1zM4 6h1v1h-1zM6 6h1v1h-1zM8 6h1v1h-1zM0 7h1v1h-1zM1 7h1v1h-1zM2 7h1v1h-1zM4 7h1v1h-1zM6 7h1v1h-1zM8 7h1v1h-1zM9 7h1v1h-1zM1 8h1v1h-1zM4 8h1v1h-1zM5 8h1v1h-1zM9 8h1v1h-1z'/><path fill='%23FFD60A' d='M7 6h1v1h-1zM3 7h1v1h-1zM7 7h1v1h-1zM0 8h1v1h-1zM2 8h1v1h-1zM3 8h1v1h-1zM6 8h1v1h-1zM7 8h1v1h-1zM8 8h1v1h-1zM0 9h1v1h-1zM1 9h1v1h-1zM2 9h1v1h-1zM3 9h1v1h-1zM4 9h1v1h-1zM5 9h1v1h-1zM6 9h1v1h-1zM7 9h1v1h-1zM8 9h1v1h-1zM9 9h1v1h-1zM0 10h1v1h-1zM1 10h1v1h-1zM2 10h1v1h-1zM7 10h1v1h-1zM8 10h1v1h-1zM9 10h1v1h-1zM0 11h1v1h-1zM1 11h1v1h-1zM2 11h1v1h-1zM7 11h1v1h-1zM8 11h1v1h-1zM9 11h1v1h-1z'/><path fill='%23FFF3B0' d='M3 10h1v1h-1zM4 10h1v1h-1zM5 10h1v1h-1zM6 10h1v1h-1zM3 11h1v1h-1zM4 11h1v1h-1zM5 11h1v1h-1zM6 11h1v1h-1z'/></svg>") left bottom / 40px 48px repeat-x;
  animation: csp-blink-hard 0.28s steps(1) infinite;
}
.csp-ember {
  position: absolute;
  bottom: 26px;
  width: 5px;
  height: 5px;
  background: #FF8A00;
  opacity: 0;
  z-index: 3;
  pointer-events: none;
  animation: csp-rise 1.9s linear infinite;
}
/* The mockup's ember turns yellow as it rises. The yellow is a layer that fades in, not a paint. */
.csp-ember::after {
  content: '';
  position: absolute;
  inset: 0;
  background: #FFD60A;
  opacity: 0;
  animation: csp-rise-lit 1.9s linear infinite;
  animation-delay: inherit;
}
.csp-stamp {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 6;
  padding: 14px 22px;
  border: 4px solid var(--csp-color-fire);
  border-radius: 14px;
  background: var(--csp-color-sizzle-surface);
  color: var(--csp-color-fire);
  font-family: var(--csp-fx-body-family);
  font-weight: 800;
  font-size: 28px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  animation: csp-stamp 3.2s ease-out forwards;
  box-shadow: 0 12px 30px -8px rgba(232, 52, 12, 0.5);
}
/*
 * With reduced motion the animation is off (css.ts), and it is the only thing that reveals the
 * stamp — so it stands still in its resting pose instead. How long it stays is the card's
 * business (`RaiseCard`'s 3.2s), not the animation's, so the window is the same.
 */
@media (prefers-reduced-motion: reduce) {
  .csp-stamp { opacity: 1; transform: translate(-50%, -50%) rotate(-8deg); }
}

/* ------------------------------------------------------------------------------------------ *
 * THE SEAT GRID — one square per seat, twenty to a row.
 * ------------------------------------------------------------------------------------------ */
.csp-seat-grid {
  display: grid;
  grid-template-columns: repeat(20, 10px);
  gap: 4px;
  justify-content: space-between;
}
.csp-seat-grid-compact { display: flex; gap: 1px; }
.csp-seat-grid-compact > .csp-seat { flex: 1 1 0; min-width: 0; width: auto; height: 6px; border-radius: 1px; }
.csp-seat {
  position: relative;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--csp-color-seat-empty);
  transition: background 0.3s;
}
.csp-seat-on { background: var(--csp-color-accent-bright); }
.csp-seat-fire { animation: csp-ember-dot 1.1s ease-in-out infinite; }
.csp-seat-fire::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  opacity: 0;
  animation: csp-ember-lit 1.1s ease-in-out infinite;
  animation-delay: inherit;
}
.csp-seat-fire { background: #FF6A00; animation-delay: 0s; }
.csp-seat-fire::after { background: #FF9400; }
.csp-seat-fire:nth-child(3n) { background: #FF9A00; animation-delay: -.35s; }
.csp-seat-fire:nth-child(3n)::after { background: #FFD800; }
.csp-seat-fire:nth-child(5n) { background: #FF3D00; animation-delay: -.7s; }
.csp-seat-fire:nth-child(5n)::after { background: #FF5500; }
.csp-seat-fire:nth-child(7n) { background: #FFC400; animation-delay: -.2s; }
.csp-seat-fire:nth-child(7n)::after { background: #FFFF00; }
.csp-seat-new0 { animation: csp-seat-new0 0.7s cubic-bezier(0.2, 1.6, 0.4, 1); }
.csp-seat-new1 { animation: csp-seat-new1 0.7s cubic-bezier(0.2, 1.6, 0.4, 1); }
/* The seats the reader is about to take, in the mint wizard: pink. */
.csp-seat-yours { background: var(--csp-color-pink); }

/* ------------------------------------------------------------------------------------------ *
 * THE HEAT BAR — a mint's overflow past its minimum, toward its cap.
 * ------------------------------------------------------------------------------------------ */
.csp-heat-track { height: 10px; border-radius: 99px; background: var(--csp-color-heat-track); overflow: hidden; }
.csp-heat-fill {
  position: relative;
  height: 10px;
  border-radius: 99px;
  overflow: hidden;
  background: var(--csp-b2-heat);
  transition: width 0.6s ease;
}
.csp-heat-fill::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.7), transparent);
  transform: translateX(-100%);
  animation: csp-shimmer 1.1s infinite;
}
/* A plain meter: the launch fee's bar. */
.csp-meter { height: 12px; border-radius: 99px; background: var(--csp-color-divider); overflow: hidden; }
.csp-meter-fill { height: 100%; border-radius: 99px; background: var(--csp-color-pink); }

/* ------------------------------------------------------------------------------------------ *
 * Figures: the flash, the countdown, the live dot and badge.
 * ------------------------------------------------------------------------------------------ */
.csp-num { border-radius: 6px; padding: 0 4px; margin: 0 -4px; }
.csp-flash-up0 { animation: csp-flash-up0 0.8s ease-out; }
.csp-flash-up1 { animation: csp-flash-up1 0.8s ease-out; }
.csp-flash-down0 { animation: csp-flash-down0 0.8s ease-out; }
.csp-flash-down1 { animation: csp-flash-down1 0.8s ease-out; }
.csp-swap0 { display: inline-block; animation: csp-swap0 0.35s ease; }
.csp-swap1 { display: inline-block; animation: csp-swap1 0.35s ease; }

.csp-countdown { display: flex; align-items: center; gap: 10px; transform-origin: left center; }
.csp-countdown-urgent { animation: csp-urgent 0.45s ease-in-out infinite alternate; }
.csp-countdown-urgent .csp-countdown-digits { color: var(--csp-color-urgent); }
.csp-countdown-tile {
  --csp-ring-radius: 22px;
  width: 104px;
}
.csp-countdown-face {
  padding: 12px 0 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.csp-countdown-digits {
  font-family: var(--csp-fx-mono-family);
  font-variant-numeric: tabular-nums;
  font-size: 50px;
  font-weight: 700;
  line-height: 1;
}
.csp-countdown-unit {
  font-family: var(--csp-fx-body-family);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.14em;
  color: var(--csp-color-text-muted);
}
.csp-ring-ink .csp-countdown-unit { color: var(--csp-color-text-colon); }
.csp-countdown-colon {
  font-family: var(--csp-fx-mono-family);
  font-size: 38px;
  font-weight: 700;
  color: var(--csp-color-text-colon);
  animation: csp-blink 1s steps(1) infinite;
}
@media (max-width: 767.98px) {
  .csp-countdown { gap: 6px; }
  .csp-countdown-tile { width: 84px; }
  .csp-countdown-digits { font-size: 40px; }
  .csp-countdown-colon { font-size: 30px; }
}

.csp-live-dot {
  position: relative;
  display: inline-block;
  flex-shrink: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--csp-color-success);
}
.csp-live-dot::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: rgba(14, 159, 94, 0.6);
  animation: csp-ping 1.4s ease-out infinite;
}
.csp-live-badge {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 7px 14px;
  border-radius: 99px;
  background: var(--csp-color-surface);
  border: 1px solid var(--csp-color-border);
  font-family: var(--csp-fx-body-family);
  font-size: 14px;
  font-weight: 600;
  color: var(--csp-color-text-primary);
}
.csp-live-badge .csp-mono { font-weight: 700; }

/* ------------------------------------------------------------------------------------------ *
 * Pills, chips and the segmented toggle.
 * ------------------------------------------------------------------------------------------ */
.csp-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
  padding: 5px 10px;
  border-radius: 99px;
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  line-height: 1.2;
  font-weight: 800;
  white-space: nowrap;
  background: var(--csp-color-surface-muted);
  color: var(--csp-color-text-secondary);
}
.csp-pill-sm { font-size: 12px; padding: 6px 10px; letter-spacing: 0.06em; }
.csp-pill-success { background: var(--csp-color-success-surface); color: var(--csp-color-success-text); }
.csp-pill-critical { background: var(--csp-color-critical-surface); color: var(--csp-color-critical-text); }
.csp-pill-caution { background: var(--csp-color-caution-surface); color: var(--csp-color-caution-text); }
.csp-pill-lavender { background: var(--csp-color-lavender-soft); color: var(--csp-color-accent-strong); }
.csp-pill-fire { background: var(--csp-color-fire-chip-surface); color: var(--csp-color-fire-chip-text); }
.csp-pill-fire-solid { background: var(--csp-color-fire); color: #FFFFFF; }
.csp-pill-accent { background: var(--csp-color-accent); color: var(--csp-color-accent-text); }
.csp-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 10px;
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  line-height: 1.3;
  font-weight: 700;
  color: var(--csp-color-text-primary);
}
.csp-chip-lavender { background: var(--csp-color-lavender-soft); }
.csp-chip-amber { background: var(--csp-color-caution-surface); color: var(--csp-color-caution-text); }
.csp-chip-mono { background: var(--csp-color-inset); font-family: var(--csp-fx-mono-family); font-weight: 400; }
.csp-pill-wiggle { animation: csp-wig 0.6s ease-in-out infinite; }
/*
 * A button that has to be pressed (`Button flashy` — Go Public): the sizzle card's fire flowing
 * through its fill (the `flow-fill` layer under the sizzle gradient, at the sizzle ring's pace) and
 * the minting pill's wiggle. Both stop while it is disabled; reduced motion stops both (css.ts).
 */
.csp-button-flashy { animation: csp-wig 0.6s ease-in-out infinite; }
/* On the layer itself: `flow-fill`'s own `::before` sets the iridescent fill, so the fire has to
   out-rank it there rather than be inherited from the button. */
.csp-button-flashy.csp-flow-fill::before {
  --csp-ring-gradient: var(--csp-b2-sizzle);
  --csp-ring-duration: 1.3s;
  --csp-ring-ease: linear;
}
.csp-button-flashy:disabled,
.csp-button-flashy:disabled::before { animation: none; }
/* The change chip on a token card: square-ish, mono. */
.csp-change {
  flex-shrink: 0;
  font-family: var(--csp-fx-mono-family);
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  font-weight: 700;
  padding: 5px 8px;
  border-radius: 8px;
}
.csp-change-up { background: var(--csp-color-success-surface); color: var(--csp-color-success-text); }
.csp-change-down { background: var(--csp-color-critical-surface); color: var(--csp-color-critical-text); }

.csp-chip {
  height: 36px;
  padding: 0 12px;
  border-radius: 99px;
  border: 1px solid var(--csp-color-border-control);
  background: var(--csp-color-surface);
  color: var(--csp-color-text-secondary);
  font-family: var(--csp-fx-mono-family);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  transition: transform 0.12s;
}
.csp-chip:hover { transform: translateY(-2px); }
.csp-chip[aria-pressed='true'] {
  background: var(--csp-color-lavender-soft);
  border-color: var(--csp-color-accent-mid);
  color: var(--csp-color-accent-strong);
}

.csp-seg { display: flex; gap: 6px; }
.csp-seg-option {
  height: 38px;
  padding: 0 14px;
  border-radius: 11px;
  border: 1px solid var(--csp-color-border);
  background: var(--csp-color-surface);
  font-family: var(--csp-fx-body-family);
  font-weight: 700;
  font-size: 14px;
  color: var(--csp-color-text-secondary);
  cursor: pointer;
}
.csp-seg-option[aria-pressed='true'] {
  border-color: var(--csp-color-accent-border);
  background: var(--csp-color-lavender);
  color: var(--csp-color-text-primary);
}

/* The design's tab rail (`Explore.dc.html` → `.tab`): a white rail, the chosen tab in ink. */
.csp-tab-rail {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px;
  border-radius: 16px;
  background: var(--csp-color-surface);
  border: 1px solid var(--csp-color-border);
}
.csp-tab-rail-option {
  height: 44px;
  padding: 0 16px;
  border-radius: 13px;
  border: 0;
  background: transparent;
  color: var(--csp-color-text-secondary);
  font-family: var(--csp-fx-body-family);
  font-weight: 700;
  font-size: 15px;
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s;
}
.csp-tab-rail-lg { border-radius: 18px; }
.csp-tab-rail-lg .csp-tab-rail-option { height: 52px; padding: 0 22px; border-radius: 14px; font-size: 17px; }
.csp-tab-rail-option:hover { background: var(--csp-color-inset-strong); }
.csp-tab-rail-option[aria-pressed='true'],
.csp-tab-rail-option[aria-current='page'] {
  background: var(--csp-color-tile-ink);
  color: var(--csp-color-tile-ink-text);
}

/* The design's empty state: a dashed box, a big emoji, one line, one way on (`Explore.dc.html`). */
.csp-dashed-empty {
  padding: 60px;
  border-radius: 22px;
  border: 2px dashed var(--csp-color-border-control);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.csp-dashed-empty-emoji { font-size: 44px; line-height: 1; }

/* ------------------------------------------------------------------------------------------ *
 * Rows: a feed row, a leaderboard row.
 * ------------------------------------------------------------------------------------------ */
.csp-feed-row {
  font-family: var(--csp-fx-body-family);
  font-size: 15px;
  line-height: 1.4;
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--csp-color-inset-feed);
  color: var(--csp-color-text-primary);
}
.csp-feed-row-new0 { animation: csp-pop0 0.5s ease-out; }
.csp-feed-row-new1 { animation: csp-pop1 0.5s ease-out; }

.csp-leader-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 6px;
  border-bottom: 1px solid var(--csp-color-divider);
  font-family: var(--csp-fx-body-family);
  color: var(--csp-color-text-primary);
  text-decoration: none;
}
.csp-leader-rank { width: 34px; flex-shrink: 0; font-weight: 700; color: var(--csp-color-text-muted); }
.csp-leader-medal {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--csp-color-surface-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
}
.csp-leader-name {
  flex-grow: 1;
  min-width: 0;
  font-weight: 700;
  font-size: 16px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.csp-leader-value { font-weight: 700; font-size: 16px; white-space: nowrap; }

/* The leaderboard's board switch: the words on a navy pill, the chosen one wearing `flow-fill`. */
.csp-seg-night {
  gap: 4px;
  padding: 4px;
  border-radius: 16px;
  background: var(--csp-color-night);
  width: fit-content;
  max-width: 100%;
  flex-wrap: wrap;
}
.csp-seg-night .csp-seg-option {
  height: 44px;
  padding: 0 16px;
  border: 0;
  border-radius: 13px;
  background: transparent;
  color: var(--csp-color-night-nav);
  font-size: 15px;
  transition: background 0.15s;
}
.csp-seg-night .csp-seg-option:hover { background: var(--csp-color-night-track); }
.csp-seg-night .csp-seg-option[aria-pressed='true'] { background: transparent; color: #16152B; }

/* ------------------------------------------------------------------------------------------ *
 * The leaderboard's podium and its board (`Leaderboard.dc.html`).
 * ------------------------------------------------------------------------------------------ */
@keyframes csp-podin { from { opacity: 0; transform: translateY(24px) scale(0.96); } to { opacity: 1; transform: none; } }
.csp-podium {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  align-items: end;
}
.csp-podium-place {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  min-width: 0;
  animation: csp-podin 0.6s cubic-bezier(0.2, 1.4, 0.4, 1) both;
}
.csp-podium-place-1 { order: 2; }
.csp-podium-place-2 { order: 1; animation-delay: 0.18s; }
.csp-podium-place-3 { order: 3; animation-delay: 0.36s; }
.csp-podium-medal { font-size: 34px; line-height: 1.2; }
.csp-podium-crown { display: inline-block; font-size: 44px; animation: csp-wig 0.7s ease-in-out infinite; }
.csp-podium-picture { display: flex; border-radius: 50%; }
/* A corp's mark is square; its gold ring on first place follows its corners. */
.csp-podium-picture:has(.csp-avatar-square) { border-radius: 15px; }
.csp-podium-picture .csp-avatar { width: 64px; height: 64px; }
.csp-podium-place-1 .csp-podium-picture { box-shadow: 0 0 0 4px var(--csp-color-surface), 0 0 0 7px var(--csp-color-yellow), 0 12px 30px -8px rgba(255, 180, 0, 0.7); }
.csp-podium-place-1 .csp-podium-picture .csp-avatar { width: 80px; height: 80px; }
.csp-podium-name {
  max-width: 100%;
  font-family: var(--csp-fx-body-family);
  font-size: 16px;
  font-weight: 800;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.csp-podium-place-1 .csp-podium-name { font-size: 18px; }
/* The name is a link; it takes the place's type, not the link's own. */
.csp-podium-name * { font: inherit; }
.csp-podium-block { width: 100%; border-radius: 20px 20px 8px 8px; }
.csp-podium-block::after { border-radius: 18px 18px 6px 6px; }
.csp-podium-block.csp-ring-fire::after { border-radius: 17px 17px 5px 5px; }
.csp-podium-block > .csp-ring-body { height: 130px; align-items: center; justify-content: center; gap: 4px; padding: 0 8px; }
.csp-podium-place-1 .csp-podium-block > .csp-ring-body { height: 180px; }
.csp-podium-place-3 .csp-podium-block > .csp-ring-body { height: 100px; }
.csp-podium-rank {
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.1em;
  color: var(--csp-color-text-secondary);
}
.csp-podium-place-1 .csp-podium-rank { color: var(--csp-color-fire-text); }
.csp-podium-value { max-width: 100%; font-size: 22px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.csp-podium-place-1 .csp-podium-value { font-size: 28px; }
@media (max-width: 639.98px) {
  .csp-podium { gap: 8px; }
  .csp-podium-value { font-size: 15px; }
  .csp-podium-place-1 .csp-podium-value { font-size: 18px; }
  .csp-podium-name, .csp-podium-place-1 .csp-podium-name { font-size: 14px; }
}

/* The card holding a board clips the header strip to its own rounded corners. */
.csp-card:has(.csp-table-board) { overflow: hidden; }
.csp-table-board th,
.csp-table-board td { padding: 0 18px; border-bottom: 0; }
.csp-table-board thead th { height: 42px; background: var(--csp-color-inset); }
.csp-table-board thead th > * { font-weight: 800; letter-spacing: 0.1em; }
.csp-table-board tbody td { height: 56px; border-top: 1px solid var(--csp-color-divider); }
.csp-table-board tbody tr { transition: background 0.15s; }
.csp-table-board tbody tr:hover { background: var(--csp-color-inset); }
.csp-table-board .csp-table-row-highlight,
.csp-table-board tbody .csp-table-row-highlight:hover { background: var(--csp-color-yellow-surface); }
.csp-table-board .csp-table-row-highlight td:first-child { box-shadow: inset 4px 0 0 var(--csp-color-yellow); }

/* ------------------------------------------------------------------------------------------ *
 * The small decorations: sparkline, caret, confetti, rising coins, token tile.
 * ------------------------------------------------------------------------------------------ */
/* Drawn as the corp page's chart is (`CorpChart`): a violet line with round ends over a 12% wash
   of the bright violet, closed by one 1px rule along the bottom — no axes, no frame. */
.csp-sparkline { width: 100%; display: block; border-bottom: 1px solid var(--csp-color-border); }
/* `Sparkline inline`: a trend inside a table row -- one line tall, a fixed short width, no rule. */
.csp-sparkline-inline {
  width: var(--csp-size-sparkline-inline);
  height: var(--csp-space-5);
  flex: none;
  border-bottom: 0;
}
/*
 * The live card's market cap with its line drawn behind it (`CapChart`): the sparkline's own
 * drawing stretched across the whole box, the figure over its top left, and — for a token with too
 * few prices to draw — "Just launched!" over its foot.
 */
.csp-cap-chart { position: relative; height: 130px; margin-top: 6px; }
.csp-cap-chart-plot { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.csp-cap-chart-top { position: relative; padding: 4px 16px 0; display: flex; flex-direction: column; gap: 2px; }
.csp-cap-chart-foot { position: absolute; left: 16px; bottom: 14px; display: flex; gap: 10px; align-items: center; }
/* The stock a card's token is paired with, in its top-right corner: the logo, the ticker under it. */
.csp-stock-badge { flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: 3px; }
.csp-stock-badge img { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; display: block; }
.csp-stock-badge-ticker {
  font-family: var(--csp-fx-mono-family);
  font-size: 11px;
  font-weight: 700;
  color: var(--csp-color-text-primary);
}
.csp-card-band { padding: 16px 16px 0; }
.csp-card-band-last { padding-bottom: 16px; }
.csp-sparkline-area { fill: var(--csp-color-accent-bright); fill-opacity: 0.12; }
.csp-sparkline-line {
  fill: none;
  stroke: var(--csp-color-accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.csp-caret {
  display: inline-block;
  width: 2px;
  height: 16px;
  margin-left: 3px;
  vertical-align: -2px;
  background: var(--csp-color-accent);
  animation: csp-caret 1s infinite;
}
.csp-bob { animation: csp-bob 3.2s ease-in-out infinite; }
.csp-shake { display: inline-block; animation: csp-shake 0.5s ease-in-out infinite; }
.csp-rumble { animation: csp-rumble 0.12s linear infinite; }
.csp-confetti { position: absolute; inset: 0; pointer-events: none; z-index: 5; }
.csp-confetti-piece {
  position: absolute;
  left: 50%;
  top: 50%;
  font-size: 30px;
  opacity: 0;
  animation: csp-confetti 1.7s cubic-bezier(0.12, 0.75, 0.3, 1) forwards;
}
.csp-coins { position: absolute; inset: 0; pointer-events: none; }
.csp-coin-rise {
  position: absolute;
  width: 22px;
  height: 22px;
  opacity: 0;
  animation: csp-coin-rise 3s cubic-bezier(0.3, 0.6, 0.4, 1) infinite;
}
.csp-coin-rise svg { display: block; width: 100%; height: 100%; animation: csp-spin3d 0.8s steps(4) infinite; }
.csp-pixel { shape-rendering: crispEdges; image-rendering: pixelated; display: block; }
.csp-token-tile {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 14px;
  font-family: var(--csp-fx-body-family);
  font-weight: 800;
  color: #16152B;
}
.csp-token-tile img { width: 100%; height: 100%; object-fit: cover; }
.csp-inline-mark {
  display: inline-block;
  width: 1.1em;
  height: 1.1em;
  border-radius: 0.3em;
  vertical-align: -0.2em;
  object-fit: cover;
}
/*
 * A linked ring is an anchor INSIDE its list item (`NftCard`), so the ring is not the grid item and
 * nothing gave it the item's height. Its body's `height: 100%` then resolved against the anchor's
 * auto height -- which is "auto" in standards mode and the LIST ITEM's height in quirks mode, where
 * dev.csuite.fun renders (a script is served before its doctype): the body took the item's full
 * height, the ring's padding went on top, and every card spilled 36px onto the row below and over
 * "See all items". Filling the item makes the body's 100% the anchor's own box in both modes.
 */
.csp-ring-link { display: block; height: 100%; text-decoration: none; }
.csp-stickered { position: relative; display: inline-flex; }
.csp-sticker {
  position: absolute;
  top: -14px;
  right: -12px;
  z-index: 2;
  transform: rotate(4deg);
  padding: 6px 10px;
  border-radius: 10px;
  border: 2px solid var(--csp-color-caution-border);
  background: var(--csp-color-caution-surface);
  color: var(--csp-color-caution-text);
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  font-weight: 800;
  white-space: nowrap;
  box-shadow: 0 8px 18px -8px rgba(22, 21, 43, 0.45);
  pointer-events: none;
}
.csp-button-inert { cursor: not-allowed; }
.csp-button-inert:hover { transform: none !important; box-shadow: none !important; }
.csp-inset-panel {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 6px;
  border-radius: 16px;
  padding: 18px;
  text-align: center;
}
.csp-inset-panel-lavender { background: var(--csp-color-inset-strong); }
.csp-inset-panel-pink { background: var(--csp-color-pink-surface); }
.csp-card-foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding-top: 14px;
  border-top: 1px solid var(--csp-color-divider);
}
.csp-emoji-tile {
  width: 64px;
  height: 64px;
  flex-shrink: 0;
  border-radius: 18px;
  background: var(--csp-color-lavender);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 36px;
}

/* ------------------------------------------------------------------------------------------ *
 * The B2 button variants the base sheet did not have. Shape comes from `.csp-button`.
 * ------------------------------------------------------------------------------------------ */
.csp-button-hot { background: var(--csp-color-fire); border-color: var(--csp-color-fire); color: #FFFFFF; }
.csp-button-hot:hover:not(:disabled) { background: var(--csp-color-fire); border-color: var(--csp-color-fire); }
.csp-button-yellow { background: var(--csp-color-yellow); border-color: var(--csp-color-yellow); color: #16152B; font-weight: 800; }
.csp-button-yellow:hover:not(:disabled) { background: var(--csp-color-yellow); border-color: var(--csp-color-yellow); }
.csp-button-lavender { background: var(--csp-color-lavender); border-color: var(--csp-color-accent-border); color: var(--csp-color-text-primary); }
.csp-button-lavender:hover:not(:disabled) { background: var(--csp-color-lavender); border-color: var(--csp-color-accent-border); }
.csp-button-lg { min-height: 52px; padding-inline: 24px; border-radius: 16px; font-size: 16px; }
.csp-button-tall { min-height: 46px; padding-inline: 18px; font-size: 15px; }

/* ------------------------------------------------------------------------------------------ *
 * THE SHELL — the night desk sidebar on a wide screen, the bottom bar below one.
 * ------------------------------------------------------------------------------------------ */
.csp-shell { display: flex; min-height: 100dvh; }
.csp-shell-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.csp-side {
  position: sticky;
  top: 0;
  height: 100dvh;
  width: 264px;
  flex-shrink: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px 14px;
  overflow: hidden auto;
  scrollbar-width: none;
  background: var(--csp-color-night);
  color: var(--csp-color-night-text);
  font-family: var(--csp-fx-body-family);
  transition: width 0.28s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.csp-side[data-collapsed='true'] { width: 84px; }
.csp-side-star {
  position: absolute;
  width: 3px;
  height: 3px;
  background: #FFFFFF;
  animation: csp-twinkle 2.4s steps(3) infinite;
  pointer-events: none;
}
/* The rail draws both its forms; its state picks one. display: contents keeps the rail's gap. */
.csp-rail-wide { display: contents; }
.csp-rail-narrow, .csp-rail-narrow-inline { display: none; }
.csp-side[data-collapsed='true'] .csp-rail-wide,
.csp-side[data-collapsed='true'] .csp-rail-wide-inline { display: none; }
.csp-side[data-collapsed='true'] .csp-rail-narrow { display: contents; }
.csp-side[data-collapsed='true'] .csp-rail-narrow-inline { display: block; }
.csp-rail-spacer { flex-grow: 1; }
.csp-rail-art { align-self: center; flex-shrink: 0; display: block; }
.csp-side-head { min-height: 48px; display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-shrink: 0; }
.csp-side[data-collapsed='true'] .csp-side-head { justify-content: center; }
.csp-side-brand { display: flex; align-items: center; text-decoration: none; }
/*
 * The logo on the icon rail: the rail's content width (84 wide less 14 of padding a side), so it
 * fills the rail rather than overflowing it. The wide rail draws it at size.brandMark, via
 * .csp-brand-mark; this only ever makes it smaller. Height alone, as there — the width follows.
 */
.csp-side[data-collapsed='true'] .csp-brand-mark { height: 56px; }
/*
 * The one chevron, the rail's next sibling in the frame's row. Its negative margins give it no width
 * in the row, so it straddles the rail's right edge and rides it as the rail's width animates;
 * sticky keeps it level with the head row while the page scrolls.
 */
.csp-side-chev {
  position: sticky;
  top: 30px;
  align-self: flex-start;
  z-index: 11;
  width: 24px;
  height: 24px;
  margin-inline: -12px;
  flex-shrink: 0;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--csp-color-night-chevron-border);
  background: var(--csp-color-night-chevron);
  color: var(--csp-color-night-nav);
  font-family: var(--csp-fx-body-family);
  font-size: 14px;
  font-weight: 800;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.csp-side-chev:hover { background: var(--csp-color-night-track); }
.csp-side-chev > span { display: block; transition: transform 0.28s cubic-bezier(0.2, 0.9, 0.3, 1); }
.csp-side[data-collapsed='true'] + .csp-side-chev > span { transform: rotate(180deg); }
.csp-chain-pill {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px 3px 3px;
  border-radius: 8px;
  background: var(--csp-color-surface);
  color: var(--csp-color-text-primary);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.csp-chain-pill img { width: 16px; height: 16px; border-radius: 4px; display: block; }
.csp-side-nav { display: flex; flex-direction: column; gap: 4px; }
.csp-side-item {
  position: relative;
  height: 50px;
  width: 100%;
  flex-shrink: 0;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--csp-color-night-nav);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 10px;
  font-family: var(--csp-fx-body-family);
  font-size: 16px;
  font-weight: 700;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.csp-side-item { isolation: isolate; }
.csp-side-item:hover { background: var(--csp-color-night-hover); color: #FFFFFF; }
/* The current item's moving gradient, on its own layer so the item itself can overflow for its tip. */
.csp-side-fill { position: absolute; inset: 0; z-index: -1; border-radius: inherit; }
.csp-side-item[aria-current='page'] { color: #16152B; box-shadow: 0 10px 24px -10px rgba(164, 140, 255, 0.8); }
.csp-side-item[aria-current='page']:hover { background: transparent; color: #16152B; }
.csp-side-ico {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: 11px;
  background: var(--csp-color-night-hover);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
}
.csp-side-item[aria-current='page'] .csp-side-ico { background: rgba(255, 255, 255, 0.75); }
.csp-side-label { flex-grow: 1; min-width: 0; white-space: nowrap; overflow: hidden; }
/* On the icon rail the name is clipped to nothing, not removed: it is still the link's name. */
.csp-side[data-collapsed='true'] .csp-side-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}
.csp-side-badge {
  font-family: var(--csp-fx-mono-family);
  font-size: 11px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 99px;
  background: #FF6A00;
  color: #16152B;
  white-space: nowrap;
}
.csp-side-tip {
  position: absolute;
  left: calc(100% + 14px);
  top: 50%;
  transform: translateY(-50%);
  background: #FFFFFF;
  color: #16152B;
  padding: 6px 10px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s;
  z-index: 20;
}
.csp-side-item:hover .csp-side-tip,
.csp-side-item:focus-visible .csp-side-tip { opacity: 1; }
.csp-side[data-collapsed='true'] { overflow: visible; }
.csp-side[data-collapsed='true'] .csp-side-item { justify-content: center; padding: 0; }
.csp-night-card {
  border-radius: 16px;
  background: var(--csp-color-night-raised);
  border: 1px solid var(--csp-color-night-border);
  padding: 14px;
  flex-shrink: 0;
}
/* The next-launch card is the way to its token's corp page: the card's own look, and a lift. */
.csp-night-card-link { display: block; color: inherit; text-decoration: none; transition: border-color 0.15s; }
.csp-night-card-link:hover { border-color: var(--csp-color-night-muted); }
.csp-bell-mini > span:first-child { font-size: 16px; }
.csp-bell-mini-clock { font-family: var(--csp-fx-mono-family); font-size: 11px; font-weight: 700; }
.csp-night-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.12em;
  color: var(--csp-color-night-muted);
}
.csp-night-feed-row {
  font-size: 13px;
  line-height: 1.35;
  color: var(--csp-color-night-body);
  padding: 8px 10px;
  border-radius: 10px;
  background: var(--csp-color-night-raised);
}
.csp-feed-link { display: block; text-decoration: none; }
.csp-night-feed-row.csp-feed-link:hover { color: var(--csp-color-night-text); }
.csp-night-feed-row-new0 { animation: csp-drop0 0.45s ease-out; }
.csp-night-feed-row-new1 { animation: csp-drop1 0.45s ease-out; }
.csp-bell-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.csp-bell-token { font-weight: 800; margin-top: 2px; display: flex; align-items: center; gap: 6px; }
.csp-bell-clock { font-family: var(--csp-fx-mono-family); font-variant-numeric: tabular-nums; font-size: 24px; font-weight: 700; color: var(--csp-color-gold); }
.csp-rail-floor { display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; }
.csp-bell-mini {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 0;
  border-radius: 14px;
  background: var(--csp-color-night-raised);
  flex-shrink: 0;
}
.csp-avatar-conic {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--csp-b2-conic);
}
.csp-button:disabled.csp-button-yellow { opacity: 0.7; }
.csp-night-button {
  height: 42px;
  padding: 0 12px;
  border-radius: 12px;
  border: 1px solid var(--csp-color-night-chevron-border);
  background: transparent;
  color: var(--csp-color-night-nav);
  font-family: var(--csp-fx-body-family);
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
}

/* The top bar over the page, on a wide screen: search, the paid total, the ticker, then the
   sun/moon switch and the wallet at its right-hand end (HeaderControls). */
.csp-topbar {
  position: sticky;
  top: 0;
  z-index: 8;
  height: 76px;
  flex-shrink: 0;
  padding: 0 40px;
  display: flex;
  align-items: center;
  gap: 20px;
  border-bottom: 1px solid var(--csp-color-border);
  background: color-mix(in srgb, var(--csp-color-surface) 70%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.csp-search {
  width: 300px;
  flex-shrink: 0;
  height: 46px;
  padding: 0 14px;
  border-radius: 14px;
  border: 1px solid var(--csp-color-border-control);
  background: var(--csp-color-surface);
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--csp-fx-body-family);
  font-size: 15px;
  color: var(--csp-color-text-primary);
}
.csp-search:focus-within { border-color: var(--csp-color-accent-bright); box-shadow: 0 0 0 4px rgba(123, 92, 255, 0.15); }
.csp-search input { border: 0; outline: 0; background: transparent; font: inherit; color: inherit; flex-grow: 1; min-width: 0; }
.csp-search input::placeholder { color: var(--csp-color-text-faint); }
.csp-paid-chip {
  flex-shrink: 0;
  height: 46px;
  padding: 0 14px;
  border-radius: 14px;
  background: var(--csp-color-night);
  color: #FFFFFF;
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--csp-fx-body-family);
}
.csp-paid-chip .csp-night-eyebrow { letter-spacing: 0.1em; }
.csp-paid-chip .csp-mono { font-size: 15px; font-weight: 700; color: var(--csp-color-night-mint); }
.csp-ticker {
  flex-grow: 1;
  min-width: 0;
  overflow: hidden;
  height: 46px;
  display: flex;
  align-items: center;
  border-radius: 14px;
  background: var(--csp-color-surface);
  border: 1px solid var(--csp-color-border);
}
.csp-ticker-track { display: flex; width: max-content; animation: csp-marquee 40s linear infinite; }
.csp-ticker-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 20px;
  font-family: var(--csp-fx-mono-family);
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  white-space: nowrap;
  border-right: 1px solid var(--csp-color-divider);
  color: var(--csp-color-text-primary);
}
.csp-ticker-item b { font-weight: 700; }
.csp-ticker-item span { color: var(--csp-color-text-muted); }
/*
 * A live token card's own ticker: edge to edge along the card's foot, on the night ground with the
 * night's words, and quicker than the header's 40s (the owner: "ticker full width, faster"; "use the
 * inverted ticker color like on mockup 2"). The items are the header's, recoloured and nothing else.
 */
.csp-ticker-card {
  flex-grow: 0;
  border-radius: 0;
  border: 0;
  background: var(--csp-color-night);
}
.csp-ticker-card .csp-ticker-track { animation-duration: 14s; }
.csp-ticker-card .csp-ticker-item { color: var(--csp-color-night-text); border-right-color: var(--csp-color-night-border); }
.csp-ticker-card .csp-ticker-item span { color: var(--csp-color-night-muted); }

/*
 * The header's right-hand end: the sun/moon switch, then the wallet — at the end of the top bar on a
 * wide screen, at the right of the strip on a narrow one. Both are in the document; the narrow
 * shell's media block swaps which one shows, so there is one wallet on screen at every width.
 */
.csp-header-controls { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.csp-header-controls-strip { display: none; }
.csp-header-controls .csp-button-yellow { min-height: 46px; border-radius: 14px; }
/* The sun/moon switch at the wallet's own height, so the two share a centre line and a top edge. */
.csp-header-controls .csp-theme-switch .csp-icon-button { width: 46px; height: 46px; min-height: 46px; border-radius: 14px; }
/* The account chip: the account's disc and its short address, the height of the search and the ticker. */
.csp-account { position: relative; display: inline-flex; }
.csp-account-chip {
  height: 46px;
  padding: 0 14px 0 8px;
  border-radius: 14px;
  border: 1px solid var(--csp-color-border);
  background: var(--csp-color-surface);
  color: var(--csp-color-text-primary);
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--csp-fx-mono-family);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.csp-account-chip:hover:not(:disabled) { border-color: var(--csp-color-border-control); }
.csp-account-chip[aria-expanded='true'] { border-color: var(--csp-color-accent-bright); }
.csp-account-chip:disabled { cursor: default; opacity: 0.7; }
/* Hung off the chip's right-hand edge: the chip is the last thing in the header. */
.csp-account .csp-dropdown-end { inset-inline-start: auto; inset-inline-end: 0; min-width: 220px; }

/* The narrow shell's bar along the bottom, with the rail's destinations. */
.csp-bottombar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: none;
  gap: 4px;
  padding: 8px 8px calc(8px + env(safe-area-inset-bottom));
  background: var(--csp-color-night);
  border-top: 1px solid var(--csp-color-night-border);
  font-family: var(--csp-fx-body-family);
}
.csp-bottom-item {
  position: relative;
  isolation: isolate;
  flex: 1 1 0;
  min-width: 0;
  min-height: 56px;
  border-radius: 14px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  color: var(--csp-color-night-nav);
  font-size: 11px;
  font-weight: 700;
  text-decoration: none;
  text-align: center;
}
.csp-bottom-item .csp-side-ico { width: 30px; height: 30px; border-radius: 10px; font-size: 16px; }
.csp-bottom-item[aria-current='page'] { color: #16152B; }
.csp-bottom-item[aria-current='page'] .csp-side-ico { background: rgba(255, 255, 255, 0.75); }
.csp-bottom-label { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csp-bottom-badge {
  position: absolute;
  top: 2px;
  right: 6px;
  font-family: var(--csp-fx-mono-family);
  font-size: 10px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 99px;
  background: #FF6A00;
  color: #16152B;
}
.csp-shell-content { flex: 1 0 auto; width: 100%; padding: 34px 40px 40px; }
.csp-shell-footer { width: 100%; padding: 0 40px 40px; }

/*
 * THE NARROW SHELL. The rail does not disappear on a phone: it BECOMES the strip across the top —
 * the same element, laid out in a row, showing its icon-rail forms (the mark, the seat button, the
 * bell, the wallet) and none of its long ones. One element for both is what keeps every control in
 * the document once: a sidebar hidden beside a second, mobile copy of it would be two "Connect
 * wallet" buttons for every test and every screen reader to trip over. The destinations move to
 * the bottom bar, and the top bar's ticker runs under the strip.
 */
@media (max-width: 1023.98px), (max-aspect-ratio: 1333/1000) {
  .csp-shell { flex-direction: column; }
  .csp-side,
  .csp-side[data-collapsed='true'] {
    position: sticky;
    top: 0;
    width: 100%;
    height: auto;
    flex-direction: row;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    overflow: visible;
  }
  .csp-side .csp-rail-wide,
  .csp-side .csp-rail-wide-inline,
  .csp-side-nav,
  .csp-side-chev,
  .csp-side-star,
  .csp-side-tip,
  .csp-rail-only { display: none !important; }
  .csp-side .csp-rail-narrow { display: contents; }
  .csp-side-head { min-height: 0; }
  /* The strip draws the logo at the icon rail's size, for the same reason. */
  .csp-side .csp-brand-mark { height: 56px; }
  .csp-side .csp-side-item { width: 44px; height: 44px; justify-content: center; padding: 0; }
  .csp-bell-mini { flex-direction: row; gap: 6px; padding: 8px 10px; }
  .csp-topbar { position: static; height: auto; padding: 8px 16px; }
  .csp-topbar .csp-search, .csp-topbar .csp-paid-chip { display: none; }
  .csp-topbar .csp-ticker { height: 38px; }
  .csp-header-controls-topbar { display: none; }
  .csp-header-controls-strip { display: flex; margin-inline-start: auto; }
  .csp-header-controls .csp-button-yellow, .csp-account-chip { min-height: 40px; height: 40px; }
  .csp-header-controls .csp-theme-switch .csp-icon-button { width: 40px; height: 40px; min-height: 40px; }
  .csp-bottombar { display: flex; }
  .csp-shell-content { padding: 20px 16px 32px; }
  .csp-shell-footer { padding: 0 16px calc(96px + env(safe-area-inset-bottom)); }
  /*
   * A narrow window on a DESKTOP keeps the destinations in the strip itself, between the mark and
   * the switch and wallet; only a touch device gets them along the bottom. What tells the two apart
   * is the primary input — a mouse or trackpad (`pointer: fine`, `hover: hover`) — not the
   * width, which both share here.
   */
  @media (hover: hover) and (pointer: fine) {
    .csp-side-nav { display: flex !important; flex-direction: row; gap: 4px; min-width: 0; }
    .csp-side .csp-side-item { width: auto; padding: 0 12px 0 5px; gap: 8px; font-size: 14px; }
    .csp-side .csp-side-ico { width: 34px; height: 34px; }
    .csp-side[data-collapsed='true'] .csp-side-label { position: static; width: auto; height: auto; clip-path: none; }
    .csp-bottombar { display: none; }
    .csp-shell-footer { padding: 0 16px 32px; }
  }
  /* Too narrow for the names too: the icons alone, each name still the link's accessible name. */
  @media (hover: hover) and (pointer: fine) and (max-width: 899.98px) {
    .csp-side .csp-side-item { width: 44px; padding: 0; justify-content: center; }
    .csp-side .csp-side-label,
    .csp-side[data-collapsed='true'] .csp-side-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  }
}

/* ------------------------------------------------------------------------------------------ *
 * The B2 pages' layout: sections, card rows, the hero.
 * ------------------------------------------------------------------------------------------ */
.csp-b2-section { display: flex; flex-direction: column; gap: 16px; scroll-margin-top: 96px; min-width: 0; }
.csp-b2-section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.csp-b2-grid {
  display: grid;
  gap: var(--g);
  align-items: stretch;
  grid-template-columns: repeat(auto-fill, minmax(max(var(--min), calc((100% - (var(--n) - 1) * var(--g)) / var(--n))), 1fr));
}
.csp-hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 24px;
  align-items: center;
}
.csp-hero-copy { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
/* `b2-`: `.csp-hero-art` is the older hero picture's class (css.ts), on six other screens. */
.csp-b2-hero-art { position: relative; min-height: 500px; display: flex; align-items: center; justify-content: center; }
@media (max-width: 899.98px) {
  .csp-hero-split { grid-template-columns: minmax(0, 1fr); }
  .csp-b2-hero-art { min-height: 0; padding: 12px 24px 0; }
}
.csp-art-stage { position: relative; max-width: 100%; }
.csp-art-stage > img { display: block; width: 100%; height: 100%; }
.csp-step-art { height: 200px; display: flex; align-items: center; justify-content: center; }
.csp-step-art img { max-height: 190px; width: auto; max-width: min(100%, 320px); transition: transform 0.3s cubic-bezier(0.2, 1.5, 0.4, 1); }
.csp-lift:hover .csp-step-art img { transform: scale(1.06) rotate(-1.5deg); }

/* ------------------------------------------------------------------------------------------ *
 * THE FORM — the creator flow's pieces: the pip stepper, the form beside its preview rail, the
 * pick tile, the slider with its figure, the summary rows, the rule cards, the trait rows, and the
 * confirmation checkbox.
 * ------------------------------------------------------------------------------------------ */
.csp-pips { position: relative; width: 100%; max-width: 600px; }
/* The track runs from the first pip's centre to the last's: half a column in from each end. */
.csp-pips-track {
  position: absolute;
  left: calc(100% / (2 * var(--csp-pips-n, 5)));
  right: calc(100% / (2 * var(--csp-pips-n, 5)));
  top: 17px;
  height: 4px;
  border-radius: 99px;
  background: var(--csp-color-divider);
}
.csp-pips-fill {
  height: 4px;
  border-radius: 99px;
  background: var(--csp-b2-iri-bar);
  transition: width 0.5s cubic-bezier(0.2, 1, 0.3, 1);
}
.csp-pips-list {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--csp-pips-n, 5), minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}
/* The mint wizard's: the pop-up's whole width, 40px in, with the track 60px in (`Mint1.dc.html`). */
.csp-pips-spread { max-width: none; padding: 0 40px; }
.csp-pips-spread .csp-pips-track { left: 60px; right: 60px; }
.csp-pip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  line-height: 1.2;
  font-weight: 700;
  text-align: center;
  color: var(--csp-color-text-muted);
}
.csp-pip-dot {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--csp-color-divider);
  color: var(--csp-color-text-primary);
  font-family: var(--csp-fx-mono-family);
  font-size: 15px;
  font-weight: 700;
  transition: transform 0.3s cubic-bezier(0.2, 1.6, 0.4, 1);
}
.csp-pip-on { color: var(--csp-color-text-primary); }
/* The page open now: lifted, with a halo. Its COLOUR is the state — green passed, yellow furthest. */
.csp-pip-on .csp-pip-dot {
  transform: scale(1.12);
  box-shadow: 0 0 0 3px var(--csp-color-surface), 0 0 0 6px var(--csp-color-accent-mid);
}
/* The furthest step reached and not yet advanced from: yellow, as the header's connect button is. */
.csp-pip-reached .csp-pip-dot { background: var(--csp-color-yellow); color: #16152B; }
.csp-pip-done { color: var(--csp-color-success-text); }
.csp-pip-done .csp-pip-dot { background: var(--csp-color-success); color: #FFFFFF; }
.csp-pip-done .csp-pip-number { display: none; }
.csp-pip-done .csp-pip-dot::after { content: '✓'; }
@media (max-width: 599.98px) {
  .csp-pip { font-size: 11px; }
  .csp-pips-spread { padding: 0; }
  .csp-pips-spread .csp-pips-track {
    left: calc(100% / (2 * var(--csp-pips-n, 5)));
    right: calc(100% / (2 * var(--csp-pips-n, 5)));
  }
}

.csp-night-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 18px;
  border-radius: 22px;
  background: var(--csp-color-night);
  color: var(--csp-color-night-text);
}
.csp-night-card-heading {
  margin: 0;
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--csp-color-night-muted);
}
.csp-night-card-body { margin: 0; font-family: var(--csp-fx-body-family); font-size: 15px; line-height: 1.45; }

/* The collection page's header card: the corp on the left, its market or its mint in 470px. */
.csp-header-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 470px;
  gap: 30px;
}
.csp-header-split > * { min-width: 0; }
@media (max-width: 1099.98px) {
  .csp-header-split { grid-template-columns: minmax(0, 1fr); }
}
/* What sits under both columns of the header card: stacked, then side by side where it is wide. */
.csp-header-actions { display: flex; flex-direction: column; gap: 16px; }
.csp-header-actions > * { min-width: 0; }
@media (min-width: 1100px) {
  .csp-header-actions {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    align-items: end;
  }
}

/* The form and its preview rail: the rail a fixed 330px beside the form, and under it on a phone. */
.csp-rail-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 330px;
  gap: 22px;
  align-items: stretch;
}
.csp-rail-split > * { min-width: 0; }
@media (max-width: 899.98px) {
  .csp-rail-split { grid-template-columns: minmax(0, 1fr); }
}

.csp-choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-width: 0;
  padding: 8px;
  border: 2px solid var(--csp-color-border);
  border-radius: 16px;
  background: var(--csp-color-surface);
  color: var(--csp-color-text-primary);
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  font-weight: 700;
  text-align: center;
  cursor: pointer;
  transition: transform 0.2s cubic-bezier(0.2, 1.5, 0.4, 1), box-shadow 0.2s;
}
.csp-choice:hover { transform: translateY(-4px); }
.csp-choice[aria-pressed='true'],
.csp-choice[aria-selected='true'] {
  border-color: transparent;
  background:
    linear-gradient(var(--csp-color-surface), var(--csp-color-surface)) padding-box,
    var(--csp-b2-iri-fill) border-box;
  box-shadow: 0 18px 36px -14px rgba(91, 63, 224, 0.55);
  transform: translateY(-4px);
}
.csp-choice:focus-visible { outline: 2px solid var(--csp-color-focus-ring); outline-offset: 2px; }
.csp-choice > * { max-width: 100%; }
/* The design's pair tile: square, its lines centred and tight. */
.csp-choice-square {
  position: relative;
  aspect-ratio: 1 / 1;
  justify-content: center;
  gap: 3px;
  padding: 12px 8px;
}
.csp-choice-mark { position: absolute; top: 6px; right: 6px; font-size: 12px; line-height: 1; }
.csp-choice img { border-radius: 10px; }

.csp-range-field {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  border-radius: 18px;
  background: var(--csp-color-inset);
}
.csp-range-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.csp-range-label { font-family: var(--csp-fx-body-family); font-size: 15px; font-weight: 800; color: var(--csp-color-text-primary); }
.csp-range-figure {
  font-family: var(--csp-fx-mono-family);
  font-variant-numeric: tabular-nums;
  font-size: 28px;
  font-weight: 700;
  color: var(--csp-color-split-referral);
}
.csp-range { width: 100%; height: 28px; margin: 0; accent-color: var(--csp-color-split-referral); cursor: pointer; }
.csp-range:disabled { cursor: default; opacity: 0.55; }

.csp-facts {
  padding: 2px 18px;
  border-radius: 18px;
  border: 1px solid var(--csp-color-border);
  background: var(--csp-color-surface);
}
.csp-fact-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px 16px;
  padding: 12px 0;
  border-bottom: 1px dashed var(--csp-color-border-control);
  font-family: var(--csp-fx-body-family);
  font-size: 15px;
  line-height: 1.4;
  color: var(--csp-color-text-muted);
}
.csp-fact-row:last-child { border-bottom: 0; }
.csp-split { display: flex; flex-direction: column; gap: 10px; }
.csp-split-bar { display: flex; gap: 2px; height: 14px; border-radius: 6px; overflow: hidden; }
.csp-split-bar > div { height: 100%; transition: width 0.3s; }
.csp-split-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  color: var(--csp-color-text-primary);
}
.csp-split-legend > span { display: inline-flex; align-items: center; gap: 6px; }
.csp-split-rows .csp-split-bar { height: 16px; }
.csp-split-rows .csp-split-legend { flex-direction: column; flex-wrap: nowrap; gap: 10px; font-size: 14px; }
.csp-split-rows .csp-split-legend > span { justify-content: space-between; }
.csp-split-rows .csp-split-legend > span > b { margin-left: auto; }
.csp-split-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 3px; }
.csp-split-seats { background: var(--csp-color-accent); }
.csp-split-creator { background: var(--csp-color-split-creator); }
.csp-split-creator-add {
  background: repeating-linear-gradient(135deg, var(--csp-color-split-creator) 0 3px, var(--csp-color-split-creator-stripe) 3px 6px);
}
.csp-split-team { background: var(--csp-color-split-team); }
.csp-fact-head {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0 4px;
  font-family: var(--csp-fx-body-family);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--csp-color-text-muted);
}
.csp-pip-button {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.csp-pip-button:hover .csp-pip-dot { transform: translateY(-2px); }
.csp-pip-button:focus-visible { outline: 2px solid var(--csp-color-focus-ring); outline-offset: 4px; border-radius: 12px; }
.csp-tag-button {
  display: inline-flex;
  align-items: center;
  padding: 5px 10px;
  border-radius: 99px;
  border: 1px dashed var(--csp-color-accent-mid);
  background: var(--csp-color-surface);
  color: var(--csp-color-accent-ink);
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.csp-tag-button:hover:not(:disabled) { background: var(--csp-color-lavender-soft); }
.csp-tag-button:disabled { opacity: 0.55; cursor: default; }
.csp-tag-button:focus-visible { outline: 2px solid var(--csp-color-focus-ring); outline-offset: 2px; }
.csp-fact-value {
  min-width: 0;
  margin-left: auto;
  font-weight: 700;
  text-align: right;
  overflow-wrap: anywhere;
  color: var(--csp-color-text-primary);
}

.csp-rule-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px;
  border-radius: 16px;
  border: 1px solid var(--csp-color-heat-track);
  background: var(--csp-color-sizzle-surface);
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  line-height: 1.4;
  color: var(--csp-color-text-secondary);
}
.csp-rule-card-title { font-size: 15px; font-weight: 800; color: var(--csp-color-text-primary); }

.csp-trait-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 5px 0;
  border-bottom: 1px solid var(--csp-color-divider);
  font-family: var(--csp-fx-body-family);
  font-size: 12px;
  color: var(--csp-color-text-primary);
}
.csp-trait-row span { font-size: 11px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; color: var(--csp-color-text-muted); }
.csp-trait-row b { font-weight: 800; text-align: right; }

.csp-inset-row {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  padding: 10px 12px;
  border-radius: 14px;
  background: var(--csp-color-inset-feed);
}

.csp-pill-plain {
  background: var(--csp-color-surface);
  border: 1px solid var(--csp-color-border-control);
  color: var(--csp-color-text-secondary);
  font-weight: 700;
}

.csp-checkbox-confirm .csp-checkbox-box {
  width: 26px;
  height: 26px;
  border-radius: 8px;
  border: 2px solid var(--csp-color-border-radio);
  background: var(--csp-color-surface);
  transition: transform 0.2s cubic-bezier(0.2, 1.6, 0.4, 1), background-color 0.2s, border-color 0.2s;
}
.csp-checkbox-confirm .csp-checkbox-input:checked + .csp-checkbox-box {
  background: var(--csp-color-success);
  border-color: var(--csp-color-success);
  box-shadow: none;
  transform: scale(1.1);
}
.csp-checkbox-label-confirm .csp-text { font-size: 15px; font-weight: 800; color: var(--csp-color-text-primary); }

/* ------------------------------------------------------------------------------------------ *
 * THE COLLECTION PAGE AND THE MINT WIZARD — figure tiles, the fine print, two callouts, the
 * wallet's steps, the coin pair, the wizard's portrait and its pages. (Its stepper, pick tiles and
 * receipt are the creator flow's: PipStepper, ChoiceTile, FactList.)
 * ------------------------------------------------------------------------------------------ */
.csp-figure-tile {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  padding: 12px 14px;
  border-radius: 16px;
  background: var(--csp-color-inset);
}
.csp-figure-tile-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--csp-fx-body-family);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--csp-color-text-muted);
}
.csp-figure-tile-value {
  font-family: var(--csp-fx-mono-family);
  font-variant-numeric: tabular-nums;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--csp-color-text-primary);
  overflow-wrap: anywhere;
}
.csp-figure-tile-hint { font-family: var(--csp-fx-body-family); font-size: 13px; line-height: 1.35; color: var(--csp-color-text-muted); }
.csp-figure-tile-lead {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 12px;
  padding: 16px 18px;
  background: var(--csp-color-inset-strong);
}
.csp-figure-tile-lead .csp-figure-tile-label { font-size: 13px; color: var(--csp-color-accent-ink); }
.csp-figure-tile-lead .csp-figure-tile-value { font-size: 30px; }
.csp-figure-tile-lead .csp-figure-tile-hint { flex-basis: 100%; }
.csp-figure-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
@media (max-width: 479.98px) {
  .csp-figure-tiles { grid-template-columns: minmax(0, 1fr); }
}
.csp-figure-tiles-spread > .csp-figure-tile-lead { grid-column: 1 / -1; }
@media (min-width: 1100px) {
  .csp-figure-tiles-spread {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
  }
  .csp-figure-tiles-spread > .csp-figure-tile-lead { grid-column: span 2; }
}

.csp-fine-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 9px 0;
  border-bottom: 1px dashed var(--csp-color-border);
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  line-height: 1.4;
}
.csp-fine-row:last-child { border-bottom: 0; }
.csp-fine-row-label { font-size: 11px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--csp-color-text-muted); }
.csp-fine-row-value { font-weight: 600; color: var(--csp-color-text-primary); min-width: 0; overflow-wrap: anywhere; }


.csp-notice {
  border-radius: 16px;
  padding: 12px 14px;
  font-family: var(--csp-fx-body-family);
  font-size: 14px;
  line-height: 1.45;
  min-width: 0;
}
.csp-notice-safe { background: var(--csp-color-success-surface); color: var(--csp-color-success-text); }
.csp-notice-info { background: var(--csp-color-inset); color: var(--csp-color-text-primary); }
.csp-notice-caution {
  align-self: flex-start;
  background: var(--csp-color-caution-surface);
  color: var(--csp-color-caution-text);
  font-weight: 700;
}

.csp-sig {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: 14px;
  background: var(--csp-color-inset);
  font-family: var(--csp-fx-body-family);
  font-weight: 700;
  font-size: 15px;
  color: var(--csp-color-text-primary);
  transition: background 0.3s;
}
.csp-sig-done { background: var(--csp-color-success-surface); color: var(--csp-color-success-text); }
.csp-sig-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.csp-sig-spin {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 3px solid var(--csp-color-border-control);
  border-top-color: var(--csp-color-accent);
  animation: csp-spin 0.8s linear infinite;
}
.csp-sig-tick { width: 20px; flex-shrink: 0; text-align: center; }
.csp-sig-tick::before { content: '\2705'; }

.csp-coin-pair { display: inline-flex; align-items: center; gap: 16px; flex-shrink: 0; }
.csp-coin-pair img { width: 84px; height: 84px; border-radius: 50%; display: block; object-fit: cover; }
.csp-coin-pair-coin { box-shadow: 0 0 0 4px var(--csp-color-gold), 0 12px 30px -10px rgba(180, 120, 0, 0.6); }
.csp-coin-pair-other { background: var(--csp-color-surface); box-shadow: 0 0 0 4px var(--csp-color-surface), 0 0 0 6px var(--csp-color-border); }
.csp-coin-pair-swap { font-size: 26px; color: var(--csp-color-text-faint); }
.csp-coin-pair-box {
  width: 84px;
  height: 84px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 40px;
  background: var(--csp-color-fire-chip-surface);
}
.csp-coin-pair-sm { gap: 10px; }
.csp-coin-pair-sm .csp-coin-pair-box { width: 52px; height: 52px; font-size: 24px; }
.csp-coin-pair-sm img { width: 52px; height: 52px; }
.csp-coin-pair-sm .csp-coin-pair-coin { box-shadow: 0 0 0 3px var(--csp-color-gold); }
.csp-coin-pair-sm .csp-coin-pair-swap { font-size: 18px; }



.csp-portrait {
  position: relative;
  overflow: hidden;
  border-radius: 20px;
  background: var(--csp-color-night);
  aspect-ratio: 1 / 1;
}
.csp-portrait img { width: 100%; height: 100%; display: block; object-fit: cover; }
.csp-portrait-badge {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  background: var(--csp-color-night);
  color: var(--csp-color-gold);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--csp-fx-body-family);
  font-size: 24px;
  font-weight: 800;
  animation: csp-wig 0.6s ease-in-out infinite;
}
/* `Portrait count`: the figure hangs off the corner, outside the picture's clip, pops in and then bobs. */
.csp-portrait-hold { position: relative; margin: 18px 18px 0 0; }
/*
 * `Portrait fit`: a SIZE container, so its own content has no say in its height. Beside another
 * column it is flex-basis 0 in a stretching mosaic item — the row is as tall as the other column,
 * and the picture is the largest square that fits it, 18px in for the count's overhang. Stacked (a
 * phone), it is a fixed 220px.
 */
.csp-portrait-fit { container-type: size; height: 220px; }
@media (min-width: 768px) {
  .csp-mosaic-item > .csp-portrait-fit { height: auto; flex: 1 1 0; min-height: 0; }
}
.csp-portrait-fit > .csp-portrait-hold { width: min(100cqw - 18px, 100cqh - 18px); }
.csp-portrait-count {
  position: absolute;
  top: -18px;
  right: -18px;
  min-width: 76px;
  height: 76px;
  padding: 0 14px;
  border-radius: 38px;
  background: var(--csp-color-yellow);
  color: #16152B;
  box-shadow: 0 0 0 4px var(--csp-color-surface), 0 12px 28px -8px rgba(255, 180, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--csp-fx-body-family);
  font-size: 34px;
  font-weight: 900;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  animation: csp-count-pop 0.7s cubic-bezier(0.2, 1.6, 0.4, 1) both, csp-count-bob 2.4s ease-in-out 0.7s infinite;
}
@keyframes csp-count-pop {
  from { transform: scale(0) rotate(-30deg); }
  to { transform: scale(1) rotate(8deg); }
}
@keyframes csp-count-bob {
  0%, 100% { transform: translateY(0) rotate(8deg); }
  50% { transform: translateY(-6px) rotate(3deg); }
}
@media (prefers-reduced-motion: reduce) {
  .csp-portrait-count { animation: none; transform: rotate(8deg); }
}
.csp-portrait-caption {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 12px;
  padding: 10px 12px;
  border-radius: 14px;
  background: rgba(22, 21, 43, 0.82);
  color: #FFFFFF;
  font-family: var(--csp-fx-body-family);
  font-size: 14px;
  font-weight: 700;
}
.csp-thumbs { display: flex; flex-wrap: wrap; gap: 8px; }
.csp-thumbs img, .csp-thumbs-more { width: 60px; height: 60px; border-radius: 14px; display: block; object-fit: cover; }
.csp-thumbs-more {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--csp-color-inset-strong);
  color: var(--csp-color-accent-ink);
  font-family: var(--csp-fx-body-family);
  font-size: 13px;
  font-weight: 800;
}

.csp-wizard-page { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* [hidden] has to win over the column above: an author display beats the UA's own [hidden] rule. */
.csp-wizard-page[hidden] { display: none; }

/* ------------------------------------------------------------------------------------------ *
 * Off-screen, nothing moves. `MotionZone` writes the attribute; see the note at the top.
 * ------------------------------------------------------------------------------------------ */
[data-motion='paused'],
[data-motion='paused']::before,
[data-motion='paused']::after,
[data-motion='paused'] *,
[data-motion='paused'] *::before,
[data-motion='paused'] *::after { animation-play-state: paused !important; }


@keyframes csp-mine-pulse { 50% { transform: scale(1.35); } }
@keyframes csp-burn-up {
  0% { clip-path: inset(0 0 0 0 round 22px); }
  100% { clip-path: inset(100% 0 0 0 round 22px); opacity: 0.4; }
}

/* A seat the reader already holds, in a mint's grid: the collections section's pink, pulsing. */
.csp-seat-mine { background: var(--csp-color-pink); animation: csp-mine-pulse 1.4s ease-in-out infinite; }

/* A card foot that never wraps: its button gives way instead, breaking its words inside its own height. */
.csp-card-foot-steady { flex-wrap: nowrap; }
.csp-card-foot-steady > :first-child { flex: 0 0 auto; }
.csp-card-foot-steady > :not(:first-child) { flex: 0 1 auto; min-width: 0; }
.csp-card-foot-steady .csp-button:not(.csp-social-square) {
  min-width: 0;
  white-space: normal;
  line-height: 1.15;
  text-align: center;
  padding-block: 4px;
}
.csp-card-foot-actions { display: flex; flex-wrap: nowrap; align-items: center; gap: 8px; min-width: 0; }
/* A glyph before a foot button's words (the copy mark on Copy referral link): sized, never squeezed. */
.csp-card-foot-actions .csp-button:not(.csp-social-square) { gap: 6px; }
.csp-card-foot-actions .csp-button > svg { width: 16px; height: 16px; flex: 0 0 auto; }

/* ---- Folder tabs fused to their panel (the profile's sections). ---- */
.csp-folder { display: flex; flex-direction: column; min-width: 0; }
.csp-folder-tabs { display: flex; align-items: flex-end; gap: 4px; flex-wrap: wrap; }
.csp-folder-tab {
  position: relative;
  z-index: 1;
  padding: 12px 22px;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: 18px 18px 0 0;
  font-family: var(--csp-fx-body-family);
  font-size: 18px;
  font-weight: 800;
  line-height: 1.2;
  color: var(--csp-color-text-muted);
  text-decoration: none;
}
.csp-folder-tab:hover { color: var(--csp-color-text-primary); }
.csp-folder-tab[aria-current='page'] {
  margin-bottom: -1px;
  padding-bottom: 13px;
  background: var(--csp-color-inset);
  border-color: var(--csp-color-border);
  color: var(--csp-color-text-primary);
}
.csp-folder-tab:focus-visible { outline: 2px solid var(--csp-color-focus-ring); outline-offset: 2px; }
.csp-folder-panel {
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-width: 0;
  padding: 24px;
  background: var(--csp-color-inset);
  border: 1px solid var(--csp-color-border);
  border-radius: 24px;
}
/* The corner is square only under the chosen tab: the first tab stands on it; any other does not. */
.csp-folder:has(.csp-folder-tab:first-child[aria-current='page']) > .csp-folder-panel { border-top-left-radius: 0; }
@media (max-width: 639.98px) {
  .csp-folder-panel { padding: 16px; }
}

/* ---- The quantity picker: − the number +, the number in the figure face. ---- */
.csp-quantity { display: inline-flex; align-items: center; gap: 10px; }
.csp-quantity-step { width: 44px; min-width: 44px; height: 44px; padding: 0; font-size: 22px; font-weight: 700; touch-action: manipulation; user-select: none; }
.csp-quantity-value {
  min-width: 40px;
  text-align: center;
  font-family: var(--csp-fx-mono-family);
  font-variant-numeric: tabular-nums;
  font-size: 26px;
  font-weight: 700;
  color: var(--csp-color-text-primary);
}

/* ---- The ringed picture. ---- */
.csp-halo {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: inline-flex;
  flex-shrink: 0;
  padding: 3px;
  border-radius: 50%;
}
.csp-halo::before {
  content: '';
  position: absolute;
  z-index: -1;
  top: -100%;
  left: 0;
  width: 300%;
  height: 300%;
  background: var(--csp-b2-iri);
  animation: csp-flow 5s ease-in-out infinite alternate;
}
.csp-halo-disc {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 34px;
  background: conic-gradient(#FF8AD8, #A48CFF, #6FD6FF, #8FF0B0, #FFE070, #FF8AD8);
  object-fit: cover;
}

/* ---- The top row: equal tiles, the chart wider. ---- */
.csp-stats-row { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr) 1.4fr; }
.csp-stats-row:has(> :only-child) { grid-template-columns: minmax(0, 1fr); }
.csp-stats-row:has(> :nth-child(3):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)) 1.4fr; }
@media (max-width: 899.98px) {
  .csp-stats-row,
  .csp-stats-row:has(> :nth-child(3):last-child) { grid-template-columns: minmax(0, 1fr); }
}

/* ---- One held collection. ----
 * The row lays itself out by ITS OWN width (a container), not the window's: it sits inside the
 * profile's folder panel and a ring, so the window says little about the room it has. */
.csp-holding { container: csp-holding / inline-size; min-width: 0; }
.csp-holding-row {
  display: grid;
  grid-template-columns: 230px 330px minmax(0, 1fr);
  gap: 22px;
  align-items: center;
}
.csp-holding-lead { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.csp-holding-share {
  display: flex;
  gap: 16px;
  align-items: center;
  padding: 14px;
  border-radius: 18px;
  background: var(--csp-color-inset);
  border: 1px solid var(--csp-color-divider);
  min-width: 0;
}
/*
 * The figures take EXACTLY the row the yield box sets (the owner: "the same top edge and the same
 * bottom edge"): the yield box and the figures both stretch to the row, and the figures add no
 * height of their own — their tiles are laid over the column, so the row's height is the lead's
 * and the yield box's, never theirs.
 */
.csp-holding-share { align-self: stretch; }
.csp-holding-figures { position: relative; align-self: stretch; min-width: 0; }
.csp-holding-foot {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding-top: 14px;
  border-top: 1px dashed var(--csp-color-border);
}
@container csp-holding (max-width: 900px) {
  .csp-holding-row { grid-template-columns: 230px minmax(0, 1fr); }
  .csp-holding-figures { grid-column: 1 / -1; }
  /* Under the other two there is no box beside them to match: the tiles take their own height. */
  .csp-tile-row-fill { position: static; }
}
@container csp-holding (max-width: 560px) {
  .csp-holding-row { grid-template-columns: minmax(0, 1fr); }
}
/*
 * As many tiles across as fit at a readable width, wrapping to the next row rather than squeezing —
 * and never wider than the row: min(132px, 100%) lets a tile shrink below its readable width
 * inside a column narrower than one, so the tiles fit the panel instead of stretching it.
 */
.csp-tile-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(132px, 100%), 1fr)); gap: 10px; align-items: stretch; min-width: 0; }
.csp-tile-row-end { display: flex; align-items: center; }
/*
 * FILL: a 2×2 grid laid over its column, filling it top to bottom — the Live panel's figures,
 * which must span exactly the yield box's height. Labels and figures keep the page's own sizes;
 * only the tiles' padding tightens, and a tile with an action puts it beside its figure so the
 * Claim fits the tile's height.
 */
.csp-tile-row-fill {
  position: absolute;
  inset: 0;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  gap: 8px;
}
.csp-tile-row-fill .csp-figure-tile { padding: 8px 12px; justify-content: center; min-height: 0; overflow: hidden; }
/* A label that wraps takes its own line height, not the page's 22px: two lines still fit the tile. */
.csp-tile-row-fill .csp-figure-tile-label { line-height: 14px; }
.csp-tile-row-fill .csp-figure-tile:has(.csp-figure-tile-action) {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-content: center;
  column-gap: 8px;
}
.csp-tile-row-fill .csp-figure-tile-label { grid-column: 1 / -1; }
.csp-tile-row-fill .csp-figure-tile-action { margin-top: 0; align-self: center; }
.csp-tile-row-fill .csp-figure-tile-action .csp-button { min-height: 30px; padding-inline: 12px; }

/* What is claimable, in the palette's green: the tile, the top card's ring and panel, its button. */
.csp-figure-tile-success { background: var(--csp-color-success-surface); }
.csp-figure-tile-success .csp-figure-tile-label { color: var(--csp-color-success-text); }
.csp-figure-tile-action { margin-top: 6px; display: flex; }
.csp-figure-tile-action > .csp-button { flex: 1 1 auto; }
.csp-ring-success {
  --csp-ring-gradient: linear-gradient(115deg, var(--csp-color-success), var(--csp-color-success-surface), var(--csp-color-success-text), var(--csp-color-success));
  --csp-ring-width: 3px;
  --csp-ring-panel: var(--csp-color-success-surface);
}
.csp-eyebrow-success { color: var(--csp-color-success-text); }
.csp-button-success { background: var(--csp-color-success); border-color: var(--csp-color-success); color: #FFFFFF; font-weight: 800; }
.csp-button-success:hover:not(:disabled) { background: var(--csp-color-success-text); border-color: var(--csp-color-success-text); }
.csp-button-success:disabled { opacity: 0.5; cursor: not-allowed; }
/* The top bar's wallet button, dressed as it is in the top bar wherever else it stands (`WalletSlot`). */
.csp-wallet-slot { display: inline-flex; }
.csp-wallet-slot .csp-button-yellow { min-height: 46px; border-radius: 14px; }

/* ---- The share grid: ten 9px squares to a row. ---- */
.csp-share-grid { display: grid; grid-template-columns: repeat(10, 9px); gap: 2px; flex-shrink: 0; }
.csp-share-cell { width: 9px; height: 9px; border-radius: 2px; background: var(--csp-color-seat-empty); }
.csp-share-seat { background: var(--csp-color-accent-mid); opacity: 0.55; }
.csp-share-mine { background: var(--csp-color-pink); animation: csp-mine-pulse 1.4s ease-in-out infinite; }

/* ---- The 14-day bars. ---- */
.csp-day-bars-box { display: flex; flex-direction: column; gap: 8px; }
.csp-day-bars {
  height: 86px;
  display: flex;
  align-items: flex-end;
  gap: 4px;
  border-bottom: 1px solid var(--csp-color-border);
}
.csp-day-bar { position: relative; flex: 1 1 0; height: 100%; display: flex; align-items: flex-end; }
.csp-day-bar-fill {
  width: 100%;
  min-height: 1px;
  border-radius: 4px 4px 0 0;
  background: var(--csp-color-accent-mid);
  transition: background 0.15s;
}
.csp-day-bar-today .csp-day-bar-fill { background: var(--csp-color-accent); }
.csp-day-bar:hover .csp-day-bar-fill { background: var(--csp-color-pink); }
.csp-day-bar-tip {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  padding: 5px 8px;
  border-radius: 8px;
  background: var(--csp-color-bubble-surface);
  color: var(--csp-color-bubble-text);
  font-family: var(--csp-fx-body-family);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s;
}
.csp-day-bar:hover .csp-day-bar-tip { opacity: 1; }
.csp-day-bars-ends {
  display: flex;
  justify-content: space-between;
  font-family: var(--csp-fx-body-family);
  font-size: 11px;
  font-weight: 700;
  color: var(--csp-color-text-faint);
}

/* ---- A notification row. ---- */
.csp-todo {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border-radius: 16px;
  background: var(--csp-color-surface);
  border: 1px solid var(--csp-color-border);
}
.csp-todo-caution { background: var(--csp-color-yellow-surface); border-color: var(--csp-color-caution-border); }
.csp-todo-icon { font-size: 26px; line-height: 1; flex-shrink: 0; }
.csp-todo-body { flex: 1 1 auto; min-width: 0; font-family: var(--csp-fx-body-family); font-weight: 800; color: var(--csp-color-text-primary); }
.csp-stickered-block { display: flex; width: 100%; }
.csp-stickered-block > :first-child { flex: 1 1 auto; min-width: 0; }

/* ---- A held collection's seats. ---- */
.csp-seat-thumbs { display: flex; gap: 6px; flex-shrink: 0; flex-wrap: wrap; }
.csp-seat-thumb {
  position: relative;
  width: 46px;
  height: 54px;
  border-radius: 12px;
  background: var(--csp-color-inset-strong);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  transition: transform 0.2s cubic-bezier(0.2, 1.6, 0.4, 1);
}
.csp-seat-thumb:hover { transform: translateY(-4px) rotate(-3deg); }
.csp-seat-thumb img { width: 30px; height: 30px; border-radius: 8px; object-fit: cover; display: block; }
.csp-seat-thumb-label {
  font-family: var(--csp-fx-mono-family);
  font-size: 9px;
  font-weight: 700;
  color: var(--csp-color-text-muted);
}
.csp-seat-thumb-locked::after { content: '🔒'; position: absolute; right: -4px; top: -6px; font-size: 13px; }
.csp-seat-thumb-more {
  background: var(--csp-color-tile-ink);
  color: var(--csp-color-tile-ink-text);
  font-family: var(--csp-fx-mono-family);
  font-size: 13px;
  font-weight: 700;
}

/* ---- The burn button. ---- */
.csp-button-burn {
  background: var(--csp-color-sizzle-surface);
  border: 2px solid var(--csp-color-fire);
  color: var(--csp-color-fire-text);
  font-weight: 800;
  white-space: nowrap;
}
.csp-button-burn:hover:not(:disabled) { background: var(--csp-color-fire); border-color: var(--csp-color-fire); color: #FFFFFF; }
.csp-button-burn:disabled { opacity: 0.55; cursor: not-allowed; }

/* ---- The redeem pop-up. ---- */
.csp-fact-tile {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px;
  border-radius: 14px;
  background: var(--csp-color-inset);
  font-family: var(--csp-fx-body-family);
  font-size: 14px;
  color: var(--csp-color-text-primary);
}
.csp-fact-tile-label { font-size: 11px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--csp-color-text-muted); }
.csp-fact-tile-value { font-size: 14px; font-weight: 800; }
.csp-fact-tile-critical { background: var(--csp-color-critical-surface); }
.csp-fact-tile-fire { background: var(--csp-color-sizzle-surface); }
.csp-fact-tile-success { background: var(--csp-color-success-surface); }
.csp-burn-stage { position: relative; width: 180px; height: 220px; }
.csp-burn-card {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 22px;
  box-shadow: 0 0 0 4px var(--csp-color-tile-ink);
  animation: csp-burn-up 2.5s ease-in forwards;
}
.csp-burn-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
.csp-burn-flames {
  position: absolute;
  left: -10px;
  right: -10px;
  bottom: -6px;
  height: 48px;
  z-index: 2;
  pointer-events: none;
  transform-origin: bottom;
  animation: csp-flick 0.5s ease-in-out infinite;
  background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 12' shape-rendering='crispEdges'><path fill='%23E8340C' d='M2 0h1v1h-1zM2 1h1v1h-1zM6 1h1v1h-1zM2 2h1v1h-1zM6 2h1v1h-1zM1 3h1v1h-1zM6 3h1v1h-1zM7 3h1v1h-1zM1 4h1v1h-1zM3 4h1v1h-1zM7 4h1v1h-1zM3 5h1v1h-1zM5 5h1v1h-1zM0 6h1v1h-1zM8 6h1v1h-1zM4 7h1v1h-1zM9 8h1v1h-1z'/><path fill='%23FF8A00' d='M2 3h1v1h-1zM2 4h1v1h-1zM6 4h1v1h-1zM1 5h1v1h-1zM2 5h1v1h-1zM6 5h1v1h-1zM7 5h1v1h-1zM1 6h1v1h-1zM3 6h1v1h-1zM5 6h1v1h-1zM6 6h1v1h-1zM7 6h1v1h-1zM0 7h1v1h-1zM1 7h1v1h-1zM3 7h1v1h-1zM5 7h1v1h-1zM7 7h1v1h-1zM8 7h1v1h-1zM0 8h1v1h-1zM4 8h1v1h-1zM5 8h1v1h-1zM8 8h1v1h-1zM9 9h1v1h-1z'/><path fill='%23FFD60A' d='M2 6h1v1h-1zM2 7h1v1h-1zM6 7h1v1h-1zM1 8h1v1h-1zM2 8h1v1h-1zM3 8h1v1h-1zM6 8h1v1h-1zM7 8h1v1h-1zM0 9h1v1h-1zM1 9h1v1h-1zM2 9h1v1h-1zM3 9h1v1h-1zM4 9h1v1h-1zM5 9h1v1h-1zM6 9h1v1h-1zM7 9h1v1h-1zM8 9h1v1h-1zM0 10h1v1h-1zM1 10h1v1h-1zM2 10h1v1h-1zM7 10h1v1h-1zM8 10h1v1h-1zM9 10h1v1h-1zM0 11h1v1h-1zM1 11h1v1h-1zM2 11h1v1h-1zM7 11h1v1h-1zM8 11h1v1h-1zM9 11h1v1h-1z'/><path fill='%23FFF3B0' d='M3 10h1v1h-1zM4 10h1v1h-1zM5 10h1v1h-1zM6 10h1v1h-1zM3 11h1v1h-1zM4 11h1v1h-1zM5 11h1v1h-1zM6 11h1v1h-1z'/></svg>") left bottom / 40px 48px repeat-x;
}
.csp-burn-flames::after {
  content: '';
  position: absolute;
  inset: 0;
  background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 12' shape-rendering='crispEdges'><path fill='%23E8340C' d='M7 0h1v1h-1zM3 1h1v1h-1zM7 1h1v1h-1zM3 2h1v1h-1zM7 2h1v1h-1zM2 3h1v1h-1zM3 3h1v1h-1zM0 4h1v1h-1zM2 4h1v1h-1zM6 4h1v1h-1zM8 4h1v1h-1zM0 5h1v1h-1zM4 5h1v1h-1zM6 5h1v1h-1zM8 5h1v1h-1zM1 6h1v1h-1zM9 6h1v1h-1zM5 7h1v1h-1z'/><path fill='%23FF8A00' d='M7 3h1v1h-1zM3 4h1v1h-1zM7 4h1v1h-1zM2 5h1v1h-1zM3 5h1v1h-1zM7 5h1v1h-1zM0 6h1v1h-1zM2 6h1v1h-1zM3 6h1v1h-1zM4 6h1v1h-1zM6 6h1v1h-1zM8 6h1v1h-1zM0 7h1v1h-1zM1 7h1v1h-1zM2 7h1v1h-1zM4 7h1v1h-1zM6 7h1v1h-1zM8 7h1v1h-1zM9 7h1v1h-1zM1 8h1v1h-1zM4 8h1v1h-1zM5 8h1v1h-1zM9 8h1v1h-1z'/><path fill='%23FFD60A' d='M7 6h1v1h-1zM3 7h1v1h-1zM7 7h1v1h-1zM0 8h1v1h-1zM2 8h1v1h-1zM3 8h1v1h-1zM6 8h1v1h-1zM7 8h1v1h-1zM8 8h1v1h-1zM0 9h1v1h-1zM1 9h1v1h-1zM2 9h1v1h-1zM3 9h1v1h-1zM4 9h1v1h-1zM5 9h1v1h-1zM6 9h1v1h-1zM7 9h1v1h-1zM8 9h1v1h-1zM9 9h1v1h-1zM0 10h1v1h-1zM1 10h1v1h-1zM2 10h1v1h-1zM7 10h1v1h-1zM8 10h1v1h-1zM9 10h1v1h-1zM0 11h1v1h-1zM1 11h1v1h-1zM2 11h1v1h-1zM7 11h1v1h-1zM8 11h1v1h-1zM9 11h1v1h-1z'/><path fill='%23FFF3B0' d='M3 10h1v1h-1zM4 10h1v1h-1zM5 10h1v1h-1zM6 10h1v1h-1zM3 11h1v1h-1zM4 11h1v1h-1zM5 11h1v1h-1zM6 11h1v1h-1z'/></svg>") left bottom / 40px 48px repeat-x;
  animation: csp-blink-hard 0.28s steps(1) infinite;
}

/* ---- The slide-in: the design's dark toast, bottom centre of the page. ---- */
.csp-slide-in {
  position: fixed;
  left: 50%;
  bottom: 40px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: min(560px, calc(100vw - 32px));
  padding: 14px 16px 14px 22px;
  border-radius: 16px;
  background: var(--csp-color-bubble-surface);
  color: var(--csp-color-bubble-text);
  font-family: var(--csp-fx-body-family);
  font-size: 16px;
  font-weight: 800;
  box-shadow: 0 20px 50px -14px rgba(22, 21, 43, 0.6);
  transform: translateX(-50%);
  animation: csp-slide-in-x 0.45s cubic-bezier(0.2, 1.3, 0.4, 1);
}
@keyframes csp-slide-in-x { from { opacity: 0; transform: translate(-50%, 24px) scale(0.96); } to { opacity: 1; transform: translate(-50%, 0); } }
.csp-slide-in-body { min-width: 0; }
@media (max-width: 639.98px) {
  .csp-slide-in { bottom: 88px; }
}

.csp-gap-0.csp-gap-0{ gap: var(--csp-space-0); }
.csp-p-0.csp-p-0{ padding: var(--csp-space-0); }
.csp-px-0.csp-px-0{ padding-inline: var(--csp-space-0); }
.csp-py-0.csp-py-0{ padding-block: var(--csp-space-0); }
.csp-h-0.csp-h-0{ height: var(--csp-space-0); }
.csp-gap-1.csp-gap-1{ gap: var(--csp-space-1); }
.csp-p-1.csp-p-1{ padding: var(--csp-space-1); }
.csp-px-1.csp-px-1{ padding-inline: var(--csp-space-1); }
.csp-py-1.csp-py-1{ padding-block: var(--csp-space-1); }
.csp-h-1.csp-h-1{ height: var(--csp-space-1); }
.csp-gap-2.csp-gap-2{ gap: var(--csp-space-2); }
.csp-p-2.csp-p-2{ padding: var(--csp-space-2); }
.csp-px-2.csp-px-2{ padding-inline: var(--csp-space-2); }
.csp-py-2.csp-py-2{ padding-block: var(--csp-space-2); }
.csp-h-2.csp-h-2{ height: var(--csp-space-2); }
.csp-gap-3.csp-gap-3{ gap: var(--csp-space-3); }
.csp-p-3.csp-p-3{ padding: var(--csp-space-3); }
.csp-px-3.csp-px-3{ padding-inline: var(--csp-space-3); }
.csp-py-3.csp-py-3{ padding-block: var(--csp-space-3); }
.csp-h-3.csp-h-3{ height: var(--csp-space-3); }
.csp-gap-4.csp-gap-4{ gap: var(--csp-space-4); }
.csp-p-4.csp-p-4{ padding: var(--csp-space-4); }
.csp-px-4.csp-px-4{ padding-inline: var(--csp-space-4); }
.csp-py-4.csp-py-4{ padding-block: var(--csp-space-4); }
.csp-h-4.csp-h-4{ height: var(--csp-space-4); }
.csp-gap-5.csp-gap-5{ gap: var(--csp-space-5); }
.csp-p-5.csp-p-5{ padding: var(--csp-space-5); }
.csp-px-5.csp-px-5{ padding-inline: var(--csp-space-5); }
.csp-py-5.csp-py-5{ padding-block: var(--csp-space-5); }
.csp-h-5.csp-h-5{ height: var(--csp-space-5); }
.csp-gap-6.csp-gap-6{ gap: var(--csp-space-6); }
.csp-p-6.csp-p-6{ padding: var(--csp-space-6); }
.csp-px-6.csp-px-6{ padding-inline: var(--csp-space-6); }
.csp-py-6.csp-py-6{ padding-block: var(--csp-space-6); }
.csp-h-6.csp-h-6{ height: var(--csp-space-6); }
.csp-gap-8.csp-gap-8{ gap: var(--csp-space-8); }
.csp-p-8.csp-p-8{ padding: var(--csp-space-8); }
.csp-px-8.csp-px-8{ padding-inline: var(--csp-space-8); }
.csp-py-8.csp-py-8{ padding-block: var(--csp-space-8); }
.csp-h-8.csp-h-8{ height: var(--csp-space-8); }
.csp-gap-10.csp-gap-10{ gap: var(--csp-space-10); }
.csp-p-10.csp-p-10{ padding: var(--csp-space-10); }
.csp-px-10.csp-px-10{ padding-inline: var(--csp-space-10); }
.csp-py-10.csp-py-10{ padding-block: var(--csp-space-10); }
.csp-h-10.csp-h-10{ height: var(--csp-space-10); }
.csp-gap-12.csp-gap-12{ gap: var(--csp-space-12); }
.csp-p-12.csp-p-12{ padding: var(--csp-space-12); }
.csp-px-12.csp-px-12{ padding-inline: var(--csp-space-12); }
.csp-py-12.csp-py-12{ padding-block: var(--csp-space-12); }
.csp-h-12.csp-h-12{ height: var(--csp-space-12); }
.csp-gap-16.csp-gap-16{ gap: var(--csp-space-16); }
.csp-p-16.csp-p-16{ padding: var(--csp-space-16); }
.csp-px-16.csp-px-16{ padding-inline: var(--csp-space-16); }
.csp-py-16.csp-py-16{ padding-block: var(--csp-space-16); }
.csp-h-16.csp-h-16{ height: var(--csp-space-16); }
.csp-gap-20.csp-gap-20{ gap: var(--csp-space-20); }
.csp-p-20.csp-p-20{ padding: var(--csp-space-20); }
.csp-px-20.csp-px-20{ padding-inline: var(--csp-space-20); }
.csp-py-20.csp-py-20{ padding-block: var(--csp-space-20); }
.csp-h-20.csp-h-20{ height: var(--csp-space-20); }