:root {
  --breakpoint-2xl: 1536px;
  --breakpoint-lg: 1024px;
  --breakpoint-md: 768px;
  --breakpoint-sm: 640px;
  --breakpoint-xl: 1280px;
  --color-accent: #7C3AED;
  --color-background: #F9FAFB;
  --color-border-strong: #A1A1AA;
  --color-border: #E4E4E7;
  --color-code-border: #27272A;
  --color-code-surface: #18181B;
  --color-code-text: #E4E4E7;
  --color-danger: #B91C1C;
  --color-info: #1D4ED8;
  --color-primary-fg: #FFFFFF;
  --color-primary: #4F46E5;
  --color-secondary-fg: #FFFFFF;
  --color-secondary: #6B7280;
  --color-success: #166534;
  --color-surface-soft: #F4F4F5;
  --color-surface: #FFFFFF;
  --color-text-muted: #52525B;
  --color-text-subtle: #71717A;
  --color-text: #18181B;
  --color-warning: #854D0E;
  --duration-dropdown-enter: 120ms;
  --duration-fast: 150ms;
  --duration-normal: 250ms;
  --duration-overlay-enter: 200ms;
  --duration-overlay-exit: 160ms;
  --duration-slow: 400ms;
  --duration-toast-enter: 220ms;
  --duration-toast-exit: 180ms;
  --easing-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --easing-ease-in: cubic-bezier(0.4, 0, 1, 1);
  --easing-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --font-body: 'Inter', system-ui, sans-serif;
  --font-heading: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
  --radii-full: 9999px;
  --radii-lg: 12px;
  --radii-md: 8px;
  --radii-none: 0px;
  --radii-sm: 4px;
  --radii-xl: 16px;
  --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.10), 0 4px 6px -2px rgba(0,0,0,0.05);
  --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.10), 0 2px 4px -1px rgba(0,0,0,0.06);
  --shadow-none: none;
  --shadow-sm: 0 1px 2px 0 rgba(0,0,0,0.05);
  --shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.10), 0 10px 10px -5px rgba(0,0,0,0.04);
  --spacing-2xl: 32px;
  --spacing-3xl: 48px;
  --spacing-lg: 16px;
  --spacing-md: 8px;
  --spacing-sm: 4px;
  --spacing-touch-target: 44px;
  --spacing-xl: 24px;
  --spacing-xs: 2px;
  --text-2xl: 1.5rem;
  --text-3xl: 1.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-sm: 0.875rem;
  --text-xl: 1.25rem;
  --text-xs: 0.75rem;
  --tk-com: #8C93B0;
  --tk-fn: #82AAFF;
  --tk-kw: #C792EA;
  --tk-num: #F78C6C;
  --tk-pn: var(--color-code-text);
  --tk-str: #C3E88D;
  --tk-type: #FFCB6B;
  --z-dropdown: 100;
  --z-modal: 300;
  --z-popover: 400;
  --z-sticky: 200;
  --z-toast: 500;
}
:root {
  --color-muted: var(--color-surface-soft);
  --color-surface-hover: var(--color-surface-soft);
  --color-border-subtle: var(--color-border);
  --color-border-hover: var(--color-border-strong);
  --color-primary-hover: color-mix(in srgb, var(--color-primary) 85%, var(--color-text));
  --color-primary-foreground: var(--color-primary-fg);
  --color-ring: var(--color-primary);
  --color-warn: var(--color-warning);
  --color-warn-soft: color-mix(in srgb, var(--color-warning) 15%, transparent);
  --color-warn-strong: color-mix(in srgb, var(--color-warning) 80%, var(--color-text));
}

/* Border-box everywhere: padding/borders count toward declared widths, so
   padded full-width bars don't overflow the viewport. The single most common
   reset every app needs — shipping it here means no app re-declares it. */
*, *::before, *::after { box-sizing: border-box; }
/* Base surface + typography floor. The page picks up the theme background/text
   tokens (so it's readable on any OS canvas, light or dark) and the --font-body
   family. Headings use --font-heading (falls back to body). Without an explicit
   body font-family the UA default (Times/serif) renders, which reads as
   unstyled. Apps override via their own WithCustomCSS (appended after this
   block — last rule wins). */
html { background-color: var(--color-background, #fff); }
body {
  margin: 0;
  min-height: 100vh;
  background-color: var(--color-background, #fff);
  color: var(--color-text, #18181b);
  font-family: var(--font-body, 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { font-family: var(--font-heading, var(--font-body, inherit)); }
/* Columns of numbers (money, counts) align when figures are tabular. */
[data-fui-comp="ui-data-table"] td,
[data-fui-comp="ui-stat-card"],
[data-fui-comp="ui-bar-chart"],
[data-fui-comp="ui-detail-list"] .ui-detail-list__value,
.ui-money,
td[data-align="end"] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}
/* The SPA runtime focuses the <main id="main-content"> landmark after a
   client-side nav so screen readers announce the new page. It's a
   programmatic (tabindex=-1) focus target, not a tabbable control, so the
   browser's default focus ring around the whole content region is noise. */
#main-content:focus, main[tabindex="-1"]:focus { outline: none; }
/* Visually-hidden helper — exposed under BOTH .fui-* (framework runtime
   uses this for the SPA route-announce region) and .ui-* (framework/ui
   components — CommandPalette's SR-only trigger, CopyButton's status
   region, etc.). Apps no longer have to call ui.BaseCSS() to opt in;
   the helper is part of the built-in auto-emitted app.css floor. Apps
   that want a custom visually-hidden recipe can override either class
   via their own WithCustomCSS — last rule wins. */
.fui-visually-hidden, .ui-visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* Skip-link: visually hidden until focused, then revealed as a
   visible overlay so keyboard users can jump to #main-content.
   Apps can override via their own .skip-link / .skip-link:focus
   rules. */
.skip-link {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.skip-link:focus {
  position: fixed !important;
  top: 8px; left: 8px;
  width: auto; height: auto;
  padding: 8px 16px;
  margin: 0;
  overflow: visible;
  clip: auto;
  white-space: normal;
  z-index: 9999;
  background: var(--color-text, #18181B);
  color: var(--color-background, #FAFAFA);
  border-radius: 4px;
  font: 0.9rem system-ui, -apple-system, sans-serif;
  text-decoration: none;
}
/* SPA-nav failure toast — shown when loadPage can't fetch the new
   page (offline, server error). Positioned bottom-right; auto-hides
   after 4s via the runtime. Strict-CSP-clean (no inline styles). */
.fui-nav-toast {
  position: fixed;
  right: 16px; bottom: 16px;
  z-index: 9999;
  max-width: calc(100vw - 32px);
  padding: 12px 16px;
  /* Reuses the theme's always-dark inkwell pair rather than hardcoding a
     palette or inventing an inverse-surface token. CodeSurface/CodeText are
     deliberately non-inverting and contrast-tuned in both schemes, which is
     exactly what a transient overlay wants — and unlike literal hex, a themed
     app's toast now tracks its palette. */
  background: var(--color-code-surface, #18181B);
  color: var(--color-code-text, #FAFAFA);
  border-radius: 8px;
  font: 0.9rem system-ui, -apple-system, sans-serif;
  box-shadow: 0 10px 25px rgba(0,0,0,0.25);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.18s, transform 0.18s;
  pointer-events: none;
}
.fui-nav-toast.is-visible {
  opacity: 1;
  transform: translateY(0);
}
/* Progress indicator on slow SPA navigation. The bar tracks the theme's
   primary color via the token — a literal hex here would keep the bar
   indigo in a teal app, and would ignore a per-request theme variant
   entirely (the whole point of app.css being theme-addressed). The
   fallback only applies if a host serves this CSS with no :root tokens. */
html[aria-busy="true"] {
  cursor: progress;
}
html[aria-busy="true"]::after {
  content: '';
  position: fixed;
  inset: 0 0 auto 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, currentColor 50%, transparent);
  animation: fui-nav-progress 1s linear infinite;
  z-index: 9999;
  pointer-events: none;
  color: var(--color-primary, #4F46E5);
}
@keyframes fui-nav-progress {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}
/* Layout body: sidebar column beside the content as a flex row at >= md;
   stacks at < md where the sidebar collapses to a hamburger drawer. */
.layout-body { display: flex; align-items: stretch; min-height: 100vh; }
.layout-body > main, .layout-body > .layout-content { flex: 1 1 auto; min-width: 0; }
.layout-body > nav { flex: 0 0 auto; background-color: var(--color-surface, #fff); border-right: 1px solid var(--color-border, #e4e4e7); }

/* Embed layout (EmbedLayout): sizes to its CONTENT, never to the viewport.
   An embedded surface lives in an iframe that the host page resizes to the
   height the frame reports. With min-height: 100vh that reported height is
   partly the frame's own height, so each report grows the frame, which grows
   100vh, which grows the next report — the panel ratchets open with a band of
   empty space under the content. Caught in a screenshot; invisible to any DOM
   assertion, because every element is present and correct. */
.layout-embed .layout-body { min-height: 0; }
@media (max-width: 47.99rem) {
  .layout-body { display: block; }
  .layout-body > nav { border-right: none; border-bottom: 1px solid var(--color-border, #e4e4e7); }
}

/* Contained layout (WithContainer): a centered editorial column for the
   content, while the header and footer span FULL WIDTH (edge-to-edge border)
   with their inner content centered to the same measure — the normal marketing
   header shape. The wrapper itself is full-width so its background/borders go
   end to end; the centering is per-region. The flex column keeps the footer at
   the bottom. --ui-layout-container-width sets the measure (default 66rem),
   --ui-layout-gutter the minimum side gutter. */
.layout--contained {
  --ui-layout-gutter: clamp(20px, 5vw, 32px);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.layout--contained .layout-body { display: block; min-height: 0; flex: 1 0 auto; }
/* Content column: centered to the measure. */
.layout--contained main, .layout--contained .layout-content {
  inline-size: 100%;
  max-inline-size: var(--ui-layout-container-width, 66rem);
  margin-inline: auto;
  padding-inline: var(--ui-layout-gutter);
  display: flex;
  flex-direction: column;
  gap: clamp(24px, 4vw, 36px);
  padding-block: clamp(40px, 6vw, 64px) clamp(48px, 7vw, 80px);
}
/* Sections and the hero get extra room above; prose keeps the tighter rhythm.
   Screens wrap their blocks in a div, so match both main>… and main>div>…. */
.layout--contained main > [data-fui-comp="ui-section"]:not(:first-child),
.layout--contained main > [data-fui-comp="ui-hero"]:not(:first-child),
.layout--contained main > div > [data-fui-comp="ui-section"]:not(:first-child),
.layout--contained main > div > [data-fui-comp="ui-hero"]:not(:first-child) { margin-top: clamp(24px, 4vw, 48px); }
/* Header + footer: full-bleed band, content centered to the measure by padding
   the difference (so the bottom border still spans edge to edge). */
.layout--contained [data-fui-comp="ui-site-header"],
.layout--contained [data-fui-comp="ui-site-footer"] {
  padding-inline: max(var(--ui-layout-gutter), calc((100% - var(--ui-layout-container-width, 66rem)) / 2));
}
.layout--contained [data-fui-comp="ui-site-header"] {
  block-size: auto;
  padding-block: clamp(14px, 3vw, 22px);
  border-bottom: 1px solid var(--color-border, #e4e4e7);
}
.layout--contained [data-fui-comp="ui-site-header"] .ui-site-header__links { margin-inline-start: auto; }
/* Long-form content prose (heading + paragraph blocks live in the screen's
   wrapper div): a comfortable measure + vertical rhythm so /about, /terms read
   like prose, not a full-width wall. */
.layout--contained main > h1, .layout--contained main > div > h1 {
  font-size: clamp(2rem, 4vw, 2.75rem); line-height: 1.1; letter-spacing: -0.02em; max-width: 24ch; margin: 0;
}
.layout--contained main > p, .layout--contained main > div > p {
  max-width: 68ch; line-height: 1.7; margin: 0;
}
.layout--contained main > div > h1 + p,
.layout--contained main > div > p + p { margin-top: 1.1rem; }
/* The markdown content block keeps its own measure on long-form pages. */
.layout--contained main > div > [data-fui-comp="ui-markdown"] { max-width: 72ch; }

/* Sidebar shell banner: the contained layout styles its header above, but a
   WithSidebar + WithHeader shell needs its own chrome — ui.SiteHeader is
   block-size: 100%, which resolves against a parent with no height, so
   without this rule the banner collapses to text height over the bare page
   background. --ui-layout-header-height follows the --ui-layout-* override
   convention (see --ui-layout-container-width). */
.layout--has-sidebar > header {
  display: flex;
  align-items: stretch;
  block-size: var(--ui-layout-header-height, 56px);
  background-color: var(--color-surface, #fff);
  border-bottom: 1px solid var(--color-border, #e4e4e7);
}

/* Sticky header (WithStickyHeader): a header component cannot stick to the
   page from inside the wrapper, because a sticky element only travels
   inside its parent's box and the component's parent is the wrapper, whose
   height is the header itself. The WRAPPER is the element that sticks.
   Background: the wrapper is transparent by default, so without one the
   page content scrolls visibly underneath it. --ui-layout-header-bg
   follows the --ui-layout-* override convention; stacking uses the
   theme's --z-sticky layer (200, above app chrome, below overlays). */
.layout--sticky-header > header {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky, 200);
  background-color: var(--ui-layout-header-bg, var(--color-background, #fff));
}
/* A sticky header makes the shell's dead scroll visible. .layout-body is
   min-height: 100vh, so a header above it means a short page scrolls by
   exactly the header's height with nothing in the overflow — under a
   pinned header that reads as broken rather than as a stray pixel. The
   contained and sidebar shells each fix this their own way already
   (min-height: 0 there, a calc() subtracting a FIXED header height here);
   a flex column fixes it for a header of any height, which is what a
   caller-supplied component is. */
.layout--sticky-header {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.layout--sticky-header > .layout-body {
  flex: 1 0 auto;
  min-height: 0;
}
/* .layout-body is min-height: 100vh, so a banner above it makes every page
   scroll by exactly the header height with nothing in the overflow; the
   body under a sidebar-shell banner subtracts it. */
.layout--has-sidebar > header + .layout-body {
  min-height: calc(100vh - var(--ui-layout-header-height, 56px));
}

/* Sidebar shell: a padded content area beside the nav. */
.layout--has-sidebar main, .layout--has-sidebar .layout-content {
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: clamp(24px, 3vw, 40px);
}

