/* Self-hosted Roboto (task 2 of homepage-build-spec.md). Google Fonts by
   link is rejected — a third-party request on every page load for a
   project whose pitch is not depending on other people's infrastructure.
   One variable-weight file covers the whole 400-800 range the design
   uses, in place of five static-weight files. Provenance and the SIL OFL
   1.1 text are in roboto-LICENSE.txt beside the font file. */
@font-face {
  font-family: "Roboto";
  font-style: normal;
  font-display: swap;
  font-weight: 400 800;
  src: url("/assets/roboto-variable-latin-wght-normal.1404ca348b.woff2")
    format("woff2-variations");
  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;
}

:root {
  color-scheme: light dark;
  --fg: #1a1a2e;
  --bg: #ffffff;
  --muted: #5a5a72;
  --accent: #2f2f6e;
  --border: #e2e2ea;
}

@media (prefers-color-scheme: dark) {
  :root {
    --fg: #eaeaf4;
    --bg: #14141f;
    --muted: #a0a0b8;
    --accent: #8f8fe8;
    --border: #2a2a3c;
  }
}

/* ============================================================
   Homepage design tokens — Penpot file "Dojo NG", Page 1
   (desktop board, read 2026-08-22). See homepage-build-spec.md,
   "The token layer".

   Namespace is --dj-site-*, never --dj-*: that prefix belongs to
   the component library, and the site will eventually load real
   components that read it. Colliding would be a nasty bug to find.

   These are literal design values, not theme-reactive — the
   homepage's bands (e.g. the gray-900 dark band) are fixed colors
   in the design, not a response to prefers-color-scheme.
   ============================================================ */
:root {
  /* Colors: Tailwind's default gray and blue ramps plus one green,
     named by role since that's what the design uses them for. */
  --dj-site-white: #ffffff;
  --dj-site-gray-50: #f9fafb;
  --dj-site-gray-200: #e5e7eb;
  --dj-site-gray-300: #d1d5db;
  --dj-site-gray-400: #9ca3af;
  --dj-site-gray-500: #6b7280;
  --dj-site-gray-600: #4b5563;
  --dj-site-gray-700: #374151;
  --dj-site-gray-800: #1f2937;
  --dj-site-gray-900: #111827;
  --dj-site-blue-200: #bfdbfe;
  --dj-site-blue-300: #93c5fd;
  --dj-site-blue-500: #3b82f6;
  --dj-site-blue-600: #2563eb;
  --dj-site-blue-700: #1d4ed8;
  --dj-site-green-600: #16a34a;

  /* Type: Roboto only. Weights the design actually uses. */
  --dj-site-font-family: "Roboto", system-ui, -apple-system, "Segoe UI",
    sans-serif;
  --dj-site-font-regular: 400;
  --dj-site-font-medium: 500;
  --dj-site-font-semibold: 600;
  --dj-site-font-bold: 700;
  --dj-site-font-extrabold: 800;

  /* Type scale. The design has 14 literal sizes one px apart in
     places no reader could tell apart — collapsed to 8 steps here.
     Sizes that don't land exactly on a step (the rounding the spec
     asks to record):
       xs   12px  <- 11 (+1px), 12 (exact)
       sm   14px  <- 13 (+1px), 14 (exact), 15 (-1px)
       base 16px  <- 16 (exact)
       md   18px  <- 17 (+1px), 18 (exact), 19 (-1px, the Hero subhead)
       lg   20px  <- 20 (exact)
       xl   24px  <- 24 (exact)
       2xl  32px  <- 30 (+2px, the FinalCTA heading — the largest
                     rounding in the scale), 32 (exact)
       3xl  56px  <- 56 (exact, the Hero h1)
     The Hero eyebrow (13/700, line-height 1.2, no letter-spacing, no
     text-transform) rounds into --dj-site-text-sm; its line-height is
     set locally where it's used, not tokenized, since no other text
     in the design shares it. */
  --dj-site-text-xs: 0.75rem;
  --dj-site-text-sm: 0.875rem;
  --dj-site-text-base: 1rem;
  --dj-site-text-md: 1.125rem;
  --dj-site-text-lg: 1.25rem;
  --dj-site-text-xl: 1.5rem;
  --dj-site-text-2xl: 2rem;
  --dj-site-text-3xl: 3.5rem;

  --dj-site-leading-tight: 1.2;
  --dj-site-leading-snug: 1.3;
  --dj-site-leading-normal: 1.5;

  /* Spacing: section padding (96/88/72/64 desktop, 56/48 mobile) and
     gaps (14/16/20/24/28/32/40), as used in the design. Already a
     regular set — nothing to collapse. */
  --dj-site-space-14: 0.875rem;
  --dj-site-space-16: 1rem;
  --dj-site-space-20: 1.25rem;
  --dj-site-space-24: 1.5rem;
  --dj-site-space-28: 1.75rem;
  --dj-site-space-32: 2rem;
  --dj-site-space-40: 2.5rem;
  --dj-site-space-48: 3rem;
  --dj-site-space-56: 3.5rem;
  --dj-site-space-64: 4rem;
  --dj-site-space-72: 4.5rem;
  --dj-site-space-88: 5.5rem;
  --dj-site-space-96: 6rem;

  /* Content widths. Sections are full-bleed; their contents aren't. */
  --dj-site-width-container: 78rem; /* 1248px — the footer row */
  --dj-site-width-grid: 65rem; /* 1040px — three/four-up strips (1024-1040 in the design) */
  --dj-site-width-card: 50rem; /* 800px — proof-card, pick-code-card */
  --dj-site-width-prose: 43.75rem; /* 700px — prose blocks (620-760 in the design) */
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  color: var(--fg);
  background: var(--bg);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  line-height: 1.5;
}

/* The measure content pages opt into (docs, components, migrate, blog,
   community, 404). Same values `header, main, footer` all carried
   directly before task 3 — moving them here, unchanged, is what keeps
   those pages rendering at the same measure. The homepage's `<main>`
   opts out (see `fullBleed` in base.njk) so its sections can go
   full-bleed with their own backgrounds. */
.page-container {
  max-width: 48rem;
  margin: 0 auto;
  padding: 1.5rem;
}

a {
  color: var(--accent);
}

/* Skip link (task 20, a11y pass) — sitewide. Visually hidden until it
   receives keyboard focus, which is when it's actually needed: a keyboard
   user who'd otherwise have to tab through the whole nav before reaching
   content. */
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--dj-site-gray-900);
  color: var(--dj-site-white);
  text-decoration: none;
  font-weight: var(--dj-site-font-semibold);
}

.skip-link:focus {
  top: 0;
}

/* Visually hidden but present for assistive tech — e.g. a <table> caption
   that would be redundant on-screen next to a visible caption paragraph. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Persistent work-in-progress marker (task 3). Replaces the old
   paragraph-on-an-empty-page version — see homepage-build-spec.md,
   decision 2. Sitewide, not homepage-only. */
.wip-banner {
  margin: 0;
  padding: 0.5rem var(--dj-site-space-20);
  background: var(--dj-site-gray-900);
  color: var(--dj-site-gray-300);
  font-size: var(--dj-site-text-xs);
  text-align: center;
}

.wip-banner a {
  color: var(--dj-site-white);
}

/* Site nav (task 3 adds the two right-side buttons, task 4 builds the
   rest: inlined logo, links, mobile disclosure). Full-bleed and
   sitewide — replaces the old measure-constrained `nav`. */
.site-nav {
  position: relative;
  display: flex;
  align-items: center;
  padding: 0 var(--dj-site-space-20);
  min-height: 60px;
  background: var(--dj-site-white);
  border-bottom: 1px solid var(--dj-site-gray-200);
}

@media (min-width: 1360px) {
  .site-nav {
    padding: 0 var(--dj-site-space-96);
    min-height: 88px;
  }
}

.site-nav__logo {
  display: inline-flex;
  align-items: center;
}

.site-nav__logo svg {
  display: block;
  width: 98px;
  height: auto;
}

@media (min-width: 1360px) {
  .site-nav__logo svg {
    width: 130px;
  }
}

/* dj-nav (task N10, nav-collapse-spec.md) replaces both the old desktop-only <nav> and the
   mobile <details> disclosure below with one component: its own container-query threshold
   picks the arrangement, so there is never a second, duplicated copy of the link list in the
   DOM the way the hand-rolled version had. --dj-nav-collapsed set directly here is a consumer
   override (nav-collapse-spec.md's design decision: a value set on the host always wins over
   the component's own 45rem default) — 1360px, not dj-nav's own 45rem, because that is the
   MEASURED minimum for logo + links + site-nav__actions + padding to fit on one row (bisected
   live 2026-09-09: 1320px still overflowed by ~5px each side, 1360px cleared with ~15px to
   spare — re-measured from the original 1200px, which predates both the GitHub link added to
   dj-nav's own list and the actions cluster growing to three buttons, either of which alone
   would have made 1200px too narrow; see the nav bug in memory.md, 2026-08-22, for the
   original tuning this supersedes); the component's own default was tuned for iPad-portrait
   widths, not for accommodating this row's action-button cluster. @media, not @container: this
   is a full-width header, not a nested sidebar, so there is nothing for container-awareness to
   buy here, and @media matches every other breakpoint in this file. */
/* flex: 1 1 0% applies in BOTH arrangements, not just expanded: :host has container-type:
   inline-size (dj-nav's own container-query mechanism), which contains its inline axis — its
   auto/content-based width computes as if it had no content at all, in either arrangement. As
   a flex item with no explicit size that's just width: 0, collapsed or not — it needs its
   width handed to it by the flex parent instead of asking for its own content's size. Collapsed,
   that width is what the dropdown panel's `inset-inline: 0` (in dj-nav's own shadow styles) has
   to span: the panel positions against :host's own padding box, so a 0-width host meant a
   0-width, unreadable panel wrapped one letter per line — not just a centering bug like the
   expanded case below. text-align: right (an inherited property, so it crosses the shadow
   boundary into :host's own rendered content) puts the trigger back on the right edge of that
   now-wide box, standing in for the old margin-left: auto, which only worked when the host was
   small enough to push. */
.site-nav__nav {
  --dj-nav-collapsed: 1;
  --dj-nav-gap: var(--dj-site-space-28);
  flex: 1 1 0%;
}

.site-nav__nav[collapsed] {
  text-align: right;
}

/* text-align is inherited, so the trigger button (a sibling of #panel, not part of "nav")
   picks up the right-align above and sits at the host's right edge as intended — but it also
   reaches the panel's actual link text, which the old dropdown always rendered left-aligned.
   ::part(nav) only reaches the <nav> wrapper (shared with the expanded arrangement, where this
   is a no-op since nothing sets text-align: right there), so this puts the links back without
   touching the trigger. */
.site-nav__nav::part(nav) {
  text-align: left;
}

@media (min-width: 1360px) {
  .site-nav__nav {
    --dj-nav-collapsed: 0;
    /* logo(130) + links(~637, all 8 including GitHub) + actions(~356, all 3 buttons) +
       padding(192) needs ~1315px just to fit with no slack, which is why this breakpoint is
       1360px, not the original 1200px (re-measured 2026-09-09 — see the comment above
       dj-nav's tag in base.njk for the full story). */
  }

  .site-nav__nav:not([collapsed])::part(nav) {
    justify-content: center;
  }
}

.site-nav__nav a {
  color: var(--dj-site-gray-700);
  text-decoration: none;
}

.site-nav__nav a:hover {
  color: var(--dj-site-gray-900);
}

/* Expanded (inline, desktop): matches the old .site-nav__links typography exactly. */
.site-nav__nav:not([collapsed]) a {
  font-size: var(--dj-site-text-sm);
  font-weight: var(--dj-site-font-medium);
}

/* Collapsed (dropdown panel, mobile/tablet): matches the old disclosure's own nav typography —
   deliberately one size up from the inline links, for touch targets. */
.site-nav__nav[collapsed] a {
  font-size: var(--dj-site-text-md);
  font-weight: var(--dj-site-font-medium);
}

/* The dropdown panel itself: dj-nav's own default is a full border box (background +
   border on all four sides); the old dropdown was flush with the viewport edges with only a
   top divider, so override to match exactly rather than accept the component's generic default. */
.site-nav__nav::part(panel) {
  background: var(--dj-site-white);
  border: none;
  border-top: 1px solid var(--dj-site-gray-200);
}

/* dj-nav's own .panel--dropdown is position: absolute against :host — fine when :host spans
   the row, but :host only spans the space after the logo (flex: 1 1 0% above), and
   container-type: inline-size makes :host a containing block for absolutely positioned
   descendants no matter what position it's given, so there's no way to widen that box to
   reach under the logo. Confirmed 2026-09-09: position: fixed is the one thing containment
   doesn't trap, so the panel escapes past :host and .site-nav both, straight to the viewport —
   which is exactly the old dropdown's own "flush with the viewport edges" behavior, just
   reached a different way. --site-nav-bottom (base.njk, set via ResizeObserver) stands in for
   the top: 100% dj-nav's own rule would have given it relative to :host's row. */
.site-nav__nav[collapsed]::part(panel) {
  position: fixed;
  inset-inline: 0;
  top: var(--site-nav-bottom, 60px);
}

.site-nav__actions {
  display: none;
  align-items: center;
  gap: var(--dj-site-space-14);
}

@media (min-width: 1360px) {
  .site-nav__actions {
    display: flex;
  }
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
  font-size: var(--dj-site-text-sm);
  font-weight: var(--dj-site-font-semibold);
  text-decoration: none;
  white-space: nowrap;
  border: 1px solid transparent;
}

/* Explicit rather than relying on the browser default (task 20, a11y pass):
   the default ring's exact color isn't guaranteed by spec, and every other
   .btn variant sits on white/light backgrounds where blue-600 is safe
   (verified 5.17:1+ against every background a .btn appears on), but
   .btn--outline-light sits on the blue-600 FinalCTA band itself, where a
   blue-ish default ring risks blending into the background it's on. */
.btn:focus-visible {
  outline: 2px solid var(--dj-site-blue-600);
  outline-offset: 2px;
}

.btn--outline-light:focus-visible {
  outline-color: var(--dj-site-white);
}

.btn--primary {
  background: var(--dj-site-blue-600);
  color: var(--dj-site-white);
}

.btn--primary:hover {
  background: var(--dj-site-blue-700);
}

.btn--ghost {
  background: none;
  color: var(--dj-site-gray-800);
  border-color: var(--dj-site-gray-300);
}

.btn--ghost:hover {
  color: var(--dj-site-gray-900);
}

.btn--lg {
  padding: 0.875rem 1.75rem;
  font-size: var(--dj-site-text-base);
}

/* The old hand-rolled mobile <details> disclosure (task 4) and its hamburger/dropdown CSS are
   gone — dj-nav (above) owns this now, trigger, panel, and all. */

code {
  background: var(--border);
  padding: 0.1em 0.35em;
  border-radius: 0.25em;
}

pre {
  overflow-x: auto;
  background: var(--border);
  padding: 1rem;
  border-radius: 0.5em;
}

pre code {
  background: none;
  padding: 0;
}

/* ============================================================
   Homepage sections (Phase 2). Each is full-bleed with its own
   background; contents are constrained through the four
   --dj-site-width-* tokens rather than section-specific literals —
   per "The token layer" in homepage-build-spec.md, a strip measured
   at 1024 or 1032 in Penpot still uses --dj-site-width-grid (1040),
   and a 620-760 prose block uses --dj-site-width-prose (700).
   ============================================================ */

.section-hero {
  background: var(--dj-site-white);
  padding: var(--dj-site-space-96) var(--dj-site-space-24)
    var(--dj-site-space-64);
  text-align: center;
}

.hero-eyebrow {
  margin: 0 0 var(--dj-site-space-20);
  font-size: var(--dj-site-text-sm);
  font-weight: var(--dj-site-font-bold);
  line-height: 1.2;
  color: var(--dj-site-blue-600);
}

.hero-title {
  margin: 0 0 var(--dj-site-space-20);
  font-size: var(--dj-site-text-3xl);
  font-weight: var(--dj-site-font-extrabold);
  line-height: 1.1;
  color: var(--dj-site-gray-900);
}

.hero-subhead {
  max-width: var(--dj-site-width-prose);
  margin: 0 auto var(--dj-site-space-32);
  font-size: var(--dj-site-text-md);
  line-height: 1.2;
  color: var(--dj-site-gray-600);
}

.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--dj-site-space-16);
  margin-bottom: var(--dj-site-space-40);
}

.hero-frameworks {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--dj-site-space-24);
  font-size: var(--dj-site-text-sm);
  font-weight: var(--dj-site-font-semibold);
  color: var(--dj-site-gray-500);
}

/* Framework marks (Bill, 2026-08-22). Five brand palettes at full
   strength fight a hero built out of one blue and a gray ramp, so they
   sit desaturated next to the gray-500 names and go full color on
   hover, which is the one state that has a reason to be emphatic. The
   marks are decoration and carry aria-hidden: the name is already text
   beside each one. */
.hero-framework {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.hero-framework svg {
  flex: none;
  filter: saturate(0.75);
  opacity: 0.9;
  transition: filter 160ms ease, opacity 160ms ease;
}

.hero-framework:hover svg {
  filter: none;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .hero-framework svg {
    transition: none;
  }
}

.lattice-divider {
  line-height: 0;
}

.lattice-divider svg {
  display: block;
  width: 100%;
  height: 40px;
}

@media (min-width: 768px) {
  .lattice-divider svg {
    height: 48px;
  }
}

.lattice-divider svg.is-desktop {
  display: none;
}

@media (min-width: 768px) {
  .lattice-divider svg.is-desktop {
    display: block;
  }
  .lattice-divider svg.is-mobile {
    display: none;
  }
}

.lattice-divider--cta {
  background: var(--dj-site-blue-600);
}

.section-problem {
  background: var(--dj-site-gray-50);
  padding: var(--dj-site-space-72) var(--dj-site-space-24);
}

.section-problem p {
  max-width: var(--dj-site-width-prose);
  margin: 0 auto;
  text-align: center;
  font-size: var(--dj-site-text-lg);
  font-weight: var(--dj-site-font-medium);
  line-height: 1.5;
  color: var(--dj-site-gray-800);
}

.section-how {
  background: var(--dj-site-white);
  padding: var(--dj-site-space-96) var(--dj-site-space-24);
}

.section-how h2 {
  margin: 0 0 var(--dj-site-space-40);
  text-align: center;
  font-size: var(--dj-site-text-2xl);
  font-weight: var(--dj-site-font-extrabold);
  line-height: 1.2;
  color: var(--dj-site-gray-900);
}

.how-steps {
  max-width: var(--dj-site-width-grid);
  margin: 0 auto;
  display: grid;
  /* Intrinsic sizing (task 19): however many 260px-minimum columns fit, no
     breakpoint to re-decide when a section changes. 260px keeps each
     step's ~272px body text from wrapping to uncomfortably short lines. */
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--dj-site-space-40);
}

.how-step-number {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-2xl);
  font-weight: var(--dj-site-font-bold);
  line-height: 1.2;
  /* Penpot draws these blue-200, which is 1.4:1 on white — below even the
     large-text threshold, and they ship as real <p> text. Bill's call
     2026-08-22: aria-hidden them in the markup AND darken to blue-500, which is
     3.7:1 and passes large-text AA at 32px bold while keeping the soft blue the
     design wanted. blue-300 and blue-400 both still fail; blue-500 is the first
     step that works. The Penpot boards were NOT changed here — the numerals are
     decorative and the design's tint is a deliberate look, so the site darkens
     them for contrast and the design keeps its own. */
  color: var(--dj-site-blue-500);
}

.how-step-title {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-md);
  font-weight: var(--dj-site-font-bold);
  line-height: 1.2;
  color: var(--dj-site-gray-900);
}

.how-step-body {
  margin: 0;
  font-size: var(--dj-site-text-sm);
  line-height: 1.5;
  color: var(--dj-site-gray-600);
}

/* Plugins (task 9) */
.section-plugins {
  background: var(--dj-site-gray-50);
  padding: var(--dj-site-space-96) var(--dj-site-space-24);
}

.section-plugins h2 {
  margin: 0 0 var(--dj-site-space-16);
  text-align: center;
  font-size: var(--dj-site-text-2xl);
  font-weight: var(--dj-site-font-extrabold);
  line-height: 1.2;
  color: var(--dj-site-gray-900);
}

.section-plugins .section-sub {
  max-width: var(--dj-site-width-prose);
  margin: 0 auto var(--dj-site-space-40);
  text-align: center;
  font-size: var(--dj-site-text-base);
  line-height: 1.5;
  color: var(--dj-site-gray-500);
}

.plugin-strip {
  max-width: var(--dj-site-width-grid);
  margin: 0 auto;
  display: grid;
  /* Intrinsic sizing (task 19), same reasoning as .how-steps. The Penpot
     design draws a vertical rule between items, which only means something
     when the column count is exactly 3 — at other column counts (auto-fit
     reflows to 1 or 2 depending on width) "before the first item in its row"
     isn't something CSS can express without going back to a breakpoint
     ladder, which is exactly what this task removes. Dropped; the gap alone
     carries the separation, same as .how-steps already does. */
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--dj-site-space-40);
}

.plugin-item h3 {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-md);
  font-weight: var(--dj-site-font-bold);
  line-height: 1.2;
  color: var(--dj-site-gray-900);
}

.plugin-item p {
  margin: 0 0 var(--dj-site-space-24);
  font-size: var(--dj-site-text-base);
  line-height: 1.5;
  color: var(--dj-site-gray-600);
}

.plugin-item a {
  font-size: var(--dj-site-text-sm);
  font-weight: var(--dj-site-font-semibold);
  color: var(--dj-site-blue-600);
  text-decoration: none;
}

.plugin-item a:hover {
  text-decoration: underline;
}

/* BuiltForRealApps (task 10) */
.section-built {
  background: var(--dj-site-gray-900);
  padding: var(--dj-site-space-96) var(--dj-site-space-24);
}

.section-built h2 {
  margin: 0 0 var(--dj-site-space-16);
  text-align: center;
  font-size: var(--dj-site-text-2xl);
  font-weight: var(--dj-site-font-extrabold);
  line-height: 1.2;
  color: var(--dj-site-white);
}

.section-built .section-sub {
  max-width: 35rem; /* 560px, the design's own value for this one sub */
  margin: 0 auto var(--dj-site-space-40);
  text-align: center;
  font-size: var(--dj-site-text-base);
  line-height: 1.2;
  color: var(--dj-site-gray-400);
}

.built-layout {
  max-width: var(--dj-site-width-grid);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--dj-site-space-32);
}

@media (min-width: 768px) {
  .built-layout {
    grid-template-columns: 1fr 1fr;
  }
}

.built-feature {
  background: var(--dj-site-blue-700);
  border-radius: 0.5rem;
  padding: var(--dj-site-space-32);
}

.built-feature .built-eyebrow {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-xs);
  font-weight: var(--dj-site-font-bold);
  line-height: 1.2;
  /* Was blue-500 on blue-700 in Penpot — 1.8:1, a real AA failure on the one
     label making an accessibility claim. Substituted blue-200 (4.7:1) at build
     time and flagged it. CONFIRMED BY BILL 2026-08-22 and the Penpot boards were
     corrected to match, so the design and this file now agree; nothing to revert.
     The mobile board had a second, different version of the same bug — the card
     body was gray-400 on blue-700 (2.6:1) where desktop had gray-300 (4.6:1) —
     fixed in the same pass. */
  color: var(--dj-site-blue-200);
}

.built-feature h3 {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-xl);
  font-weight: var(--dj-site-font-bold);
  line-height: 1.2;
  color: var(--dj-site-white);
}

.built-feature p {
  margin: 0;
  font-size: var(--dj-site-text-md);
  line-height: 1.6;
  color: var(--dj-site-gray-300);
}

.built-list {
  display: flex;
  flex-direction: column;
  gap: var(--dj-site-space-32);
  justify-content: center;
}

.built-list h3 {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-md);
  font-weight: var(--dj-site-font-bold);
  line-height: 1.2;
  color: var(--dj-site-gray-50);
}

.built-list p {
  margin: 0;
  font-size: var(--dj-site-text-md);
  line-height: 1.5;
  color: var(--dj-site-gray-400);
}

/* ComingFrom (task 11) */
.section-coming {
  background: var(--dj-site-gray-50);
  padding: var(--dj-site-space-96) var(--dj-site-space-24);
}

.section-coming h2 {
  margin: 0 0 var(--dj-site-space-16);
  text-align: center;
  font-size: var(--dj-site-text-2xl);
  font-weight: var(--dj-site-font-extrabold);
  line-height: 1.2;
  color: var(--dj-site-gray-900);
}

.section-coming .section-sub {
  max-width: var(--dj-site-width-prose);
  margin: 0 auto var(--dj-site-space-40);
  text-align: center;
  font-size: var(--dj-site-text-base);
  line-height: 1.2;
  color: var(--dj-site-gray-500);
}

.migrate-strip {
  max-width: var(--dj-site-width-grid);
  margin: 0 auto;
  display: grid;
  /* Intrinsic sizing (task 19), same reasoning as .plugin-strip — the
     vertical-rule divider is dropped for the same reason (it only makes
     sense at exactly 3 columns, and auto-fit doesn't hold a fixed count). */
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--dj-site-space-40);
}

.migrate-item h3 {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-md);
  font-weight: var(--dj-site-font-bold);
  line-height: 1.2;
  color: var(--dj-site-gray-900);
}

.migrate-item p {
  margin: 0 0 var(--dj-site-space-24);
  font-size: var(--dj-site-text-base);
  line-height: 1.5;
  color: var(--dj-site-gray-600);
}

.migrate-item a {
  font-size: var(--dj-site-text-sm);
  font-weight: var(--dj-site-font-semibold);
  color: var(--dj-site-blue-600);
  text-decoration: none;
}

.migrate-item a:hover {
  text-decoration: underline;
}

/* SocialProof (task 12) */
.section-social {
  background: var(--dj-site-white);
  padding: var(--dj-site-space-72) var(--dj-site-space-24);
  text-align: center;
}

.social-line {
  max-width: var(--dj-site-width-prose);
  margin: 0 auto var(--dj-site-space-16);
  font-size: var(--dj-site-text-md);
  font-weight: var(--dj-site-font-medium);
  line-height: 1.2;
  color: var(--dj-site-gray-700);
}

.social-sub {
  max-width: 38.75rem; /* 620px, per task 12's own measure */
  margin: 0 auto var(--dj-site-space-32);
  font-size: var(--dj-site-text-sm);
  line-height: 1.6;
  color: var(--dj-site-gray-500);
}

/* FinalCTA (task 13) */
.section-final {
  background: var(--dj-site-blue-600);
  padding: var(--dj-site-space-88) var(--dj-site-space-24);
  text-align: center;
}

.section-final h2 {
  margin: 0 0 var(--dj-site-space-24);
  font-size: var(--dj-site-text-2xl);
  font-weight: var(--dj-site-font-extrabold);
  line-height: 1.2;
  color: var(--dj-site-white);
}

.final-btns {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--dj-site-space-16);
}

.btn--outline-light {
  background: none;
  color: var(--dj-site-white);
  border-color: var(--dj-site-white);
}

.btn--outline-light:hover {
  background: rgba(255, 255, 255, 0.1);
}

/* Site footer (task 14) — replaces the old measure-constrained `footer`,
   sitewide like the nav. One footer, not two. */
.site-footer {
  background: var(--dj-site-gray-900);
  padding: var(--dj-site-space-64) var(--dj-site-space-24)
    var(--dj-site-space-40);
}

.footer-inner {
  max-width: var(--dj-site-width-container);
  margin: 0 auto;
}

.footer-top {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--dj-site-space-40);
  padding-bottom: var(--dj-site-space-40);
}

@media (min-width: 768px) {
  .footer-top {
    grid-template-columns: 1.5fr 1fr 1fr 1fr;
  }
}

.footer-brand svg {
  display: block;
  width: 110px;
  height: auto;
  margin-bottom: var(--dj-site-space-16);
}

.footer-brand p {
  margin: 0;
  max-width: 20rem;
  font-size: var(--dj-site-text-xs);
  line-height: 1.2;
  color: var(--dj-site-gray-400);
}

.footer-col h3 {
  margin: 0 0 var(--dj-site-space-24);
  font-size: 0.75rem;
  font-weight: var(--dj-site-font-bold);
  line-height: 1.2;
  letter-spacing: 0.05em;
  color: var(--dj-site-gray-500);
}

.footer-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--dj-site-space-16);
}

.footer-col a {
  font-size: var(--dj-site-text-sm);
  line-height: 1.2;
  color: var(--dj-site-gray-300);
  text-decoration: none;
}

.footer-col a:hover {
  color: var(--dj-site-white);
}

.footer-divider {
  border: none;
  border-top: 1px solid var(--dj-site-gray-800);
  margin: 0 0 var(--dj-site-space-24);
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--dj-site-space-16);
}

.footer-bottom p {
  margin: 0;
  font-size: var(--dj-site-text-xs);
  color: var(--dj-site-gray-500);
}

.footer-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dj-site-space-14);
}

.footer-badges .badge {
  background: var(--dj-site-gray-800);
  border-radius: 0.25rem;
  padding: 0.25rem 0.625rem;
  font-size: 0.6875rem;
  font-weight: var(--dj-site-font-semibold);
  color: var(--dj-site-gray-300);
}

/* Tab widget (task 15) — shared by LiveProof (light) and PickFramework (dark).
   Visible focus ring matters here: tabs.js moves focus between tabs on arrow
   keys, so a sighted keyboard user needs to see where focus landed. */
[role="tab"] {
  border: none;
  background: none;
  cursor: pointer;
  font-family: inherit;
}

[role="tab"]:focus-visible {
  outline: 2px solid var(--dj-site-blue-500);
  outline-offset: -2px;
}

/* LiveProof (task 16) */
.section-liveproof {
  background: var(--dj-site-white);
  padding: 0 var(--dj-site-space-24) var(--dj-site-space-96);
}

.proof-card {
  max-width: var(--dj-site-width-grid);
  margin: 0 auto;
  border: 1px solid var(--dj-site-gray-200);
  border-radius: 0.5rem;
  overflow: hidden;
}

.proof-tabs {
  display: flex;
  flex-wrap: wrap;
  background: var(--dj-site-gray-50);
  border-bottom: 1px solid var(--dj-site-gray-200);
  padding: var(--dj-site-space-16) var(--dj-site-space-20) 0;
  gap: var(--dj-site-space-14);
}

.proof-tab {
  padding: 0.5rem 1rem;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 0.375rem 0.375rem 0 0;
  font-size: var(--dj-site-text-sm);
  font-weight: var(--dj-site-font-semibold);
  color: var(--dj-site-gray-500);
}

.proof-tab[aria-selected="true"] {
  background: var(--dj-site-white);
  border-color: var(--dj-site-gray-200);
  color: var(--dj-site-blue-600);
}

.proof-grid-live {
  padding: var(--dj-site-space-28) var(--dj-site-space-28) var(--dj-site-space-40);
}

.proof-caption {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-sm);
  font-weight: var(--dj-site-font-medium);
  color: var(--dj-site-gray-400);
}

.proof-link {
  display: block;
  margin-top: var(--dj-site-space-24);
  text-align: center;
  font-size: var(--dj-site-text-md);
  font-weight: var(--dj-site-font-semibold);
  color: var(--dj-site-blue-600);
  text-decoration: none;
}

.proof-link:hover {
  text-decoration: underline;
}

/* PickFramework (task 17) */
.section-pick {
  background: var(--dj-site-white);
  padding: var(--dj-site-space-96) var(--dj-site-space-24);
  text-align: center;
}

.section-pick h2 {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-2xl);
  font-weight: var(--dj-site-font-extrabold);
  line-height: 1.2;
  color: var(--dj-site-gray-900);
}

.section-pick .section-sub {
  margin: 0 0 var(--dj-site-space-40);
  font-size: var(--dj-site-text-base);
  line-height: 1.2;
  color: var(--dj-site-gray-500);
}

.pick-code-card {
  max-width: var(--dj-site-width-card);
  margin: 0 auto;
  text-align: left;
  background: var(--dj-site-gray-900);
  border-radius: 0.5rem;
  overflow: hidden;
}

.pick-tabs {
  display: flex;
  flex-wrap: wrap;
  background: var(--dj-site-gray-800);
  gap: 0.25rem;
  padding: var(--dj-site-space-16) var(--dj-site-space-16) 0;
}

.pick-tab {
  padding: 0.5rem 1rem;
  border-radius: 0.375rem 0.375rem 0 0;
  font-size: var(--dj-site-text-sm);
  font-weight: var(--dj-site-font-semibold);
  color: var(--dj-site-gray-400);
}

.pick-tab[aria-selected="true"] {
  background: var(--dj-site-gray-900);
  color: var(--dj-site-white);
}

.pick-panel pre {
  margin: 0;
  padding: var(--dj-site-space-24);
  overflow-x: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--dj-site-text-sm);
  line-height: 1.6;
  color: var(--dj-site-gray-200);
}

.pick-panel .cmd {
  display: block;
  margin-bottom: var(--dj-site-space-16);
  color: var(--dj-site-blue-300);
}

/* ThemingTeaser (task 18) */
.section-theming {
  background: var(--dj-site-gray-50);
  padding: var(--dj-site-space-96) var(--dj-site-space-24);
  text-align: center;
}

.section-theming h2 {
  margin: 0 0 var(--dj-site-space-16);
  font-size: var(--dj-site-text-2xl);
  font-weight: var(--dj-site-font-extrabold);
  line-height: 1.2;
  color: var(--dj-site-gray-900);
}

.section-theming .section-sub {
  max-width: 42.5rem; /* 680px, per the design's own measure for this sub */
  margin: 0 auto var(--dj-site-space-40);
  font-size: var(--dj-site-text-base);
  line-height: 1.5;
  color: var(--dj-site-gray-600);
}

.theming-swatches {
  max-width: var(--dj-site-width-card);
  margin: 0 auto var(--dj-site-space-24);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  background: var(--dj-site-white);
  border: 1px solid var(--dj-site-gray-200);
  border-radius: 0.5rem;
}

@media (min-width: 640px) {
  .theming-swatches {
    grid-template-columns: repeat(4, 1fr);
  }
}

.swatch {
  padding: var(--dj-site-space-20);
  text-align: left;
}

.swatch-color {
  height: 48px;
  border-radius: 0.375rem;
  margin-bottom: var(--dj-site-space-16);
}

.swatch-name {
  margin: 0;
  font-size: 0.6875rem;
  font-weight: var(--dj-site-font-semibold);
  color: var(--dj-site-gray-500);
}

.swatch-hex {
  margin: 0.125rem 0 0;
  font-size: var(--dj-site-text-xs);
  color: var(--dj-site-gray-400);
}

.section-theming .section-link {
  display: inline-block;
  font-size: var(--dj-site-text-md);
  font-weight: var(--dj-site-font-semibold);
  color: var(--dj-site-blue-600);
  text-decoration: none;
}

.section-theming .section-link:hover {
  text-decoration: underline;
}

/* ---------------------------------------------------------------------------
   /components — the component gallery.

   Ported from the Penpot "Components" page: 280px cards on a 20px gutter inside
   the 1248px container (the same measure as the footer row), 6px radius and a 1px
   border rather than a shadow (the site mirrors how the real dj-* components do
   elevation), Roboto Mono for the tag, Roboto for everything else.

   The page opts out of .page-container via `fullBleed` — that measure is 48rem,
   which would stack the gallery one card wide.

   THEME-REACTIVE, and it has to be. The --dj-site-* ramp at the top of this file
   is deliberately NOT theme-reactive: those are literal design values, and the
   homepage gets away with them because every one of its sections paints its own
   fixed background, so fixed text always sits on fixed ground. A content page
   does not — it inherits `body`, whose --bg flips to #14141f under
   prefers-color-scheme: dark. Built against the ramp directly, this page put
   gray-900 headings and gray-600 body text onto a near-black background, and the
   note's inline <code> (which takes the sitewide `background: var(--border)`)
   became dark blue on black. So the gallery declares its own --gal-* tokens,
   seeded from the design values and overridden for dark. Light mode is
   byte-for-byte the Penpot design; dark mode is legible.
   --------------------------------------------------------------------------- */

:root {
  --gal-surface: var(--dj-site-white);
  --gal-border: var(--dj-site-gray-200);
  --gal-heading: var(--dj-site-gray-900);
  --gal-body: var(--dj-site-gray-600);
  --gal-label: var(--dj-site-gray-500);
  --gal-tag: var(--dj-site-blue-700);
  --gal-eyebrow: var(--dj-site-blue-600);
  --gal-code: var(--dj-site-gray-700);
  --gal-pill-bg: #f3f4f6;
  --gal-pill-fg: var(--dj-site-gray-600);
  --gal-pill-plugin-bg: #eff6ff;
  --gal-pill-plugin-fg: var(--dj-site-blue-700);
  --gal-note-bg: var(--dj-site-gray-50);
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Cards sit slightly above the page rather than matching it, so the grid still
       reads as cards when the border alone is nearly invisible. */
    --gal-surface: #1c1c2b;
    --gal-border: #33334a;
    --gal-heading: var(--fg);
    --gal-body: #c2c2d6;
    --gal-label: var(--muted);
    --gal-tag: #9db4ff;
    --gal-eyebrow: #9db4ff;
    --gal-code: #d5d5e6;
    --gal-pill-bg: #262636;
    --gal-pill-fg: #c2c2d6;
    --gal-pill-plugin-bg: #22304f;
    --gal-pill-plugin-fg: #a9c2ff;
    --gal-note-bg: #1a1a28;
  }
}

.section-gallery {
  max-width: var(--dj-site-width-container);
  margin: 0 auto;
  padding: var(--dj-site-space-56) var(--dj-site-space-24) var(--dj-site-space-96);
}

.gallery-header {
  text-align: center;
  margin-bottom: var(--dj-site-space-56);
}

.gallery-eyebrow {
  margin: 0 0 var(--dj-site-space-14);
  font-size: 0.8125rem;
  font-weight: var(--dj-site-font-bold);
  letter-spacing: 0.02em;
  color: var(--gal-eyebrow);
}

.gallery-title {
  margin: 0 0 var(--dj-site-space-14);
  font-size: var(--dj-site-text-2xl);
  font-weight: var(--dj-site-font-bold);
  line-height: var(--dj-site-leading-tight);
  color: var(--gal-heading);
}

.gallery-sub {
  max-width: var(--dj-site-width-prose);
  margin: 0 auto;
  font-size: var(--dj-site-text-base);
  line-height: var(--dj-site-leading-normal);
  color: var(--gal-body);
}

.gallery-group {
  margin-bottom: var(--dj-site-space-48);
}

.gallery-group-label {
  margin: 0 0 var(--dj-site-space-20);
  font-size: 0.75rem;
  font-weight: var(--dj-site-font-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gal-label);
}

/* auto-fill rather than a fixed four columns: the design is four-up at 1248px,
   and this reaches the same arrangement there while dropping to three, two, and
   one on the way down without a media query per step. */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17.5rem, 1fr));
  gap: var(--dj-site-space-20);
  margin: 0;
  padding: 0;
  list-style: none;
}

.gallery-card {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  padding: var(--dj-site-space-20);
  background: var(--gal-surface);
  border: 1px solid var(--gal-border);
  border-radius: 0.375rem;
}

.gallery-card__name {
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9375rem;
  font-weight: var(--dj-site-font-semibold);
  line-height: var(--dj-site-leading-tight);
  color: var(--gal-tag);
  text-decoration: none;
  /* A tag like <dj-floating-action-button> is wider than the card. Break it
     rather than let it overflow — in the Penpot draft the same names clipped at
     the card edge until the title boxes were constrained. */
  overflow-wrap: anywhere;
}

.gallery-card__name:hover,
.gallery-card__name:focus-visible {
  text-decoration: underline;
}

.gallery-card__desc {
  margin: 0;
  font-size: 0.84375rem;
  line-height: 1.45;
  color: var(--gal-body);
}

/* Pills sit at the card's bottom edge however long the description is, so a row
   of cards lines its metadata up. */
.gallery-card__pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: auto 0 0;
}

.gallery-pill {
  padding: 0.1875rem 0.5rem;
  border-radius: 0.1875rem;
  background: var(--gal-pill-bg);
  font-size: 0.6875rem;
  font-weight: var(--dj-site-font-bold);
  line-height: var(--dj-site-leading-tight);
  color: var(--gal-pill-fg);
}

.gallery-pill--plugin {
  background: var(--gal-pill-plugin-bg);
  color: var(--gal-pill-plugin-fg);
}

.gallery-note {
  margin-top: var(--dj-site-space-32);
  padding: var(--dj-site-space-20) var(--dj-site-space-24);
  background: var(--gal-note-bg);
  border: 1px solid var(--gal-border);
  border-radius: 0.375rem;
  font-size: 0.84375rem;
  line-height: 1.55;
  color: var(--gal-body);
}

.gallery-note p {
  margin: 0;
}

.gallery-note__lead {
  margin-bottom: 0.25rem !important;
  font-weight: var(--dj-site-font-bold);
  color: var(--gal-heading);
}

/* Inline code inside the gallery, everywhere it appears. The sitewide `code` rule
   fills with var(--border) and sets no color, which in dark mode is a near-black
   chip under inherited body text — the "dark blue on black" in the note. Give it
   an explicit pair here rather than leaving it to inherit. */
.gallery-note code,
.gallery-sub code {
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.95em;
  background: var(--gal-pill-bg);
  color: var(--gal-code);
}

/* Descriptions carry a lot of inline code — several one-liners name four or five
   properties — and the sitewide `code` chip (a filled background plus 0.35em of
   side padding) turns those into a row of grey blocks and leaves a visible gap
   before trailing punctuation ("composing <dj-label> ."). Inside a card the
   monospace face alone is enough to mark a property name, so drop the chip here
   and keep it everywhere else on the site. */
.gallery-card__desc code {
  background: none;
  padding: 0;
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.95em;
  color: var(--gal-code);
}
