/* ----------------------------------------------------------------------
 * Compound Section — shared chrome for the new compound-widget renderer
 * (CompoundSectionRenderer). Owns layout scaffolding (split / stack /
 * background / grid), overlay, decorative section background, and the
 * media-slot frame. Per-slot text/button styling is handled by each
 * child element's own renderer + CSS — never re-declared here.
 *
 * All colors, radii, and shadows resolve from --site-* tokens with safe
 * fallbacks — never hardcoded brand colors.
 * ---------------------------------------------------------------------- */

.el-compound-section {
  position: relative;
  isolation: isolate;
  width: 100%;
}

.el-compound-section__section-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  pointer-events: none;
}

.el-compound-section--has-section-bg::before {
  /* Subtle scrim above the decorative section background to keep content
     readable when the user hasn't configured an explicit overlay. */
  content: '';
  position: absolute;
  inset: 0;
  background: var(--site-overlay, transparent);
  z-index: 1;
  pointer-events: none;
}

.el-compound-section__inner {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-block: 0;
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: center;
}

.el-compound-section__inner:not(.el-site-container) {
  max-width: var(--cs-inner-max-width, 100%);
  margin: 0 auto;
  padding-inline: var(--cs-inner-padding-inline, 1.25rem);
}

/* ── Split layout (default for cta-with-image) ──────────────────────── */
.el-compound-section--split .el-compound-section__inner {
  grid-template-columns: minmax(min(360px, 40vw), 1fr) minmax(min(360px, 40vw), 1fr);
}

.el-compound-section--split .el-compound-section__media {
  /* Default split-layout media aspect so the image/video column has
     intrinsic height. Without this, `align-items: center` on the grid +
     `height: 100%` on the inner <img>/<video> produces a zero-height
     image that cannot resolve its own natural proportions (the old
     pre-migration CtaWithImage renderer had its own dedicated layout
     with a hardcoded ratio — the compound shell needed an equivalent).
     User-supplied imageAspectRatio still wins via --cs-media-aspect.
     Default cascades from the unified card token (--site-card-media-aspect)
     so split-layout media aligns with neighbouring carousel/grid card media. */
  aspect-ratio: var(--cs-media-aspect, var(--site-card-media-aspect, 16 / 10));
  /* Order is driven by the --cs-media-order CSS var so the renderer can
     flip the split layout (image-left vs image-right) without re-emitting
     the CSS rule. Default 1 keeps content first; renderer sets -1 for
     image-left. */
  order: var(--cs-media-order, 1);
}

@container site style(--site-bp: mobile) {
  .el-compound-section--split .el-compound-section__inner {
    grid-template-columns: 1fr;
  }
  .el-compound-section--split .el-compound-section__media {
    order: 0;
  }
}
@media (max-width: 768px) {
  .el-compound-section--split .el-compound-section__inner {
    grid-template-columns: 1fr;
  }
  .el-compound-section--split .el-compound-section__media {
    order: 0;
  }
}

/* ── Stack layout ──────────────────────────────────────────────────── */
.el-compound-section--stack .el-compound-section__inner {
  grid-template-columns: 1fr;
  text-align: center;
  justify-items: center;
}

/* ── Grid layout (cards-style — header stacks above a repeating-child grid) */
.el-compound-section--grid .el-compound-section__inner {
  grid-template-columns: 1fr;
  container: compound-grid / inline-size;
  /* Grid rows stack top-down; never stretch vertically when the parent
     Section is tall. Without this, the inner grid inherits
     `align-items: center` from the base rule and each row (header,
     cards) grows to an equal share of the Section's height, creating
     enormous gaps between the heading and the cards row. */
  align-items: start;
  /* Grid layouts (feature-cards, intent-router, testimonials, …) need a
     more generous horizontal gutter than the 1.25rem base so cards don't
     crowd the section edges — especially on dark bands where the card
     chrome gets lost against the band color. Authors can still override
     via `contentPaddingInline` → `--cs-inner-padding-inline`. */
}
.el-compound-section--grid .el-compound-section__inner:not(.el-site-container) {
  padding-inline: var(--cs-inner-padding-inline, clamp(1.25rem, 4vw, 3rem));
}
/* Default: section header content is center-aligned above the cards grid.
   Widgets that want left-aligned headers (rare) can override
   `--cs-text-align` via their renderer. */
.el-compound-section--grid .el-compound-section__content {
  text-align: var(--cs-text-align, center);
  justify-items: center;
  width: 100%;
}

/* Repeating-child cards region. `--cs-grid-columns` is set by the parent
   renderer from its "Columns" control; collapses to a single column on
   narrow viewports so mobile stacks naturally. `--cs-grid-gap` and
   `--cs-card-min-width` are fine-grained overrides with safe defaults. */
.el-compound-section__cards {
  display: grid;
  grid-template-columns: repeat(
    var(--cs-grid-columns, 3),
    minmax(min(var(--cs-card-min-width, 240px), calc(50% - var(--cs-grid-gap, 1.5rem) / 2)), 1fr)
  );
  /* Equal-height rows + equal-width cells. `grid-auto-rows: 1fr` makes every
     row the height of its tallest card so cards never look staggered between
     rows. `align-items: stretch` (already grid default) is reasserted so card
     containers fill their cell. Children must also opt in via 100% sizing,
     otherwise an explicit width on a card (e.g. min-content from a flex
     child) keeps that card narrower than its column. */
  grid-auto-rows: 1fr;
  align-items: stretch;
  justify-items: stretch;
  /* Gap defaults to the unified card-gap token so testimonials grids,
     feature-cards grids, services grids, etc. share the same inter-card
     spacing on every breakpoint. */
  gap: var(--cs-grid-gap, var(--site-card-gap, 1.5rem));
  width: 100%;
}
.el-compound-section__cards > * {
  /* Stretch every grid item to fill its allocated column AND row. Without
     this, a card whose intrinsic content is narrower than 1fr would shrink
     to its content width, producing the "row 1 wide / row 2 narrow" pattern
     reported by authors. min-width: 0 prevents flex/grid blowout from long
     unbroken strings. */
  width: 100%;
  height: 100%;
  min-width: 0;
  align-self: stretch;
  justify-self: stretch;
}

@container site style(--site-bp: mobile) {
  .el-compound-section__cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* Equal-height rows preserved on mobile per the global card-equal-height
       contract (see er-card-containers.css § Universal Card Equal-Height).
       The previous mobile override dropped `grid-auto-rows: 1fr` to avoid
       whitespace under short cards, but inconsistent card heights look
       worse than slack space — author intent for "row of cards" is that
       they share a baseline. Cards opt in via flex-column + `flex: 1`
       on body so they distribute internally instead of leaving a void. */
    grid-auto-rows: 1fr;
    align-items: stretch;
  }
  .el-compound-section__cards > * {
    height: 100%;
    align-self: stretch;
  }
}
@media (max-width: 768px) {
  .el-compound-section__cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* Equal-height rows preserved on mobile per the global card-equal-height
       contract (see er-card-containers.css § Universal Card Equal-Height).
       The previous mobile override dropped `grid-auto-rows: 1fr` to avoid
       whitespace under short cards, but inconsistent card heights look
       worse than slack space — author intent for "row of cards" is that
       they share a baseline. Cards opt in via flex-column + `flex: 1`
       on body so they distribute internally instead of leaving a void. */
    grid-auto-rows: 1fr;
    align-items: stretch;
  }
  .el-compound-section__cards > * {
    height: 100%;
    align-self: stretch;
  }
}

/* was @media(max-width:480px); bucketed to mobile (480 ≤ 768) */
@container site style(--site-bp: mobile) {
  .el-compound-section__cards {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 480px) {
  .el-compound-section__cards {
    grid-template-columns: 1fr;
  }
}

@container compound-grid (max-width: 840px) {
  .el-compound-section__cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@container compound-grid (max-width: 560px) {
  .el-compound-section__cards {
    grid-template-columns: 1fr;
  }
}

/* ── Bento layout (irregular grid for marketing feature stacks) ────────── */
/* 6-column grid where each card spans 2 columns by default and every 4th
   card spans 4 columns, producing an alternating wide/narrow rhythm:
     row 1: [4][2]
     row 2: [2][2][2]
     row 3: [4][2]
     row 4: [2][2][2]
   Works for any card count ≥ 4. With 3 cards or fewer, the irregularity
   doesn't read — author should keep `layout: grid`. Mobile collapses to
   single column to preserve readability. */
.el-compound-section--bento .el-compound-section__cards {
  grid-template-columns: repeat(6, minmax(0, 1fr));
  /* Force equal-height rows so wide cards don't collapse vertically when
     paired with shorter neighbors. The grid-auto-rows: 1fr from the base
     `.el-compound-section__cards` rule still applies. */
}
.el-compound-section--bento .el-compound-section__cards > * {
  grid-column: span 2;
}
.el-compound-section--bento .el-compound-section__cards > *:nth-child(4n+1) {
  grid-column: span 4;
}
/* Shell-as-grid-item: in bento, CompoundSectionRenderer drops the
   shell's `display: contents` so the span rules above apply to it.
   The inner card needs to fill the shell so visuals match the other
   layouts where the card itself is the grid item. */
.el-compound-section--bento .el-compound-section__cards > .el-compound-section__card-shell {
  display: flex;
  min-width: 0;
}
.el-compound-section--bento .el-compound-section__cards > .el-compound-section__card-shell > * {
  width: 100%;
  display: flex;
}
@container site style(--site-bp: mobile) or style(--site-bp: tablet) {
  .el-compound-section--bento .el-compound-section__cards {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  .el-compound-section--bento .el-compound-section__cards > * { grid-column: span 2; }
  .el-compound-section--bento .el-compound-section__cards > *:nth-child(4n+1) {
    grid-column: span 4;
  }
}
@media (max-width: 1024px) {
  .el-compound-section--bento .el-compound-section__cards {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  .el-compound-section--bento .el-compound-section__cards > * { grid-column: span 2; }
  .el-compound-section--bento .el-compound-section__cards > *:nth-child(4n+1) {
    grid-column: span 4;
  }
}
@container site style(--site-bp: mobile) {
  .el-compound-section--bento .el-compound-section__cards {
    grid-template-columns: 1fr;
  }
  .el-compound-section--bento .el-compound-section__cards > *,
  .el-compound-section--bento .el-compound-section__cards > *:nth-child(4n+1) {
    grid-column: span 1;
  }
}
@media (max-width: 768px) {
  .el-compound-section--bento .el-compound-section__cards {
    grid-template-columns: 1fr;
  }
  .el-compound-section--bento .el-compound-section__cards > *,
  .el-compound-section--bento .el-compound-section__cards > *:nth-child(4n+1) {
    grid-column: span 1;
  }
}

/* ── Carousel layout (cards slot in a horizontal scroll-snap viewport) ── */
/* Shares the --carousel-* arrow/dot contract from buildCarouselStyleVars
   with feature-cards / testimonials pre-Stage-4 so arrow-styling controls
   keep working. `--cs-cards-per-view` drives per-card width via pure CSS
   calc; `--cs-carousel-gap` controls inter-card spacing. Collapses to 1
   column below `--cs-carousel-mobile-breakpoint`. */
.el-compound-section__carousel {
  position: relative;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  /* Outer container — unclipped so the absolutely-positioned arrows can
     sit at left:8px / right:8px without fighting the inner frame's clip.
     Width clamps to the parent's column to keep the section tidy. */
  box-sizing: border-box;
}
.el-compound-section__carousel-frame {
  /* The "shadow bleed" container.
     - Vertical: 80px so card drop-shadows are never clipped at top/bottom.
     - Horizontal: equal to the inter-card gap so the active card's side
       shadow can bleed up to the gap distance, but the NEXT card's
       leading edge — which sits exactly `gap` past the frame edge —
       lands at the bleed boundary and never peeks. Net: shadow visible,
       no peek of the adjacent card. If the gap is smaller than the
       shadow extent you'll lose a few px of side-shadow softness; the
       trade-off is intentional.
     We don't use overflow:auto here; horizontal motion is driven by
     `transform: translate3d` on the inner track, so this layer never
     needs native scrolling. */
  overflow: clip;
  overflow-clip-margin-top: var(--site-carousel-shadow-bleed-block, 80px); overflow-clip-margin-bottom: var(--site-carousel-shadow-bleed-block, 80px);
  overflow-clip-margin-left: var(--site-carousel-shadow-bleed-inline, var(--cs-carousel-gap, var(--site-card-gap, 1.5rem))); overflow-clip-margin-right: var(--site-carousel-shadow-bleed-inline, var(--cs-carousel-gap, var(--site-card-gap, 1.5rem)));
  width: 100%;
  max-width: 100%;
  min-width: 0;
  box-sizing: border-box;
  /* Establish an inline-size container so child cards inside the
     `width: max-content` track can still size proportionally to the
     FRAME'S width via `cqw` (container-query units). Without this, the
     cards' `calc(100% - ...)` would resolve against the track's
     intrinsic width — a circular dependency. */
  container-type: inline-size;
}
.el-compound-section__carousel-track {
  display: flex;
  /* Carousel inter-card spacing defaults to the unified card-gap token
     so a testimonials carousel, feature-cards carousel, and services
     carousel stacked on the same page share identical inter-card spacing
     at every breakpoint. */
  gap: var(--cs-carousel-gap, var(--site-card-gap, 1.5rem));
  width: max-content;
  /* GPU-accelerated transform; hint to the compositor. */
  will-change: transform;
  /* Allow vertical page scroll while the user grabs the track for a
     horizontal swipe. The hook's pointer handler manages the horizontal
     drag explicitly. */
  touch-action: pan-y;
  /* Stretch keeps cards visually equal-height within a row. Individual
     cards control internal spacing via their own flex layout. */
  align-items: stretch;
  /* Don't let sub-pixel rounding round-trip create a 1px sliver after
     the wrap teleport. */
  backface-visibility: hidden;
}
.el-compound-section__carousel-slide {
  /* Cards size to a fraction of the frame's inline size via `cqw`
     (container-query width). Each card = (100% of frame minus inter-card
     gaps) / cardsPerView. */
  flex: 0 0 calc(
    (100cqw - var(--cs-carousel-gap, var(--site-card-gap, 1.5rem)) * (var(--cs-cards-per-view, 3) - 1)) /
    var(--cs-cards-per-view, 3)
  );
  min-width: 0;
  box-sizing: border-box;
  display: flex;
  align-items: stretch;
  padding-block: var(--site-carousel-slide-bleed-block, 16px);
  padding-inline: var(--site-carousel-slide-bleed-inline, clamp(12px, var(--cs-carousel-gap, var(--site-card-gap, 1.5rem)), 32px));
}
.el-compound-section__carousel-slide > * {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
}
.el-compound-section__carousel-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: var(--carousel-arrow-size, 44px);
  height: var(--carousel-arrow-size, 44px);
  border-radius: var(--carousel-arrow-radius, 50%);
  border: none;
  background: var(--carousel-arrow-bg, var(--site-accent, var(--site-primary)));
  color: var(--carousel-arrow-color, var(--site-accent-fg, var(--site-primary-fg, currentColor)));
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--carousel-arrow-shadow, 0 4px 16px rgba(0, 0, 0, 0.18));
  transition: transform 160ms ease, filter 160ms ease, box-shadow 160ms ease;
}
/* Arrows sit INSIDE the carousel wrapper (not offset negatively) so they
   can never push the section wider than its parent. They still float over
   the first/last card edges but stay fully within the containing box. */
.el-compound-section__carousel-nav--prev { left: 8px; }
.el-compound-section__carousel-nav--next { right: 8px; }
.el-compound-section__carousel-nav:hover {
  filter: var(--carousel-arrow-hover-filter, brightness(1.12));
  background: var(--carousel-arrow-hover-bg, var(--carousel-arrow-bg, var(--site-accent, var(--site-primary))));
  color: var(--carousel-arrow-hover-color, var(--carousel-arrow-color, currentColor));
  transform: translateY(-50%) scale(var(--carousel-arrow-hover-scale, 1.06));
}

/* ── Theme-driven variants ───────────────────────────────────────────────
 * Nav buttons can opt into the site's primary or secondary button palette so
 * a theme swap restyles every carousel across the platform. `custom` keeps
 * the legacy `--carousel-arrow-*` tokens (used for fully bespoke styling).
 * Selectors use the variant modifier so a caller can still override locally
 * by emitting `--carousel-arrow-bg` / `-color` on the section. */
.el-compound-section__carousel-nav--primary {
  background: var(--site-btn-primary-bg, var(--site-primary, currentColor));
  color: var(--site-btn-primary-text, var(--site-primary-fg, currentColor));
  border: 1px solid var(--site-btn-primary-bg, var(--site-primary, currentColor));
}
.el-compound-section__carousel-nav--primary:hover {
  background: var(--site-btn-primary-hover-bg, var(--site-primary, currentColor));
  color: var(--site-btn-primary-hover-text, var(--site-primary-fg, currentColor));
  border-color: var(--site-btn-primary-hover-bg, var(--site-primary, currentColor));
  filter: none;
}

.el-compound-section__carousel-nav--secondary {
  /* Glassy outline button — same visual contract as the availability-
     table secondary variant. The translucent fill plus backdrop-filter
     blur produces a "frosted" look on every background (light, dark,
     image, video) so the nav reads consistent across every carousel.
     Falls back to a flat outline in browsers without backdrop-filter. */
  background: var(--site-btn-outline-bg, color-mix(in srgb, currentColor 10%, transparent));
  backdrop-filter: blur(10px) saturate(1.4);
  -webkit-backdrop-filter: blur(10px) saturate(1.4);
  color: var(--site-btn-outline-text, var(--site-primary, currentColor));
  border: 1px solid var(--site-btn-outline-border, color-mix(in srgb, currentColor 25%, transparent));
  box-shadow: none;
}
.el-compound-section__carousel-nav--secondary:hover {
  background: var(--site-btn-outline-hover-bg, color-mix(in srgb, currentColor 18%, transparent));
  color: var(--site-btn-outline-hover-text, var(--site-primary-fg, currentColor));
  border-color: var(--site-btn-outline-border, color-mix(in srgb, currentColor 40%, transparent));
  filter: none;
}

@container site style(--site-bp: mobile) {
  /* Mobile carousel snap contract — clean 1-card-per-step, no half-card
     peek. Each piece below addresses one source of "half card visible" on
     mobile:
       1. slide flex-basis = 100cqw  → exactly one slide fits the frame
       2. track gap = 0              → step size is exactly slide width
                                        (no inter-card gap visible at rest)
       3. slide padding-inline = 0   → slide IS the card, no inner gutter
       4. clip-margin-inline = 0     → frame strictly clips, no edge bleed
                                        (shadow bleed compromised on mobile
                                        in exchange for a clean snap; the
                                        card carries its own shadow inside
                                        the frame anyway).
     Result: at rest, frame contains exactly one card with no neighbour
     pixels visible. During the brief 600ms transit only the moving slide
     pair shows, then snaps clean. */
  .el-compound-section__carousel-slide {
    flex-basis: 100cqw;
    flex-shrink: 0;
    padding-inline: 0;
  }
  .el-compound-section__carousel-track {
    gap: 0;
  }
  .el-compound-section__carousel-frame {
    overflow-clip-margin-left: 0; overflow-clip-margin-right: 0;
  }
}
@media (max-width: 768px) {
  /* Mobile carousel snap contract — clean 1-card-per-step, no half-card
     peek. Each piece below addresses one source of "half card visible" on
     mobile:
       1. slide flex-basis = 100cqw  → exactly one slide fits the frame
       2. track gap = 0              → step size is exactly slide width
                                        (no inter-card gap visible at rest)
       3. slide padding-inline = 0   → slide IS the card, no inner gutter
       4. clip-margin-inline = 0     → frame strictly clips, no edge bleed
                                        (shadow bleed compromised on mobile
                                        in exchange for a clean snap; the
                                        card carries its own shadow inside
                                        the frame anyway).
     Result: at rest, frame contains exactly one card with no neighbour
     pixels visible. During the brief 600ms transit only the moving slide
     pair shows, then snaps clean. */
  .el-compound-section__carousel-slide {
    flex-basis: 100cqw;
    flex-shrink: 0;
    padding-inline: 0;
  }
  .el-compound-section__carousel-track {
    gap: 0;
  }
  .el-compound-section__carousel-frame {
    overflow-clip-margin-left: 0; overflow-clip-margin-right: 0;
  }
  /* Testimonials inherits the same mobile carousel width + padding rules
     as every other compound carousel — no widget-scoped overrides. The
     legacy `[data-compound-kind="testimonials"]` rules that constrained
     this widget to a narrow column on tablet/mobile have been removed
     because they shipped before the unified site-container contract and
     diverged the testimonials track from feature-cards / services /
     image-carousel etc. (See user feedback 2026-04-29.) */
}

/* was @media(max-width:480px); bucketed to mobile (480 ≤ 768) */
@container site style(--site-bp: mobile) {
  /* Smallest screens — arrow size shrinks; viewport gutter already
     tightened to 1rem at the 768px breakpoint above. */
  .el-compound-section__carousel-nav--prev { left: 4px; }
  .el-compound-section__carousel-nav--next { right: 4px; }
  .el-compound-section__carousel-nav { width: 36px; height: 36px; }
}
@media (max-width: 480px) {
  /* Smallest screens — arrow size shrinks; viewport gutter already
     tightened to 1rem at the 768px breakpoint above. */
  .el-compound-section__carousel-nav--prev { left: 4px; }
  .el-compound-section__carousel-nav--next { right: 4px; }
  .el-compound-section__carousel-nav { width: 36px; height: 36px; }
}

/* ── Background layout ─────────────────────────────────────────────── */
/* `--cs-bg-min-height` defaults to `auto` so a flat-variant CTA band
   (no background image/video) collapses to its content height. When
   a bg image or video is present, the renderer projects the taller
   clamp so the section still reads as a hero surface. */
.el-compound-section--background {
  min-height: var(--cs-bg-min-height, auto);
  display: grid;
  align-items: var(--cs-bg-align-items, stretch);
  overflow: hidden;
  border-radius: var(--cs-section-radius, 0);
}

.el-compound-section__bg-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.el-compound-section__bg-media .el-compound-section__media {
  width: 100%;
  height: 100%;
}

.el-compound-section__bg-media img,
.el-compound-section__bg-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.el-compound-section__overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--cs-overlay-color, transparent);
  opacity: var(--cs-overlay-opacity, 0);
  pointer-events: none;
}

.el-compound-section--background .el-compound-section__overlay {
  /* Direction-aware gradient when set via the SECTION_OVERLAY_FIELDS
     control — falls through to a flat tint when direction is unset. */
  background: linear-gradient(
    var(--cs-overlay-direction, to bottom),
    var(--cs-overlay-color, transparent),
    transparent
  );
}

.el-compound-section__bg-content {
  position: relative;
  z-index: 2;
  width: 100%;
  padding: var(--cs-bg-content-padding, clamp(2rem, 6vw, 4rem) 1.5rem);
  display: grid;
  gap: 1rem;
}

.el-compound-section__bg-content:not(.el-site-container) {
  max-width: min(1200px, 100%);
  margin: 0 auto;
}

/* ── Slots ─────────────────────────────────────────────────────────── */
.el-compound-section__content {
  display: grid;
  gap: 0.75rem;
  align-content: start;
  justify-items: inherit;
}
/* Tighten header-slot rhythm. Eyebrow/heading/body sit immediately
   below each other with a predictable gap; without this rule the
   children inherit arbitrary per-element margins and produce massive
   internal whitespace (e.g. intent-router / feature-cards header).
   The slot wrappers use `display: contents` so the margin-reset must
   be applied to the actual child element within __content as well —
   that's the belt-and-suspenders selector below. */
.el-compound-section__slot--eyebrow { margin-bottom: 0; }
.el-compound-section__slot--heading .el-heading,
.el-compound-section__content .el-heading { margin-bottom: 0; }
.el-compound-section__slot--heading .el-heading__text { margin: 0; }
.el-compound-section__slot--body .el-rich-text,
.el-compound-section__slot--body > p,
.el-compound-section__content .el-rich-text > p:first-child,
.el-compound-section__content .el-rich-text > p:last-child { margin: 0; }

/* ── Universal slot-child margin baseline ─────────────────────────
 * Every compound-widget renderer (testimonial-card, feature-card,
 * review-card, faq, gallery-item, content-slide, blog-card, banner-bar,
 * hud, compound-section itself, etc.) tags each slot wrapper with
 * `data-slot-role`. The bare `.el-heading` and `.el-rich-text > p`
 * baselines are already 0 (er-content.css), but some renderers still
 * wrap other element types (buttons, images, custom blocks) as slot
 * children — those `.el` wrappers and any nested elements outside the
 * heading/rich-text family get their margins zeroed here too, and the
 * inter-paragraph gap is tightened to 0.5em for dense card layouts.
 *
 * Cascade contract: this rule has specificity (0,2,0) on the heading
 * selector — it beats the (0,1,0) site-wide default but loses to any
 * widget-specific `.el-<widget>__card .el-heading` (0,2,0 declared
 * later) and to the design panel's inline `style="margin-*:..."`
 * (always wins). Per-widget custom spacing and per-element
 * design-panel values continue to override this baseline. */
[data-slot-role] > .el,
[data-slot-role] .el-heading,
[data-slot-role] .el-eyebrow,
[data-slot-role] .el-subheading,
[data-slot-role] .el-rich-text {
  margin: 0;
}
[data-slot-role] .el-rich-text > p + p { margin-top: 0.5em; }

.el-compound-section__slot {
  /* Each slot is just a transparent wrapper — its child element
     supplies its own styling via that element's renderer + CSS. */
  display: contents;
}

/* Empty header block — when a widget has no eyebrow/heading/body/CTA/etc
   slots populated (e.g. a bare stats band with only cards), the
   `.el-compound-section__content` container renders but is empty. Leaving it
   as a flex/grid item still claims its share of the __inner `gap`, pushing
   the cards row off-center. Collapse it so the cards become the sole row. */
.el-compound-section__content:empty {
  display: none;
}

.el-compound-section__slot--heading {
  color: var(--cs-heading-color, inherit);
}

.el-compound-section__slot--body {
  color: var(--cs-body-color, inherit);
}

/* Eyebrow slot — sits above the heading via DOM order. Inherits the
   parent grid `gap` for vertical rhythm; color/typography flow from the
   child rich-text's own props (theme tokens). */

.el-compound-section__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

.el-compound-section__media {
  position: relative;
  border-radius: var(--cs-media-radius, 0);
  overflow: hidden;
  box-shadow: var(--cs-media-shadow, var(--el-media-shadow, none));
  /* Media aspect defaults to the unified card-media-aspect token (16/10).
     Per-element override flows through `imageAspectRatio` → `--cs-media-aspect`
     so a card whose author set a specific ratio still wins. */
  aspect-ratio: var(--cs-media-aspect, var(--site-card-media-aspect, auto));
  max-height: var(--cs-media-max-height, none);
}

.el-compound-section__media-primary,
.el-compound-section__media-secondary {
  position: relative;
}

.el-compound-section__media-primary > .el,
.el-compound-section__media-secondary > .el {
  height: 100%;
}

.el-compound-section__media img,
.el-compound-section__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ── Stage 7D — Panel content frame (cta-band et al.) ────────────────────
   Compound widgets that render a "panel" container over a background
   (cta-band's inner frame, future promo sections) project their panel
   chrome onto --cs-panel-* vars. Applies on `__bg-content` so the inner
   frame sits above overlay + bg media. Also drives content alignment +
   action justification via --cs-text-align / --cs-actions-justify. */
.el-compound-section__bg-content {
  background: var(--cs-panel-bg, transparent);
  border: 1px solid var(--cs-panel-border-color, transparent);
  box-shadow: var(--cs-panel-shadow, none);
  border-radius: var(--cs-panel-radius, 0);
  padding: var(--cs-panel-padding, clamp(2rem, 6vw, 4rem)) 1.5rem;
  text-align: var(--cs-text-align, inherit);
  -webkit-backdrop-filter: var(--cs-panel-filter, none);
  backdrop-filter: var(--cs-panel-filter, none);
}

.el-compound-section__bg-content:not(.el-site-container) {
  max-width: var(--cs-content-max-width, min(1200px, 100%));
}
/* CTA-band panel author override. The bg-content carries .el-site-container
   (capped at --site-max-width), so the :not() rule above is dead for cta-band.
   When the inspector sets `contentMaxWidth`, CtaBandRenderer projects it to
   `--el-cta-band-max-width` on the wrap; this attribute-scoped rule lets that
   value win over the site-container cap (and the el--stretched override) so
   authors can shrink the glass panel to fit-content / a custom px value. */
.el-cta-band-wrap[style*="--el-cta-band-max-width"] .el-compound-section__bg-content {
  max-width: var(--el-cta-band-max-width);
  margin-inline: auto;
}
.el-compound-section__bg-content .el-compound-section__actions {
  justify-content: var(--cs-actions-justify, flex-start);
}

/* was @media(max-width:480px); bucketed to mobile (480 ≤ 768) */
@container site style(--site-bp: mobile) {
  .el-cta-band-wrap > .el-compound-section--background {
    border-radius: 0;
  }
  /* Width/max-width on .el-compound-section__bg-content are owned by
     .el-site-container — do not redeclare here (siteContainerContract). */
  .el-cta-band-wrap .el-compound-section__bg-content {
    border-radius: 0;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
@media (max-width: 480px) {
  .el-cta-band-wrap > .el-compound-section--background {
    border-radius: 0;
  }
  /* Width/max-width on .el-compound-section__bg-content are owned by
     .el-site-container — do not redeclare here (siteContainerContract). */
  .el-cta-band-wrap .el-compound-section__bg-content {
    border-radius: 0;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* Section-level background image painted via CSS var (independent of any
   child media slot). Used by cta-band's legacy per-section bg image path. */
.el-compound-section[style*="--cs-section-bg-image"] {
  background-image: var(--cs-section-bg-image);
  background-size: var(--cs-section-bg-size, cover);
  background-position: var(--cs-section-bg-position, center);
  background-repeat: no-repeat;
}

/* ── CTA Band variant tinting — Stage 7D ────────────────────────────────
   The legacy .el-cta-band--{brand,dark,light,accent,image} variants used
   to paint the full section background. Now the outer wrapper carries the
   `el-cta-band-wrap--{variant}` class; we forward variant tinting to the
   inner compound-section via adjacent-child selectors so the existing
   --site-* tokens still drive the look. */
.el-cta-band-wrap {
  display: block;
  width: 100%;
  /* Overlay scrim is opt-in via the inspector (`overlayColor` + `overlayOpacity`).
     We deliberately do NOT declare default `--cs-overlay-color` /
     `--cs-overlay-opacity` here: the previous default of 58% over the site-text
     token painted a noticeable wash on every cta-band out of the box, even
     after authors set `overlayOpacity: 0`. Authors who want a scrim now have
     to ask for it explicitly in the inspector. */
}
.el-cta-band__video-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
.el-cta-band-wrap > .el-compound-section--background {
  position: relative;
  z-index: 1;
  /* Vertical breathing room between the glass panel and the bg media's
     top/bottom edges. Without this, at wide viewports (≥1280px) where the
     bg image fills a tall hero band, the section's `align-items: center`
     can't visibly center the panel because the panel's natural height
     plus internal padding equals (or exceeds) the section min-height —
     panel top edge sits flush with the bg image's top edge.

     The `padding-block` here pushes the panel inward on the Y axis so the
     bg image always shows above + below the panel. We keep
     `background-origin: border-box` so the bg image still paints full
     edge-to-edge (default `padding-box` would crop the image to the
     padded inner box, exposing the page background through the gutter).

     Inline (left/right) is intentionally NOT padded here — the existing
     `__bg-content { max-width: min(1200px, 100%); margin: 0 auto }` rule
     already centers the panel horizontally with site-container gutters.
     Adding padding-inline here would double-gutter on wide screens.

     Author override: --cs-bg-section-padding-y (projected by renderer
     when an explicit section-spacing field lands). */
  padding-block: var(--cs-bg-section-padding-y, clamp(2.5rem, 5vh, 5rem));
  background-origin: border-box;
  background-clip: border-box;
}
/* Glass-panel padding for cta-band — modern, inspector-editable, split Y/X.
   Why this rule needs its own scope: CtaBandRenderer sets --cs-bg-content-
   padding to 0 (to suppress the section's default vertical rhythm clamp),
   so the base `.el-compound-section__bg-content` rule's `var(--cs-panel-
   padding, clamp(2rem, 6vw, 4rem)) 1.5rem` is the wrong axis split for
   the panel — it leaves a hardcoded 1.5rem on the X axis and reads only
   the unified inspector value on Y. We override here with a Y/X-split
   that the inspector's `panelPaddingY` / `panelPaddingX` (projected to
   --cs-panel-padding-y / -x) drive directly, falling back to the unified
   `panelPadding` and finally to the modern responsive clamp.

   Cascade per axis (Y example):
     1. --cs-panel-padding-y      ← author set Y slider > 0
     2. --cs-panel-padding         ← author set unified slider (legacy)
     3. clamp(2.75rem, 6vw, 4.5rem) ← modern default, ~44-72px

   X cascade mirrors with `1.75rem, 4vw, 2.5rem` (28-40px). */
.el-cta-band-wrap .el-compound-section__bg-content {
  padding:
    var(--cs-panel-padding-y, var(--cs-panel-padding, clamp(2.75rem, 6vw, 4.5rem)))
    var(--cs-panel-padding-x, var(--cs-panel-padding, clamp(1.75rem, 4vw, 2.5rem)));
}
.el-cta-band-wrap[style*="--cs-panel-glass"] .el-compound-section__bg-content,
.el-cta-band-wrap[data-panel-glass="true"] .el-compound-section__bg-content {
  --cs-panel-glass-active: var(--cs-panel-glass);
  background: var(--cs-panel-bg, var(--site-glass-bg));
  border-color: var(--cs-panel-border-color, var(--site-glass-border));
  box-shadow: var(--cs-panel-shadow, var(--site-glass-highlight));
}
.el-cta-band-wrap .el-section-header__title,
.el-cta-band-wrap .el-section-header__lede {
  color: inherit;
}
.el-cta-band-wrap .el-heading__text {
  color: var(--el-color, inherit);
}
/* Variant tinting paints the section background AND seeds contrast
   fallbacks for the primary/outline button vars so CTAs never blend
   into the section on a colored band. Explicit user overrides in
   `primaryButtonColor` / `outlineButton*` still win via `--cta-*`. */
.el-cta-band-wrap--brand > .el-compound-section--background {
  background-color: var(--site-primary, transparent);
  color: var(--site-primary-fg, currentColor);
  --cta-primary-bg: var(--site-accent, var(--site-primary-fg, currentColor));
  --cta-primary-color: var(--site-accent-fg, var(--site-primary, currentColor));
  --cta-primary-border: var(--site-accent, var(--site-primary-fg, currentColor));
}
.el-cta-band-wrap--dark > .el-compound-section--background {
  background-color: var(--site-surface-dark, transparent);
  color: var(--site-text-inverse, currentColor);
  --cta-primary-bg: var(--site-accent, var(--site-primary, currentColor));
  --cta-primary-color: var(--site-accent-fg, var(--site-text, currentColor));
  --cta-primary-border: var(--site-accent, var(--site-primary, currentColor));
}
.el-cta-band-wrap--light > .el-compound-section--background {
  background-color: var(--site-surface-alt, transparent);
  color: var(--site-text, currentColor);
}
.el-cta-band-wrap--accent > .el-compound-section--background {
  background-color: var(--site-accent, transparent);
  color: var(--site-accent-fg, currentColor);
  --cta-primary-bg: var(--site-accent-fg, var(--site-primary, currentColor));
  --cta-primary-color: var(--site-accent, var(--site-primary-fg, currentColor));
  --cta-primary-border: var(--site-accent-fg, var(--site-primary, currentColor));
}

/* ───────────────────────────────────────────────────────────────────────
   Project Showcase — Stage 11C per-card chrome. Consumes `--cs-*` vars
   projected by the thin-shell `ProjectShowcaseRenderer` from parent
   controls (columns, image aspect, equalize heights, featured index,
   colors, hover, buttons, radius). Featured treatment is applied via a
   scoped `<style>` block emitted by the thin shell that selects the
   Nth `:nth-child` under the specific `[data-element-id]` scope — no
   per-child inline styles.
─────────────────────────────────────────────────────────────────────── */

/* High-specificity bridge — `.el > :not(style)` (0,1,1) beats
   `.el-project-card` (0,1,0) and zeros bg/border-radius/shadow. */
.el.el--project-card > .el-project-card {
  background: var(--cs-card-bg, var(--site-surface, transparent));
  border-radius: var(--cs-card-radius, 20px);
  box-shadow: var(--cs-card-shadow, none);
}

.el-project-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Resting-state colors cascade through the unified card resting-state
     color contract (src/lib/themeContract.ts § Card resting-state colors).
     Per-element vars (`--cs-card-bg`, `--cs-card-border-color`) win first;
     site-level `--site-card-*` retints every card in one edit; legacy
     `--site-surface` / `--site-border-muted` chains stay as final
     fallbacks. */
  background: var(--cs-card-bg, var(--site-card-bg, var(--site-surface, transparent)));
  color: var(--cs-project-title-color, var(--site-card-text, var(--site-text, inherit)));
  border-radius: var(--cs-card-radius, 20px);
  border: var(--site-card-border-width, 1px) solid var(--cs-card-border-color, var(--site-card-border, var(--site-border-muted, transparent)));
  box-shadow: var(--cs-card-shadow, var(--site-card-elevation, none));
  transition:
    transform 0.25s ease,
    box-shadow 0.25s ease,
    border-color 0.25s ease,
    background 0.25s ease,
    color 0.25s ease;
}

/* Equalize card heights — when parent's `--cs-equalize-heights` is 1
   (default) cards stretch to fill their grid row (height: 100%); when 0
   cards take their intrinsic content height. The thin shell projects the
   concrete CSS length into `--cs-card-height` (100% or auto) so no
   calc-with-0 collapse is possible. */
.el-compound-section__cards > .el-project-card {
  height: var(--cs-card-height, 100%);
}

.el-project-card:hover {
  border-color: var(--cs-card-card-hover-border-color, var(--cs-accent-color, var(--site-primary)));
  background: var(--cs-card-card-hover-bg, var(--cs-card-bg, var(--site-surface, transparent)));
  color: var(--cs-card-card-hover-text-color, inherit);
  transform: translateY(calc(-1 * var(--cs-card-card-hover-lift, 2px)));
}

.el-project-card__media {
  aspect-ratio: var(--cs-image-aspect, 16 / 10);
  height: var(--cs-image-height, auto);
  overflow: hidden;
  background: var(--site-surface-muted, transparent);
}

.el-project-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.el-project-card__media--empty {
  min-height: 120px;
}

.el-project-card__content {
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
}

.el-project-card__status {
  align-self: flex-start;
  padding: 0.35rem 0.65rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--cs-accent-color, var(--site-accent, var(--site-primary))) 16%, transparent);
  color: var(--cs-category-color, var(--cs-accent-color, var(--site-accent, var(--site-primary))));
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.el-project-card__title {
  margin: 0;
  font-size: 1.35rem;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--cs-project-title-color, inherit);
}

.el-project-card__summary {
  margin: 0;
  line-height: 1.6;
  color: var(--cs-intro-color, var(--site-text-secondary, inherit));
  max-width: min(640px, 100%);
}

.el-project-card__meta-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.el-project-card__meta {
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--cs-accent-color, var(--site-accent, var(--site-primary))) 8%, transparent);
  color: var(--cs-meta-color, inherit);
  font-size: 0.75rem;
  font-weight: 600;
}

.el-project-card__bullets {
  margin: 0;
  padding-left: 1.1rem;
  display: grid;
  gap: 6px;
  color: var(--cs-intro-color, var(--site-text-secondary, inherit));
  line-height: 1.55;
}

.el-project-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: auto;
  padding-top: 6px;
}

.el-project-card__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.7rem 1rem;
  border-radius: var(--cs-btn-radius, 999px);
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 700;
  transition: filter 0.2s ease, transform 0.2s ease;
}

.el-project-card__cta--primary {
  background: var(--cs-primary-btn-bg, var(--cs-accent-color, var(--site-accent, var(--site-primary))));
  color: var(--cs-primary-btn-fg, var(--site-accent-fg, var(--site-primary-fg, inherit)));
}

.el-project-card__cta--secondary {
  background: var(--cs-secondary-btn-bg, transparent);
  color: var(--cs-secondary-btn-fg, inherit);
  border: 1px solid var(--cs-secondary-btn-border, var(--site-border, transparent));
}

.el-project-card__cta:hover {
  filter: brightness(0.95);
}

/* Featured card treatment. The `--featured` modifier class is set by
   `ProjectCardRenderer` when its own `featured` prop is true. A second
   vector — the parent-level `featuredIndex` integer — is applied via a
   scoped <style> block that `ProjectShowcaseRenderer` emits under the
   section's `[data-element-id]` scope (CSS `:nth-child(var(--x))` is not
   supported, so the thin shell bakes the integer into the selector). The
   featured treatment here is accent-bar on the left edge. */
.el-project-card--featured {
  border-left: 4px solid var(--cs-accent-color, var(--site-accent, var(--site-primary)));
}

/* was @media(max-width:480px); bucketed to mobile (480 ≤ 768) */
@container site style(--site-bp: mobile) {
  .el-project-card__content {
    padding: 16px;
  }
  .el-project-card__title {
    font-size: 1.2rem;
  }
}
@media (max-width: 480px) {
  .el-project-card__content {
    padding: 16px;
  }
  .el-project-card__title {
    font-size: 1.2rem;
  }
}

/* ── Card Hover Contract: batch 4 — logo-slider / pricing-card / product-detail / product-featured / product-grid / project-card / property-grid / recent-posts ───────────── */

/* CARD_HOVER_FIELDS contract — see SKILL_REFERENCE.md § Card Hover Contract.
   project-card — self-scoped contract for the standalone case (project-card
   rendered outside project-showcase). When inside project-showcase, the
   parent's `--cs-card-card-hover-*` cascade above (line ~684) wins because
   it lands on the same element. The `--pjc-hover-*` chain only paints when
   the parent doesn't supply those vars (or when the per-card
   CARD_HOVER_FIELDS fields are explicitly authored on this card). */
.el-project-card {
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.25s ease, transform 0.25s ease;
}
[data-card-hover-shell]:hover .el-project-card {
  background: var(--pjc-hover-bg, var(--cs-card-card-hover-bg, var(--cs-card-bg, var(--site-surface, transparent))));
  color: var(--pjc-hover-color, var(--cs-card-card-hover-text-color, inherit));
  border-color: var(--pjc-hover-border-color,
    var(--cs-card-card-hover-border-color,
      color-mix(in srgb, var(--site-accent, currentColor) 45%, transparent)));
  box-shadow: var(--pjc-hover-shadow,
    0 8px 24px rgba(0, 0, 0, calc(0.10 * var(--pjc-hover-glow-opacity, 1))));
  transform: translateY(calc(var(--pjc-hover-lift, 0px) * -1));
}
