/*
 * sitepilot-blocks — canonical-class compatibility layer (Gutenberg conversion, Phase 7).
 *
 * The ~52 `semantic` families already style the canonical class vocabulary
 * (.hero-content, .services-grid, .service-card, .section-heading-group …) in
 * their base.css. The ~28 `bem` + 2 `bem-under` families do NOT — their base.css
 * styles .card/.grid/.section__header instead. This stylesheet gives the canonical
 * classes a sensible, ON-BRAND layout (it consumes each family's own --color-,
 * --radius and --space CSS variables, with fallbacks) so block-rendered pages
 * look right on every family.
 *
 * EVERY selector is wrapped in :where() → ZERO specificity. Any real family rule
 * (even a single-class selector) overrides these, so semantic families are
 * completely unaffected; only the gaps on bem families get filled.
 *
 * WS-B (run3 design-fix, 2026-07-12): the button/contact-info-card/about-
 * rhythm/form-input/icon-chip rules added below use PLAIN (non-:where())
 * low-but-non-zero-specificity selectors — a bare single class, e.g.
 * `.btn-primary` (specificity 0,1,0) — instead of the :where() pattern
 * above. This plugin enqueues canonical-compat.css on `enqueue_block_assets`
 * priority 5, BEFORE the family's own base.css/custom.css (see
 * sitepilot-blocks.php), so on an equal-specificity tie the family's real
 * rule (loaded later in the cascade) still wins — preserving family
 * identity/colors exactly like :where() did for those cases — while a plain
 * class selector, unlike :where(), reliably beats accidental
 * zero-specificity universal/element resets (e.g. `* { margin: 0 }`, a bare
 * `input {}`) that were silently zeroing out the old :where() spacing/
 * surface rules regardless of load order. That was the actual residual bug
 * (about-section overlaps 008/027/030/031, invisible dark-form inputs) —
 * not family rules "correctly" overriding compat, but compat losing to
 * unrelated resets it should have beaten. NO `!important` is used anywhere
 * in this file.
 *
 * The contract doc (docs/gutenberg-run3-fix-playbook.md) asks for these
 * rules to be scoped under a plugin-root class `.sp-block-page`. Verified
 * via repo-wide grep that this class is NOT emitted anywhere in the
 * codebase yet (no PHP/theme template adds it to <body> or a content
 * wrapper), and this task's scope is this CSS file only. Selectors below
 * are therefore left unscoped (bare classes) rather than nested under a
 * currently-nonexistent ancestor, which would silently match nothing. If/
 * when a `.sp-block-page` wrapper is added by another workstream, prefixing
 * these selectors with it is a pure specificity increase (0,1,0 → 0,2,0)
 * and safe to do at that point — it will not change which rules currently
 * apply, only add an extra namespace/specificity margin.
 */

/* ---- role tokens (WS-B) — resolved per family from existing family
   variables, so family branding always drives the actual color; these are
   just canonical NAMES for "what a button/highlight/card-surface is",
   matching the shared contract in docs/gutenberg-run3-fix-playbook.md. ---- */
:root {
  --sp-action: var(--color-accent, var(--color-primary, currentColor));
  --sp-action-contrast: var(--color-on-accent, var(--color-white, #fff));
  --sp-highlight: var(--color-primary, var(--color-accent, currentColor));
  --sp-surface-card: var(--color-bg-card, var(--color-surface, rgba(128, 128, 128, .05)));
}

/* ---- buttons (canonical .btn vocabulary) — previously UNDEFINED in this
   file; the direct cause of 22 bare-link CTAs + 16 pale/unstyled submit
   buttons (report P1#1). Verified against render-helpers.php sp_cta()/
   sp_static_contact_form(_custom)() and blocks/hero, blocks/cta-banner,
   blocks/product-highlight, blocks/pricing-table render.php: real markup
   emits `btn btn-primary`, `btn btn-outline`, and `btn btn-lg` (on <a> CTAs
   and the static-form <button type="submit">). `.btn-large`/`.btn-white`
   are the contract's canonical names (playbook "Shared class/token
   contract") — no render.php currently emits them, so they're included for
   contract compliance / forward-compat; `.btn-lg` is aliased to the same
   rule as `.btn-large` so today's real markup is styled identically. ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2, .5rem);
  padding: .85rem 1.9rem;
  border: 1px solid transparent;
  border-radius: var(--radius-md, 8px);
  font-family: inherit;
  font-size: .95rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  transition: background-color .2s ease, color .2s ease, border-color .2s ease,
    transform .2s ease, box-shadow .2s ease, filter .2s ease;
}
.btn-primary {
  background: var(--sp-action);
  color: var(--sp-action-contrast);
  border-color: var(--sp-action);
}
.btn-primary:hover, .btn-primary:focus-visible {
  filter: brightness(1.08);
  transform: translateY(-1px);
  color: var(--sp-action-contrast);
}
.btn-primary:active { transform: translateY(0); filter: brightness(.95); }

.btn-outline {
  background: transparent;
  color: var(--sp-action);
  border-color: var(--sp-action);
}
.btn-outline:hover, .btn-outline:focus-visible {
  background: var(--sp-action);
  color: var(--sp-action-contrast);
}
.btn-outline:active { filter: brightness(.95); }

/* WS-B (run3 design-fix, iteration 2 — 020-carbon-fiber ghost-CTA bug): the
   `.btn-outline` rule above only sets border-COLOR, relying on the base
   `.btn` rule above for border-width/border-style (border: 1px solid
   transparent). That's fine on families whose `.btn` keeps a real
   border-style — but verified against 020-carbon-fiber/base.css,
   005-cotton-cloud/base.css and 042-slate-authority/base.css: the ~29 "bem"
   families (020, 005, …) write `.btn { border: none; … }` and style
   buttons entirely through BEM double-dash classes (`.btn--outline`,
   `.btn--primary`, …) — they never define a single-dash `.btn-outline` at
   all. The real markup from sp_cta()/render.php is always single-dash
   (`class="btn btn-outline"`), so on those families `.btn-outline` above
   is the ONLY rule touching color/border for that button — but family
   `.btn`'s `border: none` (same 0,1,0 specificity, and the family's whole
   base.css stylesheet loads AFTER this entire file per sitepilot-blocks.php
   enqueue order) always wins the border-style cascade regardless of
   within-file order, so the border-color set above never renders: the
   button is transparent bg + colored text + no visible edge → a bare link
   (confirmed against 020's live HTML, exact bug report). 042-slate-authority
   is the control case: its `.btn` keeps `border: 2px solid transparent`
   (real style, not none) AND it defines its own single-dash `.btn-outline`
   — completely unaffected either way.
   Fix: a compound `.btn.btn-outline` selector — matching the exact real
   markup — raises specificity to (0,2,0), which reliably beats a
   single-class family `.btn` (0,1,0) no matter which stylesheet loads
   last. It intentionally sets ONLY the border box-model (width/style), no
   color/background — so on a family that *does* define its own
   `.btn-outline` (042), this cannot touch that family's border-color/text/
   background at all (never declared here); the one possible side-effect is
   this rule's border-width (1.5px) winning over a family's own chosen
   outline width, which is a purely cosmetic, sub-pixel-class difference,
   not a color/identity change. No `!important` needed. */
.btn.btn-outline {
  border-width: 1.5px;
  border-style: solid;
}

/* light-surface variant for buttons placed on a dark/colored/media
   background (hero over photo, accent-filled cta-section, …). */
.btn-white {
  background: #fff;
  color: var(--sp-action);
  border-color: #fff;
}
.btn-white:hover, .btn-white:focus-visible {
  background: transparent;
  color: #fff;
  border-color: #fff;
}
.btn-white:active { filter: brightness(.95); }

.btn-large, .btn-lg {
  padding: 1.05rem 2.3rem;
  font-size: 1.05rem;
}

/* BEM double-dash variants (.btn--primary/--accent/--outline/--ghost/--white/
   --lg/--sm) — additive contract-completeness baselines, NOT a bug fix: real
   render.php markup never emits these (verified — sp_cta()/sp_static_
   contact_form() only ever emit single-dash `btn-primary`/`btn-outline`).
   They exist because ~29/80 families (020-carbon-fiber, 005-cotton-cloud, …)
   write their OWN theme chrome (header.html/footer.html nav CTAs, pages/*.html)
   using this BEM naming, matching their OWN base.css `.btn--*` rules one-to-one
   — those are already fully self-contained (each defines its own full
   border/background/color, so they were never subject to the `.btn { border:
   none }` trap the fix above addresses) and correctly override these plain
   0,1,0 baselines via the normal cascade (family base.css loads after this
   file). Kept low/plain specificity throughout — a real family `.btn--*` rule
   always wins, exactly like the rest of this file's :where()/plain-class
   pattern. */
.btn--primary {
  background: var(--sp-highlight);
  color: var(--sp-action-contrast);
  border-color: var(--sp-highlight);
}
.btn--accent {
  background: var(--sp-action);
  color: var(--sp-action-contrast);
  border-color: var(--sp-action);
}
.btn--outline {
  background: transparent;
  color: var(--sp-action);
  border-color: var(--sp-action);
}
.btn.btn--outline {
  border-width: 1.5px;
  border-style: solid;
}
.btn--ghost {
  background: transparent;
  color: var(--sp-action);
  border-color: transparent;
}
.btn--white {
  background: #fff;
  color: var(--sp-action);
  border-color: #fff;
}
.btn--lg { padding: 1.05rem 2.3rem; font-size: 1.05rem; }
.btn--sm { padding: .6rem 1.3rem; font-size: .85rem; }

.btn:disabled, .btn[disabled], .btn[aria-disabled="true"] {
  opacity: .55;
  cursor: not-allowed;
  pointer-events: none;
  transform: none;
}
.btn:focus-visible {
  outline: 2px solid var(--sp-action);
  outline-offset: 2px;
}

/* ---- header / nav overflow guard (BUG A, fleet-wide fix 2026-07-13) ----
   66/80 families clip the last horizontal nav item (almost always
   "Επικοινωνία") off the right edge of the viewport, and on several the
   logo text wraps to 3-4 lines and collides with the menu. Root cause
   (verified against 001-midnight-noir, 018-crimson-gala, 060-bento-standard,
   010-paper-fold + a repo-wide grep of every family's header.html/base.css):
   the header's inner flex row (logo | nav-links | cta | hamburger) is a
   normal block-level flex container correctly capped to `.container`'s
   width, but the nav LIST — itself a flex item of that row *and* a flex
   container of the individual <a> links — keeps the browser default
   `min-width: auto`, which refuses to let a flex item shrink below its
   content's natural (min-content) width. Once enough nav items are present
   (the dynamic {{nav_links}} placeholder, plus up to 4 hardcoded extra
   links on several families) the link list overflows past the header's
   right edge; since the header is `position: fixed` (out of flow, no
   scrollbar reveals the overflow) the trailing item is simply invisible
   past the viewport edge. No family in the fleet sets `min-width` or
   `flex-wrap` on any of these selectors (verified via repo-wide grep), this
   is purely additive, not a fight over an existing declaration. Covers every
   vocabulary the fleet uses for its header chrome: `.site-header`/
   `.header-inner` (40 families), `.nav-inner` (11), `.navbar`/
   `.navbar__inner` (2), `.header`/`.header__inner` (2), `.nav-bar`/
   `.nav-wrapper` + nested `.nav` (20+4), and the `.nav-links`/`.nav__links`/
   `.navbar__links`/`.header__nav` link-list itself (80/80 — every family
   uses exactly one of these four). ORIGINALLY implemented with :where() (zero
   specificity); see the Wave-2 comment immediately below for why the actual
   rules were escalated to plain ancestor-compound selectors instead. */
/* Wave-2 (2026-07-13, residual A — specificity escalation): Wave-1 used
   :where() (zero specificity) for the rules directly below, which is safe
   against family rules that don't touch the same property, but can silently
   LOSE a cascade TIE against some equal-or-lower-specificity rule in a
   family's base.css (a reset, a later block, …) that also loads after this
   file. Full per-page live re-verify found 5 families where Wave-1 visibly
   did not take effect despite using the EXACT matching class vocabulary
   already in Wave-1's selector list (018-crimson-gala, 028-twilight-deco,
   045-carnival-heat, 067-bauhaus-mono, 072-unique-pattern — confirmed via
   header.html/base.css inspection: all five use `.header-inner`/
   `.nav-bar .nav`/`.nav-inner` + `.nav-links`/`.nav__links` +
   `.site-logo`/`.nav__logo`, not a different vocabulary). Fix: same
   properties as Wave-1, but every selector below now combines the outer
   header-wrapper class (`.site-header`/`.nav-bar`/`.navbar`/`.header`/
   `.nav-wrapper` — the only 5 vocabularies the fleet's `<header>` element
   uses fleet-wide, verified via a full grep of every family's header.html)
   with the inner target class, raising specificity to (0,2,0). That
   reliably beats any single-class (0,1,0) family rule or lower-specificity
   reset regardless of load order, closing the residual gap without having
   to prove its exact cause. Purely additive/unchanged properties, so the 11
   families Wave-1 already fixed and the 14 that always fit are unaffected. */
.site-header .header-inner, .site-header .nav-inner,
.navbar .navbar__inner, .header .header__inner,
.nav-bar .nav, .nav-wrapper .nav {
  min-width: 0;
  max-width: 100%;
}
.site-header .nav-links, .site-header .nav__links,
.navbar .navbar__links, .header .header__nav,
.nav-bar .nav__links, .nav-wrapper .nav__links {
  min-width: 0;
  flex-wrap: wrap;
  row-gap: var(--space-2, .5rem);
}
/* Logo/brand — previously unconstrained text could wrap to 3-4 lines on a
   narrow available width and visually collide with the nav (report BUG A);
   same Wave-2 specificity escalation as above (a family that DOES size its
   own logo explicitly, e.g. font-size, is still unaffected — different,
   additive properties). Also fixes 067-bauhaus-mono's TOP-clipped nav: its
   `.nav-inner` had a hardcoded `height: 68px` (verified the ONLY family in
   the fleet with a fixed px height on this row — fleet-wide grep), which
   capped the row instead of letting it grow for a wrapped 2nd line, pushing
   the overflow ABOVE the fixed header. A fleet selector can't safely
   reconstruct that family-specific magic number as a min-height, so that
   one property is fixed directly in 067's own base.css (min-height keeps
   the original 68px look; height:auto lets it grow downward) — the one
   deliberate base.css touch in this task, justified because the fleet
   selector reaches the markup but not the specific numeric height. */
.site-header .site-logo, .site-header .nav__logo,
.navbar .navbar__brand, .header .header__brand,
.nav-bar .nav__logo, .nav-wrapper .nav__logo {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  max-width: clamp(120px, 28vw, 260px);
  flex-shrink: 1;
}

/* ---- inner-page header clearance guard (BUG A residual B, 2026-07-13) ----
   4 families (008-obsidian-edge, 017-glacier-mint, 020-carbon-fiber,
   030-chrome-velocity) have an about/contact page whose ONLY block is the
   about/contact-form section itself (no hero above it — verified via
   pages/about.blocks.json + pages/contact.blocks.json: `["sitepilot/about"]`
   / `["sitepilot/contact-form"]`) — so that section's own heading
   (.section-marker + .section-label eyebrow + .section-title) sits directly
   under the position:fixed header, with only the section's own padding-top
   (family --space-section, ~6-7rem, tuned for a single-row header) for
   clearance. Once the nav-wrap fix above grows the header to 2 rows on a
   narrow/long nav, that fixed clearance is no longer enough → the heading
   overlaps the header. Fix: extra top buffer, scoped via
   `#main-content > .section:first-child` — this ONLY ever matches a page's
   OPENING content block (the theme's page.php wraps block output in a bare
   `<main id="main-content">` with no extra layer, verified via
   services/wp-provisioner/server.js), so a mid-page `#about`/`#contact`
   recurrence (e.g. the About section on the HOME page, which follows the
   hero) is untouched; `.hero`'s root class is `hero`, not `section`, so
   hero-led pages never match this rule either. Reuses the family's own
   `--space-section` scale (falling back to this file's own `--space-16`
   convention, then a flat 5rem) so the added clearance still tracks each
   family's spacing rhythm, plus a flat +3rem safety margin sized for one
   extra wrapped nav row. Plain (non-:where()) + `#main-content` id anchor so
   this reliably outranks the family's own `.section{padding:...}` shorthand. */
#main-content > .section:first-child {
  padding-top: calc(var(--space-section, var(--space-16, 5rem)) + 3rem);
}

:where(.section) { padding-block: var(--space-16, 5rem); }
:where(.container) { max-width: var(--container-max, 1200px); margin-inline: auto; padding-inline: var(--space-6, 1.5rem); }

/* hero */
/* WS (2026-07-13, BUG A residual — hero renders over nav on 060-bento-
   standard/067-bauhaus-mono/072-unique-pattern/080-unique-pattern): these
   families' own base.css never styles the canonical `.hero` markup at all
   (they built their original hero out of bespoke classes — `.tile--hero`,
   `.geo-hero`, `.gradient-hero`, `.saas-hero` — none of which match what
   the hero block actually renders), so `.hero`'s vertical spacing came
   entirely from this file's old `:where(.hero)` rule below — zero
   specificity, tied with every family's own `*, *::before, *::after {
   margin: 0; padding: 0; }` reset, and losing that tie because the family
   stylesheet loads after this file (same root-cause pattern already fixed
   for `.about-grid`/`.contact-form .form-input` above — see file-top note).
   With the padding zeroed, the hero's H1 starts flush at the top of the
   (position:fixed) header's row instead of clearing it, reading as "hero
   text over the nav" in QA screenshots. Plain (non-:where()) specificity
   reliably survives that reset; families that DO style `.hero` themselves
   (e.g. 001/018/010/069, all verified) still win the tie exactly as before
   since their own rule loads later — this only fills the gap. */
.hero { padding-block: var(--space-20, 6rem); }
:where(.hero-content) { max-width: 820px; margin-inline: auto; text-align: center; }
:where(.hero-cta-group) { display: flex; flex-wrap: wrap; gap: var(--space-4, 1rem); justify-content: center; margin-top: var(--space-6, 1.5rem); }
:where(.hero-media) { margin-top: var(--space-8, 2rem); }
:where(.hero-media img) { width: 100%; border-radius: var(--radius-lg, 12px); }

/* section heading */
:where(.section-heading-group) { text-align: center; max-width: 720px; margin-inline: auto; margin-bottom: var(--space-10, 3rem); }
:where(.section-marker) { display: block; width: 40px; height: 3px; margin: 0 auto var(--space-4, 1rem); background: var(--color-accent, var(--color-primary, currentColor)); border-radius: 2px; }
:where(.section-label) { display: block; text-transform: uppercase; letter-spacing: .1em; font-size: .8rem; color: var(--color-accent, var(--color-primary, inherit)); margin-bottom: var(--space-2, .5rem); }
:where(.section-title) { font-size: clamp(1.6rem, 3vw, 2.4rem); line-height: 1.2; }
:where(.section-subtitle) { margin-top: var(--space-3, .75rem); color: var(--color-text-muted, var(--color-text, inherit)); }

/* grids */
:where(.services-grid, .features-grid, .portfolio-grid, .team-grid, .blog-grid,
       .pricing-grid, .testimonials-grid, .reviews-grid, .gallery-grid, .shop-grid,
       .process-grid, .columns-grid, .menu-grid, .stats-row, .logos-strip) {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-6, 1.5rem);
}
:where(.stats-row) { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); text-align: center; }
:where(.logos-strip) { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); align-items: center; }
:where(.gallery-grid) { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

/* cards */
:where(.service-card, .feature-card, .team-card, .blog-card, .pricing-card,
       .testimonial-card, .review-card, .portfolio-item, .process-step,
       .product-card, .column, .timeline-item, .menu-category) {
  background: var(--color-bg-card, var(--color-surface, transparent));
  border: 1px solid var(--color-border, rgba(128,128,128,.18));
  border-radius: var(--radius-lg, 12px);
  padding: var(--space-6, 1.5rem);
}
/* WS-B (run3 2026-07-12): icon CHIP, not a bare icon — the previous rule set
   size/color only, so an svg with no family-styled wrapper rendered as a
   small neutral dot floating in the card (report P1#5, 13-page defect).
   Verified against blocks/services-grid + blocks/features-grid render.php:
   both ALWAYS wrap the icon (including sp_icon()'s own neutral-dot
   fallback) in this exact `.service-icon`/`.feature-icon` div — never a
   bare <svg> — so a background/shape here is guaranteed to apply. Plain
   (non-:where()) specificity so it survives a family reset that zeroes
   unstyled icon wrappers; still loses to any real family `.service-icon`
   rule per the cascade-order note at the top of this file. */
.service-icon, .feature-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin-bottom: var(--space-4, 1rem);
  border-radius: var(--radius-md, 10px);
  background: rgba(128, 128, 128, .12);
  background: color-mix(in srgb, var(--sp-action) 14%, transparent);
  color: var(--sp-action);
}
.service-icon svg, .feature-icon svg { width: 26px; height: 26px; }
:where(.service-number, .process-number) { display: inline-block; font-weight: 700; color: var(--color-accent, var(--color-primary, inherit)); margin-bottom: var(--space-2, .5rem); }

/* media inside cards — constrain aspect so empty/dummy images don't render as
   oversized boxes (a fleet-QA driver on image-heavy sections; real photos fill
   these in production). object-fit:cover keeps real images tidy too. */
:where(.team-photo-wrapper img, .blog-card-image img, .portfolio-item img,
       .product-image img, .gallery-item img, .card img, .service-media img,
       .feature-media img) {
  width: 100%; height: auto; border-radius: var(--radius-md, 8px);
}
:where(.portfolio-item, .gallery-item, .blog-card-image, .team-photo-wrapper,
       .product-image, .service-media, .feature-media) img {
  aspect-ratio: 4 / 3; object-fit: cover;
}
:where(.gallery-item) { aspect-ratio: 4 / 3; overflow: hidden; }
:where(.gallery-item img) { height: 100%; }
:where(.team-photo-wrapper img) { aspect-ratio: 1 / 1; }

/* testimonials / reviews */
:where(.testimonial-stars, .review-stars) { color: var(--color-accent, #f5b301); margin-bottom: var(--space-2, .5rem); }
:where(.testimonial-author, .review-author) { margin-top: var(--space-4, 1rem); display: flex; flex-direction: column; }

/* faq */
:where(.faq-list) { max-width: 780px; margin-inline: auto; display: flex; flex-direction: column; gap: var(--space-3, .75rem); }
:where(.faq-item) { border: 1px solid var(--color-border, rgba(128,128,128,.18)); border-radius: var(--radius-md, 8px); overflow: hidden; }
:where(.faq-item__question) { width: 100%; text-align: start; padding: var(--space-4, 1rem); background: none; border: 0; font: inherit; cursor: pointer; display: flex; justify-content: space-between; gap: var(--space-4, 1rem); }
/* Collapsed by default (max-height:0 + overflow:hidden) — the site-wide toggle
   JS (services/wp-provisioner/server.js) opens an item by setting an inline
   max-height (scrollHeight) and closes it by clearing that inline style, which
   falls back to this rule. Vertical padding must stay at 0 here: max-height
   only clamps the content box, so any padding here would still paint as an
   empty strip while "closed". Vertical spacing instead lives on the inner
   <p> (only visible once the container's max-height reveals it). */
:where(.faq-item__answer) {
  max-height: 0;
  overflow: hidden;
  padding-inline: var(--space-4, 1rem);
  transition: max-height .3s ease;
}
:where(.faq-item__answer p) { margin: 0 0 var(--space-4, 1rem); }
/* Editor-only exception: this stylesheet also loads in the block-editor canvas
   (enqueue_block_assets), but the frontend FAQ toggle JS does NOT run inside
   the editor iframe — without this, answers would be stuck at max-height:0 and
   invisible in ServerSideRender previews. `.editor-styles-wrapper` exists only
   on the editor canvas body, never on the frontend, so frontend collapse
   behavior is unaffected. Vertical spacing while expanded already comes from
   the inner <p> margin above; no padding change needed. */
:where(.editor-styles-wrapper .faq-item__answer) { max-height: none; }

/* pricing */
:where(.pricing-card.featured) { border-color: var(--color-accent, var(--color-primary, currentColor)); }
:where(.pricing-price) { font-size: 2rem; font-weight: 700; margin-block: var(--space-3, .75rem); }
:where(.pricing-badge) { display: inline-block; padding: .2em .7em; border-radius: 999px; background: var(--color-accent, var(--color-primary, #333)); color: var(--color-on-accent, #fff); font-size: .75rem; }

/* cta banner — the cta-banner block renders section.cta-section > .container >
   .cta-content (h2 + p + a.btn), no button-group wrapper. Most semantic families
   only add decorative touches (gradients, pseudo-element shapes) on top of a
   plain centered layout, so the baseline here is a centered content column with
   a subtle surface tint (same token pattern as the card rule above) rather than
   an accent-color fill, since accent-on-text contrast isn't guaranteed across
   families. Families that DO style .cta-section/.cta-content override every
   property below via a real selector (zero specificity here loses cleanly). */
:where(.cta-section) {
  text-align: center;
  background: var(--color-bg-card, var(--color-surface, transparent));
  border-radius: var(--radius-lg, 12px);
}
:where(.cta-content) {
  max-width: 640px;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4, 1rem);
}
:where(.cta-content .btn) { margin-top: var(--space-2, .5rem); }

/* contact / about two-column */
:where(.contact-grid, .about-grid) { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--space-8, 2rem); align-items: start; }
/* WS-B (run3 2026-07-12): about-grid vertical rhythm, part 1 — bottom half
   (about-stats/about-mv top margins) is further below, next to those
   rules. Plain (non-:where()) specificity; see file-top note on scoping.
   Fixes 008/027/030/031 about overlaps + MINOR spacing-collapse findings. */
.about-grid { margin-bottom: var(--space-10, 3rem); }
/* Mid-section stats heading: about/render.php emits a SECOND
   .section-heading-group (sp_heading()-style markup, hand-rolled) right
   after .about-grid, only when statsTitle is set. Scoped via adjacent-
   sibling combinator so the generic .section-heading-group block-heading
   spacing used by every other block (services-grid, features-grid, …) is
   left untouched. */
.about-grid + .section-heading-group { margin-top: var(--space-10, 3rem); }

/* contact-info-card — canonical `.contact-info-grid > .contact-info-card >
   (.contact-info-icon, .contact-info-label, .contact-info-value)` markup
   (shared contract; coordinated with WS-C's contact-info/contact-form
   render.php — verified directly against the current render output, both
   blocks emit this exact structure per populated field). Fixes 32
   contact-card/label degradations (plain unstyled rows, report P1#1).
   `.contact-item`/`.contact-grid` immediately below stay as backward
   compat for any remaining flat-row usage. */
.contact-info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-5, 1.25rem);
  margin-top: var(--space-6, 1.5rem);
}
.contact-info-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2, .5rem);
  padding: var(--space-5, 1.25rem);
  background: var(--sp-surface-card);
  border: 1px solid var(--color-border, rgba(128, 128, 128, .16));
  border-radius: var(--radius-lg, 12px);
}
.contact-info-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md, 8px);
  background: rgba(128, 128, 128, .12);
  background: color-mix(in srgb, var(--sp-action) 14%, transparent);
  color: var(--sp-action);
}
.contact-info-icon svg { width: 20px; height: 20px; }
.contact-info-label {
  font-size: .78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  opacity: .7;
}
.contact-info-value { font-size: 1rem; color: inherit; word-break: break-word; }
.contact-info-value a { color: inherit; text-decoration: none; }
.contact-info-value a:hover { color: var(--sp-action); }

/* .contact-rows/.contact-row — sp_contact_rows() label/value overflow rows
   (an hours breakdown, social links, …) that don't fit the singular
   contact-info fields; previously entirely unstyled in this file (report
   P1#1 names `.contact-rows` explicitly as a missing baseline). */
.contact-rows { display: flex; flex-direction: column; gap: var(--space-2, .5rem); margin-top: var(--space-4, 1rem); }
.contact-row { display: flex; flex-wrap: wrap; gap: var(--space-2, .5rem); font-size: .95rem; }
.contact-row strong { font-weight: 600; }
.contact-row a { color: var(--sp-action); text-decoration: none; }
.contact-row a:hover { text-decoration: underline; }

:where(.contact-item) { display: flex; align-items: center; gap: var(--space-3, .75rem); margin-bottom: var(--space-3, .75rem); }
:where(.contact-item svg) { width: 20px; height: 20px; flex: none; color: var(--color-accent, var(--color-primary, currentColor)); }
:where(.about-image) { width: 100%; border-radius: var(--radius-lg, 12px); }

/* about stats — canonical .about-stats grid of value/label cards (residual: the
   about block emitted these but nothing styled them, so value+label rendered as
   glued inline text instead of the template's 4-up stat grid).
   WS-B (run3 2026-07-12): vertical rhythm, part 2 — margin-top bumped and
   moved to a plain (non-:where()) rule (see the about-grid note above and
   the file-top scoping note) so it reliably beats accidental
   zero-specificity family resets. .about-stat/.about-stat__value/
   .about-stat__label (typography, not overlap-critical) stay :where(). */
.about-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--space-6, 1.5rem); margin-top: var(--space-10, 3rem); text-align: center; }
:where(.about-stat) { display: flex; flex-direction: column; gap: var(--space-1, .25rem); }
:where(.about-stat__value) { font-size: 2.5rem; font-weight: 700; line-height: 1.1; color: var(--color-accent, var(--color-primary, currentColor)); }
:where(.about-stat__label) { font-size: .9rem; opacity: .8; }

/* about mission / vision two-column (canonical about block, when present).
   margin-top bumped + border-top added as a visible separator that survives
   even if an ancestor/sibling margin collapses unexpectedly — belt-and-
   braces against the 008/027/030/031 overlap pattern where prior spacing
   alone did not fully prevent visual collision in some families. */
.about-mv {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-8, 2rem);
  margin-top: var(--space-10, 3rem);
  padding-top: var(--space-8, 2rem);
  border-top: 1px solid var(--color-border, rgba(128, 128, 128, .14));
}
:where(.about-mv__title) { font-size: 1.5rem; font-weight: 700; margin-bottom: var(--space-2, .5rem); }

/* static contact form (fallback when CF7 is not installed) */
:where(.contact-form) { display: flex; flex-direction: column; gap: var(--space-4, 1rem); }
:where(.contact-form .form-group) { display: flex; flex-direction: column; gap: var(--space-2, .4rem); }
:where(.contact-form label) { font-size: .9rem; font-weight: 500; }
/* WS-B (run3 2026-07-12): explicit background/text/border/placeholder/focus/
   autofill colors, plain (non-:where()) specificity — fixes dark-form
   families rendering low/no-contrast inputs (report P1 isolated-cause table,
   063-glass-float example) when the old zero-specificity fallback lost to
   an accidental resets and the input fell through to browser defaults
   (white bg) against a page that expects light-on-dark text, or vice
   versa. Still built from family surface/text tokens (with safe neutral
   fallbacks) so the baseline matches family theme even when it wins the
   cascade — this does not hardcode a light or dark scheme. Submit button
   is `<button class="btn btn-primary">` (render-helpers.php) — covered by
   the `.btn`/`.btn-primary` rules above, which apply to any element. */
.contact-form .form-input {
  width: 100%;
  padding: var(--space-3, .75rem) var(--space-4, 1rem);
  border: 1px solid var(--color-border, rgba(128, 128, 128, .3));
  border-radius: var(--radius-md, 8px);
  background: var(--color-surface, var(--color-bg-alt, rgba(128, 128, 128, .06)));
  color: var(--color-text, inherit);
  font: inherit;
  box-sizing: border-box;
}
.contact-form .form-input::placeholder {
  color: var(--color-text-muted, rgba(128, 128, 128, .65));
  opacity: 1;
}
.contact-form .form-input:focus {
  outline: none;
  border-color: var(--sp-action);
  box-shadow: 0 0 0 3px rgba(128, 128, 128, .18);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--sp-action) 22%, transparent);
}
.contact-form .form-input:-webkit-autofill,
.contact-form .form-input:-webkit-autofill:hover,
.contact-form .form-input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--color-text, currentColor);
  box-shadow: 0 0 0 1000px var(--color-surface, var(--color-bg-alt, rgba(128, 128, 128, .06))) inset;
  transition: background-color 9999s ease-in-out 0s;
}
:where(.contact-form .form-textarea) { min-height: 120px; resize: vertical; }
:where(.contact-form .btn) { align-self: flex-start; }

/* map */
:where(.map-embed iframe) { width: 100%; border: 0; border-radius: var(--radius-lg, 12px); }
:where(.map-placeholder) {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-3, .75rem); min-height: 340px; border-radius: var(--radius-lg, 12px);
  background: var(--color-surface, rgba(128,128,128,.06));
  background-image: linear-gradient(var(--color-border, rgba(128,128,128,.12)) 1px, transparent 1px), linear-gradient(90deg, var(--color-border, rgba(128,128,128,.12)) 1px, transparent 1px);
  background-size: 40px 40px;
  color: var(--color-text-muted, var(--color-text, inherit));
}
:where(.map-placeholder svg) { width: 40px; height: 40px; color: var(--color-accent, var(--color-primary, currentColor)); }

/* generic safety */
:where(.sitepilot-block-preview img) { max-width: 100%; }

/* ── sitepilot/media-feature (Wave-2 NEW block) ───────────────────────────────
   Image + badge + multi-paragraph body + CTA "featured" band. Two-column, with a
   media-right modifier. Reuses fleet .section/.container/.section-* + .btn rules;
   only the grid + column + spacing invariants live here. Low, non-zero specificity
   (plain classes, enqueued before family base.css) so a family can still restyle. */
.media-feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--space-8, 2.5rem);
  align-items: center;
}
.media-feature--media-right .media-feature__media { order: 2; }
.media-feature__media { min-width: 0; }
.media-feature__media img,
.media-feature__img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius-lg, 12px);
}
.media-feature__content { min-width: 0; }
.media-feature__text { margin-top: var(--space-4, 1rem); }
.media-feature__text > p { margin: 0 0 var(--space-3, 0.75rem); }
.media-feature__text > p:last-child { margin-bottom: 0; }
.media-feature__cta-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3, 0.75rem);
  margin-top: var(--space-5, 1.5rem);
}

/* process-steps: the block wraps title+text in .process-content/.process-step__content.
   A flex .process-step (number | content) needs the content child to be shrinkable
   (min-width:0) or long descriptions overflow the row and clip. */
.process-step__content, .process-content { flex: 1 1 auto; min-width: 0; }
.process-step .process-title, .process-step .process-text { overflow-wrap: break-word; }

/* ── sitepilot/product-highlight feature checklist icon (BUG B, 2026-07-13) ──
   product-highlight/render.php emits `<li>` + a BARE sp_icon('check') <svg>
   (no `.feature-icon`/`.service-icon` wrapper div, unlike services-grid/
   features-grid — the only two other blocks that render a raw sp_icon()
   already have their own sized wrapper above). That <svg> has no width/
   height attribute, so its intrinsic box is the browser UA default
   (300x150). 053-storefront-classic/base.css (and other families) ship a
   common responsive-media reset `img, video, iframe, svg { max-width: 100%;
   height: auto }` — specificity (0,0,1), a bare `svg` type selector — which
   then stretches that 300x150 default to the FULL width of the feature
   list's flex/column container (max-width:100%) with height scaled to
   match (height:auto), rendering a multi-hundred-px checkmark that
   destroys the product page (report BUG B). `.product-features li svg` is
   (0,1,2) specificity, unconditionally beating a bare `svg` selector
   regardless of stylesheet load order — this caps the icon at a normal
   checklist-glyph size without touching any other inline svg in the file
   (map/contact/service icons already have their own sizing rules above;
   decorative/full-width media never matches this selector, so nothing
   legitimately large is shrunk). */
.product-features li svg {
  width: 1.25rem;
  height: 1.25rem;
  margin-inline-end: var(--space-2, .5rem);
  vertical-align: -0.3em;
  flex: none;
  color: var(--sp-action);
}
