/*
 * Ureh public marketing site — page/assets/css/page.css
 *
 * Everything here is built on the same var(--bs-*) tokens the
 * Developer Console and Admin Portal already use (vendors/main.css,
 * copied byte-for-byte into page/vendors/ — see that folder's own
 * README-equivalent comment in includes/bootstrap.php) — no hand-picked
 * hex beyond what those tokens already resolve to. The visual register
 * is deliberately more editorial/spacious than the authenticated
 * console (generous whitespace, larger type scale, restrained motion)
 * while staying unmistakably the same product: same brand color, same
 * type stack, same border/radius/shadow language.
 */

/* ---------------------------------------------------------------------
 * 1. Typography tokens
 * main.css's own "Enterprise" theme layer names --bs-body-font-family
 * as `"DM Sans", sans-serif` (a Google Font whose @import was removed
 * at the source — see vendors/main.css's own header comment) — without
 * this override every heading/body string silently falls back to the
 * *generic* `sans-serif` keyword, not a considered stack. This is the
 * exact same fix dev-ui/admin-ui's own assets/css/*.css apply, applied
 * here for the same reason: native system-font stacks only, no network
 * font request, single source of truth for every font-family on this
 * page. (Ordinary UI/content text — --font-ui-sans below — stays on
 * this native-stack-only rule. Code/technical text does not: see the
 * @font-face block and --font-ui-mono's own comment immediately below.)
 * ------------------------------------------------------------------- */

/* JetBrains Mono, self-hosted — replaces IBM Plex Mono (2026-08-27
 * direction superseded by explicit direction to switch the code/mono
 * face sitewide). Same rationale as before for stepping off the plain
 * system-monospace stack (a deliberate, "designed for this" face for
 * code/payload typography rather than "hacker aesthetic" ligature-heavy
 * faces), just a different family. SIL Open Font License 1.1 (JetBrains
 * s.r.o.) — full text in this file's own directory,
 * page/assets/fonts/JETBRAINS-MONO-OFL.txt. Files are the static WOFF2
 * build from the @fontsource/jetbrains-mono npm package (same upstream
 * font, pre-built for self-hosting), vendored here rather than loaded
 * from Google Fonts/any CDN at runtime — same-origin, no external
 * network request when the page loads, consistent with this file's
 * existing "no network font request" rule above (which still governs
 * --font-ui-sans; this is the one deliberate, explicit exception,
 * scoped to code text only). Only the two weights actually used by any
 * mono-font rule on this page are included (400 for code/payload text,
 * 700 for the method/status badges that sit inside it) — not the full
 * variable-font family, per "avoid unnecessary dependencies."
 * font-display: swap shows the system-monospace fallback immediately
 * and swaps in JetBrains Mono once it loads, rather than a flash of
 * invisible text. */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/jetbrains-mono-latin-400-normal.woff2") format("woff2");
}

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/jetbrains-mono-latin-700-normal.woff2") format("woff2");
}

:root {
  --font-ui-sans:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI Variable",
    "Segoe UI",
    Roboto,
    "Helvetica Neue",
    Helvetica,
    Arial,
    sans-serif;

  /* The single source of truth for every code/technical-text font on
     this page. "JetBrains Mono" leads (self-hosted, see the @font-face
     block above); every name after it is the same system-monospace
     stack this token used to be entirely made of, kept as the fallback
     if the web font is ever slow, blocked, or fails to load — a request
     never renders in a font it can't get, it just degrades gracefully.
     Every rule below that renders code (the base <code>/<pre>/<kbd>/
     <samp> rule in this section, plus the handful of non-<code>
     elements that are still technical text — .pg-api-request-line,
     .pg-platform-endpoint, .pg-webhook-event-name, .pg-console-url,
     .pg-console-kpi-value) reads this one variable rather than
     hardcoding its own font stack, so there is exactly one place that
     can ever drift. */
  --font-ui-mono:
    "JetBrains Mono",
    ui-monospace,
    SFMono-Regular,
    Menlo,
    Monaco,
    Consolas,
    "Liberation Mono",
    "Courier New",
    monospace;

  /* The two standard sizes for mono/code text sitewide — .8125rem
     (13px) for anything meant to be read as code (payload/JSON blocks,
     request/response lines, inline `code` mentions, endpoint tags), and
     a smaller .75rem (12px) tier for compact badge-style mono labels
     (a webhook event-name chip, a step tag, a console URL breadcrumb)
     that were never meant to be read as a code passage. Both are real,
     named tokens instead of each component hardcoding its own literal,
     specifically so two components that should match (the endpoint line
     in Platform vs. the equivalent line in Webhooks/Code Experience —
     these were 14px and 13px respectively before this pass) can't
     silently drift apart again. */
  --pg-code-font-size: .8125rem;
  --pg-code-font-size-sm: .75rem;
  --pg-code-line-height: 1.7;

  --bs-font-sans-serif: var(--font-ui-sans);
  --bs-font-monospace: var(--font-ui-mono);
  --bs-body-font-family: var(--font-ui-sans);

  /* Real bug, confirmed live in dark mode: --bs-primary (the Enterprise
     theme's dark slate, #3A454D — see vendors/main.css) is explicitly
     left unchanged between themes, same as dev-ui/admin-ui's own dark
     block leaves every brand/status color untouched. That's correct
     everywhere this page uses --bs-primary as a BACKGROUND or border
     (.pg-btn-primary, badge fills, tinted icon-chip fills) — those stay
     exactly as designed, white text on the same brand color in both
     themes. It breaks down everywhere this page uses --bs-primary as
     plain FOREGROUND text/icon color instead (eyebrows, hover accents,
     icon glyphs) — dark slate text on this page's own near-black dark
     background (--bs-body-bg #19191a) has poor contrast; confirmed by
     screenshot (.pg-arch-node--api's label was barely legible). Rather
     than changing --bs-primary itself — which would break every
     BACKGROUND usage and contradict dev-ui/admin-ui's own explicit
     "brand colors untouched" rule — this is a second, page-local token
     for exactly the foreground-text role, identical to --bs-primary in
     light mode (zero visual change there) and brightened only where
     dark mode's own block below overrides it. Every `color:
     var(--bs-primary)` foreground use in this file now reads this
     token instead; every background/border use of --bs-primary is
     untouched. */
  --pg-accent: var(--bs-primary);

  --pg-max-width: 1280px;
  --pg-header-height: 4.5rem;
}

/* Same "crisp gray, no hue shift" deepening admin-ui/dev-ui's own
   [data-bs-theme="light"] block applies to --hk-border-primary for the
   same reason (main.css's light default, ~#e9e9e9, reads as barely-
   there on a plain white marketing page) — same value, applied to the
   token this file actually reads (--bs-border-color, not --hk-*).
   Deliberately scoped to light mode, not a bare :root: a bare :root
   value is unconditional and would keep overriding dark mode's own,
   much darker border token below regardless of theme — that was a real
   bug this pass fixes, not a hypothetical one (confirmed live: without
   this scoping, dark mode's borders stayed stuck at this light-only
   gray instead of vendors/main.css's own dark border color). */
[data-bs-theme="light"] {
  --bs-border-color: #e0e0e0;
}

/* Dark theme — the exact shared palette dev-ui/admin-ui already ship
   (dev-ui/assets/css/dev.css's own [data-bs-theme="dark"] block, kept
   in sync with admin-ui/assets/css/admin.css — see that file's own
   extensive comment for the full reasoning/history behind these exact
   values). Reproduced here as the same CSS-variable overrides, not a
   new palette: this page has no runtime dependency on dev-ui/admin-ui's
   own stylesheets (same reason it vendors main.css instead of linking
   theirs — includes/bootstrap.php), so getting the same *colors*, per
   this phase's own instruction, means copying the same token values
   into this file's own theme block, the same way vendors/main.css
   itself is a copy rather than a shared include.
   Unlike dev-ui/admin-ui, this page has no "raised card is a different
   shade than the page" concept even in light mode — every .pg-* card
   already uses var(--bs-body-bg) for its own background and relies on
   border+shadow alone for separation (see page.css section 11's own
   comment on why the outer Platform/Journey card was removed rather
   than kept as a second background level). So only --bs-body-bg
   (Level 1) is needed here, not dev-ui's separate --hk-bg-primary
   (Level 2) — every card on this page already inherits the correct
   dark tone automatically through that same var(--bs-body-bg), with
   zero component-level changes. --bs-tertiary-bg (this page's own
   existing "one step lighter than the page" utility shade — tinted
   section backgrounds, icon-chip tints, hover states) maps directly
   onto dev-ui's own same-purpose token, already the right relative
   contrast in both themes. --bs-primary/success/warning/danger are
   untouched, exactly as dev-ui/admin-ui leave them. */
[data-bs-theme="dark"] {
  --bs-body-bg: #19191a;
  --bs-body-bg-rgb: 25, 25, 26;
  --bs-tertiary-bg: #2a2a2d;
  --bs-tertiary-bg-rgb: 42, 42, 45;
  --bs-border-color: rgba(255, 255, 255, .08);
  --bs-border-color-translucent: rgba(255, 255, 255, .1);
  --bs-secondary-color: rgba(255, 255, 255, .55);
  --bs-tertiary-color: rgba(255, 255, 255, .35);
  /* Same white-glow fix as dev-ui/admin-ui's own dark block: vendors/
     main.css's base dark theme sets these two to a literal white RGB
     triplet, inconsistent with every other (black-based) shadow token
     in the same block. This page's own .pg-* shadows are already
     hardcoded rgba(0,0,0,...) rather than reading these variables, so
     they're unaffected either way — kept here anyway for any vendor
     utility class this page happens to reuse, and for parity with the
     source of truth. */
  --bs-shadow-color: 0, 0, 0;
  --hk-shadow-rgb: 0, 0, 0;

  /* --pg-accent (this page's own token — see its :root comment above):
     a brightened, same-hue-family counterpart to --bs-primary
     (#3A454D, a dark slate) for the many places this page uses the
     brand color as plain foreground text/icon color rather than a
     background — that usage genuinely needs a lighter value in dark
     mode to stay legible, unlike --bs-primary itself, which stays the
     literal brand color untouched (same rule dev-ui/admin-ui's own
     dark theme follows for every brand/status color). */
  --pg-accent: #a3b3bd;
}

/* The global code-typography rule: every bare <code>/<kbd>/<samp>/<pre>
   on the page — present or future, styled with a component class or
   not — gets the standard font and size from here, once. Component
   rules below (.pg-api-code, .pg-journey-tag, ...) only ever add what's
   genuinely specific to them (background, padding, a smaller size
   tier); none of them re-declare font-family or a competing font-size
   any more. line-height is set only on <pre> (block-level code): an
   inline <code> word sitting inside a sentence should keep that
   sentence's own line-height, not force its own. */
.pg-body code,
.pg-body kbd,
.pg-body samp,
.pg-body pre {
  font-family: var(--font-ui-mono);
  font-size: var(--pg-code-font-size);
}

.pg-body pre {
  line-height: var(--pg-code-line-height);
}

/* ---------------------------------------------------------------------
 * 2. Base
 * ------------------------------------------------------------------- */
.pg-body {
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  -webkit-font-smoothing: antialiased;
  /* Defensive clip for Bootstrap's own row/col negative-margin mechanic
     — see the .g-5 override in section 18 for the actual root-cause fix
     (this mismatch only really exists below lg, where these rows stack
     to one column anyway). Kept as a second, harmless layer. Deliberately
     NOT also on <html>: verified live that overflow-x:hidden on <html>
     itself breaks position:sticky on .pg-header (it scrolls away with
     the page instead of staying pinned) — a real, confirmed regression,
     not a hypothetical risk. <body> alone has no such effect. */
  overflow-x: hidden;
}

.pg-body .container {
  max-width: var(--pg-max-width);
  /* Closes the same .row.g-5 gutter-overshoot this file's own .pg-body
     comment describes, for the >=lg range the section 18 media-query
     fix deliberately doesn't touch (there, the larger gutter is doing
     real work spacing two side-by-side columns, so shrinking it isn't
     the right fix — clipping at each section's own container is).
     Verified live this doesn't affect .pg-mega: the mega panels are
     siblings of .pg-header-inner (this same .container class), not its
     descendants, so they're never clipped by this. */
  overflow-x: hidden;
}

.pg-skip-link {
  position: absolute;
  top: -3rem;
  left: 1rem;
  z-index: 2000;
  background: var(--bs-primary);
  color: #fff;
  padding: .625rem 1rem;
  border-radius: var(--bs-border-radius);
  transition: top .15s ease;
}

.pg-skip-link:focus {
  top: 1rem;
}

/* :where(a) keeps this base reset's specificity down to just ".pg-body"
   (a class + a bare type selector would otherwise be (0,1,1) — higher
   than a single component class like .pg-btn-primary's (0,1,0)). That
   gap was a real, live bug: .pg-btn-primary{color:#fff} was losing the
   cascade to this rule's color:inherit at rest (inherited color ==
   the ambient dark body-color, on the button's own dark-slate
   background — invisible), while .pg-btn-primary:hover{color:#fff}
   won only because a pseudo-class adds a second class-level selector.
   Every component's own color rule below is a single class already, so
   this fix doesn't change anything else's cascade outcome — it just
   stops this reset from ever outranking a real component style. */
.pg-body :where(a) {
  color: inherit;
  text-decoration: none;
}

.pg-body :focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .pg-body * {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------------
 * 3. Buttons
 * Own small button family rather than fighting Bootstrap's .btn-*
 * sizing modifiers, which are tuned for the compact authenticated
 * console, not a marketing hero — still built on the same --bs-primary/
 * --bs-border-color/--bs-border-radius tokens.
 * ------------------------------------------------------------------- */
.pg-btn {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-weight: 600;
  font-size: .9375rem;
  padding: .625rem 1.125rem;
  border-radius: var(--bs-border-radius);
  border: 1px solid transparent;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease, transform .15s ease;
  white-space: nowrap;
}

.pg-btn-lg {
  padding: .8125rem 1.5rem;
  font-size: 1rem;
}

.pg-btn-primary {
  background: var(--bs-primary);
  border-color: var(--bs-primary);
  color: #fff;
}

.pg-btn-primary:hover {
  filter: brightness(1.12);
  color: #fff;
}

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

.pg-btn-ghost {
  background: transparent;
  border-color: var(--bs-border-color);
  color: var(--bs-body-color);
}

.pg-btn-ghost:hover {
  background: var(--bs-tertiary-bg);
}

.pg-action-link {
  display: inline-flex;
  align-items: center;
  font-weight: 500;
  font-size: .9375rem;
  color: var(--bs-body-color);
  padding: .5rem .25rem;
}

.pg-action-link:hover {
  color: var(--pg-accent);
}

/* ---------------------------------------------------------------------
 * 4. Header
 * ------------------------------------------------------------------- */
.pg-header {
  position: sticky;
  top: 0;
  z-index: 1000;
  background: var(--bs-body-bg);
  border-bottom: 1px solid transparent;
}

.pg-header--scrolled {
  border-bottom-color: var(--bs-border-color);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .04);
}

.pg-header-inner {
  height: var(--pg-header-height);
  gap: 1.5rem;
}

/* Groups Brand + Nav into one tight left-hand cluster. Previously .pg-nav
   had flex:1 directly on .pg-header-inner's own space-between row, which
   pulled it toward the middle and let it consume most of the header's
   width instead of sitting close to the logo the way Supabase's nav
   does. Now .pg-header-inner has exactly two flex children (this group,
   and .pg-header-actions) — space-between puts all the slack in the one
   gap between them, not around the nav on both sides. */
.pg-header-left {
  display: flex;
  align-items: center;
  gap: 2rem;
  min-width: 0;
}

.pg-brand {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
  font-weight: 700;
  font-size: 1.0625rem;
  color: var(--bs-body-color);
  flex-shrink: 0;
}

.pg-brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: .5625rem;
  background: rgba(var(--bs-primary-rgb), .12);
  color: var(--pg-accent);
}

/* Ureh logotype (page/assets/images/ureh.io-light.svg) — the full
   mark+wordmark lockup used in the header, footer and mobile nav. Sized
   by height only with width:auto, so the source 750x300 (2.5:1) artwork
   is never stretched or deformed; the width/height attributes on the
   <img> carry the same ratio to reserve space and avoid layout shift.
   The anchor (.pg-brand) already carries the accessible name via its
   aria-label, so the <img> uses alt="" to avoid a double announcement. */
.pg-brand-logo {
  display: block;
  /* The source SVG has ~70% empty padding baked into its 750x300 frame,
     so the wordmark rendered tiny at any sensible box height. The frame
     was tightened to the ink (viewBox in the SVG; artwork paths/colours
     unchanged), so the same displayed height now shows the wordmark ~3x
     larger — the prominence a header logo needs, in the spirit of
     Stripe/Supabase, without recreating or distorting the mark. */
height: 1.3rem;
  width: auto;
  /* Defends the framed ratio even if the width/height attributes are
     ever dropped, so the mark can never squash. */
  aspect-ratio: 246 / 104.3;
}

/* The supplied file is the light-background variant (dark slate ink),
   which would be near-invisible on the dark header/footer (--bs-body-bg
   #19191a). Invert its lightness on the dark theme while rotating the
   hue back, so it stays legible and roughly on-brand without altering
   the source artwork or its proportions. This is the treatment the mark
   used before the pure-white experiment; reverted here so the neutral
   ink is not forced to a flat #FFFFFF while the accent stays a green. */
[data-bs-theme="dark"] .pg-brand-logo {
  filter: invert(1) hue-rotate(180deg);
}

/* ---------------------------------------------------------------------
 * Documentation header (components/docs-header.php)
 *
 * A minimal, focused variant of the shared header. Reuses .pg-header/
 * .pg-brand/.pg-nav/.pg-nav-trigger/.pg-header-actions so it inherits
 * the base layout and every responsive rule below; only the "Ureh Docs"
 * lockup and a couple of docs-specific touches are added here.
 * ------------------------------------------------------------------- */
.pg-docs-brand {
  gap: .5rem;
}

/* "Docs" set beside the existing Ureh wordmark so the lockup reads
   "Ureh Docs" — same weight family as the mark, a hair lighter in colour
   so the logo still leads. Inherits .pg-brand's vertical centring. */
.pg-docs-brand-label {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--bs-secondary-color);
  line-height: 1;
}

/* The docs nav is plain links (no mega menu), so give it a little more
   breathing room than the tight marketing trigger row. */
.pg-docs-nav {
  gap: .25rem;
}

.pg-nav {
  display: flex;
  align-items: center;
  gap: .125rem;
}

.pg-nav-trigger {
  display: inline-flex;
  align-items: center;
  gap: .3125rem;
  background: transparent;
  border: none;
  color: var(--bs-body-color);
  font-weight: 500;
  /* Compactness pass: 15px -> 14px and tighter horizontal padding, plus
     .pg-header-left's own tight grouping (above) — together this is what
     stops the nav from reading as "almost the entire header," matching
     Supabase's noticeably tighter proportions rather than an arbitrary
     shrink. */
  font-size: .875rem;
  padding: .5rem .625rem;
  border-radius: var(--bs-border-radius);
  white-space: nowrap;
}

.pg-nav-trigger:hover,
.pg-nav-trigger[aria-expanded="true"] {
  background: var(--bs-tertiary-bg);
  color: var(--pg-accent);
}

.pg-nav-caret {
  display: inline-flex;
  transition: transform .15s ease;
}

.pg-nav-trigger[aria-expanded="true"] .pg-nav-caret {
  transform: rotate(180deg);
}

.pg-header-actions {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-shrink: 0;
}

/* GitHub action — a non-linking placeholder (see components/header.php's
   own top-of-file comment for why: the only known repository is
   private, and a private repo has no public star count to show). Muted
   tone rather than styled as a live action, so it doesn't invite a
   click that goes nowhere. cursor:default (not pointer) is part of
   that same honesty. */
/* Padding/gap bumped from .4375rem/.625rem/.375rem (verified live in
   both Chromium and WebKit at every width from 320-1440px, at exactly
   spec — never actually compressed) to a visibly more generous size:
   the old values were technically correct but read as merely adequate
   rather than deliberate, especially at icon-only widths where a
   38px-square pill left almost no breathing room around the glyph. */
.pg-github-action {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .75rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  color: var(--bs-secondary-color);
  font-size: .8125rem;
  font-weight: 500;
  cursor: default;
  white-space: nowrap;
  /* Doesn't fix the narrow-viewport bug below on its own (that's the
     brand overflowing into this badge's space, not this badge being
     shrunk) — kept anyway as a real, correct guarantee that this
     badge's own box can never be compressed below its intended
     icon+padding size regardless of what else changes in the header
     later. */
  flex-shrink: 0;
}

/* Real bug, confirmed live at the canonical smallest phone width
   (320px, e.g. iPhone SE): .pg-header-left's own min-width: 0 lets it
   be squeezed narrower than .pg-brand's fixed (flex-shrink: 0) content
   width once .pg-header-actions (Get Started + this badge + the
   hamburger, also flex-shrink: 0 as a group) claims the rest of the
   row — the brand wordmark ends up rendering ~8px into the space this
   badge occupies, visually crushing its icon against the "Ureh"
   text. The badge's own box was never actually malformed (it already
   renders at exactly its specified padding), it's this collision with
   the overlapping brand text that reads as "deformed." Since this
   badge is a non-linking placeholder already (see this rule's own
   comment above) — the least essential item in the row — dropping it
   at the exact width where it doesn't fit is a smaller, safer fix than
   changing .pg-header-left's shrink behavior, which other breakpoints
   (where .pg-nav is actually present and needs to shrink) still rely
   on. Confirmed clear of the overlap by 340px, so 360px keeps a safety
   margin above the exact failure point. */
/* Scoped to .pg-header-actions specifically, not a bare .pg-github-action
   — this same class is reused on the mobile drawer's own copy of this
   badge (components/mobile-nav.php), which is a plain full-width list
   item with no crowding problem to hide from. A bare selector here was
   a real bug: it silently hid the drawer's GitHub row too at these
   widths for no reason, with nothing to show it was ever there. */
@media (max-width: 359.98px) {
  .pg-header-actions .pg-github-action {
    display: none;
  }
}

.pg-mobile-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px — real touch target, not just the visual icon size. */
  width: 2.75rem;
  height: 2.75rem;
  background: transparent;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
}

.pg-mobile-toggle:hover {
  background: var(--bs-tertiary-bg);
}

/* ---------------------------------------------------------------------
 * 5. Mega menu
 * A constrained, centered floating panel — NOT a full-width strip
 * anchored to the header edges. Kept in the DOM across its transition
 * (see assets/js/nav.js) so opacity/transform actually animate instead
 * of snapping via [hidden].
 * ------------------------------------------------------------------- */
.pg-mega-backdrop {
  position: fixed;
  inset: var(--pg-header-height) 0 0 0;
  /* Transparent, not a page-dimming scrim: a compact floating panel
     (below) doesn't call for the same heavy full-page dim a former
     full-width strip did. Still a real, positioned element — it's what
     closes the menu when a click lands in the "dead space" around the
     panel but is still inside <header> in the DOM (see its own click
     handler in assets/js/nav.js; a plain document-level "outside click"
     check alone wouldn't catch that specific case). */
  background: transparent;
  z-index: 998;
  /* Real bug: this covers the entire viewport below the header, and
     unlike .pg-mobile-nav's own identical fixed-overlay pattern (this
     section, below), it had no pointer-events gate at rest — only the
     [hidden] attribute stood between it and every click/scroll/hover
     on the rest of the page. assets/js/nav.js's own closeMenu() sets
     [hidden] on a 150ms delay (for a fade-out transition), but removes
     this open class synchronously the instant closing starts — so for
     up to 150ms after simply moving the mouse off a nav trigger (no
     click needed to open it in the first place; mouseenter alone
     does), an invisible, fully interactive layer sat over the whole
     page, swallowing the next click or scroll gesture that landed on
     it before the [hidden] delay caught up. Gating on the open class
     instead closes that window immediately, matching .pg-mobile-nav's
     own already-correct base rule below exactly. */
  pointer-events: none;
}

.pg-mega-backdrop--open {
  pointer-events: auto;
}

.pg-mega {
  /* The same inset .pg-header-inner's own .container padding produces
     (half the (100vw - container-max-width) side margin, plus the
     container's own 0.75rem padding) — .pg-header spans the full
     viewport, so without this the panel's left edge would sit flush
     against the browser edge instead of lining up with "Ureh"/
     "Products" above it. Previously left:0 did exactly that (flush to
     the viewport) — this aligns it with the header content's own left
     edge instead. */
  --pg-mega-inset: calc((100vw - min(100vw, var(--pg-max-width))) / 2 + .75rem);
  position: absolute;
  top: calc(100% + .5rem);
  left: var(--pg-mega-inset);
  /* Width still capped well short of the viewport (never full-bleed),
     and now also symmetric around the inset above so the panel never
     runs past the right edge at narrower "laptop" widths either. */
  width: min(960px, calc(100vw - 2 * var(--pg-mega-inset)));
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .75rem);
  box-shadow: 0 16px 40px -8px rgba(0, 0, 0, .16), 0 2px 8px rgba(0, 0, 0, .04);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity .15s ease-out, transform .15s ease-out;
  z-index: 999;
  /* Real bug, reproduced live (headless Chromium, not reasoned from the
     CSS): the panels had no pointer-events gate at rest — the identical
     gap .pg-mega-backdrop above already documents for itself, but never
     applied to the panels themselves. All three panels are absolutely
     positioned at the SAME top/left with the same z-index, so they
     overlap exactly; only [hidden] kept a closed one out of hit-testing.
     assets/js/nav.js's own closeMenu() delays that [hidden] by 150ms so
     the fade-out transition can play, while the next panel opens
     immediately — so for 150ms the closing panel is invisible (opacity 0)
     but still fully clickable, stacked over the one that just opened.
     Resources is LAST in DOM order (components/header.php), so at equal
     z-index it paints above Products/Developers and wins that hit test:
     moving Resources -> Developers and clicking "API Reference" (2nd item,
     1st column) landed on /blog instead, because Resources' own 2nd item
     in that same column is Blog. Gating on the open class closes the
     window immediately, exactly as the backdrop rule above does. */
  pointer-events: none;
}

.pg-mega--narrow {
  width: min(640px, calc(100vw - 2 * var(--pg-mega-inset)));
}

.pg-mega--open {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.pg-mega-inner {
  padding: 1.75rem;
  display: grid;
  gap: 2rem;
}

/* Named by column count, not by which menu uses them — Products and
   Developers both need 3 now (see components/header.php), Resources
   stays at 2 (LEARN/SUPPORT has no more real, verified content to add a
   3rd column with). */
.pg-mega-inner--cols-3 {
  grid-template-columns: repeat(3, 1fr);
}

.pg-mega-inner--cols-2 {
  grid-template-columns: repeat(2, 1fr);
}

.pg-mega-col--center {
  align-self: center;
}

.pg-mega-col-title {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-secondary-color);
  margin-bottom: .75rem;
}

.pg-mega-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .125rem;
}

.pg-mega-item {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: .5rem .5rem;
  margin: 0 -.5rem;
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
  transition: background-color .12s ease;
}

.pg-mega-item:hover {
  background: var(--bs-tertiary-bg);
}

.pg-mega-item-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.875rem;
  height: 1.875rem;
  flex-shrink: 0;
  border-radius: .4375rem;
  background: var(--bs-tertiary-bg);
  color: var(--pg-accent);
}

.pg-mega-item-body {
  display: flex;
  flex-direction: column;
  gap: .125rem;
  min-width: 0;
}

.pg-mega-item-title {
  font-weight: 600;
  font-size: .875rem;
  display: flex;
  align-items: center;
  gap: .5rem;
}

.pg-mega-item-desc {
  font-size: .75rem;
  color: var(--bs-secondary-color);
  line-height: 1.4;
}

/* ---------------------------------------------------------------------
 * 6. Mobile nav drawer
 * ------------------------------------------------------------------- */
.pg-scroll-lock {
  overflow: hidden;
}

.pg-mobile-nav {
  --pg-drawer-width: min(360px, 88vw);
  position: fixed;
  inset: 0;
  z-index: 1100;
  background: rgba(0, 0, 0, .25);
  opacity: 0;
  transition: opacity .2s ease;
  pointer-events: none;
}

.pg-mobile-nav--open {
  opacity: 1;
  pointer-events: auto;
}

/* The one sliding panel — header/body/footer (below) are its direct,
   stacked children; the panel itself is what translates on/off screen,
   not each of them independently. */
.pg-mobile-nav-panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--pg-drawer-width);
  background: var(--bs-body-bg);
  box-shadow: -8px 0 32px rgba(0, 0, 0, .12);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform .2s ease;
}

.pg-mobile-nav--open .pg-mobile-nav-panel {
  transform: translateX(0);
}

.pg-mobile-nav-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--bs-border-color);
  flex-shrink: 0;
}

.pg-mobile-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  background: transparent;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
}

.pg-mobile-nav-body {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: 1rem 1.25rem;
  overflow-y: auto;
  flex: 1 1 auto;
}

.pg-mobile-group-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: transparent;
  border: none;
  padding: .875rem .25rem;
  font-weight: 600;
  font-size: 1rem;
  color: var(--bs-body-color);
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-mobile-group-trigger[aria-expanded="true"] .pg-nav-caret {
  transform: rotate(180deg);
}

.pg-mobile-group-panel {
  padding: .5rem .25rem 1rem;
  display: flex;
  flex-direction: column;
  gap: .125rem;
}

.pg-mobile-col-title {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-secondary-color);
  margin: .75rem 0 .375rem;
}

.pg-mobile-col-title:first-child {
  margin-top: 0;
}

.pg-mobile-item {
  display: flex;
  align-items: center;
  gap: .625rem;
  /* ~44px effective row height at this font-size — a real touch
     target, not the Phase 1 draft's tighter .5rem. */
  padding: .75rem .25rem;
  color: var(--bs-body-color);
  font-size: .9375rem;
  border-radius: var(--bs-border-radius);
}

.pg-mobile-item:hover {
  background: var(--bs-tertiary-bg);
}

.pg-mobile-item-icon {
  display: inline-flex;
  color: var(--pg-accent);
  flex-shrink: 0;
}

.pg-mobile-simple {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: 1rem .25rem 0;
}

.pg-mobile-simple-item {
  padding: .5rem 0;
}

/* Real bug, confirmed live: the mobile drawer's GitHub badge is
   <span class="pg-mobile-simple-item pg-github-action">, and this rule
   and .pg-github-action's own padding are equal specificity (0,1,0
   each) — whichever is later in the file wins the whole shorthand, so
   this rule's "padding: .5rem 0" was overwriting .pg-github-action's
   intended horizontal padding with 0, leaving the icon flush against
   the badge's left border (measured: 0px left padding, icon 1px from
   the edge — just the border). A compound selector here is (0,2,0),
   definitively higher than either single-class rule regardless of
   source order, so this always wins without depending on which rule
   happens to come first. Same padding .pg-github-action itself uses on
   desktop, so the badge looks identical in both places. */
.pg-mobile-simple-item.pg-github-action {
  padding: .5rem .75rem;
}

.pg-mobile-nav-footer {
  display: flex;
  gap: .75rem;
  padding: 1.25rem;
  border-top: 1px solid var(--bs-border-color);
  flex-shrink: 0;
}

.pg-mobile-nav-footer .pg-action-link {
  flex: 1;
  justify-content: center;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
}

.pg-mobile-nav-footer .pg-btn {
  flex: 1;
  justify-content: center;
}

/* ---------------------------------------------------------------------
 * 7. Hero
 * ------------------------------------------------------------------- */
.pg-hero {
  position: relative;
  padding: 5rem 0 6rem;
  overflow: hidden;
}

.pg-hero-grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(var(--bs-primary-rgb), .05) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--bs-primary-rgb), .05) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(ellipse 80% 60% at 30% 0%, #000 40%, transparent 100%);
  mask-image: radial-gradient(ellipse 80% 60% at 30% 0%, #000 40%, transparent 100%);
}

.pg-hero-inner {
  position: relative;
}

.pg-eyebrow {
  display: inline-flex;
  align-items: center;
  font-size: .8125rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--pg-accent);
  margin-bottom: 1rem;
}

.pg-hero-title {
  /* Was clamp(2.25rem, 1.6rem + 2.4vw, 3.25rem) — the 52px cap forced a
     ~50-character headline into 3 uneven lines inside its own half-width
     column (.pg-hero-copy is col-lg-6, ~600px at a 1280px container),
     since the preferred value already exceeded the cap above ~1100px
     viewport. Lowering the cap to 46px is what actually lets the phrase
     settle into 2 lines instead of 3 in that same column width. */
  font-size: clamp(2.25rem, 1.75rem + 2vw, 2.875rem);
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: -.01em;
  margin-bottom: 1.25rem;
  color: var(--bs-emphasis-color, var(--bs-body-color));
  /* Balances line lengths instead of leaving a short, awkward final
     line — no-op in browsers that don't support it yet (plain wrap). */
  text-wrap: balance;
}

.pg-hero-lead {
  font-size: 1.01rem;
  /* font-size: 1.125rem; */
  line-height: 1.6;
  color: var(--bs-secondary-color);
  max-width: 34rem;
  margin-bottom: 2rem;
}

.pg-hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.pg-hero-signin {
  font-size: .875rem;
  color: var(--bs-secondary-color);
}

.pg-hero-signin a {
  font-weight: 600;
  color: var(--pg-accent);
}

.pg-hero-signin a:hover {
  text-decoration: underline;
}

/* --- Infrastructure flow strip ------------------------------------- */
.pg-flow {
  display: flex;
  align-items: center;
  gap: .625rem;
  margin-bottom: 1.25rem;
  flex-wrap: wrap;
}

.pg-flow-node {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: .75rem;
  font-weight: 600;
  color: var(--bs-secondary-color);
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-pill, 50rem);
  padding: .375rem .75rem .375rem .5rem;
}

.pg-flow-node svg {
  color: var(--bs-secondary-color);
}

.pg-flow-node--brand {
  color: var(--pg-accent);
  border-color: rgba(var(--bs-primary-rgb), .3);
  background: rgba(var(--bs-primary-rgb), .08);
}

.pg-flow-node--brand svg {
  color: var(--pg-accent);
}

.pg-flow-line {
  width: 1.5rem;
  height: 1px;
  background: var(--bs-border-color);
  flex-shrink: 0;
}

/* --- API panel ------------------------------------------------------ */
.pg-api-panel {
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .5rem);
  box-shadow: 0 24px 48px -24px rgba(0, 0, 0, .18);
  overflow: hidden;
}

.pg-api-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .875rem 1.125rem;
  border-bottom: 1px solid var(--bs-border-color);
  background: var(--bs-tertiary-bg);
}

.pg-api-request-line {
  display: flex;
  align-items: center;
  gap: .625rem;
  font-family: var(--font-ui-mono);
  font-size: var(--pg-code-font-size);
  min-width: 0;
}

.pg-api-method {
  flex-shrink: 0;
  font-weight: 700;
  color: #fff;
  background: var(--bs-primary);
  border-radius: .25rem;
  padding: .125rem .5rem;
  letter-spacing: .02em;
}

.pg-api-path {
  color: var(--bs-body-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pg-api-copy {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  flex-shrink: 0;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  padding: .3125rem .625rem;
  font-size: .8125rem;
  font-weight: 500;
  color: var(--bs-body-color);
}

.pg-api-copy:hover {
  background: var(--bs-tertiary-bg);
}

.pg-api-copy-icon--done {
  display: none;
  color: var(--bs-success);
}

.pg-api-copy--done .pg-api-copy-icon {
  display: none;
}

.pg-api-copy--done .pg-api-copy-icon--done {
  display: inline-flex;
}

.pg-api-panel-body {
  display: flex;
  flex-direction: column;
}

.pg-api-block {
  padding: 1rem 1.125rem 1.125rem;
}

.pg-api-block + .pg-api-block {
  border-top: 1px solid var(--bs-border-color);
}

.pg-api-block-label {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-secondary-color);
  margin-bottom: .625rem;
  display: flex;
  align-items: center;
  gap: .5rem;
}

.pg-api-status {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0;
  /* Bootstrap's own subtle-warning tokens (compiled into vendors/
     main.css already), not a hand-picked hex — same "processing, not
     final" signal docs_2/ui-design-system.md maps pending/info states
     to elsewhere in the product. */
  color: var(--bs-warning-text-emphasis);
  background: var(--bs-warning-bg-subtle);
  border-radius: var(--bs-border-radius-pill, 50rem);
  padding: .0625rem .5rem;
}

.pg-api-code {
  /* font-family/font-size/line-height all come from the global
     .pg-body pre rule (section 1) — this is a <pre>, so it already
     inherits the one standard code size (--pg-code-font-size, .8125rem/
     13px, matching dev-ui's own "compact data" tier) and line-height
     (--pg-code-line-height, 1.7 — the extra air is what keeps that
     smaller size from reading cramped). Nothing left to declare here. */
  margin: 0;
  color: var(--bs-body-color);
  white-space: pre;
  overflow-x: auto;
}

/* Real bug: .pg-section :where(code) (section 10) — an inline-mention
   chip style meant for a short `code` word inside a sentence — has zero
   specificity by design, but nothing here was overriding it for the
   bare <code> inside a multi-line <pre>. Since that <code> is `display:
   inline` and its content spans many lines (literal "\n"s under
   white-space: pre), the chip's own background/padding/radius rendered
   as a small pill at the START OF EVERY LINE (each wrapped line
   fragment of one inline box gets its own painted background) — visible
   as odd grey rounded marks before every row of a JSON/cURL example.
   This resets it back to plain, un-chipped text for any <code> living
   inside a real code block. */
.pg-api-code code {
  background: none;
  padding: 0;
  border-radius: 0;
  font-size: inherit;
}

/* Toned down from a more saturated violet (#7c3aed) — closer to the
   restrained, documentation-grade palette this section is going for;
   still clearly its own hue against tok-str's green and tok-num's amber. */
.tok-key { color: #5b4fc4; }
.tok-str { color: #0d7a4a; }
.tok-num { color: #b8560f; }
.tok-bool,
.tok-null { color: #b8560f; }
.tok-punc { color: var(--bs-secondary-color); }
.tok-comment { color: var(--bs-tertiary-color); }

/* Light-mode-only in spirit even though not scoped by selector name:
   #0d7a4a/#b8560f were tuned for contrast against a white code panel
   and are too dark to read against dark mode's own near-black panel
   (--bs-body-bg #19191a) — a real legibility bug, not a hypothetical
   one, confirmed by computing both pairs' contrast. Brightened
   versions in the same hue family (violet/green/orange), not a new
   palette. .tok-punc/.tok-comment already read theme-aware
   --bs-secondary-color/--bs-tertiary-color and need no override. */
[data-bs-theme="dark"] .tok-key { color: #c4b5fd; }
[data-bs-theme="dark"] .tok-str { color: #6ee7b7; }
[data-bs-theme="dark"] .tok-num,
[data-bs-theme="dark"] .tok-bool,
[data-bs-theme="dark"] .tok-null { color: #fdba74; }

/* ---------------------------------------------------------------------
 * 8. Footer
 * ------------------------------------------------------------------- */
.pg-footer {
  border-top: 1px solid var(--bs-border-color);
  padding: 4.5rem 0 2rem;
}

.pg-footer-tagline {
  font-size: .875rem;
  color: var(--bs-secondary-color);
  margin-top: .75rem;
  margin-bottom: 0;
}

/* Sits directly beneath the tagline (components/footer.php) — currently
   just the one honest GitHub placeholder (see that file's own comment:
   no other verified social account exists to show beside it). */
.pg-footer-social {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: 1.25rem;
}

/* Icon-only, unlike the header's .pg-github-action (icon+label pill) —
   7 platforms as text pills side by side would be far too wide for the
   brand column; a compact icon row is also the more conventional footer
   social-row treatment. Same honest non-interactive/muted styling as
   every other placeholder on the page (see components/footer.php's own
   comment: none of these accounts have a verified URL). */
.pg-social-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  color: var(--bs-secondary-color);
  cursor: default;
  flex-shrink: 0;
}

.pg-footer-col-title {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-secondary-color);
  margin-bottom: 1rem;
}

.pg-footer-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .625rem;
  font-size: .875rem;
}

.pg-footer-list a {
  color: var(--bs-body-color);
}

.pg-footer-list a:hover {
  color: var(--pg-accent);
}

.pg-footer-bottom {
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--bs-border-color);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  font-size: .8125rem;
  color: var(--bs-secondary-color);
}

.pg-footer-bottom p {
  margin: 0;
}

.pg-footer-bottom-actions {
  display: flex;
  align-items: center;
  gap: 1.25rem;
}

/* The toggle is a vendor .btn.btn-icon (38px, its own padding/border
   already zeroed by [class*=btn-flush-] — vendors/main.css) sitting
   next to two plain text links; align-items: center above keeps it
   vertically centered against their line height, and this just trims
   its own comfortable-but-oversized default footprint down to match
   the text links' visual weight rather than dominating the row. */
.pg-theme-toggle {
  width: 2rem;
  height: 2rem;
  color: var(--bs-secondary-color);
}

/* Real bug, confirmed live: vendors/main.css's own [class*=btn-flush-]
   convention grows a ::before pseudo-element from 0x0 to fill the
   entire button (top/left/height/width all animate to 100%) on
   hover/focus/active, painted as a solid fill. That fill is a
   *positioned* layer, so it paints above the icon SVG (which has no
   stacking context of its own, position: static) regardless of DOM
   order — the icon was never actually removed or recolored wrong, it
   was sitting directly underneath an opaque circle the whole time.
   The vendor's own paired rule that would keep the icon visible on top
   (`:hover .icon { color: var(--bs-white) }`) never matches here either
   — it requires a literal class="icon" this page's pg_icon() output
   doesn't add. Scoped to this button's own #id (specificity comfortably
   beats the vendor's class-based selectors, no !important needed) so
   dev-ui/admin-ui's own header toggle — same vendor classes, real class
   ="icon" markup, meant to have this effect — is completely unaffected. */
#theme-toggle::before {
  background: transparent;
}

.pg-footer-bottom-actions a:hover {
  color: var(--pg-accent);
}

/* Groups the copyright line with the Privacy/Terms links as one flex
   item, so .pg-footer-bottom's own space-between still only ever sees
   two top-level groups (this one, and .pg-footer-bottom-actions) —
   adding the links as a bare third child would instead have split them
   apart to the row's dead center. */
.pg-footer-bottom-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .25rem 1.25rem;
}

.pg-footer-legal-links {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}

.pg-footer-legal-links a:hover {
  color: var(--pg-accent);
}

/* ---------------------------------------------------------------------
 * 9. Responsive
 * ------------------------------------------------------------------- */
@media (max-width: 991.98px) {
  .pg-nav,
  .pg-action-link {
    display: none !important;
  }

  .pg-header-actions {
    gap: .625rem;
  }

  .pg-mega,
  .pg-mega-backdrop {
    display: none !important;
  }
}

@media (max-width: 767.98px) {
  .pg-hero {
    padding: 3rem 0 4rem;
  }

  .pg-hero-visual {
    margin-top: 2.5rem;
  }

  .pg-api-request-line {
    font-size: .75rem;
  }

  /* No .pg-mega-inner--cols-* override here: the mega menu itself is
     already display:none at the 991.98px breakpoint above, so a
     narrower one would never actually apply — removed as dead code. */

  .pg-footer-bottom {
    flex-direction: column;
    align-items: flex-start;
  }
}

@media (min-width: 992px) and (max-width: 1199.98px) {
  .pg-mega-inner--cols-3 {
    gap: 1.75rem;
  }
}

/* =======================================================================
 * PHASE 2 — Product Platform & Developer Experience
 * Everything below is additive: Phase 1's header/mega menu/mobile nav/
 * hero/footer rules above are untouched. New sections reuse Phase 1's
 * tokens and several of its component classes verbatim (.pg-btn,
 * .pg-flow-node/.pg-flow-line, .pg-api-panel/.pg-api-code/.tok-*,
 * .pg-action-link) rather than duplicating them.
 * ===================================================================== */

/* ---------------------------------------------------------------------
 * 10. Section shell + shared heading block
 * ------------------------------------------------------------------- */
.pg-section {
  padding: 6rem 0;
}

.pg-section--tint {
  background: var(--bs-tertiary-bg);
}

.pg-section-head {
  max-width: 40rem;
  margin-bottom: 3rem;
}

.pg-section-head--center {
  max-width: 32rem;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

.pg-section-title {
  font-size: clamp(1.75rem, 1.5rem + 1vw, 2.375rem);
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -.015em;
  margin-bottom: .75rem;
  text-wrap: balance;
}

.pg-section-lead {
  font-size: 1.0104rem;
  line-height: 1.6;
  color: var(--bs-secondary-color);
  margin-bottom: 0;
}

/* :where() keeps this at zero specificity, so any component's own code
   styling (.pg-journey-tag, .pg-webhook-event-name,
   .pg-console-table-row code, ...) always wins outright rather than
   needing to out-specify a bare ".pg-section code" descendant rule.
   font-family/font-size aren't declared here any more — the global
   .pg-body code rule (section 1) already gives every <code> the one
   standard size; this rule only adds the inline-mention chip look
   (background/radius/padding) on top of it. The old .875em here was a
   real inconsistency this pass fixes: a *relative* size meant this
   chip rendered at a different pixel size in every paragraph it sat in
   (~15.75px in the hero's 1.125rem lead, ~14px in plain body text) —
   the fixed token now used gives it one size everywhere. */
.pg-section :where(code) {
  background: var(--bs-tertiary-bg);
  border-radius: .25rem;
  padding: .0625rem .3125rem;
}

.pg-api-method--neutral {
  background: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

.pg-api-method--sm {
  font-size: .6875rem;
  padding: .0625rem .4375rem;
}

/* ---------------------------------------------------------------------
 * 11. Platform & Developer Journey
 * Product Platform and Developer Journey used to be two separate
 * full-width sections (with Code Experience sandwiched between them).
 * They're now one section, rendered as two open columns — "what you can
 * build" and "how you ship it" as two chapters of the same design
 * system rather than two unrelated blocks. .pg-pj-row is deliberately
 * unstyled (no border/background/shadow of its own): that outer boxed
 * card read as a table/container-within-a-container once the section
 * already sits on the page's own open background. What still gives each
 * column real structure is one level down — the row-head's own hairline
 * rule under its title, Platform's individually-bordered capability
 * cards (.pg-platform-item), and Journey's own numbered-step rail — so
 * "clean and open" doesn't mean "unstructured." Heights are NOT forced
 * equal (align-items: start below): Journey's 8 steps are naturally
 * taller than Platform's shortest tab (Infrastructure, 2 items), and
 * stretching the shorter column to match would leave a slab of dead air
 * in it — a worse "premium" signal than two honestly-sized columns.
 * ------------------------------------------------------------------- */
.pg-pj-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.75rem;
  align-items: start;
}

.pg-pj-row-head {
  padding: 1.625rem 1.75rem 1.25rem;
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-pj-row-eyebrow {
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-secondary-color);
  margin-bottom: .375rem;
}

.pg-pj-row-title {
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.3;
  margin: 0;
}

/* --- Product platform row: horizontal pill tabs + panel below. Always
   this shape now (not just <992px) — the row lives in a half-width
   column at every viewport wide enough to sit side by side with
   Journey, so the old wide-viewport-only vertical sidebar variant no
   longer has a context to appear in. */
.pg-platform-tablist {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  padding: 1.25rem 1.75rem;
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-platform-tab {
  background: transparent;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-pill, 50rem);
  color: var(--bs-body-color);
  font-weight: 600;
  font-size: .8125rem;
  padding: .4375rem .875rem;
}

.pg-platform-tab:hover {
  border-color: var(--bs-secondary-color);
}

.pg-platform-tab[aria-selected="true"] {
  background: var(--bs-primary);
  border-color: var(--bs-primary);
  color: #fff;
}

.pg-platform-panels {
  padding: 1.5rem 1.75rem 1.75rem;
  /* Stabilizer only, so switching to the shortest tab (Infrastructure)
     never flashes to near-zero height — not an attempt to equalize
     every tab's real height (see the section comment above). */
  min-height: 3rem;
}

.pg-platform-panel-summary {
  font-size: .875rem;
  line-height: 1.5;
  color: var(--bs-secondary-color);
  margin-bottom: 1.25rem;
}

.pg-platform-endpoint {
  display: inline-flex;
  align-items: center;
  gap: .625rem;
  /* Was .875rem (14px) — inconsistent with the equivalent request-line
     display elsewhere (.pg-api-request-line, Hero/Webhooks/Code
     Experience), which was already the standard 13px. Now both read
     the same token. */
  font-family: var(--font-ui-mono);
  font-size: var(--pg-code-font-size);
  margin-bottom: 1.5rem;
}

/* Each capability gets its own small bordered card (icon over title over
   description) rather than an icon+text row — a product-capability tile,
   not a data row. auto-fit is what gives tabs of different lengths a
   genuinely different composition instead of one fixed grid shape:
   Infrastructure's 2 items become two wide tiles; Payments/Developer
   Platform's 5 wrap into a denser 2-3-per-row grid — both from the same
   rule, no per-tab CSS needed. */
.pg-platform-item-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11.25rem, 1fr));
  gap: .75rem;
}

.pg-platform-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .625rem;
  padding: 1rem;
  border: 1px solid var(--bs-border-color);
  border-radius: .625rem;
  background: var(--bs-body-bg);
  transition: border-color .15s ease;
}

.pg-platform-item:hover {
  border-color: var(--bs-secondary-color);
}

.pg-platform-item-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
  border-radius: .5rem;
  background: var(--bs-tertiary-bg);
  color: var(--pg-accent);
}

.pg-platform-item-body {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  min-width: 0;
}

.pg-platform-item-title {
  font-weight: 600;
  font-size: .875rem;
}

.pg-platform-item-title .pg-docs-badge {
  margin-left: .375rem;
  vertical-align: middle;
}

.pg-platform-item-desc {
  font-size: .8125rem;
  color: var(--bs-secondary-color);
  line-height: 1.45;
}

/* build-with-ureh.php's SDK cards only: dashed border is this site's
   own "honest placeholder" convention — solid borders mean real,
   finished content everywhere else on the page, so a dashed one here
   signals "planned, not shipped" without needing repeated disclaimer
   copy per card. */
.pg-platform-item--placeholder {
  border-style: dashed;
}

/* --- Developer journey row: the numbered step-marker + connecting-rail
   language translated from dev-ui's own uh_subnav() component
   (includes/helpers.php, assets/css/dev.css's .uh-step-marker) — denser
   than the old full-width version (this row now shares a half-width
   card with Platform at every viewport where they sit side by side). */
.pg-journey-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pg-journey-step {
  position: relative;
  display: flex;
  gap: 1rem;
  padding: 1.25rem 1.75rem;
}

.pg-journey-step + .pg-journey-step {
  border-top: 1px solid var(--bs-border-color);
}

/* Connects this row's marker down to the next row's — ends flush at
   this row's own bottom edge (where the next row's border-top sits).
   Offsets are derived from this row's own padding-left (1.75rem) and
   marker size (2.125rem): left = padding-left + marker-width / 2; top =
   padding-top + marker-height. */
.pg-journey-step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 2.8125rem;
  top: 3.375rem;
  bottom: 0;
  width: 1px;
  background: var(--bs-border-color);
}

.pg-journey-marker-col {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
}

/* Same soft-tint-on-brand language as .pg-brand-mark/.pg-mega-item-icon
   (rgba(--bs-primary-rgb) tint + solid --bs-primary content) — NOT
   Bootstrap's --bs-primary-text-emphasis/-bg-subtle tokens, which stay
   tied to Bootstrap's default blue and are never re-pointed at the
   actual Ureh slate by the "Enterprise" theme layer (verified in
   vendors/main.css) — using them here would render a blue-tinted chip
   that clashes with the rest of the page. */
.pg-journey-marker {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 50%;
  background: rgba(var(--bs-primary-rgb), .1);
  color: var(--pg-accent);
  font-weight: 700;
  font-size: .75rem;
}

.pg-journey-body {
  min-width: 0;
  padding-top: .0625rem;
}

.pg-journey-step-label {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--pg-accent);
  margin-bottom: .25rem;
}

.pg-journey-title {
  font-weight: 600;
  font-size: .9375rem;
  margin-bottom: .25rem;
}

.pg-journey-desc {
  font-size: .8125rem;
  line-height: 1.45;
  color: var(--bs-secondary-color);
  margin-bottom: .5rem;
}

/* code.pg-journey-tag, not .pg-journey-tag alone: a bare single class
   is (0,1,0) — lower specificity than the global ".pg-body code" rule
   (section 1, (0,1,1)), which would otherwise win the font-size cascade
   and leave this stuck at the 13px base instead of the smaller badge
   size below. */
code.pg-journey-tag {
  display: inline-block;
  font-size: var(--pg-code-font-size-sm);
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: .25rem;
  padding: .125rem .5rem;
}

/* At >=992px the Journey column sits beside Platform in a half-width
   column — a single 8-step vertical list there runs nearly twice as
   tall as Platform's tallest tab, an unbalanced pairing. Splitting into
   two 4-step columns (steps 1-4, then 5-8, via grid-auto-flow: column —
   DOM order stays 1..8, so reading/keyboard order is unaffected) brings
   the two columns to comparable heights. The nth-child overrides below
   are hardcoded to this exact 8-step/2-column/4-row shape since the
   journey's step count is fixed, verified content, not a
   variable-length list. */
@media (min-width: 992px) {
  .pg-journey-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: repeat(4, auto);
    grid-auto-flow: column;
  }

  /* Step 5 starts a new column — no border-top (it isn't stacked under
     step 4) and a border-left instead, dividing the two columns. */
  .pg-journey-step:nth-child(5) {
    border-top: none;
  }

  .pg-journey-step:nth-child(n+5) {
    border-left: 1px solid var(--bs-border-color);
  }

  /* Step 4 is the last row of column 1 — no connecting line down to
     step 5, which sits beside it, not below it. */
  .pg-journey-step:nth-child(4)::before {
    display: none;
  }
}

/* ---------------------------------------------------------------------
 * 12. Code Experience
 * ------------------------------------------------------------------- */
.pg-code-tabs {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 1.5rem;
  align-items: start;
}

.pg-code-panel-frame {
  /* Real bug, caught live via headless-browser measurement at 390px:
     this is a CSS grid item (.pg-code-tabs) with no min-width set, so
     it defaulted to min-width:auto — which for a grid/flex item means
     "never shrink narrower than my content's own minimum," and a
     white-space:pre code block's minimum IS its full unwrapped width.
     The grid track grew to fit that instead of the track's actual
     available width, which is exactly what silently defeated
     .pg-api-code's own overflow-x:auto below (it never needed to
     scroll — its ancestor had already grown around it). min-width:0
     is what lets this item actually respect the grid's real width. */
  min-width: 0;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .5rem);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
  overflow: hidden;
}

.pg-code-tablist {
  display: flex;
  /* Deliberately the same white as .pg-code-panel-frame, not
     --bs-tertiary-bg — this section (.pg-section--tint) already sits on
     a tertiary-bg page background, so a tertiary-bg tab strip on top of
     the white card would exactly match the page behind it and read as a
     notch cut out of the card rather than an integrated tab bar. */
  background: var(--bs-body-bg);
  border-bottom: 1px solid var(--bs-border-color);
  padding: 0 .5rem;
}

.pg-code-tab {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--bs-secondary-color);
  font-weight: 600;
  font-size: .875rem;
  padding: .75rem .875rem;
  margin-bottom: -1px;
}

.pg-code-tab[aria-selected="true"] {
  color: var(--pg-accent);
  border-bottom-color: var(--bs-primary);
}

.pg-code-panel {
  position: relative;
  padding: 1rem;
}

.pg-code-copy {
  position: absolute;
  top: .75rem;
  right: .75rem;
}

.pg-code-response {
  /* Same fix as .pg-code-panel-frame's own comment above — the sibling
     grid item, same underlying risk. */
  min-width: 0;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .5rem);
  padding: 1rem;
}

.pg-code-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1.5rem;
  font-size: .875rem;
}

@media (max-width: 575.98px) {
  .pg-journey-step {
    padding: 1rem 1.25rem;
    gap: .75rem;
  }

  .pg-journey-step:not(:last-child)::before {
    left: 2.1875rem;
    top: 3rem;
  }

  .pg-journey-marker {
    width: 1.875rem;
    height: 1.875rem;
    font-size: .6875rem;
  }

  .pg-pj-row-head {
    padding: 1.375rem 1.25rem 1rem;
  }

  .pg-platform-tablist,
  .pg-platform-panels {
    padding-left: 1.25rem;
    padding-right: 1.25rem;
  }
}

/* ---------------------------------------------------------------------
 * 13. Webhooks
 * ------------------------------------------------------------------- */
.pg-webhook-flow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin: 1.5rem 0;
}

.pg-webhook-facts {
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .75rem;
  font-size: .875rem;
}

.pg-webhook-facts li {
  display: flex;
  align-items: flex-start;
  gap: .625rem;
}

.pg-webhook-facts li svg {
  flex-shrink: 0;
  color: var(--pg-accent);
  margin-top: .125rem;
}

.pg-webhook-event-name {
  display: inline-block;
  font-family: var(--font-ui-mono);
  font-size: var(--pg-code-font-size-sm);
  font-weight: 400;
  background: var(--bs-tertiary-bg);
  border-radius: .25rem;
  padding: .0625rem .5rem;
  margin-left: .375rem;
}

.pg-webhook-caption {
  font-size: .75rem;
  color: var(--bs-tertiary-color);
  margin: .625rem 0 0;
}

/* ---------------------------------------------------------------------
 * 14. Architecture
 * ------------------------------------------------------------------- */
.pg-arch {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 1.5rem 0 .5rem;
}

.pg-arch-tier {
  display: flex;
  justify-content: center;
  width: 100%;
}

.pg-arch-node {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .75rem 1.25rem;
  border: 1px solid var(--bs-border-color);
  border-radius: .625rem;
  font-weight: 600;
  font-size: .875rem;
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
}

.pg-arch-node--api {
  border-color: rgba(var(--bs-primary-rgb), .35);
  background: rgba(var(--bs-primary-rgb), .08);
  color: var(--pg-accent);
}

/* The diagram's origin point — a solid, inverted chip (rather than the
   same outline every other node uses) so the eye has one clear place to
   start reading: App (solid/dark) -> API (brand) -> Services (light
   leaf chips), three distinct tiers instead of one repeated box style. */
.pg-arch-node--app {
  background: var(--bs-body-color);
  border-color: var(--bs-body-color);
  color: var(--bs-body-bg);
}

.pg-arch-connector {
  position: relative;
  width: 1px;
  height: 2.25rem;
  background: var(--bs-border-color);
}

/* The branch point (API -> the services row) gets a small junction dot
   where it meets the horizontal bus bar below — a standard technical-
   diagram convention marking "this is where it splits," distinguishing
   it from the plain 1:1 link above it (App -> API). */
.pg-arch-connector--branch::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--bs-border-color);
  transform: translate(-50%, 50%);
}

.pg-arch-tier--services {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem 1.5rem;
  width: auto;
  margin: 0 auto;
  padding-top: 1.5rem;
  border-top: 1px solid var(--bs-border-color);
}

.pg-arch-node--service {
  font-size: .8125rem;
  font-weight: 500;
  padding: .625rem 1rem;
}

.pg-arch-node--service svg {
  color: var(--pg-accent);
}

.pg-arch-foundation {
  position: relative;
  margin-top: 2.75rem;
  padding: .875rem 1.75rem;
  border: 1px dashed var(--bs-border-color);
  border-radius: .625rem;
  /* White card, not tertiary-bg: this section is now .pg-section--tint
     (tertiary-bg background) — the band needs to read as a distinct
     raised layer against that tint, the same "white card pops off a
     tinted section" pattern .pg-api-panel/.pg-code-panel-frame already
     use elsewhere, not blend invisibly into a same-colored section. */
  background: var(--bs-body-bg);
  text-align: center;
  display: inline-flex;
  flex-direction: column;
  gap: .25rem;
  font-weight: 600;
  font-size: .875rem;
}

.pg-arch-foundation::before {
  content: "";
  position: absolute;
  top: -2.25rem;
  left: 50%;
  width: 1px;
  height: 2.25rem;
  background: var(--bs-border-color);
}

.pg-arch-foundation-sub {
  font-size: .75rem;
  font-weight: 400;
  color: var(--bs-secondary-color);
}

/* ---------------------------------------------------------------------
 * 15. Security & API Controls
 * ------------------------------------------------------------------- */
.pg-security-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.75rem 2.5rem;
  margin: 0;
  padding: 0;
}

.pg-security-item dt {
  display: flex;
  align-items: center;
  gap: .625rem;
  font-weight: 600;
  font-size: .9375rem;
  margin-bottom: .375rem;
}

.pg-security-item-icon {
  display: inline-flex;
  color: var(--pg-accent);
  flex-shrink: 0;
}

.pg-security-item dd {
  margin: 0;
  font-size: .875rem;
  color: var(--bs-secondary-color);
  line-height: 1.5;
}

.pg-security-account-note {
  font-size: .8125rem;
  color: var(--bs-secondary-color);
  border-left: 2px solid var(--bs-border-color);
  padding-left: 1rem;
  margin-top: 1.75rem;
  line-height: 1.6;
}

/* ---------------------------------------------------------------------
 * 16. Developer Console preview
 * ------------------------------------------------------------------- */
.pg-console-frame {
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .5rem);
  overflow: hidden;
  background: var(--bs-body-bg);
  box-shadow: 0 24px 48px -24px rgba(0, 0, 0, .18);
}

.pg-console-chrome {
  display: flex;
  align-items: center;
  gap: .375rem;
  padding: .625rem .875rem;
  background: var(--bs-tertiary-bg);
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-console-dot {
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--bs-border-color);
}

.pg-console-url {
  margin-left: .5rem;
  font-family: var(--font-ui-mono);
  font-size: var(--pg-code-font-size-sm);
  color: var(--bs-secondary-color);
}

.pg-console-body {
  display: flex;
  min-height: 280px;
}

.pg-console-sidebar {
  width: 150px;
  flex-shrink: 0;
  border-right: 1px solid var(--bs-border-color);
  padding: .75rem .5rem;
  display: flex;
  flex-direction: column;
  gap: .125rem;
}

.pg-console-nav-item {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .625rem;
  border-radius: var(--bs-border-radius);
  font-size: .8125rem;
  color: var(--bs-secondary-color);
}

.pg-console-nav-item svg {
  flex-shrink: 0;
}

.pg-console-nav-item--active {
  background: var(--bs-tertiary-bg);
  color: var(--pg-accent);
  font-weight: 600;
}

.pg-console-main {
  flex: 1;
  min-width: 0;
  padding: 1.25rem;
}

.pg-console-kpi {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  margin-bottom: 1.25rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-console-kpi-label {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--bs-secondary-color);
}

.pg-console-kpi-value {
  font-size: 1.5rem;
  font-weight: 700;
  font-family: var(--font-ui-mono);
}

.pg-console-table {
  display: flex;
  flex-direction: column;
  gap: .125rem;
}

.pg-console-table-row {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  gap: .75rem;
  align-items: center;
  font-size: .8125rem;
  padding: .5rem 0;
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-console-table-row:last-child {
  border-bottom: none;
}

.pg-console-table-row code {
  font-size: var(--pg-code-font-size-sm);
  background: none;
  padding: 0;
  color: var(--bs-secondary-color);
}

.pg-console-table-amount {
  font-weight: 600;
  text-align: right;
}

.pg-console-caption {
  font-size: .75rem;
  color: var(--bs-tertiary-color);
  text-align: center;
  margin: .75rem 0 0;
}

/* ---------------------------------------------------------------------
 * 17. Developer Experience journey (website -> docs -> console)
 * ------------------------------------------------------------------- */
.pg-dx-flow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .625rem;
}

.pg-dx-node {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .625rem 1rem;
  border: 1px solid var(--bs-border-color);
  border-radius: .625rem;
  font-size: .875rem;
  font-weight: 600;
  color: var(--bs-body-color);
  background: var(--bs-body-bg);
}

a.pg-dx-node:hover {
  border-color: var(--bs-primary);
  color: var(--pg-accent);
}

.pg-dx-arrow {
  display: inline-flex;
  color: var(--bs-tertiary-color);
  flex-shrink: 0;
}

/* ---------------------------------------------------------------------
 * 18. Phase 2 responsive
 * ------------------------------------------------------------------- */
@media (max-width: 991.98px) {
  /* Root-cause fix for a real, confirmed horizontal-overflow bug (found
     via headless-browser measurement, not theoretical): every split
     two-column section (hero, webhooks, security, console-preview) uses
     .row.g-5, and .g-5's own --bs-gutter-x is 3rem — .row's negative
     margin is half that (-1.5rem), which is 0.75rem more than
     .container's own 0.75rem padding compensates for, so the row's own
     edge sat 0.75rem past the viewport on each side and was genuinely
     reachable via a wheel/trackpad horizontal gesture. Below lg, every
     one of these rows is col-12 (stacked, not side-by-side), so the
     larger horizontal gutter was never doing anything visually anyway —
     dropping it back to the container's own default here removes the
     mismatch at its source instead of only clipping the symptom
     (.pg-body's overflow-x:hidden above is now a defensive second
     layer, not the only fix). --bs-gutter-y (vertical spacing between
     stacked columns) is untouched. */
  .g-5 {
    --bs-gutter-x: 1.5rem;
  }

  /* Platform and Journey stack full-width instead of sitting side by
     side once there's no longer room for two comfortable half-width
     columns (.pg-platform-tablist's pill row is the first thing to get
     cramped). */
  .pg-pj-grid {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }

  .pg-code-tabs {
    grid-template-columns: 1fr;
  }

  .pg-console-body {
    min-height: 0;
  }
}

@media (max-width: 767.98px) {
  .pg-section {
    padding: 3.5rem 0;
  }

  .pg-security-list {
    grid-template-columns: 1fr;
  }

  .pg-console-sidebar {
    display: none;
  }

  .pg-console-table-row {
    grid-template-columns: auto 1fr auto;
  }

  .pg-console-table-row code {
    display: none;
  }

  .pg-dx-flow {
    flex-direction: column;
    align-items: stretch;
  }

  .pg-dx-node {
    justify-content: center;
  }

  .pg-dx-arrow {
    transform: rotate(90deg);
    align-self: center;
  }

  .pg-arch-node {
    font-size: .8125rem;
    padding: .625rem .875rem;
  }
}

/* =======================================================================
 * PHASE 3 — Interactions, Responsive Polish, Final CTA
 * Additive only: sections 1–19 above (Phase 1 + Phase 2) are untouched
 * except for the two touch-target/padding fixes and the pg-fade-in hook
 * called out inline above.
 * ===================================================================== */

/* ---------------------------------------------------------------------
 * 19. Scroll reveal (assets/js/reveal.js)
 * Pre-visible state only exists once JS adds the class — see that
 * file's own comment on why a no-JS or slow-loading page never gets
 * stuck with hidden content. prefers-reduced-motion is handled in two
 * layers: reveal.js skips the hidden state entirely when it matches,
 * and section 2's own `.pg-body * { transition-duration: .001ms }`
 * neutralizes the transition below as a defensive second layer either
 * way. */
.pg-reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .6s ease, transform .6s ease;
}

.pg-reveal--visible {
  opacity: 1;
  transform: translateY(0);
}

/* ---------------------------------------------------------------------
 * 20. Tab-switch fade (assets/js/tabs.js)
 * ------------------------------------------------------------------- */
.pg-fade-in {
  animation: pg-fade-in .22s ease;
}

@keyframes pg-fade-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ---------------------------------------------------------------------
 * 21. Developer Experience — Easy Integration ("Integrate in minutes,
 * not days"). Not to be confused with section 17's "Developer
 * Experience journey" (.pg-dx-*, the website->docs->console flow strip)
 * — that's a navigation map; this is a capability recap.
 *
 * Composition is deliberate, not a repeated card grid: one bordered
 * showcase panel (.pg-sdk-panel) makes the SDKs the section's dominant
 * element, sized and weighted like a real product surface (a head strip
 * over a full-bleed rail, the same "head separated from body by one
 * hairline" recipe as Platform & Developer Journey's own row-heads) —
 * then the remaining capabilities drop to a single compact, unboxed
 * icon+text row (.pg-devx-fact-list) underneath, borrowing the "icon +
 * one line" idiom webhooks-section.php's own .pg-webhook-facts already
 * established, but muted (secondary-color, not primary) and laid out as
 * a wrapping grid instead of a stack — both changes are what make it
 * read as clearly secondary rather than a second copy of the same
 * component.
 * ------------------------------------------------------------------- */
.pg-sdk-panel {
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .75rem);
  background: var(--bs-body-bg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
  overflow: hidden;
  margin-bottom: 2rem;
}

.pg-sdk-panel-head {
  padding: 1.75rem 2rem 1.5rem;
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-sdk-panel-eyebrow {
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  /* Primary, not the muted secondary-color Platform/Journey's own row
     eyebrows use (.pg-pj-row-eyebrow) — this panel is the section's
     dominant element, so it gets the bolder treatment reserved for
     top-level eyebrows elsewhere (.pg-eyebrow) rather than a sub-head
     tier. Same token either way, no new color introduced. */
  color: var(--pg-accent);
  margin-bottom: .375rem;
}

.pg-sdk-panel-sub {
  font-size: .9375rem;
  color: var(--bs-secondary-color);
  margin: 0;
}

.pg-sdk-rail {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
}

.pg-sdk-rail-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .625rem;
  padding: 1.5rem 2rem;
}

.pg-sdk-rail-item + .pg-sdk-rail-item {
  border-top: 1px solid var(--bs-border-color);
}

/* >=768px: 3 across (item 4 starts row 2 — border-top instead of the
   left divider the other row-continuers get). >=992px: the full 5
   across, one continuous rail. Hardcoded to this exact 5-item count
   (same technique as Developer Journey's own 2-column split, section
   11) since the SDK list is fixed, known content. */
@media (min-width: 768px) {
  .pg-sdk-rail {
    grid-template-columns: repeat(3, 1fr);
  }

  .pg-sdk-rail-item {
    padding: 1.75rem 1.75rem;
  }

  .pg-sdk-rail-item + .pg-sdk-rail-item {
    border-top: none;
  }

  .pg-sdk-rail-item:nth-child(n+2):not(:nth-child(4)) {
    border-left: 1px solid var(--bs-border-color);
  }

  .pg-sdk-rail-item:nth-child(4) {
    border-top: 1px solid var(--bs-border-color);
  }
}

@media (min-width: 992px) {
  .pg-sdk-rail {
    grid-template-columns: repeat(5, 1fr);
  }

  .pg-sdk-rail-item:nth-child(4) {
    border-top: none;
  }

  .pg-sdk-rail-item:nth-child(n+2) {
    border-left: 1px solid var(--bs-border-color);
  }
}

/* Holds the real brand mark (pg_brand_icon()) now, not a text badge —
   sized/shaped like the other icon chips on the page (.pg-platform-
   item-icon, .pg-mega-item-icon: tinted-primary square, currentColor
   glyph) rather than the wide text-chip proportions this class used to
   need. */
.pg-sdk-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  flex-shrink: 0;
  border-radius: .5rem;
  background: rgba(var(--bs-primary-rgb), .08);
  color: var(--pg-accent);
}

.pg-sdk-rail-name {
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: -.005em;
}

.pg-sdk-rail-desc {
  font-size: .8125rem;
  color: var(--bs-secondary-color);
  line-height: 1.5;
}

.pg-devx-facts-label {
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-secondary-color);
  margin-bottom: 1rem;
}

.pg-devx-fact-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: .625rem 2rem;
}

.pg-devx-fact {
  display: flex;
  align-items: flex-start;
  gap: .625rem;
  padding: .375rem 0;
  color: inherit;
}

.pg-devx-fact-icon {
  flex-shrink: 0;
  color: var(--bs-secondary-color);
  margin-top: .125rem;
}

a.pg-devx-fact:hover .pg-devx-fact-icon {
  color: var(--pg-accent);
}

.pg-devx-fact-text {
  font-size: .875rem;
  line-height: 1.5;
  color: var(--bs-secondary-color);
}

.pg-devx-fact-text strong {
  color: var(--bs-body-color);
  font-weight: 600;
}

a.pg-devx-fact:hover .pg-devx-fact-text strong {
  color: var(--pg-accent);
}

/* ---------------------------------------------------------------------
 * 22. Final CTA
 * ------------------------------------------------------------------- */
.pg-final-cta-inner {
  max-width: 40rem;
  margin: 0 auto;
  text-align: center;
}

.pg-final-cta-title {
  font-size: clamp(1.875rem, 1.6rem + 1.2vw, 2.5rem);
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.15;
  margin-bottom: .875rem;
}

.pg-final-cta-lead {
  font-size: 1.0625rem;
  color: var(--bs-secondary-color);
  margin-bottom: 2rem;
}

.pg-final-cta-actions {
  justify-content: center;
  margin-bottom: 0;
}

/* ---------------------------------------------------------------------
 * 23. Interaction/touch-target polish
 * ------------------------------------------------------------------- */

/* Product hover states — the Product Platform tabs already
   have one (.pg-platform-tab:hover, Phase 2); the mega menu items
   already have one (.pg-mega-item:hover, Phase 1). This adds the one
   remaining interactive "product" surface that had a static border
   only: the console preview's own nav rail, which is decorative
   (aria-hidden) and intentionally NOT given a hover state — hovering a
   fake sidebar that does nothing would be a worse signal than no
   hover at all. Documented here rather than left ambiguous. */

/* Every .pg-btn already has :hover; this adds a visible, on-brand focus
   ring specifically for keyboard users tabbing through CTAs, on top of
   the page-wide :focus-visible outline (section 2) — buttons get a
   slightly stronger treatment since they're the page's primary actions. */
.pg-btn:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

/* Chrome/Safari draw a default focus ring on <a>/<button> that don't
   otherwise specify one; removing only the non-:focus-visible outline
   (mouse/touch activation) avoids a double ring with the rule above
   while never removing focus indication for keyboard users. */
.pg-btn:focus:not(:focus-visible) {
  outline: none;
}

/* ---------------------------------------------------------------------
 * 24. Documentation / API Reference / Guides / Developer Earnings — the
 * docs shell shared by all four secondary pages (documentation.php,
 * api-reference.php, guides.php use the sidebar+article layout below;
 * developer-earnings.php uses only the .pg-docs-hero banner and reuses
 * ordinary .pg-section markup for its own content, same as index.php's
 * own sections). Deliberately reuses existing components rather than a
 * parallel set: endpoint method/path chips are .pg-api-method/
 * .pg-api-path (section 4/7), request/response panels are
 * .pg-code-response (section 12), JSON/code text is .pg-api-code + the
 * .tok-* token colors (sections 1/10) — every one of those already
 * carries dark-theme support, so nothing new needed adding here for
 * that. Only what's genuinely new to a long-form docs article (the
 * sidebar nav, prose typography, parameter tables, callouts) gets new
 * classes.
 * ------------------------------------------------------------------- */
.pg-docs-hero {
  padding: 2.5rem 0 2rem;
  border-bottom: 1px solid var(--bs-border-color);
}

/* .pg-section-title/.pg-section-lead are shared with index.php's own
   hero and every section head sitewide, tuned for marketing-scale
   display type (the title clamps up to 2.375rem/38px). A documentation
   banner sitting above a dense 3-column article shell reads as
   oversized at that scale — these two descendant overrides bring the
   banner down to a documentation-appropriate size without touching the
   shared classes themselves (index.php's own hero is unaffected).
   developer-earnings.php also uses .pg-docs-hero for its own banner and
   picks up the same, deliberately consistent, refinement. */
.pg-docs-hero .pg-section-title {
  font-size: clamp(1.5rem, 1.35rem + .6vw, 1.75rem);
}

.pg-docs-hero .pg-section-lead {
  font-size: .9375rem;
  max-width: 38rem;
}

.pg-docs-layout {
  display: grid;
  grid-template-columns: 15rem 1fr;
  gap: 3rem;
  padding: 3rem 0 6rem;
  align-items: start;
}

.pg-docs-sidebar {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  position: sticky;
  top: calc(var(--pg-header-height) + 2rem);
  /* A long sidebar (5 groups) can exceed the viewport height at typical
     laptop heights — without its own scroll, the bottom groups would be
     permanently unreachable while the page is still mid-scroll (sticky
     keeps it pinned, but pinned to a position taller than the visible
     gap below the header). */
  max-height: calc(100vh - var(--pg-header-height) - 4rem);
  overflow-y: auto;
}

.pg-docs-nav-group {
  /* Real bug, not cosmetic: .pg-docs-nav-link below bleeds its
     hover/active background to the sidebar's true edges via a negative
     margin that matches its own padding exactly, on the assumption that
     some ancestor supplies that same amount of padding to cancel it
     back out. Nothing did — .pg-docs-sidebar/.pg-docs-nav-group/
     .pg-docs-nav-list were all zero-padding — so every nav link
     actually rendered .625rem wider than the sidebar's own 15rem
     column on each side, a real (not visual-only) overflow. Combined
     with .pg-docs-sidebar's own overflow-y: auto — which, per spec,
     forces overflow-x to compute as auto too when only one axis is set
     to a non-visible value — that overflowing width is exactly what
     produced the horizontal scrollbar. Supplying the padding here,
     once, is the actual fix: the nav link's negative margin now lands
     exactly on this padding edge instead of past it, so its background
     still reaches the sidebar's true edges (unchanged from the
     original design) but the link's own box no longer exceeds the
     sidebar's width at all. Adding this to the group (not the sidebar
     or the list) means .pg-docs-nav-title picks up the same inset for
     free, so heading text and link text stay aligned with each other
     exactly as before — only .pg-docs-search-trigger/.pg-docs-mobile-
     toggle (siblings of every .pg-docs-nav-group, not descendants of
     one) are untouched by it. */
  padding: 0 .625rem;
}

.pg-docs-nav-group + .pg-docs-nav-group {
  margin-top: 0;
}

.pg-docs-nav-title {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-secondary-color);
  margin-bottom: .625rem;
}

/* Matches .pg-mega-item-icon's own "accent glyph, no chip" treatment
   (section 4) rather than that pattern's boxed variant — a bare 14px
   glyph reads as a quiet section marker at this size; a background
   chip that small would look like a stray swatch, not an icon. */
.pg-docs-nav-title-icon {
  display: inline-flex;
  color: var(--pg-accent);
}

.pg-docs-nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .125rem;
}

.pg-docs-nav-link {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .5rem .75rem;
  margin: 0 -.625rem;
  border-radius: var(--bs-border-radius);
  font-size: .8125rem;
  /* A touch heavier than the browser default (400) — enough to read as
     deliberate, premium body-nav type rather than thin/default text,
     without stepping on --active's own 600 (still a full weight step
     up, so the active item stays clearly distinct). Matched on
     .pg-docs-toc-link below so the left sidebar and the "On This Page"
     column read as the same typographic family, not two different
     weights doing the same job. */
  font-weight: 500;
  color: var(--bs-secondary-color);
  transition: background-color .12s ease, color .12s ease;
}

/* Only the standalone "Build with Ureh" home link (pg_docs_sidebar()'s
   '' group) carries one of these — every other link is plain text, so
   a per-link icon on all ~20 doesn't compete with the group-title
   icons above for attention. */
.pg-docs-nav-link-icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--pg-accent);
}

.pg-docs-nav-link:hover {
  background: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
}

.pg-docs-nav-link--active {
  background: rgba(var(--bs-primary-rgb), .08);
  color: var(--pg-accent);
  font-weight: 600;
}

.pg-docs-nav-link--active .pg-docs-nav-link-icon {
  color: inherit;
}

/* Horizontal padding here (not a narrower max-width) is the fix: it
   insets every child uniformly — paragraphs, lists, headings, tables,
   code panels alike — so nothing shifts out of alignment with anything
   else, while giving prose genuine breathing room on both sides instead
   of running flush to the box's own edge (border-box sizing, set
   globally in vendors/main.css, means this doesn't change the box's
   own 46rem/max-width footprint or its position relative to the
   sidebar/TOC columns — only the usable width inside it). Reduced at
   ≤991.98px (see that media query below) rather than dropped entirely:
   a phone-width screen has little room to spare, but zero inset there
   would be the one place text still ran edge-to-edge. */
.pg-docs-content {
  min-width: 0;
  max-width: 46rem;
  padding: 0 1.5rem;
}

/* JetBrains Mono-specific tuning, scoped to the docs content column
   rather than the sitewide --pg-code-font-size/--pg-code-line-height
   tokens (section 1) or the global .pg-body pre rule those feed —
   index.php's own code panels keep the exact metrics already tuned for
   them. JetBrains Mono renders visibly larger and airier than the IBM
   Plex Mono the sitewide tokens were originally sized for: a touch of
   negative tracking and a tighter line-height bring it back to reading
   as compact and deliberate rather than loose, without touching the
   13px/.8125rem size itself (still comfortable, and changing a
   sitewide token here would be a much bigger blast radius than this
   task calls for). Applies to inline `code` mentions and <pre> blocks
   alike; line-height stays scoped to <pre> only, matching the sitewide
   rule's own reasoning (section 1) that an inline code word shouldn't
   override its sentence's own line-height. */
.pg-docs-content code,
.pg-docs-content pre {
  letter-spacing: -.015em;
}

.pg-docs-content pre {
  line-height: 1.6;
}

/* scroll-margin-top keeps an in-page #anchor jump (the sidebar links,
   or any cross-page link like header.php's mega menu) from landing a
   heading directly underneath the sticky header. */

/* The former .pg-docs-hero banner's eyebrow/H1/lead, relocated to the
   top of the content column itself (see this section's own intro
   comment for why the banner was removed) and rescaled down from
   marketing/section-head type to sit naturally above this column's own
   h2/h3/p rules below — sized as a peer of .pg-docs-content h2, not a
   repeat of index.php's hero. The h1 reuses the exact clamp the old
   .pg-docs-hero .pg-section-title override used for these same pages,
   so the heading itself doesn't change size, only where it sits. */
.pg-docs-content > .pg-eyebrow {
  font-size: .8125rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--pg-accent);
  margin: 0 0 .5rem;
}

.pg-docs-content h1 {
  font-size: clamp(1.5rem, 1.35rem + .6vw, 1.75rem);
  font-weight: 700;
  letter-spacing: -.015em;
  line-height: 1.2;
  margin: 0 0 .75rem;
  text-wrap: balance;
}

.pg-docs-content > .pg-docs-lead {
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--bs-secondary-color);
  margin: 0 0 2rem;
}

.pg-docs-content h2 {
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.25;
  margin: 3rem 0 1rem;
  scroll-margin-top: calc(var(--pg-header-height) + 1.5rem);
}

.pg-docs-content h3 {
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: -.005em;
  margin: 2rem 0 .75rem;
  scroll-margin-top: calc(var(--pg-header-height) + 1.5rem);
}

.pg-docs-content p {
  font-size: .9375rem;
  line-height: 1.7;
  color: var(--bs-secondary-color);
  margin: 0 0 1rem;
}

.pg-docs-content ul,
.pg-docs-content ol {
  font-size: .9375rem;
  line-height: 1.7;
  color: var(--bs-secondary-color);
  padding-left: 1.25rem;
  margin: 0 0 1rem;
}

.pg-docs-content li {
  margin-bottom: .375rem;
}

.pg-docs-content strong {
  color: var(--bs-body-color);
  font-weight: 600;
}

.pg-docs-lead {
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--bs-secondary-color);
  margin: 0 0 1.5rem;
}

.pg-docs-table {
  width: 100%;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .5rem);
  border-collapse: separate;
  border-spacing: 0;
  font-size: .8125rem;
  margin: 0 0 1.5rem;
  overflow: hidden;
}

/* A URL that must stay on one line.
 *
 * .pg-docs-content sets overflow-wrap:break-word, which is right for
 * prose but lets a long URL split after a slash — "https://api.ureh.io/"
 * on one line and "api/v1" on the next, which is unreadable and easy to
 * copy incorrectly. Whether it breaks depends on the width actually
 * available to the cell, so it appears at some window sizes, zoom levels
 * and font settings and not others; pinning it removes the condition
 * rather than the symptom. */
.pg-docs-url {
  white-space: nowrap;
}

/* Horizontal scroll container for a table holding unbreakable content.
 *
 * .pg-docs-table is width:100% with overflow:hidden (it clips its own
 * corners to the border radius), so a nowrap cell that no longer fits
 * would be silently cut off rather than reachable. This gives the table
 * its own scroll axis on narrow screens — the same "wide content scrolls
 * inside its own container, the page never does" rule the article and
 * changelog code blocks already follow — while leaving every other
 * .pg-docs-table on the site untouched. */
.pg-docs-tablewrap {
  overflow-x: auto;
  margin: 0 0 1.5rem;
}

.pg-docs-tablewrap > .pg-docs-table {
  margin-bottom: 0;
}

.pg-docs-table th {
  text-align: left;
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-secondary-color);
  background: var(--bs-tertiary-bg);
  padding: .625rem .875rem;
}

.pg-docs-table td {
  padding: .625rem .875rem;
  border-top: 1px solid var(--bs-border-color);
  vertical-align: top;
  color: var(--bs-secondary-color);
}

.pg-docs-table td:first-child {
  color: var(--bs-body-color);
  font-family: var(--font-ui-mono);
  white-space: nowrap;
}

.pg-docs-badge {
  display: inline-block;
  font-size: .6875rem;
  font-weight: 600;
  padding: .0625rem .4375rem;
  border-radius: var(--bs-border-radius-pill, 50rem);
  background: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
}

.pg-docs-badge--required {
  background: rgba(var(--bs-primary-rgb), .08);
  color: var(--pg-accent);
}

.pg-docs-endpoint {
  display: flex;
  align-items: center;
  gap: .625rem;
  margin-bottom: 1rem;
}

/* Single column, Request stacked above Response — not a 2-up grid.
   Measured live (Playwright, 1920px viewport): a side-by-side layout
   gives each panel only ~280px inside the docs content column (which
   is itself capped at 46rem regardless of browser width, so a wider
   window never gives the 2-up version more room to work with), forcing
   routine horizontal scroll on perfectly ordinary lines — a UUID
   reference or idempotency_key value, or the webhook signature hex
   string, none of which are unusually long for API payloads. Full
   width per panel is what actually fixes that; it doesn't need a
   responsive breakpoint since the narrow-panel problem never goes away
   at any viewport. */
.pg-docs-example-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
  margin: 0 0 1.5rem;
}

.pg-docs-callout {
  display: flex;
  gap: .75rem;
  padding: 1rem 1.125rem;
  border: 1px solid var(--bs-border-color);
  border-left: 3px solid var(--pg-accent);
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
  font-size: .875rem;
  line-height: 1.6;
  color: var(--bs-secondary-color);
  margin: 0 0 1.5rem;
}

.pg-docs-callout p {
  margin: 0;
  font-size: inherit;
  line-height: inherit;
  color: inherit;
}

.pg-docs-callout p + p {
  margin-top: .5rem;
}

.pg-docs-callout-icon {
  flex-shrink: 0;
  color: var(--pg-accent);
  margin-top: .125rem;
}

@media (max-width: 991.98px) {
  .pg-docs-layout {
    grid-template-columns: 1fr;
    gap: 2rem;
  }

  /* .pg-docs-sidebar itself is a real slide-in drawer at this width now
     — see the mobile toggle section (below, further down this file)
     for its full treatment, kept together with the toggle/backdrop/
     close-button chrome that drives it rather than split across two
     unrelated places in this stylesheet. */

  .pg-docs-content {
    max-width: none;
    padding: 0 1rem;
  }
}

/* Developer Earnings (developer-earnings.php only, formerly
   pricing.php). Commission-at-a-glance stat cards — solid
   border/radius/background match .pg-platform-item's own established
   "real, finished content" visual language (this page no longer has a
   placeholder rate table to mark as dashed/unfinished; the commission
   range is a real, published figure now). Class names below keep the
   'pg-pricing-*' prefix — an internal styling identifier, not
   user-facing text, so it isn't part of this rename. */
.pg-pricing-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 1rem;
  margin-bottom: 1rem;
}

.pg-pricing-stat {
  padding: 1.5rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .75rem);
  background: var(--bs-body-bg);
}

.pg-pricing-stat-value {
  display: block;
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.1;
  color: var(--pg-accent);
}

.pg-pricing-stat-label {
  display: block;
  margin-top: .375rem;
  font-size: .8125rem;
  line-height: 1.45;
  color: var(--bs-secondary-color);
}

.pg-pricing-faq {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2rem 3rem;
}

.pg-pricing-faq-q {
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: .5rem;
}

.pg-pricing-faq-a {
  font-size: .875rem;
  line-height: 1.6;
  color: var(--bs-secondary-color);
  margin: 0;
}

@media (max-width: 767.98px) {
  .pg-pricing-faq {
    grid-template-columns: 1fr;
  }
}

/* ---------------------------------------------------------------------
 * 25. Documentation experience — breadcrumbs, search, mobile sidebar
 * toggle, prev/next page nav, and the third "On This Page" column (the
 * true three-column shell: sidebar / content / on-this-page, each of
 * the two rails independently sticky and internally scrollable within
 * the viewport, only the content column drives normal page scroll).
 * Shared by documentation.php, api-reference.php and guides.php (the
 * three pages that call pg_docs_sidebar()). Reuses section 24's
 * tokens/typography throughout — nothing here redefines a color,
 * radius or font-size that already has a variable.
 * ------------------------------------------------------------------- */

/* Smooth in-page anchor jumps (sidebar links, "On This Page" links,
   prev/next nav) — opt-in via a class on <html> (only the three docs
   pages set it) rather than a sitewide rule, so index.php/developer-
   earnings.php/etc. are unaffected. Section 1's own `.pg-body * { scroll-behavior:
   auto !important }` reduced-motion override doesn't reach `html`
   (body's own descendants, not body's ancestor), hence the second rule
   here rather than relying on that one. */
html.pg-docs-scroll {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html.pg-docs-scroll {
    scroll-behavior: auto;
  }
}

/* assets/js/docs.js injects a .pg-code-copy button (section 12's own
   absolute-positioned convention) into every code block inside
   .pg-docs-content — .pg-code-response/.pg-api-block need position:
   relative for that to anchor against (section 12 never needed this
   before: hero.php/webhooks-section.php/code-experience.php all use a
   copy button scoped to the whole .pg-api-panel-header instead of one
   per block). .pg-docs-code-wrap is the positioning context docs.js
   creates around a bare <pre> that isn't already inside one of those
   two containers. */
.pg-code-response,
.pg-api-block {
  position: relative;
}

.pg-docs-code-wrap {
  position: relative;
  /* Real bug, not a hypothetical: the injected .pg-code-copy button is
     position: absolute at top: .75rem/right: .75rem (section 12), and a
     bare <pre> wrapped in this class (documentation.php/guides.php's
     plain single-snippet examples) had zero padding of its own — the
     button rendered flush on top of the code's first line, and on a
     short one-line snippet (the button's own ~2.4375rem footprint,
     .75rem inset + ~1.6875rem tall, taller than a single code line) it
     visibly hung off the bottom of the block entirely. Reserving this
     top gutter pushes every line of code below the button's full
     height, so nothing can ever render underneath it, at any scroll
     position or line count — a fixed vertical push, not a horizontal
     one, since .pg-api-code's overflow-x: auto only ever moves code
     sideways, never up past this line. */
  padding-top: 2.75rem;
}

/* Same button, different container: .pg-code-response/.pg-api-block
   already put a .pg-api-block-label ("Request"/"Response 202
   Accepted"/...) in normal flow above the <pre>, so the button and the
   label were meant to share one header-style row — but the button's
   own bottom edge (.75rem inset + ~1.6875rem tall = ~2.4375rem from the
   box's own padding edge) sits within a hair of where the label's
   default 1rem padding-top + one text line + .625rem margin-bottom
   naturally hands off to the <pre> below, so a font-metric rounding
   difference (or the label wrapping to two lines on a narrow width) was
   enough to dip the button into the code's first line, or the label's
   own text/status pill into the button. Scoped to .pg-docs-content only
   — hero.php/webhooks-section.php/code-experience.php reuse these same
   two classes on index.php but anchor their copy button in
   .pg-api-panel-header instead (see comment above), so they never had
   this overlap and shouldn't gain the extra clearance meant for it. */
.pg-docs-content .pg-code-response,
.pg-docs-content .pg-api-block {
  padding-top: 1.5rem;
}

.pg-docs-content .pg-api-block-label {
  padding-right: 2.25rem;
}

/* --- Sidebar search trigger (sits above the nav groups) and the
   command-palette-style modal it opens. --- */
.pg-docs-search-trigger {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  padding: .5rem .625rem;
  margin-bottom: .5rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
  font-size: .8125rem;
  text-align: left;
}

.pg-docs-search-trigger:hover {
  border-color: var(--bs-secondary-color);
  color: var(--bs-body-color);
}

.pg-docs-search-trigger-icon {
  display: inline-flex;
  flex-shrink: 0;
}

.pg-docs-search-trigger-label {
  flex: 1;
}

.pg-docs-search-kbd {
  flex-shrink: 0;
  font-family: var(--font-ui-mono);
  font-size: .6875rem;
  border: 1px solid var(--bs-border-color);
  border-radius: .25rem;
  padding: .0625rem .375rem;
  background: var(--bs-body-bg);
  color: var(--bs-tertiary-color);
}

.pg-docs-search-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .5);
  z-index: 1050;
  opacity: 0;
  transition: opacity .15s ease;
}

.pg-docs-search-backdrop--open {
  opacity: 1;
}

.pg-docs-search {
  position: fixed;
  top: 14vh;
  left: 50%;
  width: min(34rem, calc(100vw - 2rem));
  max-height: 65vh;
  transform: translate(-50%, -8px);
  z-index: 1051;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .75rem);
  background: var(--bs-body-bg);
  box-shadow: 0 20px 48px rgba(0, 0, 0, .22);
  overflow: hidden;
  opacity: 0;
  transition: opacity .15s ease, transform .15s ease;
}

.pg-docs-search--open {
  opacity: 1;
  transform: translate(-50%, 0);
}

.pg-docs-search-box {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .875rem 1rem;
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-docs-search-box-icon {
  flex-shrink: 0;
  color: var(--bs-tertiary-color);
}

.pg-docs-search-input {
  flex: 1;
  border: 0;
  background: transparent;
  color: var(--bs-body-color);
  font-size: .9375rem;
  outline: none;
}

.pg-docs-search-input::placeholder {
  color: var(--bs-tertiary-color);
}

.pg-docs-search-results {
  list-style: none;
  margin: 0;
  padding: .5rem;
  overflow-y: auto;
}

.pg-docs-search-result {
  display: block;
}

.pg-docs-search-result a {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  padding: .625rem .75rem;
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
}

.pg-docs-search-result a:hover,
.pg-docs-search-result--active a {
  background: var(--bs-tertiary-bg);
}

.pg-docs-search-result-title {
  font-size: .875rem;
  font-weight: 500;
}

.pg-docs-search-result-group {
  font-size: .75rem;
  color: var(--bs-tertiary-color);
}

.pg-docs-search-empty {
  padding: 1.5rem 1rem;
  text-align: center;
  font-size: .875rem;
  color: var(--bs-tertiary-color);
  margin: 0;
}

/* --- Mobile sidebar toggle: hidden on desktop, where the sidebar is
   already visible via section 24's own sticky column. --- */
.pg-docs-mobile-toggle {
  display: none;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  padding: .75rem 1rem;
  margin-bottom: 1.25rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
  color: var(--bs-body-color);
  font-size: .875rem;
  font-weight: 600;
  transition: background-color .12s ease, border-color .12s ease;
}

/* Matches .pg-mobile-toggle's own hover treatment (section 4, the
   header's hamburger button) — this button opens the same kind of
   drawer that one does, so it earns the same feedback. */
.pg-docs-mobile-toggle:hover {
  background: var(--bs-body-bg);
  border-color: var(--bs-secondary-color);
}

.pg-docs-mobile-toggle span:first-child {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
}

.pg-docs-mobile-toggle-caret {
  display: inline-flex;
  transition: transform .15s ease;
}

.pg-docs-mobile-toggle[aria-expanded="true"] .pg-docs-mobile-toggle-caret {
  transform: rotate(180deg);
}

/* --- Previous/Next page nav, at the foot of .pg-docs-content. --- */
.pg-docs-pagenav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--bs-border-color);
}

.pg-docs-pagenav-link {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: 1rem 1.125rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg, .75rem);
  color: inherit;
  transition: border-color .15s ease;
}

.pg-docs-pagenav-link:hover {
  border-color: var(--bs-secondary-color);
}

.pg-docs-pagenav-link--next {
  grid-column: 2;
  justify-content: flex-end;
  text-align: right;
}

.pg-docs-pagenav-link--prev .pg-docs-pagenav-arrow {
  transform: rotate(180deg);
}

.pg-docs-pagenav-arrow {
  flex-shrink: 0;
  color: var(--bs-tertiary-color);
}

.pg-docs-pagenav-label {
  display: block;
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-tertiary-color);
  margin-bottom: .1875rem;
}

.pg-docs-pagenav-title {
  display: block;
  font-size: .9375rem;
  font-weight: 600;
}

/* --- "On This Page" — the third column. Scoped to h2-level sections
   only (not h3): the left sidebar already exposes every h3 sub-step as
   its own separate concern would double the list length without adding
   real wayfinding value, and every real heading here already has a
   stable id, so nothing new needed adding to any content file beyond
   the id attributes those headings already carry from section 24.
   Hidden below 1200px — .pg-docs-layout only grows a third grid track
   at that width (see the media query below).

   1200px, not 1400px: this column used to be gated at 1400px, which
   meant a 1280px or 1366px laptop — and any desktop window that wasn't
   maximised — lost the "On This Page" rail and the three-pane shell
   entirely, falling back to the sticky-sidebar layout. That was wider
   than it needed to be. 1200px is where Bootstrap's own .container
   steps up from 960px to 1140px (page/vendors/main.css), and that step
   is exactly what makes the third column affordable: at 1200px the
   content track still gets ~620px, while at 1180px the container drops
   to 960px and the same three tracks would leave it ~440px — too
   narrow for the API reference's code blocks. So the cliff in the
   container's own scale is the breakpoint, not a round number picked
   by eye.

   Below 1200px (true tablet and phone widths) the column stays hidden
   and the sidebar becomes a drawer, which is the same call every
   comparable docs site makes at that width — three sticky columns do
   not fit a portrait tablet no matter how the tracks are sized. */
.pg-docs-toc {
  display: none;
}

/* True three-pane independent scrolling, ≥1200px — everything below
   1200px (the tablet-width sidebar+content pair, and the ≤991.98px
   mobile drawer) is untouched by this block and keeps the original
   sticky-sidebar/normal-page-scroll behavior.

   NOTE: assets/js/docs.js carries the same 1200px in its own
   `desktopShellQuery` matchMedia. The two MUST stay in sync — that
   query is how the scroll-spy decides whether to measure section
   offsets against .pg-docs-content's own box or against the viewport,
   and how the wheel handler decides whether to forward a gesture into
   the content pane. If the JS threshold and this one disagree, the spy
   silently measures against the wrong scroll container for every width
   between them.
   position: sticky alone can't do this: sticky only changes WHERE an
   element sits while the page scrolls, it doesn't give an element its
   own scroll container. True independent scrolling needs three real
   overflow:auto boxes that all share one fixed, viewport-derived
   height — which means the height has to come from somewhere concrete.
   .pg-docs-main (the <main class="pg-docs-main"> every docs page
   renders — see each page's own <main> tag) becomes exactly
   `100vh - header` tall and a flex column; its one child, .container,
   gets flex: 1 so it claims the entire height, with NO manual
   arithmetic anywhere. The overflow: hidden on .pg-docs-main
   is what stops the docs shell itself from ever growing past that
   fixed height and dragging the outer page into a scroll it shouldn't
   own.
   The min-height: 0 on every flex/grid level down to the three panes
   themselves is not optional: a flex/grid item's default min-height is
   `auto`, which means "grow to fit your content" — silently
   overriding flex: 1/height: 100% and making the whole box tall enough
   that overflow never triggers. This is the actual, well-documented
   reason "I set overflow: auto and it just doesn't scroll" happens in
   nested flex/grid layouts; the fix is exactly this min-height: 0 chain
   from the shell down to each pane. */
@media (min-width: 1200px) {
  .pg-docs-main {
    height: calc(100vh - var(--pg-header-height));
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .pg-docs-main > .container {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  /* Narrower rails and a tighter gap than the ≥1400px block below, so
     the content track keeps a readable measure inside the 1140px
     container this band gets: 14 + 13 rems of rails + 2×2rem of gap
     leaves it ~620px, against ~440px if the wide values were reused
     here. The wide values are restored verbatim at 1400px, so the
     large-screen layout this shell was designed at is unchanged. */
  .pg-docs-layout {
    grid-template-columns: 14rem minmax(0, 1fr) 13rem;
    gap: 2rem;
    flex: 1;
    min-height: 0;
    align-items: stretch;
    padding: 1.5rem 0;
  }

  /* Cancels the base/tablet rules' position: sticky + max-height (this
     breakpoint uses a real height + overflow instead), and gives each
     pane its own smooth in-pane anchor scrolling — see the reduced-
     motion override in this same section for the companion rule.
     Scoped under .pg-docs-main (not bare class names): real bug,
     confirmed live — privacy.php/terms.php reuse .pg-docs-content on
     its own, outside .pg-docs-main entirely, purely for its h2/h3/p/
     table/callout typography (see either file's own top comment). A
     bare .pg-docs-content selector here matched that reused element
     too, making it overflow-y: auto + overscroll-behavior: contain
     with no .pg-docs-main ancestor to give height: 100% anything
     concrete to resolve against — the article's own scrollHeight ended
     up equal to its clientHeight (nothing to scroll internally), and
     overscroll-behavior: contain then stopped the wheel gesture from
     chaining anywhere past it, to the actual document scroll behind
     it. The visible symptom was exactly backwards from the real docs
     shell's own dead-zone gap issue: scrolling failed WHILE hovering
     the content itself, and only worked once the cursor moved outside
     it. Scoping to .pg-docs-main here is the root-cause fix — it's
     what makes this rule apply only to the real three-pane shell those
     four docs pages render, never to .pg-docs-content reused
     standalone elsewhere. */
  .pg-docs-main .pg-docs-sidebar,
  .pg-docs-main .pg-docs-content,
  .pg-docs-main .pg-docs-toc {
    position: static;
    height: 100%;
    min-height: 0;
    max-height: none;
    overflow-y: auto;
    overscroll-behavior: contain;
    scroll-behavior: smooth;
  }

  .pg-docs-toc {
    display: flex;
    flex-direction: column;
    gap: .625rem;
  }
}

/* ≥1400px: the original rail widths and gap this shell was designed
   at, restored verbatim on top of the ≥1200px block above. Only these
   three values differ between the two bands — every structural rule
   (the fixed height, the overflow chain, the min-height: 0 chain, the
   per-pane overflow-y) is shared and lives in the block above. */
@media (min-width: 1400px) {
  .pg-docs-layout {
    grid-template-columns: 15rem minmax(0, 46rem) 14rem;
    gap: 3rem;
  }
}

@media (min-width: 1200px) and (prefers-reduced-motion: reduce) {
  .pg-docs-main .pg-docs-sidebar,
  .pg-docs-main .pg-docs-content,
  .pg-docs-main .pg-docs-toc {
    scroll-behavior: auto;
  }
}

.pg-docs-toc-title {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--bs-secondary-color);
  margin-bottom: .375rem;
}

.pg-docs-toc-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* Left border + accent text, not the sidebar's tinted-background
   treatment — a deliberately lighter visual weight for a secondary,
   same-page nav so it reads as "supporting," not a second primary
   sidebar competing for attention. */
.pg-docs-toc-link {
  display: block;
  padding: .3125rem 0 .3125rem .75rem;
  border-left: 2px solid transparent;
  font-size: .8125rem;
  line-height: 1.4;
  /* Matches .pg-docs-nav-link's own weight (above) — same reasoning,
     kept in sync so the left sidebar and this column don't drift into
     two different type weights for what's the same kind of link. */
  font-weight: 500;
  color: var(--bs-tertiary-color);
}

.pg-docs-toc-link:hover {
  color: var(--bs-body-color);
}

.pg-docs-toc-link--active {
  border-left-color: var(--pg-accent);
  color: var(--pg-accent);
  font-weight: 600;
}

/* --- Mobile sidebar drawer: the backdrop and the drawer's own header
   (title + close button) that pg_docs_sidebar() always renders, but
   that only ever do anything below 992px — see the media query below
   for their real treatment. Same "hidden above the breakpoint that
   gives it meaning" convention as .pg-docs-mobile-toggle's own base
   rule just above. --- */
.pg-docs-sidebar-backdrop {
  display: none;
}

.pg-docs-sidebar-header {
  display: none;
}

@media (max-width: 991.98px) {
  .pg-docs-mobile-toggle {
    display: flex;
  }

  /* A real slide-in drawer, the same pattern components/mobile-nav.php
     already establishes for the header's own mobile menu (section 6)
     — fixed to the viewport, off-canvas at rest, sliding in on open —
     replacing what used to be an inline flex-row-wrap block that
     shoved the rest of the page down instead of overlaying it.
     visibility (not [hidden] or display: none) is what keeps the
     closed drawer out of the accessibility tree and un-clickable: it
     transitions cleanly alongside transform (unlike display, which
     can't animate at all), and — unlike [hidden] — nothing here
     conflicts with .pg-docs-sidebar's own unconditional display: flex
     at ≥992px (see pg_docs_sidebar()'s own docblock in docs-nav.php
     for why [hidden] was ruled out for exactly that reason). */
  .pg-docs-sidebar {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(320px, 88vw);
    max-height: none;
    flex-direction: column;
    gap: 1.5rem;
    padding: 0 0 1.5rem;
    background: var(--bs-body-bg);
    box-shadow: -8px 0 32px rgba(0, 0, 0, .16);
    overflow-y: auto;
    z-index: 1100;
    visibility: hidden;
    pointer-events: none;
    transform: translateX(100%);
    transition: transform .2s ease, visibility .2s, pointer-events .2s;
  }

  .pg-docs-sidebar--open {
    visibility: visible;
    pointer-events: auto;
    transform: translateX(0);
  }

  /* .pg-docs-sidebar's own flex gap (above) already spaces these two
     vertically from the header and from each other. */
  .pg-docs-sidebar > .pg-docs-search-trigger,
  .pg-docs-sidebar > .pg-docs-nav-group {
    margin-left: 1.25rem;
    margin-right: 1.25rem;
  }

  .pg-docs-sidebar-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--bs-border-color);
    flex-shrink: 0;
    /* Sticks to the drawer's own top edge while its body scrolls under
       it — same idea as .pg-docs-toc-title staying put while the pane
       around it scrolls, just for a header instead of a label. */
    position: sticky;
    top: 0;
    background: var(--bs-body-bg);
    z-index: 1;
  }

  .pg-docs-sidebar-header-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 700;
  }

  /* Same 44px real touch target as .pg-mobile-close (section 6) —
     this closes the same kind of drawer that button does. */
  .pg-docs-sidebar-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    flex-shrink: 0;
    background: transparent;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    color: var(--bs-body-color);
    transition: background-color .12s ease;
  }

  .pg-docs-sidebar-close:hover {
    background: var(--bs-tertiary-bg);
  }

  /* The '/' shortcut hint only means anything with a physical keyboard
     — dropped here rather than in the shared .pg-docs-search-kbd rule
     itself, so it still shows in any other context that reuses this
     component with one attached. */
  .pg-docs-search-kbd {
    display: none;
  }

  /* Same dim-scrim + fade the header's own .pg-mega-backdrop/
     .pg-mobile-nav use, and the same pointer-events gate .pg-mega-
     backdrop's own base rule was missing before it was fixed to match
     .pg-mobile-nav's (section 5's own comment on that bug) — applied
     here from the start rather than discovered as a regression later. */
  .pg-docs-sidebar-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1099;
    background: rgba(0, 0, 0, .25);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .2s ease, visibility .2s, pointer-events .2s;
  }

  .pg-docs-sidebar-backdrop--open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }

  .pg-docs-pagenav-link--next {
    text-align: right;
  }
}

@media (max-width: 575.98px) {
  .pg-docs-pagenav {
    grid-template-columns: 1fr;
  }

  .pg-docs-pagenav-link--next {
    grid-column: 1;
  }
}

/* ---------------------------------------------------------------------
 * 26. Support / Contact (support.php)
 *
 * The site's first and only form. Built on the same tokens as
 * everything above it — var(--bs-body-bg) surfaces, var(--bs-border-color)
 * edges, var(--bs-border-radius) corners, --pg-accent for foreground
 * brand color — rather than Bootstrap's own .form-control family, for
 * the same reason section 3 defines its own .pg-btn instead of using
 * .btn: the vendor controls are tuned for the dense authenticated
 * console, not this page's editorial spacing and type scale. Nothing
 * here needs a dark-mode block of its own; every value resolves through
 * a token the [data-bs-theme="dark"] block at the top of this file
 * already redefines.
 * ------------------------------------------------------------------- */

/* --- Left rail ---------------------------------------------------- */
.pg-support-aside {
  position: sticky;
  /* Clears the sticky header (--pg-header-height) plus a breathing gap,
     so the rail never slides under it while the long form scrolls. */
  top: calc(var(--pg-header-height) + 1.5rem);
}

.pg-support-aside-title {
  font-size: 1rem;
  font-weight: 650;
  margin: 0 0 .5rem;
}

.pg-support-aside-text {
  color: var(--bs-secondary-color);
  font-size: .9375rem;
  line-height: 1.65;
  margin: 0 0 1rem;
}

.pg-support-aside-rule {
  border: 0;
  border-top: 1px solid var(--bs-border-color);
  margin: 2rem 0;
  opacity: 1;
}

.pg-support-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .25rem;
}

.pg-support-links a {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .5rem .625rem;
  margin-left: -.625rem;
  border-radius: var(--bs-border-radius);
  font-size: .9375rem;
  color: var(--bs-body-color);
  transition: background-color .15s ease, color .15s ease;
}

.pg-support-links a:hover {
  background: var(--bs-tertiary-bg);
  color: var(--pg-accent);
}

.pg-support-link-icon {
  display: inline-flex;
  color: var(--pg-accent);
  flex: 0 0 auto;
}

/* --- Form card ---------------------------------------------------- */
.pg-support-card {
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: calc(var(--bs-border-radius) * 2);
  padding: clamp(1.5rem, 4vw, 2.5rem);
}

.pg-support-form-title {
  font-size: 1.375rem;
  font-weight: 650;
  margin: 0 0 .375rem;
}

.pg-support-form-lead {
  color: var(--bs-secondary-color);
  font-size: .9375rem;
  margin: 0 0 1.75rem;
}

/* --- Fields ------------------------------------------------------- */
.pg-field-label {
  display: block;
  font-size: .875rem;
  font-weight: 550;
  margin-bottom: .4375rem;
}

.pg-field {
  display: block;
  width: 100%;
  padding: .6875rem .875rem;
  font-family: inherit;
  font-size: .9375rem;
  color: var(--bs-body-color);
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  transition: border-color .15s ease, box-shadow .15s ease;
  /* Without this a <select>'s own UA font is used instead of the page's
     — the one control that doesn't inherit font-family by default. */
  appearance: none;
}

.pg-field:hover:not(:disabled) {
  border-color: var(--bs-secondary-color);
}

.pg-field:focus {
  outline: none;
  border-color: var(--pg-accent);
  /* A soft ring rather than the global 2px :focus-visible outline: on a
     full-width input that outline reads as a second border. Still a
     visible, non-color-only focus indicator, and it uses the same
     "one step off the page" token every hover/tint on this site does,
     so it resolves correctly in both themes with no dark-mode override. */
  box-shadow: 0 0 0 3px var(--bs-tertiary-bg);
}

.pg-field::placeholder {
  color: var(--bs-tertiary-color);
}

/* Chevron drawn as a background image so the native select arrow can be
   suppressed (appearance:none above) without losing the affordance.
   A data URI cannot read currentColor, so its stroke is the one literal
   value in this section: a neutral mid-grey deliberately chosen to sit
   at acceptable contrast against BOTH themes' field background, rather
   than a token that only works in one. padding-right leaves room for it. */
.pg-field-select {
  padding-right: 2.5rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6l6 -6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .875rem center;
  cursor: pointer;
}

.pg-field-textarea {
  resize: vertical;
  min-height: 9rem;
  line-height: 1.6;
}

.pg-field--invalid,
.pg-field--invalid:hover {
  border-color: var(--bs-danger);
}

.pg-field--invalid:focus {
  border-color: var(--bs-danger);
  box-shadow: 0 0 0 3px rgba(220, 53, 69, .15);
}

/* Always in the DOM (support.php renders the empty slot); collapses to
   nothing until support-form.js writes text into it, so a field never
   shifts its neighbours when an error appears and disappears. */
.pg-field-error {
  margin: .375rem 0 0;
  font-size: .8125rem;
  color: var(--bs-danger);
  min-height: 1.0625rem;
}

.pg-field-error:empty {
  margin-top: 0;
}

.pg-field-footer {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}

.pg-field-count {
  margin-top: .375rem;
  font-size: .8125rem;
  color: var(--bs-tertiary-color);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.pg-field-count--near {
  color: var(--bs-warning);
}

/* --- Honeypot ------------------------------------------------------
   Off-screen rather than display:none / visibility:hidden — a bot that
   skips hidden inputs would step around the trap entirely. Nothing here
   is reachable by keyboard or assistive tech: the markup also carries
   aria-hidden and tabindex="-1" (see support.php). */
.pg-honeypot {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- Turnstile ----------------------------------------------------
   Empty (zero-height) unless support-form.js actually mounts a widget,
   so a deployment with Turnstile disabled leaves no gap in the form. */
.pg-support-turnstile:not(:empty) {
  margin-top: .25rem;
}

/* --- Actions ------------------------------------------------------ */
.pg-support-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
  margin-top: .5rem;
}

.pg-support-consent {
  margin: 0;
  font-size: .8125rem;
  line-height: 1.6;
  color: var(--bs-tertiary-color);
}

.pg-support-consent a {
  color: var(--pg-accent);
  text-decoration: underline;
}

#pg-support-submit[aria-busy="true"] {
  opacity: .7;
  cursor: progress;
}

/* --- Alert + success panels --------------------------------------- */
.pg-support-alert {
  display: flex;
  align-items: flex-start;
  gap: .625rem;
  padding: .875rem 1rem;
  margin-bottom: 1.5rem;
  border: 1px solid var(--bs-danger);
  border-radius: var(--bs-border-radius);
  background: rgba(220, 53, 69, .08);
  font-size: .9375rem;
  line-height: 1.55;
}

.pg-support-alert-icon {
  display: inline-flex;
  color: var(--bs-danger);
  flex: 0 0 auto;
  margin-top: .1rem;
}

.pg-support-result {
  text-align: center;
  padding: 1rem 0 .5rem;
}

.pg-support-result-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background: var(--bs-tertiary-bg);
  color: var(--bs-success);
  margin-bottom: 1.25rem;
}

.pg-support-result-title {
  font-size: 1.375rem;
  font-weight: 650;
  margin: 0 0 .625rem;
}

.pg-support-result-text {
  color: var(--bs-secondary-color);
  font-size: .9375rem;
  line-height: 1.65;
  max-width: 34rem;
  margin: 0 auto 1.25rem;
}

.pg-support-result-meta {
  font-size: .875rem;
  margin: 0 0 1.75rem;
}

.pg-support-result-meta code {
  padding: .1875rem .5rem;
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
}

.pg-support-result-hint {
  display: block;
  margin-top: .375rem;
  color: var(--bs-tertiary-color);
  font-size: .8125rem;
}

@media (max-width: 991.98px) {
  /* Below lg the two columns stack, so the rail has nothing to stick
     alongside — pinning it there would just park it over the form. */
  .pg-support-aside {
    position: static;
  }
}

/* ---------------------------------------------------------------------
 * 27. Blog (blog.php + components/blog-*.php)
 *
 * An editorial register on top of the same token system as everything
 * above: var(--bs-body-bg) surfaces, var(--bs-border-color) edges,
 * --pg-accent for foreground brand colour, --bs-tertiary-bg for tints.
 * No new colour is introduced, so both themes work without a single
 * dark-mode override in this section.
 *
 * TYPE SCALE — calibrated against the rest of the site, not chosen
 * freehand. The reference points, measured from the live pages:
 *
 *   .pg-hero-title        46px   the largest type on the site
 *   .pg-section-title     38px   a marketing section head
 *   .pg-docs-content h1   28px   a long-form CONTENT page title
 *   .pg-docs-content h2   24px
 *   .pg-docs-lead         17px
 *   .pg-section-lead      16px
 *   .pg-docs-content p    15px / 1.7
 *
 * The first pass overshot every one of these: the article title rendered
 * at 48px, larger than the homepage hero, and its standfirst at 20px,
 * larger than the docs lead. An article is a long-form content page, so
 * its title now sits between the docs h1 and a section head (34px) and
 * never above either of the two site-wide maxima; every supporting size
 * below is pulled onto an existing rung of that same scale rather than a
 * new one. Density follows: cards, sidebar and pagination were spaced
 * for a much larger type scale than they now carry.
 *
 * The article body deliberately reuses .pg-docs-content's typography
 * (see blog-article.php's own class list) rather than defining a second
 * prose scale — that scale is already tuned for sustained reading in the
 * documentation, and an article and a doc page should not read
 * differently. Only the handful of rules genuinely specific to long-form
 * editorial (drop-cap-free lead paragraph, wider quotes, figure spacing)
 * are added below.
 * ------------------------------------------------------------------- */

/* Restores position:sticky for the blog's sidebars.
 *
 * .pg-body .container carries overflow-x:hidden (section 2, a defensive
 * clip for Bootstrap's .row.g-5 gutter overshoot). overflow-x:hidden
 * computes overflow-y to `auto`, which makes the element a scroll
 * container — and a sticky descendant sticks to THAT, not the viewport.
 * The blog rails were therefore not sticking at all: measured, they
 * scrolled clean off the top of the page (-631px at scroll 800), and at
 * rest they were pushed down by their own top inset instead of aligning
 * with the article. This was true on the index from the day it shipped,
 * not something the article layout introduced.
 *
 * Scoped to a marker class on the two blog containers rather than
 * relaxing the global rule: the clip exists for a real reason on the
 * marketing sections that use .row.g-5, and neither blog layout uses
 * that pattern — they are CSS grids with explicit track sizes, verified
 * to produce no horizontal overflow from 360px to 1600px. */
.pg-body .pg-blog-shell {
  overflow-x: visible;
}

/* Mobile reading gutter for the blog and changelog.
 *
 * Bootstrap's .container gives 12px either side, which is what the
 * marketing pages use. That is too tight for the long-form reading
 * surfaces: measured at 360px, every heading, card, filter chip,
 * pagination control and sidebar item sat 12px from the viewport edge.
 *
 * 1.75rem is not a new value — it is exactly what a DOCUMENTATION page
 * already gives its content on mobile: .container's own 0.75rem plus
 * .pg-docs-content's 1rem override in the max-width:991.98px block.
 * Setting it on the container here reaches every child at once, so
 * headings, cards, filters, pagination and the sidebar all share one
 * inset rather than each needing its own padding.
 *
 * Applied at the same 991.98px breakpoint the documentation override
 * uses, so the two surfaces change gutter together rather than at
 * different widths. Above it the container is far from the viewport
 * edge anyway and the default is correct.
 *
 * TWO WAYS TO OPT IN, one declaration:
 *   .pg-page-gutter        on an individual .container — used by the
 *                          blog and changelog, whose components are not
 *                          shared with any other page.
 *   .pg-page-gutter-scope  on a page's <main>, applying it to every
 *                          .container inside — used by index.php, one of
 *                          whose sections (components/final-cta.php) is
 *                          also required by developer-earnings.php.
 *                          Marking that component directly would have
 *                          given developer-earnings the wider gutter on
 *                          its closing CTA and the default on every
 *                          other section, which reads worse than either
 *                          value applied consistently. */
@media (max-width: 991.98px) {
  .pg-page-gutter,
  .pg-page-gutter-scope .container {
    padding-inline: 1.75rem;
  }
}

/* Below the smallest phones the gutter eases back slightly: at 360px a
   28px inset either side leaves only 304px of content, which starts to
   squeeze code chips and two-word buttons. 1.25rem keeps a clearly
   deliberate margin while returning usable width. */
@media (max-width: 389.98px) {
  .pg-page-gutter,
  .pg-page-gutter-scope .container {
    padding-inline: 1.25rem;
  }
}

.pg-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;
}

/* Hero sits tighter than .pg-docs-hero: the lead article directly below
   it is itself a large visual, so a full docs-sized banner would push
   the actual content below the fold. */
/* The index is a listing, not a landing page: its own title sits a rung
   below .pg-section-title's 38px so it introduces the page without
   competing with the lead article directly beneath it. */
.pg-blog-hero {
  padding: 3rem 0 1.5rem;
}

.pg-blog-hero .pg-section-title {
  font-size: clamp(1.625rem, 1.4rem + .9vw, 2rem);
  margin-bottom: .625rem;
}

.pg-blog-hero .pg-section-lead {
  max-width: 44rem;
}

.pg-section--flush-top {
  padding-top: 0;
}

/* --- Two column shell --------------------------------------------- */
/* 17.5rem, not 20rem: the sidebar carries short link text and a search
   box, so the extra width read as an empty gutter and pushed the card
   grid narrower than it needed to be. */
.pg-blog-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 17.5rem;
  gap: 2.5rem;
  align-items: start;
}

.pg-blog-main {
  min-width: 0; /* lets long code blocks in an article scroll instead of widening the grid */
}

/* --- Lead article -------------------------------------------------- */
.pg-blog-lead {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: 0;
  margin-bottom: 2.5rem;
  border: 1px solid var(--bs-border-color);
  border-radius: calc(var(--bs-border-radius) * 2);
  overflow: hidden;
  background: var(--bs-body-bg);
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.pg-blog-lead:hover {
  border-color: var(--pg-accent);
  box-shadow: 0 8px 28px rgba(0, 0, 0, .07);
}

/* Without an image the two-column split leaves a large empty cell. */
.pg-blog-lead:not(:has(.pg-blog-lead-media)) {
  grid-template-columns: 1fr;
}

.pg-blog-lead-media {
  display: block;
  background: var(--bs-tertiary-bg);
  min-height: 15rem;
}

.pg-blog-lead-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.pg-blog-lead-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .625rem;
  padding: clamp(1.25rem, 2.2vw, 1.875rem);
}

.pg-blog-lead-flag {
  font-size: .6875rem;
  line-height: 1;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--pg-accent);
}

/* A card headline, not a page title — capped at 24px, the same rung as
   .pg-docs-content h2. */
.pg-blog-lead-title {
  font-size: clamp(1.25rem, 1.1rem + .7vw, 1.5rem);
  font-weight: 670;
  line-height: 1.25;
  letter-spacing: -.015em;
  color: var(--bs-body-color);
}

.pg-blog-lead-excerpt {
  color: var(--bs-secondary-color);
  font-size: .9375rem;
  line-height: 1.65;
  max-width: 38rem;
}

/* --- Tags ---------------------------------------------------------- */
.pg-blog-tag {
  display: inline-flex;
  align-items: center;
  padding: .1875rem .5625rem;
  border-radius: 100px;
  background: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
  font-size: .6875rem;
  text-transform: uppercase;
  font-weight: 650;
  letter-spacing: .04em;
  transition: background-color .15s ease, color .15s ease;
}

a.pg-blog-tag:hover {
  background: var(--pg-accent);
  color: #fff;
}

.pg-blog-tag--static {
  pointer-events: none;
}

/* --- Card grid ------------------------------------------------------ */
/* Tighter track and gap: at 17rem/1.75rem the grid fitted three cards on
   a 1440px viewport with visible slack between them. 16rem/1.25rem keeps
   the same three-up rhythm at a density that matches the reduced type. */
.pg-blog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 1.25rem;
}

.pg-blog-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--bs-border-color);
  border-radius: calc(var(--bs-border-radius) * 1.5);
  overflow: hidden;
  background: var(--bs-body-bg);
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.pg-blog-card:hover {
  border-color: var(--pg-accent);
  box-shadow: 0 4px 16px rgba(0, 0, 0, .05);
  transform: translateY(-1px);
}

.pg-blog-card-media {
  display: block;
  aspect-ratio: 16 / 9;
  background: var(--bs-tertiary-bg);
}

.pg-blog-card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.pg-blog-card-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .5rem;
  padding: 1.125rem 1.125rem 1.25rem;
  flex: 1;
}

.pg-blog-card-title {
  font-size: 1rem;
  font-weight: 640;
  line-height: 1.4;
  letter-spacing: -.01em;
  margin: 0;
}

.pg-blog-card-title a:hover {
  color: var(--pg-accent);
}

.pg-blog-card-excerpt {
  color: var(--bs-secondary-color);
  font-size: .875rem;
  line-height: 1.6;
  margin: 0;
  flex: 1;
}

/* --- Byline -------------------------------------------------------- */
.pg-blog-byline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3125rem;
  font-size: .78125rem;
  color: var(--bs-tertiary-color);
}

.pg-blog-byline-name {
  font-weight: 600;
  color: var(--bs-secondary-color);
}

/* --- Result bar ---------------------------------------------------- */
.pg-blog-resultbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: .875rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-blog-resultbar-text {
  margin: 0;
  font-size: .875rem;
  color: var(--bs-secondary-color);
}

.pg-blog-resultbar-clear {
  font-size: .8125rem;
  font-weight: 600;
  color: var(--pg-accent);
}

/* --- Empty states --------------------------------------------------- */
.pg-blog-empty {
  text-align: center;
  padding: 3.25rem 1.5rem;
  border: 1px dashed var(--bs-border-color);
  border-radius: calc(var(--bs-border-radius) * 1.5);
}

.pg-blog-empty-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--bs-tertiary-bg);
  color: var(--pg-accent);
  margin-bottom: 1rem;
}

.pg-blog-empty-title {
  font-size: 1.125rem;
  font-weight: 640;
  margin: 0 0 .375rem;
}

.pg-blog-empty-text {
  color: var(--bs-secondary-color);
  font-size: .875rem;
  line-height: 1.65;
  max-width: 28rem;
  margin: 0 auto 1.5rem;
}

/* --- Pagination ----------------------------------------------------- */
.pg-blog-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 3rem;
  padding-top: 2rem;
  border-top: 1px solid var(--bs-border-color);
}

.pg-blog-pagenums {
  display: flex;
  gap: .375rem;
}

.pg-blog-page {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  min-width: 2rem;
  height: 2rem;
  padding: 0 .6875rem;
  justify-content: center;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  font-size: .8125rem;
  font-weight: 600;
  color: var(--bs-body-color);
  transition: border-color .15s ease, background-color .15s ease, color .15s ease;
}

a.pg-blog-page:hover {
  border-color: var(--pg-accent);
  color: var(--pg-accent);
}

.pg-blog-page--current {
  background: var(--bs-primary);
  border-color: var(--bs-primary);
  color: #fff;
}

.pg-blog-page--disabled {
  opacity: .4;
  cursor: not-allowed;
}

/* The "Previous" arrow reuses the single arrow-right glyph, mirrored,
   rather than adding a second icon to the registry for one use. */
.pg-blog-page-icon--back {
  transform: rotate(180deg);
}

/* --- Sidebar -------------------------------------------------------- */
.pg-blog-sidebar {
  position: sticky;
  top: calc(var(--pg-header-height) + 1.5rem);
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
}

/* Matches .pg-eyebrow's own uppercase label treatment (13px/600/.07em)
   one size down, so the sidebar's section labels read as part of the
   same system rather than a second, unrelated label style. */
.pg-blog-widget-title {
  font-size: .6875rem;
  font-weight: 650;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
  margin: 0 0 .75rem;
}

.pg-blog-search {
  display: flex;
  align-items: center;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
  transition: border-color .15s ease;
}

.pg-blog-search:focus-within {
  border-color: var(--pg-accent);
}

.pg-blog-search-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: .5rem .6875rem;
  font-family: inherit;
  font-size: .875rem;
  color: var(--bs-body-color);
}

.pg-blog-search-input:focus {
  outline: none;
}

.pg-blog-search-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: .5rem .6875rem;
  border: 0;
  background: transparent;
  color: var(--bs-secondary-color);
  cursor: pointer;
}

.pg-blog-search-btn:hover {
  color: var(--pg-accent);
}

.pg-blog-catlist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .125rem;
}

.pg-blog-catlink {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: .4375rem .5625rem;
  margin-left: -.5625rem;
  border-radius: var(--bs-border-radius);
  font-size: .875rem;
  color: var(--bs-body-color);
  transition: background-color .15s ease, color .15s ease;
}

.pg-blog-catlink:hover {
  background: var(--bs-tertiary-bg);
  color: var(--pg-accent);
}

.pg-blog-catlink--active {
  background: var(--bs-tertiary-bg);
  color: var(--pg-accent);
  font-weight: 600;
}

.pg-blog-catcount {
  font-size: .6875rem;
  color: var(--bs-tertiary-color);
  font-variant-numeric: tabular-nums;
}

.pg-blog-minilist {
  display: grid;
  gap: .875rem;
}

.pg-blog-mini {
  display: grid;
  gap: .25rem;
}

.pg-blog-mini-title {
  font-size: .875rem;
  font-weight: 600;
  line-height: 1.45;
  color: var(--bs-body-color);
  transition: color .15s ease;
}

.pg-blog-mini:hover .pg-blog-mini-title {
  color: var(--pg-accent);
}

.pg-blog-mini-meta {
  font-size: .75rem;
  color: var(--bs-tertiary-color);
}

.pg-blog-widget--cta {
  padding: 1.25rem;
  border: 1px solid var(--bs-border-color);
  border-radius: calc(var(--bs-border-radius) * 1.5);
  background: var(--bs-tertiary-bg);
}

.pg-blog-widget-text {
  font-size: .875rem;
  line-height: 1.6;
  color: var(--bs-secondary-color);
  margin: 0 0 1rem;
}

.pg-blog-widget-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .75rem;
}

/* --- Article -------------------------------------------------------- */
.pg-article-head {
  padding: 0 0 1.5rem;
}

.pg-article-crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4375rem;
  font-size: .8125rem;
  color: var(--bs-tertiary-color);
  margin-bottom: 1rem;
}

.pg-article-crumbs a:hover {
  color: var(--pg-accent);
}

/* 34px at the cap. An article is a long-form CONTENT page, so it is
   scaled against .pg-docs-content h1 (28px) rather than the marketing
   hero — one rung up from a doc for editorial presence, still below
   .pg-section-title (38px) and well below .pg-hero-title (46px). The
   previous 48px made an article the largest type anywhere on the site,
   which is what made the page read as oversized. */
.pg-article-title {
  font-size: clamp(1.75rem, 1.5rem + 1vw, 2.125rem);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -.02em;
  margin: 0 0 .75rem;
  text-wrap: balance;
}

/* Exactly .pg-docs-lead (17px/1.6) — the site's existing standfirst
   size for a content page, rather than the 20px this used to carry. */
.pg-article-standfirst {
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--bs-secondary-color);
  margin: 0 0 1.25rem;
}

.pg-article-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4375rem;
  font-size: .8125rem;
  color: var(--bs-tertiary-color);
  padding-top: 1rem;
  border-top: 1px solid var(--bs-border-color);
}

.pg-article-author {
  font-weight: 620;
  color: var(--bs-body-color);
}

.pg-article-author-role {
  color: var(--bs-secondary-color);
}

.pg-article-hero {
  margin: 0 0 2rem;
  border-radius: calc(var(--bs-border-radius) * 2);
  overflow: hidden;
  background: var(--bs-tertiary-bg);
}

.pg-article-hero img {
  width: 100%;
  height: auto;
  display: block;
  max-height: 24rem;
  object-fit: cover;
}

/* Long-form additions on top of .pg-docs-content's own typography. */
.pg-article-body {
  max-width: 42rem;
}

/* One rung above the body's own 15px, matching .pg-docs-lead's 17px, so
   the opening paragraph carries a little more weight without becoming a
   second standfirst. */
.pg-article-body > p:first-of-type {
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--bs-body-color);
}

.pg-article-body figure {
  margin: 2rem 0;
}

/* max-width, not width: an inline figure renders at its natural size up
   to the column width. width:100% upscaled a small diagram to the full
   measure, which is blurry and gives a 64px square the height of a
   full-bleed photograph (seen in testing). The featured image above is
   deliberately different — a cover is meant to fill the column. */
.pg-article-body figure img {
  max-width: 100%;
  height: auto;
  border-radius: var(--bs-border-radius);
  display: block;
  margin-inline: auto;
}

.pg-article-body figcaption {
  margin-top: .625rem;
  font-size: .8125rem;
  color: var(--bs-tertiary-color);
  text-align: center;
}

.pg-article-body hr {
  border: 0;
  border-top: 1px solid var(--bs-border-color);
  margin: 2.5rem 0;
  opacity: 1;
}

.pg-article-body blockquote {
  margin: 1.75rem 0;
  padding: .125rem 0 .125rem 1.25rem;
  border-left: 2px solid var(--pg-accent);
  font-size: 1rem;
  line-height: 1.7;
  color: var(--bs-body-color);
}

/* A <pre> in an article can hold a long request line; it scrolls inside
   its own box rather than widening the column. */
.pg-article-body pre {
  overflow-x: auto;
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  padding: .875rem 1rem;
  margin: 1.5rem 0;
}

.pg-article-foot {
  margin-top: 3rem;
}

.pg-article-foot-cta {
  padding: 1.5rem;
  border: 1px solid var(--bs-border-color);
  border-radius: calc(var(--bs-border-radius) * 1.5);
  background: var(--bs-tertiary-bg);
}

.pg-article-foot-title {
  font-size: 1rem;
  font-weight: 640;
  margin: 0 0 .3125rem;
}

.pg-article-foot-text {
  font-size: .875rem;
  color: var(--bs-secondary-color);
  margin: 0 0 1.125rem;
}

.pg-article-foot-actions {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
}

.pg-article-related {
  margin-top: 3.5rem;
}

/* Section label for the Related Articles strip — the .pg-docs-content h2
   rung (24px), not a page-title rung. */
.pg-section-title--sm {
  font-size: clamp(1.25rem, 1.15rem + .4vw, 1.5rem);
  letter-spacing: -.01em;
}

/* --- Article 404 ---------------------------------------------------- */
.pg-blog-notfound {
  max-width: 40rem;
  margin: 0 auto;
  text-align: center;
  padding: 3rem 0;
}

.pg-blog-notfound-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .75rem;
  margin-top: 1.75rem;
}

.pg-blog-notfound-latest {
  margin-top: 3.5rem;
  text-align: left;
  padding-top: 2.5rem;
  border-top: 1px solid var(--bs-border-color);
}

/* --- Responsive ----------------------------------------------------- */
/* The article's two-column shell: prose, then the same sidebar the index
   carries.
 *
 * The prose track is a FIXED 43rem rather than 1fr, and the grid is
 * centred as a whole with justify-content. That distinction is the whole
 * reason this layout works: with a 1fr track the column stretched to
 * whatever space was left over while the text inside stayed capped at its
 * reading measure, leaving a wide dead gutter between the last word and
 * the sidebar. Sizing the track to the measure itself means the two
 * columns sit next to each other and the pair is centred in the
 * container.
 *
 * 43rem is the measure of actual TEXT on a documentation page:
 * .pg-docs-content is 46rem wide including 1.5rem of horizontal padding
 * either side. Matching the text measure rather than the box width is
 * what makes an article read at exactly the width of a doc page.
 *
 * 43 + 3.5 + 17.5 = 64rem (1024px), comfortably inside the 1280px
 * container with even margins either side. */
.pg-article-grid {
  display: grid;
  grid-template-columns: minmax(0, 43rem) 17.5rem;
  gap: 3.5rem;
  justify-content: center;
  align-items: start;
  /* This padding used to live on .pg-article-head, and moving it up to
     the grid is what makes the sidebar start level with the breadcrumbs.
     position:sticky clamps a box to its top inset the moment its natural
     position would sit above it — with the grid butting straight up
     under the sticky header, the rail was being pushed 96px down the
     page on load (measured), so it began level with the article title
     instead of the top of the column. Padding the grid moves BOTH
     columns' content below the inset, so the clamp never engages at rest
     and the rail still parks under the header once you scroll. */
  padding-top: 2.5rem;
}

.pg-article-main {
  min-width: 0;
}

/* The grid track already provides the measure, so the body's own docs
   padding is zeroed — left in place it inset every paragraph 24px from
   the title and standfirst above it, which read as a misalignment rather
   than an indent. */
.pg-article-main .pg-article-body {
  max-width: none;
  padding-inline: 0;
}

/* The sidebar sticks while the article scrolls past it, which is the
   point of putting it here: search and topics stay reachable through a
   long read. Same offset as the index's own rail. */
.pg-article-grid .pg-blog-sidebar {
  top: calc(var(--pg-header-height) + 1.5rem);
}

/* ---------------------------------------------------------------------
 * 28. Changelog (changelog.php + components/changelog-*.php)
 *
 * Built entirely on the tokens and the measured type scale sections 1
 * and 27 already establish. Nothing here introduces a colour, a size or
 * a radius that does not already exist elsewhere on the site:
 *
 *   page title      .pg-section-title, capped at 32px like the blog index
 *   entry title     17px, the .pg-docs-lead rung
 *   body/summary    15px / 1.7, .pg-docs-content p
 *   labels          11px uppercase, .pg-blog-widget-title
 *   badges/chips    the existing --bs-success/info/warning/danger tokens
 *
 * The one structural idea specific to a changelog is the two column
 * entry: a left rail carrying the date and release identifier so a
 * reader can scan releases vertically, and the change itself on the
 * right. That is the layout every mature API changelog converges on, and
 * it collapses to a single column below lg.
 * ------------------------------------------------------------------- */

.pg-cl-hero {
  padding: 3rem 0 1.5rem;
}

.pg-cl-hero .pg-section-title {
  font-size: clamp(1.625rem, 1.4rem + .9vw, 2rem);
  margin-bottom: .625rem;
}

.pg-cl-hero .pg-section-lead {
  max-width: 46rem;
}

.pg-cl-latest {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 1rem 0 0;
}

.pg-cl-latest code {
  padding: .1875rem .5rem;
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
  font-size: .8125rem;
}

/* Shared micro label: same treatment as .pg-blog-widget-title so the
   changelog's own labels read as part of one system. */
.pg-cl-block-label {
  display: inline-block;
  font-size: .6875rem;
  font-weight: 650;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--bs-tertiary-color);
  margin: 0 0 .375rem;
}

/* --- Toolbar: filter chips + search -------------------------------- */
.pg-cl-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 1.25rem;
  margin-bottom: 2rem;
  border-bottom: 1px solid var(--bs-border-color);
}

.pg-cl-filters {
  display: flex;
  flex-wrap: wrap;
  gap: .375rem;
}

.pg-cl-chip {
  display: inline-flex;
  align-items: center;
  gap: .4375rem;
  padding: .3125rem .6875rem;
  border: 1px solid var(--bs-border-color);
  border-radius: 100px;
  font-size: .8125rem;
  font-weight: 550;
  color: var(--bs-secondary-color);
  transition: border-color .15s ease, color .15s ease, background-color .15s ease;
}

.pg-cl-chip:hover {
  border-color: var(--pg-accent);
  color: var(--pg-accent);
}

.pg-cl-chip-count {
  font-size: .6875rem;
  font-variant-numeric: tabular-nums;
  color: var(--bs-tertiary-color);
}

.pg-cl-chip--active {
  background: var(--bs-primary);
  border-color: var(--bs-primary);
  color: #fff;
}

.pg-cl-chip--active .pg-cl-chip-count,
.pg-cl-chip--active:hover {
  color: #fff;
}

/* An inactive chip carries a small colour cue matching its badge, so the
   filter row reads as the same vocabulary as the entries below it. */
.pg-cl-chip--danger:not(.pg-cl-chip--active) { border-color: rgba(220, 53, 69, .35); }
.pg-cl-chip--warning:not(.pg-cl-chip--active) { border-color: rgba(255, 193, 7, .45); }
.pg-cl-chip--success:not(.pg-cl-chip--active) { border-color: rgba(25, 135, 84, .3); }

.pg-cl-search {
  display: flex;
  align-items: center;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  overflow: hidden;
  min-width: 15rem;
  transition: border-color .15s ease;
}

.pg-cl-search:focus-within {
  border-color: var(--pg-accent);
}

.pg-cl-search-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: .4375rem .625rem;
  font-family: inherit;
  font-size: .875rem;
  color: var(--bs-body-color);
}

.pg-cl-search-input:focus {
  outline: none;
}

.pg-cl-search-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: .4375rem .625rem;
  border: 0;
  background: transparent;
  color: var(--bs-secondary-color);
  cursor: pointer;
}

.pg-cl-search-btn:hover {
  color: var(--pg-accent);
}

.pg-cl-resultbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin: -1rem 0 1.75rem;
}

.pg-cl-resultbar-text {
  margin: 0;
  font-size: .875rem;
  color: var(--bs-secondary-color);
}

.pg-cl-resultbar-clear {
  font-size: .8125rem;
  font-weight: 600;
  color: var(--pg-accent);
}

/* --- Entries -------------------------------------------------------- */
/* minmax(0, 1fr) rather than the implicit `auto` track, and min-width:0
   on every nested grid item.
 *
 * A grid item defaults to min-width:auto, which refuses to shrink below
 * its own content — so a long code line inside an entry, or a wide
 * endpoint chip, made the whole list wider than the viewport and pushed
 * the page into horizontal scroll below 480px. Measured, not inferred:
 * every element from .pg-cl-entry down reported 479px against a 360px
 * viewport. The <pre> blocks already scroll internally; they just needed
 * a parent that was allowed to be narrower than they are. */
.pg-cl-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.pg-cl-entry {
  display: grid;
  grid-template-columns: 10rem minmax(0, 1fr);
  gap: 2.5rem;
  padding: 2rem 0;
  border-top: 1px solid var(--bs-border-color);
  min-width: 0;
}

.pg-cl-body {
  min-width: 0;
}

.pg-cl-entry:first-child {
  border-top: 0;
  padding-top: .5rem;
}

/* Breaking changes, security updates and deprecations get a coloured
   edge rather than a filled panel: enough to find them while scanning,
   without turning the page into a warning banner. */
.pg-cl-entry--prominent .pg-cl-body {
  border-left: 2px solid var(--bs-border-color);
  padding-left: 1.25rem;
  margin-left: -1.25rem;
}

.pg-cl-rail {
  display: flex;
  flex-direction: column;
  gap: .375rem;
  padding-top: .125rem;
}

.pg-cl-date {
  font-size: .8125rem;
  font-weight: 600;
  color: var(--bs-body-color);
  font-variant-numeric: tabular-nums;
}

.pg-cl-version {
  display: inline-block;
  width: fit-content;
  padding: .125rem .4375rem;
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
  font-family: var(--font-ui-mono);
  font-size: .6875rem;
  color: var(--bs-secondary-color);
}

.pg-cl-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .625rem;
  margin-bottom: .5rem;
}

.pg-cl-type {
  display: inline-flex;
  align-items: center;
  padding: .1875rem .5rem;
  border-radius: var(--bs-border-radius);
  font-size: .6875rem;
  font-weight: 650;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
}

.pg-cl-type--danger { background: rgba(220, 53, 69, .12); color: var(--bs-danger); }
.pg-cl-type--warning { background: rgba(255, 193, 7, .16); color: #a17700; }
.pg-cl-type--success { background: rgba(25, 135, 84, .12); color: var(--bs-success); }
.pg-cl-type--info { background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); }

[data-bs-theme="dark"] .pg-cl-type--warning { color: #e6b422; }

.pg-cl-title {
  font-size: 1.0625rem;
  font-weight: 650;
  line-height: 1.4;
  letter-spacing: -.01em;
  margin: 0;
  flex: 1 1 16rem;
}

/* The permalink stays out of the way until the entry is hovered or the
   link itself is focused, which keeps a scannable list from being
   peppered with hash marks. */
.pg-cl-anchor {
  margin-left: .375rem;
  color: var(--bs-tertiary-color);
  font-weight: 400;
  opacity: 0;
  transition: opacity .15s ease, color .15s ease;
}

.pg-cl-entry:hover .pg-cl-anchor,
.pg-cl-anchor:focus-visible {
  opacity: 1;
}

.pg-cl-anchor:hover {
  color: var(--pg-accent);
}

.pg-cl-summary {
  font-size: .9375rem;
  line-height: 1.7;
  color: var(--bs-secondary-color);
  margin: 0;
  max-width: 44rem;
}

.pg-cl-block {
  margin-top: 1rem;
}

.pg-cl-resources {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .375rem;
}

.pg-cl-resources code {
  display: inline-block;
  max-width: 100%;
  word-break: break-word;
  padding: .1875rem .5rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
  font-size: .75rem;
  color: var(--bs-body-color);
}

.pg-cl-impact {
  margin-top: 1rem;
  padding: .875rem 1rem;
  border-radius: var(--bs-border-radius);
  background: var(--bs-tertiary-bg);
}

.pg-cl-impact-text {
  margin: 0;
  font-size: .9375rem;
  line-height: 1.65;
  color: var(--bs-body-color);
}

.pg-cl-sunset {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 1rem 0 0;
  font-size: .875rem;
  color: var(--bs-body-color);
}

.pg-cl-sunset .pg-cl-block-label {
  margin: 0;
}

/* Long form detail reuses the documentation column's typography rather
   than defining a second prose scale, minus its own max-width and
   padding, which the entry already provides. */
.pg-cl-detail {
  margin-top: 1rem;
  max-width: none;
  padding: 0;
}

.pg-cl-detail > :first-child { margin-top: 0; }
.pg-cl-detail > :last-child { margin-bottom: 0; }
.pg-cl-detail h2 { font-size: 1rem; margin: 1.5rem 0 .5rem; }
.pg-cl-detail h3 { font-size: .9375rem; margin: 1.25rem 0 .5rem; }

.pg-cl-detail pre {
  overflow-x: auto;
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  padding: .75rem .875rem;
  margin: .875rem 0;
}

.pg-cl-migration {
  margin-top: 1rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  padding: .75rem 1rem;
}

.pg-cl-migration-summary {
  cursor: pointer;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--pg-accent);
  list-style: none;
}

.pg-cl-migration-summary::-webkit-details-marker {
  display: none;
}

.pg-cl-migration-summary::before {
  content: "+";
  display: inline-block;
  width: 1rem;
  font-weight: 400;
}

.pg-cl-migration[open] .pg-cl-migration-summary::before {
  content: "−";
}

.pg-cl-links {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 1rem;
}

.pg-cl-link {
  display: inline-flex;
  align-items: center;
  gap: .3125rem;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--pg-accent);
}

.pg-cl-link:hover {
  text-decoration: underline;
}

@media (max-width: 991.98px) {
  /* The rail becomes a header line above the change rather than a
     column beside it. */
  .pg-cl-entry {
    grid-template-columns: minmax(0, 1fr);
    gap: .75rem;
    padding: 1.5rem 0;
  }

  .pg-cl-rail {
    flex-direction: row;
    align-items: center;
    gap: .625rem;
  }

  .pg-cl-entry--prominent .pg-cl-body {
    border-left: 0;
    padding-left: 0;
    margin-left: 0;
  }
}

@media (max-width: 575.98px) {
  .pg-cl-hero {
    padding: 2rem 0 1.25rem;
  }

  .pg-cl-toolbar {
    align-items: stretch;
  }

  .pg-cl-search {
    width: 100%;
  }
}

/* Between the two breakpoints the fixed 43rem prose track no longer fits
   beside a 17.5rem rail inside the container, so the track becomes
   fluid and both columns narrow together rather than overflowing. */
@media (max-width: 1199.98px) {
  .pg-article-grid {
    grid-template-columns: minmax(0, 1fr) 16rem;
    gap: 2.5rem;
  }
}

@media (max-width: 991.98px) {
  /* Sidebar drops below the articles: at this width the grid is one
     column, so there is nothing for it to stick alongside. */
  .pg-blog-layout,
  .pg-article-grid {
    grid-template-columns: 1fr;
    gap: 3rem;
  }

  /* Stacked, the prose re-centres on its own measure instead of running
     the full container width. */
  .pg-article-main {
    max-width: 43rem;
    margin-inline: auto;
    width: 100%;
  }

  .pg-article-grid .pg-blog-sidebar {
    max-width: 43rem;
    margin-inline: auto;
    width: 100%;
  }

  .pg-blog-sidebar {
    position: static;
  }

  .pg-blog-lead {
    grid-template-columns: 1fr;
  }

  .pg-blog-lead-media {
    min-height: 0;
    aspect-ratio: 16 / 9;
  }
}

@media (max-width: 575.98px) {
  .pg-blog-hero {
    padding: 2rem 0 1.25rem;
  }

  .pg-blog-grid {
    grid-template-columns: 1fr;
  }

  /* The article head's rhythm tightens with the viewport: at this width
     the title, standfirst and meta rule take most of the first screen,
     and the desktop spacing pushed the opening paragraph out of view. */
  .pg-article-grid {
    padding-top: 1.75rem;
  }

  .pg-article-head {
    padding: 0 0 1.25rem;
  }

  .pg-blog-lead-body {
    gap: .5rem;
  }

  /* Page numbers are the first thing to go on a narrow screen; Previous
     and Next still give full access to the archive. */
  .pg-blog-pagenums {
    display: none;
  }

  .pg-blog-pagination {
    justify-content: space-between;
  }
}
