/*
 * library.css -- SME PDF Library (GENERATED -- do not edit)
 *
 * Built by scripts/build-css.js from assets\styles\library.css.
 * Edit the source files, then run npm run build:css.
 */
/*
 * library.css — SME PDF Library frontend entry point
 *
 * Enqueued by Assets::register() as handle `sme-pdf-library`.
 * Load order: tokens (via base) → base → components → motion.
 *
 * base.css @imports tokens.css and owns the root typography.
 * This file imports base.css (which pulls tokens) then the component files.
 * It does NOT re-declare base.css rules.
 */

/* ---- base (tokens import + root typography + reset + container) ---- */
/*
 * base.css — unlayered, .spl-root-scoped foundation.
 * No @layer / @scope: the design system must WIN over hostile themes (Mayosis/
 * Elementor) whose UNLAYERED rules beat any layered rule. Flat .spl-root prefixing
 * gives the specificity to win; a scoped `all: revert` reset wipes inherited theme
 * styles (incl. -webkit-text-fill-color) for a clean slate before tokens apply.
 */
/*
 * tokens.css — SME PDF Library design tokens
 *
 * Single source of truth for all --spl-* custom properties.
 * Values are transcribed verbatim from:
 *   docs/superpowers/specs/2026-06-19-plan-3-frontend-mockup.html
 *
 * Raw colour / spacing / font values are legitimate here (they ARE the
 * primitives). stylelint-declaration-strict-value targets CONSUMERS of
 * tokens; token definitions are exempted via .stylelintrc.json overrides.
 *
 * No @layer wrapper: this file is imported by base.css which is itself
 * unlayered. Keeping tokens out of any layer lets the .spl-root and
 * .spl-admin-root custom properties cascade freely to unlayered component rules.
 */

.spl-root,
.spl-admin-root {
  /* ------------------------------------------------------------------
   * Primitives — seeded from the live brand (smenode-labs.com)
   * ------------------------------------------------------------------ */
  --spl-color-orange-500: #fc6720;
  --spl-color-orange-600: #e6571a;
  --spl-color-orange-800: #c2410c; /* AA-safe burnt orange for white-on-orange CTAs (~5.9:1) */
  --spl-color-orange-050: #fff3ec;
  --spl-color-navy-950: #03283b;   /* hero gradient dark start stop */
  --spl-color-navy-900: #023047;   /* brand deep navy */
  --spl-color-navy-850: #063650;   /* hero gradient lighter end stop */
  --spl-color-navy-800: #04263a;
  --spl-color-navy-700: #0a3a57;
  --spl-color-teal-500: #0fa3a3;
  --spl-color-slate-600: #475569;
  --spl-color-slate-500: #64748b;
  --spl-color-slate-300: #cbd5e1;
  --spl-color-slate-200: #e6ebf1;
  --spl-color-white: #fff;
  --spl-color-grey-100: #f1f4f8;
  --spl-color-grey-050: #f7f9fc;
  --spl-color-green-700: #0d6e47; /* AA-safe on --spl-color-green-050 (~5.4:1) */
  --spl-color-green-600: #16855c;
  --spl-color-green-050: #e7f6ef;
  --spl-color-blue-600: #2c5fd6;
  --spl-color-blue-050: #eaf1ff;
  --spl-color-amber-600: #8a5600;
  --spl-color-amber-050: #fbf2df;

  /* Admin redesign primitives (Plan 7A.2 Task 1) — transcribed verbatim from
   * docs/superpowers/specs/assets/2026-07-06-admin-redesign-mockup.html :root.
   * These are WordPress core's own wp-admin neutrals (canvas/border/ink), kept
   * as distinct primitives from the marketing "slate/grey" scale above because
   * they must match wp-admin chrome exactly, not the brand's own palette. */
  --spl-color-orange-700: #d4693d; /* mockup --orange-hover */
  --spl-color-wp-canvas: #f0f0f1; /* mockup --canvas */
  --spl-color-wp-surface-alt: #f6f7f7; /* mockup --card-2 (zebra/hover surface) */
  --spl-color-wp-border: #dcdcde; /* mockup --border */
  --spl-color-wp-border-strong: #c3c4c7; /* mockup --border-strong */
  --spl-color-wp-ink: #1e1e1e; /* mockup --ink */
  --spl-color-wp-ink-muted: #646970; /* mockup --ink-muted */
  --spl-color-wp-ink-faint: #8c8f94; /* mockup --ink-faint */
  --spl-color-navy-tint-100: #eaf0f3; /* mockup --navy-tint */
  --spl-color-navy-tint-050: #f3f7f9; /* mockup --navy-tint-2 */
  --spl-color-status-good: #007a1f; /* mockup --good */
  --spl-color-status-good-bg: #edfaef; /* mockup --good-bg */
  --spl-color-status-warn: #9a6700; /* mockup --warn */
  --spl-color-status-warn-bg: #fcf6e6; /* mockup --warn-bg */
  --spl-color-status-crit: #c02629; /* mockup --crit */
  --spl-color-status-crit-bg: #fcebec; /* mockup --crit-bg */
  --spl-color-status-info: #135e96; /* mockup --info */
  --spl-color-status-info-bg: #e8f2fb; /* mockup --info-bg */

  /* ------------------------------------------------------------------
   * Font stacks
   * ------------------------------------------------------------------ */
  --spl-font-heading: "Poppins", "Poppins Fallback", system-ui, -apple-system, "Segoe UI", sans-serif;
  --spl-font-body: "Inter", "Inter Fallback", system-ui, -apple-system, "Segoe UI", sans-serif;
  --spl-font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", "Consolas", monospace;
  --spl-font-system: -apple-system, "BlinkMacSystemFont", "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif; /* mockup --font; admin redesign only, Plan 7A.2 Task 1 */

  /* ------------------------------------------------------------------
   * Type scale
   * ------------------------------------------------------------------ */
  --spl-text-xs: 0.75rem;
  --spl-text-sm: 0.85rem;
  --spl-text-base: 1rem;
  --spl-text-lg: 1.18rem;
  --spl-text-xl: 1.5rem;
  --spl-text-2xl: 2rem;
  --spl-text-3xl: 2.7rem;

  /* Admin type scale (Plan 7A.2 Task 1) — the mockup's font sizes are exact
   * px values that don't line up with the marketing rem scale above (e.g.
   * 13px vs. --spl-text-sm's 13.6px), and the mockup pixel spec is the
   * authoritative source for the admin redesign. Kept as its own flat px
   * scale rather than force-fitting the unrelated marketing scale. */
  --spl-text-admin-2xs: 11px;  /* eyebrow, thead th, pill/tag, countbadge */
  --spl-text-admin-xs: 12px;   /* tile label/delta, card__h .sub, chipbtn, viewtab, note */
  --spl-text-admin-sm: 13px;   /* btn, table body, field label, notice */
  --spl-text-admin-base: 14px; /* card__h h3, tabs .tab, field input/select */
  --spl-text-admin-md: 15px;   /* card__h h2 */
  --spl-text-admin-lg: 16px;   /* empty state h3 */
  --spl-text-admin-xl: 23px;   /* phead h1 */
  --spl-text-admin-2xl: 26px;  /* tile .num */

  /* ------------------------------------------------------------------
   * Spacing scale
   * ------------------------------------------------------------------ */
  --spl-space-1: 0.25rem;
  --spl-space-2: 0.5rem;
  --spl-space-3: 0.75rem;
  --spl-space-4: 1rem;
  --spl-space-5: 1.5rem;
  --spl-space-6: 2rem;
  --spl-space-8: 3rem;
  --spl-space-px-1: 1px;
  --spl-space-px-2: 2px;
  --spl-space-px-3: 3px;
  --spl-space-px-5: 5px;
  --spl-space-px-6: 6px;
  --spl-space-px-7: 7px;
  --spl-space-px-9: 9px;
  --spl-space-px-10: 10px;
  --spl-space-px-11: 11px;
  --spl-space-px-13: 13px;
  --spl-space-px-14: 14px;
  --spl-space-px-18: 18px;
  --spl-space-px-22: 22px;
  --spl-space-px-30: 30px;

  /* ------------------------------------------------------------------
   * Border radius
   * ------------------------------------------------------------------ */
  --spl-radius-sm: 8px;
  --spl-radius-md: 10px;
  --spl-radius-lg: 14px;
  --spl-radius-pill: 999px;
  --spl-radius-admin-sm: 5px; /* mockup --radius-sm; admin controls/pills only, not part of the xs/sm/md/lg marketing scale */
  --spl-radius-admin-2xs: 2px; /* mockup .phead h1 .accent rule */
  --spl-radius-admin-xs: 4px; /* mockup .tag */
  --spl-radius-admin-lg: 12px; /* mockup .empty .ic icon badge */

  /* ------------------------------------------------------------------
   * Border width
   * ------------------------------------------------------------------ */
  --spl-border-width-thin: 1px;

  /* ------------------------------------------------------------------
   * Shadows
   * ------------------------------------------------------------------ */
  --spl-shadow-card: 0 1px 2px rgb(2 48 71 / 7%), 0 6px 16px rgb(2 48 71 / 6%);
  --spl-shadow-lift: 0 14px 30px rgb(2 48 71 / 15%);
  --spl-shadow-hero-search-focus: var(--spl-shadow-lift), 0 0 0 3px var(--spl-color-navy-900), 0 0 0 5px var(--spl-color-white);
  --spl-shadow-admin-card: 0 1px 2px rgb(0 0 0 / 5%), 0 1px 3px rgb(0 0 0 / 4%); /* mockup --shadow; admin redesign only, Plan 7A.2 Task 1 */

  /* ------------------------------------------------------------------
   * Semantics — consumed by components; always reference primitives above
   * ------------------------------------------------------------------ */
  --spl-accent: var(--spl-color-orange-500);
  --spl-accent-strong: var(--spl-color-orange-600);
  --spl-accent-tint: var(--spl-color-orange-050);
  --spl-accent-ink: #b8480d;   /* AA-safe orange for small text/icons on white (~4.6:1) */
  --spl-accent-hover: #ff7a38; /* lighter orange for hover on .spl-btn-primary */
  --spl-accent-cta: var(--spl-color-orange-800); /* white-on-orange CTA inside the download box only (AA ~5.9:1) */
  --spl-accent-cta-hover: #a8380a; /* darker burnt orange for the download-box CTA :hover */
  --spl-accent-warm: #ff8a4d;  /* lighter warm orange — icon-badge gradient end stop */
  --spl-surface: var(--spl-color-white);
  --spl-surface-soft: var(--spl-color-grey-100);
  --spl-surface-page: var(--spl-color-grey-050);
  --spl-border: var(--spl-color-slate-200);
  --spl-control-border: var(--spl-color-slate-500);
  --spl-focus-ring: var(--spl-accent-ink);
  --spl-text: var(--spl-color-slate-500);
  --spl-text-muted: var(--spl-color-slate-500);
  --spl-text-strong: var(--spl-color-slate-600);
  --spl-heading-color: var(--spl-color-navy-900);
  --spl-accent-dark: var(--spl-accent-ink);

  /* ------------------------------------------------------------------
   * Admin design tokens (Plan 7A.2 Task 1) — consumer-facing surface for
   * .spl-admin-* component styles. Always reference a primitive above;
   * never a raw hex here. No dark-mode override: Phase 1 excludes dark
   * mode for the admin UI, so light values only.
   * ------------------------------------------------------------------ */
  --spl-admin-canvas: var(--spl-color-wp-canvas);
  --spl-admin-card: var(--spl-color-white);
  --spl-admin-card-2: var(--spl-color-wp-surface-alt);
  --spl-admin-border: var(--spl-color-wp-border);
  --spl-admin-border-strong: var(--spl-color-wp-border-strong);
  --spl-admin-ink: var(--spl-color-wp-ink);
  --spl-admin-ink-muted: var(--spl-color-wp-ink-muted);
  --spl-admin-ink-faint: var(--spl-color-wp-ink-faint);
  --spl-admin-navy: var(--spl-color-navy-900);
  --spl-admin-orange: var(--spl-color-orange-500);
  --spl-admin-orange-hover: var(--spl-color-orange-700);
  --spl-admin-navy-tint: var(--spl-color-navy-tint-100);
  --spl-admin-navy-tint-2: var(--spl-color-navy-tint-050);
  --spl-admin-good: var(--spl-color-status-good);
  --spl-admin-good-bg: var(--spl-color-status-good-bg);
  --spl-admin-warn: var(--spl-color-status-warn);
  --spl-admin-warn-bg: var(--spl-color-status-warn-bg);
  --spl-admin-crit: var(--spl-color-status-crit);
  --spl-admin-crit-bg: var(--spl-color-status-crit-bg);
  --spl-admin-info: var(--spl-color-status-info);
  --spl-admin-info-bg: var(--spl-color-status-info-bg);
  --spl-admin-radius: var(--spl-radius-sm); /* mockup --radius (8px); reuses the existing primitive */
  --spl-admin-radius-sm: var(--spl-radius-admin-sm); /* mockup --radius-sm (5px) */
  --spl-admin-gap: var(--spl-space-4); /* mockup --gap (16px); reuses the existing primitive */
  --spl-admin-font: var(--spl-font-system);
  --spl-admin-shadow: var(--spl-shadow-admin-card);

  /* ------------------------------------------------------------------
   * Additional shadows
   * ------------------------------------------------------------------ */
  --spl-shadow-chip: 0 2px 8px rgb(2 16 24 / 40%); /* lock/chip on dark cover plate */

  /* ------------------------------------------------------------------
   * Extra type scale
   * ------------------------------------------------------------------ */
  --spl-text-2xs: 0.66rem; /* tiny flag chips on cover plate */
  --spl-text-suggest-grp: 0.7rem; /* autocomplete group label */

  /* ------------------------------------------------------------------
   * Extra border radius
   * ------------------------------------------------------------------ */
  --spl-radius-xs: 3px;     /* inline mark highlight */
  --spl-radius-circle: 50%; /* circular avatars / lock badges */

  /* ------------------------------------------------------------------
   * On-dark text / border utilities (hero, band, single hero)
   * ------------------------------------------------------------------ */
  --spl-hero-text: #e7ecf3;   /* hero section body text */
  --spl-on-dark-muted: #b7c2d0; /* lede / hero-sub / post-meta on dark */
  --spl-on-dark-dim: #9fb0c2;   /* dimmer label / band sub on dark */
  --spl-on-dark-link: #cdd8e4;  /* breadcrumb links on dark */
  --spl-on-dark-border-btn: rgb(255 255 255 / 30%); /* ghost btn border on dark */

  /* ------------------------------------------------------------------
   * Scrim / overlay tokens (dark glass chips on the cover plate)
   * ------------------------------------------------------------------ */
  --spl-scrim-dark: rgb(2 16 24 / 32%);   /* .c-topic chip background */
  --spl-scrim-darker: rgb(2 16 24 / 42%); /* .c-code chip background */
  --spl-scrim-light: rgb(255 255 255 / 14%); /* badge on dark hero */
  --spl-cover-border: rgb(255 255 255 / 20%); /* .c-code border on dark */
  --spl-cover-grid-line: rgb(255 255 255 / 7%); /* cover grid overlay */
  --spl-cover-highlight: rgb(255 255 255 / 18%); /* cover corner highlight */
  --spl-cover-sheen: rgb(255 255 255 / 28%);     /* hover sheen sweep */
  --spl-cover-hue-default: #0f5e7a;      /* fallback cover gradient start when no topic hue class is present */
  --spl-cover-hue-default-lt: #1a86ad;   /* fallback cover gradient end when no topic hue class is present */
  --spl-hero-glow-accent: rgb(252 103 32 / 30%); /* archive/single hero orange glow */
  --spl-hero-glow-teal: rgb(15 163 163 / 26%);   /* archive/single hero teal glow */
  --spl-band-glow-accent: rgb(252 103 32 / 16%); /* subscription band orange glow */
  --spl-band-glow-teal: rgb(15 163 163 / 18%);   /* subscription band teal glow */
  --spl-sheet-scrim: rgb(0 0 0 / 46%);            /* mobile filter sheet backdrop */
  --spl-mask-ink: #000;                           /* mask-image opaque stop */

  /* ------------------------------------------------------------------
   * Z-index scale
   * ------------------------------------------------------------------ */
  --spl-z-statcard: 5;    /* stat card overlaps hero bottom edge */
  --spl-z-suggest: 30;    /* autocomplete panel above hero content */
  --spl-z-sheet: 100;     /* mobile filter sheet above plugin content */
  --spl-z-card-link: 1;   /* .spl-card__link ::after stretched overlay; interactive children use z-index >= 2 */
  --spl-z-skip-link: 9999; /* skip link floats above all page chrome */

  /* ------------------------------------------------------------------
   * Motion
   * ------------------------------------------------------------------ */
  --spl-motion-duration-2xs: 0.01ms; /* reduced-motion kill switch */
  --spl-motion-duration-xs: 0.15s;
  --spl-motion-duration-sm: 0.16s;
  --spl-motion-duration-md: 0.18s;
  --spl-motion-duration-lg: 0.2s;
  --spl-motion-duration-xl: 0.22s;
  --spl-motion-duration-sheet: 0.25s;
  --spl-motion-duration-cover: 0.5s;
  --spl-motion-duration-sheen: 0.6s;
  --spl-motion-duration-skeleton: 1.6s;
  --spl-motion-ease-standard: ease;
  --spl-motion-ease-emphasis: cubic-bezier(0.2, 0.7, 0.2, 1);
  --spl-motion-ease-pulse: ease-in-out;

  /* ------------------------------------------------------------------
   * Component size caps
   * ------------------------------------------------------------------ */
  --spl-cert-chip-max-width: 10rem; /* .c-code cert chip on cover plate (~160px) */
  --spl-tap-min: 44px;              /* minimum tap-target height (WCAG 2.5.5) */
  --spl-tap-target-min: 24px;       /* minimum pointer target size (WCAG 2.5.8 AA) */
  --spl-statcard-overlap: 44px;     /* archive stat card overlap with the hero bottom edge */
  --spl-pagination-pad-inline: 14px; /* pagination link horizontal padding */
  --spl-icon-xs: 13px;
  --spl-icon-sm: 14px;
  --spl-icon-md: 15px;
  --spl-icon-lg: 16px;
  --spl-icon-xl: 18px;
  --spl-icon-2xl: 22px;
  --spl-icon-feature: 46px;
  --spl-control-size-sm: 28px;
  --spl-hero-search-btn-size: 40px;
  --spl-author-avatar-size: 60px;

  /* Admin redesign component sizes (Plan 7A.2 Task 1) — dimensions with no
   * existing spacing/size-token match, that recur across the admin screens.
   * (Values that DO match an existing token reference it directly in admin.css:
   * 3px/4px/6px/8px/18px/22px/24px borders+dots+padding, and the .spl-admin-spark
   * 64x22 box mirrors the Sparkline.tsx viewBox constants, so those stay inline.) */
  --spl-admin-accent-bar-width: 34px;   /* PageHeader h1 navy accent rule width */
  --spl-admin-tile-pad-block: 15px;     /* KPI tile vertical padding (mockup .tile 15px 16px) */
  --spl-admin-topic-max-width: 150px;   /* Leads toolbar topic-ID filter width (Plan 7A.2 Task 7) */
  --spl-admin-resource-max-width: 170px; /* Leads grid Resource cell max-width before ellipsis truncation (colwidth follow-up: cut from 220px so Resource can't balloon the table past the container) */

  /* Leads DataViews table column widths (admin-UI colwidth follow-up). Fixed CAPS, not floors:
   * @wordpress/dataviews' table auto-layout distributes any spare width across columns whose
   * only constraint is a min-width, so a floor-only approach let Email/Resource/Captured
   * balloon (measured 338/194/189px) and pushed the table to 1523px, past the ~1157px
   * container. Each non-title column below is pinned with BOTH width and maxWidth (same value)
   * in LeadsSection.tsx DEFAULT_VIEW.layout.styles, referenced via var() so the value lives
   * once. Email (the titleField/primary column) and Resource (pdf_id) have no fixed-width
   * layout.styles hook that auto-layout respects here, so they are capped by CSS truncation
   * instead: Email via --spl-admin-leads-col-email-max (admin.css .dataviews-title-field rule),
   * Resource via the existing --spl-admin-resource-max-width on its render span. */
  --spl-admin-leads-col-email-max: 190px; /* Email (titleField) truncation cap; CSS-only hook */
  --spl-admin-leads-col-topic: 92px;
  --spl-admin-leads-col-downloads: 76px;
  --spl-admin-leads-col-consent: 122px;
  --spl-admin-leads-col-gated: 84px;
  --spl-admin-leads-col-captured: 146px;
  --spl-admin-leads-col-status: 96px;
  --spl-admin-field-max-width: 420px;   /* settings field column max-width */
  --spl-admin-empty-icon-size: 52px;    /* empty-state icon badge box (52x52) */
  --spl-admin-empty-icon-glyph: 24px;   /* empty-state inner SVG glyph (24x24) */
  --spl-admin-empty-pad-start: 52px;    /* empty-state top padding */
  --spl-admin-empty-pad-end: 56px;      /* empty-state bottom padding */
  --spl-admin-toggle-width: 38px;       /* settings toggle track width */
  --spl-prose-list-indent: 1.2em;
  --spl-letter-spacing-tight: 0.02em;
  --spl-letter-spacing-chip: 0.04em;
  --spl-letter-spacing-eyebrow: 0.06em;
  --spl-letter-spacing-label: 0.08em;
  --spl-letter-spacing-admin-th: 0.05em; /* mockup thead th letter-spacing */

  /* ------------------------------------------------------------------
   * Layout primitives — shell container + root typography
   * Not guarded by declaration-strict-value (layout, not colour/font).
   * ------------------------------------------------------------------ */
  --spl-card-intrinsic-size: 360px; /* contain-intrinsic-size placeholder for content-visibility on .spl-card */
  --spl-cols: 4; /* default grid columns; [sme_pdf_grid] overrides inline per instance */
  --spl-shell-max: 1365px;       /* .shell max-width, matches site header container */
  --spl-shell-width: 87%;        /* .shell proportional width on desktop (87% of viewport) */
  --spl-shell-pad: 24px;         /* .shell inline padding (mobile/tablet) */
  --spl-admin-max-width: 800px;  /* legacy Plan 7A admin cap — superseded by --spl-admin-w-data (1200px) / --spl-admin-w-form (840px); no longer consumed */
  --spl-admin-modal-width: 640px; /* Leads detail modal content width (Plan 7A Task 10) */
  --spl-admin-w-data: 1200px;    /* .spl-admin-wrap max-width — Dashboard/Leads data screens (Plan 7A.2 Task 1) */
  --spl-admin-w-form: 840px;     /* .spl-admin-wrap--form max-width — Settings form screen (Plan 7A.2 Task 1) */
  --spl-line-base: 1.6;          /* body line-height */
  --spl-line-heading: 1.18;      /* heading line-height */
  --spl-heading-tracking: -0.015em; /* heading letter-spacing */

  /* ------------------------------------------------------------------
   * Transparent-header offset — measured at runtime by header-offset.ts
   * and written to :root (NOT to .spl-root) as --spl-header-offset.
   * The value cascades from :root to .spl-root so it must NOT be redeclared
   * here; a declaration on .spl-root would override the :root JS-written value.
   * PHP seeds an inline :root default before the module loads (see Assets.php).
   * Default fallback in calc() expressions is 0px — no-op on themes without
   * an overlay header.
   * ------------------------------------------------------------------ */
}

/* ------------------------------------------------------------------
 * Topic cover-hue classes (rich, brand-anchored — no purple/indigo)
 * Applied to the card element alongside .spl-card to colour the cover
 * gradient via --cover-hue / --cover-hue-lt custom properties.
 * Prefixed with .spl-root for scope hygiene (keeps hue vars off theme
 * elements that happen to share these class names).
 * ------------------------------------------------------------------ */
.spl-root .h-networking {
  --cover-hue: #0d8a9c;
  --cover-hue-lt: #16b6c0;
}

.spl-root .h-cyber {
  --cover-hue: #d4541f;
  --cover-hue-lt: #f57a36;
}

.spl-root .h-cloud {
  --cover-hue: #2c6fd6;
  --cover-hue-lt: #4f97f0;
}

.spl-root .h-devops {
  --cover-hue: #2f7a6b;
  --cover-hue-lt: #3fae8f;
}

.spl-root .h-linux {
  --cover-hue: #c2790c;
  --cover-hue-lt: #efab2e;
}

.spl-root .h-programming {
  --cover-hue: #1f7a86;
  --cover-hue-lt: #2fb0a0;
}

.spl-root .h-career {
  --cover-hue: #e0641f;
  --cover-hue-lt: #fb8b3e;
}


/* Self-contained root: re-establish a clean box on full-width theme templates. */
.spl-root {
  color: var(--spl-text);
  font-family: var(--spl-font-body);
  background: var(--spl-surface-page);
  line-height: var(--spl-line-base);

  /* Transparent-header pull-up: slide the plugin root up behind the overlay header.
     Defaults to 0 (no-op) when --spl-header-offset is 0px; measured at runtime by
     header-offset.ts and written to :root so all .spl-root instances share the value. */
  margin-top: calc(-1 * var(--spl-header-offset, 0px));
}

/* Scoped reset — roll shared elements back to UA defaults (not theme inheritance). */
.spl-root :where(h1, h2, h3, h4, h5, h6, p, a, ul, ol, li, button, input, select, textarea, label, figure, blockquote, header, main, aside, section, nav, div, span) {
  all: revert;
}

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

/* Global heading rule — Poppins + navy by default (dark contexts override colour). */
.spl-root :is(h1, h2, h3, h4, h5, h6) {
  font-family: var(--spl-font-heading);
  color: var(--spl-heading-color);
  line-height: var(--spl-line-heading);
  margin: 0;
  letter-spacing: var(--spl-heading-tracking);
  font-weight: 700;
}

.spl-root a {
  color: var(--spl-accent-ink);
}

.spl-root :focus-visible {
  outline: 2px solid var(--spl-focus-ring);
  outline-offset: 2px;
  border-radius: var(--spl-radius-sm);
}

/*
 * Skip link — keyboard / AT users bypass hero and navigation to reach #main.
 * Visually hidden until focused; revealed via :focus-visible so only keyboard
 * users see it (not mouse users who accidentally hover or click).
 * Rendered at the very first position inside .spl-root so Tab key reaches it first.
 */
.spl-root .spl-skip-link {
  position: absolute;
  left: var(--spl-space-3);
  top: var(--spl-space-3);
  z-index: var(--spl-z-skip-link);
  padding: var(--spl-space-2) var(--spl-space-4);
  background: var(--spl-color-navy-900);
  color: var(--spl-color-white);
  font-weight: 700;
  font-size: var(--spl-text-sm);
  border-radius: var(--spl-radius-md);
  text-decoration: none;

  /* Visually hidden until focused */
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  width: 1px;
  height: 1px;
  overflow: hidden;
  white-space: nowrap;
}

.spl-root .spl-skip-link:focus-visible {
  /* Undo the visual-hide and show as a floating pill */
  clip: auto;
  clip-path: none;
  width: auto;
  height: auto;
  overflow: visible;
  white-space: normal;

  /* Use the same brand focus ring from the global :focus-visible rule */
  outline: 2px solid var(--spl-focus-ring);
  outline-offset: 2px;
}

/* Self-contained max-width container (was relied upon from the theme). Mobile-first. */
.spl-root .shell {
  width: 100%;
  max-width: var(--spl-shell-max);
  margin: 0 auto;
  padding: 0 var(--spl-shell-pad);
}

/* Desktop: switch to proportional width matching the site header container. */
@media (width >= 1025px) {
  .spl-root .shell {
    width: var(--spl-shell-width);
    padding: 0;
  }
}

/*
 * Body scroll lock — applied by the mobile filter sheet (Task 8, Plan 3.5).
 *
 * Using a class on <body> (rather than writing body.style.overflow directly) avoids
 * collisions with WordPress themes that also set overflow. The class is added by
 * lockBodyScroll() and removed by unlockBodyScroll() in the Interactivity store.
 *
 * This rule lives outside .spl-root because it targets the <body> element, which is
 * never a descendant of .spl-root. It only takes effect when the sheet is open and
 * the viewport is mobile-width (the sheet does not exist at desktop width).
 */
body.spl-body-lock {
  overflow: hidden;
}

/*
 * JS-required elements: hidden when JS is not available.
 *
 * The mobile filter trigger button (.spl-toolbar__sheet-trigger) must not appear
 * at all when JavaScript is disabled, because the bottom-sheet depends entirely on
 * the Interactivity store. The .spl-requires-js class marks elements that have no
 * functional no-JS fallback.
 *
 * Implementation: the JS-on signal is the `spl-js` class added to <html> by a tiny
 * inline script emitted by Assets::emitJsClassifier() synchronously before paint on
 * library pages. When JS runs the class is present; when JS is off it is absent.
 *
 * Note: `[data-wp-interactive]` cannot be used as the JS-on guard because WordPress
 * sets that attribute server-side regardless of whether the Interactivity store has
 * actually executed. `html.spl-js` is the correct, authoritative signal.
 *
 * The rule below hides .spl-requires-js elements by default (progressive-enhancement
 * default-hidden). The rule below it reveals them when JS is confirmed running.
 * The more specific mobile breakpoint rule in filters.css further constrains the
 * trigger display to <= 640px.
 */
.spl-requires-js {
  display: none;
}

/*
 * When JavaScript has executed (html.spl-js is present), reveal .spl-requires-js
 * elements at their intended breakpoint. The mobile breakpoint rule in filters.css
 * takes over from here at <= 640px to constrain the trigger to mobile only.
 */
html.spl-js .spl-requires-js {
  display: revert;
}


/* ---- component files ---- */
/*
 * hero.css — .spl-hero (archive hero) + .spl-single-hero (single-item detail hero)
 *             + .spl-statcard + shared eyebrow/hl utilities
 *
 * Dark-on-dark backgrounds use gradient compositing. Glow, mask, colour,
 * typography, shadow, radius, and z-index values reference --spl-* tokens.
 */

.spl-root {
  /* ---------- mono eyebrow + hl (declared early — lower specificity first) ---------- */

  .eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--spl-space-2);
    color: var(--spl-accent);
    font-family: var(--spl-font-mono);
    font-weight: 600;
    font-size: var(--spl-text-xs);
    letter-spacing: var(--spl-letter-spacing-eyebrow);
    margin-bottom: var(--spl-space-3);
  }

  .eyebrow::before {
    content: "//";
    font-weight: 700;
    opacity: 0.9;
  }

  .eyebrow.dark {
    color: var(--spl-accent-ink);
  }

  /* highlight span — base accent colour on dark backgrounds */
  .hl {
    color: var(--spl-accent);
  }

  /* orange-on-white contexts: override to AA-safe ink shade (higher specificity, after .hl) */
  .spl-section .hl,
  .spl-subscribe .hl,
  .spl-single-hero .hl {
    color: var(--spl-accent-ink);
  }

  /* ---------- archive hero ---------- */

  .spl-hero {
    position: relative;
    color: var(--spl-hero-text);
    background:
      radial-gradient(820px 360px at 86% -10%, var(--spl-hero-glow-accent), transparent 62%),
      radial-gradient(680px 420px at 6% 120%, var(--spl-hero-glow-teal), transparent 60%),
      linear-gradient(145deg, var(--spl-color-navy-950) 0%, var(--spl-color-navy-900) 48%, var(--spl-color-navy-850) 100%);

    /* Top padding absorbs the header offset so content clears the overlay header.
       Bottom padding keeps the statcard overlap working as designed. */
    padding: calc(var(--spl-header-offset, 0px) + var(--spl-space-8)) 0 calc(var(--spl-space-8) + var(--spl-statcard-overlap));
    overflow: hidden;
  }

  /* faint topology dot-grid motif */
  .spl-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    opacity: 0.5;
    pointer-events: none;
    background-image: radial-gradient(
      circle at 1px 1px,
      var(--spl-cover-grid-line) 1px,
      transparent 0
    );
    background-size: 30px 30px;
    mask-image: linear-gradient(120deg, transparent 40%, var(--spl-mask-ink) 100%);
  }

  .spl-hero .shell {
    position: relative;
  }

  .spl-hero h1 {
    color: var(--spl-color-white);
    font-size: var(--spl-text-3xl);
    max-width: 17ch;
  }

  .spl-hero h1 .hl {
    color: var(--spl-accent);
  }

  .spl-hero p.lede {
    color: var(--spl-on-dark-muted);
    font-size: var(--spl-text-lg);
    max-width: 56ch;
    margin: var(--spl-space-3) 0 var(--spl-space-5);
  }

  /* Archive / hub hero search pill.
   * The archive and taxonomy templates emit .spl-hero-search (BEM root) with
   * __form / __input / __btn children. The pill look (white bg, radius-pill,
   * shadow) applies to the outer wrapper so theme-hardening assertion 5 passes:
   *   .spl-hero-search border-radius != 0px
   * The __form inherits the layout; __input and __btn are flex cells inside.
   */
  .spl-hero-search {
    position: relative;
    display: flex;
    align-items: center;
    max-width: 560px;
    background: var(--spl-color-white);
    border-radius: var(--spl-radius-pill);
    box-shadow: var(--spl-shadow-lift);
  }

  .spl-hero-search:focus-within {
    box-shadow: var(--spl-shadow-hero-search-focus);
  }

  .spl-hero-search__form {
    display: flex;
    align-items: center;
    gap: var(--spl-space-px-10);
    padding: var(--spl-space-px-7) var(--spl-space-2) var(--spl-space-px-7) var(--spl-space-px-18);
    width: 100%;
    border-radius: var(--spl-radius-pill);
  }

  .spl-hero-search__input {
    border: 0;
    outline: 0;
    flex: 1;
    font-size: var(--spl-text-base);
    background: transparent;
    color: var(--spl-text-strong);
    min-width: 0;
    font-family: var(--spl-font-body);
  }

  .spl-hero-search__input:focus {
    outline: none;
  }

  .spl-hero-search__btn {
    flex: none;
    display: grid;
    place-items: center;
    width: var(--spl-hero-search-btn-size);
    height: var(--spl-hero-search-btn-size);
    border: 0;
    border-radius: var(--spl-radius-pill);
    background: var(--spl-accent);
    color: var(--spl-color-navy-900);
    cursor: pointer;
    transition: background var(--spl-motion-duration-md) var(--spl-motion-ease-standard);
  }

  .spl-hero-search__btn:hover {
    background: var(--spl-accent-hover);
  }

  .spl-hero-search__btn:focus-visible {
    outline: 2px solid var(--spl-color-navy-900);
    outline-offset: 2px;
  }

  .spl-hero-search__btn svg {
    width: var(--spl-icon-xl);
    height: var(--spl-icon-xl);
    transition: transform var(--spl-motion-duration-md) var(--spl-motion-ease-standard);
  }

  .spl-hero-search__btn:hover svg {
    transform: scale(1.12);
  }

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

  .spl-hero .hero-cta {
    display: flex;
    gap: var(--spl-space-3);
    flex-wrap: wrap;
    margin-top: var(--spl-space-4);
  }

  /* ---------- stat card ---------- */

  .spl-statcard {
    background: var(--spl-surface);
    border: 1px solid var(--spl-border);
    border-radius: var(--spl-radius-lg);
    box-shadow: var(--spl-shadow-lift);
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    margin-top: calc(-1 * var(--spl-statcard-overlap));
    position: relative;
    z-index: var(--spl-z-statcard);
    overflow: hidden;
  }

  .spl-statcard .stat {
    padding: var(--spl-space-5);
    text-align: center;
    border-right: 1px solid var(--spl-border);
  }

  .spl-statcard .stat:last-child {
    border-right: 0;
  }

  .spl-statcard .stat b {
    display: block;
    font-family: var(--spl-font-heading);
    font-size: var(--spl-text-2xl);
    color: var(--spl-accent-ink);
  }

  .spl-statcard .stat span {
    font-size: var(--spl-text-sm);
    color: var(--spl-text);
  }

  /* ---------- single-item hero ----------
   *
   * Full-bleed dark band: the hero element spans the full width of the page.
   * Content is centred using padding-inline so the inner 2-column grid aligns
   * exactly to the site header container (width: min(87%, 1365px) centred).
   *
   * Mobile/tablet: flat --spl-shell-pad on each side (matches .shell behaviour).
   * Desktop (>=1025px): padding-inline computed to produce the same left/right
   * edges as the archive .shell — `max()` picks the larger of the 87%-derived
   * inset and the max-width-capped inset, matching the shell model exactly:
   *   At 1280 → max(6.5%=83px,  -42px) = 83px  → inner = 1114px (= 87% × 1280)
   *   At 1440 → max(6.5%=94px,  37px)  = 94px  → inner = 1253px (= 87% × 1440)
   *   At 1903 → max(6.5%=124px, 269px) = 269px → inner = 1365px (capped at max)
   *
   * Grid: __shell (text) + __cover are the two direct children.
   * .shell inside __shell neutralised below so it does not double-constrain.
   */

  .spl-single-hero {
    position: relative;
    overflow: hidden;
    color: var(--spl-hero-text);
    background:
      radial-gradient(820px 360px at 86% -10%, var(--spl-hero-glow-accent), transparent 62%),
      radial-gradient(680px 420px at 6% 120%, var(--spl-hero-glow-teal), transparent 60%),
      linear-gradient(145deg, var(--spl-color-navy-950) 0%, var(--spl-color-navy-900) 48%, var(--spl-color-navy-850) 100%);

    /* Full-bleed with centred content: flat shell-pad on mobile/tablet.
       Top padding absorbs the header offset so the breadcrumb/H1 clears the
       overlay header; bottom padding is unchanged. */
    padding-top: calc(var(--spl-header-offset, 0px) + var(--spl-space-6));
    padding-bottom: var(--spl-space-6);
    padding-inline: var(--spl-shell-pad);

    /* 2-column grid: text shell + cover plate */
    display: grid;
    grid-template-columns: minmax(0, 1fr) 380px;
    gap: var(--spl-space-8);
    align-items: center;
  }

  /* Desktop: switch to the header-model inset so hero content aligns with
     the archive .shell and the site header container at every viewport.
     The compact variant (quick template) inherits this — no override needed. */
  @media (width >= 1025px) {
    .spl-single-hero {
      padding-inline: max(
        calc(50% - var(--spl-shell-width) / 2),
        calc((100% - var(--spl-shell-max)) / 2)
      );
    }
  }

  /* Faint dot-grid motif (mirrors archive hero) */
  .spl-single-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    opacity: 0.5;
    pointer-events: none;
    background-image: radial-gradient(
      circle at 1px 1px,
      var(--spl-cover-grid-line) 1px,
      transparent 0
    );
    background-size: 30px 30px;
    mask-image: linear-gradient(120deg, transparent 40%, var(--spl-mask-ink) 100%);
  }

  /* Text column — grid cell; .shell inside neutralised so it does not double-constrain. */
  .spl-single-hero__shell {
    position: relative;
  }

  /* Neutralise the .shell helper class when used inside the hero grid cell.
     The hero's padding-inline already centres the entire grid; re-applying
     the shell's width/margin here would double-constrain the text column. */
  .spl-single-hero .shell {
    max-width: none;
    margin: 0;
    padding: 0;
  }

  /* Badges row */
  .spl-single-hero__badges {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spl-space-2);
    margin: var(--spl-space-3) 0;
  }

  /* Badges on the dark hero use scrim-white background */
  .spl-single-hero .spl-badge {
    background: var(--spl-scrim-light);
    color: var(--spl-color-white);
  }

  /* H1 — white on dark hero band */
  .spl-single-hero__title {
    color: var(--spl-color-white);
    font-size: var(--spl-text-3xl);
    max-width: 20ch;
    margin: var(--spl-space-4) 0;
  }

  /* Meta line (reviewed date, etc.) */
  .spl-single-hero__meta {
    display: flex;
    gap: var(--spl-space-4);
    flex-wrap: wrap;
    color: var(--spl-on-dark-dim);
    font-family: var(--spl-font-mono);
    font-size: var(--spl-text-sm);
    margin-top: var(--spl-space-5);
  }

  .spl-single-hero__meta .reviewed-date {
    display: inline-flex;
    align-items: center;
    gap: var(--spl-space-px-6);
  }

  /* Breadcrumb on the dark hero uses on-dark colours. The link rule targets the
     BEM class (.spl-breadcrumb__link) so it wins regardless of element type. */
  .spl-single-hero .spl-breadcrumb {
    color: var(--spl-on-dark-muted);
  }

  .spl-single-hero .spl-breadcrumb__link {
    color: var(--spl-on-dark-link);
  }

  /* Cover plate column — the visual right-hand art panel */
  .spl-single-hero__cover {
    position: relative;
    border-radius: var(--spl-radius-lg);
    overflow: hidden;
    box-shadow: var(--spl-shadow-lift);
  }

  .spl-single-hero__cover .spl-cover:not(.spl-cover--image) {
    aspect-ratio: 4 / 3;
  }

  /* Compact variant (quick template) — single column; no cover column.
     Top padding absorbs the header offset; bottom padding is unchanged. */
  .spl-single-hero--compact {
    grid-template-columns: 1fr;
    padding-top: calc(var(--spl-header-offset, 0px) + var(--spl-space-5));
    padding-bottom: var(--spl-space-5);
  }

  /* Small inline motif for the compact hero */
  .spl-single-hero__cover-inline {
    max-width: 180px;
    margin-top: var(--spl-space-4);
  }

  .spl-single-hero__cover-inline .spl-cover:not(.spl-cover--image) {
    aspect-ratio: 4 / 3;
    border-radius: var(--spl-radius-md);
    overflow: hidden;
  }

  /*
   * Responsive collapse: at 860px the two-column hero stacks to a single column.
   * Title (__shell) must appear BEFORE the cover across the entire stacked range
   * (both tablet 641px-860px and mobile <= 640px). Explicit orders are applied here
   * so the reading sequence is: shell (badges + title + meta) then cover.
   * Desktop (> 860px) keeps the 2-column side-by-side layout; no order values needed.
   */
  @media (width <= 860px) {
    .spl-single-hero {
      grid-template-columns: 1fr;
    }

    .spl-single-hero__shell {
      order: 1;
    }

    .spl-single-hero__cover {
      order: 2;
    }
  }
}

/*
 * citation.css — .spl-citation block styles
 *
 * Styles the citation widget that renders plain, APA, and BibTeX formats.
 * All tokens defined in tokens.css; no raw colour/spacing/font/radius values.
 */

.spl-root {
  .spl-citation {
    margin-top: var(--spl-space-8);
    padding-top: var(--spl-space-6);
    border-top: var(--spl-border-width-thin) solid var(--spl-color-slate-300);
  }

  .spl-citation h2 {
    color: var(--spl-accent-ink);
    font-family: var(--spl-font-heading);
    font-size: var(--spl-text-lg);
    margin-bottom: var(--spl-space-3);
  }

  .spl-citation__label {
    font-weight: 600;
    margin-top: var(--spl-space-4);
    margin-bottom: var(--spl-space-1);
  }

  .spl-citation__plain,
  .spl-citation__apa {
    margin: 0;
  }

  .spl-citation__bibtex {
    background-color: var(--spl-color-grey-050);
    border-radius: var(--spl-radius-md);
    padding: var(--spl-space-4);
    font-family: var(--spl-font-mono);
    font-size: var(--spl-text-sm);
    overflow-x: auto;
  }
}

/*
 * cover.css — .spl-cover engineered cover plate + .spl-card-ctx container query
 *
 * The cover plate uses a grid overlay (::before) and corner highlight (::after)
 * built from scrim/overlay tokens. The .sheen hover animation is motion-gated
 * in library.css via prefers-reduced-motion.
 */

.spl-root {
  /* ---------- cover plate ---------- */

  .spl-cover {
    position: relative;
    display: block;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    text-decoration: none;
    background: linear-gradient(
      150deg,
      var(--spl-color-navy-900) -12%,
      var(--cover-hue, var(--spl-cover-hue-default)) 62%,
      var(--cover-hue-lt, var(--spl-cover-hue-default-lt)) 128%
    );
  }

  /* grid overlay */
  .spl-cover::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image:
      linear-gradient(var(--spl-cover-grid-line) 1px, transparent 1px),
      linear-gradient(90deg, var(--spl-cover-grid-line) 1px, transparent 1px);
    background-size: 26px 26px;
  }

  /* corner highlight */
  .spl-cover::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(
      420px 170px at 16% -12%,
      var(--spl-cover-highlight),
      transparent 70%
    );
  }

  .spl-cover .bp {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    transition: transform var(--spl-motion-duration-cover) var(--spl-motion-ease-emphasis);
  }

  /* Featured image variant: <img class="bp spl-cover-img"> replaces the SVG motif.
     object-fit and border-radius are the only additions; position/inset/size/display come
     from .bp above. contain shows the whole image inside the cover plate; the brand gradient
     letterboxes any uncovered area so no raw background shows through.

     Hidden-toggle convention (mirrors .spl-client-view in archive.css and .spl-gate-panel
     in gate.css): the Interactivity API drives data-wp-bind--hidden on both the <img> and the
     <svg>. We apply the two-rule :not([hidden]) pattern: an explicit display:none default
     plus a :not([hidden]) restore, so there is one codebase-wide convention and no !important. */
  .spl-cover .spl-cover-img {
    display: none; /* SSR guard: hidden by default; store reveals via data-wp-bind--hidden. */
    object-fit: contain;
    border-radius: inherit;
  }

  /* stylelint-disable-next-line no-descending-specificity -- intentional: display:none above is the SSR guard; this restores block once `hidden` is removed */
  .spl-cover .spl-cover-img:not([hidden]) {
    display: block;
  }

  /* SVG motif: shown by default (SSR); store hides it when featured image is present. */
  .spl-cover svg.bp {
    display: block;
  }

  /* stylelint-disable-next-line no-descending-specificity -- intentional: display:block above is the SSR default; this hides the motif once `hidden` is added */
  .spl-cover svg.bp[hidden] {
    display: none;
  }

  /*
   * Image cover variant (.spl-cover--image)
   *
   * When a resource has a featured image, the caller adds this modifier class to
   * the wrapping <a> (CardRenderer + single templates). The variant removes the
   * gradient background, grid overlay, and forced aspect-ratio so ONLY the image
   * is shown — no orange plate behind it, no letterbox bars. Overlay chips
   * (.c-topic, .c-free, .c-code) remain absolutely positioned inside the container
   * and are unchanged.
   *
   * Aspect guard: aspect-ratio: auto lets the image set the ratio naturally.
   * min-height (30%) prevents impossibly-thin banners; max-height (133%) prevents
   * portrait overflow in the grid — effective clamp between ~16/9 and ~3/4.
   *
   * In the container-query wide-slot (@container >= 30rem) the cover is a fixed
   * 42%-wide rectangle constrained by the sibling body height, so object-fit: cover
   * is restored there (minor crop acceptable; the image is not letterboxed).
   */

  /* Remove gradient background + forced aspect when image is present.
     Grid covers show the image at its natural aspect ratio (no crop intent).
     min-height / max-height percentage bounds are not used here: they resolve
     against an auto-height containing block in normal block flow and have no
     effect. The image's intrinsic dimensions govern the box size directly. */
  .spl-cover--image {
    background: none;

    /* Drop forced 16/10; image's intrinsic ratio takes over. */
    aspect-ratio: auto;
    overflow: hidden;
  }

  /*
   * Remove grid overlay and corner highlight pseudo-elements for the image variant.
   * These gradient layers sit on top of the image and make it look plated/orange.
   */
  .spl-cover--image::before,
  .spl-cover--image::after {
    display: none;
  }

  /*
   * The image flows naturally inside the cover box.
   * Reset the .bp absolute-fill rules; with aspect-ratio: auto the box sizes to the
   * image, so width: 100% / height: auto is the correct block-flow pattern.
   * Must override .spl-cover .spl-cover-img:not([hidden]) to reset absolute positioning
   * from .bp when the image modifier is present.
   */
  /* stylelint-disable-next-line no-descending-specificity -- intentional override: resets .bp absolute positioning on the image modifier variant */
  .spl-cover--image .spl-cover-img {
    position: static;
    inset: auto;
    width: 100%;
    height: auto;
    object-fit: unset;
    display: block;

    /* Prevent the gap below inline-level img. */
    vertical-align: bottom;
  }

  /*
   * Container-query wide slot: cover is 42% wide, height driven by card body.
   * In this constrained rectangle, object-fit: cover fills without letterbox.
   * The minor crop is acceptable here; natural-aspect mode applies at default width.
   */
  @container (min-width: 30rem) {

    /* stylelint-disable-next-line no-descending-specificity -- intentional: restores absolute fill for wide-slot constrained rectangle */
    .spl-card .spl-cover--image .spl-cover-img {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  }

  /* Shared motif <symbol> defs: emitted once per page (motif-defs.php), referenced
     by every cover plate via <use>. Kept out of layout / flow and invisible; only
     the <use> references inside .bp are painted. */
  .spl-motif-defs {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
  }

  .spl-card:hover .spl-cover .bp {
    transform: scale(1.06);
  }

  /* sheen sweep — reveal/motion rules live in library.css @media blocks */
  .spl-cover .sheen {
    position: absolute;
    inset: -40%;
    pointer-events: none;
    background: linear-gradient(
      115deg,
      transparent 42%,
      var(--spl-cover-sheen) 50%,
      transparent 58%
    );
    translate: -120% 0;
  }

  /* ---------- cover chips ---------- */

  .spl-cover .c-topic {
    position: absolute;
    top: 12px;
    left: 12px;
    font-family: var(--spl-font-mono);
    font-size: var(--spl-text-xs);
    font-weight: 600;
    letter-spacing: var(--spl-letter-spacing-tight);
    color: var(--spl-color-white);
    background: var(--spl-scrim-dark);
    padding: var(--spl-space-1) var(--spl-space-px-11);
    border-radius: var(--spl-radius-pill);
    display: inline-flex;
    align-items: center;
    gap: var(--spl-space-px-6);
  }

  .spl-cover .c-topic svg {
    width: var(--spl-icon-xs);
    height: var(--spl-icon-xs);
  }

  .spl-cover .c-code {
    position: absolute;
    bottom: 12px;
    left: 12px;
    font-family: var(--spl-font-mono);
    font-size: var(--spl-text-xs);
    font-weight: 600;
    color: var(--spl-color-white);
    background: var(--spl-scrim-darker);
    border: 1px solid var(--spl-cover-border);
    padding: var(--spl-space-px-3) var(--spl-space-px-9);
    border-radius: var(--spl-radius-sm);

    /* Robustness for long free-form cert strings (e.g. "CompTIA Security+ SY0-701"):
       cap the chip width so it never overflows or breaks the cover layout. */
    max-width: var(--spl-cert-chip-max-width);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .spl-cover .c-free {
    position: absolute;
    top: 11px;
    right: 11px;
    font-family: var(--spl-font-mono);
    font-size: var(--spl-text-2xs);
    font-weight: 800;
    letter-spacing: var(--spl-letter-spacing-label);
    color: var(--spl-color-green-700);
    background: var(--spl-color-green-050);
    padding: var(--spl-space-px-5) var(--spl-space-px-11);
    border-radius: var(--spl-radius-pill);
    box-shadow: var(--spl-shadow-chip);
  }

  /* ---------- container-query reflow for wide slots ---------- */

  .spl-card-ctx {
    container-type: inline-size;
  }

  @container (min-width: 30rem) {
    .spl-card {
      flex-direction: row;
      align-items: stretch;
    }

    .spl-card .spl-cover {
      aspect-ratio: auto;
      width: 42%;
      flex: none;
      align-self: stretch;
      min-height: 158px;
    }

    .spl-card .card-body {
      flex: 1;
    }
  }
}

/*
 * card.css — .spl-card / .spl-grid / .spl-badge / .spl-section
 *
 * The card grid, the card shell, badge colouring, and section headings.
 * Cover plate and container-query reflow live in cover.css.
 */

.spl-root {
  /* ---------- card grid ---------- */

  .spl-grid {
    display: grid;
    gap: var(--spl-space-5);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 290px), 1fr));
  }

  /* Fixed-column grid variant used by the [sme_pdf_grid] shortcode. The base
     .spl-grid stays auto-fill for the archive/hub; this variant pins the column
     count from the --spl-cols custom property the shortcode sets inline. */
  .spl-grid--cols {
    grid-template-columns: repeat(var(--spl-cols, 4), minmax(0, 1fr));
  }

  @media (width <= 60rem) {
    .spl-grid--cols {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }

  @media (width <= 35rem) {
    .spl-grid--cols {
      grid-template-columns: 1fr;
    }
  }

  /* ---------- card shell ---------- */

  .spl-card {
    background: var(--spl-surface);
    border: 1px solid var(--spl-border);
    border-radius: var(--spl-radius-lg);
    box-shadow: var(--spl-shadow-card);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    transition:
      box-shadow var(--spl-motion-duration-xl) var(--spl-motion-ease-standard),
      transform var(--spl-motion-duration-xl) var(--spl-motion-ease-standard),
      border-color var(--spl-motion-duration-xl) var(--spl-motion-ease-standard);
    position: relative;

    /* Skip off-screen render work for long grids (CLS-safe with auto keyword). */
    content-visibility: auto;
    contain-intrinsic-size: auto var(--spl-card-intrinsic-size);
  }

  .spl-card:hover {
    box-shadow: var(--spl-shadow-lift);
    transform: translateY(-3px);
    border-color: var(--spl-color-slate-300);
  }

  .spl-card .card-body {
    padding: var(--spl-space-5);
    display: flex;
    flex-direction: column;
    gap: var(--spl-space-3);
    flex: 1;
  }

  .spl-card h3 {
    font-size: var(--spl-text-lg);
  }

  .spl-card h3 a {
    color: var(--spl-heading-color);
    text-decoration: none;
  }

  .spl-card h3 a:hover {
    color: var(--spl-accent-ink);
  }

  /*
   * Whole-card stretched link (a11y — WCAG 2.4.3 Focus Order / 1.3.1 Info and Relationships).
   *
   * .spl-card__link is the title <a> — the ONE focusable link per card. Its ::after
   * pseudo-element spans the full card (position:absolute; inset:0) making the entire
   * card area activate that link. This gives:
   *   - Exactly one tab stop per card (no nested anchors, no duplicate destinations).
   *   - Accessible name = title text (no extra aria-label needed).
   *   - No non-semantic clickable div: the link is a real <a>.
   *
   * z-index: 1 on the pseudo-element ensures it sits above the card surface but below
   * any future interactive children (buttons, secondary links) that would need z-index: 2.
   * The card's existing position:relative provides the stacking context.
   *
   * Focus ring: :focus-visible from base.css applies to the <a> element itself (z-index
   * stacking does not affect focus styling; the outline draws on the <a> regardless).
   */
  .spl-card h3 a.spl-card__link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: var(--spl-z-card-link);
    border-radius: var(--spl-radius-lg);
  }

  .spl-card .badges {
    display: flex;
    gap: var(--spl-space-px-6);
    flex-wrap: wrap;
  }

  /* ---------- badges ---------- */

  .spl-badge {
    font-size: var(--spl-text-xs);
    font-weight: 700;
    letter-spacing: var(--spl-letter-spacing-tight);
    padding: var(--spl-space-px-3) var(--spl-space-px-9);
    border-radius: var(--spl-radius-pill);
    background: var(--spl-surface-soft);
    color: var(--spl-text-strong);
  }

  /* Level badge variants. CSS class uses the Level enum backing value (lowercase) emitted by
     CardRenderer. The stylelint suppress covers the non-BEM lvl-* suffix pattern. */

  /* stylelint-disable-next-line selector-class-pattern -- Level enum value is lowercase (beginner/intermediate/advanced) */
  .spl-badge.lvl-beginner {
    background: var(--spl-color-green-050);

    /* --spl-color-green-700 (#0d6e47) gives ~5.4:1 on green-050 — passes WCAG AA.
       green-600 (#16855c) was only 4.14:1 on this background. */
    color: var(--spl-color-green-700);
  }

  /* stylelint-disable-next-line selector-class-pattern -- Level enum value is lowercase */
  .spl-badge.lvl-intermediate {
    background: var(--spl-color-blue-050);

    /* blue-600 (#2c5fd6) gives ~5.5:1 on blue-050 (#eaf1ff) — passes WCAG AA. */
    color: var(--spl-color-blue-600);
  }

  /* stylelint-disable-next-line selector-class-pattern -- Level enum value is lowercase */
  .spl-badge.lvl-advanced {
    background: var(--spl-accent-tint);

    /* orange-600 (#e6571a) is only ~3.7:1 on orange-050 — fails WCAG AA.
       --spl-accent-ink (#b8480d) gives ~5.3:1 on orange-050 — passes WCAG AA. */
    color: var(--spl-accent-ink);
  }

  /* ---------- badge variant tints (emitted by single/*.php templates) ----------
   *
   * .spl-badge--cert  — neutral amber surface (certification label)
   * .spl-badge--gate  — accent tint (email-gated resource flag)
   * .spl-badge--free  — green tint (free / open resource flag)
   *
   * All contrast ratios verified WCAG AA:
   *   cert:  --spl-color-amber-600 (#8a5600) on --spl-color-amber-050 (#fbf2df) ~5.5:1
   *   gate:  --spl-accent-ink (#b8480d) on --spl-accent-tint (#fff3ec) ~4.9:1
   *   free:  --spl-color-green-700 (#0d6e47) on --spl-color-green-050 (#e7f6ef) ~5.4:1
   */

  /* stylelint-disable-next-line selector-class-pattern -- BEM modifier with -- is the intended pattern */
  .spl-badge--cert {
    background: var(--spl-color-amber-050);
    color: var(--spl-color-amber-600);
  }

  /* stylelint-disable-next-line selector-class-pattern -- BEM modifier */
  .spl-badge--gate {
    background: var(--spl-accent-tint);
    color: var(--spl-accent-ink);
  }

  /* stylelint-disable-next-line selector-class-pattern -- BEM modifier */
  .spl-badge--free {
    background: var(--spl-color-green-050);
    color: var(--spl-color-green-700);
  }

  /* ---------- card body content ---------- */

  .spl-card p.excerpt {
    margin: 0;
    font-size: var(--spl-text-sm);
    color: var(--spl-text);
    flex: 1;
  }

  .spl-card .excerpt mark {
    background: var(--spl-accent-tint);
    color: var(--spl-accent-ink);
    padding: 0 var(--spl-space-px-2);
    border-radius: var(--spl-radius-xs);
  }

  /* ---------- card meta row ---------- */

  .spl-card .meta {
    display: flex;
    align-items: center;
    gap: var(--spl-space-3);
    flex-wrap: wrap;
    font-family: var(--spl-font-mono);
    font-size: var(--spl-text-xs);
    color: var(--spl-text);
    border-top: 1px solid var(--spl-border);
    padding-top: var(--spl-space-3);
    margin-top: auto;
  }

  .spl-card .meta .m {
    display: inline-flex;
    align-items: center;
    gap: var(--spl-space-px-5);
  }

  .spl-card .meta .m svg {
    width: var(--spl-icon-xs);
    height: var(--spl-icon-xs);
  }

  /* ---------- section headings ---------- */

  .spl-section {
    padding: var(--spl-space-6) 0;
  }

  .spl-section-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--spl-space-3);
    margin-bottom: var(--spl-space-5);
    flex-wrap: wrap;
  }

  .spl-section-head h2 {
    font-size: var(--spl-text-2xl);
  }

  .spl-section-head .sub {
    color: var(--spl-text);
    font-size: var(--spl-text-sm);
    margin-top: var(--spl-space-1);
  }

  .spl-section-head a.more {
    color: var(--spl-accent-ink);
    font-weight: 700;
    font-size: var(--spl-text-sm);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: var(--spl-space-px-5);
    white-space: nowrap;
  }

  /* stylelint-disable-next-line no-descending-specificity -- unrelated selector chain; .meta .m svg and a.more svg are in different subtrees */
  .spl-section-head a.more svg {
    width: var(--spl-icon-md);
    height: var(--spl-icon-md);
    transition: transform var(--spl-motion-duration-md) var(--spl-motion-ease-standard);
  }

  .spl-section-head a.more:hover svg {
    transform: translateX(var(--spl-space-px-3));
  }
}

/*
 * tracks.css — .spl-tracks + .spl-track (browse-by-topic colourful row cards)
 */

.spl-root {
  .spl-tracks {
    display: grid;
    gap: var(--spl-space-4);
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  }

  .spl-track {
    display: flex;
    align-items: center;
    gap: var(--spl-space-4);
    background: var(--spl-surface);
    border: 1px solid var(--spl-border);
    border-radius: var(--spl-radius-md);
    padding: var(--spl-space-4);
    text-decoration: none;
    transition:
      box-shadow var(--spl-motion-duration-md) var(--spl-motion-ease-standard),
      border-color var(--spl-motion-duration-md) var(--spl-motion-ease-standard),
      transform var(--spl-motion-duration-md) var(--spl-motion-ease-standard);
  }

  .spl-track:hover {
    box-shadow: var(--spl-shadow-lift);
    transform: translateY(-2px);
  }

  .spl-track .t-ico {
    flex: none;
    width: var(--spl-icon-feature);
    height: var(--spl-icon-feature);
    border-radius: var(--spl-radius-md);
    display: grid;
    place-items: center;
    color: var(--spl-color-white);
    background: linear-gradient(140deg, var(--cover-hue), var(--cover-hue-lt));
  }

  .spl-track .t-ico svg {
    width: var(--spl-icon-2xl);
    height: var(--spl-icon-2xl);
  }

  .spl-track .t-body {
    flex: 1;
    min-width: 0;
  }

  .spl-track .t-body b {
    display: block;
    color: var(--spl-heading-color);
    font-family: var(--spl-font-heading);
    font-size: var(--spl-text-base);
  }

  .spl-track .t-body span {
    font-size: var(--spl-text-xs);
    color: var(--spl-text);
  }

  .spl-track .t-go {
    flex: none;
    color: var(--spl-accent-ink);
  }
}

/*
 * filters.css — .spl-toolbar + .spl-select
 *
 * Slim filter/sort toolbar with active chip and native select dropdown.
 */

.spl-root {
  .spl-toolbar {
    display: flex;
    align-items: center;
    gap: var(--spl-space-3);
    flex-wrap: wrap;
    padding: var(--spl-space-5) 0 var(--spl-space-3);
    border-bottom: 1px solid var(--spl-border);
  }

  .spl-toolbar .count {
    color: var(--spl-text);
    font-size: var(--spl-text-sm);
  }

  .spl-toolbar .count b {
    color: var(--spl-heading-color);
  }

  .spl-toolbar .spacer {
    flex: 1;
  }

  .spl-toolbar .active-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--spl-space-px-6);
    background: var(--spl-accent);
    color: var(--spl-color-navy-900);
    border: 0;
    border-radius: var(--spl-radius-pill);
    padding: var(--spl-space-px-5) var(--spl-space-3);
    font-size: var(--spl-text-sm);
    font-weight: 600;
    text-decoration: none;
    transition: background var(--spl-motion-duration-sm) var(--spl-motion-ease-standard);
  }

  .spl-toolbar .active-chip:hover {
    background: var(--spl-accent-hover);
  }

  /* "Clear filters" ghost pill — sibling of active chips, lives before .spacer */
  .spl-toolbar__clear {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--spl-control-border);
    border-radius: var(--spl-radius-pill);
    padding: var(--spl-space-px-5) var(--spl-space-3);
    font-size: var(--spl-text-sm);
    color: var(--spl-text);
    text-decoration: none;
    transition:
      border-color var(--spl-motion-duration-sm) var(--spl-motion-ease-standard),
      color var(--spl-motion-duration-sm) var(--spl-motion-ease-standard);
    white-space: nowrap;
  }

  .spl-toolbar__clear:hover {
    border-color: var(--spl-focus-ring);
    color: var(--spl-accent-ink);
  }

  .spl-toolbar__clear:focus-visible {
    outline: 2px solid var(--spl-focus-ring);
    outline-offset: 1px;
    border-color: var(--spl-focus-ring);
  }

  .spl-toolbar__submit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--spl-tap-target-min);
    padding: var(--spl-space-px-5) var(--spl-space-3);
    background: var(--spl-accent);
    color: var(--spl-color-navy-900);
    border: 1px solid transparent;
    border-radius: var(--spl-radius-pill);
    font-family: var(--spl-font-body);
    font-size: var(--spl-text-sm);
    font-weight: 700;
    line-height: var(--spl-line-base);
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--spl-motion-duration-sm) var(--spl-motion-ease-standard);
  }

  .spl-toolbar__submit:hover {
    background: var(--spl-accent-hover);
  }

  .spl-toolbar__submit:focus-visible {
    outline: 2px solid var(--spl-focus-ring);
    outline-offset: 2px;
  }

  /*
   * Task 5 (Plan 3.4) reactive toolbar: data-wp-bind--hidden adds the HTML
   * `hidden` attribute. The UA stylesheet's `[hidden] { display:none }` can be
   * overridden by element-level display rules, so we explicitly enforce hidden
   * on these interactive toolbar elements (same pattern as .spl-ssr-view
   * and .spl-client-view in archive.css).
   */
  .spl-toolbar__clear[hidden] {
    display: none;
  }

  /*
   * SSR chip wrapper: hidden once state.interacted is true; replaced by the
   * client data-wp-each chip template.
   */
  .spl-ssr-chips[hidden] {
    display: none;
  }

  .spl-select {
    appearance: none;
    background: var(--spl-surface);
    border: 1px solid var(--spl-control-border);
    border-radius: var(--spl-radius-md);
    padding: var(--spl-space-2) var(--spl-space-px-30) var(--spl-space-2) var(--spl-space-px-14);
    font-size: var(--spl-text-sm);
    color: var(--spl-text-strong);
    font-weight: 600;
    transition: border-color var(--spl-motion-duration-sm) var(--spl-motion-ease-standard);
    /* stylelint-disable-next-line scale-unlimited/declaration-strict-value -- CSS custom properties cannot be referenced inside data: URI strings */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='3'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--spl-space-px-11) center;
  }

  .spl-select:hover {
    border-color: var(--spl-focus-ring);
  }

  .spl-select:focus {
    border-color: var(--spl-focus-ring);
    outline: 2px solid var(--spl-focus-ring);
    outline-offset: 1px;
  }

    /*
   * Mobile tap targets. WCAG 2.5.5 minimum 44px touch area.
   * Applies to the native <select> dropdowns and the Clear-filters pill
   * at mobile widths where finger precision is lower.
   */
  @media (width <= 640px) {
    .spl-select {
      min-height: var(--spl-tap-min);
    }

    .spl-toolbar__clear {
      min-height: var(--spl-tap-min);
    }
  }

  /*
   * ---------------------------------------------------------------
   * Mobile filter bottom-sheet (Task 8, Plan 3.5)
   * Only active at width <= 640px. Desktop is completely unchanged.
   * ---------------------------------------------------------------
   *
   * Architecture: one <form> inside .spl-sheet. At desktop the sheet
   * is static/visible as the normal inline toolbar. At mobile the sheet
   * becomes a fixed overlay that slides up from the bottom when open.
   *
   * .spl-sheet                — the outer wrapper + scrim host (desktop: static)
   * .spl-sheet__scrim         — dark overlay behind the panel (mobile only)
   * .spl-sheet__panel         — the sliding panel (mobile only)
   * .spl-sheet__header        — heading row with title + close button (mobile only)
   * .spl-sheet__footer        — Apply + Cancel action row (mobile only)
   * .spl-toolbar__sheet-trigger — "Filters" button (mobile only, hidden at desktop)
   * .spl-toolbar__badge       — active-filter count bubble on the trigger
   */

  /* ---- Desktop: sheet is invisible / static so the inline form renders as before ---- */

  /* Trigger hidden on desktop. Must OUT-SPECIFY base.css's `html.spl-js .spl-requires-js
     { display: revert }` reveal (0,2,1). This whole file is nested in `.spl-root`, so a bare
     nested selector resolves via a descendant combinator: `html.spl-js .foo` would become
     `:is(.spl-root) html.spl-js .foo`, which is UNSATISFIABLE (html can never be a descendant
     of .spl-root). We therefore use the `&` nesting selector: `html.spl-js & .foo` resolves to
     `html.spl-js :is(.spl-root) .foo` (0,3,1) which both MATCHES (the trigger is inside
     .spl-root, which is inside html.spl-js) and beats the base reveal. No-JS keeps the trigger
     hidden via base `.spl-requires-js { display:none }`. Mobile re-reveal below (also 0,3,1,
     later in source + in a media query) wins at <=640px. Verified in-browser at 1440/390. */
  html.spl-js & .spl-toolbar__sheet-trigger {
    display: none;
  }

  /* On desktop, sheet container is transparent pass-through (no overlay layout). */
  .spl-sheet {
    display: contents;
  }

  /* Sheet elements that should only appear on mobile are hidden on desktop. */
  .spl-sheet__scrim,
  .spl-sheet__header,
  .spl-sheet__footer {
    display: none;
  }

  /* ---- Mobile: bottom-sheet overlay ---- */
  @media (width <= 640px) {
    /* The "Filters" trigger button -- shown only on mobile when JS is active.
       The .spl-requires-js base rule hides it by default; html.spl-js in base.css
       reveals it generally; this rule re-constrains to mobile only. */
    html.spl-js & .spl-toolbar__sheet-trigger {
      display: inline-flex;
      align-items: center;
      gap: var(--spl-space-2);
      min-height: var(--spl-tap-min);
      padding: 0 var(--spl-space-4);
      background: var(--spl-color-navy-900);
      color: var(--spl-color-white);
      border: none;
      border-radius: var(--spl-radius-md);
      font-family: var(--spl-font-body);
      font-size: var(--spl-text-sm);
      font-weight: 600;
      cursor: pointer;
      transition: background var(--spl-motion-duration-sm) var(--spl-motion-ease-standard);
    }

    html.spl-js & .spl-toolbar__sheet-trigger:hover {
      background: var(--spl-color-navy-700);
    }

    html.spl-js & .spl-toolbar__sheet-trigger:focus-visible {
      outline: 2px solid var(--spl-focus-ring);
      outline-offset: 2px;
    }

    /* Active-filter count badge on the trigger. */
    .spl-toolbar__badge {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-width: var(--spl-space-5);
      height: var(--spl-space-5);
      padding: 0 var(--spl-space-2);
      background: var(--spl-accent);
      color: var(--spl-color-navy-900);
      border-radius: var(--spl-radius-pill);
      font-size: var(--spl-text-xs);
      font-weight: 700;
      line-height: 1;
    }

    /* Hide all existing toolbar children except the trigger at mobile when JS is active.
       The sheet panel surfaces the form controls inside .spl-sheet__panel.
       Without JS (no html.spl-js class) these controls remain visible inline as a
       native GET form — the only usable filter UI on JS-off mobile. */
    html.spl-js .spl-toolbar .count,
    html.spl-js .spl-toolbar .spacer,
    html.spl-js .spl-toolbar .spl-ssr-chips,
    html.spl-js .spl-toolbar template,
    html.spl-js .spl-toolbar .spl-toolbar__clear,
    html.spl-js .spl-toolbar .spl-toolbar__submit,
    html.spl-js .spl-toolbar .spl-select {
      display: none;
    }

    /* --- Sheet outer wrapper: fixed viewport layer when open --- */
    .spl-sheet {
      display: block;
      position: fixed;
      inset: 0;
      z-index: var(--spl-z-sheet);
      pointer-events: none;
      visibility: hidden;
    }

    /* Sheet is visible when the spl-sheet--open modifier class is present.
       Added by the Interactivity store (data-wp-class--spl-sheet--open). */
    .spl-sheet.spl-sheet--open {
      visibility: visible;
      pointer-events: auto;
    }

    /* --- Scrim (backdrop) --- */
    /*
     * The scrim covers only the area ABOVE the panel (top 20dvh of the viewport).
     * The panel occupies up to max-height: 80dvh from the bottom, so the scrim
     * must stop at the panel's top edge (bottom: 80dvh from parent = top 20dvh).
     *
     * Why not inset: 0? If the scrim spans the full viewport, its centre lands
     * inside the panel region, which sits on top (later in DOM order). Playwright's
     * click() action detects the panel as the intercepting element and times out.
     * Using bottom: 80dvh keeps the scrim's click-target entirely above the panel,
     * matching the visual intent (dark backdrop behind the panel header area).
     */
    .spl-sheet__scrim {
      display: block;
      position: absolute;

      /* inset: top right bottom left — bottom is capped at 80dvh so the scrim sits
         above the slide-up panel and Playwright can click it without being intercepted
         by the panel element at the bottom 80 % of the viewport. */
      inset: 0 0 80dvh;
      background: var(--spl-sheet-scrim);
      opacity: 0;
      transition: opacity var(--spl-motion-duration-xl) var(--spl-motion-ease-standard);
    }

    .spl-sheet.spl-sheet--open .spl-sheet__scrim {
      opacity: 1;
    }

    /* --- Panel: the sliding card --- */
    .spl-sheet__panel {
      display: flex;
      flex-direction: column;
      position: absolute;
      bottom: 0;
      left: 0;
      right: 0;
      max-height: 80dvh; /* dvh for iOS Safari notch-safe height */
      background: var(--spl-surface);
      border-radius: var(--spl-radius-lg) var(--spl-radius-lg) 0 0;
      box-shadow: var(--spl-shadow-lift);

      /* Slide-in animation: panel starts below the viewport */
      transform: translateY(100%);
      transition: transform var(--spl-motion-duration-sheet) var(--spl-motion-ease-standard);
      padding-bottom: env(safe-area-inset-bottom, 0);
      overflow: hidden;
    }

    .spl-sheet.spl-sheet--open .spl-sheet__panel {
      transform: translateY(0);
    }

    /*
     * prefers-reduced-motion: remove the slide-up animation.
     * The sheet still opens/closes (visibility toggle) but without motion.
     * WCAG 2.3.3 Animation from Interactions (AAA) / best practice.
     *
     * Note: !important is used because this rule must win over the
     * .spl-sheet.spl-sheet--open specificity above to remove the transform
     * on ALL motion-sensitive state transitions.
     */
    /* biome-ignore-start -- the !important inside prefers-reduced-motion is intentional for WCAG 2.3.3 */
    @media (prefers-reduced-motion: reduce) {
      .spl-sheet__panel,
      .spl-sheet__scrim {
        transition: none !important;
      }
    }

    /* biome-ignore-end */

    /* --- Sheet header: title + close button --- */
    .spl-sheet__header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: var(--spl-space-4) var(--spl-space-5);
      border-bottom: 1px solid var(--spl-border);
      flex-shrink: 0;
    }

    .spl-sheet__title {
      font-family: var(--spl-font-heading);
      font-size: var(--spl-text-lg);
      font-weight: 700;
      color: var(--spl-heading-color);
      margin: 0;
    }

    .spl-sheet__close {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-width: var(--spl-tap-min);
      min-height: var(--spl-tap-min);
      background: none;
      border: none;
      font-size: var(--spl-text-xl);
      color: var(--spl-text);
      cursor: pointer;
      border-radius: var(--spl-radius-md);
      line-height: 1;
    }

    .spl-sheet__close:hover {
      background: var(--spl-surface-soft);
    }

    .spl-sheet__close:focus-visible {
      outline: 2px solid var(--spl-focus-ring);
      outline-offset: 2px;
    }

    /* --- Form inside the sheet: vertical stack of controls --- */
    .spl-sheet .spl-toolbar__form {
      display: flex;
      flex-direction: column;
      gap: var(--spl-space-3);
      padding: var(--spl-space-5);
      overflow-y: auto;
      flex: 1;

      /* Minimum height so short forms don't collapse */
      min-height: 0;
    }

    /* On mobile, show the controls that are hidden at the outer toolbar level.
       The html.spl-js prefix mirrors the hide rule's specificity so no-descending-specificity
       is satisfied (same specificity level, defined later in the source = wins). */
    html.spl-js .spl-sheet .spl-toolbar__form .spl-select {
      display: flex;
      width: 100%;
      min-height: var(--spl-tap-min);
    }

    html.spl-js .spl-sheet .spl-toolbar__form .count,
    html.spl-js .spl-sheet .spl-toolbar__form .spl-ssr-chips,
    html.spl-js .spl-sheet .spl-toolbar__form .spl-toolbar__clear {
      display: revert;
    }

    /* Labels for the selects: visible in sheet, screen-reader-text by default. */
    .spl-sheet .screen-reader-text {
      position: static;
      clip: auto;
      clip-path: none;
      width: auto;
      height: auto;
      overflow: visible;
      white-space: normal;
      font-size: var(--spl-text-sm);
      font-weight: 600;
      color: var(--spl-heading-color);
      margin-bottom: var(--spl-space-1);
      display: block;
    }

    /* --- Sheet footer: Apply + Cancel --- */
    .spl-sheet__footer {
      display: flex;
      gap: var(--spl-space-3);
      padding: var(--spl-space-4) var(--spl-space-5);
      border-top: 1px solid var(--spl-border);
      flex-shrink: 0;
    }

    .spl-sheet__apply {
      flex: 1;
      min-height: var(--spl-tap-min);
      font-family: var(--spl-font-body);
      font-size: var(--spl-text-base);
    }

    .spl-sheet__cancel {
      flex: 1;
      min-height: var(--spl-tap-min);
      background: none;
      border: 1px solid var(--spl-control-border);
      border-radius: var(--spl-radius-md);
      font-family: var(--spl-font-body);
      font-size: var(--spl-text-base);
      color: var(--spl-text);
      cursor: pointer;
    }

    .spl-sheet__cancel:hover {
      border-color: var(--spl-focus-ring);
      color: var(--spl-accent-ink);
    }

    .spl-sheet__cancel:focus-visible {
      outline: 2px solid var(--spl-focus-ring);
      outline-offset: 2px;
    }
  }
}

/*
 * JS-off override — top-level (outside .spl-root nesting) so the html.spl-js
 * selector is valid (html element cannot be a descendant of .spl-root).
 *
 * Without JavaScript (no html.spl-js class on <html>), the mobile bottom-sheet
 * must NOT apply the fixed-position overlay or visibility:hidden. The sheet is
 * a static block container; the <select> controls inside it are visible inline
 * as a native GET form. This is the no-JS fallback required by WCAG and the
 * progressive-enhancement contract.
 *
 * Specificity: html:not(.spl-js) .spl-root .spl-sheet  = (0,3,1)
 *              .spl-root .spl-sheet (mobile rule above) = (0,2,0)
 * The override wins, restoring a static layout for the JS-off path.
 */
@media (width <= 640px) {
  html:not(.spl-js) .spl-root .spl-sheet {
    position: static;
    inset: unset;
    /* stylelint-disable-next-line scale-unlimited/declaration-strict-value -- z-index unset is correct for JS-off; the value is a layout constant set in the JS-on rule above */
    z-index: unset;
    pointer-events: auto;
    visibility: visible;
  }
}

/*
 * suggest.css — .spl-suggest-panel (search autocomplete ARIA combobox)
 *
 * The archive/hub hero emits .spl-suggest-panel + .spl-suggest-option, populated
 * by the data-wp-each store binding. The panel is toggled by the store via the
 * .is-visible class (data-wp-class--is-visible="state.suggestOpen") rather than
 * :focus-within, so suggestions stay open while the pointer moves to an option.
 */

.spl-root {
  /* ---------- Interactivity store panel ---------- */
  .spl-suggest-panel {
    position: absolute;
    top: calc(100% + 10px);
    left: 0;
    right: 0;
    list-style: none;
    margin: 0;
    background: var(--spl-surface);
    border: 1px solid var(--spl-border);
    border-radius: var(--spl-radius-lg);
    box-shadow: var(--spl-shadow-lift);
    padding: var(--spl-space-3);
    opacity: 0;
    visibility: hidden;
    translate: 0 -6px;
    transition:
      opacity var(--spl-motion-duration-sm) var(--spl-motion-ease-standard),
      translate var(--spl-motion-duration-sm) var(--spl-motion-ease-standard),
      visibility var(--spl-motion-duration-sm) var(--spl-motion-ease-standard);
    z-index: var(--spl-z-suggest);
    text-align: left;
  }

  .spl-suggest-panel.is-visible {
    opacity: 1;
    visibility: visible;
    translate: 0 0;
  }

  .spl-suggest-option {
    display: flex;
    align-items: center;
    gap: var(--spl-space-px-10);
    padding: var(--spl-space-px-9) var(--spl-space-px-10);
    border-radius: var(--spl-radius-sm);
    color: var(--spl-text-strong);
    font-size: var(--spl-text-sm);
    cursor: pointer;
  }

  .spl-suggest-option:hover,
  .spl-suggest-option.is-active,
  .spl-suggest-option[aria-selected="true"] {
    background: var(--spl-surface-soft);
  }

  /* Client empty-state wrapper inside the unified results view. */
  .spl-client-empty {
    width: 100%;
  }
}

/*
 * buttons.css — .spl-btn / .spl-btn-primary / .spl-btn-ghost
 *
 * A11y note: navy-on-orange contrast ratio 4.68:1 (passes AA).
 * White-on-orange is 2.96:1 and MUST NEVER be used — the test guards this.
 */

.spl-root {
  .spl-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spl-space-2);
    border: 1px solid transparent;
    border-radius: var(--spl-radius-md);
    padding: var(--spl-space-px-11) var(--spl-space-px-18);
    font-weight: 700;
    font-size: var(--spl-text-sm);
    text-decoration: none;
  }

  /* The display:inline-flex above overrides the UA `[hidden]{display:none}` (author beats
     UA origin), so a .spl-btn toggled with the Interactivity API `data-wp-bind--hidden`
     (the gate reveal button, the returning-visitor unlock trigger) would stay visible when
     hidden. Restore hiding: (0,3,0 under .spl-root) beats the base .spl-btn rule. */
  .spl-btn[hidden] {
    display: none;
  }

  .spl-btn svg {
    width: var(--spl-icon-lg);
    height: var(--spl-icon-lg);
  }

  /* navy text on orange = 4.68:1 (passes AA); white-on-orange fails at 2.96:1.
     color here beats unlayered WordPress theme a:link rules via .spl-root nesting
     specificity (0,2,0) vs theme element rules (0,0,1). */
  .spl-btn-primary {
    background: var(--spl-accent);
    color: var(--spl-color-navy-900);
  }

  .spl-btn-primary:hover {
    background: var(--spl-accent-hover);
  }

  .spl-btn-ghost {
    background: transparent;
    color: var(--spl-text-strong);
    border-color: var(--spl-border);
  }

  .spl-btn-ghost.on-dark {
    color: var(--spl-color-white);
    border-color: var(--spl-on-dark-border-btn);
  }

  .spl-btn-block {
    width: 100%;
  }

  .spl-btn-lg {
    padding: var(--spl-space-px-14) var(--spl-space-px-22);
    font-size: var(--spl-text-base);
  }
}

/*
 * pagination.css — .spl-pagination + .spl-breadcrumb
 */

.spl-root {
  /* ---------- pagination ---------- */

  .spl-pagination {
    display: flex;
    justify-content: center;
    gap: var(--spl-space-2);
    margin-top: var(--spl-space-6);
  }

  .spl-pagination a,
  .spl-pagination span {
    padding: var(--spl-space-2) var(--spl-pagination-pad-inline);
    border-radius: var(--spl-radius-md);
    border: 1px solid var(--spl-border);
    text-decoration: none;
    color: var(--spl-text-strong);
    font-weight: 600;
    font-size: var(--spl-text-sm);
    background: var(--spl-surface);
  }

  .spl-pagination .current {
    background: var(--spl-accent);
    color: var(--spl-color-navy-900);
    border-color: var(--spl-focus-ring);
  }

  /* ---------- breadcrumb ---------- */

  .spl-breadcrumb {
    font-size: var(--spl-text-sm);
    color: var(--spl-text);
  }

  .spl-breadcrumb.light {
    color: var(--spl-on-dark-dim);
  }

  .spl-breadcrumb__separator {
    margin: 0 var(--spl-space-2);
    opacity: 0.6;
  }

  /* ---------- breadcrumb BEM inner elements ----------
   * Emitted by templates/parts/breadcrumb.php.
   * The outer .spl-breadcrumb nav wrapper is styled above.
   * On-dark variant (inside .spl-single-hero) gets link colour from
   * .spl-single-hero .spl-breadcrumb__link in hero.css (--spl-on-dark-link).
   */

  .spl-breadcrumb__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0;
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .spl-breadcrumb__item {
    display: inline-flex;
    align-items: center;
  }

  .spl-breadcrumb__link {
    color: var(--spl-text-strong);
    text-decoration: none;
    font-size: var(--spl-text-sm);
  }

  .spl-breadcrumb__link:hover {
    color: var(--spl-accent-ink);
    text-decoration: underline;
  }

  .spl-breadcrumb__current {
    color: var(--spl-text);
    font-size: var(--spl-text-sm);
  }
}

/*
 * bands.css — .spl-band (dark trust band) + .spl-subscribe (light subscribe band)
 *
 * The .spl-band background uses radial gradients.
 * Glow, colour, font-size, and border-radius values use tokens.
 */

.spl-root {
  /* ---------- dark trust band ---------- */

  .spl-band {
    background:
      radial-gradient(680px 320px at 88% -30%, var(--spl-band-glow-accent), transparent 60%),
      radial-gradient(520px 300px at 4% 130%, var(--spl-band-glow-teal), transparent 60%),
      var(--spl-color-navy-900);
    color: var(--spl-on-dark-link);
    padding: var(--spl-space-8) 0;
  }

  .spl-band h2 {
    color: var(--spl-color-white);
    font-size: var(--spl-text-2xl);
    max-width: 22ch;
  }

  .spl-band .sub {
    color: var(--spl-on-dark-dim);
    max-width: 58ch;
    margin: var(--spl-space-3) 0 0;
  }

  .spl-band-grid {
    display: grid;
    gap: var(--spl-space-6);
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    margin-top: var(--spl-space-6);
  }

  /* Non-text decorative icon container — white icon on orange gradient is intentionally exempt
   * from WCAG 1.4.3 text-contrast (the SVG is aria-hidden and carries no meaning). */
  .spl-band-grid .feat .fico {
    width: var(--spl-icon-feature);
    height: var(--spl-icon-feature);
    border-radius: var(--spl-radius-md);
    display: grid;
    place-items: center;
    margin-bottom: var(--spl-space-3);
    color: var(--spl-color-white);
    background: linear-gradient(140deg, var(--spl-accent), var(--spl-accent-warm));
  }

  .spl-band-grid .feat .fico svg {
    width: var(--spl-icon-2xl);
    height: var(--spl-icon-2xl);
  }

  .spl-band-grid .feat b {
    color: var(--spl-color-white);
    font-family: var(--spl-font-heading);
    display: block;
    margin-bottom: var(--spl-space-1);
  }

  .spl-band-grid .feat span {
    font-size: var(--spl-text-sm);
  }

  /* ---------- subscribe band ---------- */

  .spl-subscribe {
    background: var(--spl-surface-soft);
    padding: var(--spl-space-8) 0;
  }

  .spl-subscribe .inner {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--spl-space-8);
    align-items: center;
  }

  .spl-subscribe h2 {
    font-size: var(--spl-text-2xl);
  }

  .spl-subscribe p {
    margin-top: var(--spl-space-3);
  }

  .spl-subscribe form {
    display: flex;
    gap: var(--spl-space-3);
    flex-wrap: wrap;
  }

  .spl-subscribe input {
    flex: 1 1 220px;
    padding: var(--spl-space-px-13) var(--spl-space-4);
    border: 1px solid var(--spl-control-border);
    border-radius: var(--spl-radius-md);
    font-size: var(--spl-text-base);
  }

  .spl-subscribe small {
    display: block;
    margin-top: var(--spl-space-3);
    color: var(--spl-text);
    font-size: var(--spl-text-xs);
  }

  @media (width <= 760px) {
    .spl-subscribe .inner {
      grid-template-columns: 1fr;
    }
  }
}

/*
 * single.css — single resource detail page body layout and content components.
 *
 * Targets the BEM class vocabulary emitted by templates/single/{featured,quick,lead}.php.
 * The layout wrapper (.spl-single-body) is the 2-column grid replacing the earlier
 * .spl-single / .spl-single-grid pattern from the mockup.
 *
 * Inner rules for .spl-answer, .spl-prose, .spl-author, .spl-related are kept
 * because those classes are also referenced by other surfaces (parts templates).
 *
 * All values come from --spl-* tokens. No raw colour, font, spacing, radius,
 * box-shadow, z-index, or font-size literals (per CLAUDE.md and Stylelint gate).
 */

.spl-root {
  /* -----------------------------------------------------------------------
   * Single body — 2-column layout grid
   *
   * The body container carries the .shell class in every single variant
   * (featured/lead use .spl-single-body.shell; quick uses
   * .spl-single-content--quick.shell), so it gets the header-model width
   * (min(87%, 1365px) centred) from base.css's .spl-root .shell rule.
   * This block adds the 2-column grid on top.
   * ----------------------------------------------------------------------- */

  .spl-single-body {
    padding: var(--spl-space-8) 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--spl-space-8);
    align-items: start;
  }

  /* Main content column — min-width:0 prevents overflow out of grid cell */
  .spl-single-content {
    min-width: 0;
  }

  /* Quick variant: single-column narrow layout; no sidebar */
  .spl-single-content--quick {
    max-width: 760px;
  }

  /* Inline download / gate wrapper for the quick (no-sidebar) variant */
  .spl-single-quick-action {
    margin: var(--spl-space-6) 0;
  }

  /* -----------------------------------------------------------------------
   * Prose body — .spl-single-prose wraps the filtered post_content
   * ----------------------------------------------------------------------- */

  .spl-single-prose {
    color: var(--spl-text-strong);
    margin-top: var(--spl-space-6);
  }

  .spl-single-prose h2 {
    font-size: var(--spl-text-xl);
    margin: var(--spl-space-6) 0 var(--spl-space-3);
  }

  .spl-single-prose p {
    margin: 0 0 var(--spl-space-4);
  }

  .spl-single-prose ul {
    margin: 0 0 var(--spl-space-4);
    padding-left: var(--spl-prose-list-indent);
  }

  .spl-single-prose li {
    margin-bottom: var(--spl-space-px-6);
  }

  /* -----------------------------------------------------------------------
   * FAQ section — .spl-single-faq wraps the faq part template
   * The inner .spl-faq component is styled in faq.css; only the section
   * wrapper and its heading need rules here.
   * ----------------------------------------------------------------------- */

  .spl-single-faq {
    margin-top: var(--spl-space-6);
  }

  .spl-single-faq__heading {
    font-size: var(--spl-text-xl);
    color: var(--spl-heading-color);
    margin-bottom: var(--spl-space-4);
  }

  /* -----------------------------------------------------------------------
   * Author section — .spl-single-author is a margin wrapper; the inner
   * .spl-author card is styled below (kept for other surfaces too).
   * ----------------------------------------------------------------------- */

  .spl-single-author {
    margin-top: var(--spl-space-6);
  }

  /* -----------------------------------------------------------------------
   * Sidebar — .spl-single-sidebar is the sticky right-column aside
   * ----------------------------------------------------------------------- */

  .spl-single-sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--spl-space-5);
    position: sticky;
    top: 120px;
  }

  /* Gate variant: always shows gate form (lead template) */
  .spl-single-sidebar--gate {
    /* Inherits .spl-single-sidebar layout; no additional structural overrides needed */
  }

  /* Sidebar panel heading (h2 inside the aside, e.g. "Get the PDF") */
  .spl-single-sidebar__heading {
    font-size: var(--spl-text-lg);
    color: var(--spl-heading-color);
    margin-bottom: var(--spl-space-3);
  }

  /* -----------------------------------------------------------------------
   * Responsive collapse — stack to single column below 900px
   * ----------------------------------------------------------------------- */

  @media (width <= 900px) {
    .spl-single-body {
      grid-template-columns: 1fr;
    }
  }

  /* -----------------------------------------------------------------------
   * AEO answer box — kept; referenced by parts/answer.php on all variants
   * ----------------------------------------------------------------------- */

  .spl-answer {
    background: var(--spl-accent-tint);
    border-left: 4px solid var(--spl-accent);
    border-radius: var(--spl-radius-md);
    padding: var(--spl-space-5);
    margin: 0 0 var(--spl-space-6);
    color: var(--spl-text-strong);
    font-size: var(--spl-text-lg);
    line-height: 1.55;
  }

  .spl-answer .label {
    display: inline-flex;
    align-items: center;
    gap: var(--spl-space-px-7);
    font-size: var(--spl-text-xs);
    text-transform: uppercase;
    letter-spacing: var(--spl-letter-spacing-label);

    /* --spl-accent-ink (#b8480d) gives ~4.92:1 on --spl-accent-tint (#fff3ec) — passes WCAG AA.
       --spl-accent-strong (#e6571a) was only 3.35:1 on that background. */
    color: var(--spl-accent-ink);
    font-weight: 700;
    margin-bottom: var(--spl-space-2);
  }

  .spl-answer .label svg {
    width: var(--spl-icon-sm);
    height: var(--spl-icon-sm);
  }

  /* -----------------------------------------------------------------------
   * Prose body (legacy selector) — kept for any surface that emits .spl-prose
   * directly. Prefer .spl-single-prose for new single-page usage.
   * ----------------------------------------------------------------------- */

  .spl-prose {
    color: var(--spl-text-strong);
  }

  .spl-prose h2 {
    font-size: var(--spl-text-xl);
    margin: var(--spl-space-6) 0 var(--spl-space-3);
  }

  .spl-prose p {
    margin: 0 0 var(--spl-space-4);
  }

  .spl-prose ul {
    margin: 0 0 var(--spl-space-4);
    padding-left: var(--spl-prose-list-indent);
  }

  .spl-prose li {
    margin-bottom: var(--spl-space-px-6);
  }

  /* -----------------------------------------------------------------------
   * Author bio card — inner .spl-author (used by parts/author.php on all
   * template variants including quick). The outer .spl-single-author wrapper
   * margin is handled above.
   *
   * Design: authority-stripe card. A 3px orange left border acts as a citation
   * mark — "this was reviewed by a credentialled expert." The surface uses a
   * barely-there navy tint + hairline border so it reads as a panel without
   * shouting. The avatar ring (slate outline, white gap) mirrors the look of
   * professional certification badges.
   *
   * Tokens used:
   *   surface   --spl-surface + --spl-border
   *   accent    --spl-accent (orange stripe)
   *   radius    --spl-radius-lg, --spl-radius-circle
   *   shadow    --spl-shadow-card
   *   spacing   --spl-space-1, --spl-space-3, --spl-space-4, --spl-space-5, --spl-space-6
   *   colour    --spl-heading-color, --spl-text, --spl-accent, --spl-color-white
   *             --spl-color-navy-700, --spl-color-teal-500, --spl-border
   *   type      --spl-font-heading, --spl-text-xs, --spl-text-base, --spl-text-sm
   * ----------------------------------------------------------------------- */

  .spl-author {
    background: var(--spl-surface);
    border: 1px solid var(--spl-border);
    border-left: 3px solid var(--spl-accent);
    border-radius: var(--spl-radius-lg);
    box-shadow: var(--spl-shadow-card);
    padding: var(--spl-space-4) var(--spl-space-5);
    margin-top: var(--spl-space-6);
  }

  /* Eyebrow label: "Reviewed by" — small, uppercase, muted */
  .spl-author__eyebrow {
    font-size: var(--spl-text-xs);
    font-family: var(--spl-font-heading);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--spl-letter-spacing-label);
    color: var(--spl-text);
    margin: 0 0 var(--spl-space-3);
  }

  /* Row: avatar + stacked name/role */
  .spl-author__body {
    display: flex;
    gap: var(--spl-space-4);
    align-items: center;
  }

  /* Circular avatar — 60px, ring via outline + outline-offset */
  .spl-author__avatar {
    width: var(--spl-author-avatar-size);
    height: var(--spl-author-avatar-size);
    border-radius: var(--spl-radius-circle);
    flex: none;

    /* Initials fallback: navy-to-teal gradient */
    background: linear-gradient(
      140deg,
      var(--spl-color-navy-700),
      var(--spl-color-teal-500)
    );
    color: var(--spl-color-white);
    display: grid;
    place-items: center;
    font-weight: 700;
    font-family: var(--spl-font-heading);
    font-size: var(--spl-text-base);

    /* Badge ring: slate outline, 2px white gap */
    outline: 2px solid var(--spl-border);
    outline-offset: 2px;
    overflow: hidden;
  }

  .spl-author__avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    border-radius: var(--spl-radius-circle);
  }

  /* Text column: name above, role below */
  .spl-author__info {
    display: flex;
    flex-direction: column;
    gap: var(--spl-space-1);
    min-width: 0;
  }

  /* Author name — bold, heading font, navy */
  .spl-author__name {
    font-family: var(--spl-font-heading);
    font-weight: 700;
    color: var(--spl-heading-color);
    font-size: var(--spl-text-base);
    line-height: 1.3;
    display: block;
  }

  /* Name as link — accent on hover, focus ring */
  .spl-author__link {
    color: inherit;
    text-decoration: none;
  }

  .spl-author__link:hover {
    color: var(--spl-accent-ink);
    text-decoration: underline;
  }

  .spl-author__link:focus-visible {
    outline: 2px solid var(--spl-focus-ring);
    outline-offset: 2px;
    border-radius: var(--spl-radius-xs);
  }

  /* Role / credential — own line, muted, smaller */
  .spl-author__role {
    font-size: var(--spl-text-sm);
    color: var(--spl-text);
    display: block;
    line-height: 1.4;
  }

  /* Mobile: ensure tap target and clean stacking */
  @media (width <= 480px) {
    .spl-author__body {
      align-items: flex-start;
    }

    .spl-author__link {
      min-height: var(--spl-tap-min);
      display: inline-flex;
      align-items: center;
    }
  }

  /* -----------------------------------------------------------------------
   * Related resources section
   * ----------------------------------------------------------------------- */

  .spl-related {
    margin-top: var(--spl-space-8);
  }
}

/*
 * faq.css — .spl-faq disclosure list (schema.org FAQPage target)
 */

.spl-root {
  .spl-faq {
    margin-top: var(--spl-space-6);
  }

  .spl-faq details {
    border: 1px solid var(--spl-border);
    border-radius: var(--spl-radius-md);
    background: var(--spl-surface);
    margin-bottom: var(--spl-space-3);
    overflow: hidden;
  }

  .spl-faq summary {
    list-style: none;
    cursor: pointer;
    padding: var(--spl-space-4) var(--spl-space-5);
    font-family: var(--spl-font-heading);
    font-weight: 600;
    color: var(--spl-heading-color);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--spl-space-3);
  }

  .spl-faq summary::-webkit-details-marker {
    display: none;
  }

  .spl-faq summary .chev {
    transition: transform var(--spl-motion-duration-lg) var(--spl-motion-ease-standard);
    flex: none;
    color: var(--spl-color-slate-500);
  }

  .spl-faq details[open] summary .chev {
    transform: rotate(180deg);
  }

  .spl-faq .answer {
    padding: 0 var(--spl-space-5) var(--spl-space-4);
    color: var(--spl-text);
    font-size: var(--spl-text-sm);
  }
}

/*
 * gate.css: .spl-gate-panel* + .spl-gate-form* (lead-magnet email-gate panel)
 *
 * Emitted by templates/parts/gate-form.php. Rewrote from the old .spl-gate /
 * .spl-success selectors to match the BEM class vocabulary the template actually
 * emits. All values reference --spl-* tokens.
 *
 * Panel structure:
 *   .spl-gate-panel
 *     .spl-gate-panel__notice      (recovery hint after stale email links)
 *     .spl-gate-panel__reveal      (button: "Get the PDF")
 *     .spl-gate-panel__form-wrap   (hidden until reveal)
 *       .spl-gate-form
 *         .spl-gate-form__field
 *           .spl-gate-form__label
 *           .spl-gate-form__input
 *         .spl-gate-form__consent
 *           .spl-gate-form__checkbox
 *           .spl-gate-form__consent-label
 *         .spl-gate-form__trust
 *         .spl-gate-form__error    (role=alert, hidden until gateError set)
 *         .spl-gate-form__submit
 *     .spl-gate-panel__success     (hidden until gateSuccess)
 *       .spl-gate-panel__success-msg
 *       .spl-gate-panel__download  (button/link)
 */

.spl-root {
  /* Shared card shell for BOTH download-box states: the first-time email gate
     (.spl-gate-panel) and the returning-visitor unlock panel (.spl-unlock-panel).
     One selector list so the returning panel is never a bare, unstyled strip.
     A light hairline (--spl-color-grey-100) plus the soft card shadow gives quiet
     definition without the old heavy dark border reading as a "black box". */
  .spl-gate-panel,
  .spl-unlock-panel {
    background: var(--spl-surface);
    border: 1px solid var(--spl-color-grey-100);
    border-radius: var(--spl-radius-lg);
    padding: var(--spl-space-5);
    box-shadow: var(--spl-shadow-card);
    display: flex;
    flex-direction: column;
    gap: var(--spl-space-4);

    /* W17 Focus Not Obscured (WCAG 2.4.11): keep the panel below any sticky header
       so keyboard focus on the panel is never hidden behind the fixed/sticky header. */
    scroll-margin-top: var(--spl-header-offset, 0);
  }

  /* Primary CTA recolour — SCOPED to the download box only (never the global
     .spl-btn-primary, which stays navy-on-brand-orange). White on burnt orange
     (--spl-accent-cta #c2410c) is ~5.9:1, passing WCAG AA, and matches the
     white-on-orange CTAs elsewhere on the site. */
  .spl-gate-panel .spl-btn-primary,
  .spl-unlock-panel .spl-btn-primary {
    background: var(--spl-accent-cta);
    color: var(--spl-color-white);
  }

  .spl-gate-panel .spl-btn-primary:hover,
  .spl-unlock-panel .spl-btn-primary:hover {
    background: var(--spl-accent-cta-hover);
  }

  /* Reveal button: full width, same as primary btn; layout extension only */
  .spl-gate-panel__reveal {
    width: 100%;
    justify-content: center;
  }

  /* File-meta line ("PDF · <size>"): small, muted, informational. */
  .spl-gate-panel__filemeta {
    margin: 0;
    font-size: var(--spl-text-xs);
    color: var(--spl-text-muted);
  }

  .spl-gate-panel__notice {
    font-size: var(--spl-text-sm);
    color: var(--spl-accent-ink);
    background: var(--spl-accent-tint);
    border-radius: var(--spl-radius-sm);
    padding: var(--spl-space-2) var(--spl-space-3);
    margin: 0;
  }

  /* Social proof (Task 9): download count + credential line shown above the reveal button.
     Informational only, not interactive. Order: count, then cert (flex column). */
  .spl-gate-panel__social-proof {
    display: flex;
    flex-direction: column;
    gap: var(--spl-space-1);
    margin: 0;
    font-size: var(--spl-text-xs);
    color: var(--spl-text-muted);
  }

  .spl-gate-panel__dl-count {
    font-weight: 600;
    color: var(--spl-text-strong);
  }

  .spl-gate-panel__cert {
    color: var(--spl-text-muted);
  }

  /* Form wrapper: hidden by default (SSR guard, pre-hydration fallback).
     The template ships this element with a static `hidden` attr; the store
     removes it via data-wp-bind--hidden="!state.gateOpen" once JS hydrates.
     Explicit display:none ensures no author rule can override the UA
     [hidden]{display:none} before hydration (mirrors .spl-client-view in
     archive.css). The :not([hidden]) rule restores flex once the attr is gone. */
  .spl-gate-panel__form-wrap {
    display: none;
  }

  /* stylelint-disable-next-line no-descending-specificity -- intentional: display:none above is the SSR guard; this restores flex once `hidden` is removed */
  .spl-gate-panel__form-wrap:not([hidden]) {
    display: flex;
    flex-direction: column;
  }

  /* Form itself */
  .spl-gate-form {
    display: flex;
    flex-direction: column;
    gap: var(--spl-space-4);
  }

  /* Label + input field group */
  .spl-gate-form__field {
    display: flex;
    flex-direction: column;
    gap: var(--spl-space-px-6);
  }

  .spl-gate-form__label {
    display: block;
    font-size: var(--spl-text-sm);
    font-weight: 600;
    color: var(--spl-text-strong);
  }

  .spl-gate-form__input {
    width: 100%;
    padding: var(--spl-space-3) var(--spl-space-px-14);
    border: 1px solid var(--spl-control-border);
    border-radius: var(--spl-radius-md);
    font-size: var(--spl-text-base);
    color: var(--spl-text-strong);
    background: var(--spl-surface);
    font-family: var(--spl-font-body);

    /* W17 Focus Not Obscured (WCAG 2.4.11): keep the email input below any sticky header
       when it receives focus via the gate reveal keyboard flow. */
    scroll-margin-top: var(--spl-header-offset, 0);
  }

  .spl-gate-form__input:focus {
    outline: 2px solid var(--spl-focus-ring);
    outline-offset: 2px;
    border-radius: var(--spl-radius-sm);
    border-color: var(--spl-focus-ring);
  }

  /* Consent row: checkbox + label.
     W16 Target Size (WCAG 2.5.8): the consent row must be at least 24x24 CSS px.
     We use --spl-tap-min (44px) as the row min-height (preferred target) and size
     the checkbox to 24x24 so the control itself meets the minimum even without
     the label's contribution to the hit area. The label stretches the full row so
     clicking anywhere in it toggles the checkbox (normal label-for behaviour). */
  .spl-gate-form__consent {
    display: flex;
    gap: var(--spl-space-px-10);
    align-items: center;
    min-height: var(--spl-tap-min);
    font-size: var(--spl-text-xs);
    color: var(--spl-text);
  }

  /* W16 Target Size: checkbox control >= 24x24 CSS px.
     accent-color tokens the brand colour without a raw value. */
  .spl-gate-form__checkbox {
    flex: none;
    width: var(--spl-tap-target-min);
    height: var(--spl-tap-target-min);
    accent-color: var(--spl-accent);
    cursor: pointer;
  }

  .spl-gate-form__consent-label {
    font-size: var(--spl-text-xs);
    color: var(--spl-text);
    cursor: pointer;
  }

  /* Necessity microcopy under the email field */
  .spl-gate-form__email-why {
    font-size: var(--spl-text-xs);
    color: var(--spl-text-muted);
    margin: 0;
  }

  /* Email typo suggestion region (role=status, aria-live=polite) */
  .spl-gate-form__email-suggest {
    font-size: var(--spl-text-xs);
    color: var(--spl-text);
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--spl-space-1);
    align-items: baseline;
  }

  .spl-gate-form__email-suggest[hidden] {
    display: none;
  }

  /* Inline suggestion button (looks like a text link) */
  .spl-gate-form__email-suggest-btn {
    display: inline-flex;
    align-items: center;
    min-height: var(--spl-tap-target-min);
    background: none;
    border: none;
    padding: 0;
    font-size: var(--spl-text-xs);
    color: var(--spl-accent-ink);
    cursor: pointer;
    text-decoration: underline;
    font-family: var(--spl-font-body);
  }

  .spl-gate-form__email-suggest-btn:hover {
    color: var(--spl-accent-dark);
  }

  .spl-gate-form__email-suggest-btn:focus-visible {
    outline: 2px solid var(--spl-focus-ring);
    outline-offset: 2px;
    border-radius: var(--spl-radius-sm);
  }

  /* CASL consent statement text (aria-describedby target for the optional checkbox) */
  .spl-gate-form__consent-text {
    font-size: var(--spl-text-xs);
    color: var(--spl-text-muted);
    margin: 0;
  }

  /* Trust microcopy (aria-describedby target for the checkbox) */
  .spl-gate-form__trust {
    font-size: var(--spl-text-xs);
    color: var(--spl-text);
    margin: 0;
  }

  /* Error message (role=alert, populated by store via data-wp-text; hidden by default) */
  .spl-gate-form__error {
    font-size: var(--spl-text-sm);
    color: var(--spl-accent-ink);
    background: var(--spl-accent-tint);
    border-radius: var(--spl-radius-sm);
    padding: var(--spl-space-2) var(--spl-space-3);
    margin: 0;
  }

  /* Submit button: full width */
  .spl-gate-form__submit {
    width: 100%;
    justify-content: center;
  }

  /* Success block: hidden by default (SSR guard, pre-hydration fallback).
     Explicit display:none ensures this stays hidden until state.gateSuccess
     is set. The :not([hidden]) rule restores flex once the store removes the
     `hidden` attr via data-wp-bind--hidden="!state.gateSuccess".
     W17 Focus Not Obscured (WCAG 2.4.11): scroll-margin keeps the block below
     any sticky header when the download link inside it receives focus. */
  .spl-gate-panel__success {
    display: none;
    scroll-margin-top: var(--spl-header-offset, 0);
  }

  /* stylelint-disable-next-line no-descending-specificity -- intentional: display:none above is the SSR guard; this restores flex once `hidden` is removed */
  .spl-gate-panel__success:not([hidden]) {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--spl-space-3);
    text-align: center;
    padding: var(--spl-space-5) var(--spl-space-4);
    background: var(--spl-color-green-050);
    border-radius: var(--spl-radius-md);
  }

  .spl-gate-panel__success-icon {
    color: var(--spl-color-green-700);
  }

  .spl-gate-panel__success-title {
    font-size: var(--spl-text-base);
    color: var(--spl-color-green-700);
    margin: 0;
  }

  .spl-gate-panel__success-msg {
    font-size: var(--spl-text-sm);
    color: var(--spl-text-strong);
    margin: 0;
  }

  /* Download link inside the success block: full width.
     .spl-gate-panel__download is the legacy selector (kept for backward compatibility).
     .spl-download-link is the new selector emitted by parts/download-link.php (Plan 4.2 Task 3). */
  .spl-gate-panel__download,
  .spl-download-link {
    width: 100%;
    justify-content: center;
  }
}

/*
 * Gate reveal animation (W16/reduced-motion-safe).
 * The form-wrap slide-in only runs when the user has NOT requested reduced motion
 * (mirrors the library.css positive-motion block for the sheen effect).
 * Under prefers-reduced-motion: reduce the library.css kill-switch already zeroes
 * all animation-duration and transition-duration inside .spl-root, so no extra
 * rule is needed here for the kill-switch -- only the opt-in positive block.
 */
@media (prefers-reduced-motion: no-preference) {
  .spl-root .spl-gate-panel__form-wrap:not([hidden]) {
    animation: spl-gate-reveal var(--spl-motion-duration-lg) var(--spl-motion-ease-standard);
  }

  @keyframes spl-gate-reveal {
    from {
      opacity: 0;
      translate: 0 -6px;
    }

    to {
      opacity: 1;
      translate: 0 0;
    }
  }
}

/*
 * No-JS fallback for the gate is NOT done here. A linked-stylesheet `html:not(.spl-js) …` rule
 * proved unreliable on the live site: the reveal-hide rule applied but the identical form-show
 * rule did not, because WP Rocket "Remove Unused CSS" analyses the page with JavaScript ON (where
 * `html:not(.spl-js)` matches nothing) and strips/mangles those rules. The robust fallback lives
 * in templates/parts/gate-form.php as an inline <noscript><style> block, which is inert while JS
 * is on (so RUCSS never touches it and there is no flash for JS users) and applies only when JS
 * is disabled.
 */

/*
 * download.css — .spl-download-panel (open/free download sidebar panel)
 *
 * Ported from the .spl-panel look in single.css (surface bg, border, radius-lg,
 * padding, shadow-card). The button reuses .spl-btn + .spl-btn-primary from
 * buttons.css. Only layout additions and panel-specific sub-elements are declared here.
 *
 * Emitted by templates/parts/download.php.
 * All values reference --spl-* tokens (Stylelint strict-value gate).
 */

.spl-root {
  .spl-download-panel {
    background: var(--spl-surface);
    border: 1px solid var(--spl-border);
    border-radius: var(--spl-radius-lg);
    padding: var(--spl-space-5);
    box-shadow: var(--spl-shadow-card);
    display: flex;
    flex-direction: column;
    gap: var(--spl-space-4);
  }

  /* File name + size line — mono, small, muted */
  .spl-download-panel__meta {
    display: flex;
    align-items: center;
    gap: var(--spl-space-3);
    font-family: var(--spl-font-mono);
    font-size: var(--spl-text-sm);
    color: var(--spl-text);
    flex-wrap: wrap;
  }

  .spl-download-panel__meta .filename {
    color: var(--spl-text-strong);
    font-weight: 600;
    word-break: break-all;
  }

  .spl-download-panel__meta .filesize {
    color: var(--spl-text);
  }

  /* Download button — full width within the panel */
  .spl-download-panel__btn {
    width: 100%;
    justify-content: center;
  }

  /* Trust microcopy: "Free. No email required." */
  .spl-download-panel__trust {
    font-size: var(--spl-text-xs);
    color: var(--spl-text);
    text-align: center;
    margin: 0;
  }
}

/*
 * archive.css — archive / hub page layout components
 *
 * Emitted by:
 *   templates/archive-free_pdf.php
 *   templates/taxonomy-free_pdf_topic.php
 *
 * These classes have no corresponding CSS prior to Task 6. This file provides
 * the minimal layout rules so the archive and hub pages render correctly in
 * both SSR-only (no-JS) and client-interactive modes.
 *
 * View-toggle mechanism (confirmed from assets/frontend/store/index.ts):
 *   .spl-ssr-view  uses  data-wp-bind--hidden="state.interacted"
 *   .spl-client-view uses data-wp-bind--hidden="!state.interacted"
 *   The Interactivity API sets/removes the HTML `hidden` attribute.
 *   The `hidden` attribute has native `display:none` in all modern browsers.
 *   The `display: none` below is a pre-hydration fallback so .spl-client-view
 *   is invisible on the first SSR paint before the store sets state.interacted.
 *
 * All values reference --spl-* tokens. No hardcoded colours, spacing, or radii.
 */

.spl-root {
  /* Main content area — adds top spacing below the hero */
  /* Inner .shell wrapper supplies the max-width container; this rule adds vertical rhythm */
  .spl-archive-main {
    padding: var(--spl-space-6) 0 var(--spl-space-8);
  }

  /* Landing curated group section (Featured / Quick / Lead sections on the archive) */
  .spl-group-section {
    padding: var(--spl-space-5) 0;
  }

  /* Section heading within a group — same scale as .spl-section-head h2 */
  .spl-section-heading {
    font-size: var(--spl-text-xl);
    color: var(--spl-heading-color);
    font-family: var(--spl-font-heading);
    font-weight: 700;
    margin: 0 0 var(--spl-space-5);
  }

  /* Grid wrapper — carries data-wp-class--is-loading; the inner .spl-grid handles columns */
  .spl-grid-region {
    padding: var(--spl-space-5) 0 var(--spl-space-6);
  }

  /* Loading state — optional subtle opacity shift while the store fetches */
  .spl-grid-region.is-loading {
    opacity: 0.7;
  }

  /* SSR view: shown by default; store hides it by setting the `hidden` attribute
     (data-wp-bind--hidden="state.interacted") on the first interaction. */
  .spl-ssr-view {
    display: block;
  }

  /* Client view: hidden by default (pre-hydration fallback).
     The store reveals it by removing the `hidden` attribute
     (data-wp-bind--hidden="!state.interacted") once state.interacted is true.
     The native `hidden` attribute provides `display:none`; this declaration is
     the SSR-safe pre-hydration guard for the same effect. */
  .spl-client-view {
    display: none;
  }

  /* When the `hidden` attribute is absent (JS removed it) the element shows.
     We undo the display:none via the :not([hidden]) selector so there is no
     JS-class dependency — the Interactivity API only touches the `hidden` attr. */
  /* stylelint-disable-next-line no-descending-specificity -- intentional: the display:none above is the SSR guard; this restores block once `hidden` is removed */
  .spl-client-view:not([hidden]) {
    display: block;
  }

  /* Keyset cursor-nav pagination */
  .spl-cursor-nav {
    display: flex;
    justify-content: center;
    margin-top: var(--spl-space-6);
  }

  /* "Load more" / "See more" link or button */
  .spl-cursor-nav__next {
    display: inline-flex;
    align-items: center;
    gap: var(--spl-space-2);
    font-size: var(--spl-text-sm);
    font-weight: 700;
    color: var(--spl-accent-ink);
    text-decoration: none;
    padding: var(--spl-space-px-10) var(--spl-space-5);
    border: 1px solid var(--spl-border);
    border-radius: var(--spl-radius-md);
    background: var(--spl-surface);
    transition:
      border-color var(--spl-motion-duration-sm) var(--spl-motion-ease-standard),
      box-shadow var(--spl-motion-duration-sm) var(--spl-motion-ease-standard);
  }

  .spl-cursor-nav__next:hover {
    border-color: var(--spl-focus-ring);
    box-shadow: var(--spl-shadow-lift);
  }

  /* Live region — visually hidden; announced by screen readers when store updates */
  .spl-live-region {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* ---------- zero-results empty state (templates/parts/empty-state.php) ---------- */

  .spl-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--spl-space-8) var(--spl-space-5);
    gap: var(--spl-space-4);
  }

  .spl-empty-state__icon {
    color: var(--spl-text);
    opacity: 0.45;
    line-height: 0;
  }

  .spl-empty-state__heading {
    font-family: var(--spl-font-heading);
    font-size: var(--spl-text-xl);
    font-weight: 700;
    color: var(--spl-heading-color);
    margin: 0;
  }

  .spl-empty-state__message {
    font-size: var(--spl-text-base);
    color: var(--spl-text);
    max-width: 44ch;
    margin: 0;
  }

  /* Reset link — uses the ghost button component styling via .spl-btn.spl-btn-ghost */
  .spl-empty-state__reset {
    margin-top: var(--spl-space-2);
  }

  /* Did-you-mean block (Task 4, Plan 3.5) */
  .spl-empty-state__dym {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--spl-space-2);
    margin-top: var(--spl-space-2);
  }

  .spl-empty-state__dym[hidden] {
    display: none;
  }

  .spl-empty-state__dym-label {
    font-size: var(--spl-text-sm);
    color: var(--spl-text);
    margin: 0;
  }

  .spl-empty-state__dym-list {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--spl-space-2);
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .spl-empty-state__dym-link {
    font-size: var(--spl-text-base);
    color: var(--spl-accent-ink);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: var(--spl-space-px-2);
    transition: color var(--spl-motion-duration-xs) var(--spl-motion-ease-standard);
  }

  .spl-empty-state__dym-link:hover,
  .spl-empty-state__dym-link:focus-visible {
    color: var(--spl-accent-ink);
  }

  /* Related resources heading (emitted by templates/parts/related.php) */
  .spl-related__heading {
    font-size: var(--spl-text-xl);
    color: var(--spl-heading-color);
    font-family: var(--spl-font-heading);
    font-weight: 700;
    margin: 0 0 var(--spl-space-5);
  }
}

/*
 * utilities.css — Scoped accessibility utilities for the SME PDF Library plugin.
 *
 * Previously in @layer spl-utilities (declared AFTER spl-components in base.css
 * so utilities always beat component rules at the same specificity). Now unlayered
 * and nested under .spl-root, matching the de-layered component files from Task 4.
 * Specificity (0,2,0) beats theme element rules without requiring @layer ordering.
 *
 * Why scoped?
 * The plugin design system is deliberately scoped under .spl-root to be
 * theme-independent (spec §14). `.screen-reader-text` is a WordPress THEME class;
 * on production themes (Mayosis, Elementor, etc.) that do not define it the
 * visually-hidden elements would render VISIBLE. By defining BOTH `.screen-reader-text`
 * and `.spl-sr-only` inside `.spl-root` we guarantee them regardless of
 * theme, without polluting the global stylesheet.
 *
 * These are layout/clipping properties — not guarded by stylelint-declaration-strict-value.
 */

.spl-root {
  /* stylelint-disable-next-line selector-class-pattern */
  .screen-reader-text,
  .spl-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

/*
 * skeleton.css — skeleton placeholder cards shown while state.loading is true.
 *
 * The skeleton grid appears inside .spl-client-view while the store fetches
 * new results. It uses a CSS pulse animation (transform + opacity) to give a
 * loading-in-progress cue. The animation is disabled under
 * `prefers-reduced-motion: reduce` (WCAG 2.3.3) — the library.css kill-switch
 * sets animation-duration: 0.01ms !important on all elements inside .spl-root,
 * so static placeholders are shown instead.
 *
 * Binding: the outer wrapper carries
 *   data-wp-bind--hidden="!state.loading"
 * so it is visible only while a fetch is in-flight. The results grid wrapper
 * carries data-wp-class--is-loading="state.loading" for the existing opacity shift.
 *
 * Token-only: no hardcoded colours, spacing, or radii.
 */

/* ---- keyframe (positive-motion only; kill-switch in library.css covers reduce) ---- */

@keyframes spl-skeleton-pulse {
  0%   { opacity: 1; }
  50%  { opacity: 0.45; }
  100% { opacity: 1; }
}

.spl-root {
  /* Skeleton grid — mirrors .spl-grid column rules */
  .spl-skeleton-grid {
    display: grid;
    gap: var(--spl-space-5);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 290px), 1fr));
    padding: var(--spl-space-5) 0 var(--spl-space-6);
  }

  /* Hidden by default; shown only while state.loading (bound via data-wp-bind--hidden) */
  .spl-skeleton-grid[hidden] {
    display: none;
  }

  /* One skeleton card — same outer shape as .spl-card */
  .spl-skeleton-card {
    background: var(--spl-surface);
    border: 1px solid var(--spl-border);
    border-radius: var(--spl-radius-lg);
    box-shadow: var(--spl-shadow-card);
    overflow: hidden;
    display: flex;
    flex-direction: column;

    /* Animate the whole card (child blocks inherit) */
    animation: spl-skeleton-pulse var(--spl-motion-duration-skeleton) var(--spl-motion-ease-pulse) infinite;
  }

  /* Cover plate placeholder */
  .spl-skeleton-card__cover {
    background: var(--spl-surface-soft);
    aspect-ratio: 16 / 10;
    width: 100%;
    border-radius: 0;
  }

  /* Body placeholder rows */
  .spl-skeleton-card__body {
    padding: var(--spl-space-5);
    display: flex;
    flex-direction: column;
    gap: var(--spl-space-3);
    flex: 1;
  }

  /* Short badge-width bar */
  .spl-skeleton-card__badge {
    height: 1.25rem;
    width: 5rem;
    background: var(--spl-surface-soft);
    border-radius: var(--spl-radius-pill);
  }

  /* Title line — two lines simulated via two bars */
  .spl-skeleton-card__title-a {
    height: 1.1rem;
    width: 85%;
    background: var(--spl-surface-soft);
    border-radius: var(--spl-radius-sm);
  }

  .spl-skeleton-card__title-b {
    height: 1.1rem;
    width: 60%;
    background: var(--spl-surface-soft);
    border-radius: var(--spl-radius-sm);
  }

  /* Excerpt lines */
  .spl-skeleton-card__line {
    height: 0.85rem;
    background: var(--spl-surface-soft);
    border-radius: var(--spl-radius-sm);
  }

  .spl-skeleton-card__line--short {
    width: 70%;
  }

  /* Meta row */
  .spl-skeleton-card__meta {
    display: flex;
    gap: var(--spl-space-3);
    border-top: 1px solid var(--spl-border);
    padding-top: var(--spl-space-3);
    margin-top: auto;
  }

  .spl-skeleton-card__meta-chip {
    height: 0.85rem;
    width: 4rem;
    background: var(--spl-surface-soft);
    border-radius: var(--spl-radius-pill);
  }

  @media (prefers-reduced-motion: reduce) {
    .spl-skeleton-card {
      animation: none;
    }
  }
}


/*
 * ---- motion (prefers-reduced-motion) ----
 *
 * Positive-motion block: sheen hover sweep, only when the user has not
 * requested reduced motion.
 */
@media (prefers-reduced-motion: no-preference) {
  .spl-root .spl-cover .sheen {
    transition: translate var(--spl-motion-duration-sheen) var(--spl-motion-ease-standard);
  }

  .spl-root .spl-card:hover .spl-cover .sheen {
    translate: 120% 0;
  }
}

/*
 * Reduced-motion kill-switch (WCAG 2.3.3).
 * The !important declarations below are the ONE sanctioned use of !important
 * in this codebase — they must override any specificity to guarantee zero
 * motion for users who request reduced motion. Do not add !important elsewhere.
 */
@media (prefers-reduced-motion: reduce) {
  /* stylelint-disable declaration-no-important */
  .spl-root *,
  .spl-root *::before,
  .spl-root *::after {
    animation-duration: var(--spl-motion-duration-2xs) !important;
    transition-duration: var(--spl-motion-duration-2xs) !important;
  }
  /* stylelint-enable declaration-no-important */
}
