/*
 * #342: this used to @import all 50 files in ./behaviors/*.css
 * unconditionally — every page paid for every behavior's CSS whether it
 * used that behavior or not (confirmed: home.html uses 9 wb-* tags + 6 x-*
 * behaviors out of ~75 available). Everything except the two files below
 * now loads just-in-time via src/core/style-loader.js's ensureBehaviorCss(),
 * called from WB.inject()'s single dispatch point right before a behavior
 * first runs — see src/styles/behavior-css-manifest.js for the full
 * behaviorName -> file mapping and the reasoning behind what's excluded
 * (modal.css and stock.css styled behaviors that no longer exist and are
 * deleted, #1190 and #1172).
 *
 * layout.css and ui-utils.css stay here as unconditional imports: both are
 * under 1.5KB, and layout.css's real content (.x-grid--alt-rows) backs
 * <div x-grid>, a genuine custom element that never calls WB.inject() and so
 * never passes through the JIT hook.
 */
@import url('./behaviors/layout.css');
@import url('./behaviors/ui-utils.css');
@import url('./behaviors/code.css');

/**
 * WB Starter - Site Layout Styles
 * Works with themes.css from x-behaviors
 */

/* Reset */
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }

/* #917: an example's host is <figure x-demo>, and the UA gives a <figure>
   margin (1em top and bottom, 40 pixels each side) that a <div> host never had. demo.css undoes it, but
   demo.css loads just-in-time with the demo behavior, so until then every
   demo sat 16px lower and 80px narrower than on a <div> -- enough to change
   which demos are in view while the page's first scan decides what to build
   (measured on demos/site/overlays.html). Undone here, before any behavior,
   at zero specificity so any author rule still wins. */
:where(figure[x-demo]) { margin: 0; }

/* #274: prose <li>s have zero margin by default, so a long item that wraps
   to 2+ lines sits flush against the next item -- their line boxes visually
   touch/overlap with no gap at all. Seen project-wide on any content page
   with wrapped list text (about.html, releases.html, etc). */
li { margin-bottom: 0.35em; }
li:last-child { margin-bottom: 0; }

/* =============================================================================
   STANDALONE DEMO PAGE CONTAINER
   ============================================================================= */
/* demos/site/*.html pages are standalone (not routed through .site/.site__body,
   which is the app shell used by pages/*.html — see components.html). Without
   this, their content sits flush against the viewport edge with zero page
   margin, unlike every shell-routed page. index.html already worked around
   this with its own one-off inline <style>; this class gives every other
   standalone demo page the same breathing room via one shared rule. */
.demo-page {
  max-width: 68.75rem;
  margin: 2rem auto;
  padding: 0 1.5rem;
  /* The dev server wraps pages/*.html fragments as
     <body class="site"><div class="demo-page">, and .site is a flex COLUMN.
     A column flex item with `margin: auto` is not stretched -- it shrinks to
     fit its content, and never below its min-content width. So the widest
     unbreakable thing on the page set the width of the whole page: home.html's
     15-band audio EQ made .demo-page 486px wide at a 375px viewport, and every
     block on the page overflowed with it. An explicit width makes it fill the
     body like a block would; max-width + auto margins still centre it. */
  width: 100%;
}

/* #450's mobile pattern (body > .page, doc-viewer.html) applied here too --
   1.5rem side padding is ~36% of a 375px viewport's width. */
@media (max-width: 40rem) {
  .demo-page,
  body > .page {
    padding: 0 var(--space-md, 1rem);
  }
}

/* =============================================================================
   SITE LAYOUT
   ============================================================================= */
/* <body> while something that covers the page is open: the drawer, offcanvas
   and sheet each put their own modifier there (overlay.js), and site-engine.js
   its own while the mobile nav is open. Each removes only its own, so closing a
   sheet no longer unlocks the page under an open drawer (#1096). */
.x-drawer--scroll-lock,
.x-offcanvas--scroll-lock,
.x-sheet--scroll-lock,
.site--scroll-lock {
  overflow: hidden;
}

/* overlay.js puts it on <body> while a sheet is dragged wider or narrower. */
.x-sheet--resizing {
  cursor: ew-resize;
  user-select: none;
}

.site {
  /* Fixed to the viewport + clip overflow so .site__body is the SINGLE scroll
     container (was min-height:100vh, which let the whole window scroll too — a
     dual-scroll-container bug that broke the sticky nav and section anchoring).
     100dvh (dynamic viewport height) is the fallback-safe upgrade to 100vh:
     100vh is fixed to the LARGEST possible viewport, which on real browsers
     (not devtools emulation) can be taller than what's actually visible once
     browser chrome/toolbars are accounted for — the difference shows up as
     dead space the footer can't reach. Unsupported browsers just keep 100vh. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: var(--bg-color);
  color: var(--text-primary);
}

.site__loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  gap: 1rem;
  color: var(--text-muted);
}

/* src/index.js's showError() (#779: formerly style.display + style=""). */
.site__loading.loading--error [x-spinner] {
  display: none;
}
.site__loading-icon { color: #ef4444; font-size: 1.5rem; }
.site__loading-title { color: #ef4444; }
.site__loading-message { opacity: 0.7; display: block; margin-top: 0.5rem; }
.site__loading-note { opacity: 0.5; }

/* src/main.js: the stack of a failed site init (#779: was style.cssText). */
.site__init-error {
  margin: 1rem;
  padding: 1rem;
  background: #1e1e1e;
  color: #f87171;
  border-radius: 0.5rem;
  overflow: auto;
  font-size: 0.75rem;
}

.site__body {
  display: flex;
  /* flex: 1 (flex-basis: 0%) is what actually sizes this — it grows to fill
     whatever space header + footer don't use, so it self-corrects if either
     one's height ever changes. A literal `height: calc(100vh - 60px)` used to
     sit here too: harmless today only because flex-basis:0% overrides it for
     sizing, but it quietly went stale the moment the header grew from 60px to
     64px (see .site__header below) — dead, misleading code, removed. */
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
}

/* =============================================================================
   HEADER
   ============================================================================= */
/* Exposed so other fixed-position UI (toast.css's .x-toast__container) can
 * anchor itself below the header instead of duplicating the 4rem value.
 * Defaults to 0 so standalone pages with no .site__header aren't pushed
 * down for a header that doesn't exist. */
:root {
  --site-header-height: 0rem;
}
body:has(.site__header) {
  --site-header-height: 4rem;
}

.site__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 1.5rem;
  height: 4rem; /* Increased height slightly for better breathing room */
  flex-shrink: 0; /* Never let flex squeeze the header to make room elsewhere */
  background: var(--bg-primary);
  border-bottom: 0.0625rem solid var(--border-color);
  z-index: 1000; /* Increased z-index to stay above other content */
  transition: transform 0.3s ease, margin-bottom 0.3s ease;
  position: relative;
}

/* #390: transform alone slides the header out of VIEW but not out of the
   flex column's LAYOUT -- .site__header keeps its flex-shrink:0, 4rem-tall
   box either way, so .site__body (flex:1, fills whatever the header/footer
   don't use) never actually grew into the vacated space; the header hiding
   used to just leave a blank 4rem gap where it had been. margin-bottom
   equal to the header's own height, animated in step with the transform,
   collapses that layout contribution to 0 in sync with the slide -- .site__
   body's flex-basis genuinely grows, which is the whole point ("maximize
   screen area"), not merely visual. */
.site__header--hidden {
  transform: translateY(-100%);
  margin-bottom: -4rem;
}

.site__header--sticky {
  position: sticky;
  top: 0;
}

.header__left {
  display: flex;
  align-items: center;
  gap: 1.5rem; /* Increased gap */
}

.header__right {
  display: flex;
  align-items: center;
  gap: 1rem;
}

/* Fix for potential stray toolbar buttons overlapping header */
body > button.x-code__copy,
body > button[x-copy] {
  z-index: 9999; 
}
/* Ensure header contents don't get squished */
.header__logo-text {
  white-space: nowrap;
}


/* =============================================================================
   SPINNER
   ============================================================================= */
/* #448: tag selectors -- spinner() no longer adds a redundant
   classList.add('x-spinner') that just duplicated the <span x-spinner> tag
   name. */
/* The ATTRIBUTE form too. #448 moved these rules to the `x-spinner` TAG, but
   the authored form is almost always an attribute host -- <span x-spinner>,
   <div x-spinner> -- and a tag selector never matches those. Measured on the
   behaviors page: <div x-spinner> kept effects.css's legacy host ring (a 20px
   ring spinning at 0.8s) while the inner <div> that spinner() builds had no
   border and no animation -- the md/lg/xl size, the colour and the speed
   modifiers, which all style that inner <div>, changed nothing on screen.
   `.x-spinner` is added by spinner() alongside the inner ring, so an
   un-upgraded host keeps effects.css's ring as its no-JS fallback; the
   compound (0,2,0) outranks effects.css's `[x-spinner]` (0,1,0) whichever of
   the two stylesheets loads last. */
[x-spinner].x-spinner {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* effects.css also styles the <span x-spinner> ELEMENT as a ring (legacy); the
     schema builds an inner <div> ring, so neutralize the element ring here to
     avoid a double ring once --border-color is defined (#182). */
  width: auto;
  height: auto;
  border: 0;
  animation: none;
}

/* (0,1,1) for the attribute form: equal to the size/speed/colour modifiers
   below, which come later in this file and so still win. */
[x-spinner] > div {
  /* Explicit longhands instead of the `border` shorthand: if --border-color is
     ever unset, only the color is lost, not the whole border (which previously
     made the ring vanish entirely). #182 */
  border-width: 0.125rem;
  border-style: solid;
  border-color: var(--border-color);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: x-spin 1.2s linear infinite;
}

/* Spinner Sizes */
.x-spinner--xs div { width: 0.75rem; height: 0.75rem; border-width: 0.09375rem; }
.x-spinner--sm div { width: 1rem; height: 1rem; border-width: 0.125rem; }
.x-spinner--md div { width: 1.5rem; height: 1.5rem; border-width: 0.125rem; }
.x-spinner--lg div { width: 2rem; height: 2rem; border-width: 0.1875rem; }
.x-spinner--xl div { width: 3rem; height: 3rem; border-width: 0.25rem; }

/* Spinner Speeds */
.x-spinner--slow div { animation-duration: 2s; }
.x-spinner--medium div { animation-duration: 1.2s; }
.x-spinner--fast div { animation-duration: 0.6s; }

/* Spinner Colors */
.x-spinner--primary div { border-top-color: var(--primary); }
.x-spinner--secondary div { border-top-color: var(--secondary); }
.x-spinner--success div { border-top-color: var(--success-color); }
.x-spinner--warning div { border-top-color: var(--warning-color); }
.x-spinner--error div { border-top-color: var(--danger-color); }
.x-spinner--info div { border-top-color: var(--info-color); }

@keyframes x-spin { to { transform: rotate(360deg); } }

/* Dialog styles → src/styles/behaviors/dialog.css (migrated Phase 2) */

.header__notes-btn,
.header__playground-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border: none;
  background: var(--bg-secondary);
  color: var(--text-primary);
  border-radius: 0.375rem;
  cursor: pointer;
  font-size: 1.2rem;
  transition: all 0.2s;
  text-decoration: none;
}

.header__notes-btn:hover,
.header__playground-btn:hover {
  background: var(--bg-tertiary);
  color: var(--primary-text);
}

.header__logo {
  display: flex;
  align-items: center;
  /* 0.75rem: the value site-engine.js's header markup carried in a style=""
     attribute, which is what has always rendered (#779). */
  gap: 0.75rem;
  text-decoration: none;
  color: var(--text-primary);
  font-weight: 600;
  font-size: 1.1rem;
}

.header__logo-icon {
  font-size: 1.5rem;
  display: flex;
  align-items: center;
}

/* #795: the header mark. It used to be a 1024x1024, 1,577,322-byte PNG painted
   at 32 CSS pixels -- ~1.5 MB fetched on every page load, ~4 MB decoded, and
   the heaviest asset on the page, which is why it was also the first thing to
   lose a race and land in the error log as "failed to load after 5 attempts".
   It is now an inline SVG: nothing is fetched at all, it is sharp at any DPR,
   and `currentColor` means the theme supplies the colour instead of a baked-in
   bitmap. The size that used to live in a style="" attribute inside
   config/site.json -- a place no gate could see -- lives here. The <svg>
   carries width/height="32" as well, so the header reserves the box before
   CSS arrives and nothing shifts. */
.header__logo-mark {
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
}

.header__logo:hover {
  color: var(--primary-text);
}

.header__version {
  /* #821: this is a <button> now, not an <a>, so it needs the button resets —
     a UA-styled button would arrive with its own background, border, padding
     and font. */
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  margin-left: 0.35rem;
  font-size: 0.7em;
  font-weight: 500;
  color: var(--text-muted);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
}

.header__version:hover {
  color: var(--primary-text);
  text-decoration: underline;
}

/* =============================================================================
   NAVIGATION
   ============================================================================= */
/* The site nav is an x-sidebar (#829): <nav class="site__nav" x-sidebar>.
   These rules are the site's look for it; x-sidebar's own defaults
   (navigation.css) sit at zero specificity, so every rule here wins. */
.site__nav {
  width: var(--x-sidebar-width, fit-content);
  min-width: 3.75rem;
  max-width: none;
  padding: 0;
  border-radius: 0;
  background: var(--bg-primary);
  border-right: 0.0625rem solid var(--border-color);
  /* Hidden is the unconditional default — see the min-width reveal rule
     (769px breakpoint) + the unconditional .site__nav--mobile-open rule at the end of
     this file for why. Some real mobile browsers lay out the very first
     paint frame at a stale desktop-width viewport before <meta viewport>
     finishes processing, so a max-width media query can silently fail to
     match on that frame — if the base rule were display:flex, that frame
     would show the full sidebar over the phone's content instead of the
     collapsed nav (reported live, repeatedly: site-engine.js's
     enforceMobileNavDefault JS workaround was a losing battle against the
     same timing race). Defaulting hidden makes the failure mode safe. */
  display: none;
  flex-direction: column;
  transition: width 0.3s ease;
  overflow: visible; /* Changed from hidden to allow resizer handle */
  /* Sticky sidebar — stay pinned to the viewport as the content scrolls, so the
     menu is always at the user's current location (was position:relative, which
     scrolled the nav off-screen). */
  align-self: flex-start;
  position: sticky;
  top: 0;
  max-height: calc(100vh - 3.75rem);
  overflow-y: auto;
}

.x-sidebar--collapsed:where(.site__nav) {
  width: 3.75rem;
  min-width: 3.75rem;
}

.x-sidebar--collapsed:where(.site__nav) .x-sidebar__label {
  opacity: 0;
  width: 0;
  display: none;
}

.x-sidebar--collapsed:where(.site__nav) .x-sidebar__item {
  padding-left: 0.5rem;
  padding-right: 2rem;
}

.nav__toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border: none;
  background: var(--bg-secondary);
  color: var(--text-primary);
  border-radius: 0.375rem;
  cursor: pointer;
  font-size: 1.2rem;
  transition: all 0.2s;
}

.nav__toggle:hover {
  background: var(--bg-tertiary);
  color: var(--primary-text);
}

:where(.site__nav) .x-sidebar__items {
  display: flex;
  flex-direction: column;
  padding: 1rem 0;
  gap: 0.25rem;
  overflow-y: auto;
  flex: 1;
}

:where(.site__nav) .x-sidebar__item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  text-decoration: none;
  color: var(--text-secondary);
  opacity: 1;
  border-radius: 0;
  transition: all 0.2s;
  white-space: nowrap;
  overflow: hidden;
  font-size: 0.85rem;
}

:where(.site__nav) .x-sidebar__item:hover {
  background: var(--bg-secondary);
  color: var(--text-primary);
}

:where(.site__nav) .x-sidebar__item--active {
  background: var(--primary);
  color: var(--text-on-accent);
  font-weight: inherit;
}

:where(.site__nav) .x-sidebar__item--active:hover {
  background: var(--primary-dark);
  color: var(--text-on-accent);
}

/* #828: navigationItemStyle "pill". What reads as square is the active fill
   running wall to wall, so the pill insets it from both walls and rounds it.
   The inline padding gives back the margin, so every label stays where it is
   in the block style. */
:where(.site__nav.x-sidebar--pill) .x-sidebar__item {
  margin-inline: 0.5rem;
  padding-inline: 0.5rem;
  border-radius: 999rem;
}

:where(.site__nav) .x-sidebar__icon {
  display: none; /* Icons hidden for cleaner look */
  font-size: 1rem;
  flex-shrink: 0;
  width: 1.25rem;
  text-align: center;
}

/* Show icons when nav is collapsed */
.x-sidebar--collapsed:where(.site__nav) .x-sidebar__icon {
  display: block;
}

:where(.site__nav) .x-sidebar__label {
  transition: opacity 0.2s, width 0.2s;
}

/* =============================================================================
   MAIN CONTENT
   ============================================================================= */
.site__main {
  flex: 1;
  min-width: 0; /* allow the flex child to shrink below its content's min-content
                   width; without this, minmax() grids force horizontal overflow
                   on narrow viewports (#165) */
  padding: 2rem;
  background: var(--bg-color);
  /* Removed overflow-y: auto - body scrolls now so nav can stick */
}

.page {
  max-width: 75rem;
  margin: 0 auto;
  animation: fadeIn 0.3s ease;
}

.page__loading {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 12.5rem;
}

.page--404 {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 25rem;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(0.625rem); }
  to { opacity: 1; transform: translateY(0); }
}

/* =============================================================================
   FOOTER
   ============================================================================= */
.site__footer {
  background: var(--bg-primary);
  border-top: 0.0625rem solid var(--border-color);
  padding: 1.5rem 2rem;
  flex-shrink: 0; /* Never let flex squeeze the footer to make room elsewhere */
  transition: transform 0.3s ease, margin-top 0.3s ease;
  position: relative;
}

/* #393: mirrors .site__header--hidden (#390) for the footer. transform alone
   would slide the footer out of VIEW but not out of the flex column's LAYOUT
   -- .site__footer keeps its flex-shrink:0 box either way, so .site__body
   (flex:1) never actually grows into the vacated space; same bug #390 fixed
   for the header. Unlike the header's fixed 4rem, the footer's height isn't
   a build-time constant (renderFooter()'s social/footer links are optional
   per-site config), so initStickyFooter() (src/core/site-engine.js) measures
   the real height at runtime and sets --site-footer-collapse-height instead
   of a hardcoded number. margin-top equal to the negative of that measured
   height collapses the footer's layout contribution in step with the
   downward slide, so .site__body's flex-basis genuinely grows. */
.site__footer--hidden {
  transform: translateY(100%);
  margin-top: calc(-1 * var(--site-footer-collapse-height, 0rem));
}

.footer__content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  max-width: 75rem;
  margin: 0 auto;
  flex-wrap: wrap;
  gap: 1rem;
}

.footer__left {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  color: var(--text-muted);
  font-size: 0.875rem;
}

.footer__links a {
  color: var(--text-secondary);
  text-decoration: none;
}

.footer__links a:hover {
  color: var(--primary-text);
}

/* #1245: the live site's visit total, set by src/core/visitor-count.js. */
.footer__visits {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.footer__social {
  display: flex;
  gap: 0.75rem;
}

.footer__social-link {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  background: var(--bg-secondary);
  border-radius: 50%;
  text-decoration: none;
  font-size: 1.1rem;
  transition: all 0.2s;
}

.footer__social-link:hover {
  background: var(--primary);
  transform: translateY(-0.125rem);
}

/* =============================================================================
   GLOBAL HEADING STYLES
   ============================================================================= */
h1 {
  font-size: 2.5rem;
  margin: 0 0 0.5rem;
  color: var(--text-primary);
  font-weight: 700;
}

h2 {
  font-size: 1.75rem;
  margin: 2rem 0 1rem;
  color: var(--text-primary);
  font-weight: 700;
}

h3 {
  font-size: 1.25rem;
  margin: 1.5rem 0 0.75rem;
  color: var(--primary-text);
  font-weight: 600;
}

h4 {
  font-size: 1.1rem;
  margin: 1rem 0 0.5rem;
  color: var(--text-primary);
  font-weight: 600;
}

h5, h6 {
  color: var(--text-primary);
  font-weight: 600;
}

.page h1 {
  color: var(--text-primary);
}

.page h2 {
  color: var(--text-primary);
}

.page h3 {
  color: var(--primary-text);
}

.page h4 {
  color: var(--text-primary);
}

.page h5, .page h6 {
  color: var(--text-primary);
}

/* =============================================================================
   PAGE CONTENT STYLES
   ============================================================================= */

.page p, p {
  line-height: 1.7;
  color: var(--text-secondary);
  margin: 0 0 1rem;
}

.page__hero {
  text-align: center;
  padding: 3rem 0;
  margin-bottom: 2rem;
  border-bottom: 0.0625rem solid var(--border-color);
}

.page__hero h1 {
  font-size: 3rem;
  margin-bottom: 1rem;
}

.page__hero p {
  font-size: 1.25rem;
  color: var(--text-muted);
  max-width: 37.5rem;
  margin: 0 auto;
}

.page__section {
  margin-bottom: 3rem;
}

.page__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17.5rem, 1fr));
  gap: 1.5rem;
  margin-top: 1.5rem;
}

/* Generated page layout — flex column with gap between rows */
.page-layout {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

.page-layout > section {
  width: 100%;
}

/* =============================================================================
   RESPONSIVE
   ============================================================================= */

/* Mobile-first: Safe viewport height for mobile browsers */
@supports (height: 100dvh) {
  .site {
    min-height: 100dvh;
  }
  .site__body {
    height: calc(100dvh - 3.75rem);
  }
}

/* Tablet (768px and below) */
@media (max-width: 48rem) {
  /* Mobile nav - always starts hidden, toggle opens it */
  .site__nav {
    position: fixed;
    left: 0;
    top: 3.75rem;
    bottom: 0;
    z-index: 99;
    transform: translateX(-100%);
    width: 17.5rem;
    transition: transform 0.3s ease;
  }
  
  /* When nav is open on mobile (controlled by JS) */
  .site__nav.site__nav--mobile-open {
    transform: translateX(0);
    box-shadow: 0 0 2.5rem rgba(0, 0, 0, 0.5);
  }
  
  /* Hide desktop collapsed state on mobile */
  .site__nav.x-sidebar--collapsed {
    transform: translateX(-100%);
    width: 17.5rem;
  }
  
  .site__nav.x-sidebar--collapsed.site__nav--mobile-open {
    transform: translateX(0);
  }
  
  /* Hide nav resizer on mobile */
  :where(.site__nav) .x-sidebar__resizer {
    display: none;
  }
  
  /* Show icons on mobile for better UX */
  :where(.site__nav) .x-sidebar__icon {
    display: block;
  }
  
  /* Larger touch targets */
  :where(.site__nav) .x-sidebar__item {
    padding: 0.75rem 1rem;
    font-size: 1rem;
  }
  
  .site__main {
    padding: 1rem;
  }
  
  .page h1 {
    font-size: 1.75rem;
  }
  
  .page__hero h1 {
    font-size: 2rem;
  }
  
  .page__hero {
    padding: 2rem 0;
  }
  
  .page__hero p {
    font-size: 1rem;
  }
  
  h1 { font-size: 2rem; }
  h2 { font-size: 1.5rem; }
  h3 { font-size: 1.1rem; }
  
  /* Footer content stays a row (base default) instead of forcing every
     piece onto its own stacked line — copyright+links sit left, social
     icons sit right, wrapping only if they genuinely don't fit together. */

  /* Ensure header buttons are touch-friendly */
  .nav__toggle,
  .header__notes-btn {
    width: 2.75rem;
    height: 2.75rem;
    font-size: 1.3rem;
  }
  
  /* Tables scroll horizontally */
  .x-table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  
  /* Page grid single column */
  .page__grid {
    grid-template-columns: 1fr;
  }
}

/* Mobile backdrop overlay */
.site__nav-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  top: 3.75rem;
  background: rgba(0, 0, 0, 0.5);
  z-index: 98;
  opacity: 0;
  /* Closed: invisible AND non-interactive, or this full-screen fixed overlay
     swallows every click on the page content (#171). Only the OPEN (.visible)
     backdrop captures taps — so tapping it closes the drawer. */
  pointer-events: none;
  transition: opacity 0.3s ease;
}

@media (max-width: 48rem) {
  .site__nav-backdrop {
    display: block;
  }

  .site__nav-backdrop.visible {
    opacity: 1;
    pointer-events: auto;
  }
}

/* Small phones (480px and below) */
@media (max-width: 30rem) {
  .site__header {
    padding: 0 1rem;
    height: 3.5rem;
  }

  /* Keep the header right cluster inside the viewport on phones. The marketing
     "Get Started" CTA is redundant here (hamburger nav + in-page CTAs cover it)
     and is the widest item, so hide it; tighten the remaining controls. (#165) */
  /* No gap here: site-engine.js's header carried gap:1rem in a style=""
     attribute, so a phone-width 0.5rem never applied. Dropped with that
     attribute (#779) rather than changing the header at this width. */
  .header__right {
    min-width: 0;
  }
  /* (the marketing CTA is hidden on small phones below — see the #ctaButton
     rule placed after its base declaration so the cascade actually applies) */

  .site__body {
    height: calc(100dvh - 3.5rem);
    height: calc(100vh - 3.5rem);
  }
  
  .site__nav {
    top: 3.5rem;
    width: 100%;
    max-width: 100%;
  }
  
  .site__nav-backdrop {
    top: 3.5rem;
  }
  
  .site__main {
    /* 1rem top gap so page content sits 1rem below the sticky header (#165) */
    padding: 1rem 0.75rem 0.75rem;
  }

  .page h1 {
    font-size: 1.5rem;
  }
  
  .page__hero h1 {
    font-size: 1.75rem;
  }
  
  h1 { font-size: 1.5rem; }
  h2 { font-size: 1.25rem; }
  
  /* Full-width buttons on small phones */
  .btn, .x-btn-gradient, .x-btn-glass {
    width: 100%;
    justify-content: center;
  }
  
  .footer__left {
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
    /* flex:1 1 auto wasn't enough — with flex-basis:auto, the OUTER
       .footer__content row's wrap decision still uses footer__left's
       UNWRAPPED text width (its max-content size) even though min-width:0
       lets it shrink afterward. flex-basis:0 starts it at zero and lets
       flex-grow fill the row, so the wrap decision no longer depends on
       how wide the copyright text would be on a single line — footer__right
       (the social icons) can now sit on the same row instead of being
       pushed to its own line beneath two rows of wrapped text. */
    flex: 1 1 0;
    min-width: 0;
  }
}

/* =============================================================================
   BUTTONS
   ============================================================================= */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  border: 0.0625rem solid transparent;
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 500;
  transition: all 0.2s;
  text-decoration: none;
  line-height: 1.5;
  gap: 0.5rem;
}

.btn:focus {
  outline: 0.125rem solid var(--primary);
  outline-offset: 0.125rem;
}

.btn:disabled, .btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* Variants */
.btn--primary,
.btn-primary {
  background: var(--primary);
  color: white;
  border-color: var(--primary);
}

.btn--primary:hover,
.btn-primary:hover {
  background: var(--primary-dark, var(--primary));
  filter: brightness(1.1);
}

.btn--secondary,
.btn-secondary {
  background: var(--bg-secondary);
  color: var(--text-primary);
  border-color: var(--border-color);
}

.btn--secondary:hover,
.btn-secondary:hover {
  background: var(--bg-tertiary);
  border-color: var(--text-secondary);
}

.btn--success {
  background: var(--success-color);
  color: white;
  border-color: var(--success-color);
}
.btn--success:hover {
  filter: brightness(1.1);
}

.btn--warning {
  background: var(--warning-color);
  color: #1a1a1a;
  border-color: var(--warning-color);
}
.btn--warning:hover {
  filter: brightness(1.1);
}

.btn--error {
  background: var(--danger-color);
  color: white;
  border-color: var(--danger-color);
}
.btn--error:hover {
  filter: brightness(1.1);
}

.btn--ghost {
  background: transparent;
  color: var(--text-primary);
  border-color: transparent;
}
.btn--ghost:hover {
  background: rgba(255, 255, 255, 0.05);
  color: var(--primary-text);
}

.btn--outline {
  background: transparent;
  border: 0.0625rem solid var(--border-color);
  color: var(--text-primary);
}
.btn--outline:hover {
  border-color: var(--primary);
  color: var(--primary-text);
}

/* Sizes */
.btn--xs { font-size: 0.75rem; padding: 0.25rem 0.5rem; }
.btn--sm { font-size: 0.8rem; padding: 0.375rem 0.75rem; }
.btn--md { font-size: 0.9rem; padding: 0.5rem 1rem; }
.btn--lg { font-size: 1.1rem; padding: 0.75rem 1.5rem; }
.btn--xl { font-size: 1.25rem; padding: 1rem 2rem; }

/* =============================================================================
   WB EVENTS TOAST ANIMATIONS
============================================================================= */
@keyframes x-slide-in {
  from {
    opacity: 0;
    transform: translateX(100%);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes x-fade-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

/* =============================================================================
   DOCS VIEWER SCROLLBARS
   ============================================================================= */
#docs-div-1 > * {
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE/Edge */
}

#docs-div-1 > *::-webkit-scrollbar {
  display: none; /* Chrome/Safari/Opera */
}

/* =============================================================================
   ANIMATIONS
   ============================================================================= */
.x-animate {
  animation-fill-mode: both;
}

@keyframes x-fade-in { from { opacity: 0; } to { opacity: 1; } }
/* x-fade-out is already defined above */
@keyframes x-slide-in-left { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes x-slide-in-right { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes x-slide-in-up { from { transform: translateY(100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes x-slide-in-down { from { transform: translateY(-100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes x-zoom-in { from { transform: scale(0); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes x-zoom-out { from { transform: scale(1); opacity: 1; } to { transform: scale(0); opacity: 0; } }
@keyframes x-flip { from { transform: perspective(25rem) rotateY(90deg); opacity: 0; } to { transform: perspective(25rem) rotateY(0deg); opacity: 1; } }
@keyframes x-rotate { from { transform: rotate(-180deg); opacity: 0; } to { transform: rotate(0); opacity: 1; } }
@keyframes x-bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-1.875rem); } 60% { transform: translateY(-0.9375rem); } }
@keyframes x-shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-0.625rem); } 20%, 40%, 60%, 80% { transform: translateX(0.625rem); } }
@keyframes x-pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } }
@keyframes x-flash { 0%, 50%, 100% { opacity: 1; } 25%, 75% { opacity: 0; } }
@keyframes x-tada { 0% { transform: scale(1); } 10%, 20% { transform: scale(0.9) rotate(-3deg); } 30%, 50%, 70%, 90% { transform: scale(1.1) rotate(3deg); } 40%, 60%, 80% { transform: scale(1.1) rotate(-3deg); } 100% { transform: scale(1) rotate(0); } }
@keyframes x-wobble { 0% { transform: translateX(0%); } 15% { transform: translateX(-25%) rotate(-5deg); } 30% { transform: translateX(20%) rotate(3deg); } 45% { transform: translateX(-15%) rotate(-3deg); } 60% { transform: translateX(10%) rotate(2deg); } 75% { transform: translateX(-5%) rotate(-1deg); } 100% { transform: translateX(0%); } }
@keyframes x-jello { 11.1% { transform: none; } 22.2% { transform: skewX(-12.5deg) skewY(-12.5deg); } 33.3% { transform: skewX(6.25deg) skewY(6.25deg); } 44.4% { transform: skewX(-3.125deg) skewY(-3.125deg); } 55.5% { transform: skewX(1.5625deg) skewY(1.5625deg); } 66.6% { transform: skewX(-0.78125deg) skewY(-0.78125deg); } 77.7% { transform: skewX(0.390625deg) skewY(0.390625deg); } 88.8% { transform: skewX(-0.1953125deg) skewY(-0.1953125deg); } 100% { transform: none; } }
@keyframes x-swing { 20% { transform: rotate(15deg); } 40% { transform: rotate(-10deg); } 60% { transform: rotate(5deg); } 80% { transform: rotate(-5deg); } 100% { transform: rotate(0deg); } }
@keyframes x-rubberband { 0% { transform: scale(1); } 30% { transform: scale(1.25, 0.75); } 40% { transform: scale(0.75, 1.25); } 50% { transform: scale(1.15, 0.85); } 65% { transform: scale(0.95, 1.05); } 75% { transform: scale(1.05, 0.95); } 100% { transform: scale(1); } }
@keyframes x-heartbeat { 0% { transform: scale(1); } 14% { transform: scale(1.3); } 28% { transform: scale(1); } 42% { transform: scale(1.3); } 70% { transform: scale(1); } }
@keyframes x-marquee { 0% { transform: translateX(0); } 100% { transform: translateX(-100%); } }

/* =============================================================================
   RESIZABLE
   ============================================================================= */
.x-resizable__handle {
  background-color: rgba(255, 255, 255, 0.2);
  border: 0.0625rem solid rgba(255, 255, 255, 0.4);
  border-radius: 50%;
  transition: background-color 0.2s;
}

.x-resizable__handle:hover {
  background-color: var(--primary);
}

/* Resizable Nav — the drag handle is x-sidebar's .x-sidebar__resizer, styled
   in navigation.css (#829). NOTE: do NOT re-declare .site__nav position here.
   The base rule already sets a positioned box (so the absolute handle
   anchors), and the mobile @media block flips it to position: fixed for the
   off-canvas drawer. A duplicate position:relative here (after the media
   query) silently overrode the mobile rule and caused horizontal overflow on
   phones (#165). */

/* Resizing handled by JS overlay now */

/* Table styles → src/styles/behaviors/data.css (migrated Phase 1) */

/* =============================================================================
   PREMIUM HERO STYLES
   ============================================================================= */

/* Hero Section */
.x-hero {
  position: relative;
  min-height: 80vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--bg-color);
}

.x-hero__content {
  position: relative;
  z-index: 10;
  text-align: center;
  max-width: 50rem;
  padding: 2rem;
}

.x-hero__subtitle {
  font-size: 1.25rem;
  color: var(--text-secondary);
  max-width: 37.5rem;
  margin: 0 auto 2rem;
  line-height: 1.7;
}

/* Gradient Text */
.x-gradient-text {
  background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 50%, var(--accent) 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

/* =============================================================================
   FLOATING ORBS
   ============================================================================= */
.x-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(5rem);
  opacity: 0.5;
  animation: x-orb-float 8s ease-in-out infinite;
  pointer-events: none;
}

.x-orb--primary {
  background: var(--primary);
}

.x-orb--ocean {
  background: var(--info-color);
}

.x-orb--sunset {
  background: var(--warning-color);
}

.x-orb--cosmic {
  background: linear-gradient(135deg, var(--primary), var(--secondary), var(--accent));
}

@keyframes x-orb-float {
  0%, 100% { transform: translate(0, 0) scale(1); }
  33% { transform: translate(1.875rem, -1.875rem) scale(1.05); }
  66% { transform: translate(-1.25rem, 1.25rem) scale(0.95); }
}

/* =============================================================================
   GRADIENT BUTTONS
   ============================================================================= */
/* Element-qualified (button.x-btn-gradient, a.x-btn-gradient) so this beats
   themes.css's `[data-theme] button` native-button reset (0-1-1 specificity)
   on <button> usage -- the bare class alone (0-1-0) lost that fight and this
   component's real padding never applied, leaving the reset's 0.5rem 1rem in
   effect (8px, under the >=1rem minimum) instead of the 0.875rem/2rem below. */
button.x-btn-gradient,
a.x-btn-gradient {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* >=1rem on every side (was 0.875rem/14px vertical, under the minimum). */
  padding: 1rem 2rem;
  font-size: 1rem;
  font-weight: 600;
  color: white;
  text-decoration: none;
  border: none;
  border-radius: 0.75rem;
  cursor: pointer;
  overflow: hidden;
  background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 50%, var(--accent) 100%);
  background-size: 200% 200%;
  animation: x-gradient-shift 3s ease infinite;
  transition: transform 0.2s, box-shadow 0.2s;
}

.x-btn-gradient:hover {
  transform: translateY(-0.125rem);
  box-shadow: 0 0.625rem 2.5rem -0.625rem var(--primary);
}

.x-btn-gradient:active {
  transform: translateY(0);
}

@keyframes x-gradient-shift {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

/* =============================================================================
   GLASS BUTTONS & ELEMENTS
   ============================================================================= */
/* Element-qualified for the same reason as .x-btn-gradient above -- beats
   themes.css's `[data-theme] button` reset (0-1-1) on <button> usage. */
button.x-btn-glass,
a.x-btn-glass {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* >=1rem on every side (was 0.875rem/14px vertical, under the minimum). */
  padding: 1rem 2rem;
  font-size: 1rem;
  font-weight: 500;
  color: var(--text-primary);
  text-decoration: none;
  border: 0.0625rem solid rgba(255, 255, 255, 0.1);
  border-radius: 0.75rem;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(0.625rem);
  transition: all 0.2s;
}

.x-btn-glass:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.2);
  transform: translateY(-0.125rem);
}

.x-glass-card {
  background: rgba(255, 255, 255, 0.05);
  backdrop-filter: blur(0.625rem);
  border: 0.0625rem solid rgba(255, 255, 255, 0.1);
  border-radius: 1rem;
}

.x-input-glass {
  width: 100%;
  padding: 0.75rem 1rem;
  font-size: 1rem;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.05);
  border: 0.0625rem solid rgba(255, 255, 255, 0.1);
  border-radius: 0.5rem;
  outline: none;
  transition: all 0.2s;
}

.x-input-glass:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 0.1875rem rgba(99, 102, 241, 0.2);
}

.x-input-glass::placeholder {
  color: var(--text-muted);
}

/* The header's search box (site-engine.js), formerly style="" (#779). */
.header__search .x-input-glass.header__search-input {
  padding: 0.4rem 0.8rem;
  width: 12.5rem;
}

/* =============================================================================
   BADGES & TAGS
   ============================================================================= */
.x-badge-gradient {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: white;
  background: linear-gradient(135deg, var(--primary), var(--secondary));
  border-radius: 999rem;
}

/* The glass badge: header's and the link card's (#1096). */
.x-header__badge--glass,
.x-card__link-badge--glass {
  display: inline-flex;
  align-items: center;
  padding: 0.375rem 0.75rem;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.1);
  border: 0.0625rem solid rgba(255, 255, 255, 0.1);
  border-radius: 0.375rem;
}

/* Pulsing Dot */
.x-dot-pulse {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  background: var(--success-color);
  border-radius: 50%;
  animation: x-pulse-dot 2s ease-in-out infinite;
}

@keyframes x-pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.5; transform: scale(1.2); }
}

/* Floating card styles → src/styles/behaviors/card.css (migrated Phase 1) */

/* =============================================================================
   BADGE GROUP
   ============================================================================= */
.x-badge-group {
  display: flex;
  gap: 0.5rem;
  position: relative;
  z-index: 1;
  flex-wrap: wrap;
}

/* Progress styles → src/styles/behaviors/progress.css (migrated Phase 2) */

/* =============================================================================
   AVATARS
   ============================================================================= */
.x-avatar-gradient {
  position: relative;
  width: 3rem;
  height: 3rem;
  padding: 0.1875rem;
  background: linear-gradient(135deg, var(--primary), var(--secondary), var(--accent));
  border-radius: 50%;
}

.x-avatar-gradient img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  border: 0.125rem solid var(--bg-color);
}

/* =============================================================================
   DIVIDERS
   ============================================================================= */
.x-divider-gradient {
  border: none;
  height: 0.0625rem;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
  margin: 3rem 0;
}

/* =============================================================================
   PERFORMANCE UTILITIES
   ============================================================================= */
.x-content-auto {
  content-visibility: auto;
  contain-intrinsic-size: 0.0625rem 62.5rem; /* Approximate height */
}

/* =============================================================================
   EDIT MODE CONTROLS
   ============================================================================= */
.move-controls button {
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
  line-height: 1;
  min-width: 1.5rem;
  height: 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Striped progress styles → src/styles/behaviors/progress.css (migrated Phase 2) */

/* =============================================================================
   RACKMOUNT COMPONENT
   ============================================================================= */
.x-rackmount {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1rem;
  background: linear-gradient(180deg, #2a2a2a 0%, #1a1a1a 100%);
  border: 0.0625rem solid #333;
  border-radius: 0.25rem;
  box-shadow: 
    inset 0 0.0625rem 0 rgba(255, 255, 255, 0.1),
    0 0.25rem 0.5rem rgba(0, 0, 0, 0.5);
  position: relative;
  font-family: 'JetBrains Mono', monospace;
}

/* Rack Ears (Visual Only) */
.x-rackmount::before,
.x-rackmount::after {
  content: '';
  position: absolute;
  top: 0.625rem;
  bottom: 0.625rem;
  width: 0.75rem;
  background: #111;
  border: 0.0625rem solid #333;
  border-radius: 0.125rem;
  box-shadow: inset 0 0 0.25rem rgba(0,0,0,0.8);
}

.x-rackmount::before { left: 0.25rem; }
.x-rackmount::after { right: 0.25rem; }

/* Screw Holes */
.x-rackmount-screws,
.x-rackmount-screws-right {
  position: absolute;
  top: 0; bottom: 0; left: 0; right: 0;
  pointer-events: none;
}

.x-rackmount-screws::before,
.x-rackmount-screws::after {
  content: '⊕';
  position: absolute;
  color: #555;
  font-size: 0.625rem;
  line-height: 1;
  left: 0.375rem;
}
.x-rackmount-screws::before { top: 0.875rem; }
.x-rackmount-screws::after { bottom: 0.875rem; }

.x-rackmount-screws-right::before,
.x-rackmount-screws-right::after {
  content: '⊕';
  position: absolute;
  color: #555;
  font-size: 0.625rem;
  line-height: 1;
  right: 0.375rem;
}
.x-rackmount-screws-right::before { top: 0.875rem; }
.x-rackmount-screws-right::after { bottom: 0.875rem; }

/* Content Area (Padded to avoid ears) */
.x-rackmount__content {
  margin: 0 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.x-rackmount__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 0.0625rem solid #333;
  padding-bottom: 0.5rem;
  margin-bottom: 0.25rem;
}

.x-rackmount__title {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.0625rem;
  color: #888;
  font-weight: 700;
}

.x-rackmount__status {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: #22c55e;
  box-shadow: 0 0 0.3125rem #22c55e;
}

.x-rackmount__status.off { background: #ef4444; box-shadow: 0 0 0.3125rem #ef4444; }
.x-rackmount__status.warn { background: #eab308; box-shadow: 0 0 0.3125rem #eab308; }

/* Rackmount Button */
.x-btn-rack {
  background: #222;
  border: 0.0625rem solid #444;
  color: #ccc;
  padding: 0.5rem 1rem;
  font-family: inherit;
  font-size: 0.8rem;
  text-transform: uppercase;
  cursor: pointer;
  transition: all 0.1s;
  box-shadow: inset 0 0.0625rem 0 rgba(255,255,255,0.05);
}

.x-btn-rack:hover {
  background: #333;
  color: #fff;
  border-color: #666;
}

.x-btn-rack:active {
  background: #111;
  box-shadow: inset 0 0.125rem 0.25rem rgba(0,0,0,0.5);
  transform: translateY(0.0625rem);
}

/* =============================================================================
   CONTROL CABINET (User Requested Style)
   ============================================================================= */
.control-cabinet {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem;
  background: linear-gradient(145deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%);
  backdrop-filter: blur(0.75rem);
  border: 0.0625rem solid rgba(255, 255, 255, 0.08);
  border-radius: 1rem;
  box-shadow: 
    0 0.25rem 1.5rem -0.0625rem rgba(0, 0, 0, 0.2), 
    inset 0 0.0625rem 0 rgba(255, 255, 255, 0.1);
  min-width: 12.5rem;
}

.control-cabinet button {
  width: 100%;
  justify-content: center;
}

.run-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1.5rem;
  background: rgba(255, 255, 255, 0.05);
  border: 0.0625rem solid rgba(255, 255, 255, 0.1);
  border-radius: 0.5rem;
  color: var(--text-primary);
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  transition: all 0.2s ease;
  overflow: hidden;
}

.run-btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.2);
  transform: translateY(-0.0625rem);
}

.run-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  background: rgba(0, 0, 0, 0.2);
}

.status-light {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background-color: var(--success-color);
  box-shadow: 0 0 0.5rem var(--success-color);
  transition: background-color 0.3s, box-shadow 0.3s;
}

.run-btn.running .status-light {
  background-color: var(--danger-color);
  box-shadow: 0 0 0.5rem var(--danger-color);
  animation: pulse-red 1s infinite;
}

.refresh-btn {
  background: var(--primary);
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  cursor: pointer;
  font-weight: 600;
}

.refresh-btn:hover { filter: brightness(1.1); }

@keyframes pulse-red {
  0% { opacity: 1; box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
  70% { opacity: 1; box-shadow: 0 0 0 0.375rem rgba(239, 68, 68, 0); }
  100% { opacity: 1; box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}

/* CODE CAPTIONS (MDHTML) — moved to src/styles/behaviors/mdhtml.css
   (.x-mdhtml .code-wrapper / .code-caption), the single canonical source.
   This unscoped duplicate centered every code block horizontally
   (width:fit-content + margin:0 auto), unlike the flush-left surrounding
   prose — see mdhtml.css's own comment for the full story. */

/* =============================================================================
   PREMIUM CTA BUTTON - HEADER VERSION
   ============================================================================= */

.navbar-cta,
#ctaButton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.75rem 1.5rem;
  
  /* Premium gradient background */
  background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
  color: #fff;
  border: none;
  border-radius: 0.5rem;
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
  
  /* Smooth transitions */
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  box-shadow: 0 0.25rem 0.9375rem rgba(99, 102, 241, 0.4),
              inset 0 0.0625rem 0 rgba(255, 255, 255, 0.2);
}

/* Hover state - scale + glow */
.navbar-cta:hover,
#ctaButton:hover {
  transform: translateY(-0.1875rem) scale(1.05);
  box-shadow: 0 0.5rem 1.5625rem rgba(99, 102, 241, 0.6),
              inset 0 0.0625rem 0 rgba(255, 255, 255, 0.3),
              0 0 1.25rem rgba(99, 102, 241, 0.5);
}

/* Active/Click state */
.navbar-cta:active,
#ctaButton:active {
  transform: translateY(-0.0625rem) scale(1.02);
  box-shadow: 0 0.25rem 0.9375rem rgba(99, 102, 241, 0.4);
}

/* Animated background shimmer */
.navbar-cta::before,
#ctaButton::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
  transition: left 0.5s ease;
  pointer-events: none;
}

.navbar-cta:hover::before,
#ctaButton:hover::before {
  left: 100%;
}

/* Focus state for accessibility */
.navbar-cta:focus,
#ctaButton:focus {
  outline: 0.125rem solid #6366f1;
  outline-offset: 0.125rem;
}

/* Responsive - smaller on mobile */
@media (max-width: 48rem) {
  .navbar-cta,
  #ctaButton {
    padding: 0.6rem 1.2rem;
    font-size: 0.85rem;
  }
}

/* Small phones: hide the marketing "Get Started" CTA — it's the widest header
   item and pushes the header past the viewport (#165). Declared here, AFTER the
   base #ctaButton rule, so it isn't clobbered by the later base declaration.
   The hamburger nav + in-page CTAs cover the same action on phones. */
@media (max-width: 30rem) {
  .navbar-cta,
  #ctaButton {
    display: none;
  }
}

/* =============================================================================
   #165 — AUTHORITATIVE MOBILE OFF-CANVAS DRAWER
   Placed last so it wins the cascade. The shell had duplicate/late .site__nav
   rules that left the drawer visible (a stray strip) instead of off-screen.
   ============================================================================= */
@media (max-width: 48rem) {
  .site__nav {
    position: fixed;
    top: 3.5rem;
    bottom: 0;
    left: 0;
    width: min(82vw, 18.75rem);
    max-height: none;
    transform: translateX(-100%);
    transition: transform 0.3s ease;
    z-index: 99;
  }
  .site__nav.site__nav--mobile-open {
    transform: translateX(0);
    box-shadow: 0 0 2.5rem rgba(0, 0, 0, 0.5);
  }
  /* The drag-to-resize handle sits on the nav's right edge; when the drawer is
     off-screen it peeked through at x=0 as a stray strip. Hide it on phones —
     the drawer isn't resizable there anyway. (#165) */
  :where(.site__nav) .x-sidebar__resizer {
    display: none;
  }
}

/* #165 — Hero must not crop its headline on phones. The 400px min-height with
   vertically-centered content + overflow:hidden cropped the top once the title
   wrapped to more lines. Let it grow to fit. */
@media (max-width: 48rem) {
  .x-hero {
    min-height: auto;
    padding-top: 1.5rem;
    padding-bottom: 1.5rem;
  }
}

/* =============================================================================
   BEHAVIORS SHOWCASE — SECTION NAV (#181)
   pages/behaviors.css is not loaded by the SPA (see #186), so the section-nav
   pill styling and anchor offset live here in the always-loaded shell stylesheet.
   Selectors are safe globally: .nav-links is unique to the behaviors page, and
   scroll-margin on anchored sections is benign everywhere.
   ============================================================================= */
.nav-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  padding: 1rem;
  margin-bottom: 2rem;
  border-radius: 0.75rem;
  background: var(--bg-secondary);
  border: 0.0625rem solid var(--border-color);
}
.nav-links a {
  text-decoration: none;
  padding: 0.4rem 0.9rem;
  border-radius: 999rem;
  font-size: 0.95rem;
  white-space: nowrap;
  transition: all 0.2s ease;
  background: var(--bg-tertiary);
  color: var(--text-primary);
  border: 0.0625rem solid var(--border-color);
}
.nav-links a:hover {
  background: var(--primary);
  color: var(--text-primary);
  border-color: var(--primary);
}
/* Clear the 4rem sticky header when jumping to a section, otherwise the heading
   lands hidden behind the header and the link appears not to work. */
section[id] {
  scroll-margin-top: 5rem;
}

/* =============================================================================
   BEHAVIORS SHOWCASE — HERO HEADER (#179)
   The first content element (the ⚡ hero title) rendered ~19px under the 64px
   sticky header, clipping the emoji and the tops of the letters with no gap.
   Push the page content clear of the header and give the h1 line-height room
   for the emoji's ascent. Scoped to the behaviors page.
   ============================================================================= */
#mainPage-behaviors {
  padding-top: 2rem;
}
#mainPage-behaviors .page__hero h1,
#mainPage-behaviors > #header h1 {
  margin: 0;
  line-height: 1.45;
  padding-top: 0.1em;
}

/* =============================================================================
   HOME — CLICKABLE FEATURE CARDS (#184)
   Each Features card links to its subsystem page; the SPA intercepts ?page=.
   ============================================================================= */
.feature-card-link {
  display: block;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  border-radius: var(--radius-lg, 0.5rem);
}
.feature-card-link:focus-visible {
  outline: 0.125rem solid var(--primary);
  outline-offset: 0.1875rem;
}
/* the descendant x-card--float already lifts on hover; ensure the whole card
   reacts to keyboard focus on the anchor too */
.feature-card-link:focus-visible .x-card {
  border-color: var(--primary);
}

/* =============================================================================
   PAGE SECTION RHYTHM (#186)
   pages/*.css never loads in the SPA, so the section/heading/section-note
   spacing lives here in the always-loaded shell stylesheet, scoped to the SPA
   content container (#main) so it can't touch the shell. Restores the vertical
   spacing the showcase pages were designed with.
   ============================================================================= */
#main section[id] {
  margin-bottom: 2.5rem;
  padding-bottom: 0.5rem;
}
#main section > h2 {
  margin: 2.5rem 0 1rem;
  font-size: 1.5rem;
}
#main section > h3 {
  margin: 1.75rem 0 0.75rem;
  font-size: 1.125rem;
}
#main .section-note {
  padding: 0.75rem 1rem;
  margin: 0 0 1.25rem;
  border-left: 0.1875rem solid var(--primary);
  border-radius: 0 0.5rem 0.5rem 0;
  background: var(--bg-secondary);
  font-size: 0.9rem;
}
#main .demo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE READABILITY + FLUENT NAV (#293)
   ═══════════════════════════════════════════════════════════════════════════ */

/* Responsive images everywhere — scale to the column, never overflow. */
img { max-width: 100%; height: auto; }

/* #1327: width + height together are the author's SHAPE. height:auto above
   beats the height attribute, so <img width="100" height="50"> of a 240x150
   picture rendered 100x62.5 wherever the img behavior does not run (the
   playground turns autoInject off). Turning height:auto off for them instead
   (PR #1345) broke the other half: a 960x540 image squeezed into a narrower
   column kept height 540 and distorted. A ratio does both -- it fixes the
   shape and still scales with the width -- and it is what img.js already
   sets once it runs; this makes the same thing true before or without it.
   A browser without typed attr() drops the declaration and keeps today's
   natural-ratio behavior. An explicit aspectRatio="" (or the dashed
   aspect-ratio="", read as a fallback, #1125) still wins: img.js
   applies it, so this rule leaves those images alone, and :where() keeps its
   weight (0,1,1) under img.js's generated rules. */
img:where([width][height]):not([aspectRatio], [aspect-ratio]) {
  aspect-ratio: attr(width type(<number>)) / attr(height type(<number>));
  object-fit: cover;
}

/* Code blocks scroll inside their own box, never widen the page. pre.js gives
   .x-pre this once it runs, but a <pre> it has not reached yet (lazy
   hydration) or never will (x-ignore) had overflow:visible, so one long line
   -- home.html's setup snippet is 563px -- pushed a 375px phone sideways.
   Scroll, not wrap: code never wraps (#199, #826). Element specificity, so
   every .x-pre / .x-pre--wrap rule still wins. */
pre { max-width: 100%; overflow-x: auto; }

@media (max-width: 40rem) {
  /* Bump the base ~12.5% so EVERY rem-based size (body text, code, the
     --md-font-size docs, padding, gaps) reads comfortably on phones. One lever. */
  html { font-size: 112.5%; }

  /* Stack header/nav/main vertically — the fluent nav below flows in that
     column, at the top of main, instead of beside it as a side drawer. */
  .site__body { flex-direction: column; }

  /* Header stacks/wraps instead of overflowing off the right edge — must size to
     its content (base sets a fixed 4rem height as a flex item). */
  .site__header {
    flex-wrap: wrap;
    height: auto;
    min-height: 3.75rem;
    flex: 0 0 auto;
    row-gap: 0.5rem;
    padding: 0.5rem 0.75rem;
  }
  .header__left, .header__right { flex-wrap: wrap; row-gap: 0.5rem; }

  /* header__right's playground/notes buttons are developer tools, not
     needed by a phone visitor — drop them so hamburger + logo + the
     version link fit on a single row without wrapping. The clear-cache
     reload action lives on the version number itself now (tap it), so
     there's no separate button left that needs to stay visible here.
     Scoped under .site__header: src/styles/behaviors/header.css (an
     unrelated <header> component) reuses these exact class names with an
     unconditional display:flex and is injected into <head> at runtime after
     site.css, so an unscoped same-specificity selector here loses the
     cascade regardless of this media query matching. */
  .site__header .header__playground-btn,
  .site__header .header__notes-btn {
    display: none;
  }

  /* Fluent nav: an in-flow VERTICAL stacked menu at the top of the content —
     NOT a horizontal bar and NOT an off-canvas side drawer. Hidden by default,
     the ☰ toggle reveals it in place (display-toggle — no max-height fight). */
  .site__nav {
    position: static;
    transform: none;
    flex: 0 0 auto;
    width: 100%;
    height: auto;
    max-height: none;
    top: auto;
    overflow: visible;
    display: none;   /* collapsed by default on phones */
    box-shadow: none;
    border-right: none;
    border-bottom: 0.0625rem solid var(--border-color);
  }
  .site__nav.site__nav--mobile-open,
  .site__nav.x-sidebar--collapsed.site__nav--mobile-open {
    display: block;
    transform: none;
    box-shadow: none;
  }
  /* Items stack VERTICALLY, full width. */
  .site__nav .x-sidebar__items {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.5rem;
  }
  .site__nav .x-sidebar__item { width: 100%; }
  /* In-flow menu needs no drawer chrome. */
  .site__nav-backdrop { display: none; }
  :where(.site__nav) .x-sidebar__resizer { display: none; }

  /* Footer was 3 stacked blocks (copyright, links, social icons) each
     carrying the desktop 1.5rem/2rem padding/gap meant for a single wide
     row — compact it for a narrow column. .footer__left's own 1.5rem gap
     (copyright <-> links) was untouched by the first pass and, once those
     two wrap to separate lines on a narrow screen, that gap applies as
     row-gap too — the single biggest remaining contributor to the height.
     The <footer> element also carries a "x-footer" class (added at
     runtime by the same tag-name-collision mechanism found earlier on the
     shell's own <header> — a reusable behavior's class landing on native
     <header>/<footer> tags it was never meant to touch). behaviors/footer.css
     has its OWN (max-width:768px) padding rule for .x-footer that wins
     equal-specificity ties by injection order, silently reintroducing the
     desktop padding this override is trying to remove. Compound selector
     to beat it outright rather than depend on cascade order. */
  .site__footer.x-footer {
    padding: 0.5rem 1rem;
  }
  .footer__content {
    gap: 0.2rem;
  }
  .footer__left {
    gap: 0.35rem;
    font-size: 0.8rem;
  }
  .footer__social {
    gap: 0.5rem;
  }
  .footer__social-link {
    width: 1.375rem;
    height: 1.375rem;
    font-size: 0.8rem;
  }
}

/* =============================================================================
   #293 regression — .site__nav's base rule now defaults to display:none
   (see that rule's comment). Restore the sidebar for genuine tablet/desktop
   widths. The 769px breakpoint deliberately avoids the existing 768px
   max-width blocks above — no overlap, no cascade-order ambiguity.
   ============================================================================= */
@media (min-width: 48.0625rem) {
  .site__nav {
    display: flex;
  }
}

/* Unconditional (no media query) and last in the file — the toggled-open
   state must win at every breakpoint regardless of which max-width block
   above matched, without depending on each of them separately remembering
   to restore display alongside their own transform/position rules. It wins
   without !important (#1014): two classes outrank every `.site__nav { display:
   none }`, and nothing after it sets display. The phone block's
   `--collapsed.--mobile-open` (three classes) keeps display: block. */
.site__nav.site__nav--mobile-open {
  display: flex;
}
