
/* ===== tokens.css ===== */
/*! global tokens — site neutrals, spacing scale, motion, brand defaults.
 * Per-DAW palette tokens live in src/<daw>/tokens.css and are scoped via
 * [data-daw="..."] selectors. The :root brand defaults below are the
 * fallback for pages without a DAW scope — DAW scopes override them.
 */

/* Both Poppins (body) and Roboto Slab (logo wordmark) are self-hosted
 * via @font-face declarations in src/global/css/fonts.css — that file
 * is concatenated into the inline critical-CSS bundle by build.php
 * so the families are available without any external @import. */

:root {
  /* ---- Type ---- */
  /* Poppins matches the live site exactly. System fallbacks if Poppins fails to load. */
  --font-sans: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --fs-xs:  clamp(0.75rem, 0.7rem + 0.2vw, 0.85rem);
  --fs-sm:  clamp(0.85rem, 0.8rem + 0.25vw, 0.95rem);
  --fs-md:  clamp(1rem,    0.95rem + 0.3vw, 1.1rem);
  --fs-lg:  clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);
  --fs-xl:  clamp(1.6rem,  1.4rem + 1vw,    2rem);
  --fs-2xl: clamp(2rem,    1.7rem + 1.5vw,  2.75rem);
  --fs-3xl: clamp(2.6rem,  2rem + 2.5vw,    4rem);

  /* ---- Spacing scale ---- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;
  --sp-9: 96px;
  --sp-10: 128px;
  --sp-11: 160px;

  /* ---- Layout ---- */
  --max-content: 1200px;
  --gutter: var(--sp-5);

  /* ---- Brand defaults ----
   * Hex literals are ONLY allowed here at :root and inside per-DAW
   * src/<daw>/tokens.css. Components must reference these tokens
   * (or derivations of them via color-mix) — never hex literals,
   * never `white`/`black` keywords, never inline var() fallbacks.
   *
   * The defaults below match Logic's palette, used as a fallback for
   * pages without a [data-daw] scope. DAW scopes override every token. */
  --buttoncolor:        #3b9cd1;
  --color-bright:       #dce8ef;
  --color-dark:         #9fb2bc;
  --color-text:         #1d262b;
  --color-background:   #5d6d76;
  --color-header:       #4c748a;
  /* The -dm variants are STATIC anchors (don't swap with mode) — they
   * provide always-bright (--color-text-dm) and always-dark (--color-dark-dm)
   * references for shadows, lift overlays, recess overlays, etc. */
  --color-bright-dm:    #4c748a;
  --color-dark-dm:      #1c2b33;
  --color-text-dm:      #eef4f7;
  --color-background-dm:#80b0c4;
  --color-buy:          #F57C00;
  --hover: color-mix(in srgb, var(--buttoncolor) 80%, var(--color-text-dm));

  /* ---- Site neutrals — derived from brand tokens ----
   * Folded into the brand palette so the whole site re-themes when the
   * DAW palette changes. */
  --ink:      var(--color-text);
  --ink-soft: color-mix(in srgb, var(--color-text) 70%, var(--color-bright));
  --ink-mute: color-mix(in srgb, var(--color-text) 70%, var(--color-bright));
  --bg-0:     var(--color-bright);
  --bg-1:     color-mix(in srgb, var(--color-bright) 92%, var(--color-text));
  --bg-2:     color-mix(in srgb, var(--color-bright) 84%, var(--color-text));
  --hairline: color-mix(in srgb, var(--color-text) 18%, transparent);

  /* ---- Motion ---- */
  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  --dur-fast:  180ms;
  --dur-med:   320ms;
  --dur-slow:  600ms;
}

/* ---- DARK mode ----
 * For pages without a [data-daw] scope, swap the mode-aware tokens to
 * the -dm anchors (matching the per-DAW dark-mode pattern in
 * src/<daw>/tokens.css). Site neutrals re-derive automatically. */
@media (prefers-color-scheme: dark) {
  :root {
    --color-bright:     var(--color-bright-dm);
    --color-dark:       var(--color-dark-dm);
    --color-text:       var(--color-text-dm);
    --color-background: var(--color-background-dm);
  }
}

/* ---- Generic theme (`[data-theme="generic"]`) ----
 * Used by chrome-style pages without a DAW context — /dl/, future
 * legal pages, marketing surfaces. Initial values are a verbatim copy
 * of the :root Logic-matching defaults so generic pages render
 * identically until these tokens are intentionally edited here.
 * Editing this block does NOT affect Logic (which has its own
 * [data-daw="logic"] scope in src/logic/tokens.css). */
[data-theme="generic"] {
  /* Heavily desaturated variant of the Logic blue family — same hue,
   * ~85% less chroma than the source. Reads as near-neutral gray with
   * just a hint of cool tint. Lightness preserved per token so
   * contrast ratios stay close to the original. Orange --color-buy
   * is unchanged. */
  --buttoncolor:        #6B7B86;
  --color-bright:       #E5E6E7;
  --color-dark:         #ACAEAF;
  --color-text:         #232425;
  --color-background:   #66696A;
  --color-header:       #666D71;
  --color-bright-dm:    #666D71;
  --color-dark-dm:      #252727;
  --color-text-dm:      #F2F3F3;
  --color-background-dm:#9BA2A6;
  --color-buy:          #F57C00;
  --hover: color-mix(in srgb, var(--buttoncolor) 80%, var(--color-text-dm));
}

@media (prefers-color-scheme: dark) {
  [data-theme="generic"] {
    --color-bright:     var(--color-bright-dm);
    --color-dark:       var(--color-dark-dm);
    --color-text:       var(--color-text-dm);
    --color-background: var(--color-background-dm);
  }
}

/* ===== base.css ===== */
/*! base — modern reset, typography, layout primitives, focus, skip-link.
 *
 * Self-hosted @font-face declarations are inlined here (no @import) — keeping
 * them inside the same CSS file the browser is already parsing avoids the
 * extra round trip that @import url() would introduce before any woff2 fetch
 * could start. NEVER replace any of these with a Google Fonts <link>: see
 * feedback_self_host_fonts memory.
 *
 * Subsetting: Latin only. Site copy is English + German; all needed glyphs
 * live in the unicode range covered by the latin (not latin-ext) subset
 * Google ships. The corresponding latin-ext woff2 files have been dropped.
 */

/* Poppins — 6 weights × latin only. font-display: swap matches Google's
 * default — show fallback text immediately, swap to Poppins when it loads. */
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 300; font-display: swap; src: url("/assets/global/fonts/poppins-latin-300.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/global/fonts/poppins-latin-400.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 500; font-display: swap; src: url("/assets/global/fonts/poppins-latin-500.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 600; font-display: swap; src: url("/assets/global/fonts/poppins-latin-600.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 700; font-display: swap; src: url("/assets/global/fonts/poppins-latin-700.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: "Poppins"; font-style: normal; font-weight: 800; font-display: swap; src: url("/assets/global/fonts/poppins-latin-800.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

/* Roboto Slab — single variable woff2 (300 + 400 weights). Only used by
 * .bbw-logo (the site wordmark). 34 KB total; no unicode-range subset. */
@font-face { font-family: "Roboto Slab"; font-style: normal; font-weight: 300 400; font-display: swap; src: url("/assets/global/fonts/robotoslab.woff2") format("woff2"); }

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

/* Reduce-motion handling is targeted (per-component) instead of globally killing all
 * transitions. Heavy motion (scroll-reveal, gauge fill) is gated explicitly in motion.css
 * + gauges.js. Subtle UI transitions (hover, focus) keep playing even with reduce-motion on
 * — they're below the threshold the spec is concerned about. The `?motion=force` URL
 * override (script in <head>) bypasses both CSS + JS gates for prototype evaluation. */
@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) { scroll-behavior: auto; }
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--color-text, var(--ink));
  background: var(--color-bright, var(--bg-0));
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

@media (prefers-color-scheme: dark) {
  body { background: var(--color-dark-dm, var(--bg-0)); }
}

img, picture, svg, video {
  display: block;
  max-width: 100%;
  height: auto;
}

button, input, select, textarea {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
  background: none;
  border: 0;
  padding: 0;
}

a {
  color: color-mix(in srgb, var(--buttoncolor) 80%, var(--color-text));
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}

a:hover {
  color: var(--hover, var(--ink-soft));
}

h1, h2, h3, h4 {
  line-height: 1.05;
  margin: 0 0 var(--sp-4);
  font-weight: 700;
  letter-spacing: -0.01em;
  text-align: center;
}

h1 { font-size: var(--fs-3xl); }
h2 { font-size: var(--fs-2xl); }
h3 { font-size: var(--fs-lg); font-weight: 300; margin-bottom: var(--sp-5); }
h4 { font-size: var(--fs-lg); }

/* Body paragraphs: centered, light weight (350), 150% line-height — matches live ms-bodytext exactly. */
p {
  margin: 0 0 var(--sp-4);
  text-align: center;
  font-weight: 350;
  line-height: 1.5;
  font-size: 1rem;
  padding-inline: 2vw;
}
small { font-size: var(--fs-sm); color: var(--color-text, var(--ink-mute)); }

.container {
  width: 100%;
  max-width: var(--max-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: var(--sp-9);
}

.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 for keyboard users */
.skip-link {
  position: absolute; left: -9999px; top: 0;
  background: var(--ink); color: var(--bg-0); padding: var(--sp-2) var(--sp-4);
  text-decoration: none;
}
.skip-link:focus {
  left: var(--sp-4); top: var(--sp-4); z-index: 1000;
}

/* Visible focus styles */
:focus-visible {
  outline: 2px solid var(--buttoncolor, var(--ink));
  outline-offset: 2px;
  border-radius: 2px;
}

/* ===== components.css ===== */
/*! components — reusable section components for DAW + auxiliary pages.
 * Hero, editorial section card, gauge counter, library-maker strip, library widget,
 * lightbox, buy section, newsletter, links row, setup-videos sub-block, Famewall.
 *
 * VISUAL LANGUAGE — matches live site exactly:
 *   - Every full-width section uses the brand-color gradient (--color-bright → --color-dark).
 *   - Body paragraphs are centered, weight 350, 150% line-height (set by base.css).
 *   - H2 section headlines use bg-clip text gradient (white → text in dark; text → #555 in light).
 *   - H1 hero is uppercase, 5vw font-size, weight 700.
 */

/* ===== Section background — gradient on top-level sections only ===== */
/* Editorial / stats / links-row are now LAYOUT-ONLY (no bg). They get a gradient
 * via an outer .bg-section wrapper, which lets multiple inner blocks share a single
 * gradient (e.g. editorial + counter + links-row combined into one segment). */
/* `section.section-editorial` (element + class) targets the standalone
 * editorial bands (Take Control / Everything In Perfect Order / Mission
 * Control). The *div*-variant inside .bg-section is intentionally excluded so
 * that the first editorial keeps sharing its gradient with the counter. */
.bg-section,
.hero,
section.section-editorial,
.newsletter,
.buy,
.testimonials-section,
.famewall-section {
  background: linear-gradient(180deg, var(--color-bright), var(--color-dark));
  border-bottom: 1px solid var(--color-dark);
  box-shadow: inset 0 -22px 22px -6px color-mix(in srgb, var(--color-text) 8%, transparent);
  color: var(--color-text);
}
@media (prefers-color-scheme: dark) {
  .bg-section,
  .hero,
  section.section-editorial,
  .newsletter,
  .buy,
  .testimonials-section,
  .famewall-section {
    background: linear-gradient(0deg, var(--color-bright), var(--color-dark));
    border-bottom: 1px solid color-mix(in srgb, var(--color-text) 80%, var(--color-bright));
    box-shadow: inset 0 -22px 22px -6px color-mix(in srgb, var(--color-dark) 8%, transparent);
  }
}

/* ===== Hero ===== */
.hero {
  position: relative;
  padding: var(--sp-11) var(--gutter) var(--sp-10);
  overflow: clip;
  text-align: center;
}
/* Mobile: drop the 160 px top air. The hero sits directly below the
 * sticky header + breadcrumb on product pages, so on a small viewport
 * 160 px of negative space pushes the headline below the fold for no
 * visual benefit. Bottom padding kept generous so the next section
 * doesn't crowd. */
@media (max-width: 820px) {
  .hero {
    padding-top: var(--sp-5);
    padding-bottom: var(--sp-7);
  }
}
.hero__title {
  max-width: 30ch;
  margin-inline: auto;
  margin-bottom: var(--sp-6);
  font-size: calc(5vw);
  line-height: 1.05;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0;
  text-align: center;
  /* Strong color-shift gradient — primary text → mid-tone derivation.
     Auto-themes for light/dark via --color-text and --color-bright cascade,
     so no separate dark-mode override is needed. */
  background: linear-gradient(180deg, var(--color-text), color-mix(in srgb, var(--color-text) 55%, var(--color-bright)));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

@media (max-width: 600px) {
  .hero__title { font-size: calc(8.5vw); }
}
.hero__image-wrap {
  position: relative;
  display: block;
  margin-inline: auto;
  margin-block: var(--sp-7) var(--sp-7);
  max-width: 1200px;
  cursor: pointer;
  text-decoration: none;
}
.hero__image-wrap img {
  width: 100%;
  height: auto;
  /* NO box-shadow + NO border-radius — the source PNG has alpha at the bottom edge
     (palette+tRNS), so the image fades naturally into the section's brand-color gradient.
     Adding shadow/radius here would draw a hard rectangle through the translucent area. */
}

/* YouTube play-button overlay — pulsing white YouTube glyph centred over
 * the hero image, no surrounding disc. Click anywhere on the image opens
 * the walkthrough video. The icon fades 0.55 ↔ 0.85 every 2.6s; hover/
 * focus pauses the pulse and snaps to opacity:1 + a small scale-up.
 * Was a masked-triangle glass disc — replaced site-wide so every DAW
 * hero gets the same affordance. */
.hero__play-overlay {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;       /* clicks pass through to the anchor */
  padding-bottom: 6%;          /* sits a touch above center so it doesn't collide with the bottom alpha-fade */
}
.hero__play-overlay::after { display: none; }
.hero__play-overlay__circle { /* structural anchor — no visual treatment */ }
@keyframes hero-yt-pulse {
  0%, 100% { opacity: 0.55; transform: scale(0.98); }
  50%      { opacity: 0.85; transform: scale(1.02); }
}
/* Cover both FA render modes: <i> (webfont) AND <svg class="svg-inline--fa">
 * (SVG mode the kit may swap in at runtime). Pure white (not the per-DAW
 * --color-text-dm cream) so the affordance reads identically on every
 * platform. Glass treatment = tight bright rim + small soft dark base
 * shadow, both <4px blur so they don't trace the YouTube logo's
 * rounded-rectangle outline the way a wider drop-shadow does. */
.hero__play-overlay i,
.hero__play-overlay svg,
.hero__play-overlay .svg-inline--fa {
  display: inline-block !important;
  font-size: clamp(60px, 9vw, 140px);
  color: #fff;
  line-height: 1;
  filter:
    drop-shadow(0 0 1px rgba(255, 255, 255, 0.5))
    drop-shadow(0 2px 4px rgba(0, 0, 0, 0.35));
  /* !important on the animation so a runtime FA-Kit SVG-mode style attr
   * can't silently win. */
  animation: hero-yt-pulse 2.6s ease-in-out infinite !important;
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.hero__image-wrap:hover  .hero__play-overlay i,
.hero__image-wrap:hover  .hero__play-overlay svg,
.hero__image-wrap:hover  .hero__play-overlay .svg-inline--fa,
.hero__image-wrap:focus-visible .hero__play-overlay i,
.hero__image-wrap:focus-visible .hero__play-overlay svg,
.hero__image-wrap:focus-visible .hero__play-overlay .svg-inline--fa {
  animation: none !important;
  opacity: 1;
  transform: scale(1.04);
}
@media (prefers-reduced-motion: reduce) {
  /* Honors the project's ?motion=force escape hatch (html:not(.force-motion)). */
  html:not(.force-motion) .hero__play-overlay i,
  html:not(.force-motion) .hero__play-overlay svg,
  html:not(.force-motion) .hero__play-overlay .svg-inline--fa {
    animation: none !important;
  }
}

.hero__ctas {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  justify-content: center;
}
/* Glass buttons — translucent WHITE fill (only a faint brand hint), subtle
 * white stroke, backdrop blur. Hover intensifies stroke + fill. No halo, no
 * drop-shadow, no transform lift. Pure tonal shift. Shared by .hero__cta and
 * .links-row__link so both share one visual language.
 *
 * White-derived glass tint (--color-text-dm = always-bright). In light
 * mode this gives a frosty white-on-light look with the page bg bleeding
 * through. Dark mode override below pins it to a subtler 10%/28% — the
 * original look — so the change is light-mode only. */
.hero__cta,
.links-row__link,
.setup-videos__links a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  background: color-mix(in srgb, var(--color-text-dm) 35%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text-dm) 70%, transparent);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: var(--color-text);
  border-radius: 8px;
  font-weight: 500;
  font-family: inherit;
  font-size: var(--fs-md);
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color 320ms cubic-bezier(0.16, 1, 0.3, 1),
    border-color     320ms cubic-bezier(0.16, 1, 0.3, 1),
    color            320ms cubic-bezier(0.16, 1, 0.3, 1);
}
.hero__cta:hover,
.hero__cta:focus-visible,
.links-row__link:hover,
.links-row__link:focus-visible,
.setup-videos__links a:hover,
.setup-videos__links a:focus-visible {
  background: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  border-color: color-mix(in srgb, var(--color-text-dm) 90%, transparent);
  color: var(--color-text);
}

/* Library-widget toggle caret: single chevron-down icon that flips
 * 180° (becoming chevron-up) when the controlled widget is open.
 * library-widget.js syncs aria-expanded on every trigger; CSS picks
 * up the state attribute and rotates. */
.lib-widget-toggle__caret {
  transition: transform var(--dur-fast) var(--ease-out);
}
[data-action="open-library-widget"][aria-expanded="true"] .lib-widget-toggle__caret {
  transform: rotate(180deg);
}
@media (prefers-color-scheme: dark) {
  .hero__cta,
  .links-row__link,
  .setup-videos__links a {
    background: color-mix(in srgb, var(--color-text-dm) 10%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 28%, transparent);
  }
  .hero__cta:hover,
  .hero__cta:focus-visible,
  .links-row__link:hover,
  .links-row__link:focus-visible,
  .setup-videos__links a:hover,
  .setup-videos__links a:focus-visible {
    background: color-mix(in srgb, var(--color-text-dm) 20%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  }
}

/* Mobile: glass-button family stretches full width so taps are easy and
 * the visual rhythm stays clean. Homepage DAW-pill override lives further
 * down in the file (near the .home-hero__daw-pill base rule) so the
 * cascade order is right. */
@media (max-width: 40em) {
  .hero__cta,
  .links-row__link,
  .setup-videos__links a {
    width: 100%;
    justify-content: center;
    font-size: var(--fs-sm);
    padding: var(--sp-3);
  }
  .hero__ctas {
    display: flex;
    flex-direction: column;
    width: 100%;
    align-items: stretch;
  }
  /* Switch the row containers to a column layout on mobile so children
   * stack and stretch full-width. Without this, the flex parent stays
   * inline-flex / row and `width: 100%` on the link only fills its
   * natural-width <li>. */
  .links-row__list,
  .setup-videos__links {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
  }
  .links-row__list > li { width: 100%; }
}


/* ===== Editorial section (alternating image / text) ===== */
.section-editorial {
  padding-block: var(--sp-11);
}
/* Section background runs edge-to-edge (full-bleed gradient), but the CONTENT inside
 * is constrained with comfortable outer breathing room (~120-150px each side) and
 * a small gap between columns so image + text sit close together visually.
 * Outer breathing room ↑ + inner column gap ↓ = the balance Marc asked for. */
.section-editorial > .container {
  max-width: 1400px;
  padding-inline: var(--sp-7);  /* ~48px outer padding */
  display: grid;
  gap: var(--sp-5);              /* ~24px between text + image columns */
  grid-template-columns: 1fr 1fr;
  align-items: center;
}
.section-editorial--image-left > .container { grid-template-areas: "image text"; }
.section-editorial--image-right > .container { grid-template-areas: "text image"; }
.section-editorial__image { grid-area: image; }
.section-editorial__text { grid-area: text; }

/* H2 — uppercase, live ms-headline gradient: visible color-shift.
 * Test: padding-inline + the parent's margin-inline zeroed so the headline
 * fills its full text-column width. */
.section-editorial__h2 {
  font-size: calc(2rem + 2vw);
  margin-inline: 0;
  margin-bottom: var(--sp-8);
  letter-spacing: -0.01em;
  text-transform: uppercase;
  line-height: 1.05;
  text-align: center;
  padding-inline: 0;
  background: linear-gradient(180deg, var(--color-text), color-mix(in srgb, var(--color-text) 55%, var(--color-bright)));
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  font-weight: 800;
}

/* Prose — small inline margins (matches live ms-center-narrow's 5%). Outer breathing
 * room comes from the container's max-width + padding, not from the prose itself. */
.section-editorial__text {
  text-align: center;
}
.section-editorial__text > * {
  margin-inline: 5%;
}
.section-editorial__text p {
  /* All paragraph defaults already set in base.css: centered, weight 350, line-height 150%, font-size 1rem.
     Only override colour here (use the per-DAW text token, not ink-soft). */
  color: var(--color-text);
  margin-bottom: var(--sp-4);
}

.section-editorial__image {
  text-align: center;
}
.section-editorial__image img {
  /* Image fills more of its column — closer to the text edge with the smaller column gap. */
  max-width: 90%;
  width: auto;
  height: auto;
  margin-inline: auto;
}
/* Key-switch keyboard image — 10% smaller than the default editorial image
 * (90% × 0.9 = 81% of column width). Same in light + dark mode. */
.section-editorial__image--key-switch img {
  max-width: 81%;
}
/* Slightly larger editorial image — used when the screenshot has dense
 * detail that benefits from filling the column edge-to-edge. */
.section-editorial__image--large img {
  max-width: 100%;
}
/* 30% smaller than the default editorial image (0.7 × 90% = 63%).
 * Used when a screenshot looks too prominent at default size. */
.section-editorial__image--small img {
  max-width: 63%;
}

/* ===== Home hero =====
 * Full-viewport hero on the site root. Background image + dim overlay
 * + centred H1 + DAW pill links + subbanner copy. The header partial
 * floats above this with no transparent-bg trick — it's just sticky
 * site nav, and the hero starts immediately below it. Pills are
 * white-on-image with a backdrop blur, hover snaps to a solid white
 * fill with dark text. */
.home-hero {
  position: relative;
  min-height: calc(100svh - 64px);  /* full viewport minus the sticky header. The bottom .home-lang-picker overlaps the hero's last 120px (negative margin-top below) so its glass strip shows the hero image blurred behind it — the whole point of the glass treatment. */
  display: grid;
  place-items: center;
  padding: var(--sp-7) var(--gutter);
  /* No isolation/overflow:hidden here — homepage bg layers below are
   * position: fixed and need to escape this section to extend behind
   * the sticky header (top) and the .home-lang-picker (bottom) so the
   * glass strips have something to blur. */
}
/* Homepage backdrop: pin the studio photo + animated gradient to the
 * viewport (not the .home-hero section) so the nav glass strip at the
 * top and the .home-lang-picker glass strip at the bottom both blur
 * the bg image. Without this, the bg only fills the hero rectangle
 * and the surrounding glass strips have nothing behind them but solid
 * body bg → no visible glass effect. */
[data-page="homepage"] .home-hero__bg,
[data-page="homepage"] .home-hero__gradient,
[data-page="homepage"] .home-hero__overlay {
  position: fixed;
  inset: 0;
  z-index: -10;
}
.home-hero__bg {
  position: absolute;
  inset: 0;
  /* JPEG fallback for browsers without WebP support; image-set() below
   * upgrades to WebP where supported. Media queries pick the right
   * resolution variant per viewport so phones never download the 2400w. */
  background: url("/assets/homepage/img/hero/studio-1200.jpg") center/cover no-repeat;
  background-image: image-set(
    url("/assets/homepage/img/hero/studio-1200.webp") type("image/webp")
  );
  z-index: -3;
}
@media (max-width: 800px) {
  .home-hero__bg {
    background-image: image-set(url("/assets/homepage/img/hero/studio-800.webp") type("image/webp"));
  }
}
@media (min-width: 1801px) {
  .home-hero__bg {
    background-image: image-set(url("/assets/homepage/img/hero/studio-2400.webp") type("image/webp"));
  }
}
/* Animated gradient sweep on top of the studio photo. mix-blend-mode:
 * overlay means the photo's mid-greys pick up the gradient hue while
 * shadows / highlights keep their original tone — the image always
 * shines through the colour movement instead of being painted over.
 * Same -45deg / four-stop / 400% / 15s ease infinite recipe as the
 * P1N2O codepen. */
.home-hero__gradient {
  position: absolute;
  inset: 0;
  background: linear-gradient(-45deg, #95D8EB, #4DB4D7, #0076BE, #48BF91, #8BD9C7);
  background-size: 400% 400%;
  animation: home-hero-gradient 15s ease infinite;
  mix-blend-mode: overlay;
  z-index: -2;
  pointer-events: none;
}
@keyframes home-hero-gradient {
  0%, 100% { background-position: 0% 50%; }
  50%      { background-position: 100% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .home-hero__gradient {
    animation: none;
    /* Park on a balanced mid-position so the image still gets a
     * pleasant tint, just no movement. */
    background-position: 50% 50%;
  }
}
.home-hero__overlay {
  position: absolute;
  inset: 0;
  /* Very light dim — the screen blend already brightens the photo
   * enough that text legibility comes mostly from text-shadow on the
   * H1 / sub copy; this just nudges blacks back a touch. */
  background: rgba(0, 0, 0, 0.15);
  z-index: -1;
}
.home-hero__content {
  /* Block; centered by .home-hero's grid. The H1 sets its own text
   * width (font-size scales with vw), and homepage/controller.js
   * measures that and applies it as inline style on .home-hero__daw-list
   * so the first pill always lines up with the H1's left edge and the
   * last with its right. Capped at 95vw / 1100px so the H1 itself
   * can't overflow on huge monitors. Mobile fallback (≤800px) below
   * clears the inline width and falls back to centered + wrap. */
  max-width: min(95vw, 1100px);
  color: #fff;
}
.home-hero__h1 {
  text-align: center;
  font-size: clamp(2.5rem, 6vw, 5rem);
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0 0 var(--sp-7);
  line-height: 1.05;
  /* 15% see-through so the gradient + photo bleed through the type. */
  opacity: 0.85;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
.home-hero__h1-text { white-space: nowrap; }
@media (max-width: 40em) {
  .home-hero__h1 { font-size: clamp(1.75rem, 8vw, 2.5rem); }
}
/* DAW links on the home hero. Geometry matches the .hero__cta /
 * .links-row__link family used on the DAW product pages: 8px corners,
 * sp-3/sp-5 padding, var(--fs-md) text. The fill / stroke / text
 * colour stays white-on-dark because they sit on the dark hero image,
 * not on a mode-aware page surface. Hover intensifies the translucent
 * fill (matches the other glass-button family) — no snap to solid
 * white, no transform lift, no drop-shadow. */
.home-hero__daw-list {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--sp-2);
  justify-content: space-between;
  align-items: center;
  width: 100%;
  list-style: none;
  padding: 0;
  margin: 0 0 var(--sp-7);
}
.home-hero__daw-pill {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-3) var(--sp-5);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 8px;
  color: #fff;
  font-weight: 500;
  font-size: var(--fs-md);
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  transition:
    background-color 320ms cubic-bezier(0.16, 1, 0.3, 1),
    border-color     320ms cubic-bezier(0.16, 1, 0.3, 1);
}
.home-hero__daw-pill:hover,
.home-hero__daw-pill:focus-visible {
  background: rgba(255, 255, 255, 0.25);
  border-color: rgba(255, 255, 255, 0.6);
  /* Explicit white — overrides the global a:hover rule in base.css
   * (a:hover specificity (0,1,1) would otherwise beat the pill's
   * base color: #fff (0,1,0) on hover). */
  color: #fff;
  outline: none;
}
.home-hero__sub {
  font-size: var(--fs-md);
  font-weight: 300;
  color: rgba(255, 255, 255, 0.92);
  max-width: 60ch;
  margin: 0 auto;
  text-align: center;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.3);
}
@media (max-width: 800px) {
  /* On narrow screens, space-between with nowrap collides — pill text
   * gets clipped or the row overflows. Drop back to full-width content +
   * centered, wrapped pill list. */
  .home-hero__content {
    width: 100%;
    max-width: none;
  }
  .home-hero__daw-list {
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-3);
  }
}
/* Mobile homepage pills: only the first two (Logic + Cubase) pair up
 * at 50/50; everything below stacks full-width. Placed AFTER the
 * .home-hero__daw-pill base rule above so the cascade picks the mobile
 * padding/width — same-specificity rules: last declaration wins. */
@media (max-width: 40em) {
  .home-hero__daw-list > li {
    flex: 0 0 100%;
  }
  .home-hero__daw-list > li:nth-child(-n+2) {
    flex-basis: calc(50% - var(--sp-3) / 2);
  }
  .home-hero__daw-pill {
    width: 100%;
    justify-content: center;
    padding: var(--sp-3) var(--sp-2);
  }
}
/* Above 800px, controller.js adds .is-cramped to the list when the
 * pills' natural total exceeds the H1's text width — same wrap +
 * centre treatment as the mobile fallback, so the dead zone where
 * pills used to overflow / clip simply doesn't exist. The list keeps
 * its JS-set width (= H1 width) so wrapped rows centre within the
 * H1's horizontal bounds. */
.home-hero__daw-list.is-cramped {
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-3);
}
@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .home-hero__daw-pill {
    transition: none;
  }
}
@media (max-width: 800px) {
  .section-editorial > .container {
    grid-template-columns: 1fr !important;
    grid-template-areas: "image" "text" !important;
    /* Push image away from the headline so the h2 visually belongs to
     * the paragraph below it, not to the screenshot above it. */
    gap: var(--sp-8);
  }
  /* Pull the paragraph closer to the h2 (was sp-8 / 64px). */
  .section-editorial__h2 {
    margin-bottom: var(--sp-6);
  }
}
/* Mobile: tighten the editorial container's outer padding so the h2 and
 * paragraph aren't squeezed by ~48px gutters. */
@media (max-width: 40em) {
  .section-editorial > .container {
    padding-inline: var(--sp-3);
  }
}

/* ===== Stat counter — Variant C inside a bento glass panel ===== */
.stats {
  padding: 0 var(--gutter) var(--sp-8);
}
.bg-section .section-editorial {
  padding-block: var(--sp-10) var(--sp-8);
}
.bg-section .stats {
  padding: var(--sp-7) var(--gutter) var(--sp-7);
}
/* Stat-strip wrapper — width aligns with the editor screenshot above:
 *   editorial container (max 1400) - 2× sp-7 outer padding = 1304 inner content.
 *   image is 90% of its 1fr column, centered → image right edge sits 32px in
 *   from container's inner edge → panel max-width = 1304 - 2×32 = 1240. */
.stats__inner {
  max-width: 1240px;
  margin-inline: auto;
  container-type: inline-size;
}

/* 4-card stat strip in a glass bento frame. Cards sit edge-to-edge inside the panel,
 * each tinted with a progressive black overlay over the per-DAW brand colour
 * (matches live's opacity-1/2/4/6 pattern). */
.stats-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border-radius: 20px;
  overflow: hidden;                 /* clip the inner cards to the rounded corner */
  border: 1px solid color-mix(in srgb, var(--color-text-dm) 22%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
}
@container (max-width: 720px) {
  .stats-strip { grid-template-columns: repeat(2, 1fr); }
}

.stat-card {
  padding: var(--sp-7) var(--sp-4) var(--sp-6);
  text-align: center;
  position: relative;
  /* Theme-aware text — dark on light gradient, light on dark gradient.
   * Was hard-coded white, which dropped to 1.88:1 contrast in light mode. */
  color: var(--color-text);
  /* Same stroke recipe as .hero__cta / .links-row__link — ties the counter
   * cards into the glass-button family visually. */
  border: 1px solid color-mix(in srgb, var(--color-text) 28%, transparent);
  /* container-type lets us size the number relative to THIS card's width via cqi units */
  container-type: inline-size;
}
/* Original opaque recipe (buttoncolor mixed with black at 70/56/42/28%),
 * wrapped in another mix that swaps in 15% transparency so a hint of the
 * gradient shows through — colors and contrast preserved, just lifted off
 * fully-opaque a touch. */
.stat-card--1 { background: color-mix(in srgb, color-mix(in srgb, var(--buttoncolor) 70%, var(--color-dark-dm)) 85%, transparent); }
.stat-card--2 { background: color-mix(in srgb, color-mix(in srgb, var(--buttoncolor) 56%, var(--color-dark-dm)) 85%, transparent); }
.stat-card--3 { background: color-mix(in srgb, color-mix(in srgb, var(--buttoncolor) 42%, var(--color-dark-dm)) 85%, transparent); }
.stat-card--4 { background: color-mix(in srgb, color-mix(in srgb, var(--buttoncolor) 28%, var(--color-dark-dm)) 85%, transparent); }

/* Number sized to take ~70% of the cell's horizontal space.
 * Compound selector beats any .gauge__num inheritance. */
.stat-card .stat-card__number {
  position: static;                    /* defeat any inherited absolute positioning */
  font-size: clamp(2rem, 20cqi, 5rem); /* 20% of card inline-size, with min/max guards */
  font-weight: 700;
  color: var(--color-text-dm);
  line-height: 1.05;
  margin: 0 0 var(--sp-4) 0;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  display: block;
}
/* Label — matches live ms-bodytext font-size (calc(1.0rem)) + ms-bodytext line-height. */
.stat-card__label {
  font-size: calc(1.0rem);
  font-weight: 300;
  color: var(--color-text-dm);
  line-height: 150%;
}
.gauges {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 32px 96px;              /* much wider horizontal gap between gauges */
}
@container (max-width: 720px) {
  .gauges { grid-template-columns: repeat(2, 1fr); gap: 32px 48px; }
}
.gauge {
  position: relative;
  text-align: center;
  min-width: 0;
}
.gauge svg {
  display: block;
  margin: 0 auto;
  width: 100%;
  max-width: 182px;        /* 30% bigger than the previous 140px */
  height: auto;
  overflow: visible;
}
/* tonal-fill discs (subtle brand-color accents) */
.gauge--1 .gauge__inner { fill: color-mix(in srgb, var(--buttoncolor) 18%, transparent); }
.gauge--2 .gauge__inner { fill: color-mix(in srgb, var(--buttoncolor) 12%, transparent); }
.gauge--3 .gauge__inner { fill: color-mix(in srgb, var(--buttoncolor)  7%, transparent); }
.gauge--4 .gauge__inner { fill: color-mix(in srgb, var(--buttoncolor)  3%, transparent); }
.gauge__bg {
  stroke: var(--hairline);
  fill: none;
}
.gauge__fg {
  fill: none;
  stroke: var(--buttoncolor);
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  /* Linear timing — gauges.js sets the actual transition with the per-gauge speed
   * in linear mode so the ring fill stays in lockstep with the count-up number. */
  transition: stroke-dashoffset 6000ms linear;
}
.gauge__num {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(0.95rem, 0.75rem + 0.8vw, 1.3rem);
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  padding-bottom: 22px;
}
/* Label matches paragraph styling: same color, size, weight, line-height. */
.gauge__label {
  margin-top: 10px;
  font-size: 1rem;
  font-weight: 350;
  line-height: 1.5;
  color: var(--color-text);
}

/* ===== Links row (below counter on DAW pages) ===== */
.links-row {
  padding-block: var(--sp-8) var(--sp-9);
}
.links-row__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  justify-content: center;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Icons (FA) inherit via currentColor — no explicit color needed.
 * Visual styling for .links-row__link lives in the shared glass-button block
 * near the top of this file (with .hero__cta). */

/* ===== Logic Remote — partner cards + intro CTA =====
 * Lives inside the Logic Remote "Streamline your workflow" section. The
 * intro-cta is a centered "Watch Introduction Video" button beneath the
 * iPad image; lr-cards is a 3-column row of MetaGrid / PlugSearch /
 * Art Conductor partner callouts that span both columns of the parent
 * section-editorial grid (same pattern as .setup-videos). */
.lr-streamline__intro-cta {
  text-align: center;
  margin-top: var(--sp-5);
}
.lr-cards {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-5);
  margin-top: var(--sp-9);
}
@media (max-width: 720px) {
  .lr-cards { grid-template-columns: 1fr; }
}
.lr-card {
  padding: var(--sp-6) var(--sp-5);
  border: 1px solid color-mix(in srgb, var(--color-text) 28%, transparent);
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.lr-card__title {
  font-size: var(--fs-lg);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin: 0 0 var(--sp-4);
  color: var(--color-text);
}
.lr-card p {
  margin: 0 0 var(--sp-6);
  flex: 1;
  font-weight: 350;
  line-height: 1.6;
  text-align: center;
}
.lr-card__cta {
  display: inline-block;
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid color-mix(in srgb, var(--color-text) 30%, transparent);
  border-radius: 8px;
  font-weight: 600;
  text-decoration: none;
  color: var(--color-text);
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color     var(--dur-fast) var(--ease-out);
}
.lr-card__cta:hover,
.lr-card__cta:focus-visible {
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
  border-color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.lr-cards__footnote {
  grid-column: 1 / -1;
  text-align: center;
  margin: var(--sp-5) 0 0;
  font-size: var(--fs-sm);
  font-style: italic;
  color: color-mix(in srgb, var(--color-text) 60%, var(--color-bright));
}

/* ===== Setup-videos sub-block ===== */
/* Sibling row inside .section-editorial > .container — spans BOTH grid
 * columns so it sits centered under the text+image row. */
.setup-videos {
  grid-column: 1 / -1;
  margin-top: var(--sp-9);
  text-align: center;
  color: var(--ink-soft);
}
.setup-videos__label {
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.setup-videos__links {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  justify-content: center;
}
/* Each video link uses the shared glass-button recipe — added to the
 * .hero__cta / .links-row__link selector group at the top of this file. */

/* ===== Library-maker logo strip — REMOVED 2026-05-15 =====
 * The .library-makers__* rules and section markup were removed from
 * DAW pages. Source webp files + the partial at
 * src/global/partials/library-makers.html are kept (still consumed by
 * tools/build-flyin-assets.py + the homepage flyin runtime).
 * To restore the on-page strip, lift the previous rule block from git
 * history and add the section markup back to the DAW view templates.
 */

/* ===== Library widget (port of approved prototype, skinned to design system) ===== */
.lib-widget {
  /* Match counter panel width (1240px) and centering, regardless of parent container. */
  max-width: 1240px;
  margin: var(--sp-7) auto 0;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: max-height var(--dur-slow) var(--ease-out), opacity var(--dur-med) var(--ease-out), margin-top var(--dur-med) var(--ease-out);
  /* When openWidget calls scrollIntoView({block:"start"}), this offset
   * keeps the widget's top edge below the sticky site header instead
   * of being tucked behind it. ~88px covers the header bar + the small
   * product-page breadcrumb row. */
  scroll-margin-top: 88px;
}
.lib-widget[data-open="true"] {
  max-height: 1600px;
  opacity: 1;
}
/* Glass panel — translucent fill + backdrop blur. In LIGHT mode we tint
 * with the always-bright anchor (lifts above the bright gradient); in DARK
 * mode we tint with the always-dark anchor so the panel reads as RECESSED
 * below the dark gradient. Wells (search input, vendor buttons, autocomplete)
 * follow the same convention. Text colors use theme-aware --color-text so
 * they adapt automatically. */
.lib-widget__inner {
  padding: var(--sp-6) var(--sp-5);
  background: color-mix(in srgb, var(--color-text-dm) 22%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  color: var(--color-text);
}
@media (prefers-color-scheme: dark) {
  .lib-widget__inner {
    background: color-mix(in srgb, var(--color-dark-dm) 40%, transparent);
  }
  .lib-widget__search input,
  .vendor {
    background: color-mix(in srgb, var(--color-dark-dm) 50%, transparent);
    border-color: color-mix(in srgb, var(--color-text) 22%, transparent);
  }
  .ac__item.is-active, .ac__item:hover,
  .vendor:hover,
  .lib-result:hover {
    background: color-mix(in srgb, var(--color-dark-dm) 25%, transparent);
  }
}
.lib-widget__head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--sp-4); margin-bottom: var(--sp-4); flex-wrap: wrap;
}
.lib-widget__title { margin: 0; font-size: var(--fs-lg); font-weight: 600; color: var(--color-text); line-height: 1.2; }
.lib-widget__legend {
  margin: 0 auto 0 var(--sp-4);
  padding-inline: 0;
  font-size: var(--fs-sm);
  line-height: 1.2;
  color: var(--ink-mute);
  white-space: nowrap;
}
.lib-widget__legend .fa-square-plus { margin-right: 4px; }
/* Inline icon next to library names — sized to match surrounding text */
.lib-col .flags.fa-square-plus,
.lib-list .flags.fa-square-plus {
  font-size: 0.85em;
  color: var(--buttoncolor);
  vertical-align: baseline;
}
.lib-widget__totals { color: var(--ink-mute); font-weight: 400; font-size: var(--fs-sm); }
/* All "well" backgrounds inside the widget — search input, autocomplete,
 * vendor buttons, library hover — share one really-dark blue-tinted recipe so
 * they read as recessed wells INSIDE the panel (panel = 28% buttoncolor; wells
 * sit lower at 12%). Hover lifts them toward 26%. */
.lib-widget__close {
  /* min-height meets WCAG 2.5.5 AAA touch target (44×44+ on mobile) */
  padding: var(--sp-3) var(--sp-4);
  min-height: 44px;
  border: 1px solid color-mix(in srgb, currentColor 55%, transparent);
  background: transparent; color: var(--color-text); border-radius: 6px;
  font-size: var(--fs-sm);
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.lib-widget__close:hover {
  background: color-mix(in srgb, currentColor 14%, transparent);
  border-color: currentColor;
}

/* Results area — min-height is set per-widget by JS after initial render,
 * so each widget sizes to its own content while still resisting shrink on search.
 * Content scrolls internally if it grows past max-height. */
.lib-widget__view {
  max-height: 60vh;
  /* CSS min-height (vs the previous JS lock to scrollHeight) gives
   * just enough stability to avoid flicker as search narrows the
   * results — without trapping the panel at content-height when the
   * content is bigger than the viewport (which broke inner scroll on
   * mobile vendor lists like Orchestral Tools at 116 libraries). */
  min-height: 320px;
  overflow-y: auto;
  /* overscroll-behavior: contain stops touch-scroll from chaining to
   * the page when the inner panel hits its top/bottom — without it,
   * iOS bubbles the gesture and users can't reliably scroll long lists. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
@media (max-width: 760px) {
  /* The view's max-height has to account for everything that lives
   * INSIDE the visible viewport but ABOVE/BELOW the view itself —
   * otherwise the sticky "Show more" button at the panel's bottom
   * lands behind iOS Safari's URL/toolbar:
   *
   *   100dvh                  visible viewport (already excludes the
   *                            iOS top status bar + bottom toolbar)
   *   - ~80px sticky site header (sits inside dvh, at the top)
   *   - ~50px widget head      (h2 title + close + legend)
   *   - ~60px widget search    (input)
   *   - ~50px widget inner padding (top + bottom)
   *   - ~40px safety margin    (page padding, potential scrollbars,
   *                              autocomplete dropdown space)
   *   ─────────
   *   = calc(100dvh - 280px)
   *
   * vh stays as the fallback for browsers without dvh
   * (pre-iOS-15.4 / pre-Chrome-108). */
  .lib-widget__view {
    max-height: 75vh;
    max-height: calc(100dvh - 280px);
  }
  /* Single-vendor view (top-row is rendered by JS only in that state):
   * hide the widget's head (title + legend + close) and search, since
   * the top-row supplies back + close and search isn't useful while
   * looking at one vendor's full library list. Maximises vertical
   * space for the list itself. Desktop keeps the full chrome. */
  .lib-widget:has(.lib-widget__top-row) .lib-widget__head,
  .lib-widget:has(.lib-widget__top-row) .lib-widget__search {
    display: none;
  }
  /* Recompute view max-height now that head + search are gone — give
   * those ~140px back to the list. */
  .lib-widget:has(.lib-widget__top-row) .lib-widget__view {
    max-height: calc(100dvh - 140px);
  }
  .lib-widget__view-close {
    display: inline-flex;
  }
}

/* "Show more (N left)" button shown when the rendered list is
 * truncated to PAGE_SIZE. Sticky-bottom inside the scrollable panel
 * so it's always visible — user doesn't have to slog through the
 * full list to find it on mobile (where the panel scrolls 1000+ px).
 * On short content (no scroll needed) sticky is a no-op; button sits
 * at its natural bottom. The opaque-ish glass bg + saturate gives
 * legibility when items scroll past behind it. */
.lib-widget__more {
  display: block;
  position: sticky;
  bottom: 0;
  margin: var(--sp-5) auto 0;
  padding: var(--sp-3) var(--sp-6);
  background: color-mix(in srgb, var(--color-text-dm) 90%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 28%, transparent);
  color: var(--color-text);
  border-radius: 8px;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  z-index: 1;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color     var(--dur-fast) var(--ease-out);
}
@media (prefers-color-scheme: dark) {
  .lib-widget__more {
    background: color-mix(in srgb, var(--color-dark-dm) 90%, transparent);
  }
}
.lib-widget__more:hover,
.lib-widget__more:focus-visible {
  background: color-mix(in srgb, var(--color-text) 18%, transparent);
  border-color: color-mix(in srgb, var(--color-text) 48%, transparent);
  outline: none;
}

.lib-widget__search {
  position: relative;
  margin-bottom: var(--sp-4);
}
.lib-widget__search input {
  width: 100%;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  border-radius: 8px;
  background: color-mix(in srgb, currentColor 8%, transparent);
  color: var(--color-text);
  font-size: var(--fs-md);
}

/* Autocomplete dropdown floats over the panel's results area, so it must
 * be near-opaque to occlude the content beneath (otherwise text bleeds
 * through). Other wells (.lib-widget__search input, .vendor) stay
 * translucent because they sit inside the panel with no content behind.
 * Light mode = always-bright fill; dark mode = always-dark fill. */
.ac {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
  background: color-mix(in srgb, var(--color-text-dm) 92%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  border-radius: 8px; margin-top: 4px;
  max-height: 320px; overflow-y: auto;
  box-shadow: 0 14px 40px color-mix(in srgb, var(--color-dark-dm) 10%, transparent);
}
@media (prefers-color-scheme: dark) {
  .ac {
    background: color-mix(in srgb, var(--color-dark-dm) 92%, transparent);
    border-color: color-mix(in srgb, var(--color-text) 22%, transparent);
  }
}
.ac[hidden] { display: none; }
.ac__item {
  padding: var(--sp-2) var(--sp-4);
  cursor: pointer;
  display: flex; gap: var(--sp-3); align-items: baseline;
  border-bottom: 1px solid var(--hairline);
}
.ac__item:last-child { border-bottom: 0; }
.ac__item.is-active, .ac__item:hover { background: color-mix(in srgb, currentColor 18%, transparent); }
.ac__name { flex: 1; }
.ac__sub { color: var(--ink-mute); font-size: var(--fs-sm); }

/* Vendor tiles — column-major BALANCED layout (mirror of .lib-list/.lib-col
 * for library entries, but without the col-1-to-panel-bottom bias). All
 * available columns get an even slice; vertical dividers between columns
 * keep the visual language consistent with the library view. */
.vendor-list { display: flex; align-items: stretch; gap: 0; }
.vendor-col {
  flex: 1; min-width: 0;
  padding: 0 var(--sp-3);
  border-right: 1px solid var(--hairline);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.vendor-col:first-child { padding-left: 0; }
.vendor-col:last-child  { padding-right: 0; border-right: 0; }
.vendor {
  font: inherit; text-align: left; padding: var(--sp-2) var(--sp-3);
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  color: var(--color-text);
  background: color-mix(in srgb, currentColor 6%, transparent);
  border-radius: 6px; cursor: pointer;
  display: flex; justify-content: space-between; align-items: center;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.vendor:hover {
  background: color-mix(in srgb, currentColor 16%, transparent);
  border-color: var(--buttoncolor);
}
.vendor__count { color: var(--ink-mute); font-size: var(--fs-sm); }

/* Sticky top-row containing the back + (mobile-only) close buttons.
 * Pinned to the panel's top edge so users with deep scroll position
 * can always reach navigation. The whole row is the sticky element;
 * back and close inside are plain buttons. */
.lib-widget__top-row {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  /* Bleed into the panel's padding so the glass bg spans full width */
  margin: 0 calc(-1 * var(--sp-5)) var(--sp-3);
  padding: var(--sp-2) var(--sp-5);
  background: color-mix(in srgb, var(--color-text-dm) 90%, transparent);
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
}
@media (prefers-color-scheme: dark) {
  .lib-widget__top-row {
    background: color-mix(in srgb, var(--color-dark-dm) 90%, transparent);
  }
}

.lib-widget__back {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  color: var(--buttoncolor); cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  font: inherit;
}
.lib-widget__back:hover { text-decoration: underline; }

/* In-view close button — duplicate of .lib-widget__close, shown only
 * on mobile where the head (containing the original close) is hidden
 * for compactness. Desktop hides this; the head close stays primary. */
.lib-widget__view-close {
  display: none;
  background: transparent;
  border: 1px solid color-mix(in srgb, currentColor 55%, transparent);
  border-radius: 6px;
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--color-text);
  cursor: pointer;
  align-items: center;
}
.lib-widget__view-close:hover {
  background: color-mix(in srgb, currentColor 14%, transparent);
  border-color: currentColor;
}

/* Column-major fill via JS-built wrapper ULs (one per column).
 * Strict order: column 1 fills to its bottom, then column 2, then column 3. */
.lib-list { display: flex; align-items: stretch; gap: 0; }
.lib-col {
  list-style: none; margin: 0; padding: 0 var(--sp-5);
  flex: 1; min-width: 0;
  border-right: 1px solid var(--hairline);
}
.lib-col:first-child { padding-left: 0; }
.lib-col:last-child  { padding-right: 0; border-right: 0; }

/* Inside the widget results area, force LEFT alignment — overrides the global
 * centered defaults from base.css (p, h1-h4 are centered site-wide). */
.lib-widget__view,
.lib-widget__view *,
.lib-widget__back {
  text-align: left;
}
.lib-col li { padding: 4px 0; }
.lib-col .name { /* inline so flags sit right next to it */ }
.lib-col .flags { margin-left: 6px; font-size: var(--fs-sm); white-space: nowrap; }

.results-section { margin-bottom: var(--sp-5); }
.results-section:last-child { margin-bottom: 0; }
.results-section h3 {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-mute); font-weight: 600;
}
.results-empty { color: var(--ink-mute); font-style: italic; padding: var(--sp-4); text-align: center; }
.lib-result {
  padding: var(--sp-1) var(--sp-3);
  margin: 2px 0; border-radius: 4px;
  transition: background var(--dur-fast) var(--ease-out);
}
.lib-result:hover { background: color-mix(in srgb, currentColor 16%, transparent); }
.lib-result__vendor { color: var(--ink-mute); font-size: var(--fs-sm); margin-left: var(--sp-2); }

mark { background: color-mix(in srgb, var(--buttoncolor) 25%, transparent); color: inherit; padding: 0 2px; border-radius: 2px; }

/* ===== YouTube lightbox ===== */
.lightbox {
  position: fixed; inset: 0; z-index: 300;
  /* Soft frosted backdrop — lets the page tone bleed through, just blurred
   * and slightly dimmed. Was rgba(8,12,18,0.92) (near-opaque black). */
  background: color-mix(in srgb, var(--color-dark-dm) 25%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  display: none;
  align-items: center; justify-content: center;
  padding: var(--sp-5);
}
.lightbox[data-open="true"] { display: flex; }
.lightbox__frame {
  width: 100%;
  max-width: 1500px;
  aspect-ratio: 16/9;
  background: var(--color-dark-dm);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 40px 100px color-mix(in srgb, var(--color-dark-dm) 50%, transparent);
}
.lightbox__frame iframe {
  width: 100%; height: 100%; border: 0;
}
.lightbox__close {
  position: absolute; top: var(--sp-5); right: var(--sp-5);
  background: color-mix(in srgb, var(--color-text-dm) 10%, transparent);
  color: var(--color-text-dm); border: 0;
  /* 48×48 meets WCAG 2.5.5 AAA touch target minimum */
  width: 48px; height: 48px;
  border-radius: 50%;
  font-size: 1.2rem;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.lightbox__close:hover { background: color-mix(in srgb, var(--color-text-dm) 25%, transparent); }

/* ===== Image-zoom (click-to-enlarge) =====
 * Wrapper opt-in: <button class="zoomable">. Hover/focus reveals a
 * translucent magnifier disc overlay (.zoomable__hint). Click is
 * handled by src/global/js/image-zoom.js — opens the .image-zoom
 * overlay below with the full-resolution image on a blurred backdrop.
 * Reusable across the site; just include the script + use the markup
 * pattern (see image-zoom.js header for the full pattern). */
.zoomable {
  position: relative;
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  font: inherit;
  color: inherit;
  cursor: zoom-in;
  text-align: left;
  /* Strip the default focus outline UA gives <button>; we replace it via
   * the global :focus-visible rule on focus only. */
}
.zoomable > img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
}
.zoomable__hint {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.zoomable__hint i,
.zoomable__hint svg,
.zoomable__hint .svg-inline--fa {
  /* The hint floats over an arbitrary image whose tone we don't control,
   * so we pin the glyph to white + heavy dark drop-shadow. Reads on light
   * AND dark image content; independent of the page's light/dark theme. */
  font-size: clamp(28px, 4.5vw, 56px);
  color: #fff;
  filter:
    drop-shadow(0 0 2px rgba(0, 0, 0, 0.85))
    drop-shadow(0 2px 8px rgba(0, 0, 0, 0.55));
}
.zoomable:hover .zoomable__hint,
.zoomable:focus-visible .zoomable__hint {
  opacity: 1;
}

/* Full-size overlay (lazy-mounted by image-zoom.js). */
.image-zoom {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: color-mix(in srgb, var(--color-dark-dm) 40%, transparent);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--sp-5);
  cursor: zoom-out;
}
.image-zoom[data-open="true"] { display: flex; }
.image-zoom__img {
  max-width: 95vw;
  max-height: 90vh;
  display: block;
  object-fit: contain;
  border-radius: 8px;
  /* drop-shadow follows the alpha mask — iPad-shaped PNG gets an
   * iPad-shaped shadow. box-shadow would cast from the rectangular
   * border-box and ignore alpha. */
  filter: drop-shadow(0 40px 100px color-mix(in srgb, var(--color-dark-dm) 60%, transparent));
  cursor: default;
}
.image-zoom__close {
  position: absolute;
  top: var(--sp-5);
  right: var(--sp-5);
  background: color-mix(in srgb, var(--color-text-dm) 12%, transparent);
  color: var(--color-text-dm);
  border: 1px solid color-mix(in srgb, var(--color-text-dm) 30%, transparent);
  width: 48px;
  height: 48px;
  border-radius: 50%;
  font-size: 1.2rem;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.image-zoom__close:hover,
.image-zoom__close:focus-visible {
  background: color-mix(in srgb, var(--color-text-dm) 25%, transparent);
  border-color: color-mix(in srgb, var(--color-text-dm) 60%, transparent);
}
/* Scroll-lock while overlay is open (toggled by image-zoom.js). */
.body--zoom-open { overflow: hidden; }

/* ===== Buy section ===== */
.buy {
  padding-block: var(--sp-11);
}
.buy__inner {
  max-width: var(--max-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  gap: var(--sp-7);
  grid-template-columns: auto 1fr;
  align-items: start;
}
@media (max-width: 720px) {
  .buy__inner { grid-template-columns: 1fr; text-align: center; }
}
.buy__packshot { width: 120px; height: auto; }
@media (max-width: 720px) {
  .buy__packshot { display: none; }
}
.buy__eyebrow {
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-buy) 92%, var(--color-text));
  margin-bottom: var(--sp-2);
}
.buy__title {
  font-size: calc(1rem + 2vw);
  line-height: 1.05;
  color: var(--color-text);
  text-align: left;
  margin-bottom: var(--sp-2);
}
.buy__price {
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--color-text);
  line-height: 1.6;
  text-align: left;
  margin-bottom: var(--sp-5);
}
/* Discount-lvl: when ?discount=…&lvl=N applies a percent discount,
 * discount.js adds .ac_price--struck to #ac_price and inserts a sibling
 * .ac_price__discounted span with the new price. See discount.js + spec
 * docs/superpowers/specs/2026-05-11-discount-lvl-design.md. */
.ac_price--struck     { text-decoration: line-through; opacity: 0.55; }
.ac_price__discounted { display: block; color: var(--color-buy);
                        font-weight: 600; white-space: nowrap; }
.ac_price__label      { font-weight: 500; margin-inline-end: 0.25em; }
.buy__cta { margin-bottom: var(--sp-7); }
.buy__what-you-get {
  list-style: none; padding: 0; margin: 0;
}
.buy__what-you-get li {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) 0;
  color: var(--color-text);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
}
.buy__what-you-get__title {
  font-weight: 600;
  color: var(--color-text);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 30%, transparent);
  margin-bottom: var(--sp-2);
}
.buy__inner > div { text-align: left; }
.buy__inner > div p,
.buy__inner > div h2 { text-align: left; padding-inline: 0; }
.buy__what-you-get .fa-check {
  color: var(--buttoncolor);
}

/* Orange purchase CTAs — overrides the white-glass .hero__cta look just for
 * the two primary buy buttons (header + .buy section). Matches the live
 * site's var(--color-buy). Text is dark (not white) — white on var(--color-buy)
 * is 2.72:1 (fails AA even for large text), dark on var(--color-buy) is ~9:1. */
/* Lower Buy in the .buy section: orange-glass tint instead of the
 * white-glass that the base .hero__cta would otherwise hand it. Keeps
 * the conversion CTA visually flagged in --color-buy while staying
 * inside the glass-button family (transparent fill + backdrop blur).
 * Dark-mode override pins the tint level so it doesn't get garish on
 * the darker page bg. */
.buy a.hero__cta[id^="buy_button"] {
  background: color-mix(in srgb, var(--color-buy) 25%, transparent);
  border-color: var(--color-buy);
  color: var(--color-text);
}
.buy a.hero__cta[id^="buy_button"]:hover,
.buy a.hero__cta[id^="buy_button"]:focus-visible {
  background: color-mix(in srgb, var(--color-buy) 45%, transparent);
  border-color: var(--color-buy);
  color: var(--color-text);
}
@media (prefers-color-scheme: dark) {
  .buy a.hero__cta[id^="buy_button"] {
    background: color-mix(in srgb, var(--color-buy) 35%, transparent);
  }
  .buy a.hero__cta[id^="buy_button"]:hover,
  .buy a.hero__cta[id^="buy_button"]:focus-visible {
    background: color-mix(in srgb, var(--color-buy) 55%, transparent);
  }
}

/* ===== Newsletter ===== */
.newsletter {
  padding-block: var(--sp-10);
  text-align: center;
}
.newsletter__inner {
  max-width: 880px;                 /* was 700 — give the consent label room to stay on one line */
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.newsletter__form {
  display: flex; flex-direction: column; gap: var(--sp-3);
  align-items: center;
}
.newsletter__row {
  display: flex; gap: 0; align-items: stretch;
  width: 100%; max-width: 560px;     /* was 480 — slightly wider so the email + Join button look balanced */
}
.newsletter__row input[type=email] {
  flex: 1;
  min-width: 0;                        /* allow shrink below intrinsic input width on narrow viewports */
  padding: 0 var(--sp-4);
  height: 44px;
  border: 1px solid transparent;
  border-radius: 8px 0 0 8px;
  background: color-mix(in srgb, var(--color-text-dm) 95%, transparent);
  color: var(--color-dark-dm);
  font-size: var(--fs-md);
}
@media (max-width: 440px) {
  /* Buy back room for the placeholder/typed content by tightening
   * input + button padding once the row's natural fit gets cramped. */
  .newsletter__row input[type=email] { padding: 0 var(--sp-3); }
  .newsletter__submit { padding: 0 var(--sp-4); }
}
.newsletter__row input[type=email]:focus-visible {
  outline: 2px solid var(--buttoncolor);
  outline-offset: -2px;
}
.newsletter__submit {
  padding: 0 var(--sp-5);
  height: 44px;
  /* White-derived glass tint matching the rest of the glass-button
   * family. Dark-mode override below pins it to the subtler 10%/28%
   * values so dark mode looks identical to the original. */
  background: color-mix(in srgb, var(--color-text-dm) 35%, transparent);
  color: var(--color-text);
  border: 1px solid color-mix(in srgb, var(--color-text-dm) 70%, transparent);
  border-left: 0;                /* sits flush with the email input */
  border-radius: 0 8px 8px 0;
  font-weight: 600;
  cursor: pointer;
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color     var(--dur-fast) var(--ease-out);
}
.newsletter__submit:hover,
.newsletter__submit:focus-visible {
  background: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  border-color: color-mix(in srgb, var(--color-text-dm) 90%, transparent);
}
@media (prefers-color-scheme: dark) {
  .newsletter__submit {
    background: color-mix(in srgb, var(--color-text-dm) 10%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 28%, transparent);
  }
  .newsletter__submit:hover,
  .newsletter__submit:focus-visible {
    background: color-mix(in srgb, var(--color-text-dm) 20%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  }
}
.newsletter__consent {
  display: inline-flex; align-items: flex-start; gap: var(--sp-2);
  /* Was: forced wrap. Now: explicit width matching the row, so the label has room and
     wraps gracefully only if the user has zoomed text up. */
  max-width: 560px;
  width: 100%;
  font-size: var(--fs-sm);
  line-height: 1.45;
  color: color-mix(in srgb, var(--color-text) 80%, transparent);
  text-align: left;
}
.newsletter__consent input[type="checkbox"] {
  flex-shrink: 0;
  margin-top: 2px;
}
.newsletter__consent span {
  flex: 1;
}
.newsletter__status {
  margin-top: var(--sp-3);
  min-height: 1.5em;
  font-size: var(--fs-sm);
  color: var(--color-text);
}

/* ===== Testimonials wall =====
 * Replaces the cross-origin Famewall iframe with our own glass-tile grid.
 * Data ships from src/global/content/testimonials.json (scraped at build
 * time by tools/scrape-testimonials.php) — no runtime third-party calls,
 * no author photos (GDPR — would have leaked visitor IPs to Famewall). */
.testimonials-section {
  padding-block: var(--sp-10);
}
.testimonials-grid {
  max-width: 1240px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.testimonials-grid__header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-7);
  color: var(--color-text);
  font-size: var(--fs-md);
  flex-wrap: wrap;
}
.testimonials-grid__avg {
  font-size: var(--fs-2xl);
  font-weight: 700;
}
.testimonials-grid__avg-stars {
  color: var(--buttoncolor);
  font-size: var(--fs-lg);
}
.testimonials-grid__avg-label {
  color: var(--ink-mute);
}

/* CSS-columns layout = masonry-style stacking that handles variable quote
 * lengths gracefully. break-inside: avoid keeps each tile intact across
 * column breaks. */
.testimonials-grid__list {
  column-count: 3;
  column-gap: var(--sp-5);
}
@media (max-width: 900px) { .testimonials-grid__list { column-count: 2; } }
@media (max-width: 600px) { .testimonials-grid__list { column-count: 1; } }

/* Tile bg / border match the vendor tiles inside the library browser
 * (.vendor): currentColor 6% in light mode, black 50% in dark mode. */
.testimonial {
  display: block;
  break-inside: avoid;
  margin-bottom: var(--sp-5);
  padding: var(--sp-5);
  background: color-mix(in srgb, currentColor 6%, transparent);
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  border-radius: 12px;
  color: var(--color-text);
}
@media (prefers-color-scheme: dark) {
  .testimonial {
    background: color-mix(in srgb, var(--color-dark-dm) 50%, transparent);
    border-color: color-mix(in srgb, var(--color-text) 22%, transparent);
  }
}

.testimonial__byline {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
  border: 0;
  padding: 0;
  margin: 0 0 var(--sp-3);
}
.testimonial__name {
  font-style: normal;
  font-weight: 600;
  font-size: var(--fs-md);
  color: var(--color-text);
}
.testimonial__role {
  font-size: var(--fs-sm);
  color: var(--ink-mute);
}

/* Quote — matches body paragraphs upstairs (weight 350, line-height 1.5,
 * 1rem) for a unified editorial voice across the page. */
.testimonial__quote {
  margin: 0 0 var(--sp-4);
  font-size: 1rem;
  line-height: 1.5;
  font-weight: 350;
  text-align: left;
  color: var(--color-text);
  font-style: normal;
}
.testimonial__quote::before { content: "\201C"; margin-right: 2px; opacity: 0.5; }
.testimonial__quote::after  { content: "\201D"; margin-left: 2px; opacity: 0.5; }

/* "(Translated)" badge below the quote — small, muted, no quote marks.
 * text-align overrides the global `p { text-align: center }` from base.css. */
.testimonial__translated {
  margin: 0 0 var(--sp-3);
  padding: 0;
  font-size: var(--fs-sm);
  font-style: italic;
  opacity: 0.55;
  text-align: left !important;
}

/* Stars at the bottom — brand blue (var(--buttoncolor)), not gold. */
.testimonial__stars {
  display: inline-flex;
  gap: 2px;
  font-size: var(--fs-sm);
  color: var(--buttoncolor);
}
.testimonial__stars .fa-regular {
  color: color-mix(in srgb, var(--buttoncolor) 45%, var(--color-bright));
}

.testimonials-grid__more {
  display: block;
  margin: var(--sp-6) auto 0;
  padding: var(--sp-3) var(--sp-6);
  /* White-derived glass tint (vs. text-derived = dark wash in light mode).
   * Backdrop-filter does the actual "frosted" work; this is just the
   * surface shade. Dark-mode override below preserves the existing look. */
  background: color-mix(in srgb, var(--color-text-dm) 35%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text-dm) 70%, transparent);
  color: var(--color-text);
  border-radius: 8px;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  transition:
    background-color 320ms cubic-bezier(0.16, 1, 0.3, 1),
    border-color     320ms cubic-bezier(0.16, 1, 0.3, 1);
}
.testimonials-grid__more:hover,
.testimonials-grid__more:focus-visible {
  background: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  border-color: color-mix(in srgb, var(--color-text-dm) 90%, transparent);
}
@media (prefers-color-scheme: dark) {
  .testimonials-grid__more {
    background: color-mix(in srgb, var(--color-text-dm) 10%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 28%, transparent);
  }
  .testimonials-grid__more:hover,
  .testimonials-grid__more:focus-visible {
    background: color-mix(in srgb, var(--color-text-dm) 20%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  }
}

/* Mobile-only: collapse the review list + show-more button behind a
 * glass toggle. The header (avg + stars + "average from N reviews")
 * stays visible always. data-state="open" on .testimonials-grid is
 * what reveals the list. Desktop ignores all of this. */
.testimonials-grid__toggle { display: none; }
@media (max-width: 760px) {
  .testimonials-grid__toggle {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    margin-top: var(--sp-5);
    padding: var(--sp-3) var(--sp-5);
    /* White-derived glass tint matching .testimonials-grid__more. */
    background: color-mix(in srgb, var(--color-text-dm) 35%, transparent);
    border: 1px solid color-mix(in srgb, var(--color-text-dm) 70%, transparent);
    border-radius: 10px;
    color: var(--color-text);
    font: inherit;
    font-weight: 500;
    cursor: pointer;
    backdrop-filter: blur(10px) saturate(140%);
    -webkit-backdrop-filter: blur(10px) saturate(140%);
    transition:
      background-color var(--dur-fast) var(--ease-out),
      border-color     var(--dur-fast) var(--ease-out);
  }
  .testimonials-grid__toggle:hover,
  .testimonials-grid__toggle:focus-visible {
    background: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 90%, transparent);
    outline: none;
  }
  .testimonials-grid:not([data-state="open"]) .testimonials-grid__list,
  .testimonials-grid:not([data-state="open"]) .testimonials-grid__more {
    display: none;
  }
  .testimonials-grid[data-state="open"] .testimonials-grid__list {
    margin-top: var(--sp-4);
  }
}
@media (max-width: 760px) and (prefers-color-scheme: dark) {
  /* Preserve the existing subtler glass for dark mode so the new
   * lighter values only kick in on light. */
  .testimonials-grid__toggle {
    background: color-mix(in srgb, var(--color-text-dm) 10%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 28%, transparent);
  }
  .testimonials-grid__toggle:hover,
  .testimonials-grid__toggle:focus-visible {
    background: color-mix(in srgb, var(--color-text-dm) 20%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  }
}
.testimonials-grid__toggle-caret {
  font-size: 0.75em;
  transition: transform var(--dur-fast) var(--ease-out);
}
.testimonials-grid__toggle[aria-expanded="true"] .testimonials-grid__toggle-caret {
  transform: rotate(180deg);
}

.testimonials-empty {
  text-align: center;
  color: var(--ink-mute);
  font-style: italic;
  padding: var(--sp-7);
}

/* ===== FreeScout speech-bubble launcher (bottom-right floating icon) =====
 * Glass bubble matching .hero__cta / .links-row__link surface, with a darker
 * variant of the Buy button orange (var(--color-buy)) used for stroke AND speech icon
 * — so the launcher reads as "buy-adjacent but secondary."
 *
 * Sized via explicit width/height (NOT `transform: scale()`) so the
 * button + its backdrop-blur surface re-rasterize cleanly at the
 * larger size. Earlier versions used transform-scale and produced
 * jagged edges + blurry icon: the GPU composites the backdrop-blurred
 * texture at the natural ~50px size and stretches it on the page.
 * Explicit dimensions force layout-time rasterization at full quality.
 *
 * The dialog that opens on click is themed by FreeScout's own config
 * (#112C3B in view.html) — we deliberately don't touch that. */
#fsw-btn {
  width: 88px !important;
  height: 88px !important;
  /* Centre the inner <img> — FreeScout's widget doesn't constrain it
   * to centre by default, and our explicit-size override exposes that. */
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  background: color-mix(in srgb, var(--color-buy) 25%, transparent) !important;
  border: 1.5px solid var(--color-buy) !important;
  backdrop-filter: blur(10px) saturate(140%) !important;
  -webkit-backdrop-filter: blur(10px) saturate(140%) !important;
  box-shadow: none !important;
  transition:
    background-color 320ms cubic-bezier(0.16, 1, 0.3, 1),
    border-color     320ms cubic-bezier(0.16, 1, 0.3, 1) !important;
}
/* FreeScout's widget injects the speech bubble as an <img> with a
 * data-URI SVG — NOT an inline <svg> element. So the selector targets
 * #fsw-btn img. The svg sub-selectors (kept below for resilience if
 * FreeScout ever switches to inline <svg>) are no-ops today. */
#fsw-btn img {
  width: 45px !important;
  height: 45px !important;
  opacity: 0.55 !important;
  /* Tint the white speech-bubble toward --color-buy (#F57C00).
   * Filter chain steps:
   *   brightness(0)  → reduce white pixel to black
   *   invert(57%)    → push from black toward mid-light
   *   sepia(99%)     → introduce yellow/orange hue
   *   saturate(2000%) → punch saturation hard
   *   hue-rotate(0)  → keep at orange band
   *   brightness(99%)/contrast(102%) → small tone adjustments
   * Empirical approximation, not exact #F57C00. Tune invert / sepia /
   * saturate values if it looks off. */
  filter: brightness(0) saturate(100%) invert(57%) sepia(99%) saturate(2000%) hue-rotate(0deg) brightness(99%) contrast(102%) !important;
}
#fsw-btn svg,
#fsw-btn svg * {
  color: var(--color-buy) !important;
  fill: var(--color-buy) !important;
  stroke: var(--color-buy) !important;
}
#fsw-btn svg {
  width: 45px !important;
  height: 45px !important;
  opacity: 0.7 !important;
}
#fsw-btn:hover {
  background: color-mix(in srgb, var(--color-buy) 40%, transparent) !important;
  border-color: var(--color-buy) !important;
}
#fsw-btn:hover svg,
#fsw-btn:hover svg * {
  color: var(--color-buy) !important;
  fill: var(--color-buy) !important;
  stroke: var(--color-buy) !important;
}
@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) #fsw-btn {
    transition: none !important;
  }
}

/* Hide the launcher bubble on mobile (matches the burger-menu breakpoint
 * at 820px). Mobile users get the "Contact Us" button in the burger
 * drawer instead — same data-action="open-support" hook, no floating
 * overlay competing with content. The bubble itself stays in the DOM so
 * the .click() trigger from footer.js still works. */
@media (max-width: 820px) {
  #fsw-btn {
    display: none !important;
  }
}

/* Homepage: hide the bubble in both viewports. The widget script is
 * loaded so the mobile-menu Contact button can fire #fsw-btn.click(),
 * but the visible bubble is suppressed so the page still "ends with
 * the hero" per the homepage design intent. */
body[data-page="homepage"] #fsw-btn {
  display: none !important;
}

/* FreeScout dialog panel (the iframe that opens on click). Contents are
 * cross-origin (served from tickets.babylonwaves.com) so we can only
 * skin the envelope: size, corners, shadow, border. The interior fonts
 * / form colours are governed by FreeScout's own config — only the
 * accent colour bleeds through (s.color in the loader script).
 *
 * Widget.js sets these via inline cssText, so !important is required.
 * Scoped to desktop because the widget's own <760px branch goes
 * fullscreen with squared corners — keep that. */
@media (min-width: 760px) {
  #fsw-iframe {
    /* Floating panel anchored to the bottom-right corner (matches the
     * bubble's `position: "br"`). 1/3 viewport wide, 50vh tall. All
     * corners rounded since the panel no longer reaches the viewport
     * edges. Widget.js sets left/right/bottom inline so left:auto is
     * needed to override its positioning. */
    width: 33.333vw !important;
    height: 50vh !important;
    max-height: 50vh !important;
    left: auto !important;
    right: 16px !important;
    bottom: 16px !important;
    border-radius: 12px !important;
    border: 1px solid color-mix(in srgb, var(--color-text) 12%, transparent) !important;
    box-shadow:
      0 24px 60px color-mix(in srgb, var(--color-dark-dm) 35%, transparent),
      0 4px 16px color-mix(in srgb, var(--color-dark-dm) 25%, transparent) !important;
  }
}

/* Blur the page behind the FreeScout dialog while it's open. widget.js
 * sets `display: block` inline on #fsw-iframe when the dialog opens
 * and `display: none` when it closes, so the :has() selector flips
 * the blur on/off automatically — no JS shim needed. The iframe
 * itself is excluded from the blur via :not() so the dialog stays
 * crisp. No transition: a CSS-shorthand `transition: filter` here
 * would clobber the header's drawer transitions, and the dialog
 * open/close is a discrete click anyway. */
body:has(#fsw-iframe[style*="display: block"]) > *:not(#fsw-iframe) {
  filter: blur(2px);
}

/* ===== e-junkie cart overlay — backdrop skin only =====
 * e-junkie injects its own <style> at runtime for .EJLoaderV3 (the dimming
 * backdrop). We override with !important to lighten the dim and add backdrop
 * blur so the page behind reads as a soft frosted background. The iframe
 * geometry (fullscreen, square corners) stays as e-junkie ships it. */
.EJLoaderV3.EJOverlayV3 {
  background: color-mix(in srgb, var(--color-dark-dm) 10%, transparent) !important;
  backdrop-filter: blur(8px) saturate(130%) !important;
  -webkit-backdrop-filter: blur(8px) saturate(130%) !important;
}

/* Section slide-in animations removed at Marc's request — sections render
 * fully visible from page load. Keeping the [data-reveal] attribute on the
 * markup is harmless (just a hook with no styling). */
[data-reveal],
[data-reveal][data-revealed="true"] {
  opacity: 1;
  transform: none;
}

/* ===== Document pages (return-policy, privacy, about/Impressum, agb) =====
 * Prose-heavy pages: single column with a readable measure, left-aligned
 * text, generous line-height. Sits on the page's mode-aware --color-bright
 * background (set by base.css) so it inherits the same theming as the rest
 * of the site. No page gradient. */
.doc-page {
  max-width: 760px;
  margin-inline: auto;
  padding: var(--sp-9) var(--gutter) var(--sp-9);
  color: var(--color-text);
}
.doc-page h1 {
  font-size: var(--fs-2xl);
  font-weight: 700;
  margin: 0 0 var(--sp-7);
  text-align: left;
  letter-spacing: -0.01em;
}
.doc-page h2 {
  font-size: var(--fs-xl);
  font-weight: 600;
  margin: var(--sp-8) 0 var(--sp-4);
  text-align: left;
  letter-spacing: -0.005em;
}
.doc-page h3 {
  /* Override the global h3 (weight 300, --fs-lg) to give doc-page sub-headings
   * a touch more weight without going to h2 size. */
  font-size: var(--fs-lg);
  font-weight: 500;
  margin: var(--sp-6) 0 var(--sp-3);
  text-align: left;
}
.doc-page p {
  text-align: left;
  padding-inline: 0;
  font-size: 1rem;
  font-weight: 350;
  line-height: 1.65;
  margin-bottom: var(--sp-4);
}
.doc-page a {
  color: color-mix(in srgb, var(--buttoncolor) 70%, var(--color-text));
}
.doc-page strong { font-weight: 600; }
.doc-page address {
  font-style: normal;
  margin-bottom: var(--sp-4);
  line-height: 1.65;
}
.doc-page ul, .doc-page ol {
  padding-left: var(--sp-5);
  margin-bottom: var(--sp-4);
  line-height: 1.65;
}
.doc-page li { margin-bottom: var(--sp-1); }
/* Section grouping for bilingual content (privacy page). */
.doc-page__lang {
  margin-block: var(--sp-9);
  padding-top: var(--sp-7);
  border-top: 1px solid color-mix(in srgb, var(--color-text) 15%, transparent);
}
.doc-page__lang:first-of-type {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
/* Smaller print used in some Disclaimer paragraphs */
.doc-page__small {
  font-size: var(--fs-sm);
  color: color-mix(in srgb, var(--color-text) 80%, var(--color-bright));
}

/* ===== Embed-frame =====
 * Generic full-width iframe presentation used by /dl/ (download bento
 * from dl2.babylonwaves.com), /support/ (GitBook docs), /thanks/ (e-junkie
 * post-purchase rp.php), /review/ (Famewall reviews). All four pages are
 * thin iframe wrappers that forward URL params (where applicable) to the
 * upstream service while keeping our site chrome around the embed. */
.embed-frame {
  position: relative;
  margin-bottom: var(--sp-7);
}

/* Loading state: centred spinner over the iframe's translucent
 * background while the embedded page boots. Removed by adding
 * .is-loaded to .embed-frame on the iframe's load event (inline
 * onload handler — no external JS). pointer-events:none so the
 * iframe stays interactive even before fade-out completes. */
.embed-frame__loader {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 1;
  transition: opacity 320ms ease-out;
}
.embed-frame.is-loaded .embed-frame__loader {
  opacity: 0;
}
.embed-frame__spinner {
  width: 48px;
  height: 48px;
  border: 4px solid color-mix(in srgb, var(--color-text) 15%, transparent);
  border-top-color: var(--color-text);
  border-radius: 50%;
  animation: embed-frame-spin 1s linear infinite;
}
@keyframes embed-frame-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  /* Static ring (still visible as a "loading" affordance) for users
   * with the OS reduce-motion setting on. */
  html:not(.force-motion) .embed-frame__spinner { animation: none; }
}

.embed-frame h2 {
  font-size: var(--fs-xl);
  font-weight: 600;
  margin: 0 0 var(--sp-4);
  text-align: left;
}
.embed-frame__iframe {
  display: block;
  width: 100%;
  height: 800px;
  border: 0;
  border-radius: 12px;
  background: color-mix(in srgb, var(--color-text) 4%, transparent);
}
.embed-frame__iframe--tall {
  height: 1000px;
}

/* ===== Download bento (dl/index.php success state) =====
 * Three-column bento: primary product zip is a hero tile spanning all
 * 3 cols, then 3 auxiliary tiles (snapshots / presets / manuals) below.
 * Hero tile picks up --color-buy (the commerce signal) for its primary
 * CTA. Glass surface matches the .edu-card pattern.
 *
 * Width override: .doc-page caps at 760px for prose readability, but
 * the dl page is bento-heavy and benefits from the full --max-content
 * width (matches hero / editorial sections / library widget on the
 * marketing pages). */
/* .doc-page caps at 760px for prose readability. The pages below are
 * layout-heavy (bento, full-bleed iframes, edu hero card) and benefit
 * from the full --max-content width instead. */
.doc-page.dl-page,
.doc-page.edu-page,
.doc-page.support-page,
.doc-page.thanks-page { max-width: var(--max-content); }

.doc-page.thanks-page .embed-frame__iframe--tall { height: 1250px; }

/* Support page: the iframe IS the page. Override doc-page's max-width
 * + vertical padding so it goes full-bleed, fill the viewport height
 * (100svh — small-viewport-height, stable on mobile when address bars
 * show/hide), and drop the bottom margin so the footer sits flush
 * against the iframe with no empty space between them. */
.doc-page.support-page {
  max-width: none;
  padding: 0;
}
.doc-page.support-page .embed-frame {
  margin: 0;
}
.doc-page.support-page .embed-frame__iframe,
.doc-page.support-page .embed-frame__iframe--tall {
  height: 100svh;
  border-radius: 0;
}
.dl-page__head { margin-bottom: var(--sp-7); }
.dl-page__head h1 {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-2xl);
  font-weight: 600;
  letter-spacing: -0.01em;
  text-align: left;
}
.dl-page__email {
  margin: 0;
  padding-inline: 0;
  font-size: var(--fs-sm);
  color: color-mix(in srgb, var(--color-text) 60%, var(--color-bright));
  text-align: left;
}
.dl-bento {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-4);
}
@media (max-width: 720px) {
  .dl-bento { grid-template-columns: 1fr; }
}
.dl-tile {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-2) var(--sp-5) var(--sp-5);
  background: color-mix(in srgb, var(--buttoncolor) 30%, transparent);
  border: 1px solid var(--buttoncolor);
  border-radius: 14px;
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: var(--color-text);
  text-decoration: none;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color     var(--dur-fast) var(--ease-out),
    transform        var(--dur-fast) var(--ease-out);
}
.dl-tile:hover,
.dl-tile:focus-visible {
  background: color-mix(in srgb, var(--buttoncolor) 42%, transparent);
  border-color: color-mix(in srgb, var(--buttoncolor) 75%, var(--color-text-dm));
  transform: translateY(-1px);
}
@media (prefers-color-scheme: dark) {
  .dl-tile {
    background: color-mix(in srgb, var(--buttoncolor) 10%, transparent);
  }
  .dl-tile:hover,
  .dl-tile:focus-visible {
    background: color-mix(in srgb, var(--buttoncolor) 18%, transparent);
  }
}
.dl-tile--hero {
  grid-column: 1 / -1;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  padding: var(--sp-5) var(--sp-6);
  min-height: 0;
  background: color-mix(in srgb, var(--color-buy) 20%, transparent);
  border-color: var(--color-buy);
}
.dl-tile--hero:hover,
.dl-tile--hero:focus-visible {
  background: color-mix(in srgb, var(--color-buy) 30%, transparent);
  border-color: color-mix(in srgb, var(--color-buy) 75%, var(--color-text-dm));
}
.dl-tile__body { flex: 1; }
.dl-tile__eyebrow {
  margin: 0 0 var(--sp-2);
  padding-inline: 0;
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: left;
  color: color-mix(in srgb, var(--color-buy) 30%, var(--color-text));
}
h2.dl-tile__title {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.005em;
  text-align: left;
  color: var(--color-text);
}
.dl-tile--hero h2.dl-tile__title {
  font-size: var(--fs-xl);
  margin: 0 0 var(--sp-3);
}
/* Auxiliary download buttons — color the leading FA icon with --color-buy.
 * Element selectors so the rule works whether the FA Kit runs in webfont
 * mode (renders <i>) or SVG mode (replaces with <svg>); the base `.fa`
 * class can be stripped in SVG mode, so we don't target it. */
.dl-tile:not(.dl-tile--hero) .dl-tile__btn i,
.dl-tile:not(.dl-tile--hero) .dl-tile__btn svg {
  color: var(--color-buy);
}
.dl-tile__desc {
  margin: 0;
  padding-inline: 0;
  font-size: var(--fs-sm);
  font-weight: 350;
  line-height: 1.5;
  text-align: left;
  color: color-mix(in srgb, var(--color-text) 75%, var(--color-bright));
}
.dl-tile__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  align-self: flex-start;
  padding: var(--sp-2) var(--sp-4);
  border-radius: 999px;
  font-size: var(--fs-sm);
  font-weight: 500;
  white-space: nowrap;
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
  color: var(--color-text);
  border: 1px solid color-mix(in srgb, var(--color-text) 22%, transparent);
}
.dl-tile:hover .dl-tile__btn,
.dl-tile:focus-visible .dl-tile__btn {
  background: color-mix(in srgb, var(--color-text) 16%, transparent);
}
.dl-tile__btn--primary {
  padding: var(--sp-3) var(--sp-5);
  font-size: var(--fs-md);
  background: var(--color-buy);
  border-color: var(--color-buy);
  color: var(--color-dark-dm);
}
.dl-tile--hero:hover .dl-tile__btn--primary,
.dl-tile--hero:focus-visible .dl-tile__btn--primary {
  background: color-mix(in srgb, var(--color-buy) 80%, var(--color-text-dm));
  border-color: var(--color-dark-dm);
}
.dl-page__notice {
  margin-top: var(--sp-5);
  padding-inline: var(--sp-3);
  text-align: center;
}
.dl-page__notice p {
  margin: 0;
  padding-inline: 0;
  font-size: 12px;
  font-weight: 300;
  line-height: 1.5;
  text-align: center;
  color: color-mix(in srgb, var(--color-text) 75%, var(--color-bright));
}
.dl-page__notice p.dl-page__notice--tag {
  margin-top: var(--sp-2);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 65%, var(--color-bright));
}
.dl-divider {
  margin: var(--sp-6) 0;
  text-align: center;
  font-size: var(--fs-md);
  letter-spacing: 0.4em;
  color: color-mix(in srgb, var(--color-text) 35%, var(--color-bright));
  user-select: none;
}

/* Changelog scrollable card — used on the /dl/ page below the download
 * bento. Two-column layout: a clickable version-nav sidebar on the left
 * and the scrollable entries card on the right. Both share max-height
 * so the full release history is accessible without making the page tall.
 * Server-rendered from src/global/content/changelog.json (scraped at
 * build time by tools/scrape-changelog.php). */
/* Gap between the secondary download row and the changelog bento.
 * Set to 2× the dl-bento row gap (--sp-4 × 2 = --sp-6) so the rhythm
 * between the two bentos visually relates to the inter-tile rhythm. */
.changelog { margin-top: var(--sp-6); }
.changelog__layout {
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: var(--sp-5);
  align-items: start;
}
/* Title sits as the first item inside .changelog__scroll, above the
 * version entries. Plain heading — not sticky, not in the nav. */
h2.changelog__title {
  position: sticky;
  top: 0;
  z-index: 2;
  margin: 0 calc(var(--sp-5) * -1) var(--sp-3);
  padding: var(--sp-3) var(--sp-5);
  background: color-mix(in srgb, var(--color-text-dm) 35%, var(--color-bright));
  backdrop-filter: blur(40px) saturate(180%);
  -webkit-backdrop-filter: blur(40px) saturate(180%);
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 12%, transparent);
  font-size: var(--fs-xl);
  font-weight: 600;
  line-height: 1.1;
  text-align: left;
  color: var(--color-text);
}
@media (prefers-color-scheme: dark) {
  h2.changelog__title {
    background: color-mix(in srgb, var(--color-text-dm) 14%, var(--color-dark-dm));
  }
}
@media (max-width: 720px) {
  .changelog__layout { grid-template-columns: 1fr; }
  /* On mobile collapse the sidebar — the scroll card alone provides
   * navigation via natural scrolling. */
  .changelog__nav { display: none; }
}
.changelog__nav {
  max-height: 55vh;
  overflow-y: auto;
  padding: var(--sp-3);
  background: color-mix(in srgb, var(--color-text) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);
  border-radius: 12px;
}
.changelog__nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.changelog__nav li {
  margin-bottom: 2px;
}
.changelog__nav a {
  display: block;
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-sm);
  font-weight: 500;
  /* Muted-orange shade — readable but recessive. Hover lifts the
   * orange toward full --color-buy; .is-active is bright orange. */
  color: color-mix(in srgb, var(--color-buy) 35%, var(--color-text));
  text-decoration: none;
  border-radius: 6px;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    color            var(--dur-fast) var(--ease-out);
}
.changelog__nav a:hover,
.changelog__nav a:focus-visible {
  background: color-mix(in srgb, var(--color-buy) 12%, transparent);
}
.changelog__nav a.is-active {
  background: var(--color-buy);
  color: var(--color-dark-dm);
}
.changelog__scroll {
  max-height: 55vh;
  overflow-y: auto;
  padding: 0 var(--sp-5) var(--sp-5);
  background: color-mix(in srgb, var(--color-text) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);
  border-radius: 12px;
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
}
.changelog__scroll:focus-visible {
  outline: 2px solid var(--buttoncolor);
  outline-offset: 2px;
}
.changelog__entry {
  margin-bottom: var(--sp-5);
  padding-bottom: var(--sp-5);
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 12%, transparent);
}
.changelog__entry:last-child {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: 0;
}
.changelog__version {
  font-size: var(--fs-md);
  font-weight: 600;
  margin: 0 0 var(--sp-3);
  letter-spacing: 0.02em;
  text-align: left;
  color: color-mix(in srgb, var(--color-buy) 50%, var(--color-text));
}
.changelog__body p {
  margin: 0 0 var(--sp-2);
  padding-inline: 0;
  text-align: left;
  font-size: var(--fs-sm);
  font-weight: 350;
  line-height: 1.55;
}
.changelog__body p:last-child { margin-bottom: 0; }
.changelog__body p strong {
  font-weight: 600;
  color: color-mix(in srgb, var(--color-text) 90%, var(--color-bright));
  letter-spacing: 0.04em;
}

/* ===== EDU page =====
 * Bento-style two-card layout for the discount tiers (Individual /
 * Multi-Seat), plus a graduation-cap icon next to the H1 and a CTA
 * button at the bottom. Sits inside .doc-page so prose typography
 * still applies. */
.edu-page__title {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.edu-page__icon {
  font-size: 0.85em;
  color: var(--color-buy);
}
.edu-bento {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-5);
  margin-block: var(--sp-7);
}
@media (max-width: 600px) {
  .edu-bento { grid-template-columns: 1fr; }
}
.edu-card {
  padding: var(--sp-6) var(--sp-5);
  background: color-mix(in srgb, var(--color-text) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);
  border-radius: 12px;
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
}
.edu-card h2 {
  font-size: var(--fs-lg);
  font-weight: 600;
  margin: 0 0 var(--sp-3);
  text-align: left;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-text);
}
.edu-card p {
  margin: 0;
  font-weight: 350;
  line-height: 1.6;
  color: var(--color-text);
  padding-inline: 0;
  text-align: left;
}
.edu-card strong {
  color: color-mix(in srgb, var(--buttoncolor) 70%, var(--color-text));
}
.edu-page__cta {
  margin-top: var(--sp-7);
  text-align: left !important;
}
/* Glass treatment matching the .hero__cta family — translucent
 * text-tinted fill with a tonal border + blur. AA contrast ≥ 9:1 for
 * the body text (--color-text on the glass surface) in both modes. */
.edu-page__cta-button {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  background: color-mix(in srgb, var(--color-text) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 28%, transparent);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: var(--color-text);
  border-radius: 8px;
  font-weight: 600;
  text-decoration: none;
  transition:
    background-color var(--dur-med) var(--ease-out),
    border-color     var(--dur-med) var(--ease-out),
    color            var(--dur-med) var(--ease-out);
}
.edu-page__cta-button:hover,
.edu-page__cta-button:focus-visible {
  background: color-mix(in srgb, var(--color-text) 20%, transparent);
  border-color: color-mix(in srgb, var(--color-text) 55%, transparent);
  color: var(--color-text);
}

/* ===== Landing pages (hidden/ — newsletter subscribe/unsubscribe/confirm,
 *       e-junkie post-purchase thanks) =====
 * Email-redirect / opt-in confirmation pages. Centered single message,
 * big headline (emoji-bearing per live site convention), small supporting
 * line, CTA back to root. Uses the same mode-aware --color-bright body
 * background as the doc pages. */
.landing {
  min-height: 60vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-9) var(--gutter);
  text-align: center;
}
.landing__inner {
  max-width: 640px;
  margin-inline: auto;
}
.landing__title {
  font-size: clamp(1.5rem, 1rem + 2vw, 2.25rem);
  font-weight: 600;
  line-height: 1.25;
  margin: 0 0 var(--sp-5);
  color: var(--color-text);
}
.landing__msg {
  font-size: var(--fs-md);
  font-weight: 350;
  line-height: 1.6;
  margin: 0 0 var(--sp-7);
  padding-inline: 0;
  color: color-mix(in srgb, var(--color-text) 85%, var(--color-bright));
}
.landing__cta a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  background: color-mix(in srgb, var(--color-text) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 28%, transparent);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: var(--color-text);
  border-radius: 8px;
  font-weight: 500;
  font-size: var(--fs-md);
  text-decoration: none;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color     var(--dur-fast) var(--ease-out);
}
.landing__cta a:hover,
.landing__cta a:focus-visible {
  background: color-mix(in srgb, var(--color-text) 20%, transparent);
  border-color: color-mix(in srgb, var(--color-text) 55%, transparent);
}

/* ===== Consent banner =====
 * Bottom-fixed glass strip. Mode-aware surface (uses --color-bright /
 * --color-dark anchors that auto-flip with prefers-color-scheme). Hidden
 * by `hidden` attribute until consent.js shows it (first visit OR when
 * Cookie Settings is clicked). */
.consent {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 200;
  background: color-mix(in srgb, var(--color-bright) 92%, var(--color-text));
  border-top: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: var(--color-text);
  box-shadow: 0 -8px 32px color-mix(in srgb, var(--color-dark-dm) 12%, transparent);
}
.consent[hidden] { display: none; }
.consent__inner {
  max-width: var(--max-content);
  margin-inline: auto;
  padding: var(--sp-4) var(--gutter);
  display: flex;
  gap: var(--sp-5);
  align-items: center;
  flex-wrap: wrap;
}
.consent__body { flex: 1; min-width: 280px; }
.consent__title {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-md);
  font-weight: 600;
  text-align: left;
  color: var(--color-text);
}
.consent__msg {
  margin: 0;
  padding-inline: 0;
  text-align: left;
  font-size: var(--fs-sm);
  font-weight: 300;
  line-height: 1.5;
  color: color-mix(in srgb, var(--color-text) 85%, var(--color-bright));
}
.consent__msg a {
  /* 60/40 buttoncolor/text mix lands at ~5–7:1 against the banner bg in
   * both modes; raw 80/20 was failing AA in dark + borderline in light. */
  color: color-mix(in srgb, var(--buttoncolor) 60%, var(--color-text));
}
.consent__actions {
  display: flex;
  gap: var(--sp-3);
  flex-shrink: 0;
}
.consent__btn {
  padding: var(--sp-3) var(--sp-5);
  border-radius: 8px;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color     var(--dur-fast) var(--ease-out),
    color            var(--dur-fast) var(--ease-out);
}
.consent__btn--primary {
  background: var(--buttoncolor);
  border: 1px solid var(--buttoncolor);
  color: var(--color-text-dm);
}
.consent__btn--primary:hover,
.consent__btn--primary:focus-visible {
  background: var(--hover);
  border-color: var(--hover);
}
.consent__btn--secondary {
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--color-text) 35%, transparent);
  color: var(--color-text);
}
.consent__btn--secondary:hover,
.consent__btn--secondary:focus-visible {
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
  border-color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
/* Dark-mode override: in dark mode the base bg recipe (--color-bright 92%
 * + --color-text) produces a mid-blue ~#5b7e93 which is a "lift" surface
 * on a dark page — text contrast falls under AA. Swap to a recess recipe
 * anchored to --color-dark-dm so the banner reads as a near-dark glass
 * strip in dark mode and text contrasts pass cleanly. */
@media (prefers-color-scheme: dark) {
  .consent {
    background: color-mix(in srgb, var(--color-dark-dm) 92%, var(--color-text-dm));
    border-top: 1px solid color-mix(in srgb, var(--color-text-dm) 18%, transparent);
  }
}

/* While the consent banner is visible, hide the FreeScout launcher so it
 * can't overlap the banner buttons in the bottom-right corner. After
 * accept/reject the banner re-gets the `hidden` attribute and the
 * launcher re-appears. */
body:has(.consent:not([hidden])) #fsw-btn {
  display: none !important;
}

/* On mobile, hide the FreeScout launcher whenever any library widget
 * is open. The widget fills nearly the whole visible viewport and the
 * launcher would float over its content. Closing the widget unsets
 * data-open and the launcher reappears. */
@media (max-width: 760px) {
  body:has(.lib-widget[data-open="true"]) #fsw-btn {
    display: none !important;
  }
}

/* ===== Site footer =====
 * Mode-aware band: near-white (--color-text-dm always-bright anchor) in
 * LIGHT mode, dark (--color-dark-dm always-dark anchor) in DARK mode. The
 * @media override flips just the background; --color-text (mode-aware)
 * handles content color cleanly in both modes. Three columns on desktop,
 * stacked under ~720px. */
.site-footer {
  background: var(--color-text-dm);
  color: var(--color-text);
  /* Padding-top + finep margin-top together set where the columns sit AND
   * the gap to the fine print. Increased here (and finep margin-top reduced
   * by the same amount) so the columns block visually shrinks while the
   * fine-print line and the bottom strip stay at their current page Y. */
  padding-top: calc(var(--sp-9) + var(--sp-5));
}
@media (prefers-color-scheme: dark) {
  .site-footer { background: var(--color-dark-dm); }
}
.site-footer__inner {
  width: 100%;
  max-width: var(--max-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.site-footer__cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-7);
  align-items: start;
}
@media (max-width: 720px) {
  .site-footer__cols { grid-template-columns: 1fr; gap: var(--sp-7); }
}
.site-footer__col p {
  text-align: left;
  padding-inline: 0;
  margin-bottom: var(--sp-5);
  font-size: var(--fs-sm);
  font-weight: 300;
  line-height: 1.5;
  color: color-mix(in srgb, var(--color-text) 80%, var(--color-bright));
}
.site-footer__h {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-md);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: left;
  color: var(--color-text);
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 22%, transparent);
  padding-bottom: var(--sp-2);
}
/* Use element selectors (i, svg) instead of `.fa` because FontAwesome's Kit
 * can run in SVG mode — it replaces <i class="fa ..."> with <svg ...> and
 * strips the base `.fa` class from the rendered DOM. Element selectors
 * stay stable across both webfont and SVG modes.
 * Color uses a 70/30 darken recipe so the icons pass the WCAG UI 3:1
 * threshold against the near-white footer bg in light mode (raw --buttoncolor
 * was 2.7:1). The recipe also reads cleanly in dark mode (~6.5:1). */
.site-footer__h i,
.site-footer__h svg {
  margin-right: var(--sp-1);
  color: color-mix(in srgb, var(--buttoncolor) 70%, var(--color-text));
}

/* Payment-method icons row — branded blue, sized larger so they read as
 * recognizable card brand badges rather than tiny line glyphs. */
.site-footer__cards {
  display: flex;
  gap: var(--sp-4);
  margin-block: var(--sp-3);
}
.site-footer__cards i,
.site-footer__cards svg {
  font-size: 2.2rem;
  /* Light mode: muted-dark mid-tone, visible on the near-white footer bg.
   * Dark mode override below: full off-white (always-bright anchor) on the
   * dark footer bg. */
  color: color-mix(in srgb, var(--color-text) 70%, var(--color-bright));
}
@media (prefers-color-scheme: dark) {
  .site-footer__cards i,
  .site-footer__cards svg {
    color: var(--color-text-dm);
  }
}

/* Vertical link list — chevron-style (matches live "fa-angle-right" pattern) */
.site-footer__links {
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-footer__links li {
  margin-bottom: var(--sp-2);
}
.site-footer__links a {
  color: var(--color-text);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  transition: color var(--dur-fast) var(--ease-out);
}
.site-footer__links a:hover,
.site-footer__links a:focus-visible {
  color: color-mix(in srgb, var(--buttoncolor) 70%, var(--color-text));
}
.site-footer__links i,
.site-footer__links svg {
  color: color-mix(in srgb, var(--buttoncolor) 70%, var(--color-text));
  width: 1.1em;
}
/* Contact Us icon picks up the buy/support orange so it stands apart from
 * the other (blue) link icons — visual cue that it opens the chat. Same
 * 70/30 darken recipe applied so it reads on the near-white footer bg
 * in light mode (raw --color-buy was 2.4:1, fails UI 3:1). */
.site-footer__links a[data-action="open-support"] i,
.site-footer__links a[data-action="open-support"] svg {
  color: color-mix(in srgb, var(--color-buy) 70%, var(--color-text));
}

/* Tag cloud — small pills linking to per-DAW pages */
.site-footer__tags {
  list-style: none;
  margin: var(--sp-4) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.site-footer__tags a {
  display: inline-block;
  padding: var(--sp-1) var(--sp-3);
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);
  border-radius: 999px;
  color: var(--color-text);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: 300;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.site-footer__tags a:hover,
.site-footer__tags a:focus-visible {
  background: color-mix(in srgb, var(--color-text) 18%, transparent);
  border-color: var(--buttoncolor);
}

/* Fine print — center-aligned, soft, sits below the columns. Top
 * margin reduced and bottom padding bumped by the same amount so the
 * line moves up visually but the overall .site-footer__inner height
 * stays the same. */
.site-footer__finep {
  margin: var(--sp-7) 0 0;
  padding: 0 10vw 0 0;
  text-align: left;
  font-size: var(--fs-xs);
  font-weight: 300;
  line-height: 1.5;
  color: color-mix(in srgb, var(--color-text) 65%, var(--color-bright));
}
/* Paragraph break before © line — tighter than the gap to the columns
 * (--sp-7) since this is a within-finep separation, not a section gap.
 * Bottom margin pushes the policies bottom-strip away so the copyright
 * line breathes. */
.site-footer__finep--copyright {
  margin-top: var(--sp-3);
  margin-bottom: var(--sp-5);
}

/* Bottom strip — policies row, separated from the main footer band by a
 * faint top border. Same surface color so the rule provides the only
 * visual delineation (avoids mode-specific darkening tricks that would
 * flip directions between light/dark). */
.site-footer__btm {
  border-top: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);
}
.site-footer__policies {
  list-style: none;
  margin: 0;
  padding: var(--sp-4) var(--gutter);
  max-width: var(--max-content);
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  align-items: center;  /* vertically centre the language <select> against the text links it sits next to */
  gap: var(--sp-2) var(--sp-5);
  font-size: var(--fs-sm);
}
/* Link icon next to each policy item — 70/30 darken recipe to pass UI
 * contrast on the near-white footer bg in light mode. */
.site-footer__policies i,
.site-footer__policies svg {
  color: color-mix(in srgb, var(--buttoncolor) 70%, var(--color-text));
}
.site-footer__policies a,
.site-footer__btn-link {
  color: color-mix(in srgb, var(--color-text) 80%, var(--color-bright));
  text-decoration: none;
  font-weight: 300;
  transition: color var(--dur-fast) var(--ease-out);
}
.site-footer__policies a:hover,
.site-footer__policies a:focus-visible,
.site-footer__btn-link:hover,
.site-footer__btn-link:focus-visible {
  color: var(--color-text);
}
/* Reset <button> defaults so Cookie Settings reads as a link */
.site-footer__btn-link {
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

/* ============================================================
 * Site header / navigation
 * ============================================================
 * Sticky glass nav. Single shell shared by both partials
 * (header-generic + header-product). The product variant adds a
 * Buy CTA and a breadcrumb row. Scroll behavior is driven by a
 * data-scrolled attribute set on <header> by header.js — when
 * scrolled past 0, the 🅱️ logo prefix and the breadcrumb both
 * collapse to free up vertical space.
 *
 * Body padding-top keeps content from sliding under the sticky
 * header. We use scroll-padding-top instead of margin so anchor
 * jumps respect the fixed header.
 */

.site-header {
  position: sticky;
  /* --discount-bar-h is set by body.has-discount-banner (discount.js); when
   * the banner is hidden the var is unset and the fallback 0px keeps the
   * header flush against the viewport top as before. */
  top: var(--discount-bar-h, 0px);
  z-index: 50;
  font-family: var(--font-sans);
}

/* ---------- Discount banner (injected by /assets/global/js/discount.js)
 * Thin solid stripe pinned above the sticky nav, surfacing the active
 * personal-discount when a ?discount=CODE&lvl=N URL has been captured.
 * Visibility & wording are owned by discount.js; this rule only defines
 * presentation. Solid --color-buy (not the cart button's glass tint) so
 * it reads as an attention-grabbing campaign stripe rather than a UI
 * chrome element. Text color is dark in both light + dark modes since
 * solid orange has the same hex in both palettes and dark text on
 * --color-buy reaches ~9:1 contrast. */
#discount-banner {
  position: sticky;
  top: 0;
  z-index: 51;                       /* above .site-header (50) */
  background: var(--color-buy);
  color: #111;
  text-align: center;
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: 1.4;
  padding: 6px var(--sp-4);
  letter-spacing: 0.01em;
}
body.has-discount-banner { --discount-bar-h: 32px; }

/* Glass bg + backdrop live on the BAR (not the whole header) so the
 * breadcrumb row beneath stays visually transparent — it shows the page
 * bg behind it instead of its own pane.
 *
 * The glass surface is rendered on a ::before pseudo (NOT directly on
 * .site-header__bar), so the bar element itself does NOT establish a
 * containing block for its position:fixed descendants. (backdrop-filter
 * on a real element creates a containing block for fixed/absolute
 * descendants per spec — putting it on a pseudo avoids that, so the
 * mobile drawer's `position: fixed` escapes to the viewport as expected.)
 *
 * The bar spans the full viewport width (so the glass is edge-to-edge),
 * but its inner content is auto-centered to --max-content using a
 * padding-inline `max()` trick (no extra wrapper element needed). */
.site-header__bar {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  /* +25% vertical: was var(--sp-3) (12px) each side → now 20px each side. */
  padding-block: calc(var(--sp-3) + var(--sp-2));
  padding-inline: max(var(--gutter), calc((100% - var(--max-content)) / 2));
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 10%, transparent);
}
.site-header__bar::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: color-mix(in srgb, var(--color-text-dm) 95%, transparent);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
}
@media (prefers-color-scheme: dark) {
  .site-header__bar { border-bottom-color: color-mix(in srgb, var(--color-text-dm) 10%, transparent); }
  .site-header__bar::before {
    background: color-mix(in srgb, var(--color-text-dm) 10%, color-mix(in srgb, var(--color-dark-dm) 82%, transparent));
  }
}

/* ---------- Logo (text primary, PNG fallback) ---------- */

.bbw-logo {
  position: relative;  /* anchor for the absolutely-positioned 🅱️ */
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: var(--color-text);
  font-family: "Roboto Slab", Georgia, "Times New Roman", serif;
  font-weight: 400;
  /* 32pt at the high end (≈ 42.67px = 2.667rem). Floor 1.25rem so the
   * wordmark yields enough bar room for the lang chooser + hamburger
   * at ~320px viewports (was 1.75rem, which squashed the hamburger). */
  font-size: clamp(1.25rem, 1.2rem + 1.5vw, 2.667rem);
  letter-spacing: -0.01em;
  line-height: 1;
}
.bbw-logo:hover,
.bbw-logo:focus-visible { color: var(--color-text); }

/* The wordmark sits at the .bbw-logo container's left edge so it
 * left-aligns with the breadcrumb's "Products" link below (which
 * shares the same gutter padding on the bar). The 🅱️ is absolutely
 * positioned to the LEFT of the wordmark — overhanging into the bar's
 * gutter — so its presence/absence has zero effect on the wordmark's
 * position or any other element in the bar. */
.bbw-logo__text {
  display: inline-block;
}

.bbw-logo__b {
  position: absolute;
  right: 100%;
  top: 50%;
  margin-right: var(--sp-5);
  font-size: 1em;          /* same height as the wordmark */
  line-height: 1;
  transform: translateY(-50%);
  /* Driven by --scroll-progress (set by header.js): fades smoothly
   * from 1 → 0 as the user scrolls from 0 → SCROLL_FADE_RANGE px. */
  opacity: calc(1 - var(--scroll-progress, 0));
}

/* Fade out the 🅱️ when the viewport gutter shrinks below the size it
 * needs to sit fully inside the page. The bar centers content to
 * --max-content (1200px); on viewports < ~1320px the gutter is just
 * --gutter (24px), which is narrower than the 🅱️ + its margin-right
 * (~67px at full size). Source-order trumps the scroll-progress rule. */
@media (max-width: 1320px) {
  .bbw-logo__b { opacity: 0; }
}

/* PNG fallback — opt-in via .bbw-logo--imgfallback on the parent.
 * Hidden by default; the user can swap to image rendering later. */
.bbw-logo__img {
  display: none;
  height: 1em;
  width: auto;
}
.bbw-logo--imgfallback .bbw-logo__text,
.bbw-logo--imgfallback .bbw-logo__b { display: none; }
.bbw-logo--imgfallback .bbw-logo__img { display: inline-block; }

/* ---------- Primary nav links ---------- */

.site-nav {
  flex: 1;
  display: flex;
  justify-content: center;
}
.site-nav__list {
  list-style: none;
  display: flex;
  gap: var(--sp-1);
  margin: 0;
  padding: 0;
}
.site-nav__link {
  display: inline-block;
  padding: var(--sp-2) var(--sp-3);
  border-radius: 8px;
  font-size: var(--fs-sm);
  font-weight: 500;
  text-decoration: none;
  color: var(--color-text);
  transition:
    background-color var(--dur-fast) var(--ease-out),
    color            var(--dur-fast) var(--ease-out);
}
.site-nav__link:hover,
.site-nav__link:focus-visible {
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
  color: var(--color-text);
}

/* Products dropdown — toggle button + submenu panel.
 * The toggle is a real <button>, so it's keyboard-focusable and
 * announced as such; the submenu is shown when (a) the parent <li>
 * is hovered, (b) any child receives focus, or (c) header.js sets
 * data-open="true" via click. */
.site-nav__item { position: relative; }
.site-nav__link--toggle {
  background: transparent;
  border: 0;
  font: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}
.site-nav__caret {
  font-size: 0.7em;
  transition: transform var(--dur-fast) var(--ease-out);
}
.site-nav__item--has-submenu:hover .site-nav__caret,
.site-nav__item--has-submenu:focus-within .site-nav__caret,
.site-nav__item--has-submenu[data-open="true"] .site-nav__caret {
  transform: rotate(180deg);
}

.site-nav__submenu {
  position: absolute;
  top: calc(100% + var(--sp-2));
  left: 0;
  min-width: 12rem;
  list-style: none;
  margin: 0;
  padding: var(--sp-2);
  background: color-mix(in srgb, var(--color-bright) 92%, transparent);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  border: 1px solid color-mix(in srgb, var(--color-text) 12%, transparent);
  border-radius: 12px;
  box-shadow: 0 12px 28px color-mix(in srgb, var(--color-dark-dm) 18%, transparent);
  /* Hidden by default; revealed by hover / focus-within / [data-open]. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition:
    opacity    var(--dur-fast) var(--ease-out),
    transform  var(--dur-fast) var(--ease-out),
    visibility 0s linear var(--dur-fast);
}
@media (prefers-color-scheme: dark) {
  .site-nav__submenu {
    background: color-mix(in srgb, var(--color-dark-dm) 92%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 12%, transparent);
  }
}
.site-nav__item--has-submenu:hover .site-nav__submenu,
.site-nav__item--has-submenu:focus-within .site-nav__submenu,
.site-nav__item--has-submenu[data-open="true"] .site-nav__submenu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}

.site-nav__sublink {
  display: block;
  padding: var(--sp-2) var(--sp-3);
  border-radius: 6px;
  font-size: var(--fs-sm);
  font-weight: 500;
  text-decoration: none;
  color: color-mix(in srgb, var(--color-text) 75%, var(--color-bright));
  white-space: nowrap;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    color            var(--dur-fast) var(--ease-out);
}
.site-nav__sublink:hover,
.site-nav__sublink:focus-visible {
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
  color: var(--color-text);
}

/* ---------- CTA cluster ---------- */

.site-header__cta {
  display: flex;
  gap: var(--sp-2);
  flex-shrink: 0;
}

/* Glass-button family — matches .hero__cta on the Logic page so all
 * site CTAs share one visual language. Translucent text-tinted fill,
 * tonal border, blur-saturate backdrop. The buy variant overrides
 * with --color-buy as the surface (same pattern as .buy a.hero__cta). */
.site-header__btn[hidden] { display: none; }
.site-header__btn {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-2) var(--sp-4);
  background: color-mix(in srgb, var(--color-text) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text) 28%, transparent);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: var(--color-text);
  border-radius: 8px;
  font-weight: 500;
  font-family: inherit;
  font-size: var(--fs-sm);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--dur-med) var(--ease-out),
    border-color     var(--dur-med) var(--ease-out),
    color            var(--dur-med) var(--ease-out);
}
.site-header__btn:hover,
.site-header__btn:focus-visible {
  background: color-mix(in srgb, var(--color-text) 20%, transparent);
  border-color: color-mix(in srgb, var(--color-text) 55%, transparent);
  color: var(--color-text);
}
/* Glass-orange treatment matching the lower .buy a.hero__cta family
 * — same 25%/45% bg mix, solid --color-buy stroke, body text color.
 * Buy stays glass regardless of cart state; the .is-cart-active
 * override below has been removed since base + active are identical.
 * Dark-mode override pins the tint level so it doesn't get garish on
 * the darker page bg. */
.site-header__btn--buy {
  background: color-mix(in srgb, var(--color-buy) 25%, transparent);
  border-color: var(--color-buy);
  color: var(--color-text);
}
.site-header__btn--buy:hover,
.site-header__btn--buy:focus-visible {
  background: color-mix(in srgb, var(--color-buy) 45%, transparent);
  border-color: var(--color-buy);
  color: var(--color-text);
}
@media (prefers-color-scheme: dark) {
  .site-header__btn--buy {
    background: color-mix(in srgb, var(--color-buy) 35%, transparent);
  }
  .site-header__btn--buy:hover,
  .site-header__btn--buy:focus-visible {
    background: color-mix(in srgb, var(--color-buy) 55%, transparent);
  }
}

/* Cart variant — orange-tinted glass that signals commerce + count.
 * Replaces the Demo button when the cart has ≥ 1 item (cart-status.js
 * toggles the [hidden] attribute). Text color is a 60/40 buy/text mix
 * so the count digit stays readable on the tinted background in both
 * modes. */
.site-header__btn--cart {
  background: transparent;
  border-color: var(--color-buy);
  color: color-mix(in srgb, var(--color-buy) 60%, var(--color-text));
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.site-header__btn--cart:hover,
.site-header__btn--cart:focus-visible {
  background: color-mix(in srgb, var(--color-buy) 12%, transparent);
  border-color: var(--color-buy);
  color: color-mix(in srgb, var(--color-buy) 75%, var(--color-text));
}
.site-header__btn--cart .EJ-CartItemsNum {
  font-size: 0.9em;
  margin-inline-start: 0.35em;
  /* Plain inline number — color inherits the button's color
   * (which the .is-cart-active swap also handles cleanly). */
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  text-align: center;
  line-height: 1.5;
}

/* (.is-cart-active swap on Buy removed — Buy is now permanently
 * glass-orange, the same look it previously had only when the cart
 * had items. The cart pill alone signals "you have items" since it
 * appears on demand. The is-cart-active class is still toggled by
 * cart-status.js but no longer styles Buy.) */

/* Device-specific button visibility:
 *   --demo: desktop only (the burger drawer doesn't need a Demo download
 *           on phones — folks on small screens are usually here to read,
 *           not to install).
 *   --contact: mobile only (desktop has the FreeScout bubble; the in-bar
 *              button on desktop would be redundant. The bubble is hidden
 *              on mobile, so the drawer button takes its place there).
 * Breakpoint matches the burger-menu breakpoint at 820px. */
@media (max-width: 820px) {
  .site-header__btn--demo { display: none; }
}
@media (min-width: 821px) {
  .site-header__btn--contact { display: none; }
}

/* ---------- Hamburger (mobile) ---------- */

.site-header__hamburger {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;  /* default flex-shrink:1 would squash it to nothing on narrow viewports where logo+lang fill the bar */
  padding: 0;
  background: color-mix(in srgb, var(--color-text) 6%, transparent);
  color: var(--color-text);
  border: 1px solid color-mix(in srgb, var(--color-text) 22%, transparent);
  border-radius: 8px;
  cursor: pointer;
  font-size: var(--fs-md);
}


/* ---------- Breadcrumb (product variant only) ---------- */

/* Breadcrumb sits below the bar with NO background — page bg shows
 * through. Both opacity and height shrink continuously with scroll
 * (--scroll-progress, set by header.js): at progress 0 it's at full
 * height + opacity, at 1 it's collapsed to 0. */
.site-header__breadcrumb {
  background: transparent;
  overflow: hidden;
  opacity: calc(1 - var(--scroll-progress, 0));
  max-height: calc(3rem * (1 - var(--scroll-progress, 0)));
}
.site-header__breadcrumb ol {
  list-style: none;
  display: flex;
  gap: var(--sp-2);
  margin: 0;
  padding-block: var(--sp-2);
  padding-inline: max(var(--gutter), calc((100% - var(--max-content)) / 2));
  font-size: var(--fs-sm);
  color: var(--color-text);
}
.site-header__breadcrumb li + li::before {
  content: "›";
  margin-right: var(--sp-2);
  color: color-mix(in srgb, var(--color-text) 70%, var(--color-bright));
}
.site-header__breadcrumb a {
  color: inherit;
  text-decoration: none;
}
.site-header__breadcrumb a:hover,
.site-header__breadcrumb a:focus-visible {
  color: var(--color-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.site-header__breadcrumb [aria-current="page"] {
  color: var(--color-text);
  font-weight: 500;
}

/* ---------- Drawer wrapper (desktop = transparent, mobile = panel) ---------- */

/* Desktop: the wrapper just exists for mobile-mode reuse; on wide
 * viewports its children participate in the bar's flex layout
 * normally via `display: contents`. */
.site-header__drawer {
  display: contents;
}

/* ---------- Mobile collapse + slide-in drawer ---------- */

/* Drawer breakpoint deliberately a touch wider (820px, not 720px)
 * than the rest of the site's 720 cutoff: with the desktop bar showing
 * logo + Products dropdown + Support + EDU + Buy + Demo, the Demo
 * button starts getting clipped before the layout is actually 720
 * narrow. Other components (buy section, dl-bento, etc.) still flip
 * at 720 because they have more room to play with. */
@media (max-width: 820px) {
  .site-header__bar { gap: var(--sp-3); }
  .site-header__hamburger { display: inline-flex; }

  /* Pin the bar's right-edge tools (hamburger) by pushing logo to
   * fill remaining space — drawer is fixed-positioned so it doesn't
   * occupy bar layout. */
  .site-header__bar > .bbw-logo { margin-right: auto; }

  .site-header__drawer {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(82vw, 360px);
    padding: calc(var(--sp-7) + var(--sp-4)) var(--sp-5) var(--sp-5);
    overflow-y: auto;
    background: color-mix(in srgb, var(--color-text-dm) 24%, color-mix(in srgb, var(--color-bright) 88%, transparent));
    backdrop-filter: blur(24px) saturate(160%);
    -webkit-backdrop-filter: blur(24px) saturate(160%);
    border-left: 1px solid color-mix(in srgb, var(--color-text) 12%, transparent);
    box-shadow: -12px 0 32px color-mix(in srgb, var(--color-dark-dm) 22%, transparent);
    transform: translateX(100%);
    transition: transform var(--dur-med) var(--ease-out);
    z-index: 60;
  }
  @media (prefers-color-scheme: dark) {
    .site-header__drawer {
      background: color-mix(in srgb, var(--color-text-dm) 12%, color-mix(in srgb, var(--color-dark-dm) 88%, transparent));
      border-left-color: color-mix(in srgb, var(--color-text-dm) 12%, transparent);
    }
  }
  .site-header[data-drawer-open="true"] .site-header__drawer {
    transform: translateX(0);
  }

  /* Inside the drawer: full-width nav links, vertical layout. The
   * Products toggle button is hidden — submenu is flattened into the
   * main list as inline siblings. */
  .site-header__drawer .site-nav,
  .site-header__drawer .site-header__cta {
    flex: none;
    flex-direction: column;
    gap: var(--sp-1);
  }
  .site-header__drawer .site-nav__list {
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-1);
    width: 100%;
  }
  .site-header__drawer .site-nav__link {
    width: 100%;
    padding: var(--sp-3) var(--sp-3);
    font-size: var(--fs-md);
  }
  .site-header__drawer .site-nav__link--toggle { display: none; }
  .site-header__drawer .site-nav__submenu {
    /* Static, fully visible, no glass panel — flat list inside the drawer. */
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border: 0;
    box-shadow: none;
    padding: 0;
    min-width: 0;
  }
  .site-header__drawer .site-nav__sublink {
    width: 100%;
    padding: var(--sp-3) var(--sp-3);
    font-size: var(--fs-md);
  }

  /* Buttons in the drawer:
   * - Buy + Cart share the first row (Buy takes most of the width;
   *   Cart sizes to its content). Cart starts hidden so Buy alone
   *   stretches across the full row until cart-status.js reveals Cart.
   * - Contact Us wraps to the second row at full width.
   * row-wrap on the .cta container + per-button flex-basis does it. */
  .site-header__drawer .site-header__cta {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--sp-2);
  }
  .site-header__drawer .site-header__btn {
    justify-content: center;
    padding: var(--sp-3) var(--sp-4);
    font-size: var(--fs-md);
    border-radius: 8px;
  }
  /* Buy + Cart share the row (flex-basis 0 + flex-grow ratio). Buy
   * grows ~3× faster so Cart stays compact (just icon + count) and
   * Buy fills whatever's left. */
  .site-header__drawer .site-header__cta > .site-header__btn--buy {
    flex: 3 1 0;
    width: auto;
  }
  .site-header__drawer .site-header__cta > .site-header__btn--cart {
    flex: 0 0 auto;
    width: auto;
  }
  /* Anything else (Contact Us) wraps to its own full-width row. */
  .site-header__drawer .site-header__cta > .site-header__btn:not(.site-header__btn--buy):not(.site-header__btn--cart) {
    flex: 1 1 100%;
    width: 100%;
  }
}

/* ---------- Drawer scrim (backdrop behind the open drawer) ---------- */

.site-header__scrim {
  position: fixed;
  inset: 0;
  background: color-mix(in srgb, var(--color-dark-dm) 45%, transparent);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  z-index: 55;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-med) var(--ease-out);
}
.site-header[data-drawer-open="true"] .site-header__scrim {
  opacity: 1;
  pointer-events: auto;
}
.site-header[data-drawer-open="true"] .site-header__scrim[hidden] {
  display: block;
}
@media (min-width: 821px) {
  .site-header__scrim { display: none; }
}

/* Lock background scroll while the drawer is open. Class is toggled on
 * <body> by header.js. */
.body--drawer-open { overflow: hidden; }

/* ===== "Needs fix" review badge =====
 * Editor-facing flag for content that needs a follow-up: a video URL
 * that's broken or duplicated, a section still tagged BESPOKE, etc.
 * Set data-needs-fix="true" on any element and a bright orange pill
 * is appended via ::after. High-vis on purpose — these should never
 * ship to production. Removing the attribute removes the badge. */
[data-needs-fix="true"]::after {
  content: "needs fix";
  display: inline-block;
  margin-left: 0.5em;
  padding: 0.15em 0.7em;
  font-size: 0.72em;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  background: #ff5722;
  color: #fff;
  border-radius: 999px;
  vertical-align: middle;
  line-height: 1.4;
  white-space: nowrap;
  box-shadow: 0 2px 6px color-mix(in srgb, #ff5722 45%, transparent);
}

/* ===== Flag pill banner =====
 * Small uppercase pill banner. Used for inline announcements at the
 * top of a section: version highlights, beta tags, "new" badges.
 * Variants pick the colour scheme — keep contrast ≥ 4.5:1 in every
 * variant (so e.g. --info uses dark text on yellow, not white). */
.flag-banner {
  display: inline-block;
  padding: 8px 14px;
  font-size: 0.85em;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  border-radius: 6px;
  margin-bottom: 1em;
}
.flag-banner--info {
  background: #f5b800;
  color: #1a1a1a;
}
/* Mobile: tone the flag down — faint yellow tint background, yellow
 * text, no border, and shrink the type by 25% so it stops dominating
 * the hero. */
@media (max-width: 40em) {
  .flag-banner { font-size: 0.64em; }
  .flag-banner--info {
    background: color-mix(in srgb, #f5b800 8%, transparent);
    color: #f5b800;
  }
}

/* ----- Language switcher (EN | DE) -----
 * Compact pair of text buttons in the site header. The active language
 * is communicated via aria-current="page" — CSS uses the same attribute
 * to underline + opacity-up the active button so visual + assistive
 * states stay in sync. Inactive sibling carries aria-current="false"
 * (literal — empty string is invalid per WAI-ARIA). Click handler
 * lives in /assets/global/js/lang-switch.js (sets bbw_lang cookie +
 * reloads; Apache .htaccess rewrite picks the per-language file). */
.site-lang {
  display: flex;
  gap: var(--sp-2);
  margin-inline: var(--sp-3);
  font-size: var(--fs-sm);
  font-weight: 500;
}

/* Wide viewports: float the chooser into the right gutter, mirroring
 * the 🅱️ glyph that sits in the left gutter (.bbw-logo__b). The bar
 * centres its content to --max-content (1200px); on viewports ≥1321px
 * there's enough gutter on the right to host the chooser outside the
 * centred content area. Below that breakpoint the chooser stays inline
 * as a regular flex child (same threshold as the 🅱️). */
@media (min-width: 1321px) {
  .site-header__bar > .site-lang {
    position: absolute;
    left: calc(50% + var(--max-content) / 2 + var(--sp-5));
    top: 50%;
    transform: translateY(-50%);
    margin-inline: 0;
  }
}
.site-lang__btn {
  background: transparent;
  border: 0;
  padding: 0;
  color: var(--color-text);
  opacity: 0.55;
  cursor: pointer;
  font: inherit;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.site-lang__btn[aria-current="page"] {
  opacity: 1;
  text-decoration: underline;
  text-underline-offset: 4px;
}
.site-lang__btn:hover {
  opacity: 1;
}
@media (max-width: 820px) {
  .site-header__drawer .site-lang {
    margin-inline: 0;
    margin-top: var(--sp-4);
    justify-content: center;
    gap: var(--sp-4);
  }
}
/* Footer language picker — sits as a <li> in .site-footer__policies
 * (next to the legal links: Impressum, Privacy, etc.). The <select>
 * is styled to match the link typography of the bar but stays a
 * native <select> for full-language access. */
.site-footer__policies__lang {
  display: inline-flex;
  align-items: center;
}
.footer-lang__select {
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--color-text) 25%, transparent);
  border-radius: var(--radius-sm);
  padding: var(--sp-1) var(--sp-2);
  color: inherit;
  font: inherit;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.footer-lang__select:hover,
.footer-lang__select:focus { opacity: 1; }

/* Homepage bottom language picker — replaces the header chooser on the
 * homepage (suppressed by build.php for slug === 'homepage'). Centered,
 * glass-styled bottom strip with backdrop-blur background mirroring
 * the .site-header__bar treatment at the top, so the homepage is
 * sandwiched between matching glass strips. The <select> itself uses
 * the same .hero__cta-style glass treatment for visual continuity. */
.home-lang-picker {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: var(--sp-6) var(--sp-3);
  flex-wrap: wrap;
  border-top: 1px solid color-mix(in srgb, var(--color-text-dm) 25%, transparent);
  /* Overlap the bottom 120px of the hero so the hero's image bleeds
   * behind this strip — backdrop-filter on ::before below blurs the
   * image, producing the actual glass effect. Total visible height
   * stays the same: hero(100svh-64px) + picker(120px overlap) =
   * fits within the initial viewport. z-index lifts the picker above
   * the hero's animated gradient + overlay layers. */
  margin-top: -120px;
  z-index: 2;
}
.home-lang-picker::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Low-opacity overlay so the studio image shows through clearly —
   * 95% would have masked the blur entirely. */
  background: color-mix(in srgb, var(--color-text-dm) 35%, transparent);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
}
@media (prefers-color-scheme: dark) {
  .home-lang-picker {
    border-top-color: color-mix(in srgb, var(--color-text-dm) 10%, transparent);
  }
  .home-lang-picker::before {
    /* Dark mode: subtler dark tint so the bg image stays legible. */
    background: color-mix(in srgb, var(--color-dark-dm) 50%, transparent);
  }
}
.home-lang-picker__label {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--color-text);
  opacity: 0.7;
}
.home-lang-picker__select {
  background: color-mix(in srgb, var(--color-text-dm) 35%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-text-dm) 70%, transparent);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-radius: 8px;
  padding: var(--sp-2) var(--sp-4);
  color: var(--color-text);
  font: inherit;
  font-size: var(--fs-md);
  font-weight: 500;
  cursor: pointer;
  transition:
    background-color 320ms cubic-bezier(0.16, 1, 0.3, 1),
    border-color     320ms cubic-bezier(0.16, 1, 0.3, 1);
}
.home-lang-picker__select:hover,
.home-lang-picker__select:focus-visible {
  background: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  border-color: color-mix(in srgb, var(--color-text-dm) 90%, transparent);
}
@media (prefers-color-scheme: dark) {
  .home-lang-picker__select {
    background: color-mix(in srgb, var(--color-text-dm) 10%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 28%, transparent);
  }
  .home-lang-picker__select:hover,
  .home-lang-picker__select:focus-visible {
    background: color-mix(in srgb, var(--color-text-dm) 20%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 45%, transparent);
  }
}
@media (max-width: 40em) {
  .home-lang-picker {
    padding: var(--sp-3) var(--sp-3);
    margin-top: -64px;
  }
  .home-lang-picker__label { display: none; }
}

/* ===== ArtFinder (/art-finder/) =====
 * Scoped under [data-page="art-finder"]. Palette tokens come from
 * src/art-finder/tokens.css. The visible chrome (search input, search
 * button, match toggle, dropdowns, scope checkboxes, chips, panels,
 * Find Similar) all use the BBW glass-button recipe (translucent
 * --color-text-dm fill at 35% / blur(10px) saturate(140%)) so the page
 * reads as part of the site. Lighter than v1 — let the page bg show
 * through.
 *
 * The page uses a sticky-footer flex column at desktop+ (above 600px)
 * so the 4-panel grid claims the remaining viewport height between
 * the BBW header and the footer — same single-screen feel the
 * standalone had. Below 600px the layout falls back to natural
 * document flow so mobile users can scroll through all 4 panels.
 */

@media (min-width: 601px) {
  /* The data-page attribute IS on <body>, so this targets body. */
  [data-page="art-finder"] {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
  }
  [data-page="art-finder"] main#main { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  [data-page="art-finder"] main#main > .bg-section { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  [data-page="art-finder"] .bg-section > .af-page { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  [data-page="art-finder"] .af-controls-region { flex: 0 0 auto; }
  /* Grid flex-fills the remaining viewport between controls + footer.
   * min-height: 30rem (~480px) keeps each panel usable on monitors
   * too short to show all 4 panels + footer — at that floor the
   * footer scrolls below the fold. On taller monitors flex: 1 1 0
   * grows the grid to fill, naturally bounded by available height
   * so the bottom never clips below the viewport. */
  [data-page="art-finder"] .af-grid { flex: 1 1 0; min-height: 30rem; grid-template-rows: 1fr 1fr; }
}

/* Override --buttoncolor to a darker gray-blue (#4F5C66, ~7.7:1
 * vs --color-text-dm white text) so the match-toggle active state
 * + checked checkbox both pass AA. Generic-theme default #6B7B86
 * was 3.84:1 — failed normal-text contrast. Only scoped to this
 * page; doesn't leak to support/edu. */
[data-page="art-finder"] {
  --buttoncolor: #4F5C66;
}

/* Panel-header icons inherit the h2's color so they sit at the same
 * weight as the title text (no separate accent token). */
[data-page="art-finder"] .af-panel-header h2 .af-panel-icon {
  margin-right: var(--sp-2);
}

/* Gradient lives on the .bg-section wrapper (shared rule near the top
 * of the file). Page-vertical-padding sits on the inner container so
 * the gradient + bottom hairline extend full-bleed. Tight padding so
 * the grid claims as much viewport as possible (the BBW header above
 * already provides air between the page top and the search row). */
[data-page="art-finder"] .bg-section .af-page {
  padding-block: var(--sp-5) var(--sp-5);
}

[data-page="art-finder"] .af-controls-region {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  margin-bottom: var(--sp-7);
}

/* Shared glass surface — applied to query input, dropdowns, search
 * button, match-toggle shell, scope-checkbox pills, and chips. Single
 * source of truth so the family stays cohesive. Border-radius and
 * padding are overridden per-element below where needed. */
[data-page="art-finder"] :is(
  #af-query, .af-selects select, #af-search-button,
  .af-match-toggle
) {
  background: color-mix(in srgb, var(--color-text-dm) 35%, transparent);
  /* Solid --color-text border (dark in light mode, light in dark mode
   * via the cascade swap). Earlier 35% mix landed at ~1.95:1 over the
   * gray page bg — visible but reading as gray, not dark. */
  border: 1px solid var(--color-text);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: var(--color-text);
  border-radius: 8px;
  font-family: inherit;
  transition:
    background-color 240ms cubic-bezier(0.16, 1, 0.3, 1),
    border-color     240ms cubic-bezier(0.16, 1, 0.3, 1),
    color            240ms cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-color-scheme: dark) {
  [data-page="art-finder"] :is(
    #af-query, .af-selects select, #af-search-button,
    .af-match-toggle
  ) {
    /* Background only — border-color inherits from the light-mode
     * rule and auto-swaps via --color-text's cascade override. */
    background: color-mix(in srgb, var(--color-text-dm) 10%, transparent);
  }
}

[data-page="art-finder"] :is(
  #af-query, .af-selects select, #af-search-button
):focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 2px;
}

/* ---- Query row: input fills, search button on the right ---- */
[data-page="art-finder"] .af-query-row {
  display: flex;
  gap: var(--sp-2);
}

[data-page="art-finder"] .af-query-wrap {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
}

[data-page="art-finder"] #af-query {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--sp-3) var(--sp-4);
  /* Right padding reserves room for the absolutely-positioned match
   * toggle PLUS gap between the native <input type="search"> ✕ clear
   * button (which renders at the inner edge of the padding) and the
   * toggle. controller.js measures the toggle's actual width and
   * stamps it into --af-match-toggle-w on the document root, so this
   * adapts to any language's button-label width.
   *   = toggle width + right offset (~sp-2) + small gap to ✕ (sp-4)
   * Fallback covers the brief moment between page paint and JS run. */
  padding-right: calc(var(--af-match-toggle-w, 9rem) + var(--sp-4));
  font-size: var(--fs-md);
  /* Heavily translucent fill so the gradient bg shows through.
   * Border-color intentionally NOT set here — the shared :is() rule's
   * solid var(--color-text) wins via source order at equal specificity. */
  background: color-mix(in srgb, var(--color-text-dm) 12%, transparent);
}

@media (prefers-color-scheme: dark) {
  [data-page="art-finder"] #af-query {
    background: color-mix(in srgb, var(--color-text-dm) 4%, transparent);
  }
}

[data-page="art-finder"] #af-query::placeholder {
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}

/* Custom-paint the native <input type="search"> cancel button so its
 * color is under our control (mask-image + background-color cascade-
 * swap via --color-text). Mask uses Font Awesome's `regular circle-xmark`
 * path so it visually matches the icon family used elsewhere on the page.
 * Chrome/Safari only — Firefox doesn't render the cancel button at all
 * and users rely on the Escape-to-clear shortcut wired in controller.js. */
[data-page="art-finder"] #af-query::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  margin-left: var(--sp-1);
  cursor: pointer;
  background-color: var(--color-text);
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><path fill='black' d='M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z'/></svg>");
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><path fill='black' d='M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z'/></svg>");
  mask-repeat: no-repeat;
  -webkit-mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-position: center;
  mask-size: contain;
  -webkit-mask-size: contain;
}

[data-page="art-finder"] #af-query.af-loading {
  background-image: linear-gradient(90deg,
    transparent,
    color-mix(in srgb, var(--buttoncolor) 22%, transparent),
    transparent);
  background-size: 200% 100%;
  animation: af-loading 1.2s linear infinite;
}

@media (prefers-reduced-motion: no-preference) {
  @keyframes af-loading {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
  }
}

/* ---- Match toggle: glass shell sits inset on the right of #af-query ---- */
[data-page="art-finder"] .af-match-toggle {
  position: absolute;
  right: var(--sp-2);
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  padding: 2px;
  border-radius: 6px;  /* tighter than the shared 8px for the inset look */
}

[data-page="art-finder"] .af-match-toggle button {
  border: 0;
  background: transparent;
  color: var(--color-text);
  padding: 4px 10px;
  font: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
  border-radius: 4px;
  transition: background-color 200ms ease, color 200ms ease;
}

[data-page="art-finder"] .af-match-toggle button.af-match-active {
  /* The page-scoped --buttoncolor override (#4F5C66) gives ~7.7:1
   * contrast against --color-text-dm white text — passes AA. */
  background: var(--buttoncolor);
  color: var(--color-text-dm);
}

[data-page="art-finder"] .af-match-toggle button:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: -2px;
}

/* ---- Search submit — glass-on-glass, hover intensifies ---- */
[data-page="art-finder"] #af-search-button {
  flex: 0 0 auto;
  padding: var(--sp-3) var(--sp-5);
  font-size: var(--fs-md);
  font-weight: 500;
  cursor: pointer;
  /* Brighter fill than the input (primary-action emphasis), solid
   * --color-text border so the stroke reads firm against the gray
   * page bg. */
  background: color-mix(in srgb, var(--color-text-dm) 70%, transparent);
  border-color: var(--color-text);
  /* Allow icon + text to share the button cleanly. */
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

/* Desktop: text-only button (FA icon hidden). Matches the standalone's
 * desktop look. */
@media (min-width: 601px) {
  [data-page="art-finder"] #af-search-button .af-search-icon { display: none; }
}

[data-page="art-finder"] #af-search-button:hover,
[data-page="art-finder"] #af-search-button:focus-visible {
  background: color-mix(in srgb, var(--color-text-dm) 90%, transparent);
  border-color: var(--color-text);
}

@media (prefers-color-scheme: dark) {
  [data-page="art-finder"] #af-search-button {
    background: color-mix(in srgb, var(--color-text-dm) 28%, transparent);
  }
  [data-page="art-finder"] #af-search-button:hover,
  [data-page="art-finder"] #af-search-button:focus-visible {
    background: color-mix(in srgb, var(--color-text-dm) 45%, transparent);
    /* Border-color inherits from light-mode rule; --color-text
     * cascade-swaps to light here. */
  }
}

/* ---- Controls row: scope checkboxes + dropdowns ---- */
[data-page="art-finder"] .af-controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  align-items: center;
  justify-content: space-between;
}

[data-page="art-finder"] .af-toggles {
  border: 0;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* Each scope label is plain inline-flex with a custom-painted square
 * checkbox + the label text — no pill background, no border. The native
 * input is hidden via appearance:none and repainted to match the glass
 * family. */
[data-page="art-finder"] .af-toggles label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 4px 0;
  font-size: var(--fs-sm);
  color: var(--color-text);
  cursor: pointer;
  user-select: none;
}

[data-page="art-finder"] .af-toggles input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  margin: 0;
  border: 1px solid var(--color-text);
  border-radius: 3px;
  background: transparent;
  /* Drives the SVG checkmark via stroke='currentColor' on :checked.
   * --color-bright is the inverse of --color-text in both modes
   * (light: light bg → dark text; dark: dark bg → light text), so
   * the checkmark stays visible regardless of color scheme. */
  color: var(--color-bright);
  position: relative;
  cursor: pointer;
  transition: background-color 160ms ease, border-color 160ms ease;
}

[data-page="art-finder"] .af-toggles input[type="checkbox"]:checked {
  background: var(--buttoncolor);
  border-color: var(--buttoncolor);
}

[data-page="art-finder"] .af-toggles input[type="checkbox"]:checked::after {
  /* mask-image + background-color = always-light --color-text-dm so
   * the checkmark stays visible on the dark blue bg in both modes
   * (--color-text-dm doesn't swap on @media; it's hardcoded
   * always-light per the BBW token convention). */
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--color-text-dm);
  mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='2.5 7.5 5.5 10 11.5 4'/></svg>");
  -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='2.5 7.5 5.5 10 11.5 4'/></svg>");
  mask-repeat: no-repeat;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
}

[data-page="art-finder"] .af-toggles input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 2px;
}

/* ---- Dropdowns ---- */
[data-page="art-finder"] .af-selects {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

[data-page="art-finder"] .af-selects select {
  padding: 6px var(--sp-4) 6px var(--sp-3);
  font-size: var(--fs-sm);
  cursor: pointer;
}

/* ---- Chips ---- */
/* Reserve one chip row's height even when empty so the first added
 * filter doesn't push the bento grid down. 34px = chip's intrinsic
 * height (24px button min + 4px padding × 2 + 1px border × 2). */
[data-page="art-finder"] #af-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  min-height: 34px;
}

[data-page="art-finder"] .af-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px var(--sp-3);
  font-size: var(--fs-sm);
  border-radius: 999px;
  /* Solid --color-bright surface (cascade-swaps light↔dark): bright in
   * light mode, mid-gray in dark mode. Both give the chip a clearly
   * visible pill surface, with --color-text glyph contrasting at
   * ~12:1 / ~4.7:1. The translucent-glass treatment from the shared
   * :is rule made the chip vanish against the dark page bg. */
  background: var(--color-bright);
  border: 1px solid var(--color-text);
  color: var(--color-text);
}

[data-page="art-finder"] .af-chip button {
  border: 0;
  background: transparent;
  /* Explicit color (not inherit) so it doesn't get clobbered by any
   * UA stylesheet defaults — picks up --color-text directly. */
  color: var(--color-text);
  cursor: pointer;
  /* Padding + min-size hit the WCAG 2.2 SC 2.5.8 24×24 touch target. */
  padding: 4px 6px;
  min-width: 24px;
  min-height: 24px;
  font-size: var(--fs-md);
  line-height: 1;
}

[data-page="art-finder"] .af-chip button:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 2px;
  border-radius: 2px;
}

[data-page="art-finder"] .af-hint {
  margin: 0;
  font-size: var(--fs-sm);
  color: color-mix(in srgb, var(--color-text) 65%, transparent);
}

/* ---- Four-panel grid laid out as 2×2 (matches the standalone). Each
 * panel is a bento glass tile with a fixed-ish height so the list
 * inside scrolls instead of growing to fit the data — without that
 * constraint a panel with 1000+ articulations would take over the page. ---- */
[data-page="art-finder"] .af-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}

/* Filters disclosure — mobile only. controller.js relocates the
 * match toggle, scope checkboxes, and selects INTO .af-options-body
 * when the (max-width: 600px) MQ matches; on desktop the disclosure
 * is hidden via display: none and the controls live in the regular
 * .af-controls row below. State (open/closed) is persisted in
 * localStorage by controller.js. */
[data-page="art-finder"] .af-options { display: none; }

@media (max-width: 600px) {
  /* Tighter gap between the controls block (chips reserve their own row
   * via #af-chips min-height) and the bento grid on mobile — desktop's
   * sp-7 (48px) reads as a wasted band on a phone screen. */
  [data-page="art-finder"] .af-controls-region { margin-bottom: var(--sp-3); }

  [data-page="art-finder"] .af-grid { grid-template-columns: 1fr; }

  /* Articulations sits at the top of the stack on mobile when the
   * other three panels are all collapsed — Articulations is the
   * primary result panel, no point hiding it behind 3 collapsed
   * sections. The :has() rule reverts the order to natural (4th)
   * the moment any of M/L/I is expanded, so the user can see the
   * panel they just expanded at its expected position. */
  [data-page="art-finder"] .af-grid > .af-panel[data-panel="articulations"] {
    order: -1;
  }
  [data-page="art-finder"] .af-grid:has(
    .af-panel[data-panel="manufacturers"]:not(.is-collapsed),
    .af-panel[data-panel="libraries"]:not(.is-collapsed),
    .af-panel[data-panel="instruments"]:not(.is-collapsed)
  ) > .af-panel[data-panel="articulations"] {
    order: 0;
  }

  /* Search bar layout: input + Search button on the same row. The
   * match toggle (when desktop has it inset on the input) is moved
   * into the Filters disclosure on mobile by controller.js. */
  [data-page="art-finder"] .af-query-row {
    align-items: stretch;
    gap: var(--sp-2);
  }
  [data-page="art-finder"] .af-query-wrap {
    flex: 1 1 auto;
    min-width: 0;
  }
  /* Input no longer needs the toggle-clearance padding (toggle is
   * now in the disclosure, not inset on the input). */
  [data-page="art-finder"] #af-query {
    padding-right: var(--sp-4);
  }
  /* Search button: icon-only on mobile so the input gets all the
   * horizontal space; aria-label keeps the accessible name "Search". */
  [data-page="art-finder"] #af-search-button {
    flex: 0 0 auto;
    width: auto;
    padding: var(--sp-3) var(--sp-4);
  }
  [data-page="art-finder"] #af-search-button .af-search-button-text { display: none; }
  [data-page="art-finder"] #af-query,
  [data-page="art-finder"] #af-search-button { font-size: var(--fs-sm); }

  /* The .af-controls block (scope checkboxes + selects) is hidden
   * on mobile because controller.js moves its children into the
   * Filters disclosure. */
  [data-page="art-finder"] .af-controls { display: none; }

  /* Outer box wrapping search row + Filters disclosure as one unit. */
  [data-page="art-finder"] .af-search-section {
    border: 1px solid var(--color-text);
    border-radius: 8px;
    background: color-mix(in srgb, var(--color-text-dm) 12%, transparent);
    padding: var(--sp-3);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
  }
  /* Inside the box, the input loses its individual border so only the
   * outer container draws a frame. Background stays so the input is
   * still discernible against the box's bg. */
  [data-page="art-finder"] .af-search-section #af-query {
    border-color: transparent;
  }

  /* Filters disclosure — flat text + chevron, no border, no background. */
  [data-page="art-finder"] .af-options {
    display: block;
    border: 0;
    background: transparent;
    border-radius: 0;
  }
  [data-page="art-finder"] .af-options-summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 0;
    font-family: 'Poppins', sans-serif;
    font-weight: 500;
    font-size: var(--fs-sm);
    color: var(--color-text);
    user-select: none;
  }
  [data-page="art-finder"] .af-options-summary::-webkit-details-marker { display: none; }
  [data-page="art-finder"] .af-options-caret {
    color: color-mix(in srgb, var(--color-text) 60%, transparent);
    transition: transform 200ms ease;
    font-size: var(--fs-xs);
  }
  [data-page="art-finder"] .af-options:not([open]) .af-options-caret {
    transform: rotate(-90deg);
  }
  /* When open, hairline divider above the body to separate it from
   * the Filters trigger row. */
  [data-page="art-finder"] .af-options[open] .af-options-body {
    margin-top: var(--sp-3);
    padding-top: var(--sp-3);
    border-top: 1px solid color-mix(in srgb, var(--color-text) 30%, transparent);
  }
  [data-page="art-finder"] .af-options-body {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
  }
  /* Inside the disclosure, controls render in normal flow. The match
   * toggle was absolute on desktop; reset for static placement here. */
  [data-page="art-finder"] .af-options-body .af-match-toggle {
    position: static;
    transform: none;
    align-self: flex-start;
  }
  [data-page="art-finder"] .af-options-body .af-toggles {
    border: 0;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
  }
  [data-page="art-finder"] .af-options-body .af-selects {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
  }
  [data-page="art-finder"] .af-options-body .af-selects select {
    padding: 4px var(--sp-2);
    font-size: var(--fs-xs);
    max-width: 11rem;
  }
}

/* The panel is a plain <section>/<header>; collapse is class-driven
 * (.is-collapsed) and lives entirely inside the mobile @media block.
 * Desktop never references the class, so panels are always rendered
 * regardless of whatever state mobile last left them in. */
[data-page="art-finder"] .af-panel {
  display: flex;
  flex-direction: column;
  background: color-mix(in srgb, var(--color-text-dm) 28%, transparent);
  border: 1px solid var(--color-text);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-radius: 10px;
  overflow: hidden;
  min-height: 0;
}

/* Caret rotation — points down when expanded, right when collapsed.
 * Lives in the base scope (no @media) but only matters on mobile where
 * the caret is actually visible. Desktop hides the caret entirely. */
[data-page="art-finder"] .af-panel-caret {
  margin-left: auto;
  font-size: var(--fs-sm);
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  transition: transform 200ms ease;
}
[data-page="art-finder"] .af-panel.is-collapsed > .af-panel-header > .af-panel-caret {
  transform: rotate(-90deg);
}

@media (min-width: 601px) {
  /* Desktop: panels are always expanded — caret hidden, header is
   * non-interactive. The .is-collapsed class is invisible to this
   * media block, so mobile state never leaks into the desktop view. */
  [data-page="art-finder"] .af-panel-caret { display: none; }
  [data-page="art-finder"] .af-panel-header { cursor: default; }
}

@media (max-width: 600px) {
  [data-page="art-finder"] .af-panel-header { cursor: pointer; }
  /* Slight tap-feedback on the header tap target. */
  [data-page="art-finder"] .af-panel-header:hover {
    background: color-mix(in srgb, var(--color-text) 5%, transparent);
  }
  /* Collapsed: hide the list entirely. */
  [data-page="art-finder"] .af-panel.is-collapsed > .af-panel-list {
    display: none;
  }
  /* Expanded: bound the list height so even a long list doesn't
   * push the next panel below the fold. The list's existing
   * overflow-y: auto kicks in inside this bound. */
  [data-page="art-finder"] .af-panel:not(.is-collapsed) > .af-panel-list {
    max-height: 60vh;
  }
}

@media (prefers-color-scheme: dark) {
  [data-page="art-finder"] .af-panel {
    /* Background only — border-color inherits + auto-swaps via
     * --color-text's cascade override. */
    background: color-mix(in srgb, var(--color-text-dm) 8%, transparent);
  }
}

[data-page="art-finder"] .af-panel-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  /* Solid var(--color-text) + ~30% mix so the separator reads as a
   * deliberate hairline, not a heavy black line. The 30% mix still
   * lands ~3:1 on the page bg in light mode — visible. */
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 30%, transparent);
}

[data-page="art-finder"] .af-panel-header h2 {
  margin: 0;
  font-family: 'Poppins', sans-serif;
  font-weight: 600;
  font-size: var(--fs-md);
  color: var(--color-text);
}

[data-page="art-finder"] .af-panel-count {
  font-size: var(--fs-xs);
  color: color-mix(in srgb, var(--color-text) 65%, transparent);
}

[data-page="art-finder"] .af-panel-list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  flex: 1 1 auto;
  user-select: none; /* prevent select-copy of articulation names per spec test plan */
  /* Thin, glass-toned scrollbar so the count column hugs the panel
   * border on platforms where scrollbars reserve track width
   * (Windows, macOS with "Always show scroll bars"). Without this
   * the default ~15px track ate the row's right edge. */
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--color-text) 30%, transparent) transparent;
}

[data-page="art-finder"] .af-panel-list::-webkit-scrollbar {
  width: 6px;
}
[data-page="art-finder"] .af-panel-list::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--color-text) 30%, transparent);
  border-radius: 3px;
}
[data-page="art-finder"] .af-panel-list::-webkit-scrollbar-track {
  background: transparent;
}

[data-page="art-finder"] .af-panel-list li {
  /* Light hairline between rows; matches the panel-header rule. */
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);
}

[data-page="art-finder"] .af-panel-list li:last-child { border-bottom: 0; }

[data-page="art-finder"] .af-item {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  /* Asymmetric horizontal padding: keep the row's left air at sp-3
   * (where the primary name reads) but tighten the right edge to sp-1
   * so the count sits ~4px from the bento border. */
  padding: var(--sp-2) var(--sp-1) var(--sp-2) var(--sp-3);
  text-align: left;
  background: transparent;
  border: 0;
  color: var(--color-text);
  font: inherit;
  font-size: var(--fs-sm);
  cursor: pointer;
}

[data-page="art-finder"] .af-item:hover {
  /* --color-text-based tint cascades to visible in both modes
   * (--buttoncolor at mid luminance disappeared on the dark panel bg). */
  background: color-mix(in srgb, var(--color-text) 12%, transparent);
}

[data-page="art-finder"] .af-item:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: -2px;
}

[data-page="art-finder"] .af-item[aria-pressed="true"] {
  /* Stronger than hover so the selected state is unambiguous. */
  background: color-mix(in srgb, var(--color-text) 30%, transparent);
}

[data-page="art-finder"] .af-item.af-dim { opacity: 0.45; }

[data-page="art-finder"] .af-item-name { flex: 1 1 auto; }

/* Tabular pair at the row's right edge: sub (manufacturer/library) is
 * right-aligned in its slot, count is left-aligned in its slot, so down
 * the column the sub-text shares a right edge and the counts share a
 * left edge. min-width on each gives the column its width — content
 * longer than the min still expands the slot naturally. */
[data-page="art-finder"] .af-item-sub {
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  color: color-mix(in srgb, var(--color-text) 40%, transparent);
  text-align: right;
  min-width: 5em;
}

[data-page="art-finder"] .af-art-name { flex: 1 1 auto; }

[data-page="art-finder"] .af-count {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  text-align: left;
  /* 1.6em fits the worst-case 3-digit count (e.g. 999) in tabular-nums
   * with no extra slack — keeps the count column close to the row's
   * right edge instead of leaving a half-empty slot. */
  min-width: 1.6em;
}

[data-page="art-finder"] .af-articulation-row { display: flex; align-items: stretch; }
[data-page="art-finder"] .af-articulation-row .af-item { flex: 1 1 auto; }

/* Find Similar — glass mini-button inside the row, same family as the chrome */
[data-page="art-finder"] .af-fresh-search {
  flex: 0 0 auto;
  border: 1px solid color-mix(in srgb, var(--color-text-dm) 70%, transparent);
  background: color-mix(in srgb, var(--color-text-dm) 35%, transparent);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  color: var(--color-text);
  font: inherit;
  font-size: var(--fs-xs);
  padding: 0 var(--sp-3);
  cursor: pointer;
  transition: background-color 200ms ease, border-color 200ms ease;
}

[data-page="art-finder"] .af-fresh-search:hover {
  background: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  border-color: color-mix(in srgb, var(--color-text-dm) 90%, transparent);
}

[data-page="art-finder"] .af-fresh-search:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: -2px;
}

@media (prefers-color-scheme: dark) {
  [data-page="art-finder"] .af-fresh-search {
    background: color-mix(in srgb, var(--color-text-dm) 10%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 28%, transparent);
  }
  [data-page="art-finder"] .af-fresh-search:hover {
    background: color-mix(in srgb, var(--color-text-dm) 20%, transparent);
    border-color: color-mix(in srgb, var(--color-text-dm) 55%, transparent);
  }
}

[data-page="art-finder"] .af-empty,
[data-page="art-finder"] .af-error {
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-sm);
  color: color-mix(in srgb, var(--color-text) 65%, transparent);
}

[data-page="art-finder"] .af-error {
  color: color-mix(in srgb, var(--color-text) 90%, var(--buttoncolor));
}

[data-page="art-finder"] .af-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ===== Homepage logo fly-in layer =====
 * Decorative atmospheric layer drawn between .home-hero__overlay
 * (z -1) and .home-hero__content (no z-index, stacks by source order).
 * The hero's existing layers all use negative z-indices, so leaving
 * .home-flyin's z-index unset puts it above overlay but below content
 * via source-order stacking — exactly the layer position we want.
 * Logos are pre-rendered white-on-transparent (see
 * tools/build-flyin-assets.py) so no runtime filter is needed —
 * opacity + blur transitions composite cleanly on the dark studio bg.
 *
 * Default state: hidden. Revealed only when motion is OK. Cut at 60em
 * (matches the gallery-collapse breakpoint).
 */
.home-flyin {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: none;
}
.home-flyin img {
  position: absolute;
  max-height: 96px;
  max-width: 320px;
  width: auto;
  height: auto;
  pointer-events: none;
  /* Initial opacity is 0; WAAPI fades in. Setting it here prevents a
   * single-frame flash before the first animate() call. */
  opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .home-flyin { display: block; }
}
.force-motion .home-flyin {
  display: block;
}
@media (max-width: 60em) {
  .home-flyin { display: none !important; }
}

/* ===== error-page.css ===== */
/* ===== Error pages (403, 404, 500, 503) =====
 * Chrome-free pages: no header/footer/consent banner. Self-contained
 * layout that always centers content in the viewport.
 *
 * Markup contract: <body class="error-page" data-code="404">
 *   <main class="error-page__inner">
 *     <div class="error-page__hero"> SVG + .error-page__code </div>
 *     <ul class="error-page__langs"> 7 <li> </ul>
 *     [<a class="error-page__link"> ... </a>]   (optional, 403/404 only)
 *   </main>
 * </body>
 */

body.error-page {
    min-height: 100dvh;
    margin: 0;
    display: grid;
    place-items: center;
    padding: clamp(1rem, 4vw, 3rem);
    box-sizing: border-box;
    /* Background, text color, and font come from base.css's `body` rule
     * — which already handles dark/light via tokens.css. We add nothing
     * here so the cascade works. */
}

.error-page__inner {
    display: grid;
    grid-template-columns: auto auto;
    grid-template-areas:
        "hero langs"
        "link link";
    column-gap: clamp(2rem, 4vw, 4rem);
    row-gap: clamp(1rem, 3vw, 2rem);
    align-items: center;
    justify-content: center;
    justify-items: center;
    max-width: 1100px;
    width: 100%;
}

.error-page__hero {
    grid-area: hero;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
}

.error-page__hero svg {
    width: clamp(180px, 32vw, 360px);
    height: auto;
    fill: currentColor;
    display: block;
    /* FA v7's car-burst + screwdriver-wrench have path data extending
     * above the declared viewBox (negative y on spark tips / handle
     * bevels). Default SVG `overflow: hidden` would clip those — let
     * them render. */
    overflow: visible;
    /* Glass look: soft floating shadow under the icon. The translucent
     * gradient (defined inside each SVG as #glassHero) creates the
     * actual glass-pane effect; the drop-shadow gives it depth. */
    filter:
        drop-shadow(0 4px 8px rgba(0, 0, 0, 0.18))
        drop-shadow(0 14px 28px rgba(0, 0, 0, 0.10));
}

/* 403 lock — narrower glyph than the others, reads larger at the same
 * width. Shrink 20% so it sits visually balanced with car-burst (404),
 * triangle (500), and screwdriver-wrench (503). */
body[data-code="403"] .error-page__hero svg {
    width: clamp(144px, 25.6vw, 288px);
}

.error-page__code {
    font-size: clamp(3rem, 8vw, 6rem);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
    margin: 0;
}

.error-page__langs {
    grid-area: langs;
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: clamp(0.4rem, 1.2vw, 0.9rem);
    font-size: clamp(1.25rem, 2.6vw, 2rem);
    font-weight: 600;
    line-height: 1.2;
}

.error-page__langs li {
    margin: 0;
}

/* Rainbow palette — light mode (mid-saturated, AA on white). */
.error-page__langs li:nth-child(1) { color: #d62828; } /* EN — red */
.error-page__langs li:nth-child(2) { color: #d97706; } /* DE — orange */
.error-page__langs li:nth-child(3) { color: #b45309; } /* FR — amber */
.error-page__langs li:nth-child(4) { color: #15803d; } /* IT — green */
.error-page__langs li:nth-child(5) { color: #0e7490; } /* ES — teal */
.error-page__langs li:nth-child(6) { color: #4338ca; } /* JA — indigo */
.error-page__langs li:nth-child(7) { color: #7e22ce; } /* zh-CN — violet */

.error-page__link {
    grid-area: link;
    margin-top: clamp(1rem, 3vw, 2rem);
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: inherit;
    text-decoration: none;
    font-weight: 500;
    font-size: clamp(0.95rem, 1.6vw, 1.1rem);
}

.error-page__link svg {
    width: 1em;
    height: 1em;
    fill: currentColor;
}

.error-page__link:hover,
.error-page__link:focus-visible {
    text-decoration: underline;
}

/* Mobile / narrow viewport: single column. */
@media (max-width: 720px) {
    .error-page__inner {
        grid-template-columns: 1fr;
        grid-template-areas:
            "hero"
            "langs"
            "link";
        text-align: center;
    }
    .error-page__langs {
        align-items: center;
    }
}

/* Dark mode — same hues, lighter so they hit AA on dark bg. Per
 * feedback_dark_mode_overrides.md the dark rule must come AFTER the
 * light rule (same selector, same specificity, last one wins). */
@media (prefers-color-scheme: dark) {
    .error-page__langs li:nth-child(1) { color: #f87171; } /* EN — red */
    .error-page__langs li:nth-child(2) { color: #fb923c; } /* DE — orange */
    .error-page__langs li:nth-child(3) { color: #fbbf24; } /* FR — amber */
    .error-page__langs li:nth-child(4) { color: #4ade80; } /* IT — green */
    .error-page__langs li:nth-child(5) { color: #22d3ee; } /* ES — teal */
    .error-page__langs li:nth-child(6) { color: #818cf8; } /* JA — indigo */
    .error-page__langs li:nth-child(7) { color: #c084fc; } /* zh-CN — violet */
}

/* ===== motion.css ===== */
/*! motion — hover transforms + scroll-driven parallax on editorial images.
 * Section reveal-on-scroll animations were removed at Marc's request;
 * the directional [data-reveal-variant] rules used to live here.
 */

/* Soft hover lift for cards / interactive elements */
.hover-lift {
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.hover-lift:hover {
  transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  html:not(.force-motion) .hover-lift:hover { transform: none !important; }
}
