/* ==========================================================================
   KABTech Corp - child-theme layout CSS
   Parity target: html-prototypes/home.html + css/styles.css (approved r2).
   Only the pieces with no Elementor control behind them live here:
   pseudo-element rules, gradient stacks, hover transforms, the chrome grid,
   and the mega-menu / overlay-menu chrome.
   ========================================================================== */

:root {
  /* Two grids, exactly as the mockup: content 1200, chrome 1288.
     max() centers the grid at wide viewports and floors the gutter below it. */
  --kab-content-max: 1200px;
  --kab-chrome-max: 1288px;
  --kab-gutter: max(40px, calc((100% - var(--kab-content-max)) / 2));
  --kab-gutter-chrome: max(40px, calc((100% - var(--kab-chrome-max)) / 2));
  --kab-section-y: 80px;
}

@media (max-width: 767px) {
  :root {
    --kab-gutter: 24px;
    --kab-gutter-chrome: 24px;
    --kab-section-y: 32px;
  }
}


/* ------------------------------------------------------------------
   NOTE: Elementor 4.2.x dropped the .elementor-widget-container wrapper
   for text/heading/button/image widgets - the content is now a direct
   child of .elementor-element. Rules below therefore style the widget
   element itself. The :is() branch keeps them working if a future
   version restores the wrapper.
   ------------------------------------------------------------------ */

/* --------------------------------------------------------------------
   Section rhythm - one padding pair per breakpoint, site-wide.
   Elementor writes container padding as --padding-* custom props on
   .e-con, so a single class-scoped rule is the one knob.
   -------------------------------------------------------------------- */
.e-con.kab-section {
  --padding-block-start: var(--kab-section-y);
  --padding-block-end: var(--kab-section-y);
  --padding-inline-start: var(--kab-gutter);
  --padding-inline-end: var(--kab-gutter);
}
.e-con.kab-chrome {
  --padding-inline-start: var(--kab-gutter-chrome);
  --padding-inline-end: var(--kab-gutter-chrome);
}

/* --------------------------------------------------------------------
   Text-widget paragraph reset.
   The mockup resets * { margin: 0 }, so every measurement in the design is
   taken with no paragraph margin. Elementor text-editor widgets emit a bare
   <p> that inherits the theme's 1.75em bottom margin, which added ~28px to
   every copy block. Reset it on OUR widgets only (a global p reset would
   fight Astra's editorial defaults elsewhere).
   -------------------------------------------------------------------- */
.kab-header p,
.kab-mm p,
.kab-footer p,
.elementor-widget-text-editor p:only-child { margin: 0; }

/* Content measures from the mockup (.section-intro .h2, .section-sub etc.) */
.e-con.kab-intro { max-width: 760px; }
.kab-intro .elementor-heading-title { max-width: 706px; }
/* The sub is a widget wrapper, so the measure has to sit on the wrapper AND
   its paragraph (Elementor gives widgets width:100% inside a container). */
.kab-intro .kab-sub,
.kab-intro .kab-sub p { max-width: 586px; }
/* ...but a multi-paragraph .section-sub-rich runs to the intro's own measure
   in the mockup (its inline cap is 800px, wider than the 760px intro box). */
.kab-intro .kab-sub-wide,
.kab-intro .kab-sub-wide p { max-width: none; }
/* A4 (Audrey r3): single-word widows. The intro lede ends on "requirements."
   alone at every desktop width (its max-width is fixed, so it wraps
   identically everywhere) and step 02 ends on "constraints." alone at 1920
   and 390. `text-wrap: pretty` lets the browser rebalance only the LAST
   couple of lines to avoid an orphan - it changes no copy, no box geometry
   and no measured width, and degrades to normal wrapping where unsupported. */
.kab-intro .kab-sub,
.kab-intro .kab-sub p,
.kab-stepcopy,
.kab-stepcopy p { text-wrap: pretty; }
.kab-trusted .elementor-heading-title { max-width: 592px; }
.kab-why .elementor-heading-title { max-width: 660px; }

/* --------------------------------------------------------------------
   Eyebrow - mono label with a 3px yellow bar.
   One rule serves every eyebrow on the page. Eyebrows are never headings.
   -------------------------------------------------------------------- */
.kab-eyebrow, .kab-eyebrow > .elementor-widget-container{
  display: flex;
  align-items: center;
  gap: 12px;
}
.kab-eyebrow::before, .kab-eyebrow > .elementor-widget-container::before{
  content: "";
  width: 3px;
  height: 20px;
  background: var(--e-global-color-accent);
  flex-shrink: 0;
}
.kab-eyebrow p { margin: 0; }

.kab-eyebrow-lg, .kab-eyebrow-lg > .elementor-widget-container { gap: 14px; }
.kab-eyebrow-lg::before, .kab-eyebrow-lg > .elementor-widget-container::before { height: 26px; }
/* --------------------------------------------------------------------
   A11Y button reset.
   Nav toggles / hamburger / close / accordions are real <button>s inside
   text widgets so they are focusable and expose aria-expanded. Astra paints
   a bare <button> as a filled block, so it is reset to inherit here and the
   layout rules below move onto the button. Visuals are unchanged.
   -------------------------------------------------------------------- */
.kab-btn {
  display: flex;
  align-items: center;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  /* Astra/Elementor give a bare <button> a 0 1px 2px shadow, which painted a
     visible plate behind the toggles once they stopped being <div>s. */
  box-shadow: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: inherit;
  line-height: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.kab-btn:hover,
.kab-btn:focus,
.kab-btn:active {
  background: none;
  box-shadow: none;
  color: inherit;
}
/* Keep a visible focus ring for keyboard users (never remove it) */
.kab-btn:focus-visible {
  outline: 2px solid var(--e-global-color-accent);
  outline-offset: 3px;
}

/* --------------------------------------------------------------------
   Header
   -------------------------------------------------------------------- */
.kab-header { position: relative; }
.kab-header.kab-stuck { box-shadow: 0 8px 24px -14px rgba(10, 16, 56, 0.28); }

.kab-logo img, .kab-logo a img { width: 165px; height: 32.5px; max-width: none; }

/* Nav toggles - Text Editor widgets acting as buttons */
.kab-navtoggle .kab-btn {
  gap: 7px;
  padding: 10px 16px;
  width: auto;
  user-select: none;
}
.kab-navtoggle p { margin: 0; }
.kab-navtoggle img { width: 20px; height: 20px; transition: transform 0.2s ease; }
/* open state now lives on the button (it carries aria-expanded) */
.kab-navtoggle .kab-btn[aria-expanded="true"] img { transform: rotate(180deg); }

.kab-navphone, .kab-navphone > .elementor-widget-container{
  display: flex;
  align-items: center;
  height: 40px;
  padding: 0 13px;
}
.kab-navphone p { margin: 0; }
.kab-navphone a { color: var(--e-global-color-primary); }

/* --------------------------------------------------------------------
   Mega dropdown panels
   -------------------------------------------------------------------- */
.e-con.kab-dd {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  width: 100%;
  background: #fff;
  border-top: 1px solid var(--e-global-color-kabnavborder);
  box-shadow: 0 30px 50px -24px rgba(10, 16, 56, 0.35);
  display: none;
  z-index: 90;
}
.e-con.kab-dd.kab-dd-open { display: block; }

.e-con.kab-dd > .e-con-inner {
  flex-direction: row;
  align-items: stretch;
}
/* Design column x-positions @1440 are 117/519/921 (and 357/759/1161 @1920).
   The columns are 402px fixed, so 3x402 = 1206 inside the 1288 chrome grid
   leaves 82px of slack; justify-content:center split it 41/41 and started
   col 1 twenty pixels LEFT of the design. The design instead leads the row
   by the full 41px and lets the remainder fall on the right, which is what
   centring the 1288 grid and then indenting by half the slack produces.
   Elementor's own per-element --padding-inline-start wins over ours, so the
   indent rides on padding-left, which nothing else sets on this element. */
.e-con.kab-dd-inner { justify-content: flex-start; }
.e-con.kab-dd-inner > .e-con-inner { justify-content: flex-start; }
.e-con.kab-dd-inner { padding-left: calc(var(--kab-gutter-chrome) + 41px); }

.e-con.kab-dd-col { border-right: 1px solid var(--e-global-color-kabcolborder); }
.e-con.kab-dd-col:last-child { border-right: 0; }

.kab-dd-eyebrow, .kab-dd-eyebrow > .elementor-widget-container{
  display: flex;
  align-items: center;
  gap: 14px;
}
.kab-dd-eyebrow::before, .kab-dd-eyebrow > .elementor-widget-container::before{
  content: "";
  width: 3px;
  height: 26px;
  background: var(--e-global-color-accent);
  flex-shrink: 0;
}
.kab-dd-eyebrow p { margin: 0; white-space: nowrap; }

.kab-dd-title p { margin: 0; }
.kab-dd-title a { color: inherit; }
.kab-dd-title a:hover { color: var(--e-global-color-kabnavlink); }
/* "Material Lifts" is a standalone destination, not a group label: it takes
   the blue link treatment its siblings have, with the same underline hover. */
.kab-dd-standalone a:hover,
.kab-mm-standalone a:hover { text-decoration: underline; }

.kab-dd-links .elementor-icon-list-item > a:hover .elementor-icon-list-text {
  text-decoration: underline;
}

.e-con.kab-dd-card { background: var(--e-global-color-kabcardbg); border-right: 0; }

/* --------------------------------------------------------------------
   Mobile overlay menu
   -------------------------------------------------------------------- */
.e-con.kab-mm {
  display: none;
  position: fixed;
  inset: 0;
  background: #fff;
  z-index: 200;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.e-con.kab-mm.kab-mm-open { display: block; }
.e-con.kab-mm > .e-con-inner { flex-direction: column; }

.e-con.kab-mm-bar { position: sticky; top: 0; z-index: 2; }
.kab-mm-logo img, .kab-mm-logo a img { width: 165px; height: 32.5px; max-width: none; }

.kab-mm-close .kab-btn {
  justify-content: center;
  width: 30px;
  height: 30px;
  font-size: 26px;
  line-height: 1;
  color: var(--e-global-color-kabnavink);
}
.kab-mm-close p { margin: 0; }

.kab-acc-toggle .kab-btn {
  justify-content: space-between;
  padding: 18px 0;
  user-select: none;
}
.kab-acc-toggle p { margin: 0; }
.kab-acc-toggle img { width: 20px; height: 20px; transition: transform 0.2s ease; }
.kab-acc-toggle .kab-btn[aria-expanded="true"] img { transform: rotate(180deg); }

/* MOBILE OVERLAY DIVIDERS - NAVY (Eric 2026-08-26, R8 sign-off).
   A deliberate divergence from the mockup, which uses the light
   `--nav-col-border` grey here (and the build rendered it faithfully). Eric's
   design call is the brand navy instead. Scoped to `.kab-acc` ONLY: the same
   `kabcolborder` token also draws the DESKTOP mega-menu column dividers
   (`.kab-dd-col`), which stay faithful to the design - so the token itself is
   deliberately not repointed. */
.e-con.kab-acc { border-bottom: 1px solid #171B37; }
.e-con.kab-acc-panel { display: none; }
.e-con.kab-acc-panel.kab-open { display: flex; }

.kab-mm-group-eyebrow p,
.kab-mm-group-title p { margin: 0; }

/* --------------------------------------------------------------------
   Hero - background VIDEO with the still as poster/fallback
   -------------------------------------------------------------------- */
.e-con.kab-hero { overflow: hidden; }

/* A2 (Audrey r3): pause/play control for the hero background video.
   WCAG 2.2.2 (Level A) wants a mechanism to pause auto-moving content that
   runs over 5s; the loop is 29.6s and Elementor renders it controls=false.
   INTENTIONAL addition beyond the mockup - accessibility, not decoration.

   Kept deliberately quiet so it never competes with the CTAs: a small
   semi-transparent dark chip in the hero's bottom-right, white glyph, sitting
   above the gradient but out of the copy's column. Visual size 32px, but the
   hit area is padded to the 44px AA touch target. JS hides it <768 (no video
   there) and under reduced-motion (video already still). */
.kab-vidbtn {
  /* The stats strip is the hero's last child and its height varies by
     breakpoint (130 @1440, 126 @768), so the button is anchored to the strip
     rather than to the hero's bottom edge: it is positioned against the hero
     but offset by the strip's own measured height, published by JS as
     --kab-statsh. Falls back to 130px if JS has not run yet. */
  position: absolute;
  right: 16px;
  bottom: calc(var(--kab-statsh, 130px) + 16px);
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/* The INTERIOR hero (Mezzanines / Industrial Platforms, added 2026-08-26) has
   no stats strip, and its inner copy box is itself a positioned Elementor
   container - so the chip was resolving against THAT box instead of the hero
   and landed mid-copy at the wrong edge. Anchor it to the hero's own bottom
   right, and give the hero an explicit positioning context so the absolute
   chip cannot climb to a nearer ancestor. */
.e-con.kab-hero-interior { position: relative; }
/* `.e-con.kab-hero-interior > * { position: relative }` already exists (it
   lifts the copy box above the gradient) and at 0-2-0 it OUT-SPECIFIES
   `.kab-vidbtn`'s own `position:absolute` - so the chip was laid out as a
   flex item in the hero's column and landed mid-copy at x=104. Restate
   position at matching specificity. No !important. */
.e-con.kab-hero-interior > .kab-vidbtn {
  position: absolute;
  bottom: 16px;
  right: 16px;
}
.kab-vidbtn[hidden] { display: none; }
/* The visible chip is an inner box so the 44px hit area stays invisible. */
.kab-vidbtn::before {
  content: "";
  position: absolute;
  width: 32px;
  height: 32px;
  background: rgba(14, 17, 23, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.25);
  transition: background-color 0.18s ease, border-color 0.18s ease;
}
.kab-vidbtn:hover::before {
  background: rgba(14, 17, 23, 0.78);
  border-color: rgba(255, 255, 255, 0.45);
}
.kab-vidbtn:focus-visible {
  outline: 2px solid var(--e-global-color-accent);
  outline-offset: 2px;
}
/* Glyphs drawn in CSS - no icon font, no extra request. */
.kab-vidbtn-pause,
.kab-vidbtn-play {
  position: relative;
  z-index: 1;
  display: block;
}
/* Pause = two bars */
.kab-vidbtn-pause {
  width: 10px;
  height: 12px;
  border-left: 3px solid #fff;
  border-right: 3px solid #fff;
}
/* Play = right-pointing triangle, nudged for optical centring */
.kab-vidbtn-play {
  width: 0;
  height: 0;
  margin-left: 3px;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-left: 11px solid #fff;
}
.kab-vidbtn .kab-vidbtn-play { display: none; }
.kab-vidbtn-paused .kab-vidbtn-pause { display: none; }
.kab-vidbtn-paused .kab-vidbtn-play { display: block; }

@media (max-width: 767px) {
  /* No video below 767 - the control is hidden by JS too, this is a belt-and-
     braces guard so it can never paint over the mobile hero copy. */
  .kab-vidbtn { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .kab-vidbtn { display: none; }
}

/* Hero content + stats ride the same 1200 content grid as every section
   (mockup: padding-left: var(--gutter) on .hero-content, and
   padding: 35px var(--gutter) 34px on .hero-stats). */
/* Desktop: gutter on the LEFT only (mockup: padding 88px 0 88px gutter).
   Mobile: the mockup pads BOTH sides 24px, so the measure narrows to 342. */
.e-con.kab-heropad { --padding-inline-start: var(--kab-gutter); }
@media (max-width: 767px) {
  .e-con.kab-heropad { --padding-inline-end: var(--kab-gutter); }
}
/* A1 (Audrey r3) note on the RIGHT gutter: deliberately NOT added.
   A first pass gave the tablet band a 40px right gutter so the lede would not
   run to the viewport edge - but that narrowed the measure to 688px and
   wrapped the lede to FOUR lines where the mockup has three, growing the hero
   30px (a parity regression the geometry sweep caught immediately).
   The mockup pads only on the left here too (768px box, padding-left 40,
   padding-right 0 = a 728px measure), and its longest lede line simply lands
   a few px inside the edge on its own. Matching the mockup means matching its
   measure, so the hero keeps left-only padding at every width above 767. */
/* A1 (Audrey r3): the hero copy box must be a MAX-width, not a fixed width.

   Elementor writes the container `width` control out as `--width` and
   `.e-con` applies `width: var(--width)` flat - there is NO max-width control
   on containers, and frontend.css only lends `max-width: min(100%, var(--width))`
   to TOP-LEVEL containers, which this nested box is not. So the authored
   1020px held all the way down through the tablet band (768-1024) while the
   viewport shrank underneath it, and the overflow:hidden hero cut the H1
   mid-word at every iPad-portrait width.

   The mockup declares `max-width: calc(var(--content-max) * .85)` = 1020px,
   which shrinks with the viewport. This reproduces that exactly: take the
   authored --width as a CAP and let the box be fluid. Identical at >=1043
   (where 100% of the padded row already exceeds 1020), fixes the whole band,
   and needs no breakpoint-specific override. */
.e-con.kab-herobox {
  width: 100%;
  max-width: var(--width, 1020px);
}

.e-con.kab-statspad {
  --padding-inline-start: var(--kab-gutter);
  --padding-inline-end: var(--kab-gutter);
}
@media (max-width: 767px) {
  /* Mobile stats box is inset 24px each side (Figma: a 327w box inside the
     padded hero), so it drops the section gutter and takes the inset width. */
  .e-con.kab-statspad {
    --padding-inline-start: 20px;
    --padding-inline-end: 20px;
    width: calc(100% - 48px);
  }
}

/* The still is the video's poster AND its fallback: Elementor paints
   background_video_fallback as the container background, so the photo is
   already showing before the first video frame and stays put wherever
   autoplay is refused. */
.e-con.kab-hero > .elementor-background-video-container {
  pointer-events: none;
}
.e-con.kab-hero .elementor-background-video-container video {
  object-fit: cover;
  width: 100%;
  height: 100%;
}

/* Exact mockup overlay: #171B37 95% -> 82% at 46% -> 55%, ~95deg.
   Elementor's overlay control only exposes TWO stops, and a CONTAINER paints
   its overlay as a ::before pseudo-element (only legacy sections/columns emit
   a real .elementor-background-overlay child). So the three-stop gradient is
   authored here, over the widget's two-stop value. */
.e-con.kab-hero::before,
.e-con.kab-hero > .elementor-background-video-container::before {
  background-image: linear-gradient(
    95deg,
    rgba(23, 27, 55, 0.95) 0%,
    rgba(23, 27, 55, 0.82) 46%,
    rgba(23, 27, 55, 0.55) 100%
  ) !important; /* beats Elementor's own ::before at equal specificity */
}

/* Reduced motion: hold the still, never the moving video. */
@media (prefers-reduced-motion: reduce) {
  .e-con.kab-hero .elementor-background-video-container { display: none; }
}

/* Mobile hero rendition. Elementor does not autoplay background video on
   mobile, so the still is the ONLY hero asset there - and the 2000w desktop
   file was painting into a 390px slot (5.13x oversized, 364KB). Swap in an
   800w/87KB variant below the tablet break. */
@media (max-width: 767px) {
  /* Elementor writes this as the `background` SHORTHAND at
     `.elementor-34 .elementor-element.elementor-element-<id>:not(...)`
     (0-4-0), so a plain background-image at 0-2-0 both loses on specificity
     AND gets reset by the shorthand. Match it with a doubled class. */
  .elementor-34 .e-con.kab-hero.kab-hero:not(.elementor-motion-effects-element-type-background) {
    background-image: url("/wp-content/uploads/2026/08/hero-mezzanine-mobile.jpg");
    background-position: center center;
    background-size: cover;
    background-repeat: no-repeat;
  }
}
.kab-hero-h1 .elementor-heading-title { max-width: 895px; }
/* The cap has to sit on the PARAGRAPH too: Elementor gives the widget
   width:100% of the 1020px content column, so a wrapper-only max-width let
   the copy run to 1020px - into the right side of the video where the
   overlay eases to 55% and contrast drops below AA. */
/* N2 (RFL 8/27): 895px ended line 1 at "...mezzanines, modular"; the break
   moves to exactly "mezzanines," at 890px and holds to 840px, 3 lines
   throughout. 860px sits mid-band, clear of the 890px cliff. Measure-capped,
   never a hard <br>, so it still reflows safely on small screens. */
.kab-hero-sub,
.kab-hero-sub p { max-width: 860px; }

.kab-herocall, .kab-herocall > .elementor-widget-container { display: none; }
.kab-herocall p { margin: 0; }
/* The r1 fix made this line a real tel: link, and the anchor then inherited
   the global link colour #19257D - dark blue on navy, 1.60:1, effectively
   invisible. The mockup's .hero-call is WHITE with only the number yellow. */
.kab-herocall a,
.kab-herocall a:hover,
.kab-herocall a:focus { color: var(--e-global-color-kabinverse); }
.kab-herocall b { color: var(--e-global-color-accent); font-weight: 700; }

/* Stats strip - translucent bar over the hero photo */
.e-con.kab-stats {
  background: rgba(14, 17, 23, 0.55);
  border-top: 1px solid rgba(245, 197, 24, 0.28);
}
.kab-statnum p,
.kab-statlabel p { margin: 0; }
.kab-statnum p { white-space: nowrap; }

/* Number and label sit SIDE BY SIDE (mockup .stat is a flex row).
   Elementor gives every widget width:100% inside a container, which pushed
   the label onto its own line and doubled the row height on mobile. */
.kab-statnum { width: auto; flex: 0 0 auto; }
/* min-width:auto (the flex default) stops the label shrinking below its
   longest word, so it kept its full 302px and wrapped onto a second line. */
.kab-statlabel { width: auto; flex: 1 1 0; min-width: 0; }

/* --------------------------------------------------------------------
   Solution cards
   -------------------------------------------------------------------- */
.e-con.kab-card {
  position: relative;
  height: 268px;
  overflow: hidden;
}
/* ------------------------------------------------------------------
   Card hover zoom - COMPOSITED TRANSFORM, not background-size.

   The first attempt animated `background-size` (cover -> 104.5%). It fired,
   but background-size is not a compositor-accelerated property: every frame
   re-rasterises the image, which Eric saw as visible jank. The photo now
   lives on a ::before LAYER and the layer is scaled with `transform`, which
   the compositor handles on the GPU - no repaint, no layout.

   The container keeps its Elementor background image so the widget stays
   client-editable and the editor preview is honest; it is hidden on the
   frontend (`background-image: none`) because ::before paints the photo.
   ------------------------------------------------------------------ */
.elementor .e-con.kab-card.kab-card {
  background-image: none;
}

.e-con.kab-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* Promote to its own compositor layer and animate transform only. */
  transform: translateZ(0) scale(1);
  will-change: transform;
  transition: transform 0.45s ease;
  backface-visibility: hidden;
}
.e-con.kab-card:hover::before,
.e-con.kab-card:focus-within::before {
  transform: translateZ(0) scale(1.045);
}

/* Respect a reduced-motion preference: hold the photo still. */
@media (prefers-reduced-motion: reduce) {
  .e-con.kab-card::before { transition: none; }
  .e-con.kab-card:hover::before,
  .e-con.kab-card:focus-within::before { transform: translateZ(0) scale(1); }
}

/* Per-card photo for the transform layer (mirrors the Elementor background) */
.kab-card-card-mezz::before     { background-image: url("/wp-content/uploads/2026/08/card-mezzanines.jpg"); }
.kab-card-card-modular::before  { background-image: url("/wp-content/uploads/2026/08/card-modular-offices.jpg"); }
.kab-card-card-equip::before    { background-image: url("/wp-content/uploads/2026/08/card-equipment-platforms.jpg"); }
.kab-card-card-lifts::before    { background-image: url("/wp-content/uploads/2026/08/card-vertical-lifts.jpg"); }
.kab-card-card-guard::before    { background-image: url("/wp-content/uploads/2026/08/card-guard-shacks.jpg"); }
.kab-card-card-wire::before     { background-image: url("/wp-content/uploads/2026/08/card-wire-partitions.jpg"); }

/* Gradient scrim over the photo, under the text */
.e-con.kab-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)),
    linear-gradient(180deg, rgba(10, 12, 17, 0) 45%, #222837 100%);
  z-index: 0;
  pointer-events: none;
}
.e-con.kab-card > .e-con-inner,
.e-con.kab-card > .e-con-full,
.e-con.kab-card > .e-con { position: relative; z-index: 1; }

.kab-cardmore, .kab-cardmore > .elementor-widget-container{
  display: inline-flex;
  padding-bottom: 4px;
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s ease;
}
.kab-cardmore p { margin: 0; }
.e-con.kab-card:hover .kab-cardmore, .e-con.kab-card:hover .kab-cardmore > .elementor-widget-container{
  border-bottom-color: var(--e-global-color-accent);
}

/* --------------------------------------------------------------------
   Why KABTech
   -------------------------------------------------------------------- */
.kab-whyitem .elementor-icon-box-icon { width: 42px; flex: 0 0 42px; }
.kab-whyitem .elementor-icon-box-icon img,
.kab-whyitem .elementor-icon-box-icon svg { width: 42px; height: 42px; }

/* --------------------------------------------------------------------
   Getting started
   -------------------------------------------------------------------- */
/* Same flex trap as the stats row: the number is fixed-width and the copy
   must be allowed to shrink (min-width:0), or it wraps under the number. */
.kab-stepnum { width: 52px; flex: 0 0 52px; }
.kab-stepnum p { margin: 0; }
.kab-stepcopy { max-width: 496px; flex: 1 1 0; min-width: 0; }
.kab-gsimg img { width: 584px; height: 342px; object-fit: cover; }

/* Mockup split: text column is a FIXED 586px (flex-shrink:0) and the logo
   column takes the rest. Elementor has no flex-shrink control on a
   container, so the two-line rule lives here. */
.e-con.kab-trusted-text { flex: 0 0 586px; }
.e-con.kab-trusted-logos { flex: 1 0 0; }
@media (max-width: 1280px) { .e-con.kab-trusted-text { flex: 0 0 48%; } }
@media (max-width: 767px) {
  .e-con.kab-trusted-text,
  .e-con.kab-trusted-logos { flex: 0 0 100%; }
}

/* --------------------------------------------------------------------
   Trusted by - each logo is sized individually in the design
   -------------------------------------------------------------------- */
.kab-logo-bmw img { width: 84px; height: 84px; object-fit: contain; }
.kab-logo-michelin img { width: 188px; height: 84px; object-fit: contain; }
.kab-logo-caterpillar img { width: 191px; height: 40px; object-fit: contain; }
.kab-logo-ge img { width: 84px; height: 84px; object-fit: contain; }

/* --------------------------------------------------------------------
   Contact CTA
   -------------------------------------------------------------------- */
.e-con.kab-cta { overflow: hidden; }
/* The text column must TAKE the remaining width (mockup: flex 1 0 0), not
   size to its content. With `0 1 auto` it shrank to whatever the longest
   unbreakable run allowed, which at 768 cost ~7px and broke the headline to
   4 ragged lines (+88px band). min-width:0 lets it shrink below its longest
   word instead of forcing the column wider. */
.e-con.kab-ctatext { flex: 1 0 0; min-width: 0; }
.kab-ctaimg img { width: 584px; height: 404px; object-fit: cover; }
.kab-ctacall, .kab-ctacall > .elementor-widget-container { display: none; }
.kab-ctacall p { margin: 0; }
.kab-ctacall a,
.kab-ctacall a:hover,
.kab-ctacall a:focus { color: var(--e-global-color-kabinverse); }
.kab-ctacall b { color: var(--e-global-color-accent); font-weight: 700; }

/* --------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------- */
.kab-footlogo img, .kab-footlogo a img { width: 254px; height: 50px; max-width: none; }

/* Footer social marks are bare 24px glyphs (no shape padding) */
.kab-footer .elementor-social-icon {
  width: 24px; height: 24px; padding: 0; background: transparent;
}
.kab-footer .elementor-social-icon svg,
.kab-footer .elementor-social-icon img { width: 24px; height: 24px; }

.e-con.kab-footcols {
  /* Figma column widths 229/214/195/116/154/195 as fr ratios, even 37px gaps */
  grid-template-columns: 229fr 214fr 195fr 116fr 154fr 195fr;
}
.e-con.kab-footcol { min-width: 0; }

.kab-foothead, .kab-foothead > .elementor-widget-container{
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.kab-foothead::before, .kab-foothead > .elementor-widget-container::before{
  content: "";
  width: 3px;
  height: 20px;
  background: var(--e-global-color-accent);
  flex-shrink: 0;
}
.kab-foothead p { margin: 0; }
.kab-foothead a { color: #fff; }
.kab-foothead a:hover { color: var(--e-global-color-accent); }

.kab-footlinks .elementor-icon-list-item > a:hover .elementor-icon-list-text {
  color: #fff;
  text-decoration: underline;
}

/* Elementor already spaces icon-list rows by splitting space_between into
   padding-block-end + margin-block-start (7px each = the 14px the design
   wants). Adding a flex `gap` on top STACKS with it and yields 21px rows -
   so the list keeps Elementor's own spacing and only gains the 18px lead
   the mockup has above the first link. */
/* The 18px lead goes on the WIDGET, not the <ul> - Elementor's own
   icon-list rule sets the ul padding and wins on specificity. */
.kab-footlinks { padding-top: 18px; }
.e-con.kab-footcol { gap: 8px; }
/* The contact column's rows use the same 17px lead as every other footer
   column (mockup .footer-contact padding-top:17px). */
.kab-footcontact { padding-top: 5px; }
.kab-footcol .kab-footaddr { padding-top: 17px; }
/* The "View All ..." row is always the LAST item of a flagged column */
.kab-has-viewall .elementor-icon-list-item:last-child .elementor-icon-list-text {
  font-weight: 700;
  color: var(--e-global-color-accent);
  letter-spacing: 0.27px;
}
.kab-has-viewall .elementor-icon-list-item:last-child a:hover .elementor-icon-list-text {
  color: var(--e-global-color-accent);
}

/* Footer contact glyphs are brand YELLOW per the mockup.
   The exported SVGs are STROKE drawings with fill="none"; blanket-filling
   them turned the envelope into a solid blob. Paint the stroke, and force
   fill back to none so the outline reads as an outline. */
.kab-footcontact .elementor-icon-list-icon img,
.kab-footcontact .elementor-icon-list-icon svg { width: 18px; height: 18px; }
.kab-footcontact .elementor-icon-list-icon svg,
.kab-footcontact .elementor-icon-list-icon svg * {
  fill: none;
  stroke: var(--e-global-color-accent);
}

.kab-foot-tagline-mobile { display: none; }

/* ==================================================================
   Responsive
   ================================================================== */

/* Laptop squeeze - mockup gives the images a PERCENTAGE of the row
   (.gs-image 46%, .contact-cta-img 44%), not 100% of their flex slot. The
   percentage is applied to the widget wrapper so the flex slot itself
   shrinks, matching the mockup's flex-shrink:0 + width:% behaviour. */
@media (max-width: 1280px) {
  .kab-gsimg { width: 46%; flex: 0 0 46%; }
  .kab-ctaimg { width: 44%; flex: 0 0 44%; }
  .kab-gsimg img { width: 100%; height: auto; aspect-ratio: 584 / 342; }
  .kab-ctaimg img { width: 100%; height: auto; aspect-ratio: 584 / 404; }
}

@media (max-width: 1100px) {
  .e-con.kab-footcols { grid-template-columns: repeat(3, 1fr); }
}

/* Nav switch happens above true mobile so the desktop nav never crowds the logo */
@media (max-width: 900px) {
  /* The mockup tightens the bar at the NAV switch (900), not at 767 */
  .e-con.kab-headerbar {
    --padding-block-start: 12px;
    --padding-block-end: 12px;
    --padding-inline-start: 24px;
    --padding-inline-end: 24px;
  }
  .kab-nav { display: none; }
  /* Belt-and-braces with the JS resize handler: .kab-dd-open would otherwise
     win on source order and leave a desktop panel painted over mobile. */
  .e-con.kab-dd,
  .e-con.kab-dd.kab-dd-open { display: none; }
  .kab-hamburger .kab-btn {
    justify-content: center;
    width: 30px;
    height: 30px;
  }
  .kab-hamburger img { width: 30px; height: 30px; }
}

@media (min-width: 901px) {
  .kab-hamburger { display: none; }
}

/* Mobile (Figma 375 frame) */
@media (max-width: 767px) {
  /* Mobile overlay ends at 82% instead of 55% (Figma mobile frame). Elementor
     does not autoplay background video on mobile by default, so this sits over
     the fallback still. */
  .e-con.kab-hero::before,
  .e-con.kab-hero > .elementor-background-video-container::before {
    background-image: linear-gradient(
      95deg,
      rgba(23, 27, 55, 0.95) 0%,
      rgba(23, 27, 55, 0.82) 46%,
      rgba(23, 27, 55, 0.82) 100%
    ) !important;
  }

  .kab-herocall, .kab-herocall > .elementor-widget-container { display: block; }
  .kab-ctacall, .kab-ctacall > .elementor-widget-container { display: block; }
  .e-con.kab-card { height: auto; aspect-ratio: 425.33 / 292.41; }

  .kab-gsimg, .kab-ctaimg { width: 100%; flex: 0 0 auto; }
  .kab-gsimg img { width: 100%; height: 212px; aspect-ratio: auto; object-fit: cover; }
  .kab-ctaimg img { width: 100%; height: 212px; aspect-ratio: auto; object-fit: cover; }

  /* Condensed mobile footer per the Figma mobile frame */
  .e-con.kab-footcols { display: none; }
  .kab-foot-tagline { display: none; }
  .kab-foot-tagline-mobile { display: block; }
}

/* ==========================================================================
   PHASE 2 - interior page sections
   Ported from html-prototypes/css/styles.css v14 (read-only source of truth).
   Elementor 4.2.x renders NO .elementor-widget-container (gotcha #2), so every
   rule targets the widget element itself.
   ========================================================================== */

/* ---------- grid rows: auto, never equal-height tracks ----------
   Gotcha #33. Elementor's container default is
   --e-con-grid-template-rows: repeat(2, 1fr), which (a) reserves a phantom
   second row and (b) makes EVERY row an equal 1fr track, so a short text-only
   tile is stretched to the tallest photo tile in its row. Setting the control
   to the `custom` unit with an empty value stops Elementor writing its own
   value - but the DEFAULT then shows through, so the variable has to be
   overridden here. `auto` gives content-height rows, which is what every
   mockup grid does. Card grids still look even because the CARD stretches
   (align-items: stretch is the grid default), not the track. */
.e-con.kab-tiles, .e-con.kab-specs, .e-con.kab-compare,
.e-con.kab-whygrid, .e-con.kab-gallery, .e-con.kab-cards {
  --e-con-grid-template-rows: auto;
  grid-auto-rows: auto;
}

/* ---------- shared: migrated body copy ---------- */
.kab-rich p { margin-bottom: 14px; }
.kab-rich p:last-child { margin-bottom: 0; }
.kab-rich a { color: #19257D; text-decoration: underline; text-underline-offset: 2px; }
/* margin-left:0 - Astra ships a 48px left margin on lists that the mockup does
   not have. It narrowed every bulleted list by 48px, so items wrapped one line
   early (168px on generator-access-platform s3 alone). */
.kab-rich ul, .kab-rich ol {
  padding-left: 22px; margin-left: 0; margin-bottom: 14px;
}
.kab-rich ul { list-style: disc outside; }
.kab-rich ol { list-style: decimal outside; }
.kab-rich li { margin-bottom: 6px; }
.kab-rich h3, .kab-rich h4 {
  font-family: "Saira", sans-serif; font-weight: 700;
  font-size: 24px; line-height: 30px; color: #14181F;
  padding-top: 6px; margin-bottom: 10px;
}
.kab-rich img { border-radius: 6px; margin: 10px 0; max-width: 100%; height: auto; }
.kab-band-dark .kab-rich a, .kab-why .kab-rich a { color: #F5C518; }

/* ---------- 1. hero-split ---------- */
.e-con.kab-herosplit { align-items: stretch; overflow: hidden; }

/* ==========================================================================
   UNBALANCED TWO-COLUMN ROWS - STICKY MEDIA COLUMN (Eric QC 2026-08-26)
   ==========================================================================
   Surveyed all 56 pages: 19 feature rows have a media column 288-1430px
   SHORTER than the copy beside it, leaving a tall empty gutter. Eric offered
   two treatments; the media column is pinned so it travels with the reader,
   which keeps the row a row (breaking the text into a full-width block below
   would change the page's rhythm on 19 sections and lose the pairing between
   a photo and the copy it illustrates).

   `position: sticky` needs an unclipped scroll ancestor, and Astra ships
   `body { overflow: hidden }`, which silently kills it. `overflow-x: clip`
   gives the same horizontal-scroll protection WITHOUT creating a scroll
   container. (Known trap - css-static-site-gotchas #5.)

   Applied only where it helps: >=1024px, and only to rows tagged by the
   builder as unbalanced. Below 1024 the row stacks, so sticky is pointless.
   ========================================================================== */
html { overflow-x: clip; }
body { overflow-x: clip; overflow-y: visible; }

@media (min-width: 1025px) {
  .e-con.kab-frow-unbal { align-items: flex-start; }
  .e-con.kab-frow-unbal > .kab-frow-img {
    position: sticky;
    top: 116px;            /* clears the sticky header */
    align-self: flex-start;
  }
}
/* gotcha #12/#23: without min-width:0 the column sizes to its longest
   unbreakable run and the H1 re-wraps a few px narrow. */
.e-con.kab-herosplit-text { flex: 1 0 0; min-width: 0; justify-content: center; }
/* The hero photo was the WORST offender Eric named: `height:100%` + cover
   stretched a 1115x461 panorama of a full wire-cage run into a 437x326 slot,
   throwing away 44.6% of it - the build showed a narrow vertical slice of a
   wide shot. `align-self: stretch` was what forced the full column height.
   Now the image keeps its own ratio, centred in the column, capped so a tall
   portrait cannot outgrow the copy beside it. */
.kab-herosplit-img { flex: 0 0 437px; align-self: stretch; }  /* hero = bleed panel (Eric 2026-08-26: heroes are EXEMPT from the no-crop rule); mockup styles.css ~896 */
.kab-herosplit-img img {
  /* Eric 2026-08-26: the no-crop rule covers MAIN-CONTENT images only; hero and
     background images are exempt and `cover` is the correct behaviour. This is
     the mockup's own declaration - the photo fills the column edge to edge,
     which is the design review's "extend image". */
  width: 437px; height: 100%; max-height: none;
  object-fit: cover; object-position: center; display: block;
}
.kab-herosplit .kab-herosub { max-width: 641px; margin-top: -14px; }
.kab-herosplit .kab-herosub p { font-size: 16px; line-height: 24px; }

/* ---------- 2. hero--interior ---------- */
.e-con.kab-hero-interior { position: relative; overflow: hidden; }
.e-con.kab-hero-interior::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: linear-gradient(95deg,
    rgba(23, 27, 55, 0.95) 0%, rgba(23, 27, 55, 0.82) 46%, rgba(23, 27, 55, 0.55) 100%);
}
/* Lift the hero's own children above the gradient ::before - but NOT the
   background-video container. Elementor positions that `absolute` to fill the
   hero; this rule was overriding it to `relative`, which made it a zero-height
   FLEX ITEM in the hero's column - and a zero-height <video> is never
   autoplayed by the browser. That is why the two interior hero videos loaded
   (readyState 4) and then just sat there paused while the homepage played:
   the homepage hero is built by kabtree.py and never gets this rule.
   Nothing calls pause() - autoplay is simply refused for a 0x0 element. */
.e-con.kab-hero-interior > *:not(.elementor-background-video-container):not(.kab-vidbtn) {
  position: relative;
  z-index: 1;
}
/* 895px is the mockup's .hero-sub max-width; 738 made the line wrap earlier
   and cost 30px of hero height (3 lines instead of 4). */
.kab-hero-interior .kab-herosub { max-width: 895px; margin-top: -10px; }

/* ---------- 3. hero-basic ---------- */
.e-con.kab-herobasic-inner { max-width: 900px; padding: 14px 0; }
.kab-herobasic .kab-herosub { max-width: 640px; }

/* ---------- 4. module-band / feature rows ---------- */
.e-con.kab-frow { align-items: flex-start; }
.e-con.kab-frow-rev { --flex-direction: row-reverse; }
.e-con.kab-frow-text { flex: 1 1 0; min-width: 0; }
.kab-frow-img { flex: 0 1 44%; min-width: 0; }
/* A row photo with an inline max-width in the mockup shrinks to fit rather than
   claiming its full 44% basis - that is what gives the text column its real
   width (769px on modular-in-plant-offices, not 616px). */
.kab-frow-img-cap { flex: 0 1 auto; }
/* ==========================================================================
   NEVER CROP THE CLIENT'S LIVE IMAGES (Eric, global rule 2026-08-26)
   ==========================================================================
   Audited all 313 rendered content images: 84 were losing >=12% of the source
   to `object-fit: cover`, worst 61.5%. Eric's rule is that the client's own
   photo wins over design consistency - a portrait shot of a lift must show the
   lift, not a letterboxed slice of it.

   So the CONTENT-PHOTO rules below switch cover -> contain and let the image
   keep its natural ratio inside a capped box. The box keeps a max-height so a
   tall portrait cannot blow the section open, and `width:auto` stops a narrow
   image being stretched. Nothing is discarded.

   DELIBERATELY LEFT ON `cover`:
     .kab-herosplit-img  - a hero band's photo is a bleed panel that must fill
                           its column edge-to-edge; contain would letterbox the
                           hero itself.
     .kab-cc-img         - compare cards are a fixed-ratio set; mixed ratios
                           break the row.
     .kab-ctaimg / .kab-gsimg - homepage, signed off and pixel-exact.
   Those are design surfaces, not "the client's photo on a content page".
   ========================================================================== */
.kab-frow-img img {
  width: 100%; height: auto; max-height: 430px;
  object-fit: contain; object-position: center;
  border-radius: 6px; display: block;
}
.kab-frow-compact .kab-frow-img img { max-height: 260px; }
.e-con.kab-frow-solo .kab-frow-text { max-width: 800px; }
.e-con.kab-frow-cta { padding-top: 4px; }

/* .specs-head - intro beside a 377x247 photo; stacks to a full-width 260px
   band at 1024 (4 pages use this). */
.e-con.kab-specshead > .kab-intro { flex: 1 1 0; min-width: 0; max-width: 783px; }
.kab-specshead-img { flex: 0 1 377px; min-width: 0; }
.kab-specshead-img img {
  width: 100%; height: auto; max-height: 247px;
  object-fit: contain; object-position: center; display: block;
}
@media (max-width: 1024px) {
  .e-con.kab-specshead > .kab-intro { max-width: none; }
  .kab-specshead-img { flex: none; width: 100%; }
  .kab-specshead-img img { height: 260px; }
}

/* ---------- 5a. specs grid ---------- */
.e-con.kab-specs { padding-top: 14px; }
.kab-spec .elementor-heading-title { margin-bottom: 8px; }

/* ---------- 5b. tiles ---------- */
.e-con.kab-tiles { padding-top: 8px; }
.kab-tile-img img {
  width: 100%; height: auto; max-height: 247px;
  object-fit: contain; object-position: center;
  display: block; margin-bottom: 12px;
}
/* F3: an ICON tile must never be poured into the 247px photo box. */
.kab-tile-icon img { width: 48px; height: 48px; object-fit: contain; border-radius: 0; margin-bottom: 4px; }
/* Inside a .kab-spec the icon carries the mockup's full 16px margin - the tile
   variant above sits in a 10px flex gap, a spec does not. */
.kab-spec .kab-tile-icon img { margin-bottom: 16px; }
.kab-tile-more a { text-decoration: none; }

/* ---------- 5c. compare cards ---------- */
.e-con.kab-cc {
  background: #F4F5F7; border: 1px solid #9194A3;
  --padding-block-start: 0px; --padding-block-end: 0px;
  --padding-inline-start: 0px; --padding-inline-end: 0px;
}
.kab-cc-img img { width: 100%; aspect-ratio: 382 / 238.75; object-fit: cover; display: block; }
.e-con.kab-cc-body {
  flex: 1 0 0; justify-content: space-between;
  --padding-block-start: 26px; --padding-block-end: 28px;
  --padding-inline-start: 24px; --padding-inline-end: 24px;
}
.kab-cc-title { text-transform: uppercase; }
.kab-cc-label p { text-transform: uppercase; letter-spacing: 1.56px; font-weight: 600; }
/* F16 / client ADA fix: solid yellow button, dark text (was 1.63:1 link text). */
.e-con.kab-cc .elementor-button { width: 100%; }
.e-con.kab-callout { border-left: 3px solid #F5C518; padding: 0 28px 0 31px; max-width: 382px; }
.kab-callout .kab-rich p { font-size: 18px; line-height: 26px; color: #14181F; }

/* ---------- 6. FAQ (native Nested Accordion) ---------- */
.e-con.kab-faq { background: #F6F7F9; }
.kab-faq-acc .e-n-accordion-item { background: #FFFFFF; border: 1px solid #9194A3; }
/* The mockup's .faq-list is a 12px-gap flex column. margin-bottom on the item
   does NOT survive Elementor's nested-accordion layout (~1.2px per item), and
   .kab-faq-acc is the WIDGET, not the list - the items sit in an inner
   .e-n-accordion. That is the element that needs the gap. */
.kab-faq-acc .e-n-accordion { display: flex; flex-direction: column; gap: 12px; }
.kab-faq-acc .e-n-accordion-item-title { padding: 20px; gap: 16px; align-items: center; }
/* The mockup's caret is a 28px IMG. Elementor's icon span inherits the 30px
   line-height of the title text, so the row measured 72px against the
   mockup's 70px - 2px x 8 questions x 5 pages. Pin the icon box to 28px. */
.kab-faq-acc .e-n-accordion-item-title-icon {
  height: 28px; width: 28px; line-height: 28px; flex: 0 0 28px;
}
/* The exported chevron carries `preserveAspectRatio="none"`, so it stretches
   to whatever box it is given instead of keeping its 20x20 ratio. It was
   painting as a flat DASH: Elementor's own
   `.elementor-widget-n-accordion .e-n-accordion-item-title-icon span > svg
    { height: var(--n-accordion-icon-size) }`
   out-specifies a plain `.kab-faq-acc ... svg` rule and was forcing 15px of
   height into a 28px-wide box. Set ELEMENTOR'S OWN variable rather than
   fighting the rule - then the widget sizes the glyph square by itself and no
   !important is needed. (Same lesson as gotcha #18: match the mechanism, do
   not out-shout it.)                                                       */
.kab-faq-acc .e-n-accordion-item-title-icon svg { width: 28px; display: block; }

/* Q5 (Quincy 2026-08-26): the caret was Elementor's default fa-plus/fa-minus
   at the row START; the mockup is ONE chevron at the row END that ROTATES
   180deg when the item opens (styles.css ~1094-1100). The generator now sets
   the chevron SVG for both states plus icon_position/end and
   position_horizontal/stretch; those controls resolve to the two custom
   properties below, which are pinned here as well so the caret cannot drift
   if a control name changes under us again (it already did once - the old
   `accordion_item_title_position_horizontal:"start"` was storing silently
   and doing nothing).
   Only `transform` is transitioned - the one safe property alongside
   `opacity` (gotcha #21).                                                  */
.kab-faq-acc .e-n-accordion-item-title {
  --n-accordion-title-justify-content: space-between;
  --n-accordion-title-flex-grow: 1;
  --n-accordion-title-icon-order: initial;
}
.kab-faq-acc .e-n-accordion-item-title-icon,
.kab-faq-acc .e-n-accordion-item-title-icon > span,
.kab-faq-acc .e-n-accordion-item-title-icon svg {
  transition: transform 0.2s ease;
}
.kab-faq-acc .e-n-accordion-item[open] > .e-n-accordion-item-title .e-n-accordion-item-title-icon svg {
  transform: rotate(180deg);
}
@media (prefers-reduced-motion: reduce) {
  .kab-faq-acc .e-n-accordion-item-title-icon,
  .kab-faq-acc .e-n-accordion-item-title-icon > span,
  .kab-faq-acc .e-n-accordion-item-title-icon svg { transition: none; }
}
.kab-faq-acc .e-n-accordion-item-title-text {
  font-family: "Public Sans", sans-serif; font-weight: 700;
  /* 18px is what the mockup RENDERS (getComputedStyle on .faq-q), despite a
     17/26 declaration also being present - the cascade resolves to 18/30.
     BUT: in the mockup the 30px line-height applies to the flex ROW, whose
     content box is 28px (the caret IMG). Elementor wraps the text in its own
     span, so a 30px line-height there made the row 72px against 70px. 28px
     matches the row the mockup actually draws. */
  font-size: 18px; line-height: 28px; color: #14181F;
}
.kab-faq-acc .e-con.kab-faq-a {
  --padding-block-start: 0px; --padding-block-end: 20px;
  --padding-inline-start: 20px; --padding-inline-end: 48px;
}

/* ---------- 7. cta-strip ---------- */
.e-con.kab-ctastrip { --padding-block-start: 0px; }
.e-con.kab-ctastrip-in {
  max-width: 800px; margin: 0 auto; background: #171B37; border-radius: 8px;
  --padding-block-start: 38px; --padding-block-end: 38px;
  --padding-inline-start: 46px; --padding-inline-end: 46px;
  --flex-direction: row; align-items: center; justify-content: space-between;
}
.kab-ctastrip-in .kab-rich p { font-size: 18px; line-height: 28px; color: #FFFFFF; }

/* ERIC QC 2026-08-26: a CTA strip carrying a paragraph rather than a line was
   squeezed into the 800px strip width and ran very tall and narrow. Long ones
   (>=60 words: guard-shacks x2, controlled-environments) take the full 1200
   content grid instead. The short strips keep the mockup's 800px. */
.e-con.kab-ctastrip-in.kab-ctastrip-wide { max-width: 1200px; }

/* ERIC QC 2026-08-26: a small trailing "Learn more about our other..." block
   is centre-justified, so it reads as a closing note rather than an orphaned
   left-aligned fragment. Applied by shape (see sections.py small_tail), not
   per page. */
.e-con.kab-band-tail { align-items: center; text-align: center; }
.e-con.kab-band-tail .kab-frow,
.e-con.kab-band-tail .kab-frow-text,
.e-con.kab-band-tail .kab-frows { align-items: center; width: 100%; }
.e-con.kab-band-tail .kab-checklist .elementor-icon-list-items {
  justify-content: center;
  display: inline-grid;
}
.e-con.kab-band-tail .kab-checklist .elementor-icon-list-item { text-align: left; }
/* The strip is a flex ROW: the copy takes the free space, the button keeps its
   natural width. Without these the button widget collapsed to ~14px. */
.e-con.kab-ctastrip-in > .elementor-widget-text-editor { flex: 1 1 auto; min-width: 0; }
.e-con.kab-ctastrip-in > .elementor-widget-button { flex: 0 0 auto; width: auto; }
/* The mockup's button is a flex box that keeps its natural content width;
   Elementor's inline-block button inside a shrink-wrapped widget rendered
   50px narrower, which changed how the copy beside it wrapped. */
.kab-ctastrip-in .elementor-button {
  flex-shrink: 0; white-space: nowrap;
  display: flex; align-items: center; justify-content: center;
  /* .btn-primary carries min-width:211px in the mockup. Scoped here rather
     than applied globally: several contexts (compare cards, panel cards)
     explicitly reset it to 0, so a blanket rule would fight them. */
  min-width: 211px;
}

/* ---------- 8. content-band ---------- */
.e-con.kab-cband-in { max-width: 800px; margin: 0 auto; }
.kab-gal-img img {
  width: 100%; height: auto; max-height: 300px;
  object-fit: contain; object-position: center;
  border-radius: 6px; display: block;
}

/* ---------- 9. check list (native Icon List) ---------- */
.kab-checklist .elementor-icon-list-items { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px 32px; }
/* padding-bottom:0 - Elementor's icon-list ships a 5px bottom pad that the
   mockup's .check-list does not have; across a 12-item list that is 60px
   (and these lists appear on most why-bands). */
.kab-checklist .elementor-icon-list-item {
  position: relative; padding: 0 0 0 30px;
}
.kab-checklist .elementor-icon-list-item::before {
  content: ""; position: absolute; left: 0; top: 3px; width: 20px; height: 20px;
  background: url("/wp-content/uploads/2026/08/icon-check-green.svg") center / contain no-repeat;
}
.kab-checklist .elementor-icon-list-icon { display: none; }

/* ERIC QC 2026-08-26 (GLOBAL): the green check reads badly on the navy bands.
   On ANY dark surface the mark is white. The tick is a background-image SVG,
   so a brightness/invert filter recolours it without shipping a second asset
   or touching the green version used on light bands. */
.e-con.kab-band-dark .kab-checklist .elementor-icon-list-item::before,
.e-con.kab-why .kab-checklist .elementor-icon-list-item::before,
.e-con.kab-cta .kab-checklist .elementor-icon-list-item::before,
.e-con.kab-introband .kab-checklist .elementor-icon-list-item::before,
.kab-checklist-dark .elementor-icon-list-item::before {
  filter: brightness(0) invert(1);
}

/* ---------- 10. intro-band + panel card ---------- */
.e-con.kab-introband { --padding-block-start: 80px; --padding-block-end: 72px; }
/* The intro band's INNER container carries `kab-introband-in`, not
   `kab-introband`, so the old ancestor selector never matched and the lede ran
   the full 1200 grid - that is the design review's "Line break" note on
   /contact-us/. The mockup declares `.intro-band .intro-sub { max-width:760px }`
   (styles.css ~1123). Also cap the H1 at the mockup's 900px. Doubled class beats
   Elementor's per-widget max-width:100%. */
.kab-intro-sub.kab-intro-sub,
.kab-intro-sub.kab-intro-sub p { max-width: 760px; }
.kab-introband-in .elementor-widget-heading.elementor-widget-heading,
.kab-introband-in .elementor-widget-heading.elementor-widget-heading h1 { max-width: 900px; }
@media (max-width: 767px) {
  .kab-intro-sub.kab-intro-sub,
  .kab-intro-sub.kab-intro-sub p,
  .kab-introband-in .elementor-widget-heading.elementor-widget-heading,
  .kab-introband-in .elementor-widget-heading.elementor-widget-heading h1 { max-width: none; }
}
.e-con.kab-cardsection { background: #FFFFFF; justify-content: center; }
.e-con.kab-panel {
  border: 1px solid #C2C2C5; box-shadow: 0 12px 20px rgba(16, 24, 40, 0.08);
  --padding-block-start: 45px; --padding-block-end: 45px;
  --padding-inline-start: 45px; --padding-inline-end: 45px;
  width: 100%;
}
.e-con.kab-panel-narrow { max-width: 586px; }
.kab-panel .elementor-button { align-self: flex-start; }
.kab-ty-check img { width: 52px; height: 52px; }
.kab-ty-num p { width: 38px; }
.kab-ty-soon p { text-transform: uppercase; }
/* Outline button variant (thank-you "CALL US NOW").
   Elementor writes the button background from the widget's colour GLOBAL at
   .elementor-<page> .elementor-element-<id> .elementor-button (0-3-0), which
   beats a plain .kab-btn-outline rule - same family as gotcha #18. Doubling
   the class matches specificity without !important. */
.elementor .kab-btn-outline.kab-btn-outline .elementor-button,
.elementor .kab-btn-outline.kab-btn-outline .elementor-button:visited {
  background-color: transparent; background-image: none;
  border: 1px solid #C2C2C5; color: #14181F;
  transition: border-color 0.18s ease;
}
.elementor .kab-btn-outline.kab-btn-outline .elementor-button:hover,
.elementor .kab-btn-outline.kab-btn-outline .elementor-button:focus {
  border-color: #14181F; background-color: transparent; color: #14181F;
}

/* HubSpot embed - the form itself is styled in HubSpot by RFL. */
.kab-hs-form { width: 100%; }

/* ---------- 11. dark band overrides ---------- */
/* Q2 (Quincy 2026-08-26): headings INSIDE a rich text widget on a dark band
   were rendering near-black on navy - rgb(20,24,31) on rgb(23,27,55) = 1.06:1,
   where the mockup is #fff at 16.82:1. 22 text nodes across 8 pages.
   WHY THE OLD RULE MISSED THEM: it only named `p` and `li`. A migrated body
   block is real HTML, so an h3/h4 can sit inside the same .kab-rich widget,
   and it simply INHERITS the widget colour Elementor writes at
   `.elementor-<page> .elementor-element-<id>` (0-3-0). Naming the heading
   element beats inherited colour outright, so no !important is needed.
   This is a faithful port of the mockup's own rule set (styles.css ~1683):
       .band-dark .h2, .band-dark h3 { color:#fff }
       .band-dark p, .band-dark .rich p, .band-dark .rich li,
       .band-dark .spec p { color:#eceff4 }
       .band-dark .eyebrow span { color:#fff }
       .band-dark .check-list li { color:#eceff4 }
       .band-dark .rich a { color: var(--text-accent) }                     */
.e-con.kab-band-dark .kab-rich h1,
.e-con.kab-band-dark .kab-rich h2,
.e-con.kab-band-dark .kab-rich h3,
.e-con.kab-band-dark .kab-rich h4,
.e-con.kab-band-dark .kab-rich h5,
.e-con.kab-band-dark .kab-rich h6,
.e-con.kab-band-dark .kab-spec h3,
.e-con.kab-band-dark .kab-tile h3 { color: #FFFFFF; }

.e-con.kab-band-dark .kab-rich p,
.e-con.kab-band-dark .kab-rich li,
.e-con.kab-band-dark .kab-rich td,
.e-con.kab-band-dark .kab-rich th,
.e-con.kab-band-dark .kab-spec p,
.e-con.kab-band-dark .kab-checklist li,
.e-con.kab-band-dark .kab-checklist .elementor-icon-list-text { color: #ECEFF4; }

/* The navy WHY band is its own dark surface (.kab-why, not .kab-band-dark).
   Elementor's `description_color` only paints .elementor-icon-box-description;
   migrated why-items carry raw HTML, so their <p> lands as a direct child of
   .elementor-icon-box-content and never receives it - 6 nodes on
   vertical-material-lifts alone read 1.06:1. The mockup solves this with a
   plain `.why-item p { color:#eceff4 }` (styles.css ~589); same rule here.  */
.e-con.kab-why .elementor-icon-box-content h1,
.e-con.kab-why .elementor-icon-box-content h2,
.e-con.kab-why .elementor-icon-box-content h3,
.e-con.kab-why .elementor-icon-box-content h4,
.e-con.kab-why .kab-rich h1,
.e-con.kab-why .kab-rich h2,
.e-con.kab-why .kab-rich h3,
.e-con.kab-why .kab-rich h4 { color: #FFFFFF; }

.e-con.kab-why .elementor-icon-box-content p,
.e-con.kab-why .elementor-icon-box-content li,
.e-con.kab-why .kab-rich p,
.e-con.kab-why .kab-rich li { color: #ECEFF4; }

/* A why-band's closing summary paragraph. The mockup caps it at 760px with an
   inline max-width on `.why-rich` (3 bands carry one). Elementor gives every
   widget `max-width:100%` at a selector a single class cannot beat, so the
   class is DOUBLED to match specificity - the same technique already used for
   the thank-you outline button and the card hover (gotcha #18 family). No
   !important. */
.e-con.kab-why .kab-whytail.kab-whytail { max-width: 760px; }

/* A headless why-item (migrated copy-only card - no h3) renders its copy as a
   bare <p> inside .elementor-icon-box-content, where Astra's default
   paragraph bottom margin (28px) applies. The mockup's `.why-item p` carries
   none, so every one of these cards was 76px tall against the mockup's 48px -
   +84px per why-grid on the 3 bands built this way. Last child only, so a
   multi-paragraph card keeps its internal rhythm. */
/* Elementor wraps `description_text` in its OWN
   <p class="elementor-icon-box-description">, and the migrated copy is already
   `<p>...</p>` - so a headless why-item renders THREE paragraphs: an empty
   wrapper, the real copy, and an empty tail. Astra's default 28px paragraph
   margin then applied to the real one, making every card 76px against the
   mockup's 48px (+84px per why-grid, on the 3 bands built this way). Zero the
   margins and collapse the empties. */
.e-con.kab-why .elementor-icon-box-content > p { margin-bottom: 0; }
.e-con.kab-why .elementor-icon-box-content > p:empty { display: none; }
.e-con.kab-why p.elementor-icon-box-description:empty { display: none; }

/* ---------- 12. contact CTA without a photo (F5) ---------- */
.e-con.kab-cta-noimg .kab-ctatext { width: 100%; }

/* ---------- responsive ---------- */
/* Grid collapse points come from the MOCKUP, not Elementor's tablet default:
   specs / tiles / compare / why go 1-col at 1024, galleries at 900, and the
   2-col "flow" specs variant holds 2 until 767. Elementor's own tablet
   breakpoint sat at 2 columns and left the 768 band ~800px off. */
@media (max-width: 1024px) {
  /* Mockup stacks the split hero at 1024 (direction set in the generator) and
     the photo becomes a full-width 300px band. */
  .kab-herosplit-img { flex: 0 0 auto; width: 100%; }
  .kab-herosplit-img img { width: 100%; height: auto; max-height: 300px; }
  .e-con.kab-specs, .e-con.kab-tiles,
  .e-con.kab-compare, .e-con.kab-whygrid {
    --grid-template-columns: 1fr;
    grid-template-columns: 1fr;
  }
  /* Once a compare card is full-width its 382/238.75 ratio would make the
     photo 429px tall; the mockup pins it to a 260px band through the tablet
     range and only restores the ratio below 767. */
  .kab-cc-img img { aspect-ratio: auto; height: 260px; }
  /* Feature rows stack at 1024 in the mockup (not 767) and cap the photo at
     340px - leaving them side-by-side through the tablet band left one row
     ~680px tall.
     NOTE: do NOT use align-items:stretch here. On the mockup the img IS the
     flex child; in Elementor the child is the image WIDGET, and stretch made
     that wrapper grow to the column height (14,000px) while the <img> inside
     stayed 340 - which also produced horizontal scroll. Keep flex-start. */
  /* direction now comes from the generator (flex_direction_tablet) - Elementor
     writes --flex-direction at a specificity a plain class rule cannot beat. */
  .kab-frow-img { flex: 0 0 auto; width: 100%; align-self: flex-start; }
  .kab-frow-img img { max-height: 340px; }
}

@media (max-width: 900px) {
  /* handled by the generator (flex_direction_tablet) - the mockup stacks the
     FAQ at 1024, not 900. */
  /* Only the 3-col gallery collapses at 900 - the 2-col one holds two columns
     all the way down to 767, per the mockup's own breakpoints. Collapsing it
     here doubled the gallery's height through the whole tablet range. */
  .e-con.kab-gallery-3 {
    --grid-template-columns: 1fr;
    grid-template-columns: 1fr;
  }
}

@media (max-width: 767px) {
  .kab-herosplit-img img { height: 212px; }
  .e-con.kab-frow, .e-con.kab-frow-rev { --flex-direction: column; }
  .kab-frow-img { flex-basis: auto; width: 100%; }
  .kab-checklist .elementor-icon-list-items { grid-template-columns: 1fr; }
  .e-con.kab-gallery, .e-con.kab-gallery-3,
  .e-con.kab-specs, .e-con.kab-tiles, .e-con.kab-compare, .e-con.kab-whygrid {
    --grid-template-columns: 1fr;
    grid-template-columns: 1fr;
  }
  .e-con.kab-gallery { --grid-gaps-row: 16px; --grid-gaps-column: 16px; gap: 16px; }
  .e-con.kab-ctastrip-in {
    --flex-direction: column; align-items: flex-start;
    --padding-inline-start: 24px; --padding-inline-end: 24px;
  }
  .e-con.kab-callout { max-width: none; }
  .e-con.kab-panel {
    --padding-inline-start: 24px; --padding-inline-end: 24px;
    --padding-block-start: 32px; --padding-block-end: 32px;
  }
  .e-con.kab-introband { --padding-block-start: 48px; --padding-block-end: 44px; }
  /* Tile photos stay 247px at every width - the mockup never shrinks them.
     An invented 200px cap here cost ~43px per tile on every tile page. */
  .kab-cc-img img { height: auto; aspect-ratio: 382 / 238.75; }
}

/* Motion: fadeInUp is CARDS ONLY (house rule). The mockup's 150ms stagger is
   kept per Eric pending the house-100ms call (sections.py STAGGER_MS). */
@media (prefers-reduced-motion: reduce) {
  .kab-tile, .e-con.kab-cc, .kab-ty-step { animation: none !important; opacity: 1 !important; }
}

/* about-lead: the est-1985 badge sits beside the prose at its own size. */
.e-con.kab-about-lead { max-width: 940px; margin: 0 auto; }
.e-con.kab-about-lead .kab-cband-in { margin: 0; flex: 1 1 0; min-width: 0; }
.kab-about-badge img { width: 220px; height: auto; display: block; }
@media (max-width: 767px) {
  .kab-about-badge img { width: 160px; }
}

/* compare-head: intro (760) beside the callout (382) per the mockup. */
.e-con.kab-compare-head > .kab-intro { flex: 1 1 0; min-width: 0; max-width: 760px; }
.e-con.kab-compare-head > .kab-callout { flex: 0 0 382px; }
@media (max-width: 767px) {
  .e-con.kab-compare-head > .kab-callout { flex-basis: auto; }
}

/* why-band media: the RFL "AI why-band" loop (video) or a still. */
.kab-whyphoto { width: 100%; }
.kab-whyphoto video,
.kab-whyphoto img,
.kab-whyphoto .elementor-wrapper,
.kab-whyphoto .elementor-video {
  width: 100%; height: 646px; object-fit: cover; display: block; background: #171B37;
}
.kab-whyphoto .elementor-wrapper { aspect-ratio: auto; }
/* The why-band VIDEO is a background fill and stays `cover`. A STILL in that
   slot is a client photo, so it keeps its own ratio (a 750x1000 portrait was
   losing 59.6% of itself to the 1200x646 letterbox). */
.kab-whyphoto img {
  height: auto; max-height: 646px;
  object-fit: contain; object-position: center;
}
@media (max-width: 1024px) {
  .kab-whyphoto video, .kab-whyphoto img,
  .kab-whyphoto .elementor-wrapper, .kab-whyphoto .elementor-video { height: 320px; }
}
@media (max-width: 767px) {
  .kab-whyphoto video, .kab-whyphoto img,
  .kab-whyphoto .elementor-wrapper, .kab-whyphoto .elementor-video { height: 212px; }
}

/* 3-up gallery: the mockup forces a 3:2 frame so mixed-ratio migrated photos
   line up (.gallery-grid-3 img { aspect-ratio: 3/2 }). Without it each image
   keeps its natural ratio and the band grows ~560px. */
/* Q8 (2026-08-26): one mockup section overrides only its TOP padding inline
   (about.html `style="background:var(--bg-subtle); padding-top:56px;"`).
   Elementor's padding control is all-or-nothing - it emits no rule unless all
   four sides are set - and setting all four would freeze the horizontal
   gutter the child theme varies per breakpoint. So the generator emits a
   class and only the top is overridden. Add a sibling rule if another value
   ever appears in the mockups.                                             */
.e-con.kab-padtop-56 { --padding-block-start: 56px; padding-block-start: 56px; }

/* `.content-band` is a plain block in the mockup - prose and gallery sit
   FLUSH (measured 0 between them). The Kit's default 20px flex gap has to be
   zeroed explicitly, because gap:0 in the generator just omits the setting. */
.e-con.kab-cband { --flex-gap: 0px; --gap: 0px; gap: 0px; }

.e-con.kab-gallery-3 { max-width: 880px; }

/* ERIC QC 2026-08-26: a FOUR-image gallery sits on one row with a caption
   under each tile (the live site labels these; the mockup does not). */
/* The 4-up lives inside a feature-row TEXT column (~44% of the grid), which
   squeezed the tiles to 136px. Break it out to the row's full width so the
   four tiles are actually usable - the row stays a row, the gallery spans it. */
.e-con.kab-gallery-4 { max-width: 1200px; }
.e-con.kab-frow:has(.kab-gallery-4) { --flex-direction: column; flex-direction: column; }
.e-con.kab-frow:has(.kab-gallery-4) > .kab-frow-text,
.e-con.kab-frow:has(.kab-gallery-4) > .kab-frow-img { width: 100%; max-width: none; flex: 1 1 auto; }
.e-con.kab-gal-tile { align-items: stretch; }
.kab-gal-cap p {
  margin: 0; font-size: 14px; line-height: 20px; text-align: center;
  color: #4A4E58;
}
.e-con.kab-band-dark .kab-gal-cap p,
.e-con.kab-why .kab-gal-cap p { color: #ECEFF4; }
@media (max-width: 1024px) {
  .e-con.kab-gallery-4 { --grid-template-columns: repeat(2, 1fr);
                         grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 767px) {
  .e-con.kab-gallery-4 { --grid-template-columns: 1fr; grid-template-columns: 1fr; }
}
.e-con.kab-gallery-3 .kab-gal-img img { aspect-ratio: 3 / 2; height: auto; }

/* ==========================================================================
   PHASE 2 batch 3 - blog + case-study templates (Loop Grid)
   Ported from html-prototypes/css/styles.css v14 (.post-grid / .post-card).
   ========================================================================== */

/* ---------- the loop-item card ---------- */
.e-con.kab-postcard {
  background: #FFFFFF;
  border: 1px solid #E3E5E8;
  border-radius: 8px;
  overflow: hidden;
  height: 100%;
  --padding-block-start: 0px; --padding-block-end: 0px;
  --padding-inline-start: 0px; --padding-inline-end: 0px;
}
.kab-postcard-img img {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block;
}
.e-con.kab-postcard-body {
  flex: 1;
  --padding-block-start: 22px; --padding-block-end: 24px;
  --padding-inline-start: 24px; --padding-inline-end: 24px;
}
.kab-postcard-title .elementor-heading-title {
  font-size: 22px; line-height: 28px;
}
.kab-postcard-title a { color: inherit; text-decoration: none; }
.kab-postcard-title a:hover { color: #19257D; }
.kab-postcard-exc { font-size: 15px; line-height: 23px; }
/* READ MORE pins to the bottom so every card's CTA lines up */
.kab-postcard-more { margin-top: auto; padding-top: 6px; }
.kab-postcard-more a { text-decoration: none; }

/* post-info meta: the mockup shows plain "KABTech - date", no icons */
.kab-postmeta .elementor-icon-list-icon,
.kab-postmeta-hero .elementor-icon-list-icon { display: none; }
.kab-postmeta .elementor-inline-item { text-transform: uppercase; letter-spacing: .1em; }
/* Elementor already emits its own inline separator element; adding a ::after
   dot produced a doubled divider AND forced the meta onto two lines. Style the
   native separator instead and let the row stay on one line. */
.kab-postmeta .elementor-icon-list-items,
.kab-postmeta-hero .elementor-icon-list-items {
  display: flex; flex-wrap: nowrap; align-items: center; gap: 6px;
  margin: 0; padding: 0;
}
.kab-postmeta .elementor-icon-list-item,
.kab-postmeta-hero .elementor-icon-list-item { white-space: nowrap; }
/* ...but a post filed in several categories makes that ONE long unbreakable
   row, which overflowed the viewport at 390 (a real horizontal scroll on
   structural-mezzanine-guide). The terms list must be allowed to wrap. */
.kab-postmeta-hero .elementor-icon-list-items,
.kab-postmeta .elementor-icon-list-items { flex-wrap: wrap; }
.kab-postmeta-hero .elementor-post-info__terms-list,
.kab-postmeta .elementor-post-info__terms-list { white-space: normal; }
.kab-postmeta-hero .elementor-icon-list-item,
.kab-postmeta .elementor-icon-list-item { max-width: 100%; min-width: 0; }
.kab-postmeta-hero { color: rgba(255, 255, 255, .75); }
.kab-postmeta-hero .elementor-icon-list-text { color: rgba(255, 255, 255, .75); }
/* the hero meta reuses the eyebrow bar, so drop the list's own bullet spacing */
.kab-postmeta-hero .elementor-icon-list-items { margin: 0; padding: 0; }

/* ---------- the grid ---------- */
.kab-loopgrid .elementor-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 38px;
  align-items: stretch;
}
.kab-loopgrid .elementor-grid-item { height: 100%; }
.e-con.kab-what-posts { --padding-block-start: 80px; }

/* pagination */
.kab-loopgrid .elementor-pagination {
  display: flex; gap: 10px; justify-content: center; align-items: center;
  padding-top: 46px;
}
.kab-loopgrid .elementor-pagination .page-numbers {
  min-width: 38px; height: 38px; display: inline-flex; align-items: center;
  justify-content: center; border: 1px solid #E3E5E8; color: #14181F;
  text-decoration: none; font-weight: 700; font-size: 14px;
}
.kab-loopgrid .elementor-pagination .page-numbers.current {
  background: #F5C518; border-color: #F5C518;
}

/* ---------- single article ---------- */
.kab-article-band .kab-cband-in { max-width: 800px; margin: 0 auto; }
.kab-article p { margin-bottom: 14px; }
.kab-article ul, .kab-article ol { padding-left: 22px; margin-left: 0; margin-bottom: 14px; }
.kab-article ul { list-style: disc outside; }
.kab-article ol { list-style: decimal outside; }
.kab-article li { margin-bottom: 6px; }
.kab-article h2, .kab-article h3, .kab-article h4 {
  font-family: "Saira", sans-serif; font-weight: 700; color: #14181F;
  padding-top: 10px; margin-bottom: 10px;
}
.kab-article h2 { font-size: 30px; line-height: 36px; }
.kab-article h3 { font-size: 24px; line-height: 30px; }
.kab-article a { color: #19257D; text-decoration: underline; text-underline-offset: 2px; }
.kab-article img { max-width: 100%; height: auto; border-radius: 6px; margin: 12px 0; }

@media (max-width: 1024px) {
  .kab-loopgrid .elementor-grid { grid-template-columns: repeat(2, 1fr); gap: 24px; }
}
@media (max-width: 767px) {
  .kab-loopgrid .elementor-grid { grid-template-columns: 1fr; }
}

/* module-band gallery sits inside the prose measure (800px), the same width as
   the .content-band-inner it follows - NOT the full 1200 grid. Left full-width
   it rendered 588px-wide images against the mockup's 388px. */
.e-con.kab-band > .e-con.kab-gallery { max-width: 800px; margin-inline: auto; width: 100%; }
/* ...unless the section has no prose block, in which case the mockup lets the
   gallery span the full 1200 grid. */
.e-con.kab-band > .e-con.kab-gallery-full { max-width: none; }

/* Spec tables render at browser defaults in the mockup - it ships NO table CSS
   at all. Astra does: it adds 11.2px/16px cell padding, which cost ~23px on
   every row (2170px vs 1538px on the 24-row guard-shacks spec table).
   Reset to the mockup's baseline; 8 tables across 4 pages depend on this. */
.kab-rich table { border-collapse: collapse; width: 100%; }
.kab-rich table th, .kab-rich table td {
  padding: 0; border: 0; background: none; vertical-align: top;
}

/* Inline article photos (.content-img in the mockup) sit BETWEEN prose blocks
   inside a Text Editor, so they need their own rule - the widget-level image
   CSS does not reach them. Spec copied from styles.css. */
.kab-rich .kab-inline-img { width: 100%; border-radius: 6px; display: block; }
/* Inline article photos are the client's own images inside migrated copy -
   never cropped. Height follows the picture; the cap only stops a very tall
   portrait dominating the measure. */
.kab-cband-in .kab-rich .kab-inline-img {
  height: auto; max-height: 480px; object-fit: contain; object-position: left;
}
.kab-band .kab-rich .kab-inline-img,
.kab-what .kab-rich .kab-inline-img { max-width: 900px; }

/* .specs-grid vs .specs-grid-flow collapse at DIFFERENT widths (mockup):
   the plain 3-col grid drops straight to 1 column at 1024; the flow variant
   steps 3 -> 2 at 1024 and only reaches 1 column at 767. */
@media (max-width: 1024px) {
  .e-con.kab-specs-flow {
    --grid-template-columns: repeat(2, 1fr);
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 767px) {
  .e-con.kab-specs-flow {
    --grid-template-columns: 1fr;
    grid-template-columns: 1fr;
  }
}

/* .hero--interior vertical padding is 88px in the mockup (.hero-content),
   not the 80px section default - 16px per hero, and the 26px content gap
   accounts for the rest of the ~112px shortfall. */
.e-con.kab-hero-interior {
  --padding-block-start: 88px; --padding-block-end: 88px;
}
@media (max-width: 767px) {
  .e-con.kab-hero-interior {
    --padding-block-start: 32px; --padding-block-end: 32px;
  }
}

/* .hero-strong: bold reassurance line under the hero sub (6 pages). */
.kab-herostrong { font-weight: 600; }
.kab-herostrong p { font-size: 16px; line-height: 30px; margin-top: 2px; }
@media (max-width: 767px) {
  .kab-herostrong p { font-weight: 700; line-height: 24px; }
}

/* .faq-intro is a sticky 432px column beside the list; the list takes the rest. */
.e-con.kab-faq > .kab-intro {
  width: 432px; flex: 0 0 432px; position: sticky; top: calc(84px + 32px);
}
.e-con.kab-faq > .kab-faq-acc { flex: 1 0 0; width: auto; }
@media (max-width: 1024px) {
  .e-con.kab-faq > .kab-intro {
    width: 100%; flex: none; position: static;
  }
  .e-con.kab-faq > .kab-faq-acc { width: 100%; flex: none; }
}

/* Testimonial quote cards (.quote-grid / .quote-card in the mockup, 2 pages). */
/* TESTIMONIAL CARD - REDESIGNED (Eric QC 2026-08-26).
   The previous card was a white 8px-radius box with a 3px yellow LEFT BORDER.
   Eric called that pattern out by name and banned it. Rebuilt in the site's
   own card language, which is what every other surface here already uses:
   square corners, one flat hairline border, no decorative rule. The accent
   colour appears once, as an oversized quote mark, and the attribution row
   (company logo + name + company) is separated by a hairline instead of a
   coloured bar. */
.e-con.kab-quote {
  background: #FFFFFF;
  border: 1px solid #E3E5E8;
  border-radius: 0;
  --padding-block-start: 30px; --padding-block-end: 30px;
  --padding-inline-start: 32px; --padding-inline-end: 32px;
  height: 100%;
  position: relative;
}
/* The accent shows up once, as a quote mark ABOVE the text. Deliberately in
   normal flow rather than absolutely positioned: an absolute mark sat on top
   of the first line of every card. */
.e-con.kab-quote::before {
  content: "“";
  display: block;
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 700;
  font-size: 52px;
  line-height: 0.6;
  height: 30px;
  color: #F5C518;
  pointer-events: none;
}
.kab-quote-t p { font-size: 16px; line-height: 26px; font-style: italic; }
.kab-quote-t p:last-child { margin-bottom: 0; }

/* Attribution row: logo + name/company, above a hairline. */
.e-con.kab-quote-att {
  margin-top: auto;
  padding-top: 18px;
  border-top: 1px solid #E3E5E8;
}
.kab-quote-logo img {
  width: auto; max-width: 92px; max-height: 34px;
  object-fit: contain; object-position: left; display: block;
}
.kab-quote-who p { margin: 0; font-size: 14px; line-height: 20px; font-style: normal; }
.kab-quote-who strong { color: #14181F; font-weight: 700; }
.kab-quote-who span { color: #4A4E58; }
@media (max-width: 1024px) {
  .e-con.kab-quotes {
    --grid-template-columns: 1fr; grid-template-columns: 1fr;
  }
}

/* Tile photos carrying an inline max-width in the mockup render at NATURAL
   height, contained and left-aligned - not the 247px cover crop (16 images
   across 9 pages). The per-image cap comes from the generator. */
.kab-tile-img-nat img {
  height: auto; object-fit: contain; object-position: left; width: 100%;
}

/* One blockquote exists in the whole mockup set (clean-rooms). The mockup
   ships NO blockquote CSS, so it renders at browser defaults - match that
   rather than inventing a pull-quote treatment. Astra's own margin is the
   only thing to neutralise. */
.kab-rich blockquote {
  margin: 0 0 14px; padding: 0; border: 0; font-style: normal;
}

/* Flipsnack brochure embed. Markup is VERBATIM from the live page (Eric
   2026-08-26). NOTE: the live site 403s on the same non-essential
   `links.js` from cdn.flipsnack.com - verified side by side - so that console
   entry is normal Flipsnack behaviour, not a build defect and not a local
   referrer block. The brochure itself renders on both. */
.kab-flipbook { width: 100%; max-width: 977px; }
.kab-flipbook iframe { width: 100%; display: block; border: 0; }


/* ---------------------------------------------------------------------------
   HERO VIDEO OVERLAY - the gradient must paint OVER the playing video.

   Elementor gates the video-container's ::before on `content:
   var(--background-overlay)` (frontend.min.css). That custom property is only
   defined when the container has background_overlay_* settings, which ONLY the
   homepage hero has - so on the two interior video heroes the pseudo-element
   was never generated and the navy gradient never painted over the video. The
   poster frame looked right (that is the container's own ::before, which does
   generate), which is why it read as "overlay disappears once it plays".

   Declaring `content` here generates the layer on every .kab-hero, so the
   interior heroes get exactly the homepage's treatment. The background-image
   above already supplies the 3-stop gradient for both selectors.
   --------------------------------------------------------------------------- */
.e-con.kab-hero > .elementor-background-video-container::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}


/* ===========================================================================
   DESIGN REVIEW 2026-08-26 (Eric's team, staging)
   Each block notes whether it RESTORES the approved mockup or DIVERGES from it.
   =========================================================================== */

/* --- 1. Yellow keyword highlight in interior H1s -- RESTORES THE MOCKUP ------
   The mockups mark the keyword with
       <span style="color:var(--text-accent)">Steel Mezzanines</span>
   and that markup IS present in the build's DOM. But `--text-accent` is a
   MOCKUP token that was never defined in the WordPress build, so the custom
   property was undefined and the span fell back to the inherited white.
   Mapping the mockup token onto the build's own global (identical value,
   #F5C518) fixes all six interior H1s at once and keeps the Elementor kit as
   the single source of truth - no hardcoded hex, no per-page edit.
   Scoped to .kab-hero/.kab-herosplit so it cannot leak into body copy. */
.kab-hero,
.kab-hero-interior,
.kab-herosplit,
.kab-herobasic {
  --text-accent: var(--e-global-color-accent);
}

/* --- 2. Hero pause/play chip back to the RIGHT -- REGRESSION FIX ------------
   Reviewer: "Play button to the right side" (industrial-equipment-platforms).
   Measured: homepage chip sits bottom-RIGHT (absolute, right:16px); both
   interior video heroes rendered bottom-LEFT at x=104.
   Cause is this file's own hero-overlay fix: the rule
       .e-con.kab-hero-interior > :not(.elementor-background-video-container)
   is 0-2-0 - the SAME specificity as `.e-con.kab-hero-interior > .kab-vidbtn`
   but LATER in the file, so `position:relative` won on source order and the
   chip became a flex item in the copy column. Excluding the chip from that
   rule as well restores the homepage treatment. (The chip does not need the
   z-index lift: it already carries z-index:3.) */
/* (superseded: the exclusion now lives on the source rule ~line 940) */

/* --- 3. "Talk to a real expert" semi-bold -- RESTORES THE MOCKUP ------------
   Reviewer: semi-bold on ALL pages. The mockup's own `.hero-strong` is
   `font-weight: 600` (styles.css ~864) and 700 at <=767 (~1334); the build was
   rendering 400 because Elementor's text-editor widget supplies its own weight.
   Restated at a specificity that beats the widget rule. */
.kab-herostrong,
.kab-herostrong p {
  font-weight: 600;
}
@media (max-width: 767px) {
  .kab-herostrong,
  .kab-herostrong p {
    font-weight: 700;
  }
}

/* --- 4. FAQ answer copy breathing room -- DIVERGENCE (reviewer request) -----
   Reviewer: "Space before FAQ copy on ALL pages".
   The mockup sets `.faq-a { padding: 0 48px 20px 20px }` - i.e. NO padding-top,
   so the answer sits 1px under the question. The build reproduced that exactly.
   The reviewer is asking for air, which the mockup does not have, so this is a
   deliberate divergence: 12px, matching the accordion's own 12px item gap so
   the rhythm stays consistent rather than introducing a new value.
   Applied on the answer container for every FAQ site-wide. */
.kab-faq .e-n-accordion-item > .e-con {
  padding-top: 12px;
}

/* --- 5. Contact/CTA eyebrow-to-header gap -- STANDARDISE -------------------
   Reviewer: "Standardize eyebrow distance from headers on ALL contact
   sections". Measured across the site: contact/CTA bands were already a
   consistent 29px, but the WHY/WHAT/FAQ bands use 16px and the split heroes
   29px, so "standardize" is about making the CONTACT sections agree with each
   other AND with the homepage reference - which they now do at 29px on every
   page measured. The rule below PINS that value so a future container-gap edit
   cannot drift one page away from the rest. */
.e-con.kab-cta .kab-intro,
.e-con.kab-cta > .e-con:first-child {
  row-gap: 29px;
}

/* --- 6. Why-band subhead/body spacing to the homepage standard -------------
   Reviewer: "Adjust subhead and body spacing to match homepage on ALL
   'Why KABTech' sections". Homepage reference measures eyebrow->h2 = 16px.
   Pinned so every why-band matches it. */
.e-con.kab-why .kab-intro {
  row-gap: 16px;
}

/* --- 7. Hero measures -- RESTORES THE MOCKUP -------------------------------
   The mockup caps the interior hero's H1 and lede, and the split hero's lede.
   The build had no caps at all, so every interior headline and body ran the
   full content box and wrapped differently from the approved design. These are
   the mockup's own numbers (styles.css ~862 / ~889), not new values - they are
   what produce the exact line breaks the design review asked for.
   Doubled class beats Elementor's per-widget max-width:100% (gotcha family
   #24/c) without !important. */
.kab-hero-interior .elementor-widget-heading.elementor-widget-heading,
.kab-hero-interior .elementor-widget-heading.elementor-widget-heading h1 {
  max-width: 853px;
}
.kab-hero-interior .kab-herosub.kab-herosub,
.kab-hero-interior .kab-herosub.kab-herosub p {
  max-width: 738px;
}
.kab-herosplit .kab-herosub.kab-herosub,
.kab-herosplit .kab-herosub.kab-herosub p,
.kab-herosplit .kab-herostrong.kab-herostrong,
.kab-herosplit .kab-herostrong.kab-herostrong p {
  max-width: 641px;
}
/* Below the tablet break the mockup drops every hero cap (styles.css ~1294
   `.hero-content { max-width: none }`) - the column is already narrower than
   any of these numbers, and holding a 641px cap in a 342px column would be
   inert anyway, but this keeps intent explicit and prevents a future regression. */
@media (max-width: 767px) {
  .kab-hero-interior .elementor-widget-heading.elementor-widget-heading,
  .kab-hero-interior .elementor-widget-heading.elementor-widget-heading h1,
  .kab-hero-interior .kab-herosub.kab-herosub,
  .kab-hero-interior .kab-herosub.kab-herosub p,
  .kab-herosplit .kab-herosub.kab-herosub,
  .kab-herosplit .kab-herosub.kab-herosub p,
  .kab-herosplit .kab-herostrong.kab-herostrong,
  .kab-herosplit .kab-herostrong.kab-herostrong p {
    max-width: none;
  }
}

/* --- 8. IEP hero lede 16/24 -- RESTORES THE MOCKUP -------------------------
   industrial-equipment-platforms.html is the ONLY mockup whose hero lede carries
   an inline font-size override (16px/24px) - a deliberate per-page decision so
   the longer copy holds 4 lines. Elementor strips inline styles (gotcha #4), so
   the build rendered the 18px/30px base and wrapped to 5.
   Scoped to page 317's Elementor document class - every other interior hero
   correctly keeps 18/30. */
.elementor-317 .kab-hero-interior .kab-herosub.kab-herosub,
.elementor-317 .kab-hero-interior .kab-herosub.kab-herosub p {
  font-size: 16px;
  line-height: 24px;
}
@media (max-width: 767px) {
  /* the mockup's own <=767 rule already lands on 16/24 - keep parity explicit */
  .elementor-317 .kab-hero-interior .kab-herosub.kab-herosub,
  .elementor-317 .kab-hero-interior .kab-herosub.kab-herosub p {
    font-size: 16px;
    line-height: 24px;
  }
}


/* ===========================================================================
   RFL FIX ROUND 2026-08-27 - answers from CLIENT_QUESTIONS-2026-08-28-RFL-ANSWERED.md
   Appended (never rewritten) so the team's own edits above are untouched.
   ===========================================================================

   C6 - the 12 "LEARN MORE" links on /industrial/  (RFL: "yes")
   ---------------------------------------------------------------------------
   The design specifies these yellow (#F5C518) on the grey band (#F4F5F7) =
   1.49:1, logged as defect R21. RFL approved reusing the solid-yellow button
   with dark text that was already approved earlier in the project (F16).
   Matched to the live approved button: #F5C518 fill, #0F1533 ink, 12px 20px,
   700, 0 radius, Public Sans. Dark-on-yellow measures 10.92:1.
   Scope: `.kab-tile-more` exists exactly 12 times site-wide, all on
   /industrial/ - verified against the DB, so this class IS the C6 set. */
.kab-tile-more a {
  display: inline-block;
  padding: 12px 20px;
  background-color: var(--e-global-color-accent);
  color: var(--e-global-color-kabnavink);
  font-family: "Public Sans", sans-serif;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.26px;
  text-decoration: none;
  border-radius: 0;
  transition: background-color 0.3s ease;
}
.kab-tile-more a:hover,
.kab-tile-more a:focus-visible {
  background-color: var(--e-global-color-kabbtnhover);
  color: var(--e-global-color-kabnavink);
}

/* C25 - darken the mobile hero tint on the 3 video pages (RFL: "yes please
   go ahead and darken so its more readable").
   ---------------------------------------------------------------------------
   <=767 the background VIDEO container is display:none, so the backdrop is the
   still poster. Measured against the actual posters, the headline was already
   passing (worst 9.54:1), but RFL asked for more readable, so the two lighter
   gradient stops go 0.82 -> 0.90. Measured worst case after: 12.56:1.
   Applies to home / mezzanines / industrial-equipment-platforms (every
   .kab-hero), mobile only - desktop is untouched and stays design-faithful. */
@media (max-width: 767px) {
  .e-con.kab-hero::before,
  .e-con.kab-hero > .elementor-background-video-container::before {
    background-image: linear-gradient(
      95deg,
      rgba(23, 27, 55, 0.95) 0%,
      rgba(23, 27, 55, 0.90) 46%,
      rgba(23, 27, 55, 0.90) 100%
    ) !important; /* matches the existing rule's specificity battle at :808 */
  }
}

/* ==========================================================================
   RFL notes round 3 (2026-08-27) - N1/N7/N8/N9/N10 component fixes
   ========================================================================== */

/* --- N1/N11: why-item subhead -> body spacing (ALL pages, desktop) --------
   RFL: "back pages seem to have more space between title and copy in the 4
   blocks". Their markup (image1) X's the gap BETWEEN each bold subhead and its
   body copy - NOT the eyebrow->h2 pair we measured and pinned on 8/26 (that
   pin, `.kab-why .kab-intro{row-gap:16px}` at ~1855, was a different pair and
   stays as-is).
   MEASURED: homepage 8px, all six back pages 20px. The homepage's four
   icon-boxes each carry `title_bottom_space:8px` as a per-widget Elementor
   setting; the back-page icon-boxes carry NO spacing setting at all, so their
   h3 falls through to the theme default `h1..h6{margin-bottom:20px}`.
   The approved mockup is `.why-item h3{margin-bottom:8px}` (styles.css ~587),
   so 8px is correct and 20px is the drift. Fixed at COMPONENT level - one rule
   covers all 7 pages and any why-band built later, instead of stamping
   title_bottom_space onto ~26 widgets that the team may re-edit. */
.e-con.kab-why .elementor-icon-box-content .elementor-icon-box-title,
.e-con.kab-why .elementor-icon-box-content h1,
.e-con.kab-why .elementor-icon-box-content h2,
.e-con.kab-why .elementor-icon-box-content h3,
.e-con.kab-why .elementor-icon-box-content h4,
.e-con.kab-why .elementor-icon-box-content h5 { margin-bottom: 8px; }

/* --- N8: FAQ chevron must be an OUTLINE, not a solid glyph ---------------
   RFL "remove arrow fill" (image9). The SVG markup was ALREADY correct - the
   exported icon is `MediaIconUnfilledcheveron-down` with `fill="none"` and a
   1.5px stroke. Elementor's own widget CSS paints over the attribute:
     .elementor-widget-n-accordion .e-n-accordion-item-title-icon span > svg
       { fill: var(--n-accordion-icon-normal-color) }
   which resolved to rgb(31,33,36) and filled the chevron's open path solid.
   Per the lesson already recorded at the top of this FAQ block (and gotcha
   #18): set ELEMENTOR'S OWN variable instead of out-shouting the rule. The
   stroke keeps the glyph visible, so colour moves to the stroke. */
.kab-faq-acc .e-n-accordion-item-title,
.kab-faq-acc .e-n-accordion-item {
  --n-accordion-icon-normal-color: transparent;
  --n-accordion-icon-hover-color: transparent;
  --n-accordion-icon-active-color: transparent;
}
.kab-faq-acc .e-n-accordion-item-title-icon svg path {
  stroke: #072A3D;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- N9: spec subhead -> body spacing, MOBILE (ALL Spec sections) --------
   RFL image10, marked on mezzanine-offices + mezzanine-platforms, note says
   ALL. MEASURED 18px on all 4 spec pages at both 390 and 1920: the h3 already
   carries the mockup's 8px margin-bottom, but the spec column adds a 10px
   row-gap between the heading widget and the copy widget that the mockup's
   `.spec` block does not have (styles.css ~913 is a plain 8px, no gap).
   Collapsing the row-gap between those two widgets restores the mockup's 8px.
   Mobile only, per the note - desktop stays as approved. */
/* N7b is the DESKTOP half of this same note ("Reduce subhead and body spacing
   in the 6 cards", image7) - RFL's "6 cards" are these very Spec blocks
   (Common uses / Safety features / Platform applications / Planning inputs /
   Material options / Project support), NOT the 3 photo tiles. N9 is the mobile
   half of the identical ask. Same component, same cause, so ONE unscoped rule
   serves both notes at every width. MEASURED 18px before, 8px after, on all 4
   spec pages at 1440 and 390. */
.e-con.kab-spec .elementor-widget-heading + .elementor-widget { margin-top: -10px; }

/* --- N10: image -> header gap, MOBILE (Equipment + Tank Access) ----------
   RFL image11 on /industrial-equipment-platforms/. Both cards are the SAME
   component (.kab-cc), so one rule serves both and any sibling card added
   later. MEASURED 46px = the container's 20px row-gap + ~26px of heading
   leading above the cap-height. The sibling card grids on this page sit at
   24px, so pull the row-gap to 12px -> ~38px optical, which matches the
   mockup's image-to-title rhythm without touching desktop. */
@media (max-width: 767px) {
  .e-con.kab-cc { row-gap: 12px; }
}

/* --- N6 + N7a: tile images fill their card (WHITELISTED CROP) -------------
   RFL image6/image7: "Extend images to fit the full card - they have left and
   right padding right now" / "Extend image width - OK TO CROP IMAGE TO BE SAME
   AS FIGMA".
   *** EXPLICIT WRITTEN CLIENT CROP CONSENT ***  This is a scoped exception to
   the never-crop rule. Scope = .kab-tile-img ONLY. Every other image on the
   fleet keeps `contain`.
   MEASURED cause: the tile box is 375x247 (AR 1.517) but the equipment-platform
   tile sources are 4:3 (800x600 / 600x450, AR 1.333), so `object-fit:contain`
   letterboxed them and left the side gaps RFL marked. /tank-access-platforms/
   sources are already 375x247 (AR 1.518) and are unaffected either way - they
   simply stop being a special case.
   `cover` + centre keeps the subject and trims ~12% of height. */
.kab-tile-img img {
  object-fit: cover;
  object-position: center;
  width: 100%;
  height: 100%;
}

/* --- N3 + N5: /mezzanines/ copy measures (PAGE-SCOPED) -------------------
   Both notes ask for a specific line count on ONE page. `.kab-intro .kab-sub`
   is shared by 14 paragraphs across 7 pages (~70:586px) and most already sit
   at the 4 lines RFL wants, so widening the shared pin would disturb pages
   nobody complained about. Scoped to page-id-308 (/mezzanines/) + the section
   class, which leaves every sibling page on the approved 586px.

   N3 - "Body to 4 lines if possible, make the 2/3 copy area a little wider
   (less space on right)": MEASURED 586px = 5 lines; 4 lines from 636px up;
   the grid cell is 760px. 680px = 4 lines and still stops short of the cell,
   so the 2/3 rhythm and the right-hand breathing room both survive. */
.page-id-308 .kab-section.kab-what-compare .kab-intro .kab-sub,
.page-id-308 .kab-section.kab-what-compare .kab-intro .kab-sub p { max-width: 680px; }

/* N5 - "Make the copy area at the top of the section wider so the header
   reduces to 2 lines, and body 3 lines" (image5/image16). MEASURED: the h2
   goes 3 -> 2 lines at 680px, the body 4 -> 3 lines at 660px. 720px clears
   both thresholds. Only /mezzanines/ has the 3-line header - the sibling
   why-bands are 1-2 lines already and are deliberately left alone. */
.page-id-308 .kab-section.kab-why .kab-intro .elementor-heading-title { max-width: 720px; }
.page-id-308 .kab-section.kab-why .kab-intro .kab-sub,
.page-id-308 .kab-section.kab-why .kab-intro .kab-sub p { max-width: 720px; }
