/* ----------------------------------------------------------------------
 * 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%;
  /* Flat background paint — emitted by CompoundSectionRenderer when
     props.backgroundColor is set and no gradient pair is composed.
     Paints on the section element itself so card shadows / inner
     decorations land on this surface rather than escaping through the
     wrapper's z-index:-1 .el--top-level::before bleed (which lets the
     page bg show through behind the section content). */
  background: var(--cs-bg-color, transparent);
}

.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.
     When --cs-bg-gradient is set (renderer wires gradientDirection +
     gradientEndColor + backgroundColor) the gradient takes precedence so
     the composed gradient shows through instead of being shadowed by the
     flat --site-overlay scrim. */
  content: '';
  position: absolute;
  inset: 0;
  background: var(--cs-bg-gradient, 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 just outside the carousel wrapper on desktop so they don't
   cover card copy/media. Mobile keeps them tucked inside below. */
.el-compound-section__carousel-nav--prev { left: calc(var(--carousel-arrow-outset, clamp(44px, 3.5vw, 56px)) * -1); }
.el-compound-section__carousel-nav--next { right: calc(var(--carousel-arrow-outset, clamp(44px, 3.5vw, 56px)) * -1); }
.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
  );
}

/* Background gradient override — when the renderer wires
   gradientDirection+gradientEndColor (+backgroundColor as start) onto
   --cs-bg-gradient, the overlay layer adopts it as its background so the
   composed gradient shows through instead of being shadowed by a flat
   --cs-overlay-color scrim. Opacity forces to 1 since the gradient itself
   is the intended paint. Wins over the --cs-overlay-* path above when set. */
.el-compound-section[style*="--cs-bg-gradient"] .el-compound-section__overlay {
  background: var(--cs-bg-gradient);
  opacity: 1;
}

.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;
}
/* CTA-band panels hug their content. The wrap's max-width still caps the
   ceiling (so a paragraph wraps at that width), but with no contentMaxWidth
   the panel sizes naturally to the widest wrapped line + padding rather
   than auto-stretching to fill the section. Universal — every tenant.
   Site-container variants already center via `el-site-container` so the
   `:not(.el-site-container)` guard keeps this rule from re-declaring
   width chrome on the contract wrapper (see siteContainerContract test). */
.el-cta-band-wrap .el-compound-section__bg-content:not(.el-site-container) {
  width: fit-content;
  margin-inline: auto;
}
.el-cta-band-wrap .el-compound-section__bg-content.el-site-container {
  width: fit-content;
}
.el-compound-section__bg-content .el-compound-section__actions {
  justify-content: var(--cs-actions-justify, flex-start);
}

/* Mobile (≤480px) CTA-band tweaks. RADIUS-STRIP REMOVED 2026-05-13 —
   the unconditional `border-radius: 0` on `.el-cta-band-wrap` +
   `.el-compound-section__bg-content` was a parasitic mobile-fullbleed
   regression: every CTA-band lost its rounded panel framing on phones.
   The opt-in full-bleed contract (siteContainerWidth='full') has its
   own `.el-compound-section__bg-content.el-site-container--full {
   border-radius: 0 }` rule lower in this file (search for
   "Full-bleed panel contract"), which authors trigger deliberately.
   Backdrop-filter is dropped on phones for performance — that's the
   only thing left in this @media. */
@container site style(--site-bp: mobile) {
  .el-cta-band-wrap .el-compound-section__bg-content {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
@media (max-width: 480px) {
  .el-cta-band-wrap .el-compound-section__bg-content {
    -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));
}
/* Full-bleed panel contract — when the panel container is explicitly set to
   `Full bleed` width (siteContainerWidth=full → .el-site-container--full),
   the panel meets the viewport edges. Rounded corners at a viewport edge
   read as broken/clipped half-circles, so strip the radius. Authors who
   want a rounded panel must select a constrained Content Width (default /
   narrow / wide) that keeps a visible gutter on both sides. Applies to
   every compound-section panel renderer (cta-band, future promo widgets). */
.el-compound-section__bg-content.el-site-container--full {
  border-radius: 0;
  border-left-width: 0;
  border-right-width: 0;
}
.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;
  filter: var(--project-tile-filter, none);
}

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

/* ── Equal-height card contract (universal) ─────────────────────────────
   Every card-grid widget (pricing-table, testimonials, feature-cards,
   services, region-portfolio, etc.) renders inside
   `.el-compound-section__cards`. The grid already declares
   `grid-auto-rows: 1fr` + `align-items: stretch`, but animation wrappers
   (ElementWrapper + AnimationWrapper + motion.div) between the grid and
   the actual card don't inherit full track height and collapse to
   content height. When two cards have different content lengths (varying
   feature counts, optional badges, different price-string widths), the
   shorter wrapper card shrinks → asymmetric heights.

   Fix: force every direct + first-indirect wrapper inside the cards-grid
   to claim `height: 100%`. The card itself already declares `height:100%`
   per its dedicated rule (.el-pricing-table__card, .el-feature-card,
   etc.), so this is purely about not breaking the chain.

   Universal — applies to ANY compound-section card grid. */
.el-compound-section__cards > * {
  height: 100%;
  min-height: 0;
}
.el-compound-section__cards > * > .el-animate,
.el-compound-section__cards > * > [class*="el-node-"],
.el-compound-section__cards > .el-animate,
/* When the shell is `display: contents` (the standard case for
   pricing/testimonial/feature-card grids), the `.el-animate` becomes the
   grid's direct child and its OWN child is the actual `el-node-*` card.
   Force that nested card to fill the animate wrapper too — otherwise a
   card with shorter content (e.g. fewer feature bullets) collapses below
   the row height while neighbours stay full-size. */
.el-compound-section__cards .el-animate > [class*="el-node-"],
.el-compound-section__cards .el-animate > .el {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Card-mounted "Recommended" / "Most popular" / "Best value" status
   badges MUST not contribute layout height — that would make badged
   cards taller than non-badged neighbours in the same grid row. Pin
   them above the card via absolute positioning; cards that need a
   visible reservation can set padding-top via the card's own padding
   token. Renderer-emitted badge classes follow the convention
   `*__badge` / `[data-card-badge]`. */
.el-pricing-table__card .el-pricing-table__badge,
.el-pricing-card .el-pricing-card__badge,
[data-card-badge],
[data-pricing-badge] {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* `highlighted: true` on a pricing tier (or feature card) should
   communicate emphasis through border / shadow / accent bar — never
   transform: scale(...), which both breaks alignment AND triggers a
   non-equal visual height in the grid row. */
.el-pricing-table__card[data-highlighted="true"],
.el-feature-card[data-highlighted="true"] {
  transform: none;
}
