/* ==========================================================================
   DoMoreWithSlides.com — JB Consulting
   Single stylesheet. Design tokens are sampled from logo.png:
     blue  #4F81BD   purple #8064A2   border/deep blue #385D8A
   See docs/brand-guidelines.md before changing any token.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Brand — sampled directly from the logo */
  --blue-500: #6d99cd;
  --blue-600: #4f81bd; /* logo blue */
  --blue-700: #3e6a9e;
  --blue-800: #385d8a; /* logo border */
  --blue-900: #28405f;

  --purple-500: #9a82b6;
  --purple-600: #8064a2; /* logo purple */
  --purple-700: #6b5189;
  --purple-800: #574170;

  /* Tinted neutrals — grey with a hint of the brand blue */
  --ink-900: #16202e;
  --ink-800: #24303f;
  --ink-700: #3c4859;
  --ink-600: #566274;
  --ink-500: #7b8697;
  --ink-300: #c3cad4;
  --ink-200: #dde3ea;
  --ink-100: #eef2f6;
  --ink-050: #f7f9fb;
  --white: #ffffff;

  --green-700: #1f7a52;
  --amber-700: #96601a;

  /* Semantic */
  --bg: var(--white);
  --bg-alt: var(--ink-050);
  --bg-sunken: var(--ink-100);
  --surface: var(--white);
  --border: var(--ink-200);
  --border-strong: var(--ink-300);
  --text: var(--ink-800);
  --text-muted: var(--ink-600);
  --text-heading: var(--ink-900);
  --link: var(--blue-800);
  --link-hover: var(--purple-700);
  --accent: var(--blue-600);
  --accent-alt: var(--purple-600);
  --shadow-color: 40 64 95;

  /* Type — system stack, no webfont request (keeps the site fast + offline-safe) */
  --font-sans: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial,
    sans-serif;
  --font-mono: "Cascadia Code", "SFMono-Regular", Consolas, "Liberation Mono",
    monospace;

  /* Fluid type scale */
  --fs-xs: 0.8125rem;
  --fs-sm: 0.9375rem;
  --fs-base: clamp(1rem, 0.97rem + 0.15vw, 1.0625rem);
  --fs-lg: clamp(1.125rem, 1.06rem + 0.3vw, 1.25rem);
  --fs-xl: clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);
  --fs-2xl: clamp(1.5rem, 1.3rem + 1vw, 2rem);
  --fs-3xl: clamp(1.875rem, 1.5rem + 1.7vw, 2.75rem);
  --fs-4xl: clamp(2.25rem, 1.6rem + 2.8vw, 3.75rem);

  /* Space */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4rem;
  --sp-9: 6rem;

  --radius-sm: 6px;
  --radius: 10px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  --measure: 68ch;
  --container: 1140px;
  --container-narrow: 780px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #12171f;
    --bg-alt: #171d27;
    --bg-sunken: #1d242f;
    --surface: #1b222c;
    --border: #2e3846;
    --border-strong: #414d5e;
    --text: #dde3ea;
    --text-muted: #a3aebc;
    --text-heading: #f2f5f8;
    --link: #8db0dc;
    --link-hover: #b79ed4;
    --accent: var(--blue-500);
    --accent-alt: var(--purple-500);
    --shadow-color: 0 0 0;
    --green-700: #5cc699;
    --amber-700: #e0ac62;
  }
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem; /* clears the sticky header on anchor jumps */
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3,
h4 {
  color: var(--text-heading);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.018em;
  margin: 0 0 var(--sp-4);
  text-wrap: balance;
}

h1 {
  font-size: var(--fs-4xl);
}
h2 {
  font-size: var(--fs-3xl);
}
h3 {
  font-size: var(--fs-xl);
  letter-spacing: -0.01em;
}
h4 {
  font-size: var(--fs-lg);
  letter-spacing: 0;
}

p,
ul,
ol,
dl {
  margin: 0 0 var(--sp-4);
}

p {
  max-width: var(--measure);
  text-wrap: pretty;
}

a {
  color: var(--link);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

a:hover {
  color: var(--link-hover);
}

:focus-visible {
  outline: 3px solid var(--accent-alt);
  outline-offset: 2px;
  border-radius: 3px;
}

img,
svg {
  max-width: 100%;
  height: auto;
}

strong {
  color: var(--text-heading);
  font-weight: 650;
}

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.1em 0.38em;
}

hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: var(--sp-7) 0;
}

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */

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


.lede {
  font-size: var(--fs-lg);
  color: var(--text-muted);
  max-width: 60ch;
}

.center {
  text-align: center;
}

.center p,
.center .lede {
  margin-inline: auto;
}

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-2);
  z-index: 100;
  padding: var(--sp-2) var(--sp-4);
  background: var(--surface);
  border: 2px solid var(--accent);
  border-radius: var(--radius-sm);
  transform: translateY(-160%);
  transition: transform 0.15s ease;
}

.skip-link:focus {
  transform: translateY(0);
}


/* --------------------------------------------------------------------------
   4. Header / nav
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: 4.25rem;
}

/* The brand box stays the height of the bar whatever the logo does, so the
   header keeps its 4.25rem: overflow does not contribute to layout. */
.brand {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  text-decoration: none;
  height: 4.25rem;
}

.brand img {
  display: block;
  height: 3rem;
  width: auto;
}

@media (max-width: 40rem) {
  .brand img {
    height: 2.5rem;
  }
}

/* Three zones, but only where all three fit: brand left, the Article/Slides
   switch centred in the bar, nav and CTA right.

   82rem is measured, not guessed. A 1fr/auto/1fr grid centres the switch
   against the bar itself rather than against the space left between its
   neighbours -- which is what "in the middle" has to mean when the two sides
   are different widths -- but it also forces the side columns equal, so the
   row costs twice the wider side. The nav is 534px, the logo 528px and the
   switch 160px: 534 * 2 + 160, plus 32 of gap and 48 of padding = 1308px.
   82rem is 1312px. Below it the CTA wraps to two lines, so the header falls
   back to the wrapping flex row the drawer depends on.

   Grow the switch, the logo or the nav wording and this has to be measured
   again -- enlarging the switch from 130px to 160px is what moved it here
   from 80rem. */
@media (min-width: 82rem) {
  .site-header__inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    /* The header alone breaks out of --container: the three zones do not fit
       inside 1140px, and "logo left" reads better against the viewport edge
       than against a centred column the logo starts well inside of. */
    max-width: none;
  }

  .brand {
    justify-self: start;
    align-items: flex-start;
  }

  .site-nav {
    justify-self: end;
  }

  .view-switch {
    justify-self: center;
    margin-left: 0;
  }

  /* The artwork is 2673x486 with transparent padding: ink starts 20.6% down,
     the wordmark occupies 23.7%-54.5% of the height, the mascot's box
     72.2%-99.8%. At 6rem tall with a -3px offset the wordmark lands centred
     in the bar and the top of the box sits just above the bottom border, so
     the box crosses it and hangs about 30px below. Change the height and the
     offset together or the wordmark drifts off centre. */
  .brand img {
    height: 6rem;
    margin-top: -3px;
    /* The overhang would otherwise sit over the page and swallow clicks meant
       for it. The anchor is still the full bar-height box, so the wordmark
       stays the link. */
    pointer-events: none;
  }
}

/* Article / Slides switch.
   Two segments; the one you are on is marked with aria-current and rendered as
   the selected half. It is navigation rather than a scripted control, so it
   works with JavaScript disabled, with middle-click and with "open in new tab".

   margin-left: auto keeps it hard against the nav in the wrapping flex row
   used below the nav breakpoint. Above that the grid puts it in the centre
   column and cancels the auto margin — see .site-header__inner.

   The decks carry the same control the other way round. Keep the two in step:
   slides/styles.css section 8. */
.view-switch {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: auto;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  padding: 3px;
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1;
}

/* Sized to sit level with the CTA button rather than looking like a caption
   next to it: --fs-sm and --sp-3 vertical bring the pill to about 45px, the
   button is 44px. It is a primary navigation control, not a footnote. */
.view-switch__opt {
  display: block;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
}

a.view-switch__opt:hover,
a.view-switch__opt:focus-visible {
  color: var(--link-hover);
  background: var(--bg-sunken);
}

/* Same gradient as .btn--primary: white clears 4.5:1 at both ends because it
   uses the -700 shades. See docs/brand-guidelines.md. */
.view-switch__opt[aria-current] {
  color: var(--white);
  background-image: linear-gradient(
    100deg,
    var(--blue-700) 0%,
    var(--purple-700) 100%
  );
}

@media (forced-colors: active) {
  .view-switch__opt[aria-current] {
    background-image: none;
    background: ButtonFace;
    color: ButtonText;
    border: 1px solid ButtonText;
    forced-color-adjust: none;
  }
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
}

.nav-toggle__bars {
  display: grid;
  gap: 3px;
  width: 1rem;
}

.nav-toggle__bars span {
  display: block;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
}

.site-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

.site-nav a {
  display: block;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
}

.site-nav a:hover {
  background: var(--bg-sunken);
  color: var(--link-hover);
}

.site-nav a[aria-current="page"] {
  color: var(--accent-alt);
  box-shadow: inset 0 -2px 0 var(--accent-alt);
}

.site-nav .btn {
  margin-left: var(--sp-2);
  /* `.site-nav a` is more specific than `.btn` and `.btn--primary`, so without
     these the header CTA silently loses the button treatment and inherits
     --text on the gradient, which measures 2.0:1 and fails AA. Re-assert the
     button's own look. White clears 4.5:1 at both ends of this gradient —
     see docs/brand-guidelines.md. */
  padding: 0.75rem 1.35rem;
  border-radius: var(--radius-pill);
  font-weight: 650;
  color: var(--white);
}

.site-nav .btn:hover {
  background-image: none;
}

/* Two-line CTA is what the header width budget above is protecting against. */
.site-nav a {
  white-space: nowrap;
}

/* Mobile nav.
   No-JS safe: the toggle button is hidden and the nav is a plain stacked list
   until the inline `js` class lands on <html>. Only then does the collapsed
   drawer behaviour switch on. Never make `.site-nav` display:none without
   gating it behind `.js`, or the nav becomes unreachable with JS disabled. */
@media (max-width: 55.99rem) {
  .site-header__inner {
    flex-wrap: wrap;
  }

  .nav-toggle {
    display: none;
  }

  .js .nav-toggle {
    display: inline-flex;
  }

  /* No-JS: stacked list, in flow, always visible. */
  .site-nav {
    flex-basis: 100%;
    padding-bottom: var(--sp-4);
  }

  /* JS present: collapse into an overlay drawer. */
  .js .site-nav {
    position: absolute;
    inset-inline: 0;
    top: 100%;
    flex-basis: auto;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 12px 24px -12px rgb(var(--shadow-color) / 0.28);
    padding: var(--sp-3) var(--sp-5) var(--sp-5);
    display: none;
  }

  .js .site-nav[data-open="true"] {
    display: block;
  }

  .site-nav ul {
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-1);
  }

  .site-nav a {
    padding: var(--sp-3);
    font-size: var(--fs-base);
  }

  .site-nav .btn {
    margin: var(--sp-3) 0 0;
    justify-content: center;
  }
}

@media (min-width: 56rem) {
  .nav-toggle,
  .js .nav-toggle {
    display: none;
  }
  .site-nav,
  .js .site-nav {
    display: block;
  }
}

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 650;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  padding: 0.75rem 1.35rem;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.15s ease, background 0.15s ease;
}

.btn:active {
  transform: translateY(1px);
}

/* Gradient runs logo-blue -> logo-purple. Both ends clear 4.5:1 with white. */
.btn--primary {
  background-image: linear-gradient(
    100deg,
    var(--blue-700) 0%,
    var(--purple-700) 100%
  );
  color: var(--white);
  box-shadow: 0 6px 18px -8px rgb(var(--shadow-color) / 0.5);
}

.btn--primary:hover {
  background-image: linear-gradient(
    100deg,
    var(--blue-800) 0%,
    var(--purple-800) 100%
  );
  color: var(--white);
  box-shadow: 0 10px 22px -8px rgb(var(--shadow-color) / 0.55);
}

.btn--secondary {
  background: transparent;
  color: var(--link);
  border-color: var(--border-strong);
}

.btn--secondary:hover {
  background: var(--bg-sunken);
  border-color: var(--accent);
  color: var(--link-hover);
}

.btn--lg {
  font-size: var(--fs-base);
  padding: 0.95rem 1.9rem;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
}

.center .btn-row {
  justify-content: center;
}

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;
  padding-block: var(--sp-8) var(--sp-8);
  background: linear-gradient(
    170deg,
    color-mix(in srgb, var(--blue-600) 9%, var(--bg)) 0%,
    var(--bg) 62%
  );
  border-bottom: 1px solid var(--border);
}

@media (min-width: 48rem) {
  .hero {
    padding-block: var(--sp-9);
  }
}

.hero__kicker {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--purple-700);
  background: color-mix(in srgb, var(--purple-600) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--purple-600) 30%, transparent);
  border-radius: var(--radius-pill);
  padding: 0.3rem 0.85rem;
  margin-bottom: var(--sp-5);
}

@media (prefers-color-scheme: dark) {
  .hero__kicker {
    color: var(--purple-500);
  }
}


/* --------------------------------------------------------------------------
   7. Slides

   The page *is* its deck. Each section holds one or more slides as inline
   <svg> — the same markup as assets/img/slides/<deck>/slideN.svg, and the same
   markup slides/*.html inlines. See docs/slide-images.md.

   Inline rather than <img> for one reason: an SVG behind an <img> is its own
   document and cannot see this file, so it would need a frozen copy of the
   palette and could never follow a token change. Inlined, every colour below
   is the page's own token, which is what makes a slide sit in the page instead
   of on top of it. The slides carry no background of their own.

   The heading of each section is a real <h1>/<h2>, visually hidden: the slide
   draws it, so showing it twice would be duplication, but the document still
   needs an outline for a screen reader and a search engine.
   -------------------------------------------------------------------------- */

/* Spacing between slides is mostly *inside* them: a slide's own canvas starts
   its first line at y=74 and usually ends by y=700 of 800, so roughly 100 units
   of margin at the bottom and 60 at the top come for free. The padding here
   sits on top of that, which is why it is small — it only has to separate the
   alternating background bands, not the content. */
.slide-section {
  padding-block: var(--sp-4);
  padding-inline: var(--sp-5);
  border-bottom: 1px solid var(--border);
}

.slide-section--alt {
  background: var(--bg-alt);
}

@media (min-width: 48rem) {
  .slide-section {
    padding-block: var(--sp-5);
  }
}

/* The track.
   It exists to cap the width, to centre the slide, to give the middle slide of
   the Fix section somewhere to hang `#pricing`, and — since v2 — to be the box
   the responsive layout measures. It does not scroll: a slide always fits the
   width it is given.

   assets/js/site.js observes this element, not the window: a slide is a
   self-contained component and what matters to it is the space it actually
   has, not how wide the browser happens to be. See docs/responsive-slides.md. */
.slide-track {
  max-width: var(--container);
  margin-inline: auto;
}

.slide-track + .slide-track {
  margin-top: var(--sp-2);
}

.slide-svg {
  display: block;
  width: 100%;
  height: auto;
}

/* --------------------------------------------------------------------------
   7a. The narrow layout

   A slide is a fixed 1280x800 canvas, so it scales with the space it is given
   and its text scales with it — at 360px that used to put the smallest type
   near 4px, and there was no fix, because SVG text does not re-wrap.

   It re-wraps now. Two mechanisms, and they are separate on purpose:

     Blocks move.  Anything repositionable is a <g data-lg="…"> carrying its
       own narrow offset in a `style` attribute as --nx/--ny. The rule below
       is the only place a transform is ever applied, and it applies only in
       the narrow layout — so the coordinates in the file stay absolute and
       stay the desktop layout, which is what tools/svg-to-pptx.ps1 reads.
       A block keeps its authored width when it moves, so the line breaks
       inside it still hold.

     Prose flows.  A line too wide for the 356-unit narrow column cannot move
       its way out of trouble, so it is not <text> at all: it is a
       <switch><foreignObject> block of real HTML that wraps to whatever width
       it is given, with a hand-broken <text> fallback behind it for the
       PowerPoint export. See docs/responsive-slides.md.

   With JavaScript off, neither happens and the page renders exactly as it did
   before — .is-narrow is only ever added by script. That is CLAUDE.md rule 8.
   -------------------------------------------------------------------------- */

/* --nx/--ny come from the slide, per group. The fallback keeps a group that
   declares neither exactly where the file puts it. */
.slide-svg.is-narrow [data-lg] {
  transform: translate(var(--nx, 0px), var(--ny, 0px));
}

/* Stacked, a slide is a phone-shaped column ~436 units wide. Rendered edge to
   edge on a 900px tablet that would be a 2x zoom of a phone screen, so it is
   capped: at any track wider than this the slide simply centres. */
.slide-svg.is-narrow {
  max-width: 460px;
  margin-inline: auto;
}

/* Flowing prose. foreignObject clips to its own box by default; every box here
   is sized for the taller narrow case, so this is a belt rather than braces. */
.slide-svg foreignObject {
  overflow: visible;
}

/* The flow inherits nothing from `text` — an HTML box uses `color`, not
   `fill`, which is why every colour class in section 8 sets both. */
/* The reset here is not defensive tidiness, it is load-bearing.

   A flow is an HTML box living inside the page, so every page rule that
   matches it applies — and the slide vocabulary and the page vocabulary share
   class names. `.lede` is both a slide class and a page class, and the page
   one carries `max-width: 60ch`, which silently broke the hero lede onto
   three lines the first time a flow used it. An SVG <text class="lede"> never
   noticed, because max-width means nothing to SVG text.

   So the box declares what it is rather than hoping nothing reaches it. Add a
   class to the slide vocabulary that also exists as a page class and this is
   what keeps the two apart. */
.slide-svg .flow {
  max-width: none;
  min-width: 0;
  margin: 0;
  padding: 0;
  text-align: left;
  font-family: var(--font-sans);
  line-height: 1.45;
  color: var(--text);
  -webkit-hyphens: none;
  hyphens: none;
}

/* No text-wrap in the wide layout, and that is deliberate.

   A flow's foreignObject is exactly as wide as the longest line the copy was
   hand-broken at, which is what makes the browser break it in the same place
   the writer did. `balance` overrides that — it evened "One deck. One URL." /
   "Every way someone wants to consume it." into two middling lines and threw
   away the rhetorical short first line. `pretty` is milder but can still pull
   a word across.

   Stacked, there is no authored break to preserve: the column is a different
   width and every line is chosen by the browser anyway. So `pretty` earns its
   place there and only there. */
.slide-svg.is-narrow .flow {
  text-wrap: pretty;
}

/* A flow sets its own leading, and these values are not free choices: they are
   the line pitches the slides were already hand-broken at, so a block that
   becomes a flow keeps the vertical rhythm it had. 60px display type at 1.45
   would open the hero headline from a 66-unit pitch to 87.

   The pitch is line-height x font-size. The first baseline sits
   (line-height / 2 + 0.41405) x font-size below the box top — the constant is
   Segoe UI's ascent and descent over its em, and docs/responsive-slides.md
   tabulates the result for every class, because that is the number you need
   to place a foreignObject against a grid drawn in baselines. */
.slide-svg .flow.h1 { line-height: 1.1; }
.slide-svg .flow.h2 { line-height: 1.15; }
.slide-svg .flow.lede { line-height: 1.38; }
.slide-svg .flow.punch { line-height: 1.6; }

/* The flow equivalent of text-anchor="middle". A centred <text> is anchored at
   its midpoint; a flow is a box, so it is centred by aligning inside one that
   straddles the same midpoint. Slides whose whole layout is centred — the two
   calls to action, and the "not sure which one you need" close — are built
   this way. */
.slide-svg .flow.mid { text-align: center; }

.slide-svg .flow strong { font-weight: 700; color: var(--text-heading); }
.slide-svg .flow em { font-style: italic; }

/* The narrow content column. Every stacked block is authored to 356 units, so
   one value covers the lot; a flow already narrower than this (a card's, at
   308) is untouched by it.

   site.js also sets the foreignObject's own width attribute, which is what
   getBBox reports and therefore what the stack is measured from. This is the
   half that makes the text wrap; both are needed. */
.slide-svg.is-narrow .flow {
  max-width: 356px;
}

/* Display sizes are set for a 1120-unit measure. In a 356-unit column a 60px
   headline gets about twelve characters to the line, so the display classes —
   and only those — step down. Scoped to .flow so that a <text> which does fit
   the narrow column keeps the size it was authored at. */
.slide-svg.is-narrow .flow.h1 { font-size: 34px; letter-spacing: -0.6px; }
.slide-svg.is-narrow .flow.h2 { font-size: 28px; letter-spacing: -0.4px; }
.slide-svg.is-narrow .flow.lede { font-size: 20px; }
.slide-svg.is-narrow .flow.punch { font-size: 20px; }
.slide-svg.is-narrow .flow.num { font-size: 40px; }

/* --------------------------------------------------------------------------
   8. The slide's own vocabulary

   These class names are the slide's, not the page's, and they are duplicated
   in two other places: slides/styles.css section 1 (pointing at the deck's
   --d-* tokens, which resolve to these same values) and inside each .svg file
   (so the file still previews correctly when opened on its own). Change one,
   change all three — docs/slide-images.md has the check.

   Every class that sets a colour sets `fill` and `color` together. The same
   class has to dress two different things now: an SVG <text>, which takes
   `fill`, and the HTML inside a <foreignObject>, which takes `color` and
   ignores `fill` entirely. Setting both is what lets one vocabulary do it —
   drop the `color` half and flowing text silently falls back to whatever it
   inherited, which is nearly right and therefore hard to spot.
   -------------------------------------------------------------------------- */

.slide-svg text {
  font-family: var(--font-sans);
  fill: var(--text);
}

/* The gradient stops are in the page's .slide-defs block, not inside a slide,
   so these cannot be scoped to .slide-svg. They were, and both stops fell back
   to SVG's default stop-color -- black -- which is what .grad text rendered as.
   The stops also carry literal stop-color attributes as a floor: CSS still wins,
   but a selector mistake now degrades to the light palette instead of black. */
.slide-defs .gs-a { stop-color: var(--accent); }
.slide-defs .gs-b { stop-color: var(--accent-alt); }
.slide-svg .grad { fill: url(#g-slide); }

/* The same treatment on flowing text. An HTML box cannot reference an SVG
   paint server, so the ramp is re-declared here as a CSS gradient clipped to
   the glyphs. The two tokens are the ones the .slide-defs stops use, so the
   colour is the same one either way.

   background-clip: text needs a transparent colour to show through, which
   means the text has no colour at all if the clip is not honoured. -webkit-
   first for Safari, then the standard property; forced-colors turns the whole
   thing off, because a gradient there is a disappearing act. */
.slide-svg .flow.grad,
.slide-svg .flow .grad {
  background-image: linear-gradient(90deg, var(--accent), var(--accent-alt));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

@media (forced-colors: active) {
  .slide-svg .flow.grad,
  .slide-svg .flow .grad {
    background-image: none;
    color: CanvasText;
  }
}

.slide-svg .eyebrow {
  font-size: 19px;
  font-weight: 700;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  fill: var(--accent-alt);
  color: var(--accent-alt);
}

.slide-svg .head {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  fill: var(--text-muted);
  color: var(--text-muted);
}

.slide-svg .h1 {
  font-size: 60px;
  font-weight: 700;
  letter-spacing: -1.2px;
  fill: var(--text-heading);
  color: var(--text-heading);
}

.slide-svg .h2 {
  font-size: 42px;
  font-weight: 700;
  letter-spacing: -0.8px;
  fill: var(--text-heading);
  color: var(--text-heading);
}

.slide-svg .h3 {
  font-size: 23px;
  font-weight: 700;
  fill: var(--text-heading);
  color: var(--text-heading);
}

.slide-svg .lede { font-size: 24px; fill: var(--text-muted); color: var(--text-muted); }
.slide-svg .body { font-size: 19px; }
.slide-svg .sm { font-size: 17px; }
.slide-svg .xs { font-size: 15px; }
.slide-svg .muted { fill: var(--text-muted); color: var(--text-muted); }
.slide-svg .b { font-weight: 700; fill: var(--text-heading); color: var(--text-heading); }
.slide-svg .i { font-style: italic; }

.slide-svg .punch {
  font-size: 22px;
  font-weight: 700;
  fill: var(--text-heading);
  color: var(--text-heading);
}

.slide-svg .num {
  font-size: 52px;
  font-weight: 700;
  fill: var(--accent-alt);
  color: var(--accent-alt);
}

.slide-svg .card {
  fill: var(--surface);
  stroke: var(--border);
  stroke-width: 1.5;
}

/* --bg-sunken, not --bg-alt: a panel has to stay visible on an alternating
   section, whose background already is --bg-alt. */
.slide-svg .panel {
  fill: var(--bg-sunken);
  stroke: var(--border);
  stroke-width: 1.5;
}

/* The icon tile in the top-right of a card. The gradient is the brand ramp at
   low alpha over whatever surface is behind it, so the tile follows the theme
   the way the slide does; --link clears 4.5:1 on the tint at both ends.
   The glyphs are built from <line> and <circle> only. No slide contains a
   <path>, and tools/svg-to-pptx.ps1 reads no shape other than those two, the
   rect and the text -- a path would export as nothing. */
.slide-svg .tile { fill: url(#g-slide-tile); stroke: none; }

.slide-svg .tile-glyph {
  fill: none;
  stroke: var(--link);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Exactly one featured tier, as on the pricing slide. */
.slide-svg .feature { stroke: var(--accent-alt); stroke-width: 3; }

.slide-svg .rule { stroke: var(--accent); stroke-width: 5; stroke-linecap: round; }
.slide-svg .hair { stroke: var(--border); stroke-width: 1.5; }
.slide-svg .good { fill: var(--green-700); color: var(--green-700); }
.slide-svg .warn { fill: var(--amber-700); color: var(--amber-700); }
.slide-svg .accent { fill: var(--link); color: var(--link); }
.slide-svg .qbar { fill: var(--accent-alt); }
.slide-svg .tbl { font-size: 14px; }
.slide-svg .tblhead { font-size: 14px; font-weight: 700; fill: var(--text-heading); color: var(--text-heading); }

.slide-svg .pill-live { fill: none; stroke: var(--green-700); stroke-width: 1.5; }
.slide-svg .pill-soon { fill: none; stroke: var(--amber-700); stroke-width: 1.5; }

.slide-svg .pill-text-live {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.8px;
  fill: var(--green-700);
  color: var(--green-700);
}

.slide-svg .pill-text-soon {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.8px;
  fill: var(--amber-700);
  color: var(--amber-700);
}

/* The call-to-action shapes. This gradient is the -700 shades and does not move
   between themes: white clears 4.5:1 at both ends only on those. Same reasoning
   as .btn--primary — see docs/brand-guidelines.md. */
.slide-svg .btn-fill { fill: url(#g-slide-btn); }

.slide-svg .btn-ghost {
  fill: none;
  stroke: var(--border-strong);
  stroke-width: 1.5;
}

.slide-svg .btn-label { font-size: 20px; font-weight: 700; fill: var(--white); color: var(--white); }
.slide-svg .btn-label-ghost { font-size: 20px; font-weight: 700; fill: var(--link); color: var(--link); }

/* Links drawn into a slide. The <text> inside gives each its accessible name. */
.slide-svg a {
  cursor: pointer;
}

.slide-svg a:focus-visible {
  outline: 3px solid var(--accent-alt);
  outline-offset: 3px;
  border-radius: 4px;
}

.slide-svg a:hover .btn-label-ghost,
.slide-svg a:focus-visible .btn-label-ghost {
  fill: var(--link-hover);
}

.slide-svg a:hover .btn-ghost,
.slide-svg a:focus-visible .btn-ghost {
  stroke: var(--accent-alt);
}

/* Hidden, but it must stay in the layout tree: display:none on an ancestor
   makes a referenced gradient unresolvable in some engines. */
.slide-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

@media print {
  .slide-track {
    break-inside: avoid;
  }
}

/* --------------------------------------------------------------------------
   9. Footer
   -------------------------------------------------------------------------- */

.site-footer {
  background: var(--bg-alt);
  border-top: 1px solid var(--border);
  padding-block: var(--sp-7) var(--sp-5);
  font-size: var(--fs-sm);
}

.site-footer__grid {
  display: grid;
  gap: var(--sp-6);
  margin-bottom: var(--sp-6);
}

@media (min-width: 48rem) {
  .site-footer__grid {
    grid-template-columns: 1.4fr 1fr 1fr;
  }
}

.site-footer h4 {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--text-muted);
  margin-bottom: var(--sp-3);
}

.site-footer ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--sp-2);
}

.site-footer a {
  font-weight: 600;
  text-decoration: none;
}

.site-footer a:hover {
  text-decoration: underline;
}

.site-footer p {
  color: var(--text-muted);
}

.site-footer__bar {
  border-top: 1px solid var(--border);
  padding-top: var(--sp-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  justify-content: space-between;
  color: var(--text-muted);
}

.site-footer__bar p {
  margin: 0;
}

/* --------------------------------------------------------------------------
   10. Utilities
   -------------------------------------------------------------------------- */

.mb-0 {
  margin-bottom: 0;
}

.mt-6 {
  margin-top: var(--sp-6);
}

/* Present for a screen reader and a search engine, absent from the page.
   Every .slide-section's heading uses this: the slide draws the heading, so
   showing it again as page type would be the same words twice. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Windows High Contrast / forced-colors.
   Everything gradient-based falls back to system colours. The slides are the
   interesting case: they are inline SVG, so the forced palette *does* reach
   into them — unlike an SVG behind an <img>, which is a separate document the
   override never enters. That is one of the reasons they are inlined.
   `fill` and `stroke` are not auto-forced, so the slide's own roles are
   re-pointed at system colours by hand below. */
@media (forced-colors: active) {
  .btn--primary,
  .btn--primary:hover {
    background-image: none;
    background: ButtonFace;
    color: ButtonText;
    border: 1px solid ButtonText;
    forced-color-adjust: none;
  }

  .hero,
  .hero__kicker {
    background-image: none;
    background: Canvas;
  }

  .slide-svg text,
  .slide-svg .b,
  .slide-svg .h1,
  .slide-svg .h2,
  .slide-svg .h3,
  .slide-svg .punch,
  .slide-svg .tblhead {
    fill: CanvasText;
  }

  .slide-svg .muted,
  .slide-svg .lede,
  .slide-svg .head,
  .slide-svg .eyebrow,
  .slide-svg .num,
  .slide-svg .accent,
  .slide-svg .good,
  .slide-svg .warn,
  .slide-svg .grad,
  .slide-svg .pill-text-live,
  .slide-svg .pill-text-soon {
    fill: CanvasText;
  }

  /* Flowing text is HTML, so `fill` above never reaches it. Every one of these
     classes now dresses two things and only one of them listens to `fill` —
     without this, the whole of a slide's prose keeps its own colour in a
     forced-colours theme, which is exactly the failure the mode exists to
     prevent. The .grad case is handled separately: it has to give up its
     gradient as well as its colour. */
  .slide-svg .flow,
  .slide-svg .flow strong,
  .slide-svg .flow em {
    color: CanvasText;
  }

  /* Surfaces keep their outline so a card is still a card. */
  .slide-svg .card,
  .slide-svg .panel {
    fill: Canvas;
    stroke: CanvasText;
  }

  .slide-svg .rule,
  .slide-svg .hair,
  .slide-svg .qbar,
  .slide-svg .feature,
  .slide-svg .pill-live,
  .slide-svg .pill-soon {
    stroke: CanvasText;
    fill: CanvasText;
  }

  /* The tile loses its gradient the way the CTA shapes do; an outline is what
     keeps it reading as a tile, and the glyph inside it as a glyph. */
  .slide-svg .tile {
    fill: Canvas;
    stroke: CanvasText;
    stroke-width: 1.5;
  }

  .slide-svg .tile-glyph {
    stroke: CanvasText;
  }

  /* The call-to-action shapes lose their gradient; the outline carries them. */
  .slide-svg .btn-fill {
    fill: ButtonFace;
    stroke: ButtonText;
    stroke-width: 1.5;
  }

  .slide-svg .btn-ghost {
    stroke: ButtonText;
  }

  .slide-svg .btn-label,
  .slide-svg .btn-label-ghost {
    fill: ButtonText;
  }

  :focus-visible {
    outline: 3px solid Highlight;
  }
}

@media print {
  .site-header,
  .site-footer,
  .nav-toggle,
  .btn {
    display: none !important;
  }
  body {
    color: #000;
    background: #fff;
  }
}
