:root {
  --breakpoint-2xl: 1536px;
  --breakpoint-lg: 1024px;
  --breakpoint-md: 768px;
  --breakpoint-sm: 640px;
  --breakpoint-xl: 1280px;
  --color-accent: oklch(0.79 0.12 164);
  --color-background: oklch(0.155 0.012 168);
  --color-border-strong: oklch(0.43 0.025 168);
  --color-border: oklch(0.32 0.018 168);
  --color-code-border: #27272A;
  --color-code-surface: #18181B;
  --color-code-text: #E4E4E7;
  --color-danger: oklch(0.72 0.16 25);
  --color-info: oklch(0.75 0.10 235);
  --color-primary-fg: oklch(0.18 0.025 168);
  --color-primary: oklch(0.79 0.12 164);
  --color-secondary-fg: #FFFFFF;
  --color-secondary: #6B7280;
  --color-success: oklch(0.76 0.13 154);
  --color-surface-soft: oklch(0.245 0.018 168);
  --color-surface: oklch(0.205 0.015 168);
  --color-text-muted: oklch(0.72 0.024 168);
  --color-text-subtle: oklch(0.60 0.022 168);
  --color-text: oklch(0.94 0.012 168);
  --color-warning: oklch(0.82 0.13 82);
  --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: 'Hanken Grotesk', system-ui, sans-serif;
  --font-heading: 'Bricolage Grotesque', 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;
  background: #18181B;
  color: #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. Apps can override the
   color via .fui-nav-busy { background: ... } */
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: #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); }
@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: 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);
}


/* Brand mark — three bars in a rounded chip; a link home from every screen.
   The bar dimensions (2-4-8-10-12px) are a hand-drawn glyph, not scale spacing. */
.gb-brand{display:inline-flex;align-items:center;gap:10px;font-family:var(--font-heading);font-size:19px;font-weight:700;letter-spacing:-.02em;color:inherit;text-decoration:none}
.gb-brand-mark{width:30px;height:30px;display:block;flex:none}
.gb-brand:hover .gb-brand-mark{opacity:.85}

/* The landing headline is an h1 kept deliberately quiet — the tool leads.
   The double-class selector outranks the layout's heading scale
   (.layout--contained main > div > h1). */
.layout--contained main h1.gb-eyebrow,h1.gb-eyebrow{margin:clamp(8px,2vw,20px) 0 0;font-family:var(--font-body);font-size:.95rem;font-weight:400;line-height:1.5;color:var(--color-text-muted);letter-spacing:.01em}

/* A quiet running total under the headline. The count itself carries the brand
   accent + heading face; the rest stays muted, so it reads as a fact, not a boast. */
.gb-generated{margin:6px 0 0;font-size:.9rem;color:var(--color-text-muted);line-height:1.5}
.gb-generated strong{font-family:var(--font-heading);font-weight:600;color:var(--color-primary);font-variant-numeric:tabular-nums}

/* The quick generator: content input + download side by side. */
.gb-tool{display:grid;grid-template-columns:minmax(0,1fr) clamp(220px,26vw,280px);gap:clamp(28px,5vw,60px);align-items:start;margin-top:clamp(28px,5vw,44px)}
.gb-quickrow{display:flex;gap:10px;align-items:stretch}
.gb-quickrow input{flex:1;min-width:0}

/* Type chips — radio pills. The input is visually hidden but focusable so
   keyboard and screen-reader flows work; :has() paints the checked pill. */
.gb-chips{display:flex;flex-wrap:wrap;gap:var(--spacing-md);margin-top:var(--spacing-lg)}
.gb-chip{position:relative;display:inline-flex;align-items:center;padding:7px 14px;border:1px solid var(--color-border);border-radius:var(--radii-full);font-size:.85rem;color:var(--color-text-muted);cursor:pointer;user-select:none;transition:color 120ms ease,background 120ms ease,border-color 120ms ease}
.gb-chip:hover{color:var(--color-text);border-color:var(--color-border-strong)}
.gb-chip input{position:absolute;inset:0;opacity:0;margin:0;cursor:pointer}
.gb-chip:has(input:checked){background:var(--color-primary);border-color:var(--color-primary);color:var(--color-primary-fg);font-weight:600}
.gb-chip:has(input:focus-visible){outline:2px solid var(--color-primary);outline-offset:2px}
.gb-chip-more{border-style:dashed;text-decoration:none}

/* The paper stage — the one bright surface, reserved for the codes. The soft
   14px radius and the deep glow are bespoke: this card is meant to feel like
   a physical print, distinct from the framework's component radii. */
.gb-paper{background:oklch(.965 .009 96);border-radius:14px;padding:clamp(14px,2vw,22px);box-shadow:0 24px 60px -40px oklch(.03 .02 168 / .9)}
.gb-paper img{display:block;width:100%;aspect-ratio:1;object-fit:contain;transition:opacity 160ms ease}
.gb-paper img[hidden]{display:none}
/* When the current input can't render, the paper leaves with the image —
   an empty white card is worse than no card. The error line remains. */
.gb-stage.is-error .gb-paper{display:none}
.gb-stage.is-loading .gb-paper img{opacity:.35}
.gb-previnfo{display:flex;justify-content:space-between;gap:12px;margin-top:10px;font-size:.8rem;color:var(--color-text-subtle)}
.gb-stageactions{display:flex;flex-wrap:wrap;gap:14px;margin-top:14px;justify-content:center}

/* The quiet facts row under the landing tool. */
.gb-facts{margin-top:clamp(44px,7vw,76px);border-top:1px solid var(--color-border);padding-top:clamp(24px,4vw,36px);display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:clamp(24px,4vw,40px)}
.gb-fact{margin:0;font-size:.95rem;color:var(--color-text-muted);line-height:1.6}
.gb-fact strong{display:block;color:var(--color-text);font-weight:600;margin-bottom:6px;font-family:var(--font-heading);letter-spacing:-.01em}
.gb-fact p{margin:0 0 12px}

/* Paired form fields align to their tops — a taller neighbor (help text,
   wrapped label) must not stretch its siblings' controls. */
#gen-form .ui-grid{align-items:start}

/* Studio mode tabs — a segmented-control look (recessed track + raised active
   segment), but real nav links so each mode is a routable, deep-linkable page.
   The runtime toggles aria-current/.active on sibling navigation. */
.gb-studio-tabs{display:inline-flex;gap:var(--spacing-xs);width:fit-content;max-width:100%;padding:var(--spacing-sm);border:1px solid var(--color-border);border-radius:var(--radii-lg);background:var(--color-surface)}
.gb-studio-tabs a{padding:8px 18px;border-radius:var(--radii-md);font-size:.9rem;font-weight:500;color:var(--color-text-muted);text-decoration:none;white-space:nowrap;transition:color 120ms ease,background 120ms ease}
.gb-studio-tabs a:hover{color:var(--color-text)}
.gb-studio-tabs a[aria-current="page"],.gb-studio-tabs a.active{background:var(--color-surface-soft);color:var(--color-text);font-weight:600;box-shadow:0 1px 2px -1px oklch(0 0 0/.5)}

/* Code blocks carry long unbreakable URLs (the AI prompt, generate links) —
   they must scroll inside their own frame, never stretch the page measure.
   Grid/flex ancestors default min-width:auto, which lets the pre's
   min-content width blow past the section; zero it on the containers that
   hold a code block. */
main [data-fui-comp="ui-code-block"]{max-inline-size:100%;min-inline-size:0;overflow-x:auto}
main .ui-section__body:has([data-fui-comp="ui-code-block"]),
main .ui-stack:has(> [data-fui-comp="ui-code-block"]){min-inline-size:0;max-inline-size:100%}

/* Every studio mode panel sits the same distance below the tab strip. */
.gb-studio-panel{margin-top:clamp(20px,3vw,32px)}
/* Studio: form beside an always-visible stage. */
.gb-studio{display:grid;grid-template-columns:minmax(280px,400px) minmax(0,1fr);gap:clamp(28px,4vw,60px);align-items:start}
.gb-studio-stage{position:sticky;top:var(--spacing-xl);justify-self:center;width:min(340px,100%)}
.gb-stage-load{position:absolute;display:none}
.gb-stage.is-loading .gb-stage-load{display:block}
.gb-stage{position:relative}

/* Share link beside its copy button in the API fact. */
.gb-share-link{align-self:center;color:var(--color-text-muted);font-size:.85rem;word-break:break-all}
/* Cookie honesty notice — floating card, bottom-left. Soft 14px radius + glow
   match the paper card, so it's a literal alongside them. */
.gb-cookie{position:fixed;left:var(--spacing-lg);bottom:var(--spacing-lg);z-index:60;max-width:min(520px,calc(100vw - 32px));border:1px solid var(--color-border-strong);border-radius:14px;box-shadow:0 18px 50px -24px oklch(0 0 0 / .8)}

/* Author backlink in the footer bottom strip. */
.gb-author{color:var(--color-primary);text-decoration:none}
.gb-author:hover{text-decoration:underline}
.gb-gh-mark{vertical-align:-2px;margin-right:3px}

/* Inline status messages (aria-live regions under forms). */
.gb-msg{margin:0;min-height:1.4em;color:var(--color-text-muted);font-size:.85rem;line-height:1.45}
.gb-msg.err{color:var(--color-danger)}
.gb-msg.ok{color:var(--color-success)}

/* Staged-file affordance: once a file is chosen the runtime marks the
   filename node .is-populated — make the whole dropzone read as "armed" so
   it's obvious a file is loaded and ready, not still waiting for one. */
.gb-batch-upload:has(.ui-fileupload__filename.is-populated) .ui-fileupload__zone{border-style:solid;border-color:var(--color-primary);background:oklch(.79 .12 164 / .06)}
.gb-batch-upload:has(.is-populated) .ui-fileupload__prompt{opacity:.55;font-size:.9rem}
.gb-batch-upload .ui-fileupload__filename.is-populated .ui-fileupload__list{margin:var(--spacing-md) 0 0;padding:0;list-style:none}
.gb-batch-upload .ui-fileupload__filename.is-populated li{color:var(--color-primary);font-weight:600;font-size:var(--text-base)}
.gb-batch-upload .ui-fileupload__filename.is-populated li::before{content:"✓ ";font-weight:700}

/* Batch per-item failures: a scannable list a user works down and fixes. */
.gb-batch-errors{list-style:none;margin:0;padding:0;display:grid;gap:var(--spacing-md)}
.gb-batch-errors li{padding:10px 14px;border:1px solid var(--color-border);border-radius:var(--radii-md);background:var(--color-surface);font-size:.85rem;line-height:1.5}
.gb-batch-errors b{font-family:var(--font-heading);font-weight:600;color:var(--color-text);margin-right:var(--spacing-md)}
.gb-batch-errors .gb-batch-reason{color:var(--color-danger)}
.gb-batch-errors code{color:var(--color-text-muted);word-break:break-all}

@media(max-width:820px){
  .gb-tool{grid-template-columns:1fr}
  .gb-tool .gb-stage{max-width:300px}
  .gb-studio{grid-template-columns:1fr}
  .gb-studio-stage{position:static;justify-self:start}
}
/* Override the framework's default indigo progress bar with celadon. */
html[aria-busy="true"]::after{color:var(--color-primary)}
/* Tame the toast dismiss button. The framework ships a filled 44x44 circle
   (WCAG tap target + --color-surface-soft background) that dominates a compact
   toast. Keep a comfortable target but drop the filled look — transparent by
   default, a subtle circle only on hover. Extra ancestor qualifier raises the
   specificity so this wins over the lazily-loaded component sheet regardless of
   load order. */
[data-fui-toast-stack] [data-fui-comp="ui-notification"] .ui-notification__dismiss{min-inline-size:2rem;min-block-size:2rem;background:transparent;color:var(--color-text-muted)}
[data-fui-toast-stack] [data-fui-comp="ui-notification"] .ui-notification__dismiss:hover{background:var(--color-surface-soft);color:var(--color-text)}
