/* =========================================================================
   SKP DESIGN SYSTEM — SHELL AND SHARED PRIMITIVES
   v1 (2026-08-25)   Loaded by skp-components.php at priority 30.
   Depends on: skp-theme-tokens (5) -> skp-design-system (20) -> skp-product-tiers (25)

   WHY THIS FILE EXISTS
   docs/design-system/02-COMPONENTS.md opens with a "Shared anatomy" contract:
   every section is .skp-section > .skp-inner, carrying .skp-eyebrow,
   .skp-section-title and .skp-section-lede. A consistency audit on 2026-08-25
   found that THREE OF THOSE FOUR PARTS WERE DECLARED NOWHERE:

     .skp-section        declared only inside `.skp-mbm .skp-section` — one page
     .skp-eyebrow        ZERO declarations in any stylesheet
     .skp-section-title  ZERO declarations
     .skp-section-lede   ZERO declarations
     .skp-inner          the only one that existed (skp-design-system.css)

   So every component's markup reasoned from a shell that did not exist:
   eyebrows rendered at body size, ledes ran unmeasured to the container edge,
   and full-bleed sections were not full-bleed. Fifteen components then each
   worked around it privately, which produced five card implementations, seven
   grid mechanisms and seven different section-rhythm escalations.

   This file is the hoist. Components are reduced to what these do not cover.

   LOADING AFTER BOTH LEGACY SHEETS IS DELIBERATE. skp-design-system.css sets
   `.skp-product-page .skp-section { padding-block: clamp(48px,6vw,88px) }` at
   (0,2,0), and skp-product-tiers.css owns the legacy accent bar. Source order
   is what resolves those ties without an !important arms race.
   ========================================================================= */

/* =========================================================================
   REGION 1 — SHELL
   Must come first: everything below assumes the cap and the full bleed exist.
   ========================================================================= */

.skp-section {
  position: relative;
  width: 100vw;
  max-width: 100vw;
  /* full-bleed out of any capped ancestor without depending on the page wrapper
     having already broken out. margin-inline is safe here because the element
     is a block in normal flow. */
  margin-inline: calc(50% - 50vw);
  padding-inline: 0;
  padding-block: var(--skp-section-rhythm);
  box-sizing: border-box;
}

/* (0,2,0) beats skp-design-system.css's `.skp-product-page .skp-section` at the
   same specificity on source order, which is why this file loads at 30. This
   single rule replaces the seven different escalation shapes the components
   had each invented. */
.skp-product-page .skp-section,
.skp-resource-page .skp-section {
  padding-block: var(--skp-section-rhythm);
}

/* A hero and a tight band are not a standard section. */
.skp-section.skp-hero    { padding-block: var(--skp-hero-rhythm); }

/* Astra's transparent header is position:absolute and ~204px tall, so it sits
   OVER the hero rather than above it. Without extra top padding the eyebrow and
   the first line of the H1 render underneath the nav. Measured 2026-08-26:
   header 204px, hero padding-top 115px, eyebrow top 172px — obscured. */
body.ast-theme-transparent-header .skp-section.skp-hero {
  padding-top: calc(var(--skp-hero-rhythm) + 210px);
}
@media (max-width: 900px) {
  body.ast-theme-transparent-header .skp-section.skp-hero {
    padding-top: calc(var(--skp-hero-rhythm) + 120px);
  }
}
.skp-section.skp-stats,
.skp-section.skp-lineage { padding-block: var(--skp-band-rhythm); }

/* The one cap, on every section, without exception.
   skp-design-system.css already declares this with !important at higher
   specificity; this is the fallback for sections outside .skp-product-page. */
.skp-section > .skp-inner {
  width: 100%;
  max-width: var(--skp-inner-cap);
  margin-inline: auto;
  box-sizing: border-box;
}

/* The mobile gutter. skp-design-system.css sets `width: 100% !important` on
   .skp-inner at (0,3,1), so a plain rule here loses and the gutter collapses to
   ~11px a side — measured 367px at a 390 viewport where the contract in
   01-FOUNDATIONS §6.1 says ~354px. Matching the !important is the only way to
   win without reordering the sheets. */
@media (max-width: 900px) {
  body .skp-section > .skp-inner,
  body .skp-product-page .skp-section > .skp-inner {
    width: calc(100% - 36px) !important;
    max-width: calc(100% - 36px) !important;
    margin-inline: auto !important;
  }
}

/* =========================================================================
   REGION 2 — SHARED ANATOMY
   The three parts 02-COMPONENTS defines and no sheet implemented.
   ========================================================================= */

/* The element selector is not decoration. skp-design-system.css:110 ships
   `.skp-product-page p, .skp-resource-page p { font-size: var(--skp-body) }`
   at (0,2,0); a bare `.skp-eyebrow` is (0,1,0) and LOSES, so every eyebrow
   rendered at body size. `p.skp-eyebrow` reaches (0,1,1) and, loading at
   priority 30 against 20, wins the tie on source order. */
p.skp-eyebrow,
.skp-eyebrow {
  display: block;
  margin: 0 0 var(--skp-space-md);
  font: 700 var(--skp-label-small)/1.2 var(--skp-font);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--skp-accent);
}

/* On a hub the accent is #FFCD57, which is 1.36:1 on surface-container — an
   eyebrow is small text and must clear 4.5:1, so on LIGHT surfaces the hub
   eyebrow takes the container ink instead of the raw accent. */
body.skp-tier-material :is(.skp-surface, .skp-surface-container, .skp-surface-high) .skp-eyebrow {
  color: var(--skp-on-tertiary-container);
}

.skp-section-title {
  position: relative;
  margin: 0 0 var(--skp-space-md);
  padding-inline-start: calc(var(--skp-accent-bar) + var(--skp-space-md));
  font: 600 var(--skp-headline-medium)/1.10 var(--skp-font);
  letter-spacing: -0.015em;
  color: inherit;
  max-width: 32ch;
}

/* The mandatory left accent bar. Previously existed only in
   skp-product-tiers.css, hardcoded twice at 4px and 3px. */
.skp-section-title::before {
  content: "";
  position: absolute;
  inset-block: 0.12em 0.12em;
  inset-inline-start: 0;
  width: var(--skp-accent-bar);
  border-radius: var(--skp-shape-extra-small);
  background: var(--skp-accent);
}

/* A centred section (CTA) has no bar and no indent. */
.skp-section--centred .skp-section-title {
  padding-inline-start: 0;
  max-width: none;
}
.skp-section--centred .skp-section-title::before { content: none; }

/* The legacy accent bar in skp-product-tiers.css:102 and :241 is
   `.skp-tier-material .skp-section:not(.skp-hero) > .skp-inner > h2` — (0,4,2),
   which outranks everything above and hardcodes 16px/4px against --skp-signal.
   Until those two blocks are deleted, this neutralises them at (0,5,2) so the
   token-driven bar is the one that renders. Delete this block the day
   skp-product-tiers.css loses its heading rules. */
:is(.skp-tier-material, .skp-tier-application) .skp-section:not(.skp-hero) > .skp-inner > h2.skp-section-title {
  padding-left: calc(var(--skp-accent-bar) + var(--skp-space-md));
  border-left: 0;
}

/* Same specificity problem as the eyebrow — see above. */
p.skp-section-lede,
.skp-section-lede {
  margin: 0 0 var(--skp-space-xl);
  /* shares the title's left edge, bar included — never indented past it */
  padding-inline-start: calc(var(--skp-accent-bar) + var(--skp-space-md));
  font: 400 var(--skp-body-large)/1.6 var(--skp-font);
  max-width: var(--skp-measure-lede);
  color: var(--skp-on-surface-variant);
}
.skp-section--centred .skp-section-lede {
  padding-inline-start: 0;
  margin-inline: auto;
}

/* ---- head → body rhythm --------------------------------------------------
   The gap between a section's head block and its body resolved to SEVEN
   different values across the fifteen components, because nothing owned it.
   It is one value. Put .skp-section__body on the body wrapper. */
.skp-section__body { margin-block-start: var(--skp-space-2xl); }

/* =========================================================================
   REGION 3 — PRIMITIVES
   .skp-lift and .skp-cue precede .skp-panel so a panel-scoped override wins.
   ========================================================================= */

/* ---- inline prose link ---------------------------------------------------
   :where() keeps specificity at (0,1,0) so a component can override without
   an arms race. The !important on text-decoration is not stylistic: Astra's
   `a:not(...)` rules outrank any component selector (07 §3.3), which is how
   card copy shipped underlined. */
.skp-prose a:not(.skp-btn):not(.skp-panel__link),
:where(.skp-section-lede, .skp-definition__body, .skp-faq__answer, .skp-proof__caption) a:not(.skp-btn) {
  color: var(--skp-link-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.skp-prose a:focus-visible,
:where(.skp-section-lede, .skp-definition__body, .skp-faq__answer, .skp-proof__caption) a:focus-visible {
  outline: var(--skp-focus-width) solid var(--skp-focus-ring);
  outline-offset: var(--skp-focus-offset);
}

/* ---- grid ----------------------------------------------------------------
   ONE pinned-column grid, replacing five separate auto-fit mechanisms.
   auto-fit orphans the last tile whenever the count is not divisible by the
   computed column count — 5 cards render 4+1. Columns are PINNED. */
/* The descendant form is required, not stylistic: Astra styles
   `.entry-content ul` at (0,2,0), so a bare `.skp-grid` at (0,1,0) keeps the
   theme's bullets and indent. Three components had each written their own
   private escalation for this. It is decided once, here. */
.skp-grid,
.skp-section .skp-grid,
.skp-section .skp-duo__list {
  display: grid;
  gap: var(--skp-grid-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}
.skp-section .skp-duo__list { display: block; }   /* a list, not a grid */
.skp-grid > * { min-width: 0; }             /* or a long word forces overflow */

@media (min-width: 901px)  { .skp-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .skp-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.skp-grid--2 { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 901px) { .skp-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

@media (min-width: 1400px) { .skp-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* 5 items must land 3+2, never 4+1. */
@media (min-width: 1100px) {
  .skp-grid--5up > :nth-child(4),
  .skp-grid--5up > :nth-child(5) { grid-column: span 1; }
  .skp-grid--5up { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .skp-grid--5up > :nth-child(-n+3) { grid-column: span 2; }
  .skp-grid--5up > :nth-child(n+4)  { grid-column: span 3; }
}

/* ---- lift ----------------------------------------------------------------
   Motion and its reduced-motion guard in ONE place. Elevation means
   "you can click this" — .skp-lift is applied only to interactive surfaces. */
.skp-lift {
  box-shadow: var(--skp-elevation-card);
  transition: transform var(--skp-motion-standard),
              box-shadow var(--skp-motion-standard);
}
.skp-lift:hover { transform: translateY(-3px); box-shadow: var(--skp-elevation-card-hover); }
.skp-lift:active { transform: translateY(0); }

/* ---- cue -----------------------------------------------------------------
   The arrow affordance, one implementation, replacing four. */
.skp-cue::after {
  content: "";
  display: inline-block;
  width: 0.5em;
  height: 0.5em;
  margin-inline-start: 0.5em;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  transform: rotate(45deg) translateY(-0.1em);
  transition: transform var(--skp-motion-fast);
}
.skp-cue:hover::after { transform: rotate(45deg) translate(0.12em, -0.12em); }

/* ---- panel ---------------------------------------------------------------
   THE card, replacing five near-identical implementations.
   NOT named .skp-card: skp-design-system.css:149 forces
   `border-radius: var(--skp-radius-card) !important` on that name. */
.skp-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--skp-space-sm);
  padding: var(--skp-card-pad);
  border-top: var(--skp-accent-bar) solid var(--skp-accent);
  border-radius: var(--skp-shape-card);
  background: var(--skp-surface);
  color: var(--skp-on-surface);
  box-sizing: border-box;
}

/* A panel on an already-white section flips to the container tint, so a card is
   always distinguishable from the surface behind it. Both components that
   hardcoded their card surface got this wrong on one of the two tiers. */
.skp-surface .skp-panel { background: var(--skp-surface-container); }
:is(.skp-inverse, .skp-secondary) .skp-panel {
  background: rgba(255, 255, 255, 0.06);
  color: var(--skp-inverse-on-surface);
}

.skp-panel > :last-child { margin-bottom: 0; }

/* Card heading and body type, owned once. Five components had byte-identical
   copies of this, and a sixth relied on the `.skp-title-large` utility at
   (0,1,0) — which loses to the theme's own heading rules, so its cards rendered
   at the wrong leading. (0,1,1) here wins and covers all six. */
.skp-panel > h3,
.skp-panel > h4 {
  margin: 0;
  font: 600 var(--skp-title-large)/1.25 var(--skp-font);
  letter-spacing: -0.01em;
  color: inherit;
}
.skp-panel > p {
  margin: 0;
  font: 400 var(--skp-body-medium)/1.68 var(--skp-font);
  color: var(--skp-on-surface-variant);
}

/* Card media. A fixed ratio is not cosmetic: card images arrive at whatever
   dimensions they were shot at — the blog cards mix 1024x1024 squares with a
   1152x1536 portrait — and 02 §12 requires every tile in a row to share one
   ratio. object-fit does the normalising so no image needs re-cropping. */
.skp-panel__media {
  margin: calc(var(--skp-card-pad) * -1) calc(var(--skp-card-pad) * -1) var(--skp-space-md);
  border-radius: var(--skp-shape-card) var(--skp-shape-card) 0 0;
  overflow: hidden;
}
.skp-panel__media img {
  display: block;
  width: 100%;
  height: auto;
  /* 3/2 on a 660px card renders a 441px image that owns the card and pushes the
     copy below the fold. A card image is a BAND that supports the claim, not the
     subject. 16/9 plus a hard cap keeps it subordinate at every card width. */
  aspect-ratio: 16 / 9;
  max-height: clamp(170px, 19vw, 260px);
  object-fit: cover;
}

/* ---- figures --------------------------------------------------------------
   NEVER UPSCALE. An image rendered above its intrinsic size is blurry, and the
   failure mode is not subtle: a 74x13 decorative rule inherited `width: 100%`
   and rendered at 1354x846 — an 18x upscale that added 800px of blur to the
   page. A 300x200 logo rendered at 772x515 the same way.

   The fix is to stop treating "image" as one thing. A PHOTOGRAPH fills its
   frame and crops; a MARK sits at its own size and never crops; a DECORATIVE
   rule renders exactly as drawn. */
.skp-figure { margin: 0; }
.skp-figure__link {
  display: block;
  text-decoration: none !important;
  border-radius: var(--skp-shape-media);
  overflow: hidden;
}
.skp-figure__link:focus-visible {
  outline: var(--skp-focus-width) solid var(--skp-focus-ring);
  outline-offset: var(--skp-focus-offset);
}

/* default: behave, do not stretch */
.skp-figure img {
  display: block;
  max-width: 100%;
  height: auto;
}

/* a photograph fills its tile and crops to a shared ratio (02 §12) */
.skp-figure--photo img,
.skp-figure__link img {
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: clamp(180px, 20vw, 300px);
  object-fit: cover;
  border-radius: var(--skp-shape-media);
}

/* a decorative rule renders exactly as drawn — 74x13 stays 74x13 */
/* Specificity is deliberate. A component's own media rule — e.g.
   `.skp-definition .skp-definition__media img` at (0,2,1) — correctly forces
   width:100% and a crop ratio for a PHOTOGRAPH, and would destroy a decorative
   rule. This must outrank any component media rule, so it reaches (0,3,1) and
   is scoped by the marker class rather than by the component. */
.skp-figure--rule img,
.skp-section .skp-figure--rule img,
.skp-definition .skp-definition__media.skp-figure--rule img,
.skp-section .skp-figure--logo img {
  width: auto;
  max-width: 100%;
  height: auto;
  aspect-ratio: auto;
  max-height: none;
  object-fit: none;
  opacity: 0.9;
}
.skp-figure--rule { display: flex; justify-content: flex-start; margin-block: var(--skp-space-lg); }

/* a mark sits at its own scale, never cropped, never enlarged */
.skp-figure--logo {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 96px;
}
.skp-figure--logo img,
.skp-section .skp-figure--logo img {
  width: auto !important;
  max-height: 56px;
  max-width: min(100%, 220px);
  aspect-ratio: auto;
  object-fit: contain;
  border-radius: 0;
}

/* The WHOLE panel is the hit area when it links. The stretched ::after is what
   makes that true; the text-decoration reset must carry !important because
   Astra's link rules outrank component selectors. */
.skp-panel__link {
  text-decoration: none !important;
  color: inherit;
}
.skp-panel__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}
.skp-panel :is(h2, h3, h4, span) { text-decoration: none !important; }

/* An inline link inside card prose competes with the card's own affordance when
   it is full-strength blue and underlined mid-sentence. It stays a link — it
   inherits the ink and carries a faint rule that resolves on hover. Deference:
   the copy reads first, the link is available second. */
.skp-panel p a:not(.skp-btn):not(.skp-panel__link) {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
  text-underline-offset: 3px;
  transition: text-decoration-color var(--skp-motion-fast), color var(--skp-motion-fast);
}
.skp-panel p a:not(.skp-btn):not(.skp-panel__link):hover {
  color: var(--skp-link-ink);
  text-decoration-color: currentColor;
}

/* Elevation and hover are granted ONLY to a panel that actually links.
   A static card carries no shadow and no hover — elevation is a claim that
   the thing responds. */
.skp-panel:has(.skp-panel__link) {
  box-shadow: var(--skp-elevation-card);
  transition: transform var(--skp-motion-standard),
              box-shadow var(--skp-motion-standard);
}
.skp-panel:has(.skp-panel__link):hover {
  transform: translateY(-3px);
  box-shadow: var(--skp-elevation-card-hover);
}
.skp-panel:has(.skp-panel__link:focus-visible) {
  outline: var(--skp-focus-width) solid var(--skp-focus-ring);
  outline-offset: var(--skp-focus-offset);
}
.skp-panel__link:focus-visible { outline: none; }   /* the panel carries the ring */

/* ---- duo -----------------------------------------------------------------
   The two-column list panel that Guidance and Comparison share ~85% of.
   Guidance qualifies a buyer in or out; Comparison sets this product against
   the alternative. Same shape, different job. */
/* NOTE the paired on-colour. The first revision of this rule set `background`
   with no `color` — the exact defect the whole role system exists to prevent,
   and the one that shipped white text on a white live CTA. A background without
   its pair is a defect no matter who writes it. */
.skp-duo {
  display: grid;
  gap: var(--skp-grid-gap);
  padding: var(--skp-card-pad);
  border-radius: var(--skp-shape-card);
  background: var(--skp-surface-container);
  color: var(--skp-on-surface);
}

/* On an already-tinted section the panel steps up one rung, so it stays
   distinguishable from the surface behind it. */
.skp-surface-container .skp-duo,
.skp-surface-high .skp-duo {
  background: var(--skp-surface-container-high);
  color: var(--skp-on-surface);
}
:is(.skp-inverse, .skp-secondary) .skp-duo {
  background: rgba(255, 255, 255, 0.06);
  color: var(--skp-inverse-on-surface);
}
@media (min-width: 901px) {
  .skp-duo { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* the divider IS the separation — no boxes around the columns */
  .skp-duo > :last-child {
    padding-inline-start: var(--skp-card-pad);
    border-inline-start: var(--skp-hairline);
  }
}
.skp-duo__heading {
  margin: 0 0 var(--skp-space-md);
  font: 600 var(--skp-title-large)/1.25 var(--skp-font);
}
.skp-duo__list { margin: 0; padding: 0; list-style: none; }
.skp-duo__list li {
  position: relative;
  padding-inline-start: 1.75em;
  margin-bottom: var(--skp-space-md);
  font: 400 var(--skp-body-medium)/1.68 var(--skp-font);
  color: var(--skp-on-surface-variant);
}
/* Check and cross, in accent and muted — never green and red. Colour is
   catalogue navigation here, not a value judgement. */
.skp-duo__list--yes li::before,
.skp-duo__list--no  li::before {
  position: absolute;
  inset-inline-start: 0;
  font-weight: 700;
}
.skp-duo__list--yes li::before { content: "\2713"; color: var(--skp-primary); }
.skp-duo__list--no  li::before { content: "\2715"; color: var(--skp-on-surface-variant); }

/* ---- hairline ------------------------------------------------------------ */
.skp-hairline-top    { border-top: var(--skp-hairline); }
.skp-hairline-bottom { border-bottom: var(--skp-hairline); }

/* ---- perforation motif ---------------------------------------------------
   00-PRINCIPLES §4.4: a dot matrix at roughly the pitch of PVC mesh, on dark
   bands only, sparingly. The product explaining itself at zero cost to the
   reader's attention.

   The mask is the whole trick. An unmasked field of dots is wallpaper and would
   sit behind the copy, which the principle forbids. Fading it in diagonally from
   the right means it reads as material catching light at the edge of the frame,
   and the text column never touches it. */
:is(.skp-inverse, .skp-secondary).skp-section::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: radial-gradient(circle at 1px 1px, rgba(255, 255, 255, 0.11) 1px, transparent 0);
  background-size: 13px 13px;
  -webkit-mask-image: linear-gradient(102deg, transparent 46%, #000 88%);
          mask-image: linear-gradient(102deg, transparent 46%, #000 88%);
}
/* the hero already layers a photograph and a scrim; the motif rides above both
   but still under the copy */
.skp-hero.skp-inverse::after { z-index: 0; }
.skp-hero .skp-inner { position: relative; z-index: 1; }

@media (max-width: 900px) {
  /* at one column the copy spans the full width, so there is nowhere for the
     motif to sit without falling behind text */
  :is(.skp-inverse, .skp-secondary).skp-section::after { content: none; }
}

/* ---- split: copy beside media -------------------------------------------
   04 §8: media takes the larger share, copy is always the LEFT column, and at
   <=900px the media moves after the copy. */
.skp-split {
  display: grid;
  gap: var(--skp-grid-gap);
  align-items: center;
}
@media (min-width: 901px) {
  .skp-split { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); }
}
.skp-split__copy > :last-child { margin-bottom: 0; }
.skp-split__media { margin: 0; }

/* a map is a GRAPHIC, not a brand mark — it carries information and needs to be
   read. Classifying it as a logo rendered it at 94x56 in a field of whitespace. */
.skp-figure--map img,
.skp-section .skp-figure--map img {
  display: block;
  width: 100%;
  max-width: 620px;
  height: auto;
  aspect-ratio: auto;
  max-height: none;
  object-fit: contain;
  margin-inline: auto;
}

/* A marketplace route, not a credit.
   03 §0.3: the Alibaba store is a real buying channel for this business, so the
   mark is sized to be recognised and clicked. It was first shipped at 34px,
   which reads as a footnote. It is a target, so it also carries the 48px floor. */
.skp-proof__actions {
  display: flex;
  align-items: center;
  gap: var(--skp-space-xl);
  flex-wrap: wrap;
  margin-top: var(--skp-space-xl);
}
.skp-proof__mark {
  display: inline-flex;
  align-items: center;
  gap: var(--skp-space-sm);
  min-height: var(--skp-target-min);
  padding: var(--skp-space-sm) var(--skp-space-md);
  border: var(--skp-hairline);
  border-radius: var(--skp-shape-button);
  background: var(--skp-surface);
  text-decoration: none !important;
  transition: border-color var(--skp-motion-fast), box-shadow var(--skp-motion-fast),
              transform var(--skp-motion-fast);
}
.skp-proof__mark img {
  width: auto;
  height: auto;
  max-height: 30px;
  max-width: 132px;
  aspect-ratio: auto;
  object-fit: contain;
}
.skp-proof__mark::after {
  /* Labelled, not a bare badge. Deliberately does NOT claim Trade Assurance or
     escrow terms — those are Tier-B claims about a third party and unconfirmed
     for this store. The platform's presence carries the trust signal on its own. */
  content: "Order via our Alibaba store";
  font: 600 var(--skp-label-medium)/1.3 var(--skp-font);
  color: var(--skp-on-surface);
  white-space: nowrap;
}
.skp-proof__mark:hover {
  border-color: var(--skp-primary);
  box-shadow: var(--skp-elevation-1);
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .skp-proof__mark { transition: none; }
  .skp-proof__mark:hover { transform: none; }
}
.skp-proof__mark:focus-visible { outline: var(--skp-focus-width) solid var(--skp-focus-ring); outline-offset: var(--skp-focus-offset); }

/* ---- capability evidence strip ------------------------------------------
   03 §0.0.4: "we do the job well" needs execution evidence, not adjectives.
   These four claims — vertically integrated, global reach, R&D, customisation —
   sat as 627px of text with nothing to show. The strip puts the lamination line
   and the finished stock directly under the claim they evidence. */
.skp-capability__evidence { margin-top: var(--skp-space-2xl); }

/* ---- actions ------------------------------------------------------------
   01 §2.4: an action needs more air above it than prose does, because it is a
   change of mode — the reader stops reading and decides. Flush against the
   block above, a button reads as that block's last row rather than as a choice.
   Measured live 2026-08-27: `Get a Quote` sat at 0px under a card grid, and the
   Definition CTA at -143px, overlapping the paragraph above it. */
.skp-hero__actions,
.skp-selector__actions,
.skp-proof__actions,
.skp-actions {
  display: flex;
  align-items: center;
  gap: var(--skp-space-md);
  flex-wrap: wrap;
  margin-top: var(--skp-space-xl);              /* body -> action */
}

/* after a heavier block the separation grows, same reasoning as section rhythm */
:is(ul, ol, table, figure, .skp-grid, .skp-duo) + :is(.skp-selector__actions, .skp-proof__actions, .skp-actions) {
  margin-top: var(--skp-space-2xl);             /* block -> action */
}

/* a lone button in its own paragraph — the Definition CTA pattern */
.skp-section p > .skp-btn:only-child { margin-top: var(--skp-space-sm); }
.skp-section p:has(> .skp-btn:only-child) { margin-top: var(--skp-space-xl); }

@media (max-width: 900px) {
  .skp-hero__actions,
  .skp-selector__actions,
  .skp-proof__actions,
  .skp-actions { gap: var(--skp-space-stack); }   /* 12px stacked · 04 §1.7 */
}

/* ---- range · the directory sweep ----------------------------------------
   03 §7.0: the homepage is a directory and routing coverage is its metric.
   Deliberately LOW visual weight — this is a index, not a pitch. Rules between
   groups, no cards, no elevation: it should read as a contents page. */
.skp-range__groups {
  display: grid;
  gap: var(--skp-grid-gap);
}
@media (min-width: 901px) {
  .skp-range__groups { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.skp-range__group { border-top: var(--skp-accent-bar) solid var(--skp-accent); padding-top: var(--skp-space-lg); }
.skp-range__family { margin: 0 0 var(--skp-space-md); font: 600 var(--skp-title-large)/1.25 var(--skp-font); }
.skp-range__family a { color: inherit; text-decoration: none !important; }
.skp-range__family a:hover { color: var(--skp-link-ink); }
.skp-range__list { margin: 0; padding: 0; list-style: none; display: grid; gap: 0; }
.skp-range__list li + li { border-top: var(--skp-hairline); }
.skp-range__list a {
  display: flex;
  align-items: center;
  min-height: var(--skp-target-min);
  padding: var(--skp-space-sm) 0;
  font: 400 var(--skp-body-medium)/1.4 var(--skp-font);
  color: var(--skp-on-surface);
  text-decoration: none !important;
  transition: color var(--skp-motion-fast), padding-left var(--skp-motion-fast);
}
.skp-range__list a:hover { color: var(--skp-link-ink); padding-left: var(--skp-space-sm); }
.skp-range__list a:focus-visible { outline: var(--skp-focus-width) solid var(--skp-focus-ring); outline-offset: var(--skp-focus-offset); }
@media (prefers-reduced-motion: reduce) { .skp-range__list a { transition: none; } }

/* ---- inline CTA ----------------------------------------------------------
   03-PAGE-COMPOSITION §2.0 permits ONE mid-page CTA, and only when it preserves
   a live conversion block. Three pages need it — 500gsm, rolls and oem all
   carry a conversion block before their FAQ.
   It is deliberately quieter than the closing band: a contained panel rather
   than a full-bleed band, no perforation motif, and it never carries the page's
   primary action. If this looks as loud as the closing CTA, it is wrong. */
.skp-cta--inline {
  padding-block: var(--skp-band-rhythm);
}
.skp-cta--inline > .skp-inner {
  padding: var(--skp-card-pad);
  border-radius: var(--skp-shape-card);
  border-left: var(--skp-accent-bar) solid var(--skp-accent);
  background: var(--skp-surface-container);
  color: var(--skp-on-surface);
  text-align: start;                     /* the closing band centres; this does not */
}
.skp-cta--inline .skp-section-title,
.skp-cta--inline h3 {
  margin: 0 0 var(--skp-space-sm);
  font: 600 var(--skp-title-large)/1.25 var(--skp-font);
}
.skp-cta--inline p { margin: 0 0 var(--skp-space-md); }

/* =========================================================================
   REGION 4 — REDUCED MOTION
   04-INTERACTION §7 specifies a global fence. The token layer covers only its
   own classes, so the fence lives here where it can reach the primitives.
   The focus ring is not motion and is deliberately untouched.
   ========================================================================= */

@media (prefers-reduced-motion: reduce) {
  .skp-lift,
  .skp-cue::after,
  .skp-panel:has(.skp-panel__link) { transition: none; }
  .skp-lift:hover,
  .skp-panel:has(.skp-panel__link):hover { transform: none; }
  .skp-cue:hover::after { transform: rotate(45deg) translateY(-0.1em); }
}

/* ===== BEGIN GENERATED COMPONENTS — build_component_library.mjs ===== */
/*  Component blocks. Each is reduced against the primitives above: it contains
    ONLY what those do not already provide. Regenerate with
      node tools/build_component_library.mjs
    Editing between these markers by hand will be overwritten. */

/* ---- Hero -------------------------------------------------------------- */
/* =========================================================================
   HERO
   Spec: 02-COMPONENTS §1 · 03-PAGE-COMPOSITION §1,§5 · 04-INTERACTION §1,§8,
         §9,§10 · 07-BUILD-STANDARDS §2.3,§3.2 · 11-CONTENT-SPEC §1

   REDUCED AGAINST skp-components.css. What is NOT here any more:
     .skp-section          full bleed, width, margin-inline, position:relative
                           and padding-block: var(--skp-hero-rhythm) — the shell
                           ships `.skp-section.skp-hero` itself, so the component
                           declares no rhythm and no escalation for it.
     .skp-inner            the cap and the mobile gutter.
     .skp-eyebrow          label-small, 0.2em tracking, uppercase, bottom margin,
                           and --skp-accent ink.
     .skp-section-lede     body-large, --skp-measure-lede, the muted ink role
                           (which .skp-inverse rebinds for the dark band) and the
                           prose-link colour/underline/focus ring inside a lede.
     --skp-hairline        the chip border and the material eyebrow border, both
                           of which rebind to --skp-outline-inverse on the dark
                           band. Deletes --skp-hero-chip-border and its override.
     .skp-btn / focus      the token layer, in full.
     ink custom properties --skp-hero-ink and --skp-hero-ink-muted are gone: the
                           surface utility supplies the H1's ink and the anatomy
                           supplies the lede's, on BOTH surfaces.

   The base is the LIGHT, two-column use-case hero, because a resource page and a
   tool page both use Hero(application-plain) and carry no tier body class at all.
   Everything else is a delta. There is deliberately no
   `body.skp-tier-application .skp-hero` block: the base IS that tier.

   DEPLOY GATE — NOT AN OVERRIDE. skp-product-tiers.css lines 32-99 and 184-238
   still ship `.skp-tier-* .skp-section.skp-hero` background / ::before / h1 / p /
   .skp-kicker / a.skp-btn-* rules at (0,3,0)-(0,3,1), several with !important.
   They are RETIRED by this component and DELETED in the same deploy. The v3 build
   fought them with four (0,3,2)! escalation selectors; those are removed here,
   because an escalation that duplicates a deletion gate is the arms race
   07-BUILD-STANDARDS §3.2 forbids. The ONE escalation kept is the H1 font-size,
   and only because the rule it beats lives in skp-design-system.css, which is not
   being deleted.
   ========================================================================= */

/* --- Base (light, two-column, use-case density) -------------------------- */

.skp-hero {
  /* Component-local structural values no token covers. */
  --skp-hero-min-height:    auto;
  --skp-hero-title-size:    var(--skp-headline-large);
  --skp-hero-title-lh:      1.08;
  --skp-hero-title-measure: 24ch;
  --skp-hero-rule-width:    52%;

  /* perforation motif — 00-PRINCIPLES §4.4, dark bands only. Motif geometry,
     not a radius, a type size or a width cap; no token covers a mesh pitch. */
  --skp-hero-perf-pitch:    15px;   /* the aperture pitch of 1000D mesh */
  --skp-hero-perf-dot:      1.5px;

  /* the media's top edge lines up with the H1's FIRST line, not with the eyebrow
     above it — resolved by the :has() rule below */
  --skp-hero-lead-offset:   0px;

  /* position:relative comes from .skp-section in the shell. isolation keeps the
     backdrop's negative z-index inside this section; overflow clips the
     full-bleed backdrop HERE, never on <body> (04-INTERACTION §10). */
  isolation: isolate;
  overflow: hidden;
  min-height: var(--skp-hero-min-height);
}

/* Top rule. Colour, ink and thickness all come from the alias layer:
     use-case  --skp-accent = primary,  --skp-accent-bar = 3px
     hub       --skp-accent = tertiary, --skp-accent-bar = 4px
     .skp-inverse rebinds --skp-accent to --skp-inverse-primary on the section
     itself, which is what makes the material hero's rule yellow even on the
     /products/ directory where no tier body class exists.
   The fade is a MASK, not a colour stop — no transparent-black fringe, and no
   literal colour anywhere. */
.skp-hero::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: 2;
  width: var(--skp-hero-rule-width);
  height: var(--skp-accent-bar);
  background: var(--skp-accent);
  color: var(--skp-on-accent);   /* the mandatory paired on-colour, and the mask's currentColor */
  -webkit-mask-image: linear-gradient(90deg, currentColor 0%, transparent 100%);
          mask-image: linear-gradient(90deg, currentColor 0%, transparent 100%);
  pointer-events: none;
}

/* Gradient wash, painted BEHIND the content but ABOVE the surface utility's flat
   background, so .skp-surface keeps ownership of the background/ink pair. */
.skp-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: linear-gradient(135deg,
      color-mix(in srgb, var(--skp-primary) 10%, transparent) 0%,
      color-mix(in srgb, var(--skp-secondary) 6%, transparent) 100%);
  pointer-events: none;
}

/* --- Parts --------------------------------------------------------------- */

/* Cap and gutter belong to the shell — only the split grid here. The column gap
   is --skp-space-2xl, not --skp-grid-gap: the alias is the CARD grid gap, and
   01-FOUNDATIONS §5 names 44px as the space between major groups inside a
   section, which is what a hero split is. Not .skp-grid: that primitive is the
   pinned CARD grid, and the hero is a 1fr / 1.15fr editorial split. */
.skp-hero > .skp-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  align-items: start;                    /* 04-INTERACTION §8 */
  column-gap: var(--skp-space-2xl);
  row-gap: var(--skp-space-xl);
}

.skp-hero__copy { order: 1; min-width: 0; }

.skp-hero__media {
  order: 2;
  min-width: 0;
  margin: var(--skp-hero-lead-offset) 0 0;
}

.skp-hero .skp-hero__media > .skp-hero__img {
  display: block;
  width: 100%;
  max-width: 100%;                       /* 04-INTERACTION §10 overflow guard */
  height: auto;
  aspect-ratio: 3 / 2;                   /* 04-INTERACTION §9 — hero is 3/2 */
  object-fit: cover;
  border-radius: var(--skp-shape-media);
  box-shadow: var(--skp-elevation-media); /* the one sanctioned non-interactive elevation */
}

.skp-hero .skp-hero__caption {
  margin: var(--skp-space-md) 0 0;
  font: 400 var(--skp-body-small)/1.5 var(--skp-font);
  color: var(--skp-on-surface-variant);
}

/* Baseline share: the eyebrow pill pushes the H1 down by its own line box, plus
   its block padding, plus its bottom margin. Push the media down by exactly the
   same amount so the H1's first line and the image's top edge meet within the
   2px gate. Every term reads the token the pill and .skp-eyebrow actually use,
   so the offset self-corrects. The light pill has NO border, so there is no
   border term — the material variant's outlined pill has one, and that variant
   has no media column.
   Scoped to --application: it is the only variant with a media column.
   Browsers without :has() get 0 — the pre-system behaviour, not a break. */
.skp-hero--application > .skp-inner:has(.skp-eyebrow) {
  --skp-hero-lead-offset: calc(
      (var(--skp-label-small) * 1.2)   /* eyebrow line box, from .skp-eyebrow  */
    + (var(--skp-space-sm) * 2)        /* eyebrow padding-block, from the pill */
    +  var(--skp-space-md)             /* eyebrow margin-bottom, .skp-eyebrow  */
  );
}

/* Eyebrow: PILL CHROME ONLY. Type, tracking, uppercase and bottom margin all
   come from .skp-eyebrow. shape-chip, NOT shape-full — 01-FOUNDATIONS §3.1, a
   pill that is not clickable is a bug; 02-COMPONENTS §1 means the shape language
   of a small label, not the 999px action radius. Background and ink are set in
   the SAME rule, always (07-BUILD-STANDARDS §3.4). */
.skp-hero .skp-hero__eyebrow {
  display: inline-flex;
  align-items: center;
  padding-block: var(--skp-space-sm);
  /* 0.2em of .skp-eyebrow tracking hangs off the last letter; trim it back so
     the pill looks optically symmetrical */
  padding-inline: var(--skp-space-md) calc(var(--skp-space-md) - 0.2em);
  border-radius: var(--skp-shape-chip);
  background: var(--skp-primary-container);
  color: var(--skp-on-primary-container);   /* 7.05:1 measured */
}

/* H1. No colour is declared: .skp-surface sets --skp-on-surface and .skp-inverse
   sets --skp-inverse-on-surface plus its `:is(h1,…)` descendant rule, so the ink
   is correct on both surfaces without a component property. The type ROLE is
   tier-resolved through --skp-hero-title-size rather than taken from a
   .skp-display-large / .skp-headline-large utility, because the role differs by
   TIER and no single class on the markup can express that. */
.skp-hero .skp-hero__title {
  margin: 0 0 0.5em;                     /* 01-FOUNDATIONS §2.4 heading -> body */
  max-width: var(--skp-hero-title-measure);
  font-family: var(--skp-font);
  font-weight: 700;
  line-height: var(--skp-hero-title-lh);
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* THE ONE SURVIVING ESCALATION, and the arithmetic is counted, not guessed
   (07-BUILD-STANDARDS §3.2). skp-design-system.css:105 ships
   `body .skp-hero h1 { font-size: var(--skp-h1) !important }` at (0,1,2)!, and
   that sheet is the legacy h1/h2/h3 ramp owner — it is NOT being deleted. This
   selector is (0,3,2) — html + body + three classes — so the tokenised scale
   wins the important-vs-important contest on every tier. Revisit when
   skp-design-system.css drops its blanket h1 ramp. */
html body .skp-section.skp-hero .skp-hero__title {
  font-size: var(--skp-hero-title-size) !important;
}

/* Lede. .skp-section-lede supplies body-large, the 62ch measure, the muted ink
   role and the prose-link treatment. Two declarations are genuinely the hero's:
   the anatomy lede is indented to share a section TITLE's accent-bar edge, and
   the hero has no section title, so the indent would hang the lede 17px right of
   its own H1; and 11-CONTENT-SPEC §1 requires TWO lede paragraphs, which need a
   paragraph gap rather than the anatomy's section gap. */
.skp-hero .skp-hero__lede {
  padding-inline-start: 0;
  margin-block-end: var(--skp-space-md);
}
.skp-hero .skp-hero__lede:last-of-type { margin-block-end: 0; }

/* Spec chips. Static, so shape-chip, --skp-hairline and no elevation — a shadow
   on something you cannot click is noise. Use-case heroes only; 02-COMPONENTS §1
   gives the hub no chip row. Type is the .skp-label-medium utility on the <li>;
   the one override is tracking — a chip carrying "3.2 m (126 in) roll width" is
   a compact data token, not a section kicker, so 0.02em not 0.08em. */
.skp-hero .skp-hero__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--skp-space-sm);
  margin: var(--skp-space-lg) 0 0;
  padding: 0;
  list-style: none;
}

.skp-hero .skp-hero__chip {
  display: inline-flex;
  align-items: center;
  margin: 0;
  padding: var(--skp-space-sm) var(--skp-space-md);
  border: var(--skp-hairline);
  border-radius: var(--skp-shape-chip);
  letter-spacing: 0.02em;
  background: var(--skp-surface);
  color: var(--skp-on-surface);          /* the paired ink, same rule, always */
}

/* Actions. 1 primary + 1 secondary maximum — 04-INTERACTION §1.5. This rule is
   the WHOLE of the hero's button CSS. Geometry, palette, hover, active,
   disabled, focus ring and mobile full-width all live in skp-theme-tokens.css
   and resolve correctly from the section's surface utility. */
.skp-hero .skp-hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--skp-space-md);              /* 14px, 04-INTERACTION §1.3 */
  margin-block-start: var(--skp-space-xl);
}

/* Full-bleed photographic backdrop (material only). Guarded so a stray backdrop
   on a light hero can never put a photograph behind dark text. */
.skp-hero:not(.skp-hero--material) .skp-hero__backdrop { display: none; }

.skp-hero__backdrop {
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
}

.skp-hero__backdrop-img {
  display: block;
  width: 100%;
  max-width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* --- Variant: material (dark, scrimmed photography, single column) -------- */
/* Surface only. The top rule's colour and thickness are not restated:
   .skp-inverse on the same element rebinds --skp-accent to --skp-inverse-primary
   and --skp-on-accent to --skp-on-tertiary, and --skp-accent-bar comes from the
   tier. Buttons, focus ring, H1 ink and lede ink are likewise rebound by
   .skp-inverse in the token layer, so none of them appears in this block. */

.skp-hero--material {
  --skp-hero-min-height: min(72vh, 720px);
  display: flex;
  align-items: center;                   /* the copy sits centred in the 720px band */
}

.skp-hero--material > .skp-inner { grid-template-columns: minmax(0, 1fr); }

/* The backdrop carries the surface, so the light wash is switched off. */
.skp-hero--material::after { content: none; }

/* Outlined pill instead of filled. --skp-accent is already --skp-inverse-primary
   here and --skp-hairline is already --skp-outline-inverse — both rebound by
   .skp-inverse, so neither value is written twice. */
.skp-hero--material .skp-hero__eyebrow {
  background: transparent;
  color: var(--skp-accent);
  border: var(--skp-hairline);
}

/* Scrim + radial primary wash, weighted LEFT where the copy is, so the copy
   column sits on near-solid secondary regardless of what the photograph does.
   Measured worst case (a blown-out white sky under the copy): white 11.1:1,
   inverse-primary 7.4:1 — both clear 4.5:1. */
.skp-hero--material .skp-hero__backdrop::before {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--skp-scrim);
  background-image:
    linear-gradient(90deg,
      color-mix(in srgb, var(--skp-secondary) 88%, transparent) 0%,
      color-mix(in srgb, var(--skp-secondary) 46%, transparent) 46%,
      transparent 82%),
    radial-gradient(90% 120% at 8% 0%,
      color-mix(in srgb, var(--skp-primary) 46%, transparent) 0%,
      transparent 64%);
  color: var(--skp-inverse-on-surface);   /* the scrim's paired ink */
  pointer-events: none;
}

/* Perforation motif — a radial-gradient dot tiled by background-size, masked so
   it only ever appears to the RIGHT of the copy column. 00-PRINCIPLES §4.4: dark
   bands only, very low opacity, never behind text that must be read. Gated on
   @supports so a browser without mask-image gets no dots at all rather than dots
   across the headline. */
@supports (mask-image: linear-gradient(currentColor, transparent)) {
  .skp-hero--material .skp-hero__backdrop::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: radial-gradient(circle at center,
        var(--skp-inverse-primary) var(--skp-hero-perf-dot),
        transparent calc(var(--skp-hero-perf-dot) + 0.5px));
    background-size: var(--skp-hero-perf-pitch) var(--skp-hero-perf-pitch);
    color: var(--skp-inverse-on-surface);
    opacity: 0.10;
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 58%, currentColor 86%);
            mask-image: linear-gradient(90deg, transparent 0%, transparent 58%, currentColor 86%);
    pointer-events: none;
  }
}

/* --- Variant: application (light, two column) ---------------------------- */
/* The base IS this variant — declaring the same grid under the modifier would be
   duplication, not an override. The class exists so the markup names its variant
   and so the :has() baseline rule has a hook. */

/* --- Variant: application-plain (light, one column, no photograph) -------- */

.skp-hero--application-plain > .skp-inner { grid-template-columns: minmax(0, 1fr); }

/* With no photograph the wash has to carry the band on its own. */
.skp-hero--application-plain::after {
  background-image:
    radial-gradient(80% 130% at 88% 0%,
      color-mix(in srgb, var(--skp-primary) 16%, transparent) 0%,
      transparent 62%),
    linear-gradient(135deg,
      color-mix(in srgb, var(--skp-primary) 12%, transparent) 0%,
      color-mix(in srgb, var(--skp-secondary) 8%, transparent) 100%);
}

/* --- Tier delta (type and measure only) ---------------------------------- */
/* Four declarations. Section rhythm, accent colour, accent-bar thickness, card
   padding and grid gap are all resolved by the alias layer and are NOT here.
   What is left is the H1 type role (which differs by tier per 01-FOUNDATIONS
   §2.2 and has no alias), its leading and measure, and the top rule's fade
   length (52% / 62%, stated per tier in 02-COMPONENTS §1, likewise unaliased).
   Split this way, a --material hero on a page with no tier class (the /products/
   directory, 03-PAGE-COMPOSITION §7) still renders correctly on surface: dark
   ink, yellow rule, yellow buttons, yellow focus ring — all from .skp-inverse.
   Only the type ramp and the 4px bar degrade to use-case values. */

body.skp-tier-material .skp-hero {
  --skp-hero-title-size:    var(--skp-display-large);
  --skp-hero-title-lh:      1.04;
  --skp-hero-title-measure: 19ch;
  --skp-hero-rule-width:    62%;
}

/* --- Responsive (<=900px — the breakpoint that matters) ------------------ */

@media (max-width: 900px) {
  .skp-hero > .skp-inner {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--skp-space-xl);
  }

  /* media after copy: read what it is, then see it */
  .skp-hero__copy  { order: 1; }
  .skp-hero__media { order: 2; margin-block-start: 0; }

  /* the baseline offset only means anything in two columns */
  .skp-hero--application > .skp-inner:has(.skp-eyebrow) { --skp-hero-lead-offset: 0px; }

  /* height goes auto; the flex centring is pointless at one column */
  .skp-hero--material { display: block; --skp-hero-min-height: auto; }

  /* the copy now spans the full width, so the left-weighted scrim ramp would
     leave the right-hand words on bare photograph — go flat instead */
  .skp-hero--material .skp-hero__backdrop::before {
    background-image: linear-gradient(180deg,
      color-mix(in srgb, var(--skp-secondary) 78%, transparent) 0%,
      color-mix(in srgb, var(--skp-secondary) 86%, transparent) 100%);
  }

  /* and the motif has nowhere left to sit that is not behind text */
  .skp-hero--material .skp-hero__backdrop::after { display: none; }

  /* Stacking and spacing only. The 100% width of each button comes from the
     token layer's `@media (max-width:900px) { .skp-btn { width: 100% } }`;
     align-items:stretch is belt-and-braces so the stack still fills if that rule
     is ever RUCSS-stripped. */
  .skp-hero .skp-hero__actions {
    flex-direction: column;
    align-items: stretch;
    gap: calc(var(--skp-space-sm) + var(--skp-space-xs));  /* 12px, 04-INTERACTION §1.7 */
  }

  .skp-hero .skp-hero__title { max-width: 100%; }
}

/* --- Reduced motion ------------------------------------------------------ */
/* None needed. This component declares no transition and no transform anywhere;
   the token layer carries the guard for .skp-btn and .skp-state, and
   skp-components.css REGION 4 carries the fence for the primitives. A guard here
   would be a rule with nothing to disable. */
/* ---- Lineage ----------------------------------------------------------- */
/* =========================================================================
   LINEAGE
   Spec: 02-COMPONENTS §2 · 03-PAGE-COMPOSITION §1 (navigation signature) ·
         04-INTERACTION §2,§5,§5.1,§6 · 11-CONTENT-SPEC §2

   WRITTEN AGAINST THE MARKUP THAT ACTUALLY SHIPS.
   skp-product-tiers.php lines 115-122 emit a bare <nav class="skp-lineage">:
   no .skp-section, no .skp-inner, no surface utility, no `skp-state` on the
   anchor and no chevron. The v4 draft styled the clean markup instead, which
   means it would have rendered NOTHING on the live pages. Every rule below
   tagged INJECTOR GAP exists only because the injector does not emit something,
   and is DELETED — not rewritten — when it does.

   Selector base is `nav.skp-lineage`, (0,1,1). That beats the legacy (0,1,0)
   `.skp-lineage` block in skp-product-tiers.css lines 248-298 on specificity as
   well as on source order, so a phased deploy cannot leave behind the 999px pill
   radius, the 0.92rem font ramp, the baseline alignment or the double gutter.
   Deleting that block is still the gate; this just means the gate failing is a
   regression rather than a broken page.

   WHAT THE PRIMITIVES TOOK: the band rhythm is --skp-band-rhythm (was a
   hand-written 8px), both dividers are --skp-hairline (was `1px solid
   var(--skp-outline-variant)` twice), the accent edge is --skp-accent /
   --skp-accent-bar (was a body.skp-tier-application block that changed nothing a
   tier-free rule could not, and left an untiered render with no edge at all).
   ========================================================================= */

/* --- Band ---------------------------------------------------------------- */

nav.skp-lineage {
  box-sizing: border-box;
  width: 100vw;
  max-width: 100vw;
  margin-inline: calc(50% - 50vw);
  /* The same token the shell would apply through `.skp-section.skp-lineage`.
     Declared here because the injected markup has no .skp-section; when it does,
     this line is a harmless duplicate of the shell and can go. */
  padding-block: var(--skp-band-rhythm);
  /* ZERO, deliberately. The gutter belongs to .skp-inner, never to the band. The
     legacy rule put `padding: 14px var(--skp-gutter)` HERE, which applies the
     gutter twice the moment an inner exists — the double cap that widened
     industrial-pvc-curtains from 1354 to 1400. Declaring both padding longhands
     also fully overrides that shorthand. */
  padding-inline: 0;
  /* INJECTOR GAP: no .skp-surface class ships, so the pair is set here — 02
     §2 specifies `surface` on an outline-variant bottom rule. Background and ink
     in the same rule, always. Delete when the injector emits the utility. */
  background: var(--skp-surface);
  color: var(--skp-on-surface);
  border-block-end: var(--skp-hairline);
}

/* INJECTOR GAP: with no .skp-inner there is no capped wrapper, so the band
   carries the cap itself. var(--skp-gutter) reproduces the .skp-inner content
   edge exactly at every width — 43.2px at 1440 (matching min(94%,1600px) of
   100vw), 160px at 1920, and the 18px floor at 390 — so the chip's left edge
   lines up with the hero H1 below it. Scoped with :not(.skp-section) so that the
   moment the injector adds that class the shell's padding-inline: 0 governs and
   this rule stops matching. Plain CSS3, no :has(), nothing for RUCSS to choke
   on. Delete with the injector fix. */
nav.skp-lineage:not(.skp-section) { padding-inline: var(--skp-gutter); }

/* The flex row, written for BOTH markups: today the nav itself is the row; once
   .skp-inner is emitted, the inner is the row and the nav is a one-child flex
   container, which is inert. One rule covers the migration in both directions. */
nav.skp-lineage,
nav.skp-lineage > .skp-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--skp-space-md);
}

/* --- Parts --------------------------------------------------------------- */

/* :is(:link, :visited) carries the pair, so the theme's :visited recolour cannot
   repaint a breadcrumb after one click.

   The accent edge REPLACES the 1px border on that side, so the inline padding is
   trimmed by the difference — expressed as a calc against the same token, so a
   4px bar would compensate itself. --skp-outline is the INTERACTIVE border role
   (01-FOUNDATIONS §1.4, which names a chip); outline-variant is the divider role
   and is used on the band's bottom rule, not here — which is also why the border
   is NOT --skp-hairline. shape-chip, not shape-full: a 999px pill in a breadcrumb
   reads as a button it is not. */
nav.skp-lineage .skp-lineage__material:is(:link, :visited) {
  display: inline-flex;
  align-items: center;
  gap: var(--skp-space-sm);
  /* 48px tall despite being visually small — 04-INTERACTION §6 calls Lineage out
     by name. The buyer may be on a factory floor wearing a work glove. */
  min-height: var(--skp-target-min);
  padding-block: var(--skp-space-sm);
  padding-inline: calc(var(--skp-space-md) - var(--skp-accent-bar) + 1px) var(--skp-space-md);
  border: 1px solid var(--skp-outline);
  border-inline-start: var(--skp-accent-bar) solid var(--skp-accent);
  border-radius: var(--skp-shape-chip);
  background: var(--skp-surface-container);
  color: var(--skp-on-surface);          /* 9.46:1 on surface-container, measured */
  text-decoration: none !important;      /* Astra's a:not(...) outranks any component selector — 07 §3.3 */
  transition: transform var(--skp-motion-standard),
              box-shadow var(--skp-motion-standard),
              border-color var(--skp-motion-fast),
              background-color var(--skp-motion-fast);
}

/* 04-INTERACTION §2: the card/chip underline reset is not optional and applies to
   ALL descendants, not just spans — a future <em> or <b> would render underlined. */
nav.skp-lineage .skp-lineage__material * { text-decoration: none; }

/* The `<` affordance 02-COMPONENTS §2 specifies, drawn as a rotated border box
   rather than a glyph: the injector emits no chevron span, and generated TEXT
   content is announced by some screen readers as "less than". A border box has no
   text to announce. Colour is --skp-accent so the chevron and the edge it points
   away from are one colour: 6.64:1 on surface-container, measured, against a 3.0
   non-text floor. The nudge is ~2px along the rotated axis (04-INTERACTION §7:
   translate, never scale). */
nav.skp-lineage .skp-lineage__material::before {
  content: "";
  flex: 0 0 auto;
  width: 0.42em;
  height: 0.42em;
  border-inline-start: 2px solid var(--skp-accent);
  border-block-end: 2px solid var(--skp-accent);
  transform: rotate(45deg);
  transition: transform var(--skp-motion-fast);
}

/* INJECTOR GAP: the spans carry no .skp-label-small, so the type role is stated
   here. When the injector emits the utility class, this rule reduces to the
   optical tracking trim and the colour. */
nav.skp-lineage .skp-lineage__eyebrow {
  font: 700 var(--skp-label-small)/1.2 var(--skp-font);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  /* trim the trailing tracking so the gap to the name is optically even */
  margin-inline-end: -0.2em;
  padding-inline-end: 0.2em;
  color: var(--skp-on-surface-variant);  /* #4f5d70, 6.13:1 on surface-container, measured */
}

/* Name and current label stay at the label-small SCALE but keep their own case:
   uppercasing a product name in a breadcrumb costs scan speed and gains nothing.
   INJECTOR GAP for the same reason as the eyebrow. */
nav.skp-lineage .skp-lineage__name {
  font: 700 var(--skp-label-small)/1.2 var(--skp-font);
  letter-spacing: 0.02em;
  color: var(--skp-on-primary-container); /* 8.09:1 on surface-container, measured */
}

nav.skp-lineage .skp-lineage__current {
  font: 600 var(--skp-label-small)/1.2 var(--skp-font);
  letter-spacing: 0.02em;
  color: var(--skp-on-surface);           /* 9.46:1 on surface-container, measured */
}

/* A hairline, drawn as a border rather than a background so no rule in this
   component sets a background without a paired on-colour. */
nav.skp-lineage .skp-lineage__sep {
  flex: 0 0 auto;
  width: var(--skp-space-lg);
  height: 0;
  border-block-start: var(--skp-hairline);
}

/* --- States -------------------------------------------------------------- */

/* INJECTOR GAP, AND THE ONE THAT IS A WCAG FAILURE IF IT IS DROPPED.
   The injected anchor carries no `skp-state`, so `a.skp-state:focus-visible` in
   skp-theme-tokens.css never matches and the chip would have NO focus indicator
   at all. This ring is the token layer's own values, referenced not restated —
   width, colour and offset are all tokens, so it cannot go stale and it is
   correct if the band is ever placed on a dark surface. Delete this rule the day
   the injector emits `skp-state`; do not delete it before. */
nav.skp-lineage .skp-lineage__material:focus-visible {
  outline: var(--skp-focus-width) solid var(--skp-focus-ring);
  outline-offset: var(--skp-focus-offset);
}

/* Hover and focus read identically (04-INTERACTION §1.4). Not .skp-lift: that
   primitive carries a RESTING elevation-card, and a breadcrumb chip that sits
   raised claims more than it is; it also cannot be applied, because the class
   would have to be in markup this component does not own. 2px, translate only. */
nav.skp-lineage .skp-lineage__material:hover,
nav.skp-lineage .skp-lineage__material:focus-visible {
  transform: translateY(-2px);
  box-shadow: var(--skp-elevation-card);
  border-color: var(--skp-accent);       /* all four edges join the accent edge */
  background: var(--skp-surface-container-high);
  color: var(--skp-on-surface);          /* the paired ink, set in the same rule */
}

nav.skp-lineage .skp-lineage__material:hover::before,
nav.skp-lineage .skp-lineage__material:focus-visible::before {
  transform: rotate(45deg) translate(0.1em, 0.1em);   /* ~2px, toward the parent */
}

nav.skp-lineage .skp-lineage__material:active {
  transform: translateY(0);
  box-shadow: var(--skp-elevation-0);
}

/* --- Variants ------------------------------------------------------------ */
/* None. 03-PAGE-COMPOSITION §5 lists no Lineage variant, and a variant that is
   not on that list does not exist. A second form goes into 02-COMPONENTS first
   (10-GOVERNANCE §2), not here. */

/* --- Tier delta (structural only) ---------------------------------------- */

/* The one tier block, and it is structure rather than a value: Lineage is the
   use-case tier's navigation signature. A hub has no parent to route to, so if
   the injector ever mis-fires the band must not render at all rather than render
   pointing at itself. (0,2,2) beats both the legacy `.skp-lineage { display:flex }`
   and this component's own row rule. The old accent-edge tier block is gone —
   the alias layer resolves it at :root. */
body.skp-tier-material nav.skp-lineage { display: none; }

/* --- Responsive (<=900px) ------------------------------------------------ */

@media (max-width: 900px) {
  nav.skp-lineage,
  nav.skp-lineage > .skp-inner { gap: var(--skp-space-sm); }

  /* the separator goes first: on a narrow band it wraps onto its own line and
     reads as a stray rule */
  nav.skp-lineage .skp-lineage__sep { display: none; }

  /* the eyebrow duplicates what the chevron and the chip already say, and the
     product name is what the buyer is actually tapping toward. The chip's
     accessible name stays "Material PVC Mesh" only if the eyebrow is hidden with
     display:none — which removes it from the accessible name too, leaving
     "PVC Mesh". That is the intended announcement. */
  nav.skp-lineage .skp-lineage__eyebrow { display: none; }

  /* let a long title truncate rather than push the band two rows tall and shove
     the hero down the page. min-width:0 is what lets a flex item shrink below
     its content width — without it this overflows horizontally. */
  nav.skp-lineage .skp-lineage__current {
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* --- Reduced motion ------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  nav.skp-lineage .skp-lineage__material,
  nav.skp-lineage .skp-lineage__material::before { transition: none; }
  nav.skp-lineage .skp-lineage__material:hover,
  nav.skp-lineage .skp-lineage__material:focus-visible,
  nav.skp-lineage .skp-lineage__material:active { transform: none; }
  nav.skp-lineage .skp-lineage__material:hover::before,
  nav.skp-lineage .skp-lineage__material:focus-visible::before { transform: rotate(45deg); }
  /* the focus ring is not motion and stays */
}
/* ---- Stats ------------------------------------------------------------- */
/* =========================================================================
   STATS  ·  a credibility band of hard numbers
   Structure 02 §3 · copy 11 §3 · outline rule 03 §4
   REDUCED against site/mu-plugins/skp-components.css (2026-08-25).

   DELETED — the shell and the primitives now own all of this:
     --skp-stats-rhythm and `body .skp-section.skp-stats { padding-block }`
        -> `.skp-section.skp-stats { padding-block: var(--skp-band-rhythm) }`
           in the shell. The hub rhythm line inside the tier block goes with
           it: --skp-band-rhythm already resolves 28-44px / 36-56px.
     --skp-stats-divider + the ENTIRE `.skp-stats.skp-inverse` surface delta
        -> --skp-hairline is rebound to outline-inverse by .skp-inverse.
     --skp-stats-label-ink and the hand-written light grey
        -> .skp-inverse rebinds --skp-on-surface-variant to
           --skp-inverse-on-surface-variant (10.96:1). Reference the ONE role.
     --skp-stats-item-pad -> --skp-card-pad is referenced directly; it already
           inherits 22px / 30px from the tier alias layer.
     the 901-1099px padding-compression media query -> 02 §3 gives this band
           its own mobile spec (2x2 at 900), and --skp-card-pad is already
           modest at 1100. One less breakpoint to keep in sync.

   WHAT SURVIVES, AND WHY IT HAS TO
   1. The value's TYPE ROLE is a real tier fact with no token behind it:
      headline-large on a use-case page, display-medium on a hub (01 §2.1,
      02 §3). It cannot be a `.skp-display-medium` class in the HTML, because
      `.skp-product-page p` is (0,1,1) and beats a bare utility at (0,1,0) —
      the value would silently render at var(--skp-body). Same reason the
      label is not `.skp-label-small`. Every part selector is therefore
      qualified with .skp-stats to reach (0,2,0).
   2. The equal-column divider row and the 2x2 mobile drop ARE this
      component. .skp-grid is a card grid with pinned columns; Stats is a
      count-sized row separated by rules, not boxes.

   NOT INTERACTIVE. No hover, no elevation, no focus target, nothing to
   animate — "elevation signals interactivity, never decoration" (01 §4).
   ========================================================================= */

/* Only the tier's type role is declared here. Colour, padding, rhythm and
   divider all arrive from tokens that already resolve per tier AND per
   surface. */
.skp-stats {
  --skp-stats-value-size:  var(--skp-headline-large);
  --skp-stats-value-lh:    1.08;      /* 01 §2.1 headline-large */
  --skp-stats-value-track: -0.015em;  /* 01 §2.1 headline-large */
}
body.skp-tier-material .skp-stats {
  --skp-stats-value-size:  var(--skp-display-medium);
  --skp-stats-value-lh:    1.06;      /* 01 §2.1 display-medium */
  --skp-stats-value-track: -0.02em;   /* 01 §2.1 display-medium */
}

/* One row, equal columns, sized from the item count. auto-fit would orphan
   the last tile; grid-auto-flow:column cannot. */
.skp-stats .skp-stats__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: start;
}

.skp-stats .skp-stats__item {
  min-width: 0;                       /* defeats the grid min-content floor */
  padding-inline: var(--skp-card-pad);
}

/* Dividers BETWEEN, never boxes AROUND. --skp-hairline is outline-variant on
   a light band and outline-inverse on the dark one, with no rule here. */
.skp-stats .skp-stats__item + .skp-stats__item { border-inline-start: var(--skp-hairline); }
.skp-stats .skp-stats__item:first-child { padding-inline-start: 0; }
.skp-stats .skp-stats__item:last-child  { padding-inline-end: 0; }

/* label-small, restated rather than classed — see note 1 in the header. */
.skp-stats .skp-stats__label {
  margin: 0 0 var(--skp-space-xs);
  font: 700 var(--skp-label-small)/1.2 var(--skp-font);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--skp-on-surface-variant);
  text-align: left;
}

/* var(--skp-accent) is already correct everywhere this band is allowed to
   sit: primary on surface-container, inverse-primary on the hub band. */
.skp-stats .skp-stats__value {
  margin: 0 0 var(--skp-space-sm);
  font-family: var(--skp-font);
  font-weight: 700;
  font-size: var(--skp-stats-value-size);
  line-height: var(--skp-stats-value-lh);
  letter-spacing: var(--skp-stats-value-track);
  font-variant-numeric: tabular-nums;
  color: var(--skp-accent);
  text-align: left;
}

.skp-stats .skp-stats__caption {
  margin: 0;
  font: 400 var(--skp-body-small)/1.6 var(--skp-font);
  color: var(--skp-on-surface-variant);
  text-align: left;
}

/* --- Responsive: the 2x2 drop, which is the component's own behaviour --- */

/* <=900px: 2x2, dividers become horizontal rules (02 §3 Mobile). The desktop
   first/last-child rules are (0,3,0) and live outside any media query, so
   they are reset here at matching specificity rather than left to leak. */
@media (max-width: 900px) {
  .skp-stats .skp-stats__list {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--skp-grid-gap);
  }
  .skp-stats .skp-stats__item,
  .skp-stats .skp-stats__item + .skp-stats__item,
  .skp-stats .skp-stats__item:first-child,
  .skp-stats .skp-stats__item:last-child {
    padding-inline: 0;
    padding-block: var(--skp-space-lg);
    border-inline-start: 0;
  }
  .skp-stats .skp-stats__item:nth-child(n + 3) { border-block-start: var(--skp-hairline); }
}

/* <=900px, 3-tile sets only: a 2-column grid renders 3 tiles as 2 + 1. Kept
   in its OWN rule so a UA without :has() drops only this and still gets the
   valid 2-up base. */
@media (max-width: 900px) {
  .skp-stats .skp-stats__list:has(> .skp-stats__item:nth-child(3):last-child) {
    grid-template-columns: minmax(0, 1fr);
  }
  .skp-stats .skp-stats__list:has(> .skp-stats__item:nth-child(3):last-child) .skp-stats__item:nth-child(n + 2) {
    border-block-start: var(--skp-hairline);
  }
}

/* <=480px: one column (02 §3 Mobile). */
@media (max-width: 480px) {
  .skp-stats .skp-stats__list { grid-template-columns: minmax(0, 1fr); }
  .skp-stats .skp-stats__item:nth-child(n + 2) { border-block-start: var(--skp-hairline); }
}
/* ---- Definition -------------------------------------------------------- */
/* =========================================================================
   DEFINITION  ·  what the product IS, in plain prose
   Structure 02 §4 · copy 11 §4 · measure 01 §2.3
   REDUCED against site/mu-plugins/skp-components.css (2026-08-25).

   DELETED — now owned by the shell, the primitives or the token layer:
     `body .skp-section.skp-definition { padding-block }` -> the shell's
        `.skp-product-page .skp-section { padding-block: var(--skp-section-rhythm) }`
     `.skp-definition .skp-inner > .skp-section-title { margin-block; text-align }`
        -> .skp-section-title owns its margin, its type and its accent bar.
        The private accent-bar rules are gone with it.
     --skp-definition-measure / --skp-definition-size / --skp-definition-lh
        -> --skp-prose-measure / --skp-prose-size / --skp-prose-leading. That
           deletes the ENTIRE `body.skp-tier-material .skp-definition` block:
           nothing tier-specific is left in this component.
     --skp-definition-link, `.skp-definition:is(.skp-inverse,.skp-secondary)`
        and both the :hover and the :focus-visible link rules
        -> the primitives already carry `:where(… .skp-definition__body …)
           a:not(.skp-btn)` for colour/underline/offset and the matching
           :focus-visible ring, and --skp-link-ink is rebound per surface.
           A component must not own prose-link styling.
     `.skp-definition--prose .skp-definition__body { grid-template-columns }`
        -> the base is already single column; the variant class is a content
           marker, and 03 §5 lists exactly two Definition variants.
     the `box-shadow: var(--skp-elevation-0)` on the figure -> elevation-0 is
        the default; declaring "no shadow" is not a rule.
     the paragraph `color:` declaration -> .skp-surface sets background AND
        on-colour together and it inherits. Restating it is the duplication
        that lets a background and its ink drift apart.

   WHAT SURVIVES: the 60/40 split, the measure enforcement, the asserted
   left alignment, and the media frame. Nothing else.

   SPECIFICITY FLOOR. skp-design-system.css §2 `.skp-product-page p` is
   (0,1,1) and sets var(--skp-body); the prose selectors here are (0,2,1) and
   win, which is what keeps hub prose at body-large instead of body-medium.
   ========================================================================= */

/* --skp-grid-gap is 22px on a use-case page and 30px on a hub, so one
   declaration covers both tiers. `start` rather than `center` so the copy's
   first line and the media's top edge share a baseline in every case. */
.skp-definition .skp-definition__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--skp-grid-gap);
  align-items: start;
}

.skp-definition .skp-definition__prose { min-width: 0; }

/* THE TWO RULES THAT CARRY THE COMPONENT.
   1. Body copy never exceeds its measure, whatever the container width — a
      1600px section licenses a 1600px SECTION, not a 1600px line.
   2. Left-aligned, never centred (02 §4). Asserted rather than inherited,
      because a stray theme or block rule that centres 72ch of technical
      prose makes the page read as a course landing page. */
.skp-definition .skp-definition__prose > p {
  margin: 0 0 1.15em;                              /* body -> next body, 01 §2.4 */
  max-width: var(--skp-prose-measure);             /* 72ch use-case / 62ch hub */
  font: 400 var(--skp-prose-size)/var(--skp-prose-leading) var(--skp-font);
  text-align: left;
}
.skp-definition .skp-definition__prose > p:last-child { margin-block-end: 0; }

/* Definition is prose, but a list occasionally survives a re-parent from the
   old markup. It obeys the same measure so it cannot outrun the paragraphs. */
.skp-definition .skp-definition__prose :is(ul, ol) {
  margin: 0 0 1.15em;
  padding-inline-start: var(--skp-space-lg);
  max-width: var(--skp-prose-measure);
}
.skp-definition .skp-definition__prose li {
  margin-block-end: 0.5em;                         /* 01 §2.4 */
  font: 400 var(--skp-prose-size)/var(--skp-prose-leading) var(--skp-font);
  text-align: left;
}

/* Media. shape-media (28px) because radius is a part of speech: extra-large
   means "this is photography" (01 §3.1). Declared rather than left to
   skp-design-system.css's --skp-radius-media, which is a compat alias marked
   for deletion. */
.skp-definition .skp-definition__media { margin: 0; min-width: 0; }
.skp-definition .skp-definition__media img {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;                                 /* 04 §10: an image without this is a named overflow cause */
  aspect-ratio: 16 / 10;                           /* 02 §4 */
  object-fit: cover;
  border-radius: var(--skp-shape-media);
}

/* Caption aligns to the IMAGE's left edge, never centred (04 §8). On a dark
   band the surface utility colours figcaption itself; --skp-on-surface-variant
   is rebound there too, so this one rule is correct on both. */
.skp-definition .skp-definition__caption {
  margin: var(--skp-space-md) 0 0;
  max-width: var(--skp-measure-body);
  font: 400 var(--skp-body-small)/1.6 var(--skp-font);
  color: var(--skp-on-surface-variant);
  text-align: left;
}

/* --- Variant: prose-media ---
   Prose 60 / media 40 at >=901px (02 §4). STATED DEVIATION from 04 §8's
   `minmax(0,1fr) minmax(0,1.15fr)`: that is the Hero split, where the
   photograph is the argument. Here the prose is the payload and the
   component spec is the more specific rule. Authored mobile-first, so <=900px
   needs no column reset — and the figure is already second in the DOM, so it
   lands after the copy with no `order` property. */
@media (min-width: 901px) {
  .skp-definition--prose-media .skp-definition__body {
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  }
}

/* Nothing in Definition animates and nothing in it is focusable that the
   primitives do not already ring, so there is no states block and no
   reduced-motion block. */
/* ---- Specs ------------------------------------------------------------- */
/* =========================================================================
   SPECS  ·  02-COMPONENTS §5  ·  "the most important component on the site"
   v5 (2026-08-25) — REDUCED against site/mu-plugins/skp-components.css.

   A buyer comparing suppliers is comparing this block, so it is built for
   scanning first and decoration never.

   Variant chooser (03-PAGE-COMPOSITION §5) — a rule, not a judgement:
     .skp-specs--table   >=3 rows AND >=2 value columns   -> <table>
     .skp-specs--chips   <=6 rows, 1 value column         -> <dl> of chips
     .skp-specs--grid    anything else                    -> <dl> of .skp-panel

   ---------------------------------------------------------------------
   WHAT THE PRIMITIVES NOW OWN, AND IS THEREFORE ABSENT FROM THIS FILE

     .skp-section        full bleed, the 100vw break-out and padding-block
                         from --skp-section-rhythm. The (0,3,0) rhythm
                         escalation this component used to carry is GONE:
                         skp-components.css declares
                         `.skp-product-page .skp-section` at the same
                         specificity and wins on source order at priority 30.
     .skp-inner          the min(94%,1600px) cap and the mobile gutter.
     .skp-eyebrow        label-small, 0.2em tracking, uppercase, accent ink,
                         AND the hub light-surface contrast carve-out. The
                         --skp-specs-eyebrow-ink custom property and its tier
                         override are deleted with it.
     .skp-section-title  headline-medium plus the mandatory left accent bar.
     .skp-section-lede   body-large, --skp-measure-lede, muted ink, shared
                         left edge with the title.
     .skp-grid           THE WHOLE GRID MECHANISM. A fixed 3-up template, six
                         :has() column-pinning blocks, the tier 4-up rule and
                         the <=900px single-column reset are all deleted. It
                         also closes the 901-1099px blocker for free: base
                         .skp-grid is 2-up in that band.
     .skp-panel          IS the grid variant's cell. --skp-card-pad, the
                         accent top border, shape-card, the .skp-surface
                         flip, the flex column, and flat-unless-it-links.
     .skp-prose          the note's inline link — colour, underline, offset
                         and focus ring.
     .skp-focusable      the scroll region's focus ring (token layer §focus).
     --skp-hairline      the divider, previously written longhand five times.

   WHAT IS LEFT IS THE TABLE. No primitive covers tables, and the mobile
   data-label + ::before stack is this component's whole reason to exist
   below 900px.

   CHOOSING THE .skp-grid MODIFIER (chips and grid variants) — count the pairs:
     1-2, 4   ->  .skp-grid--2     2-up, never 3+1
     3, 6, 9  ->  .skp-grid        3-up, exact rows
     5        ->  .skp-grid--5up   3+2, never 4+1
     8, 12    ->  .skp-grid--4     4-up on a use-case page, 3-up below 1400px
     7,10,13  ->  .skp-grid        3-up + the stranded-tile span below
   A material hub is capped at 3 per row (03-PAGE-COMPOSITION §1) and never
   takes --4.

   Every contrast figure quoted below is the v4 MEASURED value recorded in
   skp-theme-tokens.css, not an estimate and not carried over from v3.
   ========================================================================= */

/* --- Base --------------------------------------------------------------
   Two literals survive, and only because 02-COMPONENTS §5 states them
   normatively with no token: the row padding, 13/18 on a use-case page and
   15/20 on a hub. :root IS the use-case tier in v4, so the base carries that
   pair and body.skp-tier-material states the delta. Rhythm, gap, card
   padding, accent colour and accent-bar width all resolve through the tier
   alias layer and need no rule at all. ------------------------------------ */

.skp-specs {
  --skp-specs-cell-y: 13px;   /* 02-COMPONENTS §5, use-case row padding */
  --skp-specs-cell-x: 18px;   /* 02-COMPONENTS §5, use-case row padding */
}

/* The data block sits under the section header. Its top margin COLLAPSES with
   .skp-section-lede's margin-bottom, so a section with a lede keeps the lede's
   rhythm and one without still clears the title. (0,2,0) is required to beat
   .skp-grid's own `margin: 0`. */
.skp-specs .skp-specs__frame,
.skp-specs .skp-grid { margin: var(--skp-grid-gap) 0 0; }

/* --- Part · frame (table variant only: clip + scroll container) --------- */

.skp-specs__frame {
  position: relative;
  border: var(--skp-hairline);
  border-radius: var(--skp-shape-card);       /* shape-large, 02-COMP §5 */
  background: var(--skp-surface-container-lowest);
  color: var(--skp-on-surface);               /* paired on-colour, same rule */
  overflow: hidden;                           /* clips the header row corners */
  /* no box-shadow: not interactive, so flat is the default and stating
     elevation-0 was noise */
}

/* --- Part · table ------------------------------------------------------ */

.skp-specs .skp-specs__table {
  width: 100%;
  margin: 0;
  border: 0;
  border-collapse: collapse;
  border-spacing: 0;
  /* (0,2,1) beats skp-entry-content-table-responsive.css's `.entry-content
     table` (0,1,1), which paints scroll-cue gradients on every body table.
     The shorthand also resets its background-size/repeat/attachment. */
  background: transparent;
  text-align: left;
}

.skp-specs__caption {
  /* present for assistive tech, absent visually — the <h2> already names the
     section on screen and a duplicated visible caption reads as a stutter */
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The markup also puts .skp-primary on the <tr>, which is what actually pairs
   the background with its on-colour. This rule is the safety net for the day
   someone drops that class, and it declares both halves together for the same
   reason (01-FOUNDATIONS §1 — a background without its on-colour is a defect). */
.skp-specs .skp-specs__table thead th {
  padding: var(--skp-specs-cell-y) var(--skp-specs-cell-x);
  border: 0;
  background: var(--skp-primary);
  color: var(--skp-on-primary);               /* 7.27:1 measured */
  font: 700 var(--skp-label-small)/1.2 var(--skp-font);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-align: left;
  vertical-align: middle;
}

/* Astra and skp-entry-content-table-responsive.css both put a 1px border on
   every .entry-content cell — "boxes around every cell" is on the don't list
   (02-COMPONENTS §5). Two classes here (0,2,2) beat their (0,1,2), so this
   wins on specificity and needs no !important. Do not weaken the selector. */
.skp-specs .skp-specs__table :is(tbody th, tbody td) {
  padding: var(--skp-specs-cell-y) var(--skp-specs-cell-x);
  border: 0;
  text-align: left;
  vertical-align: top;
  overflow-wrap: break-word;                  /* long part codes cannot overflow */
  font: 400 var(--skp-body-medium)/1.68 var(--skp-font);
  color: var(--skp-on-surface);               /* 10.35:1 measured */
}

/* (0,3,2) — declared after the :is() rule at (0,2,2), so the weight sticks */
.skp-specs .skp-specs__table tbody th[scope="row"] {
  width: 26%;                                 /* label column; the remaining
                                                 74% splits evenly across the
                                                 value columns at the 640px
                                                 scroll floor */
  font-weight: 600;
}

/* zebra — surface-container-low on even rows (02-COMPONENTS §5) */
.skp-specs .skp-specs__table tbody tr:nth-of-type(even) {
  background: var(--skp-surface-container-low);
  color: var(--skp-on-surface);               /* paired on-colour, same rule */
}

/* hairline divider between rows, never a full cell grid */
.skp-specs .skp-specs__table tbody tr + tr :is(th, td) {
  border-top: var(--skp-hairline);
}

/* --- Part · value helpers ---------------------------------------------
   Named .skp-specs__num, not .skp-specs__value--num: a `--` suffix is a
   VARIANT of an existing part, and there is no .skp-specs__value part.

   BOTH selectors are required. The CSS Fonts `font` shorthand resets
   font-variant-numeric to `normal`, so the cell rule above at (0,2,2) — and
   the .skp-title-large utility on a <dd> — silently cancel tabular figures
   set at (0,1,0). The table selector lands at (0,3,1) and the general one at
   (0,2,0), enough to beat each in turn. Columns of figures that do not align
   are the one thing this component exists to get right. ------------------- */

.skp-specs .skp-specs__num,
.skp-specs .skp-specs__table td.skp-specs__num { font-variant-numeric: tabular-nums; }

.skp-specs__unit {
  color: var(--skp-on-surface-variant);       /* 6.70:1 on #ffffff, 6.41:1 on
                                                 the #f8fafc zebra row */
  font-weight: 400;
}

/* --- Part · note -------------------------------------------------------
   The inline link is .skp-prose's job now: colour (--skp-link-ink, rebound
   per surface), underline, 3px offset and the focus ring all arrive from
   skp-components.css. WCAG 2.5.8 exempts a target inside a sentence from the
   48px floor, which is why that one link is not padded to size. Only the type
   and the measure, which no primitive carries at body-small, are set here. -- */

.skp-specs__note {
  margin: var(--skp-space-md) 0 0;
  max-width: var(--skp-measure-body);
  font: 400 var(--skp-body-small)/1.6 var(--skp-font);
  color: var(--skp-on-surface-variant);       /* 6.13:1 on surface-container */
}

/* --- Part · chip (chips variant) ---------------------------------------
   A chip is deliberately NOT a .skp-panel: shape-chip rather than shape-card,
   a LEFT accent rather than a top one, and the tight row padding rather than
   --skp-card-pad. The grid variant's cell IS a panel and uses it directly,
   which is why there is no .skp-specs__cell rule anywhere in this file. ---- */

.skp-specs__chip {
  display: flex;
  flex-direction: column;
  gap: var(--skp-space-xs);
  padding: var(--skp-specs-cell-y) var(--skp-specs-cell-x);
  border: var(--skp-hairline);
  /* written ONCE: --skp-accent-bar / --skp-accent resolve to 3px primary at
     :root and 4px tertiary under body.skp-tier-material (01 §5.2) */
  border-inline-start: var(--skp-accent-bar) solid var(--skp-accent);
  border-radius: var(--skp-shape-chip);       /* NOT shape-full — a pill that
                                                 is not clickable is a bug */
  background: var(--skp-surface-container-lowest);
  color: var(--skp-on-surface);               /* paired on-colour, same rule */
}

/* --- Part · label / value (both <dl> variants) -------------------------
   Weight, leading and tracking come from .skp-label-small / .skp-title-large
   in the markup — a <dt> and a <dd> are not touched by `.skp-product-page p`,
   so the utility wins unopposed and there is nothing to restate. Spacing is
   the flex gap on the chip and on .skp-panel, so both margins are zero. ---- */

.skp-specs__chip-label,
.skp-specs__cell-label { margin: 0; color: var(--skp-on-surface-variant); }

.skp-specs__chip-value,
.skp-specs__cell-value { margin: 0; overflow-wrap: break-word; }  /* zeroes the <dd> indent */

/* --- Grid · the one thing .skp-grid does not cover ---------------------
   Counts of 7 / 10 / 13 strand exactly one tile at 3-up, and a material hub
   is capped at 3 per row (03-PAGE-COMPOSITION §1) so it cannot escape to
   4-up. The final cell spans the row instead, which reads as a deliberate
   wide tile rather than an orphan. Every other count picks a .skp-grid
   MODIFIER in the markup — see the header.
   Scoped to >=1100px, where .skp-grid is 3-up and all three counts strand.
   In the 901-1099 2-up band 10 falls as five clean rows, so the span must
   not fire there. A browser without :has() keeps the plain 3-up, which is
   never broken. ---------------------------------------------------------- */

@media (min-width: 1100px) {
  .skp-specs .skp-grid:has(> :last-child:nth-child(7))  > :last-child,
  .skp-specs .skp-grid:has(> :last-child:nth-child(10)) > :last-child,
  .skp-specs .skp-grid:has(> :last-child:nth-child(13)) > :last-child { grid-column: 1 / -1; }
}

/* --- Variant · table ---------------------------------------------------
   Above the stack breakpoint the frame becomes a scroll container, so a wide
   table degrades to a scroll and never to crushed columns. Paired with
   role="region" + tabindex="0" + an accessible name in the markup so it is
   keyboard reachable (WCAG 2.1.1), and with .skp-focusable so the token
   layer's focus block paints the ring — no focus rule is written here.
   No state tint is layered on the frame: a 10% currentColor wash over a spec
   table drops cell contrast below the 4.5:1 floor, so the ring carries focus
   alone — a deliberate, stated exception to 04-INTERACTION §5. ------------ */

@media (min-width: 901px) {
  .skp-specs--table .skp-specs__frame { overflow: auto; }
  /* 640px is the width below which three columns of body-medium stop being
     readable; at 901px the inner is ~847px, so it does not fire early */
  .skp-specs--table .skp-specs__table { min-width: 640px; }
}

/* --- Surface adaptation (03-PAGE-COMPOSITION §6) ------------------------
   Specs is specified on surface-container. If alternation forces it onto
   plain surface, the inset wells step DOWN a rung instead of vanishing.
   The grid variant's cells need no rule at all: skp-components.css already
   flips `.skp-surface .skp-panel` to the container tint. ------------------ */

.skp-specs.skp-surface :is(.skp-specs__frame, .skp-specs__chip) {
  background: var(--skp-surface-container-low);
  color: var(--skp-on-surface);               /* paired on-colour, same rule */
}

.skp-specs.skp-surface .skp-specs__table tbody tr:nth-of-type(even) {
  background: var(--skp-surface-container);
  color: var(--skp-on-surface);               /* paired on-colour, same rule */
}

/* --- Tier delta --------------------------------------------------------
   ONE block, two lines. Section rhythm, grid gap, card padding, accent
   colour, accent-bar width AND the hub eyebrow's contrast carve-out are all
   resolved by the v4 alias layer and skp-components.css, so none of them
   appears here — restating one would be a no-op today and a drift source
   tomorrow. What remains is the row padding, which 02-COMPONENTS §5 states
   normatively with no token. There is no body.skp-tier-application block at
   all, and no page-id selector anywhere in this file. --------------------- */

body.skp-tier-material .skp-specs {
  --skp-specs-cell-y: 15px;   /* 02-COMPONENTS §5, hub row padding */
  --skp-specs-cell-x: 20px;   /* 02-COMPONENTS §5, hub row padding */
}

/* --- Responsive · 901-1399px (01-FOUNDATIONS §6.3) ----------------------
   THE BAND THIS COMPONENT PREVIOUSLY SKIPPED — the audit blocker.
     901-1099   multi-column grids drop to 2-up. .skp-grid now does that for
                the chips and grid variants, so nothing is restated here.
     both bands spacing compresses. A table has no column count to drop, so
                the band is honoured on the row gutter instead: at an
                847-1315px inner, a three-column table at the 18/20px gutter
                runs into the 640px legibility floor and starts scrolling on
                a viewport wide enough to show it whole.
   Both selectors are listed and this block sits AFTER the tier delta,
   because `body.skp-tier-material .skp-specs` is (0,2,1) and a bare
   `.skp-specs` at (0,1,0) would lose on a hub. Media queries add no
   specificity — equal weight plus later source order is what wins. ------- */

@media (min-width: 901px) and (max-width: 1399px) {
  .skp-specs,
  body.skp-tier-material .skp-specs { --skp-specs-cell-x: var(--skp-space-md); }  /* 14px */
}

/* --- Responsive · <=900px ---------------------------------------------
   THE STACK, and the reason this component still owns CSS at all. This is
   the known live failure mode: hiding <thead> without a data-label on every
   <td> leaves each value with no column identity, which is worse than a
   horizontal scroll, not better (07-BUILD-STANDARDS §4.4).

   These selectors also have to beat skp-entry-content-table-responsive.css,
   which at <=768px forces .entry-content tables to display:block +
   overflow-x:auto and their thead/tbody back to display:table with
   width:max-content; min-width:100%. Two classes here beat its one — but
   width AND min-width must both be reset, or its min-width:100% survives on
   the hidden <thead> and reflates it into horizontal overflow.

   The chips and grid variants need no rule: .skp-grid is single-column below
   901px by default, and the frame's base `overflow: hidden` already holds
   because the scroll rule is >=901px only. -------------------------------- */

@media (max-width: 900px) {

  /* 1. the table stops being a table */
  .skp-specs .skp-specs__table,
  .skp-specs .skp-specs__table tbody {
    display: block;
    width: 100%;
    min-width: 0;
    max-width: 100%;
    overflow: visible;
    margin-left: 0;
    margin-right: 0;
  }

  /* 2. header row visually hidden, still in the accessibility tree */
  .skp-specs .skp-specs__table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    min-width: 0;
    padding: 0;
    margin: -1px;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* 3. each row becomes its own labelled block; the row carries the gutter so
        the ::before label sits flush at the block's own left edge */
  .skp-specs .skp-specs__table tbody tr {
    display: block;
    padding: var(--skp-specs-cell-y) var(--skp-specs-cell-x);
  }

  .skp-specs .skp-specs__table tbody tr + tr { border-top: var(--skp-hairline); }

  /* the desktop cell-level divider would double up inside the stack */
  .skp-specs .skp-specs__table tbody tr + tr :is(th, td) { border-top: 0; }

  /* 4. the row header becomes the block's title, not a label/value pair */
  .skp-specs .skp-specs__table tbody th[scope="row"] {
    display: block;
    width: auto;
    padding: 0 0 var(--skp-space-xs);
    font: 600 var(--skp-title-medium)/1.3 var(--skp-font);
    color: var(--skp-on-surface);
  }

  /* 5. every value regains its column identity from data-label. 46% / 42% and
        the 0.08em tracking are quoted verbatim from the code block in
        07-BUILD-STANDARDS §4.4 — it is NOT .skp-label-small's 0.2em */
  .skp-specs .skp-specs__table tbody td {
    display: block;
    position: relative;
    padding: var(--skp-space-sm) 0 var(--skp-space-sm) 46%;
  }

  .skp-specs .skp-specs__table tbody td::before {
    content: attr(data-label);
    position: absolute;
    left: 0;
    top: var(--skp-space-sm);
    width: 42%;
    font: 700 var(--skp-label-small)/1.2 var(--skp-font);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--skp-on-surface-variant);     /* 6.70:1 on #ffffff */
  }
}

/* --- Responsive · <=480px ---------------------------------------------- */

@media (max-width: 480px) {
  /* a 46% label column leaves too little room for a value at 354px content;
     the label moves above the value instead of beside it */
  .skp-specs .skp-specs__table tbody td { padding: var(--skp-space-sm) 0 0; }
  .skp-specs .skp-specs__table tbody td::before {
    position: static;
    display: block;
    width: auto;
    margin-bottom: var(--skp-space-xs);
  }
}

/* Nothing in this component animates, so it declares no reduced-motion
   fence. The v4 file carried one as a defensive wildcard; the primitives now
   guard their own motion (.skp-lift, .skp-cue, .skp-panel:has(link)) and
   none of them is used here. Re-adding a `.skp-specs *` fence would be dead
   CSS pretending to be a safeguard. */
/* ---- Selector ---------------------------------------------------------- */
/* =========================================================================
   SELECTOR  ·  02-COMPONENTS §6  ·  03-PAGE-COMPOSITION §5
   Routes a buyer between variants of the SAME product. Routing, not
   qualification — qualification is Guidance.
     cards    >= 3 options
     compare   = 2 options

   REDUCED AGAINST skp-components.css (2026-08-25). Deleted from this file,
   because a primitive now owns it:
     · section rhythm escalation pair          -> .skp-section
     · eyebrow / title / lede type + measure   -> shared anatomy
     · the whole grid + four media queries     -> .skp-grid (+ --2/--4/--5up)
     · card base, radius, padding, elevation   -> .skp-panel
     · the accent ::before overlay             -> .skp-panel border-top
     · both surface-adaptation rules           -> .skp-panel surface flip
     · the stretched ::after hit area          -> .skp-panel__link
     · the text-decoration !important reset    -> .skp-panel
     · hover / active / focus / :has() ring    -> .skp-panel:has(...)
     · the \2192 arrow and its transition      -> .skp-cue
   The accent bar is now a REAL border-top on .skp-panel, not an overlay. Do
   not reinstate the overlay: it would double the edge.
   ========================================================================= */

/* Astra ships `.entry-content ul` at (0,1,1), which outranks .skp-grid (0,1,0)
   and would re-indent and re-bullet the option list. Two classes here land at
   (0,2,0) and win with no !important (07-BUILD-STANDARDS §3.2, §3.3). */
.skp-selector .skp-grid { margin: 0; padding: 0; list-style: none; }

/* --- Part · role kicker ---------------------------------------------------
   Names the PURCHASE TYPE, not an adjective (11-CONTENT-SPEC §6). A chip:
   shape-chip, never shape-full — a pill that is not clickable is a bug.
   align-self stops the panel's flex column stretching it to the card width.
   Authored as a <span>, not a <p>, so `.skp-product-page p` (0,1,1) cannot
   beat .skp-label-small (0,1,0) and ship it at body size; a flex item is
   blockified, so it still behaves as a block. */
.skp-selector__role {
  align-self: start;
  padding: var(--skp-space-xs) var(--skp-space-sm);
  border-radius: var(--skp-shape-chip);
  background: var(--skp-surface-container-high);
  color: var(--skp-on-surface-variant);
}

/* Type comes from .skp-title-large / .skp-body-medium in the markup; only the
   theme's heading and paragraph margins are cleared here. */
.skp-selector__title,
.skp-selector__summary { margin: 0; }
.skp-selector__summary,
.skp-selector__tokens  { color: var(--skp-on-surface-variant); }

/* --- Part · cue -----------------------------------------------------------
   .skp-cue supplies the arrow and its motion. Only the seating and the ink
   are component business. margin-block-start:auto pins the cue to the card
   foot so every card in a row ends on the same line.
   Ink is --skp-primary and NEVER --skp-accent: the cue is TEXT, and #FFCD57
   on a white panel measures 1.49:1. */
.skp-selector__cue {
  margin-block-start: auto;
  padding-block-start: var(--skp-space-md);
  color: var(--skp-primary);
}
/* .skp-panel__link::after covers the whole card, so the pointer is never over
   the cue span and .skp-cue:hover can never fire. The card drives the arrow. */
.skp-panel:hover .skp-selector__cue::after {
  transform: rotate(45deg) translate(0.12em, -0.12em);
}

/* --- Escape hatch · 7 against a 3-column maximum -------------------------
   The only count .skp-grid cannot pin evenly (3+3+1). The last card becomes a
   band rather than an orphan tile. Never combine with .skp-grid--4. */
.skp-selector__wide { grid-column: 1 / -1; }

/* --- Variant · compare (exactly 2) --------------------------------------
   Two halves fused into ONE panel. Half a card lifting out of its own
   container is a visual bug, so the container takes the elevation and the
   halves give theirs up. Corners are applied per half rather than with
   overflow:hidden, which would clip the panel's focus ring.
   The :has() selectors are (0,3,0)/(0,4,0) on purpose: .skp-panel's own
   elevation and hover rules are (0,2,0)/(0,3,0), so a plain descendant
   selector would tie and depend on source order. */
.skp-selector--compare .skp-grid {
  gap: 0;
  border-radius: var(--skp-shape-card);
  box-shadow: var(--skp-elevation-card);
  transition: box-shadow var(--skp-motion-standard);
}
.skp-selector--compare .skp-grid:has(.skp-panel:hover) {
  box-shadow: var(--skp-elevation-card-hover);
}
.skp-selector--compare .skp-panel:has(.skp-panel__link) {
  border-radius: 0;
  box-shadow: none;
}
.skp-selector--compare .skp-panel:has(.skp-panel__link):hover {
  transform: none;
  box-shadow: none;
}
.skp-selector--compare .skp-panel + .skp-panel {
  border-inline-start: var(--skp-hairline);
}
.skp-selector--compare .skp-panel:first-child {
  border-start-start-radius: var(--skp-shape-card);
  border-end-start-radius:   var(--skp-shape-card);
}
.skp-selector--compare .skp-panel:last-child {
  border-start-end-radius: var(--skp-shape-card);
  border-end-end-radius:   var(--skp-shape-card);
}

/* --- Responsive ---------------------------------------------------------
   .skp-grid already stacks at <=900px. Only the compare panel needs a
   remap: the divider turns horizontal and the corners move to the ends. */
@media (max-width: 900px) {
  .skp-selector--compare .skp-panel + .skp-panel {
    border-inline-start: 0;
    border-block-start: var(--skp-hairline);
  }
  .skp-selector--compare .skp-panel:first-child {
    border-radius: var(--skp-shape-card) var(--skp-shape-card) 0 0;
  }
  .skp-selector--compare .skp-panel:last-child {
    border-radius: 0 0 var(--skp-shape-card) var(--skp-shape-card);
  }
}

/* --- Reduced motion -----------------------------------------------------
   skp-components.css fences .skp-cue and .skp-panel already; the two rules
   this component ADDS are the two it has to fence. The focus ring is not
   motion and is deliberately untouched. */
@media (prefers-reduced-motion: reduce) {
  .skp-selector--compare .skp-grid { transition: none; }
  .skp-panel:hover .skp-selector__cue::after {
    transform: rotate(45deg) translateY(-0.1em);
  }
}
/* ---- Guidance ---------------------------------------------------------- */
/* =========================================================================
   GUIDANCE  ·  02-COMPONENTS §7
   Qualifies the buyer IN or OUT of the product. Distinct from Selector: a
   selector chooses BETWEEN SKP products, guidance decides whether the buyer
   needs this product at all. The negative column is mandatory and it always
   links somewhere.

   REDUCED AGAINST skp-components.css (2026-08-25). Deleted, because a
   primitive now owns it:
     · rhythm pair                              -> .skp-section
     · eyebrow / title / lede type + measure     -> shared anatomy
     · the two-column grid and its gutter        -> .skp-duo
     · the divider and its matching inside pad   -> .skp-duo > :last-child
     · the column heading                        -> .skp-duo__heading
     · the list reset and item type/spacing      -> .skp-duo__list
     · the check and cross markers, their inks
       and the inherited line-height fix         -> .skp-duo__list--yes / --no
     · the exit link's colour, underline,
       underline-offset and focus ring           -> .skp-prose prose links
     · the arrow and its hover translate         -> .skp-cue
     · the whole <=900px block                   -> .skp-duo is 2-up only >=901

   What remains is three things: the surface step, the flex column that seats
   the exit link at the column foot, and the exit link's box.
   ========================================================================= */

/* Astra's `.entry-content ul` (0,1,1) outranks .skp-duo__list (0,1,0) and
   would re-indent and re-bullet both columns, pushing the check and cross
   away from the text. (0,2,0) here, no !important (07 §3.2). */
.skp-guidance .skp-duo__list { margin: 0; padding: 0; list-style: none; }

/* .skp-duo paints the panel surface-container and 03 §6 puts the Guidance
   BAND on surface-container too, so on the default band the panel would
   vanish into its own section. It steps up one rung. Background and its
   on-colour in the SAME rule — a background without its pair is the defect
   that shipped white text on a white CTA. */
.skp-guidance.skp-surface-container .skp-duo {
  background: var(--skp-surface-container-high);
  color: var(--skp-on-surface);
}

/* A flex column purely so the exit link can seat at the foot; the two columns
   are rarely the same length and the route out must read as the conclusion. */
.skp-guidance__col { display: flex; flex-direction: column; min-width: 0; }

/* --- The mandatory route out --------------------------------------------
   Telling a buyer this is wrong for them without saying what is right wastes
   the honesty (02 §7). The paragraph also carries .skp-prose, which is what
   gives the link its colour, underline, offset and focus ring from the
   primitive — none of that is declared here. */
.skp-guidance__alt {
  margin: auto 0 0;
  padding-block-start: var(--skp-space-lg);
  border-top: var(--skp-hairline);
  color: var(--skp-on-surface-variant);
}

/* A tertiary TEXT link, never .skp-btn — 04 §1.5 allows only text links
   mid-page, so the token layer's fully implemented button is deliberately
   unused. display:flex gives the link its own row and a 48px target without
   dressing it up as a second primary action. Type comes from .skp-label-large
   in the markup; the arrow comes from .skp-cue. */
.skp-guidance__alt-link {
  display: flex;
  align-items: center;
  margin-block-start: var(--skp-space-sm);
  min-height: var(--skp-target-min);
}
/* ---- Applications ------------------------------------------------------ */
/* =========================================================================
   APPLICATIONS  ·  02-COMPONENTS §8  ·  heading "Where {product} is used"

   REDUCED against skp-components.css. Now owned by the primitives and DELETED
   from here: the section shell and its rhythm, .skp-eyebrow / .skp-section-title
   (accent bar included) / .skp-section-lede, the grid and every column-pinning
   media query, the card, the stretched ::after hit area, the !important
   text-decoration reset, elevation-and-hover-only-when-linked via :has(), the
   focus ring, the inline prose-link treatment and the reduced-motion guard.

   Also gone: the legacy `.skp-applications__card` / `__grid` neutralisers. The
   authored markup no longer USES those class names — it uses .skp-grid and
   .skp-panel — so the legacy `border-left: 3px solid var(--skp-signal)` and the
   `:hover { transform }` leak can no longer reach an authored card at all.
   Deleting the collision is better than out-specifying it.

   NO TIER BLOCK. Padding, gap, accent colour, accent-bar width and rhythm all
   resolve through the v4 alias layer.
   ========================================================================= */

/* Head block -> body. Adjacent-sibling margins collapse, so this resolves to
   44px whether or not a lede is present, and covers the gallery too. */
.skp-applications > .skp-inner > .skp-grid { margin-block-start: var(--skp-space-2xl); }

/* `.skp-product-page h3` (0,1,1) forces line-height 1.1 and -0.015em onto every
   h3, out-specifying the (0,1,0) .skp-title-large utility. font-size already
   agrees (--skp-h3 aliases title-large); leading and tracking do not. */
.skp-applications .skp-panel > h3 {
  font: 600 var(--skp-title-large)/1.25 var(--skp-font);
  letter-spacing: -0.01em;
}

/* .skp-panel is a flex column with `gap` but zeroes only the LAST child's
   bottom margin, so Astra's block margins ADD to the gap. See pitfalls — this
   belongs in .skp-panel, not in every component that uses it. */
.skp-applications .skp-panel > :is(h3, p) { margin-block: 0; }

/* --- Variant: --cards-media --------------------------------------------
   Media is full-bleed to the card edge. The negative margin is derived from
   the tier padding token, so switching 22 -> 30px cannot leave a hairline of
   card showing at the frame edge. .skp-panel has a radius but no overflow
   clip, so the clip is set here rather than assumed. */
.skp-applications--cards-media .skp-panel { overflow: hidden; }
.skp-applications--cards-media .skp-applications__media {
  align-self: stretch;
  margin: calc(-1 * var(--skp-card-pad)) calc(-1 * var(--skp-card-pad)) var(--skp-space-md);
  line-height: 0;            /* the panel's own 8px gap adds to the 14px = 22px */
}

/* One ratio per row · 02 §12, 04 §9. This is the whole of what Applications
   still owns about images. */
.skp-applications__media img,
.skp-applications__figure img {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}
.skp-applications__figure { margin: 0; }
.skp-applications__figure img { border-radius: var(--skp-shape-media); }

/* 13px is the 02 §12 caption value and no spacing token lands on it. Ink is
   deliberately (0,1,0), one step BELOW `.skp-inverse :is(figcaption, small,
   .skp-caption)` (0,2,0), so a dark band still wins. */
.skp-applications__caption {
  margin: 13px 0 0;
  text-align: left;                       /* image's left edge · 04 §8 */
  color: var(--skp-on-surface-variant);
}

/* =========================================================================
   ESCALATION — legacy `.skp-applications` collision. NOT optional.

   skp-product-tiers.css (priority 25) styles the BARE class for the INJECTED
   hub index:
     .skp-applications         { background: var(--skp-ink); position: relative }
     .skp-applications::before { <dot motif overlay> }
     body .entry-content .skp-applications h2 { color: #ffffff !important }

   That background is (0,1,0) and loads AFTER the surface utilities in
   skp-theme-tokens.css (priority 5), so it BEATS .skp-surface-container on a
   tie. Left alone, an AUTHORED Applications section renders as a dark full-bleed
   band with a white !important H2 on top — an invisible section title, the same
   class of defect as the white-on-white live CTA.

   The full-bleed width/margin is NOT overridden: that is what .skp-section is.
   Only the tint, the motif and the ink are corrected, and pairs are set together.
   Every selector is compounded on `.skp-section`, which the INJECTED markup does
   not carry — so none of this touches the injected band.

   !important appears exactly once, solely to beat an existing !important. It is
   deleted the day skp-product-tiers.php emits the markup named in pitfalls.
   ========================================================================= */
.skp-section.skp-applications::before { content: none; }

.skp-section.skp-applications.skp-surface           { background: var(--skp-surface);                background-image: none; color: var(--skp-on-surface); }
.skp-section.skp-applications.skp-surface-container { background: var(--skp-surface-container);      background-image: none; color: var(--skp-on-surface); }
.skp-section.skp-applications.skp-surface-high      { background: var(--skp-surface-container-high); background-image: none; color: var(--skp-on-surface); }

body .skp-section.skp-applications:is(.skp-surface, .skp-surface-container, .skp-surface-high)
     > .skp-inner > h2.skp-section-title {
  color: var(--skp-on-surface) !important;   /* beats the legacy white !important */
}
/* ---- Capability -------------------------------------------------------- */
/* =========================================================================
   CAPABILITY  ·  02-COMPONENTS §9  ·  heading "Options and customisation"

   REDUCED against skp-components.css. Deleted from here because the primitives
   own them: the section shell and rhythm, the shared anatomy, the grid and its
   pinned columns, the card (padding, accent top edge, radius, and the surface
   flip that gives it surface-container on a surface section), the inline
   prose-link block, the focus ring and the (0,3,0) padding escalation.

   THE RULE THIS COMPONENT EXISTED TO ENFORCE IS NOW FREE. A Capability card's
   props are { title, body } — there is no href, so `.skp-panel:has(.skp-panel__link)`
   never matches and the panel carries no shadow, no hover and no transition by
   construction. The old explicit `box-shadow: var(--skp-elevation-0)` and the
   'no transition on purpose' comment are gone: the primitive cannot get it wrong.

   NO TIER BLOCK. Padding, gap, accent and bar width resolve through the alias layer.
   ========================================================================= */

.skp-capability > .skp-inner > :is(.skp-grid, .skp-capability__list) {
  margin-block-start: var(--skp-space-2xl);   /* head block -> body · 01 §5 */
}

/* `.skp-product-page h3` (0,1,1) beats the (0,1,0) type utility on leading and
   tracking; font-size already agrees via the --skp-h3 alias. */
.skp-capability .skp-panel > h3 {
  font: 600 var(--skp-title-large)/1.25 var(--skp-font);
  letter-spacing: -0.01em;
}
.skp-capability .skp-panel > :is(h3, p) { margin-block: 0; }   /* panel gap, not margins */

/* --- Variant: --list ----------------------------------------------------
   Hairline-divided term/detail rows. A <dl>, never six one-line <h3>s: the
   outline stays H1 -> H2 -> (cards only), which 03 §4 and 06 depend on.
   The container rule is .skp-hairline-top in the markup; --skp-hairline is the
   shorthand that used to be written out longhand in eight components. */
.skp-capability__list { margin: 0; padding: 0; }

.skp-capability__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);      /* mobile first; 2-col at >=901 */
  gap: var(--skp-space-sm);
  padding-block: var(--skp-card-pad);
  border-bottom: var(--skp-hairline);         /* rules between rows, never a card each */
}

.skp-capability__term {
  margin: 0;
  padding-inline-start: var(--skp-space-md);
  border-inline-start: var(--skp-accent-bar) solid var(--skp-accent);
}

/* Also the <dd> reset: the UA gives <dd> a 40px margin-inline-start, which
   drifts the detail column right of its own term at every breakpoint. No colour
   is set — ink is inherited, so this can never out-specify a dark-surface utility. */
.skp-capability__detail { margin: 0; max-width: var(--skp-measure-body); }

/* The closing 'what to send first' sentence required by 11 §9. */
.skp-capability__close { margin: var(--skp-space-xl) 0 0; color: var(--skp-on-surface-variant); }

@media (min-width: 901px) {
  /* 22ch is a term-column TRACK, not a measure cap. --skp-measure-* are prose
     caps and must not be substituted. It is the only literal length here. */
  .skp-capability__row {
    grid-template-columns: minmax(0, 22ch) minmax(0, 1fr);
    gap: var(--skp-grid-gap);
    align-items: start;
  }
}

/* --- States · Tier deltas · Reduced motion ------------------------------
   ALL THREE ARE EMPTY, AND THAT IS THE SPECIFICATION. Nothing in Capability is
   interactive, nothing transitions, and every per-tier value resolves through
   the alias layer. If a future edit adds a hover it must justify the elevation
   against 04 §3 first — and if the item needs to link, it is Related or
   Selector, not Capability. */
/* ---- Process ----------------------------------------------------------- */
/* =========================================================================
   PROCESS  ·  02-COMPONENTS §10  ·  heading "From order to delivery"

   REDUCED against skp-components.css. The section shell and rhythm, the shared
   anatomy, the grid and all four column-pinning media queries, the inline
   prose-link block, the focus ring and the (0,3,0) padding escalation are all
   primitives now and are deleted from here.

   THE TIER BLOCK COLLAPSED. Marker fill is var(--skp-accent) / var(--skp-on-accent)
   — primary/on-primary at 7.27:1 on a use-case page, tertiary/on-tertiary at
   11.06:1 on a hub — so no colour is named in this stylesheet at all, and the
   pair cannot be half-changed. What is left of the tier block is the two values
   02 §10 states explicitly and the alias layer does not carry: the 40/44px
   marker box and the title-medium/title-large step title.

   NEVER STATE ELAPSED TIME. 'Held as stock' is publishable; 'ships in N days'
   is a blocked Tier-D claim and 02 §10 calls it the most likely accidental
   breach on the site. The placeholder copy models the safe phrasing.
   ========================================================================= */

.skp-process {
  --skp-process-marker: 40px;                     /* 02 §10 · use-case */
  --skp-process-title:  var(--skp-title-medium);  /* 02 §10 · use-case */
}
body.skp-tier-material .skp-process {
  --skp-process-marker: 44px;                     /* 02 §10 · hub */
  --skp-process-title:  var(--skp-title-large);   /* 02 §10 · hub */
}

.skp-process > .skp-inner > :is(.skp-grid, .skp-prose) {
  margin-block-start: var(--skp-space-2xl);       /* head block -> body · 01 §5 */
}

/* --- Variant: --steps --- */
.skp-process__step { position: relative; }        /* anchors the hub connector */

.skp-process__marker {
  display: flex;
  align-items: center;
  justify-content: center;
  width:  var(--skp-process-marker);
  height: var(--skp-process-marker);
  margin: 0 0 var(--skp-space-md);
  /* 02 §10 specifies a numbered CIRCLE. 01 §3.1's "a pill that is not clickable
     is a bug" is about badges masquerading as actions; this marker is
     aria-hidden, has no hit area and no state, so shape-full is correct. */
  border-radius: var(--skp-shape-full);
  background: var(--skp-accent);                  /* pair set together · 01 §1 */
  color: var(--skp-on-accent);
  font: 700 var(--skp-title-medium)/1 var(--skp-font);
  font-variant-numeric: tabular-nums;
  /* 40/44px is below --skp-target-min on purpose: the 48px floor applies to
     INTERACTIVE targets (04 §6) and this marker is decorative. */
}

/* The step title carries NO type utility in the markup: its role is
   title-medium on a use-case page and title-large on a hub, so --skp-process-title
   is the single switch. This is the one place a font shorthand is required
   rather than preferred. (0,2,0) also beats `.skp-product-page h3` (0,1,1),
   which would otherwise force 1.1 leading and -0.015em tracking. */
.skp-process--steps .skp-process__heading {
  margin: 0 0 0.5em;                              /* heading -> body · 01 §2.4 */
  font: 600 var(--skp-process-title)/1.3 var(--skp-font);
  letter-spacing: -0.01em;
}
.skp-process__body { margin: 0; max-width: var(--skp-measure-body); }

/* --- Variant: --prose ---------------------------------------------------
   No markers, no numbers, no <h3>. Stage names are inline <strong> lead-ins,
   so a narrative Process cannot smuggle four extra headings into the outline.
   ONE rule now covers both tiers: --skp-prose-size / -leading / -measure are
   the tier body role, so the hub block that used to sit here is gone. */
.skp-process--prose .skp-prose p {
  margin: 0 0 1.15em;                             /* body -> body · 01 §2.4 */
  font: 400 var(--skp-prose-size)/var(--skp-prose-leading) var(--skp-font);
  max-width: var(--skp-prose-measure);
}
.skp-process--prose .skp-prose p:last-child { margin-bottom: 0; }
.skp-process__stage { font-weight: 700; }

/* --- Hub band + connector ----------------------------------------------
   A material hub renders the steps as ONE horizontal band. grid-auto-flow
   makes the track count follow the step count with no :has() arithmetic and no
   orphan risk. The hairline connector bridges the grid gap between markers and
   is scoped to >=1100px because on a WRAPPING grid a connector points at the
   step above rather than the step before. Structural, so it stays a tier rule. */
@media (min-width: 1100px) {
  body.skp-tier-material .skp-process--steps .skp-grid {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
  }
  body.skp-tier-material .skp-process--steps .skp-process__step::before {
    content: "";
    position: absolute;
    top: calc(var(--skp-process-marker) / 2);
    left: calc(-1 * var(--skp-grid-gap));
    width: var(--skp-grid-gap);
    height: 1px;
    background: var(--skp-outline-variant);       /* 1.39:1 hairline · 01 §1.4 */
  }
  body.skp-tier-material .skp-process--steps .skp-process__step:first-child::before { content: none; }
}

/* --- States · Reduced motion -------------------------------------------
   EMPTY. Process has no interactive part; the only focusable element is an
   inline link in a step body or in --prose, and both are covered by the
   prose-link primitive (.skp-prose a, and .skp-process__body inherits nothing
   it needs). Nothing transitions or transforms, so there is no guard to write. */
/* ---- Comparison -------------------------------------------------------- */
/* =========================================================================
   COMPARISON  ·  02-COMPONENTS §11
   This product versus the obvious alternative. `two-col` only — there is no
   third form. FOUR items each side, symmetrical: an unbalanced comparison
   (5 for us, 2 against) reads as a straw man and costs more credibility than
   it wins. Heavily quoted by AI overviews, so the markup stays plain.
   NOT a <table>: two lists, so no data-label is required or present.

   REDUCED AGAINST skp-components.css (2026-08-25). Deleted, because a
   primitive now owns it:
     · rhythm pair                              -> .skp-section
     · eyebrow / title / lede type + measure     -> shared anatomy
     · the panel: grid, gutter, padding, radius,
       surface and its on-colour                -> .skp-duo
     · the divider and its matching inside pad   -> .skp-duo > :last-child
     · the column heading + its rule             -> .skp-duo__heading
     · the list reset and item type/spacing      -> .skp-duo__list
     · the link's colour, underline, offset
       and focus ring                            -> .skp-prose prose links
     · the arrow and its hover translate          -> .skp-cue
     · the whole <=900px block                    -> .skp-duo is 2-up only >=901

   Guidance reduces to almost this same file. See the pitfalls: the four
   permitted differences are the marker, the default band, the preamble and
   the symmetry rule. Nothing else should ever diverge.
   ========================================================================= */

/* Astra's `.entry-content ul` (0,1,1) outranks .skp-duo__list (0,1,0) and
   would re-indent and re-bullet both columns. (0,2,0) here (07 §3.2). */
.skp-comparison .skp-duo__list { margin: 0; padding: 0; list-style: none; }

/* Default band is surface (03 §6), so the surface-container panel already
   reads apart from it and this rule does not fire. It exists for the day the
   page's alternation flips the band. Background and its on-colour in the same
   rule — a background without its pair is the white-on-white CTA defect. */
.skp-comparison.skp-surface-container .skp-duo {
  background: var(--skp-surface-container-high);
  color: var(--skp-on-surface);
}

/* A flex column purely so the link out can seat at the column foot, where it
   reads as the conclusion rather than an aside. */
.skp-comparison__col { display: flex; flex-direction: column; min-width: 0; }

/* --- The one thing Comparison does not share with Guidance ---------------
   .skp-duo__list--yes / --no carry a check and a cross. Here BOTH sides take
   the SAME neutral dot: an accented marker on the left and a grey one on the
   right editorialises a comparison that exists to be even-handed — the
   opposite of what this component is for. This is also why no tier accent is
   applied on either tier: colour here would take a side.
   --skp-on-surface-variant, not --skp-outline: outline is rgba(16,32,51,.28),
   which composites over the panel at about 1.8:1 and reads as a MISSING
   bullet rather than a quiet one. #4f5d70 measures 6.13:1 on #f2f5f8.
   .skp-duo__list li already supplies position:relative and the 1.75em marker
   column, so only the glyph is set here. The 0.62em offset optically centres
   the dot on the first line; no token covers an optical offset. */
.skp-comparison .skp-duo__list li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.62em;
  width: var(--skp-space-sm);
  height: var(--skp-space-sm);
  border-radius: var(--skp-shape-extra-small);
  background: var(--skp-on-surface-variant);
}

/* --- The link out --------------------------------------------------------
   To the alternative's page, or the resource that compares them. The wrapper
   carries .skp-prose, which is what gives the link its colour, underline,
   offset and focus ring from the primitive — none of that is declared here.
   A text link, never .skp-btn: 04 §1.5 allows only tertiary text links
   mid-page, so the token layer's button is deliberately unused. */
.skp-comparison__link-out {
  margin: auto 0 0;
  padding-block-start: var(--skp-space-lg);
  border-top: var(--skp-hairline);
}
.skp-comparison__link {
  display: flex;
  align-items: center;
  min-height: var(--skp-target-min);
}
/* ---- Proof ------------------------------------------------------------- */
/* ==========================================================================
   PROOF  —  02-COMPONENTS §12 · evidence photography
   Variants: gallery-2 (default) · gallery-3 · figure

   REDUCED against skp-components.css. Deleted from here: the
   `body .skp-section.skp-proof { padding-block }` escalation (the shell owns
   the rhythm now, and beats `.skp-product-page .skp-section` on source order),
   the grid, `min-width: 0` on grid children, the gallery-2 column rule and the
   mobile single-column block.

   BLOCKER CLOSED BY DELETION. This component previously declared
   `.skp-proof__caption a { color: …; text-underline-offset: 3px }` with NO
   `text-decoration: underline` — an orphan offset on a link that had no
   underline to offset, so a caption link was distinguishable by hue alone.
   The prose-link primitive names `.skp-proof__caption` in its :where() list and
   supplies colour, underline, 1px thickness, the 3px offset AND the focus ring,
   correct on every surface. Both local rules are gone.

   NO TIER BLOCK, and that is the correct answer: --skp-grid-gap resolves 22/30px
   and --skp-section-rhythm 44-72 / 64-104px by themselves.
   ========================================================================== */

.skp-proof > .skp-inner > .skp-grid {
  margin-block-start: var(--skp-space-xl);
  align-items: start;                 /* tiles top-align; captions may run 2 lines */
}

.skp-proof__item { display: flex; flex-direction: column; margin: 0; padding: 0; }

/* (0,3,0) — beats `.skp-product-page figure img` (0,2,1), which sets the
   --skp-radius-media compat alias. Both resolve to shape-extra-large today; the
   compound keeps the radius owned here rather than by an alias due for deletion.
   This rule is the whole of what Proof still owns. */
.skp-proof .skp-proof__item > .skp-proof__media {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;                /* ONE ratio per row · 02 §12, 04 §9 */
  object-fit: cover;
  object-position: center;
  border-radius: var(--skp-shape-media);
  /* pre-decode placeholder. The paired on-colour is what alt text falls back to
     if the file 404s, so both halves are set in the same rule (01 §1). */
  background: var(--skp-surface-container-high);
  color: var(--skp-on-surface);
}

/* Caption GEOMETRY. 13px under the image and on the IMAGE'S LEFT EDGE, never
   centred (02 §12, 04 §8) — WordPress block CSS centres figure captions, so the
   explicit text-align at (0,2,0) is required. 13px is the spec value and no
   spacing token lands on it. TYPE comes from .skp-body-small in the markup. */
.skp-proof .skp-proof__caption { margin: 13px 0 0; padding: 0; text-align: left; }

/* Caption INK is deliberately (0,1,0) — one step BELOW
   `.skp-inverse :is(figcaption, small, .skp-caption)` (0,2,0) in the token layer,
   so a Proof band placed on a dark surface keeps a legible caption with no
   dark-band rule duplicated here. Raising this to (0,2,0) would tie and win on
   source order, painting dark grey ink on navy. */
.skp-proof__caption { color: var(--skp-on-surface-variant); }

/* The de-rating paragraph required by 11 §12 wherever a frame shows testing,
   stickers, certificates or compliance activity. Always LAST. */
.skp-proof__derating { margin: var(--skp-space-xl) 0 0; color: var(--skp-on-surface-variant); }

/* gallery-3 · 3 or 6 images. The primitive drops to 2 columns in the 901-1099px
   band, which renders a 3-image set as 2+1. 04 §3 forbids the orphan and no
   `.skp-grid--3` modifier exists, so this band is the ONE column rule this
   component still owns. At 901px the tracks are still ~267px, comfortable for a
   3/2 frame. Raise a `.skp-grid--3` under 10-GOVERNANCE §2 and delete this. */
@media (min-width: 901px) and (max-width: 1099px) {
  .skp-proof--gallery-3 .skp-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* figure · one captioned image. Held to the reading measure so a single frame
   sits with the prose column instead of stretching to the 1600px cap.
   --skp-measure-body is used deliberately as the reading-column WIDTH.
   The explicit single track is required: the bare .skp-grid would give the lone
   figure a half-width track from 901px up. */
.skp-proof--figure .skp-grid {
  grid-template-columns: minmax(0, 1fr);
  max-width: var(--skp-measure-body);
}
@media (max-width: 900px) {
  .skp-proof--figure .skp-grid { max-width: 100%; }
  .skp-proof > .skp-inner > .skp-grid { margin-block-start: var(--skp-space-lg); }
}

/* --- States · Tier deltas · Reduced motion ------------------------------
   ALL THREE EMPTY. Proof is evidence, not navigation: no elevation, no hover
   lift, no pointer, no transform, and nothing in this component or in
   skp-design-system.css puts a shadow on a figure image, so a suppression rule
   would be dead code rather than a safety net. The hub band reads full-bleed
   because .skp-section is 100vw — the GRID still caps at .skp-inner. Never
   break the cap to fake full bleed (03 §8). */
/* ---- Related ----------------------------------------------------------- */
/* =========================================================================
   RELATED  ·  route to supporting resources and siblings
   Structure 02 §13 · copy 11 §13 · cards 04 §3 · links §2 · touch §6
   REDUCED against site/mu-plugins/skp-components.css (2026-08-25).

   DELETED — the primitives now own the entire card variant:
     `body .skp-section.skp-related { padding-block }` -> the shell.
     `.skp-related .skp-inner > .skp-section-title { margin; text-align }`
        -> .skp-section-title.
     --skp-related-item-bg and the three `.skp-related.skp-surface*` flips
        -> `.skp-surface .skp-panel` and `:is(.skp-inverse,.skp-secondary)
           .skp-panel` already flip the card tint off the section utility.
     --skp-related-item-ink -> .skp-panel sets background and colour together.
     `.skp-related__list` grid + the FOUR `:has()` count-pin rules + BOTH
        responsive media queries -> .skp-grid / --2 / --5up. Choosing the
        modifier by item count in the markup replaces ~25 lines of :has()
        specificity management, including the ":has() inherits (0,5,0) so the
        media query must repeat it" workaround and the "never merge a plain
        selector into a :has() list" workaround. Both classes of bug are gone
        because the mechanism is gone.
     `.skp-related__card` in full — padding, accent top border, radius,
        elevation, transition, hover lift, active, and the `* { text-decoration }`
        reset -> .skp-panel + .skp-panel__link. The reset now carries the
        !important this component previously omitted, which is what Astra's
        `a:not(...)` rules require (07 §3.3).
     `.skp-related__card > * { position: relative; z-index: 1 }` -> not needed:
        .skp-panel does not paint a state tint over its own copy, and
        `.skp-state` must NOT be added to a .skp-panel__link because the
        stretched ::after and the tint ::after are the same pseudo-element.
     `.skp-related__cue` in full -> .skp-cue on the <h3>.
     the link-cards half of the reduced-motion block -> the primitives' fence
        covers .skp-lift, .skp-cue and .skp-panel:has(.skp-panel__link).
     the `link-cards-application` template -> a tier is not a variant. 03 §5
        lists exactly two Related variants and both serve both tiers.

   STILL NO TIER BLOCK, and now no focus rule and no card rule either.
   --skp-accent, --skp-accent-bar, --skp-card-pad, --skp-grid-gap and
   --skp-section-rhythm all resolve per tier inside the primitives.

   WHAT SURVIVES: the two text parts of a card, and the pill.
   ========================================================================= */

/* --- link-cards: only the copy inside the panel needs a rule ---
   Both are qualified to (0,2,0) because skp-design-system.css §2 sets
   `.skp-product-page h3` and `.skp-product-page p` at (0,1,1); a bare
   `.skp-title-large` / `.skp-body-medium` utility is (0,1,0) and would lose
   its line-height to the theme ramp's 1.1. Margins are 0 because .skp-panel
   spaces its children with gap. */
.skp-panel > .skp-related__label {
  margin: 0;
  font: 600 var(--skp-title-large)/1.25 var(--skp-font);
  letter-spacing: -0.01em;
}
.skp-panel > .skp-related__summary {
  margin: 0;
  font: 400 var(--skp-body-medium)/1.68 var(--skp-font);
  color: var(--skp-on-surface-variant);   /* 6.13:1 on surface-container, 6.70:1 on surface */
}

/* --- pills --- */

.skp-related .skp-related__pills {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;                /* a wrapped label must not stretch its row-mates */
  gap: var(--skp-space-md);               /* 14px > the 8px minimum between targets, 04 §6 */
}

/* BORDER COLOUR IS LOAD-BEARING. 04 §2: on a pill the border carries the
   affordance, so it must clear the 3.0 non-text floor. --skp-outline-variant
   is 1.39:1 and --skp-outline 1.81:1 — both are divider roles. --skp-primary
   measures 6.64:1 on surface-container and 7.27:1 on surface. It deliberately
   does NOT take --skp-accent: on a hub that is #FFCD57 at 1.36:1, an
   affordance nobody can see. Same correction 04 §1.2 made for the secondary
   button. The !important on text-decoration is Astra, not taste — the same
   reason .skp-panel carries it. */
.skp-related .skp-related__pill {
  display: inline-flex;
  align-items: center;
  min-height: var(--skp-target-min);      /* 48px, both axes */
  max-width: 100%;
  padding-block: var(--skp-space-sm);
  padding-inline: var(--skp-space-lg);
  background: var(--skp-surface);
  color: var(--skp-on-surface);           /* paired on-colour, set in the same rule */
  border: 1px solid var(--skp-primary);
  border-radius: var(--skp-shape-full);   /* shape-full: it IS an action */
  font: 600 var(--skp-label-large)/1.4 var(--skp-font);
  text-decoration: none !important;
  transition: transform var(--skp-motion-standard), box-shadow var(--skp-motion-standard);
}

/* A pill must never share its section's tint or its edge disappears (04 §3).
   Same flip .skp-panel performs, applied to the one item this component
   still owns. */
.skp-related.skp-surface .skp-related__pill { background: var(--skp-surface-container); }

/* 2px lift, resting elevation. The border colour is never transitioned and
   never flips to the accent — it is already the affordance colour. Focus is
   NOT declared: every pill is `a.skp-state`, which the token layer rings with
   --skp-focus-ring and tints at 0.10. */
.skp-related .skp-related__pill:is(:hover, :focus-visible) {
  transform: translateY(-2px);
  box-shadow: var(--skp-elevation-card);
}
.skp-related .skp-related__pill:active {
  transform: translateY(0);
  box-shadow: var(--skp-elevation-0);
}

/* Pills stay wrapped at <=900px rather than going full width: the token
   layer's `.skp-btn { width: 100% }` and 04 §1.7 govern BUTTONS, and these
   are navigation links, not the section's action. The 48px height and 14px
   gap already satisfy §6. No media query is needed to say so. */

/* Reduced motion: only the pill, because the primitives' fence already
   covers .skp-cue and .skp-panel:has(.skp-panel__link). The focus ring is not
   motion and is untouched (01 §7). */
@media (prefers-reduced-motion: reduce) {
  .skp-related .skp-related__pill { transition: none; }
  .skp-related .skp-related__pill:is(:hover, :focus-visible, :active) { transform: none; }
}
/* ---- FAQ --------------------------------------------------------------- */
/* ==========================================================================
   FAQ  —  02-COMPONENTS §14 · 04-INTERACTION §4
   Native <details>/<summary>. NO JAVASCRIPT.
   All closed by default. Several may be open at once — never an accordion.
   Hairline rules BETWEEN items. Never a card per question.
   The answer NEVER animates its height: <details> cannot do it reliably
   without JS, and janky height animation is worse than an instant reveal.

   REDUCED against skp-components.css. Deleted from here:
   · the `body .skp-section.skp-faq { padding-block }` escalation — the shell
     owns the rhythm and beats `.skp-product-page .skp-section` on source order;
   · both longhand `1px solid var(--skp-outline-variant)` dividers, now
     `var(--skp-hairline)`, which was written out longhand in eight components;
   · `.skp-faq__answer a { color; text-underline-offset }` and its focus rule —
     the SAME orphan-offset blocker Proof carried. The prose-link primitive
     names `.skp-faq__answer` and supplies colour, underline, thickness, offset
     and ring on every surface.
   The FOCUS RING is still not restated: the token layer carries
   `.skp-faq summary:focus-visible`. This component adds only the 04 §5 tint.
   ========================================================================== */

.skp-faq {
  /* Marker geometry. 1.5rem and the 2px bar are the 02 §14 spec values and no
     token covers them; named here once so they are auditable. */
  --skp-faq-marker: 1.5rem;
  --skp-faq-stroke: 2px;
}

.skp-faq__list {
  margin-block-start: var(--skp-space-xl);
  border-top: var(--skp-hairline);
}

.skp-faq__item {
  border-bottom: var(--skp-hairline);
  /* deliberately no background, no radius, no shadow — a card per question is
     the thing this component exists to prevent (02 §14 do/don't) */
  background: none;
  border-radius: var(--skp-shape-none);
  box-shadow: var(--skp-elevation-0);
}

.skp-faq__question {
  display: flex;
  align-items: center;
  position: relative;
  min-height: var(--skp-target-min);          /* 48px full-row target · 04 §6 */
  /* 18px block is the 02 §14 spec value and has no token; 44px right is the
     marker column and IS a token. */
  padding: 18px var(--skp-space-2xl) 18px 0;
  cursor: pointer;
  color: var(--skp-on-surface);
  -webkit-tap-highlight-color: transparent;
  list-style: none;                           /* strip the triangle · engine 1 */
}
.skp-faq__question::-webkit-details-marker { display: none; }   /* Safari / legacy Chrome */
.skp-faq__question::marker { content: ""; }                     /* Firefox / modern */

/* <summary> may legally contain exactly one heading element, so the question is
   a real <h3> in the outline (03 §4, 07 §1.3). The full type is restated here
   rather than taken from .skp-title-medium because `.skp-product-page h3` at
   (0,1,1) sets font-size AND line-height 1.1 AND letter-spacing -0.015em, all
   of which outrank a (0,1,0) utility. This (0,2,0) compound wins all three. */
.skp-faq .skp-faq__q {
  margin: 0;
  padding: 0;
  font: 600 var(--skp-title-medium)/1.3 var(--skp-font);
  letter-spacing: 0;                          /* title-medium tracks at 0 */
  color: inherit;                             /* follows the summary, incl. hover */
}

/* The +/- marker: two bars rather than a content swap, so the change is a real
   opacity transition. `content` is not animatable, and a rotate is banned
   outright (01 §7). Right-aligned, vertically centred, 8px inset (02 §14). */
.skp-faq__question::before,
.skp-faq__question::after {
  content: "";
  position: absolute;
  top: 50%;
  background: var(--skp-primary);
  pointer-events: none;
}
.skp-faq__question::before {                  /* horizontal bar — always shown */
  right: var(--skp-space-sm);
  width: var(--skp-faq-marker);
  height: var(--skp-faq-stroke);
  margin-top: calc(var(--skp-faq-stroke) / -2);
}
.skp-faq__question::after {                   /* vertical bar — retracts on open */
  right: calc(var(--skp-space-sm) + (var(--skp-faq-marker) / 2) - (var(--skp-faq-stroke) / 2));
  width: var(--skp-faq-stroke);
  height: var(--skp-faq-marker);
  margin-top: calc(var(--skp-faq-marker) / -2);
  transition: opacity var(--skp-motion-standard);
}

/* --- Answer --- */
.skp-faq__answer { padding: 0 var(--skp-space-2xl) 18px 0; }
/* NO max-height, NO height transition, NO overflow trick. Instant reveal.
   (0,2,1) beats `.skp-product-page p` (0,1,1), which is why the type is set
   here rather than with a .skp-body-medium class in the markup. Answer type is
   body-medium on BOTH tiers (02 §14), so it does NOT use --skp-prose-size. */
.skp-faq .skp-faq__answer :is(p, li) {
  margin: 0;
  font: 400 var(--skp-body-medium)/1.68 var(--skp-font);
  color: var(--skp-on-surface-variant);       /* 6.70:1 on surface */
  max-width: var(--skp-measure-body);         /* 72ch · 01 §2.3 */
}
.skp-faq .skp-faq__answer p + p   { margin-top: 1.15em; }   /* body -> body · 01 §2.4 */
.skp-faq .skp-faq__answer :is(ul, ol) { margin: 0.5em 0 0; padding-left: 1.2em; }
.skp-faq .skp-faq__answer li + li { margin-top: 0.5em; }    /* item -> item · 01 §2.4 */

/* --- Variants ---
   FAQ has none. 03 §5 lists no FAQ branch, and a variant that is not on that
   list does not exist. Item count (6-8) is content, not a variant.

   --- Tier deltas ---
   NONE. Rhythm resolves from --skp-section-rhythm. The marker stays
   --skp-primary on BOTH tiers: 02 §14's table has a single "Both tiers"
   column, so it is NOT --skp-accent — do not let a hub turn it yellow. */

/* --- States --- */

/* Open: the vertical bar disappears, + becomes -. Nothing else changes. */
.skp-faq__item[open] > .skp-faq__question::after { opacity: 0; }

/* Hover: summary text goes primary and NOTHING MOVES. `color` is never
   transitioned (01 §7), so the change is instantaneous by design. The h3
   inherits it, so no second selector is needed. */
.skp-faq__question:hover { color: var(--skp-primary); }

/* Focus: 04 §5 takes BOTH the ring and the 0.10 state tint. The RING comes from
   the token layer's `.skp-faq summary:focus-visible` and is deliberately not
   restated. This adds only the tint — background and its paired on-colour in
   the same rule (01 §1); the pair measures 7.05:1. */
.skp-faq__question:focus-visible {
  background: var(--skp-primary-container);
  color: var(--skp-on-primary-container);
}

/* --- Responsive --- */
@media (max-width: 900px) {
  .skp-faq__list { margin-block-start: var(--skp-space-lg); }
  /* 02 §14 mobile: 16px 40px 16px 0. The marker box is 1.5rem at an 8px inset,
     so 40px still clears it and the whole row stays the target. min-height
     keeps the row at 48px even for a one-line question. */
  .skp-faq__question { padding: 16px 40px 16px 0; }
  .skp-faq__answer   { padding: 0 40px 16px 0; }
}

/* --- Reduced motion --- */
@media (prefers-reduced-motion: reduce) {
  .skp-faq__question::after { transition: none; }
  /* The focus ring is not motion and is unaffected here (04 §7). */
}
/* ---- CTA --------------------------------------------------------------- */
/* ==========================================================================
   CTA  —  02-COMPONENTS §15 · 04-INTERACTION §1
   The conversion band. ONE action. Always to /contact-us/.

   WHAT THIS COMPONENT NO LONGER DOES
   1. IT DOES NOT IMPLEMENT A BUTTON. `.skp-cta__btn` is gone. The token layer
      owns .skp-btn / --primary / --secondary including the surface lookup
      (yellow fill + DARK on-tertiary ink on a dark band), 48px min-height,
      hover/active/disabled and the mobile full-width rule.
   2. IT DOES NOT RESTATE THE BAND COLOURS. `.skp-inverse` /
      `.skp-surface-container` set background AND on-colour together, and
      `.skp-inverse :is(h1..h6, strong)` already covers the h3.
   3. IT DOES NOT DRAW A FOCUS RING. `.skp-btn:focus-visible` and
      `a.skp-state:focus-visible` use --skp-focus-ring, which the dark surface
      utilities rebind to yellow automatically.
   4. IT DOES NOT SET ITS OWN RHYTHM. `.skp-section` + `.skp-product-page
      .skp-section` in skp-components.css both resolve --skp-section-rhythm and
      beat skp-design-system.css on source order. The (0,2,1) escalation is gone.
   5. IT HAS NO TIER BLOCK ANY MORE. --skp-prose-size / -leading / -measure ARE
      the tier body role — body-large/1.6/62ch on a hub, body-medium/1.68/72ch
      on a use-case page — which is exactly what the three deleted
      `--skp-cta-body-*` locals and the `body.skp-tier-material .skp-cta` block
      were hand-rolling.

   STILL TRUE, AND STILL THE TWO THINGS THAT SHIPPED BROKEN:
   · The heading is an <h3>, never an <h2>. Deploy blocker.
   · A dark-band heading rule must cover h2 AND h3. The surface utility does;
     verify it computes to #ffffff rather than assuming.
   ========================================================================== */

/* --- Base --- */
.skp-cta {
  position: relative;      /* anchors the perforation overlay */
  overflow: hidden;        /* the motif must not bleed past the band */
  text-align: center;
}

/* Lift content above the motif. Cap and gutter stay owned by the shell —
   nothing here touches max-width or margin. */
.skp-cta > .skp-inner { position: relative; z-index: 1; }

/* --- Parts --- */

/* Type comes from .skp-headline-medium in the markup; only the SIZE is
   re-asserted, because `.skp-product-page h3 { font-size: var(--skp-h3) }` at
   (0,1,1) outranks the (0,1,0) utility and would render the CTA heading at
   title-large — card size. That rule's line-height 1.1 and -0.015em already
   match headline-medium, so nothing else needs restating.
   .skp-measure-center in the markup centres the cap; margin-block is zeroed
   here because Astra puts block margins on headings. */
.skp-cta .skp-cta__heading {
  margin-block: 0;
  max-width: var(--skp-measure-lede);
  font-size: var(--skp-headline-medium);
}

/* (0,2,0) beats `.skp-product-page p` (0,1,1), so the body type is set here
   rather than with a .skp-body-* class. ONE rule, both tiers: the prose tokens
   resolve the 02 §15 / 03 §1 body role by themselves. `auto` inline margins
   centre the cap; .skp-measure-center states the same intent declaratively. */
.skp-cta .skp-cta__body {
  margin: 0.5em auto 0;                        /* heading -> its body · 01 §2.4 */
  font: 400 var(--skp-prose-size)/var(--skp-prose-leading) var(--skp-font);
  max-width: var(--skp-prose-measure);
}

.skp-cta__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--skp-space-md);                    /* 14px desktop · 04 §1.3 */
  margin-block-start: var(--skp-space-xl);
}

/* --- Variants --- */

/* band-dark — material hub. The BAND is `.skp-inverse` on the section; this
   variant contributes the perforation motif and nothing else. currentColor at
   5.5% inherits the band's on-colour, so the motif needs no hex and cannot
   desync from the surface. The 22px grid and the 1.6/2.2px dot are pattern
   geometry, not spacing — no token covers them. */
.skp-cta--band-dark::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, currentColor 1.6px, transparent 2.2px);
  background-size: 22px 22px;
  opacity: 0.055;
  pointer-events: none;
  z-index: 0;
}

/* band-light — use-case page. NO CSS, deliberately. The entire difference is
   the `skp-surface-container` utility on the section, which sets the tint and
   its paired ink together. The variant class stays in the markup as the QA hook
   that proves the 03 §5 tier->variant rule was applied. */

/* --- Tier deltas ---
   NONE. The last one — the hub's body-large/62ch lede — is now expressed by
   --skp-prose-size / -leading / -measure in the single .skp-cta__body rule.

   --- States ---
   NONE. The buttons are .skp-btn .skp-state, so hover lift, active, focus ring
   and the 0.10 focus tint all come from the token layer, correct on both the
   navy and the tinted band. Nothing else in this section is interactive. */

/* --- Responsive · 04 §1.7 --- */
@media (max-width: 900px) {
  .skp-cta__actions {
    flex-direction: column;                    /* primary is first in DOM, stays on top */
    align-items: stretch;
    gap: 12px;                                 /* stacked spacing · 04 §1.3, no token is 12px */
    margin-block-start: var(--skp-space-lg);
  }
  /* width: 100% and the 48px floor come from the token layer's
     `@media (max-width: 900px) { .skp-btn { width: 100% } }` — not restated. */
}

/* --- Reduced motion ---
   Nothing in this component transitions or transforms; the button's guard lives
   with the button, in the token layer. Kept as a named region so the absence is
   a decision — if a future revision animates the band, the guard goes here. */

/* ===== END GENERATED COMPONENTS ===== */
