/* CADOpt reusable components — buttons, badges, tags, cards, stat, form controls */

/* ---- Buttons ----
   One shared system for every CTA on the site — this is the single
   source of truth referenced by the "Request a Discussion" reference
   button; every page-level override that used to hand-tune height/
   padding/font-size (template-parts/cta-band.php did, most visibly) has
   been migrated to a variant class instead, so there is exactly one
   geometry, one timing curve and one hover behaviour everywhere .btn is
   used. width:fit-content keeps each button its own natural content
   width — buttons are NOT meant to share a uniform width, only a uniform
   height/radius/padding/type/shadow/hover. */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--space-2);
  width:fit-content; min-height:48px; padding:14px 28px;
  font-family:var(--font-brand); font-size:15px; font-weight:600; line-height:1;
  border-radius:var(--radius-pill);
  border:1px solid transparent;
  cursor:pointer;
  transition:background-color 180ms var(--ease-out),
             transform 180ms var(--ease-out),
             box-shadow 180ms var(--ease-out),
             border-color 180ms var(--ease-out);
  text-decoration:none;
}
.btn:hover{ transform:translateY(-2px); text-decoration:none; }
.btn:active{ transform:scale(.98); }

/* Primary — the reference button. filter:brightness darkens the EXISTING
   --grad-blade gradient uniformly on hover (a real "slightly darker
   brand blue", not a second gradient to keep in sync by hand). */
.btn--primary{ background:var(--grad-blade); color:#fff; box-shadow:var(--shadow-brand); }
.btn--primary:hover{ filter:brightness(.93); box-shadow:var(--shadow-lg); }

.btn--solid{ background:var(--blue-900); color:#fff; }
.btn--solid:hover{ background:var(--blue-800); }

/* Secondary — same pill geometry, white/light fill, brand-blue text and
   a visible (not merely neutral-grey) thin blue border. */
.btn--secondary{ background:var(--white); color:var(--brand); border-color:var(--blue-200); }
.btn--secondary:hover{ border-color:var(--brand); background:var(--blue-50); }

/* On-brand — white pill for sitting on a --grad-blade/--grad-ink filled
   surface (the closing cta-band, and dark feature cards) where
   .btn--primary's own blue background would disappear into it. */
.btn--on-brand{ background:var(--white); color:var(--brand); box-shadow:var(--shadow-lg); }
.btn--on-brand:hover{ background:var(--blue-50); }

.btn--ghost{ background:transparent; color:var(--text-body); }
.btn--ghost:hover{ background:var(--blue-50); color:var(--brand); }

.btn--link{ width:auto; min-height:0; padding:0; background:none; color:var(--blue-600); font-weight:600; }
.btn--link:hover{ text-decoration:underline; }
@media (max-width:600px){
  /* Full-bleed CTA cards (cta-band, the dark bento feature card) already
     stack their button under generous padding — letting it go full
     width there reads as intentional, not accidental uniform sizing. */
  .cta-band .btn, .partner-bento__card--4 .btn{ width:100%; }
}

/* ---- Button group: pill CTA + circular icon accent ----
   The pill carries the label; the circle is a decorative second affordance
   pointing at the same destination — both link the same place, so a
   keyboard/screen-reader user never loses functionality by the circle
   existing. Reserve for a single hero-level primary action, not every CTA. */
.btn-group{ display:inline-flex; align-items:center; gap:var(--space-3); }
.btn-icon-circle{
  display:inline-flex; align-items:center; justify-content:center; flex:none;
  width:44px; height:44px; border-radius:50%;
  background:var(--blue-500); color:#fff;
  transition:transform var(--duration-base) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
.btn-icon-circle svg{ width:18px; height:18px; }
.btn-icon-circle:hover{ background:var(--blue-600); transform:translateY(-2px) rotate(45deg); text-decoration:none; }


/* ---- Badges ---- */
.badge{
  display:inline-flex; align-items:center; height:26px; padding-inline:var(--space-3);
  border-radius:var(--radius-pill);
  font-family:var(--font-brand); font-size:12px; font-weight:700; letter-spacing:.02em;
  text-transform:uppercase;
}
.badge--brand{ background:var(--surface-brand-soft); color:var(--brand); }
.badge--success{ background:var(--success-100); color:var(--success); }
.badge--warning{ background:var(--warning-100); color:var(--warning); }
.badge--danger{ background:var(--danger-100); color:var(--danger); }
.badge--neutral{ background:var(--graphite-900); color:#fff; }

/* ---- Tags ---- */
.tag{
  display:inline-flex; align-items:center; gap:var(--space-2); height:28px; padding-inline:var(--space-3);
  border:1px solid var(--border-strong); border-radius:var(--radius-sm);
  font-family:var(--font-brand); font-size:13px; font-weight:600; color:var(--text-body);
  background:var(--surface-page);
}
.tag--active{ border-color:var(--blue-400); color:var(--brand); }
.tag__dot{ width:6px; height:6px; border-radius:50%; background:var(--brand); flex:none; }

/* ---- "Trusted by engineering teams" client row (front-page.php) ----
   One shared container for both real logos and the remaining text-only
   names, so the row reads as one consistent set rather than two
   different-looking treatments sitting side by side. */
.trusted-clients{ display:flex; flex-wrap:wrap; justify-content:center; gap:14px; }
.client-logo{
  display:flex; align-items:center; justify-content:center;
  min-width:120px; height:58px; padding:10px 18px; box-sizing:border-box;
  background:var(--white); border:1px solid var(--border-subtle); border-radius:var(--radius-md);
}
/* Consistent logo HEIGHT, not width — logo aspect ratios differ (Bosch's
   wordmark+symbol is much wider than KOEL's), so forcing equal width
   would squash or letterbox some of them. */
.client-logo img{ max-height:34px; max-width:130px; width:auto; height:auto; object-fit:contain; }
.client-logo--text span{ font-family:var(--font-brand); font-size:14px; font-weight:600; color:var(--text-body); }

/* ---- Client logo strip (single horizontal row + thin separators) ----
   Distinct from .client-logo above: that component is a bordered-card
   grid (Clientele Overview sections); this one is a single unboxed row
   with a hairline divider between marks instead of a card around each —
   used where the brief calls for "a clean corporate logo strip", not a
   grid of tiles. Consistent logo-container HEIGHT (not width — logos
   have very different native proportions, e.g. Boeing's wide wordmark vs
   BEML's squarer mark) keeps the row visually balanced without
   stretching or cropping any individual mark. Equal flex-basis on
   desktop distributes items evenly across the full row; the divider is
   an absolutely-positioned pseudo-element sized to ~78% of the item's
   height (not edge-to-edge) so it reads as a subtle rule, not a column
   border. Dividers are dropped once the row wraps (tablet/mobile) since
   a left-edge line stops meaning "between these two" the moment items no
   longer share one line. */
.client-strip{
  display:flex; align-items:center; justify-content:center; flex-wrap:wrap;
  background:var(--white);
}
.client-strip__item{
  position:relative; flex:1 1 0; box-sizing:border-box;
  display:flex; align-items:center; justify-content:center;
  height:100px; min-width:110px; padding-inline:clamp(14px, 2.6vw, 28px);
}
.client-strip__item:not(:first-child)::before{
  content:""; position:absolute; left:0; top:50%; transform:translateY(-50%);
  width:1px; height:76%; background:var(--border-subtle);
}
.client-strip__item img{ max-height:64px; max-width:100%; width:auto; height:auto; object-fit:contain; }

@media (max-width:900px){
  .client-strip__item{ flex:0 0 25%; height:92px; }
  .client-strip__item::before{ display:none; }
}
@media (max-width:600px){
  .client-strip__item{ flex:0 0 50%; height:84px; }
  .client-strip__item img{ max-height:56px; }
}

/* ---- Cards ---- */
.card{
  border-radius:var(--radius-md);
  padding:var(--space-6);
  background:var(--surface-page);
  transition:transform var(--duration-base) var(--ease-out), box-shadow var(--duration-base) var(--ease-out);
}
.card--elevated{ box-shadow:var(--shadow-md); border:1px solid var(--border-subtle); }
.card--elevated:hover{ transform:translateY(-3px); box-shadow:var(--shadow-lg); }
.card--outline{ border:1px solid var(--border-strong); }
.card--soft{ background:var(--surface-brand-soft); }
.card--blade{ border-top:3px solid; border-image:var(--grad-blade) 1; }

.service-card{ display:flex; flex-direction:column; gap:var(--space-3); }
/* Equal card height across BOTH rows of the Solutions tab grid — CSS Grid
   only equalises height within one row by default, not across rows, so a
   fixed floor (sized for the longest copy in either tab: a 2-line
   description) is what actually keeps all 6 cards the same height,
   regardless of which row a shorter description lands in. */
.solutions-cards .service-card{ min-height:210px; }
.solutions-cards .card--dark{ min-height:210px; }
.service-card__icon{
  width:48px; height:48px; border-radius:var(--radius-md);
  display:flex; align-items:center; justify-content:center;
  background:var(--surface-brand-soft); color:var(--brand);
  flex:none;
}
.service-card__icon svg{ width:24px; height:24px; stroke-width:2; }
.service-card h3{ margin-bottom:var(--space-2); }
.service-card p{ color:var(--text-muted); margin:0; }

/* ---- Dark filled card ----
   Solid-ink surface, white icon badge, white heading, muted-white body.
   Reuses the site's existing --grad-ink dark surface (same treatment as
   the CTA/portfolio dark cards) rather than a new one-off navy. */
.card--dark{
  background:var(--grad-ink); border-radius:var(--radius-lg);
  padding:var(--space-6); display:flex; flex-direction:column; gap:var(--space-3);
  transition:transform var(--duration-base) var(--ease-out), box-shadow var(--duration-base) var(--ease-out);
}
.card--dark:hover{ transform:translateY(-4px); box-shadow:var(--shadow-lg); }
.card--dark .service-card__icon{ background:var(--white); color:var(--brand); }
.card--dark h3{ color:var(--white); margin-bottom:var(--space-2); }
.card--dark p{ color:rgba(255,255,255,.72); margin:0; }

/* ---- Stat ---- */
.stat__value{
  font-family:var(--font-brand); font-weight:700; font-size:48px; line-height:1;
}
.stat__value.gradient{
  background:var(--grad-blade); -webkit-background-clip:text; background-clip:text; color:transparent;
}
.stat__label{
  display:block; margin-top:var(--space-2);
  font-family:var(--font-brand); font-size:12px; font-weight:700;
  letter-spacing:.06em; text-transform:uppercase; color:var(--text-muted);
}

/* ---- Form controls ---- */
.field{ display:flex; flex-direction:column; gap:var(--space-2); }
.field label{
  font-family:var(--font-brand); font-size:13px; font-weight:600; color:var(--text-strong);
}
.field .hint{ font-size:12px; color:var(--text-muted); }
.field input[type=text], .field input[type=email], .field input[type=tel],
.field select, .field textarea{
  height:44px; padding-inline:var(--space-4);
  border:1px solid var(--border-strong); border-radius:var(--radius-sm);
  font-family:var(--font-body); font-size:14px; color:var(--text-strong);
  background:var(--surface-page);
  transition:border-color var(--duration-base) var(--ease-out), box-shadow var(--duration-base) var(--ease-out);
}
.field textarea{ height:auto; min-height:120px; padding-block:var(--space-3); resize:vertical; }
.field input:focus, .field select:focus, .field textarea:focus{
  outline:none; border-color:var(--blue-400); box-shadow:var(--focus-ring);
}
.field-check{ display:flex; align-items:center; gap:var(--space-2); font-size:14px; color:var(--text-body); }
.field-check input{ width:18px; height:18px; accent-color:var(--brand); }

.switch{ position:relative; display:inline-block; width:40px; height:22px; flex:none; }
.switch input{ opacity:0; width:0; height:0; }
.switch .slider{
  position:absolute; inset:0; background:var(--graphite-300); border-radius:var(--radius-pill);
  transition:background-color var(--duration-base) var(--ease-out); cursor:pointer;
}
.switch .slider::before{
  content:""; position:absolute; width:16px; height:16px; left:3px; top:3px;
  background:#fff; border-radius:50%;
  transition:transform var(--duration-base) var(--ease-out);
}
.switch input:checked + .slider{ background:var(--brand); }
.switch input:checked + .slider::before{ transform:translateX(18px); }

/* ---- Hero carousel (homepage banner) ----
   Full-bleed, edge-to-edge — deliberately NOT wrapped in .container at
   the section level, so the background fills the viewport like a true
   banner rather than the site's other (contained, rounded-corner) dark
   surfaces. Ported behaviour from
   _skills/13_web_experience_craft/patterns/thumb360-kit.js's "hero
   slider" module (assets/js/main.js: initHeroCarousel()) — same
   data-t3-hero/-slide/-dot/-prev/-next/-pause hooks the kit defines;
   everything below is CADOpt's own visual design on top of that.
   Each slide's background reuses the site's existing .ph placeholder
   pattern (see front-page.php's file-header note — real photography
   per slide is a pending asset, same as the rest of the homepage) with
   a dark scrim layered over it so heading/description text stays
   readable once real imagery lands; nothing here is invented stock
   photography.
   Accent colour: var(--blue-300) — the same light-blue token already
   used for accents on every other dark (--grad-ink) surface on this page
   (Client Stories' quote mark, the header mega-panels' dark "view all"
   links). A warm amber (#f0b429) was used here previously; removed
   entirely per client direction — CADOpt's system carries no yellow. */
.hero-carousel{
  position:relative; overflow:hidden; background:var(--grad-ink);
}
/* Exactly fills the viewport below the header (100vh minus --header-h)
   rather than a vh-percentage-with-floor — the whole banner, including
   the bottom tab bar, needs to fit in the first screen with no
   scrolling required to reach the tab bar, on any viewport height, not
   just tall ones. Using height (not min-height) makes this exact
   rather than a floor a taller viewport could exceed. */
.hero-carousel__track{ position:relative; height:calc(100vh - var(--header-h)); }
.hero-carousel__slide{
  position:absolute; inset:0;
  opacity:0; visibility:hidden;
  transition:opacity var(--duration-slow) var(--ease-out), visibility 0s linear var(--duration-slow);
}
.hero-carousel__slide[data-active="true"]{ opacity:1; visibility:visible; transition-delay:0s; }
/* Real photography — object-fit:cover fills the slide without
   distortion; object-position is nudged per-breakpoint below so the
   subject (kept right-of-frame in every source image, matching the
   bottom-left text block) stays in view rather than a naive centre
   crop cutting it off on narrower viewports. */
.hero-carousel__media{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; object-position:65% center;
}
/* Slide 1's video (see front-page.php) frames its subject centred, not
   right-weighted like the photo slides above — this keeps it centred at
   every breakpoint instead of inheriting the photos' 65%/58%/50% tuning,
   which would crop it off-centre for no reason specific to this video. */
video.hero-carousel__media{ object-position:center center; }
.hero-carousel__scrim{
  position:absolute; inset:0; z-index:1;
  background:linear-gradient(100deg, rgba(8,22,40,.94) 0%, rgba(8,22,40,.76) 40%, rgba(8,22,40,.34) 74%, rgba(8,22,40,.08) 100%);
}
/* Vertically centred via position:absolute + top:50%/translateY(-50%)
   — not flex align-items, for the same reason as before: an explicit
   mechanism that doesn't depend on how a flex container happens to
   distribute space around an auto-height child. Horizontal position
   (left) is unchanged from the earlier bottom-anchored layout — only
   the vertical alignment moved. max-width uses min() against 100vw so
   the block can never overflow past the right edge on a viewport
   narrower than left+820px. */
.hero-carousel__content{
  position:absolute; z-index:2;
  left:40px; right:auto; top:50%; bottom:auto; transform:translateY(-50%);
  width:auto; max-width:min(820px, calc(100vw - 96px));
  color:#fff;
}
@media (min-width:1440px){
  .hero-carousel__content{ left:48px; }
}
.hero-carousel__eyebrow{ color:var(--blue-300); }
.hero-carousel__heading{
  margin:16px 0 0;
  font-size:clamp(26px,4.2vw,46px); line-height:1.14; font-weight:700;
}
/* The heading itself is the slide's click-through (matching the
   reference, which carries no separate button) rather than a
   .btn row underneath it — that also keeps the block shorter, so
   align-items:flex-end on the slide lands it closer to the tab bar. */
.hero-carousel__heading-link{ color:#fff; text-decoration:none; }
/* text-decoration-thickness is explicit — the browser's own default auto
   thickness scales with font-size and renders visibly chunky at this
   heading's clamp(26px,4.2vw,46px) size, out of step with the thin (1px)
   line language used everywhere else in this hero (tab active indicator,
   tab hover underline). */
.hero-carousel__heading-link:hover{
  text-decoration:underline; text-decoration-color:rgba(255,255,255,.6);
  text-decoration-thickness:1px; text-underline-offset:6px;
}
.hero-carousel__desc{
  color:rgba(255,255,255,.85); margin-top:18px; max-width:56ch;
  font-size:16px; line-height:1.6;
  border-left:2px solid rgba(255,255,255,.4); padding-left:16px;
}

/* Bottom bar — absolutely positioned over the slide, so it reads as
   part of the banner rather than a separate strip below it.
   padding-inline is set to the SAME left offset as .hero-carousel__content
   below (40px / 48px at >=1440px, matching its own breakpoints), not
   .container's centred max-width/gutter math — the hero is an edge-aligned
   full-bleed banner, not a contained section, and .container's centred
   padding drifts well past 40px on wide viewports, which is what made the
   tab row's left edge sit well right of the heading/eyebrow above it. */
.hero-carousel__bar{
  position:absolute; left:0; right:0; bottom:0; z-index:3;
  display:flex; align-items:center; justify-content:space-between; gap:var(--space-6);
  padding-block:var(--space-5); padding-inline:40px;
}
@media (min-width:1440px){ .hero-carousel__bar{ padding-inline:48px; } }
/* Equal-column grid, not flex — all four items now start at the same
   row/top position regardless of how many lines any one label wraps to
   (flex alone let the first, longer label sit visually disconnected from
   the other three). justify-items:start keeps each button its own
   content width within its column, rather than stretched full-width —
   the active-indicator below is width:100% of the BUTTON, so it reads as
   matching that item's own text, not the whole column. overflow-x:auto
   stays as a safety net at very narrow custom widths (matches the
   .home-cta__badges precedent elsewhere on this page), though
   minmax(0,1fr) already prevents real horizontal overflow by letting
   columns shrink/wrap first. overflow-y:hidden is REQUIRED alongside it —
   per spec, setting only overflow-x to a non-visible value silently
   computes overflow-y to auto too, which is what was producing the
   native vertical scroll arrows any time a wrapped label made this
   container even 1px taller than its row. The ::-webkit-scrollbar rule
   plus scrollbar-width/-ms-overflow-style hide the (now purely
   horizontal, mobile-only) scrollbar itself — scrolling still works via
   touch/trackpad, there's just no visible track/arrows. */
.hero-carousel__tabs{
  display:grid; grid-template-columns:repeat(4, minmax(0, 1fr));
  justify-items:start; overflow-x:auto; overflow-y:hidden;
  scrollbar-width:none; -ms-overflow-style:none;
  column-gap:clamp(20px,3vw,40px); min-width:0;
}
.hero-carousel__tabs::-webkit-scrollbar{ display:none; }
.hero-carousel__tab{
  position:relative; background:none; border:0; cursor:pointer;
  padding:0; text-align:left; max-width:210px;
  font-family:var(--font-brand); font-size:14px; font-weight:600;
  color:rgba(255,255,255,.6); line-height:1.3;
  transition:color var(--duration-fast) var(--ease-out);
}
.hero-carousel__tab:hover{ color:#fff; }
.hero-carousel__tab[aria-current="true"]{ color:#fff; font-weight:700; }
/* Indicator line — driven ONLY by [aria-current="true"] (the real active
   slide, toggled by main.js's render() on click and on auto-advance),
   deliberately NOT by :hover. An earlier version also lit the bar on
   hover, which meant hovering a tab showed its line even though the
   slide behind it hadn't changed yet (a real mismatch between what the
   indicator claimed and what was actually on screen) — hover now only
   brightens the tab's text colour as a lighter preview cue, the bar
   itself always tracks the true displayed slide. Fixed 80px width +
   left:50%/translateX(-50%) centers it against the button's OWN width
   regardless of label length (width:100% previously stretched it to
   match each label's own text width — very wide for "Engineering
   services & solutions", a sliver for the short ones). */
.hero-carousel__tab::before{
  content:""; position:absolute; left:50%; top:-14px;
  width:80px; height:3px; border-radius:2px; background:transparent;
  transform:translateX(-50%);
  transition:background-color 240ms var(--ease-out);
}
.hero-carousel__tab[aria-current="true"]::before{ background:var(--blue-300); }
.hero-carousel__controls{ display:flex; align-items:center; gap:var(--space-2); flex:none; }
.hero-carousel__arrow, .hero-carousel__pause{
  width:42px; height:42px; flex:none; display:grid; place-items:center;
  background:rgba(255,255,255,.12); border:1px solid rgba(255,255,255,.28);
  color:#fff; border-radius:50%; cursor:pointer;
  transition:background-color var(--duration-fast) var(--ease-out);
}
.hero-carousel__arrow:hover, .hero-carousel__pause:hover{ background:rgba(255,255,255,.24); }
.hero-carousel__arrow svg, .hero-carousel__pause svg{ width:16px; height:16px; display:block; }
/* Pause/play icon swap — both SVGs render server-side (cadopt_icon()),
   JS only toggles .is-paused; no client-side icon-string juggling. */
.hero-carousel__pause-icon--play{ display:none; }
.hero-carousel__pause.is-paused .hero-carousel__pause-icon--pause{ display:none; }
.hero-carousel__pause.is-paused .hero-carousel__pause-icon--play{ display:block; }

@media (max-width:900px){
  .hero-carousel__track{ height:calc(100vh - var(--header-h)); }
  .hero-carousel__content{ left:24px; max-width:min(820px, calc(100vw - 48px)); }
  .hero-carousel__heading{ font-size:clamp(24px,6vw,32px); }
  .hero-carousel__bar{ flex-direction:column; align-items:flex-start; gap:var(--space-4); padding-block:var(--space-4); padding-inline:24px; }
  .hero-carousel__controls{ align-self:flex-end; }
  .hero-carousel__tabs{ grid-template-columns:repeat(2, minmax(0, 1fr)); row-gap:var(--space-5); width:100%; }
  /* The scrim already covers most of the frame at this width (the text
     block takes up more of it proportionally), so the crop can centre
     up rather than favour the right — keeps the subject from being
     pushed almost entirely off-frame on a narrower viewport. */
  .hero-carousel__media{ object-position:58% center; }
}
@media (max-width:600px){
  .hero-carousel__content{ max-width:none; left:20px; right:20px; }
  .hero-carousel__desc{ font-size:15px; }
  .hero-carousel__bar{ padding-inline:20px; }
  /* Reverts to the pre-grid flex + horizontal-scroll pattern at this
     width — a clean scrollable strip reads better on a phone than a
     cramped 2-column grid, and .hero-carousel__tab's own ::before
     indicator is positioned relative to the tab itself, so it keeps
     working unchanged under either display mode. overflow-y:hidden and
     the scrollbar-hiding rules are inherited from the base rule above —
     this only needs to touch display/gap. */
  .hero-carousel__tabs{ display:flex; flex-wrap:nowrap; overflow-x:auto; gap:20px; }
  .hero-carousel__tab{ max-width:150px; font-size:13px; }
  .hero-carousel__tab::before{ width:56px; }
  /* Content now spans the full width edge-to-edge (left+right both
     set above), so the angled left-heavy scrim would leave its right
     side under text with almost no darkening. A flatter, more uniform
     scrim keeps contrast even across the whole line at this width. */
  .hero-carousel__scrim{
    background:linear-gradient(180deg, rgba(8,22,40,.55) 0%, rgba(8,22,40,.55) 45%, rgba(8,22,40,.88) 100%);
  }
  .hero-carousel__media{ object-position:50% center; }
}

/* ---- Homepage closing CTA — certification badges ----
   Two columns on desktop: pitch on the left, real certification badge
   images (client-supplied) on the right, each labelled underneath. */
/* position:relative + z-index puts this card's own paint layer above
   the footer that follows it in the DOM (which would otherwise paint on
   top by default, being later); margin-bottom pulls that footer up
   underneath the card's own bottom edge, so the card visibly floats in
   front of — not tucked behind — the footer.
   A literal "50% of my own height" isn't expressible as a margin
   percentage (percentage margins resolve against the containing block's
   WIDTH, not the element's own height) or reliably as a fixed px guess
   against auto/content-driven height. --home-cta-h fixes that: it's a
   single explicit height (as min-height, so real content is never
   clipped if it ever runs slightly taller), sized generously above this
   card's actual content (padding clamp(32-56px)×2 ≈ up to 112px + the
   taller of the copy column or the one-line badge row, ≈ 130-150px) so
   min-height is normally what's actually rendered. margin-bottom then
   divides that SAME variable by exactly -2 — so the overlap is
   mathematically half of the declared height by construction, not a
   separately-tuned number that can drift out of sync with it. */
.home-cta{
  --home-cta-h:clamp(220px,24vw,264px);
  position:relative; z-index:1;
  display:grid; grid-template-columns:1fr 1fr; gap:clamp(28px,4vw,56px); align-items:center;
  background:var(--grad-blade); border-radius:var(--radius-xl);
  box-shadow:var(--shadow-brand);
  padding:clamp(32px,5vw,56px);
  min-height:var(--home-cta-h);
  margin-top:clamp(48px,6vw,80px);
  margin-bottom:calc(var(--home-cta-h) / -2);
}
.home-cta__content h2{ color:#fff; margin:0; font-size:clamp(1.4rem,2.6vw,1.9rem); }
.home-cta__content p{ color:rgba(255,255,255,.85); margin:12px 0 0; font-size:15px; max-width:42ch; }
/* All 5 badges on one line. flex-wrap:nowrap plus a size that scales
   down with viewport width (clamp, not fixed px) keeps them fitting the
   right-hand half of the card at any desktop size; overflow-x is a
   safety net only, for widths too narrow to fit even the shrunk size. */
.home-cta__badges{
  display:flex; flex-wrap:nowrap; gap:clamp(6px,1vw,16px);
  justify-content:flex-end; overflow-x:auto;
}
.home-cta__badge{
  display:flex; flex-direction:column; align-items:center; gap:6px;
  background:#fff; border-radius:var(--radius-lg);
  padding:clamp(8px,1vw,14px); width:clamp(58px,7vw,88px); flex:none;
}
.home-cta__badge img{ width:clamp(32px,4vw,48px); height:clamp(32px,4vw,48px); object-fit:contain; }
.home-cta__badge span{
  font-family:var(--font-brand); font-weight:600; font-size:clamp(8.5px,0.85vw,11px);
  color:var(--text-strong); text-align:center; line-height:1.25;
}
@media (max-width:700px){
  /* The card stacks (content, then badges) at this width, making it
     considerably taller — --home-cta-h is re-declared for that taller
     stacked shape (not the desktop two-column one), min-height and
     margin-bottom both recompute from it automatically since they're
     already written as var(--home-cta-h)/calc() in the base rule above,
     so this stays exactly half at every mobile width too, not a
     separately-chosen smaller fraction. */
  .home-cta{
    --home-cta-h:clamp(260px,60vw,320px);
    grid-template-columns:1fr; text-align:left;
  }
  .home-cta__badges{ justify-content:flex-start; }
}

/* ---- Placeholder block ----
   Dimensioned stand-in for real photography/screenshots not yet sourced
   (see content/00_notes_and_gaps.md). Reserves the exact space the real
   asset will take so nothing shifts layout when it lands — never a broken
   <img>, never invented stock photography. Remove this class the moment a
   real asset is dropped in. */
.ph{
  position:relative; overflow:hidden;
  width:100%; background:var(--graphite-100);
  border:1px dashed var(--border-strong);
  display:flex; align-items:center; justify-content:center;
}
.ph::before{
  content:""; position:absolute; inset:0;
  background:repeating-linear-gradient(135deg, var(--graphite-150) 0 10px, transparent 10px 20px);
}
.ph::after{
  content:attr(data-ph);
  position:relative; z-index:1; max-width:80%;
  font-family:var(--font-mono); font-size:11px; text-align:center; line-height:1.5;
  color:var(--text-subtle); background:var(--white); padding:6px 10px; border-radius:var(--radius-xs);
}

/* ---- Products showcase (dark, two-column) ----
   Left: heading + description + CTA (~35% width). Right: a two-column
   vendor list with the first entry styled as the active/lead partner.
   Uses the site's existing --grad-ink dark surface (same as the CTA and
   "explore the full portfolio" cards) rather than a flat #000 — a pure
   black surface on a branded site is a craft-standard violation. Accent
   is CADOpt's own blue-400, not an off-brand teal/green. */
.products-showcase{
  background:var(--grad-ink); border-radius:var(--radius-xl);
  padding:clamp(40px,6vw,72px);
  display:grid; grid-template-columns:1fr 1.85fr; gap:56px; align-items:start;
}
.products-showcase__eyebrow{ color:var(--blue-300); }
.products-showcase h2{ color:var(--white); margin-top:14px; font-size:clamp(1.8rem,3vw,2.5rem); }
.products-showcase p{ color:rgba(255,255,255,.72); margin-top:16px; max-width:38ch; }
.products-showcase .btn{ margin-top:28px; }
/* Products mega menu only: no intro heading/description/CTA here (see
   header.php) — a single child (.products-partners) fills the panel, so
   this drops the shared 1fr/1.85fr grid split entirely rather than
   leaving an unused second column. padding-inline overrides just the
   left/right component of the shared clamp(40px,6vw,72px) padding above
   (leaving top/bottom as-is) to an exact 70px, so the gap from the dark
   panel's edge to the first and last column is a fixed, equal value
   rather than one that grows with viewport width. */
.products-showcase--full{ display:block; padding-inline:70px; }

/* Logo-only showcase — every partner mark at the same fixed size, laid
   out as a 4-column grid so 8 logos read as two clean horizontal rows.
   justify-items/align-items centre each fixed-size wrap within its own
   flexible cell, which is what gives the row its even, symmetrical
   spacing regardless of how wide any one logo's own artwork is. */
.products-showcase__logos{
  display:grid; grid-template-columns:repeat(4, 1fr); gap:40px;
  justify-items:center; align-items:center;
}
.products-showcase__logo-wrap{
  display:flex; align-items:center; justify-content:center;
  width:120px; height:36px;
}
/* object-fit:contain (via max-width/max-height + auto sizing) scales
   each mark down to fit its box without stretching, and centres it
   there — unlike the header dropdown's left-aligned list, this is a
   symmetrical showcase row, so every logo centres within its own cell
   rather than sharing one left edge. */
.products-showcase__logo{
  max-width:100%; max-height:100%; width:auto; height:auto;
  object-fit:contain; display:block;
}
/* XPLM's source file has almost no internal padding around its mark, so
   at full size within the shared box it reads visibly larger than the
   rest — see cadopt_products_menu_data()'s `logo_scale` doc. */
.products-showcase__logo--sm{ max-width:82%; max-height:82%; }

@media (max-width:600px){
  .products-showcase__logos{ grid-template-columns:repeat(2, 1fr); gap:28px; }
  .products-showcase__logo-wrap{ width:104px; height:32px; }
}

@media (max-width:900px){
  .products-showcase{ grid-template-columns:1fr; gap:32px; }
  .products-showcase p{ max-width:none; }
}

/* Nested inside the Products mega menu: the panel itself (.mega-panel--dark)
   already supplies the dark, full-bleed background AND the top/bottom
   padding (.mega-panel--full's padding:var(--space-8) 0), so strip this
   component's own card chrome rather than double it up — but only the
   block (vertical) side. Horizontal padding is deliberately left alone
   here: .products-showcase--full owns padding-inline on its own (a
   dedicated, symmetric 70px). This selector has higher specificity
   (.mega-panel--dark .products-showcase, two classes vs one) than
   .products-showcase--full, so its old `padding:0` shorthand silently
   zeroed that 70px on both sides regardless of source order — collapsing
   the left inset to near-nothing while .products-partners' equal-width
   grid tracks left uneven leftover space trailing the shorter columns on
   the right, reading as a lopsided outer gap. padding-block:0 fixes it
   at the root: it zeroes only what this rule was ever meant to zero
   (top/bottom), leaving padding-inline entirely to
   .products-showcase--full so Products/Services/Industries all get one
   consistent, genuinely symmetric horizontal inset. */
/* Header context only (the on-page /solutions/ "Technology Partners"
   section keeps its own 1fr/1.85fr/56px layout untouched below). Columns
   go content-sized (auto) instead of stretchy fr units, then
   justify-content:center centers the whole intro+companies+products
   group as one block inside the panel — this is what actually fixes the
   "big empty gap on the right, content left-heavy" look: previously the
   1fr companies+products column stretched to fill all remaining panel
   width even though its content never got wider than ~340px. gap is
   unified to --space-8 so intro↔companies and companies↔products read as
   evenly spaced, not 56px vs 24px. align-items stays the inherited
   `start`, so the companies/products column's top still lines up with
   the "Products" heading's top exactly as before. */
.mega-panel--dark .products-showcase{
  background:none; padding-block:0; border-radius:0;
  grid-template-columns:minmax(240px,340px) auto;
  justify-content:center; gap:var(--space-8);
}

/* ---- Products mega menu (header nav) — two-level hover ----
   Company list on the left; the currently hovered/focused company's real
   products swap into the right panel. Distinct from .products-showcase__nav
   above (the on-page "Technology Partners" section on /solutions/, which
   keeps its own single-column + flyout pattern) — the header dropdown gets
   its own markup so this redesign never touches that page section.
   Switching is JS-driven (main.js, [data-t3-products-mega]) rather than
   pure-CSS :hover so the active company survives the cursor crossing the
   divider into the right panel: nothing here closes on mouseleave, it only
   changes when a different company is hovered or focused — no flicker. */
/* Products column is a fixed width, not auto/1fr — auto would track the
   *currently active* company's longest product name (display:none panels
   contribute nothing to intrinsic sizing), so the whole centered block
   would shift width, and therefore position, every time a different
   company is hovered. A fixed width sized to the longest approved name
   ("CADSEEK Analytics") keeps the block's total width — and so its
   centered position — constant across every company. */
.products-showcase__mega{
  display:grid; grid-template-columns:224px 236px; gap:var(--space-8);
  align-items:start;
}
/* Services only: a wider right column. Its panels need room for either
   Training's 2-column grid or 3D Metrology's wide single-row flow layout
   (below) — both need more than the 236px Products/Industries get away
   with for a short description or single-word product names. The mega
   panel itself has plenty of unused width to spare (it's centred well
   short of .mega-panel--full's own edge at typical desktop widths), so
   this only changes Services' own column, never the shared base rule
   Products/Industries still use untouched. Falls back to the narrow
   @media(max-width:1080px) rule below at that breakpoint, same as they
   do, since that rule is declared after this one and shares its
   specificity. */
.products-showcase__mega--services{ grid-template-columns:224px 440px; }
.products-showcase__companies{ list-style:none; margin:0; padding:0; }
.products-showcase__companies li + li{ margin-top:6px; }
.products-showcase__company{
  display:flex; align-items:center; gap:var(--space-2);
  padding:4px var(--space-3);
  margin-inline:calc(var(--space-3) * -1);
  border-left:2px solid transparent;
  border-radius:0 var(--radius-xs) var(--radius-xs) 0;
  font-family:var(--font-brand); font-size:var(--mega-item-size); font-weight:var(--mega-item-weight); line-height:var(--mega-item-line);
  color:rgba(255,255,255,.68);
  transition:color var(--duration-base) var(--ease-out),
             border-color var(--duration-base) var(--ease-out),
             background-color var(--duration-base) var(--ease-out);
}
/* Vendor mark — the sole visual identifier for its row, vertically
   centred via the flex row above. A fixed-size wrapper (not the img
   itself) reserves the same box for every company regardless of its
   logo's native proportions; the img fills that wrapper at 100%/100% and
   object-fit:contain scales the mark down to fit inside it without
   stretching. object-position:left keeps every logo's own visible left
   edge flush with the wrapper's left edge — and so with every other
   logo's — rather than centred within any leftover letterboxed space.
   A company still waiting on a real logo file (no invented/approximated
   marks — see cadopt_products_menu_data()) gets its plain name text
   inside this same fixed box instead of an <img>, so its row occupies
   the identical footprint as every logo row rather than sizing to its
   own text. */
.products-showcase__company-logo-wrap{
  flex:none; display:flex; align-items:center; justify-content:flex-start;
  width:96px; height:28px; overflow:hidden; white-space:nowrap;
}
.products-showcase__company-logo{
  width:100%; height:100%; display:block;
  object-fit:contain; object-position:left center;
}
/* XPLM's source file has virtually no internal padding around its mark
   (unlike the other vendor files), so at the shared 100%/100% fill it
   reads visibly larger than the rest despite sharing the exact same
   container. This scales the image down within that same container —
   still object-fit:contain, still left-aligned — rather than resizing
   the container itself, so the fixed box stays identical across every
   company. */
.products-showcase__company-logo--sm{ width:82%; height:82%; }
.products-showcase__company:hover,
.products-showcase__company:focus-visible{
  color:var(--white); background:rgba(255,255,255,.04); text-decoration:none;
}
/* Subtle, premium active state — accent text colour + a thin left
   indicator, not a filled button. No background fill, no heavy shadow. */
.products-showcase__company.is-active{
  color:var(--blue-300); border-left-color:var(--blue-300);
}
/* Positioning context for the panels below — each one is placed at a
   `top` matching its own company's row (set inline by main.js from the
   live, measured position of that row, never a hardcoded px value, so it
   holds at any screen size / font-size / zoom level). */
.products-showcase__products{ min-width:0; position:relative; }
/* Hidden by default and shown only via .is-active — belt-and-braces
   alongside the `hidden` attribute main.js also toggles, since a class
   selector this specific (display:flex) would otherwise outrank the
   browser's own [hidden]{display:none} rule and show every company's
   products stacked at once. Absolutely positioned (top set by JS) so the
   visible panel sits level with the currently hovered/active company
   row instead of always starting at the top of the products column. */
.products-showcase__panel{ display:none; flex-direction:column; gap:1px; position:absolute; top:0; left:0; right:0; }
.products-showcase__panel.is-active{ display:flex; }
/* Services reuses this same panel: most rows show a single description
   paragraph (.products-showcase p already colours text correctly for
   this dark surface — this only resets its own margin-top/max-width,
   meant for the left intro column, so the description's visible top
   lines up exactly with the row it was positioned against above, not
   offset by 16px); rows with real sub-services (3D Metrology, Training)
   instead render a .products-showcase__product link list here, same as
   Products — no separate flyout/submenu component needed for either
   case.
   font-size/weight/line-height set explicitly to the shared mega-menu
   description scale (tokens.css) — this per-item description (e.g.
   "Skilled engineers, deployed on a project or time-bound basis.") is a
   different UI role from .products-showcase p's own left-column intro
   copy above the "All Industries" button, which keeps its own larger
   lead-paragraph size unchanged. */
.products-showcase__panel p{
  margin-top:0; max-width:none;
  font-size:var(--mega-desc-size); font-weight:var(--mega-desc-weight); line-height:var(--mega-desc-line);
}
/* Wraps a service's sub-link list (3D Metrology, Training) so the gap
   between links lives here, one level below .products-showcase__panel —
   needed once the panel can hold either this wrapper OR a plain <p>
   description, so the panel's own layout doesn't have to change shape
   per branch. Default is a single tight column; --2col (added in
   header.php only once a service has more than 4 children — currently
   just Training's six) reflows into two columns so a long list finishes
   in half the vertical space instead of pushing past the panel's
   available height and forcing .mega-panel's overflow:auto to kick in
   (the scrollbar this whole component exists to avoid). */
.products-showcase__product-grid{ display:flex; flex-direction:column; gap:1px; }
.products-showcase__product-grid--2col{
  display:grid; grid-template-columns:1fr 1fr; column-gap:18px; row-gap:2px; align-items:start;
}
/* Shared "wide flow" layout — 3D Metrology and Training both use it (see
   cadopt_services_menu_data()'s 'layout' => 'flow' and header.php's
   $cadopt_is_flow): items pack left-to-right by their own text width and
   wrap a whole item to the next line only once a row genuinely runs out
   of room, instead of the --2col grid's fixed column count, which is what
   forced individual long names into a column narrower than their own
   text. row-gap is intentionally tight (8px, not the 18-24px originally
   spec'd) — these rows read as one compact list, not spaced-out cards,
   and a smaller gap also means more rows fit before a row-aligned panel
   (bottom-most rows, e.g. Training's own) could ever reach the mega
   panel's edge. flex:0 0 auto sizes every item to its own content;
   without it a flex item still shrinks below that content width by
   default, which is what caused wrapping even with white-space:nowrap
   applied. */
.products-showcase__product-grid--flow{ display:flex; flex-wrap:wrap; row-gap:8px; column-gap:28px; }
.products-showcase__product-grid--flow .products-showcase__product{
  flex:0 0 auto; padding:2px 0; margin-inline:0;
}
/* 3D Metrology only — its five names must never break mid-word (see the
   docblock above); Training's own two long names stay free to wrap onto
   a second line by client instruction, so this modifier is NOT applied
   to Training's flow grid. */
.products-showcase__product-grid--flow-nowrap .products-showcase__product{ white-space:nowrap; }
.products-showcase__product{
  display:block; padding:4px var(--space-3);
  margin-inline:calc(var(--space-3) * -1);
  border-radius:var(--radius-sm);
  font-family:var(--font-brand); font-size:var(--mega-subitem-size); font-weight:var(--mega-subitem-weight); line-height:var(--mega-subitem-line);
  color:rgba(255,255,255,.85);
  transition:color var(--duration-fast) var(--ease-out), background-color var(--duration-fast) var(--ease-out);
}
.products-showcase__product:hover,
.products-showcase__product:focus-visible{
  color:var(--blue-300); background:rgba(255,255,255,.06); text-decoration:none;
}

@media (max-width:1080px){
  .products-showcase__mega{ grid-template-columns:160px 190px; gap:var(--space-4); }
  .products-showcase__companies li + li{ margin-top:5px; }
  .products-showcase__company, .products-showcase__product{
    font-size:14px; padding:4px var(--space-2); margin-inline:calc(var(--space-2) * -1);
  }
  .products-showcase__company-logo-wrap{ width:76px; height:23px; }
}

/* ---- Products mega menu: one grid, six equal columns ----
   Products-only (Services/Industries keep the .products-showcase__mega
   hover-swap pattern above). Every column — including the last — is the
   exact same minmax(0,1fr) track width and the exact same gap from its
   neighbour: no per-column justify-self/content-sizing that would make
   any one column start at a different horizontal position than the
   grid's own even math predicts. That's deliberately NOT
   justify-content:space-between on a flex row (which pins only the
   first/last item to the container edges and lets the middle gaps
   balloon or shrink with content width) — a real grid gives every
   column an identical, predictable width regardless of how wide its
   own logo or product names are.
   PTC/Graebert/Cadence/iSEEK are one company per column (logo + product
   list — Cadence's 8 items make that column tall; height follows the
   content, nothing else). Sigmetrix/XPLM/KeyShot/BIMDeX have no named
   products, so they pair up two-per-column instead of getting a column
   each (Sigmetrix+KeyShot, XPLM+BIMDeX — client direction, marked-up
   reference): the second logo just stacks below the first inside that
   one column using the same row gap, so it sits close beneath it rather
   than being tied to the height of the tall Cadence column next door.
   Columns are minmax(0,1fr) rather than a fixed px width — six fixed
   tracks don't fit most real desktop widths once the panel's own
   gutters are subtracted, and a grid doesn't shrink fixed-max tracks to
   make room the way it does fr tracks; the result was horizontal
   overflow (a scrollbar) on ordinary screens. 1fr tracks always divide
   whatever width is actually available, so they can never overflow,
   while staying perfectly equal to each other regardless of that width.
   max-width:1240px + margin-inline:auto caps how wide the columns grow
   on very large screens and centres the block within the panel's own
   equal padding-inline (.products-showcase--full) — align-items:start
   keeps every column's first logo on the same top line.
   justify-items:center is what actually keeps the visible content
   balanced across columns of very different natural width (a single
   narrow logo vs. Cadence's 8-item list): each column's own content box
   shrink-wraps to its own natural width (see .products-partners__column
   below) and centres on its track's midpoint, so the distance between
   any two neighbouring midpoints is pure grid math (track width + gap),
   never shifted by how wide any one column's own logo or text happens
   to be. A fixed shared width across every column was tried and
   rejected: forcing e.g. Sigmetrix's ~110px-wide logo into the same box
   as Cadence's ~130px-wide longest item just relocates the imbalance —
   the narrow column's box still has to be left-aligned inside it
   (per the "keep product text left-aligned" requirement), so the extra
   width shows up as one-sided trailing blank space instead of being
   absent altogether. Letting each column size to its own real content
   and centring THAT is the only way the gap next to a short column
   isn't structurally bigger than the gap next to a long one. */
.products-partners{
  display:grid; grid-template-columns:repeat(6, minmax(0, 1fr)); column-gap:var(--space-8); row-gap:var(--space-8);
  align-items:start; justify-items:center; max-width:1240px; margin-inline:auto;
}
/* No explicit width here — flex-direction:column + the default
   align-items:stretch means this box sizes itself to its own WIDEST
   child (see .products-partners__item below) rather than to a shared
   number across companies, and justify-items:center above then centres
   that shrink-wrapped box within its equal 1fr track. For a two-logo
   column (Sigmetrix+KeyShot, XPLM+BIMDeX) the stretch also matches both
   items to that same widest-child width, which is what gives the pair a
   literal shared left edge — not independent per-logo centring, which
   would let a narrower KeyShot drift off Sigmetrix's edge above it. */
.products-partners__column{
  display:flex; flex-direction:column; gap:var(--space-8); min-width:0;
}
/* Still no explicit width: each item sizes to its own natural content
   (its brand-logo-wrap, capped at 108px below, or its longest product
   name — whichever is wider), then the column above stretches every
   item in it to match the widest one. align-items:flex-start keeps the
   logo and the product list flush to a shared left edge *within* that
   box — the box itself is what gets centred in the track, not its
   individual children, so "centred column, left-aligned product list"
   both hold at the same time rather than fighting each other. */
.products-partners__item{
  display:flex; flex-direction:column; gap:14px; min-width:0; align-items:flex-start;
}
.products-partners__brand{
  display:inline-flex; align-items:center;
  padding:4px; margin:-4px; border-radius:var(--radius-xs);
  transition:background-color var(--duration-fast) var(--ease-out);
}
.products-partners__brand:hover,
.products-partners__brand:focus-visible{ background:rgba(255,255,255,.05); }
.products-partners__brand-logo-wrap{
  display:flex; align-items:center; justify-content:flex-start;
  width:100%; max-width:108px; height:32px;
}
.products-partners__brand-logo{
  max-width:100%; max-height:100%; width:auto; height:auto;
  object-fit:contain; display:block;
}
/* PTC and XPLM's source files have almost no internal padding around
   their marks, so at full size they read visibly larger than the rest —
   see cadopt_products_menu_data()'s `logo_scale` doc. */
.products-partners__brand-logo--sm{ max-width:82%; max-height:82%; }
/* Defensive text fallback — every current principal carries a real logo
   file, but this keeps a future name-only addition from rendering blank. */
.products-partners__brand-name{
  font-family:var(--font-brand); font-size:var(--mega-item-size); font-weight:var(--mega-item-weight); line-height:var(--mega-item-line); color:var(--white);
}
.products-partners__product-list{
  list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px;
}
.products-partners__product-link{
  display:block; padding:3px 0;
  font-family:var(--font-brand); font-size:var(--mega-subitem-size); font-weight:var(--mega-subitem-weight); line-height:var(--mega-subitem-line);
  color:rgba(255,255,255,.75);
  transition:color var(--duration-fast) var(--ease-out);
}
.products-partners__product-link:hover{ color:var(--blue-300); text-decoration:none; }

@media (max-width:1080px){
  /* Outer inset scales down with the column gap here rather than
     staying fixed at 70px on every width — same equal-both-sides rule
     (.products-showcase--full still owns padding-inline alone; see the
     .mega-panel--dark .products-showcase note above), just a smaller
     shared number once the panel itself has less room to work with. */
  .products-showcase--full{ padding-inline:var(--space-8); }
  .products-partners{
    grid-template-columns:repeat(3, minmax(0, 1fr)); gap:20px; max-width:520px;
  }
  .products-partners__column{ gap:20px; }
  .products-partners__brand-logo-wrap{ max-width:84px; height:26px; }
}

/* ---- Mobile drawer: nested company → products accordion ----
   Reuses .acc/.acc__trigger/.acc__panel as-is (main.js already binds every
   [data-t3-acc-trigger] it finds, nested or not) — only indentation and
   type-scale change for the second level, so it reads as a sub-list rather
   than a sibling section. */
/* UPDATE (2026-09-15, mobile-only hierarchy fix): a second-tier trigger
   ("PTC" inside Products, "3D Metrology" inside Services, an industry
   name inside Industries) used to differ from its top-level parent only
   by a 16px indent and a slightly muted colour — same 16px/600-weight
   type size as the parent row, so on a narrow drawer it read as a peer
   section rather than something nested under Products/Services/
   Industries. .acc--sub itself (the second-tier trigger + its own panel,
   as one block) now gets an inset "card" treatment — left border, tint
   background, indent — so the whole nested block visibly reads as
   *inside* its parent row, not a sibling of it. Scoped entirely to
   .acc/.drawer, which only render in the mobile drawer (the desktop
   mega-menu is separate markup), so this cannot affect desktop. */
.acc--sub{
  border-bottom:0; margin-left:var(--space-4); padding-left:var(--space-3);
  border-left:2px solid var(--border-subtle); background:var(--surface-sunken);
}
.acc__trigger--sub{
  min-height:44px; padding-left:0;
  font-size:var(--mega-item-size); font-weight:500; line-height:var(--mega-item-line); color:var(--text-body);
}
.acc--sub .acc__panel a{ padding-left:var(--space-4); font-size:var(--mega-subitem-size); font-weight:var(--mega-subitem-weight); line-height:var(--mega-subitem-line); }
/* Company with no approved sub-products (Sigmetrix, XPLM, KeyShot, BIMDeX):
   a plain link at the same indent/inset treatment as .acc--sub above, but
   with no chevron and nothing to expand — there is no accordion trigger
   to open onto an empty panel. */
.acc__link--sub{
  display:flex; align-items:center;
  min-height:44px; margin-left:var(--space-4); padding:var(--space-3) var(--space-3) var(--space-3) var(--space-3);
  border-left:2px solid var(--border-subtle); background:var(--surface-sunken);
  font-family:var(--font-brand); font-size:var(--mega-item-size); font-weight:500; line-height:var(--mega-item-line); color:var(--text-body);
}
/* A service/industry's own description, shown as a tappable link to that
   service/industry's page inside its accordion sub-panel — e.g. "Skilled
   engineers, deployed on a project or time-bound basis." Same description
   scale as .mega-panel__link span / .products-showcase__panel p on
   desktop, not the plain submenu-link scale it would otherwise inherit
   from .acc__panel a, since it's playing a different role (supporting
   copy, not a peer navigation item). Same inset treatment as .acc--sub /
   .acc__link--sub above, for the same reason.  */
.acc__link--desc{
  display:block; min-height:44px; margin-left:var(--space-4); padding:var(--space-3) var(--space-3) var(--space-3) var(--space-3);
  border-left:2px solid var(--border-subtle); background:var(--surface-sunken);
  font-size:var(--mega-desc-size); font-weight:var(--mega-desc-weight); line-height:var(--mega-desc-line); color:var(--text-muted);
}

/* ---- "Why CADOpt is the Right Engineering Partner" (front-page.php) ----
   Originally an asymmetric "bento" grid (2 small + 1 wide light card,
   plus a tall dark feature card spanning both rows) — per direct client
   instruction the 3 light cards were swapped for 2 simple stat cards, so
   this is now just 3 equal items in a single row: 2 stat cards + the
   dark feature card. Every colour below is an existing CADOpt token
   (--surface-brand-soft, --grad-ink, --brand, --border-subtle); nothing
   new introduced. */
.pill-eyebrow{
  display:inline-flex; align-items:center;
  height:28px; padding-inline:16px;
  border:1px solid var(--border-strong); border-radius:var(--radius-pill);
  font-family:var(--font-brand); font-size:12px; font-weight:700;
  letter-spacing:var(--tracking-eyebrow); text-transform:uppercase;
  color:var(--text-strong);
}
/* 3 equal columns; card 3 (wide) spans columns 1-2 in row 2, card 4
   (dark) spans both rows in column 3. Grid's default align-items:stretch
   is what keeps card 4's bottom edge flush with card 3's — it stretches
   across exactly row1 + gap + row2, not a guessed/hardcoded height. */
.partner-bento{
  display:grid; grid-template-columns:repeat(3, minmax(0, 1fr));
  gap:18px; margin-top:48px;
}
/* Stat cards — same thin-border rectangle treatment used here originally
   and reused (unchanged) by About Story's own stat row below. */
.partner-bento__stat{
  border:1px solid var(--border-subtle); border-radius:var(--radius-md);
  padding:22px 24px;
  display:flex; flex-direction:column; gap:6px;
  background:var(--white);
}
/* About Story's own stat row (front-page.php) — reuses .partner-bento__stat
   card styling verbatim (same thin border, same value/label treatment),
   just a 2-up wrapper. */
.about-story__stats{ display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:16px; margin-top:24px; }
/* This section's own stat cards need their label held to one line (the
   text column here is narrower than the "Why CADOpt" bento section's,
   so the shared .partner-bento__stat defaults wrapped "Analysis-time
   reduction at INDO-MIM" to 2 lines) — scoped to .about-story__stats so
   the OTHER place .partner-bento__stat is used is untouched. Slightly
   tighter padding frees the extra width the smaller font alone doesn't
   cover. */
.about-story__stats .partner-bento__stat{ padding:16px 14px; }
.about-story__stats .partner-bento__stat-label{ white-space:nowrap; font-size:13px; }
@media (max-width:400px){
  .about-story__stats .partner-bento__stat-label{ white-space:normal; }
}
.partner-bento__stat-value{ font-family:var(--font-brand); font-size:1.7rem; font-weight:700; color:var(--brand); line-height:1.2; }
.partner-bento__stat-label{ font-size:14px; color:var(--text-muted); }
.partner-bento__card{
  background:var(--surface-brand-soft); border-radius:var(--radius-md);
  padding:var(--space-6);
  display:flex; flex-direction:column; gap:var(--space-3);
}
.partner-bento__card--1{ grid-column:1; grid-row:1; }
.partner-bento__card--2{ grid-column:2; grid-row:1; }
.partner-bento__card--3{ grid-column:1 / 3; grid-row:2; }
.partner-bento__card--4{
  grid-column:3; grid-row:1 / 3;
  background:var(--grad-ink); color:#fff;
  text-decoration:none;
}
.partner-bento__icon{
  width:40px; height:40px; border-radius:50%; flex:none;
  border:1.5px solid var(--brand); color:var(--brand);
  display:flex; align-items:center; justify-content:center;
}
.partner-bento__card--4 .partner-bento__icon{ border-color:rgba(255,255,255,.4); color:#fff; }
.partner-bento__icon svg{ width:20px; height:20px; stroke-width:1.75; }
.partner-bento__card h3{ font-size:18px; margin:0; }
.partner-bento__card--4 h3{ color:#fff; font-size:21px; }
.partner-bento__card p{ color:var(--text-muted); margin:0; font-size:14.5px; line-height:1.6; }
.partner-bento__card--4 p{ color:rgba(255,255,255,.85); }
.partner-bento__card--4 p + p{ margin-top:2px; }
/* The card's own .btn (btn--on-brand) needs to sit pinned to the card's
   bottom edge, not stretched full-width — .btn's shared geometry now
   covers the rest (size, colour, hover, radius). */
.partner-bento__card--4 .btn{ margin-top:auto; align-self:flex-start; }
@media (max-width:900px){
  .partner-bento{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
  .partner-bento__card--1{ grid-column:1; grid-row:1; }
  .partner-bento__card--2{ grid-column:2; grid-row:1; }
  .partner-bento__card--3{ grid-column:1 / 3; grid-row:2; }
  .partner-bento__card--4{ grid-column:1 / 3; grid-row:3; }
}
@media (max-width:600px){
  .partner-bento{ grid-template-columns:1fr; gap:16px; margin-top:36px; }
  .partner-bento__card--1, .partner-bento__card--2, .partner-bento__card--3, .partner-bento__card--4{
    grid-column:auto; grid-row:auto;
  }
}

/* ---- Client Stories (homepage) ----
   Sticky image left (a single static element, not part of the slide
   loop — stays put as the carousel advances) + a true one-at-a-time
   carousel right (single active slide, prev/next arrows, no dots/
   pause — nine slides would make a dot row noisy). Reuses main.js
   initHeroCarousel() as-is (data-t3-hero/-slide/-prev/-next — the same
   behaviour already driving the homepage hero banner); only the
   visual design here is specific to this section. Dark surface
   (--grad-ink, the same dark gradient used elsewhere on this site,
   not a one-off colour) per the client's reference direction. */
/* align-items was "center" — that's what let the right column float
   independently of the image's own top/bottom edges instead of sharing
   them. Grid's default (stretch) now gives both columns the row's full
   height, and .client-stories__content below turns that into real
   top/bottom anchoring via flex-direction:column. */
.client-stories__layout{
  display:grid; grid-template-columns:0.85fr 1fr; gap:clamp(32px,5vw,64px);
}
.client-stories__media{ position:relative; }
.client-stories__media img{
  width:100%; height:clamp(360px,44vw,520px); object-fit:cover;
  border-radius:var(--radius-xl); display:block;
}
/* Real count, not a fabricated rating — the reference's "4.5+ / Guest
   Feedback" badge implies an aggregate review score CADOpt has no data
   for. This shows the real number of stories below instead. */
.client-stories__badge{
  position:absolute; top:24px; left:24px; z-index:1;
  background:var(--white); border-radius:var(--radius-lg);
  padding:16px 20px; box-shadow:var(--shadow-lg);
}
.client-stories__badge-value{ font-family:var(--font-brand); font-weight:700; font-size:28px; color:var(--brand); line-height:1; }
.client-stories__badge-label{ font-size:13px; color:var(--text-muted); margin-top:4px; }

/* align-self:flex-start — .client-stories__content became a flex column
   for the nav-bottom-alignment fix; flex's default align-items:stretch
   was stretching this (and any other child with no explicit width) to
   the full column width. inline-block alone only wins in normal block
   flow, not inside a flex container. */
.client-stories__pill{
  display:inline-block; align-self:flex-start; width:fit-content;
  background:rgba(255,255,255,.1); border:1px solid rgba(255,255,255,.18);
  border-radius:var(--radius-pill); padding:8px 20px;
  font-family:var(--font-brand); font-size:13px; font-weight:600; color:rgba(255,255,255,.85);
}
.client-stories__content{ display:flex; flex-direction:column; height:100%; }
.client-stories__content h2{ color:#fff; margin-top:16px; }
/* flex:1 + min-height:0 — the carousel/track now claim exactly whatever
   space is left in the column after the pill/heading above and the nav
   below, instead of reserving a fixed height sized for the single
   longest of the 9 real testimonials. That fixed-height approach kept
   every SHORT quote's nav sitting exactly where the longest quote needed
   it (wasted space below short quotes), and — the actual bug being fixed
   here — still let the longest quote's own overhead push the nav past
   the image's bottom edge whenever the column's available height was
   less than that fixed reservation. Nav is now unconditionally pinned to
   the image's bottom edge (via .client-stories__content's height:100%
   and this flex:1 track), for every quote length. */
.client-stories__carousel{ position:relative; margin-top:32px; flex:1; min-height:0; display:flex; flex-direction:column; }
.client-stories__track{ position:relative; flex:1; min-height:0; overflow:hidden; }
.client-stories__slide{
  position:absolute; inset:0;
  opacity:0; visibility:hidden;
  transition:opacity var(--duration-slow) var(--ease-out), visibility 0s linear var(--duration-slow);
  display:flex; flex-direction:column; align-items:flex-start; margin:0;
}
.client-stories__slide[data-active="true"]{ opacity:1; visibility:visible; transition-delay:0s; }
.client-stories__quote-mark{ color:var(--blue-300); margin-bottom:10px; flex:none; }
.client-stories__quote-mark svg{ width:22px; height:22px; display:block; }
/* Smaller size + tighter line-height than before (was 16px/1.7) — the
   single biggest lever for fitting more of a long testimonial into a
   height now capped by the image, short of shrinking further into
   illegible territory. overflow:hidden on the track above is the
   remaining safety net for the rare case where even this still doesn't
   fully fit at the shortest image height (360px, narrow viewports) —
   the quote's own tail clips there rather than pushing the nav down
   again, which was the actual reported bug. */
.client-stories__quote{
  margin:0 0 14px; font-size:13.5px; line-height:1.55; color:rgba(255,255,255,.9);
  overflow:hidden; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:11;
}
.client-stories__name{ font-family:var(--font-brand); font-weight:700; font-size:15px; color:#fff; flex:none; }
.client-stories__company{ font-size:13px; color:rgba(255,255,255,.6); margin-top:2px; flex:none; }

/* margin-top:auto pins this to the bottom of the flex column (the
   content column now stretches to the image's own height via
   .client-stories__layout/.client-stories__content above) instead of
   sitting a fixed distance below the carousel. var(--space-5) remains as
   a floor via the carousel's own margin, not fought against here. */
.client-stories__nav{ display:flex; gap:var(--space-2); margin-top:auto; padding-top:var(--space-5); }
.client-stories__nav-btn{
  width:44px; height:44px; display:grid; place-items:center;
  background:rgba(255,255,255,.1); border:1px solid rgba(255,255,255,.22);
  border-radius:50%; cursor:pointer; color:#fff;
  transition:background-color var(--duration-fast) var(--ease-out);
}
.client-stories__nav-btn:hover{ background:rgba(255,255,255,.2); }
.client-stories__nav-btn svg{ width:18px; height:18px; }

@media (max-width:900px){
  .client-stories__layout{ grid-template-columns:1fr; }
  .client-stories__media img{ height:280px; }
}

/* ---- Page banner (About Us / Management / Careers) ----
   Shared shell for the three new standalone pages, two modifiers:
   --dark (no real hero image on the live cadopt.com About/Management
   pages, per direct source review — a solid ink-gradient band instead
   of inventing a stock photo those pages don't have) and --image
   (Careers' real reference DOES have a hero photo, so this one uses an
   actual image + scrim, matching .hero-carousel__scrim's own gradient
   language rather than a one-off overlay). */
.page-banner{ position:relative; overflow:hidden; }
.page-banner--dark{ background:var(--grad-ink); }
.page-banner--image{ min-height:340px; display:flex; align-items:center; }
.page-banner--image img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0; }
.page-banner__scrim{
  position:absolute; inset:0; z-index:1;
  background:linear-gradient(100deg, rgba(8,22,40,.88) 0%, rgba(8,22,40,.6) 55%, rgba(8,22,40,.3) 100%);
}
.page-banner__content{ position:relative; z-index:2; padding-block:56px; color:#fff; }
.page-banner__content .eyebrow{ color:var(--blue-300); }
.page-banner__content h1{ color:#fff; margin-top:14px; }
.page-banner__content p{ color:rgba(255,255,255,.85); margin-top:14px; max-width:62ch; }

/* Breadcrumb embedded inside a .page-banner (light-on-dark), instead of the
   default white strip below the banner — see template-parts/breadcrumb.php */
.page-banner__content .breadcrumb--on-image{ padding-block:0; border-bottom:none; margin-top:16px; }
.page-banner__content .breadcrumb--on-image ol{ color:rgba(255,255,255,.65); }
.page-banner__content .breadcrumb--on-image a{ color:rgba(255,255,255,.75); }
.page-banner__content .breadcrumb--on-image a:hover{ color:#fff; }
.page-banner__content .breadcrumb--on-image li + li::before{ color:rgba(255,255,255,.4); }
.page-banner__content .breadcrumb--on-image li[aria-current="page"]{ color:#fff; font-weight:600; }

@media (max-width:600px){
  .page-banner--image{ min-height:260px; }
  .page-banner__content{ padding-block:36px; }
}

/* Contact Us — interactive office/location tabs and detail card. */
.location-tabs{ display:flex; flex-wrap:wrap; gap:10px; }
.location-tab{
  padding:10px 22px; border-radius:var(--radius-pill); border:1px solid var(--border-strong);
  background:var(--surface-page); color:var(--text-body); font-size:14px; font-weight:600; cursor:pointer;
  transition:background-color var(--duration-base) var(--ease-out), color var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out);
}
.location-tab:hover{ border-color:var(--brand); }
.location-tab.is-active{ background:var(--brand); border-color:var(--brand); color:#fff; }

.location-detail h3{ margin:0 0 var(--space-4); }
.location-detail__grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:var(--space-5); }
.location-detail__grid h4{ font-size:12px; text-transform:uppercase; letter-spacing:.04em; color:var(--text-muted); margin:0 0 6px; }
.location-detail__grid p{ font-size:14px; color:var(--text-body); margin:0 0 4px; line-height:1.5; }

/* Partner/product pages (PTC, Graebert, Cadence, iSEEK, Sigmetrix, XPLM,
   KeyShot, BIMDeX) — logo+text and image+text split rows, single column on
   mobile so the reference pages' side-by-side layout doesn't cramp a
   narrow viewport. Generic names since every partner page reuses these,
   not just PTC. */
.partner-about-grid{ grid-template-columns:200px 1fr; gap:var(--space-8); align-items:start; }
.partner-media-grid{ grid-template-columns:1fr 1.4fr; gap:var(--space-8); align-items:center; }
@media (max-width:700px){
  .partner-about-grid, .partner-media-grid{ grid-template-columns:1fr; }
  .partner-about-grid img{ max-width:140px; margin:0 auto; }
}

/* UPDATE (2026-09-15, image/text column height match): a plain <img
   width:100%;height:auto;> in a .partner-media-grid-style row dictates
   its own height off its aspect ratio, which can run well past a short
   text column even with align-items:stretch — a percentage height
   directly on the <img> can't fix this, because it's measured against
   the grid row's height while that row's own auto size is still being
   resolved (an indefinite/circular reference), so browsers treat it as
   height:auto and the row ends up exactly as tall as the image anyway.
   .media-crop sidesteps the circularity: its <img> is position:absolute,
   so the wrapper itself has no intrinsic content height and stretches
   (align-items:stretch, the grid default) to match whatever height the
   OTHER column's real content already resolved — then the image fills
   that resolved box via inset:0 + object-fit:cover. Needs no dedicated
   height for use as one column here or the same wrapper anywhere else
   two columns need to end level. Only .partner-media-grid's own mobile
   breakpoint (700px, collapses to one column) needs a fallback height
   for this class, since there's no sibling row to borrow from once
   stacked — reusable, not one-off, since other pages share this exact
   image+text pattern (Industries, and the PTC/Graebert/Cadence/iSEEK/etc.
   partner pages above). */
.media-crop{ position:relative; overflow:hidden; border-radius:var(--radius-lg); }
.media-crop img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }
@media (max-width:700px){
  .partner-media-grid .media-crop{ aspect-ratio:4/3; }
}

/* .media-crop-bg — same problem as .media-crop above, solved with a CSS
   background-image instead of a position:absolute <img>, per client
   instruction: on desktop the element carrying the photo has NO
   normal-flow image content at all (min-height:0, no intrinsic size of
   its own beyond what grid stretch gives it), so there is no path by
   which it can ever set the row's height — the text column is the only
   thing that can. On mobile (≤700px, where .partner-media-grid collapses
   to one column and there is no sibling row to stretch to) the
   background-image is turned off and a real <img> — present in the
   markup the whole time as .media-crop-bg__mobile-img, display:none on
   desktop — is shown instead, sized by its own natural aspect ratio like
   any normal stacked content image. */
.media-crop-bg{
  display:block; min-height:0;
  background-size:cover; background-position:center; background-repeat:no-repeat;
  border-radius:var(--radius-lg);
}
.media-crop-bg__mobile-img{ display:none; }
@media (max-width:700px){
  .media-crop-bg{ background-image:none !important; min-height:0; aspect-ratio:auto; border-radius:0; }
  .media-crop-bg__mobile-img{ display:block; width:100%; height:auto; border-radius:var(--radius-lg); }
}

/* Partner logo mark shown on a light card/section background — some
   sourced logo files (e.g. Sigmetrix) carry their own dark background
   baked into the image, others are transparent; this wrapper keeps sizing
   consistent either way without cropping or stretching. */
.partner-logo-mark{ max-width:220px; width:100%; height:auto; display:block; }

/* Suite/module info cards (Cadence, XPLM) — image + heading, no CTA: for
   real product families that don't have a standalone page in this rebuild
   (see page docblocks), so a card is shown without a dead link rather
   than inventing a route. */
.info-card{
  background:var(--surface-page); border:1px solid var(--border-subtle); border-radius:var(--radius-lg);
  overflow:hidden; height:100%; box-sizing:border-box; display:flex; flex-direction:column;
}
.info-card img{ width:100%; aspect-ratio:4/3; object-fit:cover; display:block; }
.info-card__body{ padding:var(--space-5); }
.info-card h3{ font-size:16px; margin:0; }

/* CAD Feature cards (PTC page) — real product-screenshot image, numbered
   label, title and a real "Know More" link (not a dead "#", see
   page-ptc.php's docblock for where it actually points). */
.cad-feature-card{
  background:var(--surface-page); border:1px solid var(--border-subtle); border-radius:var(--radius-lg);
  padding:var(--space-5); text-align:center; height:100%; box-sizing:border-box;
  display:flex; flex-direction:column; align-items:center; gap:8px;
  transition:transform var(--duration-base) var(--ease-out), box-shadow var(--duration-base) var(--ease-out);
}
.cad-feature-card:hover{ transform:translateY(-4px); box-shadow:var(--shadow-md); }
.cad-feature-card img{ width:96px; height:96px; object-fit:contain; border-radius:var(--radius-md); }
.cad-feature-card__num{ font-family:var(--font-mono); font-weight:700; color:var(--brand); font-size:14px; margin-top:6px; }
.cad-feature-card h3{ font-size:16px; margin:0; }
.cad-feature-card__link{ font-size:13px; font-weight:600; color:var(--brand); display:inline-flex; align-items:center; gap:4px; margin-top:auto; }
.cad-feature-card__link:hover{ text-decoration:underline; }

/* Other Products tiles — compact link tiles, not full cards; for a short
   list of real product names each pointing at a real destination. Still
   used on the partner pages that list real category names with no
   matching route in this rebuild (e.g. XPLM Solutions) — see .product-tile
   below. The PTC page's own "Other Products" section was upgraded to the
   card grid further down (.other-products-grid / .other-product-card). */
.product-tile-grid{ display:flex; flex-wrap:wrap; gap:12px; }
.product-tile{
  padding:12px 22px; border:1px solid var(--border-strong); border-radius:var(--radius-pill);
  font-size:14px; font-weight:600; color:var(--text-body); background:var(--surface-page);
  transition:background-color var(--duration-base) var(--ease-out), color var(--duration-base) var(--ease-out), border-color var(--duration-base) var(--ease-out);
}
.product-tile:hover{ background:var(--brand); border-color:var(--brand); color:#fff; }
/* Non-clickable variant — a real category name with no matching route in
   this rebuild yet (see the relevant page's docblock), shown plain rather
   than as a dead link. */
span.product-tile:hover{ background:var(--surface-page); border-color:var(--border-strong); color:var(--text-body); }

/* Other Products card grid (PTC page) — premium clickable product-nav
   cards, replacing the plain pill row. 4 columns on desktop so 7 real
   items fall into a natural 4-then-3 layout with no forced stretching. */
.other-products-grid{ display:grid; grid-template-columns:repeat(4,1fr); gap:18px; }
@media (max-width:900px){ .other-products-grid{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:480px){ .other-products-grid{ grid-template-columns:1fr; } }

.other-product-card{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  min-height:78px; padding:18px 20px; box-sizing:border-box;
  background:var(--surface-page); border:1px solid var(--border-subtle); border-radius:16px;
  font-size:15px; font-weight:600; color:var(--text-strong);
  transition:border-color 200ms ease, transform 200ms ease, box-shadow 200ms ease, background-color 200ms ease;
}
.other-product-card:hover{
  border-color:var(--brand); box-shadow:var(--shadow-sm); transform:translateY(-2px); color:var(--text-strong);
}
.other-product-card__title{ display:inline-flex; align-items:center; }
.other-product-card__arrow{ flex:none; display:inline-flex; color:var(--brand); transition:transform 200ms ease; }
.other-product-card:hover .other-product-card__arrow{ transform:translateX(4px); }

/* Featured variant — a subtle blue tint, used only for the current/latest
   release among an "Other Products" list (e.g. PTC Creo 12.0), never more
   than one card per grid. */
.other-product-card--featured{ background:var(--surface-brand-soft); border-color:var(--blue-200); }
.other-product-card__badge{
  font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  color:var(--brand); background:var(--white); border-radius:var(--radius-pill);
  padding:2px 9px; margin-left:8px; flex:none;
}

/* Job opening cards (Careers) — equal height within a row, meta line and
   Apply button pinned to a consistent position regardless of description
   length. */
.job-card{ display:flex; flex-direction:column; gap:var(--space-3); height:100%; box-sizing:border-box; }
.job-card h3{ margin:0; font-size:19px; }
.job-card__meta{ display:flex; flex-wrap:wrap; gap:4px var(--space-4); font-size:13px; color:var(--text-muted); }
.job-card p{ font-size:14.5px; line-height:1.6; color:var(--text-body); margin:0; flex:1; }
.job-card .btn{ margin-top:var(--space-2); align-self:flex-start; }

/* Leadership team cards (Management) — equal-ratio photo, name, title, and
   an optional collapsed bio (client-supplied real bios) behind a plain
   <details> toggle so the base card stays photo + name + title only. */
.team-card{
  background:var(--surface-page); border:1px solid var(--border-subtle);
  border-radius:var(--radius-lg); overflow:hidden; height:100%; box-sizing:border-box;
  display:flex; flex-direction:column;
  transition:transform var(--duration-base) var(--ease-out), box-shadow var(--duration-base) var(--ease-out);
}
.team-card:hover{ transform:translateY(-4px); box-shadow:var(--shadow-md); }
.team-card__photo{ aspect-ratio:1/1; overflow:hidden; background:var(--surface-brand-soft); }
.team-card__photo img{ width:100%; height:100%; object-fit:cover; display:block; }
.team-card__body{ padding:var(--space-5); text-align:center; }
.team-card h3{ font-size:18px; margin:0; }
.team-card__role{ color:var(--text-muted); font-size:14px; margin-top:4px; }
.team-card__details{ margin-top:var(--space-4); text-align:left; }
.team-card__details summary{ cursor:pointer; color:var(--brand); font-size:14px; font-weight:600; list-style:none; text-align:center; }
.team-card__details summary::-webkit-details-marker{ display:none; }
.team-card__details summary::after{ content:"  \25BE"; }
.team-card__details[open] summary::after{ content:"  \25B4"; }
.team-card__bio{ margin-top:10px; font-size:14px; line-height:1.6; color:var(--text-body); }

/* Dark navy card with a glass icon chip top-left — introduced for the
   Technology Partners cards (About Us), reused as-is for the "Why CADOpt?"
   cards (Careers) per client request. Distinct from the site's existing
   .card--dark (white icon badge); deliberately its own component since the
   icon treatment differs, not a modifier on .card--dark. */
.tech-partner-card{
  background:var(--grad-ink); border-radius:20px; padding:32px;
  display:flex; flex-direction:column; gap:var(--space-4); height:100%; box-sizing:border-box;
  transition:transform var(--duration-base) var(--ease-out), box-shadow var(--duration-base) var(--ease-out);
}
.tech-partner-card:hover{ transform:translateY(-4px); box-shadow:var(--shadow-lg); }
.tech-partner-card__icon{
  width:48px; height:48px; border-radius:12px; flex:none;
  display:flex; align-items:center; justify-content:center;
  background:rgba(255,255,255,.12); color:#fff;
}
.tech-partner-card__icon svg{ width:20px; height:20px; stroke-width:2; }
.tech-partner-card h3{ color:#fff; font-size:24px; line-height:1.3; margin:0; }
.tech-partner-card p{ color:rgba(255,255,255,.72); font-size:16px; line-height:1.6; margin:0; }
/* Logo-only variant (About Us) — real partner logo in place of the
   icon-chip + "CADOpt Technologies + X" heading, used where the card's
   own logo is identifying enough on its own. Fixed-height flex box so
   every card's description starts at the same line regardless of each
   partner's own logo proportions — same fixed-box-not-the-image approach
   already used for the header mega-menu's company marks
   (.products-showcase__company-logo-wrap). */
.tech-partner-card__logo-area{ height:52px; display:flex; align-items:center; }
.tech-partner-card__logo{ max-height:52px; max-width:100%; width:auto; height:auto; object-fit:contain; }
.tech-partner-card__logo-text{ color:#fff; font-family:var(--font-brand); font-weight:700; font-size:22px; }
/* Some real partner marks (Hexagon's own wordmark is near-black) aren't
   legible straight on this card's dark navy — rather than recolour the
   logo file itself, it sits on a small white chip instead, the same
   "put the unmodified mark on a light patch" treatment already used for
   the footer's certification badges. PTC/Graebert/iSEEK don't need this;
   only applied where a logo actually requires it. */
.tech-partner-card__logo-chip{
  background:#fff; border-radius:var(--radius-sm); padding:8px 14px;
  display:inline-flex; align-items:center; height:52px; box-sizing:border-box;
}
.tech-partner-card__logo-chip .tech-partner-card__logo{ max-height:36px; }
@media (max-width:600px){
  .tech-partner-card{ padding:24px; }
  .tech-partner-card h3{ font-size:20px; }
}

/* Tightens the gap to the section directly above/below only — two adjacent
   .section blocks each carry a full --section-rhythm (up to 128px) of
   their own padding, which stacks into an oversized gap between them. */
.section--tight-top{ padding-top:clamp(48px,6vw,80px); }
.section--tight-bottom{ padding-bottom:clamp(48px,6vw,80px); }

/* Certification badge tile — a light-background sibling of the footer's
   existing .home-cta__badge (same sizing/radius/object-fit rules, reused
   rather than re-invented), with a border added since the footer's plain
   white tile has no boundary against a dark backdrop to define it against
   — here it needs one to read as a tile on a light card. */
.cert-badge{
  display:flex; flex-direction:column; align-items:center; gap:6px;
  background:#fff; border:1px solid var(--border-subtle); border-radius:var(--radius-lg);
  padding:clamp(8px,1vw,14px); width:clamp(64px,8vw,96px); flex:none;
}
.cert-badge img{ width:clamp(36px,4vw,52px); height:clamp(36px,4vw,52px); object-fit:contain; }
.cert-badge span{ font-family:var(--font-brand); font-weight:600; font-size:11px; color:var(--text-strong); text-align:center; line-height:1.25; }
/* Awards section's Certifications card — a fixed 2x2 grid instead of the
   flex-wrap row, so 4 tiles read as one deliberate block rather than
   whatever happens to fit per line. Fills the full card width (no
   max-width cap) so the tiles read as one balanced block regardless of
   the card's column width, instead of floating in a corner with empty
   space beside them. */
.cert-badge-grid{ display:grid; grid-template-columns:repeat(2,1fr); gap:16px; }
.cert-badge--lg{ width:100%; padding:22px 14px; }
.cert-badge--lg img{ width:48px; height:48px; }
.cert-badge--lg span{ font-size:12.5px; }

/* Recognition row — compact title + muted subtitle, replacing a plain <li>
   bullet list with something easier to scan at a glance. */
.recognition-row + .recognition-row{ margin-top:var(--space-5); padding-top:var(--space-5); border-top:1px solid var(--border-subtle); }
.recognition-row__title{ font-family:var(--font-brand); font-weight:600; font-size:16px; color:var(--text-strong); }
.recognition-row__meta{ font-size:14px; color:var(--text-muted); margin-top:4px; }

/* Awards section — a 4-cell grid (Milestones heading, an empty spacer,
   the milestone-card stack, and the right column), NOT two independent
   columns. This is what makes both alignment requirements structural
   instead of guessed pixel offsets:
   - Row 1 = the heading + the spacer. A CSS Grid row's height is shared
     across every column in it, so row 1 is exactly as tall as the
     "Milestones" heading on BOTH sides, even though the spacer is empty —
     meaning row 2 starts at the identical y-coordinate in both columns,
     which is exactly "top of Certifications == top of the first
     milestone card".
   - Row 2 = the milestone stack and the right column, with
     align-items:stretch forcing the shorter right column to fill row 2's
     full height (driven by the taller milestone stack). The Recognition
     card then absorbs that extra height via flex:1 below, so its bottom
     edge lands exactly on row 2's bottom edge too — "bottom of Recognition
     == bottom of the last milestone card". No margin-top/margin-bottom
     guesses anywhere in this rule. */
.awards-columns{
  display:grid;
  grid-template-columns:minmax(0,2fr) minmax(300px,1fr);
  gap:24px;
  align-items:stretch;
}
@media (max-width:900px){
  .awards-columns{ grid-template-columns:minmax(0,3fr) minmax(240px,2fr); }
}
@media (max-width:600px){
  .awards-columns{ grid-template-columns:1fr; }
}

/* Right column: a column flex stack so Recognition (flex:1, below) can
   grow to fill whatever height align-items:stretch gives this column,
   while Certifications keeps its own natural (flex:0) height. */
.awards-columns__right{ display:flex; flex-direction:column; height:100%; gap:22px; }
/* Recognition specifically (not Certifications) grows to fill the
   leftover space; becoming a column flex container itself keeps its own
   content pinned to the top instead of centering it, so any extra height
   collects below the last recognition row, not around it. */
.awards-recognition-card{ flex:1; display:flex; flex-direction:column; }

/* Milestone row — year + description side by side in a fixed/flexible
   grid, sized by its own text only (no min-height, no flex-stretch), so a
   one-line entry stays compact and only a genuinely two-line entry grows
   taller. Flips to year-above-description on mobile per client spec. */
.milestone-row{ display:grid; grid-template-columns:80px 1fr; gap:20px; align-items:start; padding:14px 20px; height:auto; min-height:0; }
.milestone-row__year{ font-family:var(--font-mono); font-weight:700; color:var(--brand); font-size:17px; }
.milestone-row__desc{ color:var(--text-muted); font-size:13.5px; line-height:1.6; margin:0; }
@media (max-width:600px){
  .milestone-row{ grid-template-columns:1fr; gap:4px; padding:12px 16px; }
}

/* Responsive video embed (YouTube/Vimeo iframes reproduced from live
   cadopt.com product pages — PTC IoT, PTC Augmented Reality, Graebert).
   aspect-ratio keeps it correctly proportioned at any width instead of a
   fixed pixel height that either crops or letterboxes on other viewports;
   overflow:hidden + the site's own --radius-lg is what rounds the video's
   corners without touching the iframe's own rendering. No autoplay is set
   on any embed using this — every iframe below keeps the source's default
   click-to-play behaviour. */
.video-embed{ position:relative; width:100%; aspect-ratio:16/9; border-radius:var(--radius-lg); overflow:hidden; background:var(--surface-sunken); box-shadow:var(--shadow-md); }
.video-embed iframe{ position:absolute; inset:0; width:100%; height:100%; border:0; }

/* Fills the full container width instead of the global p{max-width:var(--measure)}
   reading-measure cap — used where a lead paragraph should read as a wide
   intro rather than a narrow article column (e.g. Staffing Solutions). */
.lead--full{ max-width:none; }

/* Feature list — plain paragraphs (e.g. About Us intro), flush-aligned with
   the section heading above them and filling the full container width —
   base.css sets a global p{ max-width:var(--measure) } (68ch) for reading
   comfort on body copy, which silently capped this to a narrow column even
   inside a full-width container; overridden here since this block is meant
   to read as a wide intro, not a narrow article measure. */
.feature-list{ display:grid; gap:var(--space-4); }
.feature-list p{ margin:0; max-width:none; font-size:17px; line-height:1.7; color:var(--text-body); }
@media (max-width:600px){
  .feature-list p{ font-size:15px; line-height:1.65; }
}

/* ---- Spec table (3D Scanning datasheet) ---- */
.spec-table{ width:100%; border-collapse:collapse; font-size:13.5px; }
.spec-table th, .spec-table td{ padding:12px 20px; text-align:left; border-bottom:1px solid var(--border-subtle); }
.spec-table th{ color:var(--text-strong); font-weight:700; background:var(--surface-sunken); }
.spec-table td:first-child{ color:var(--text-strong); font-weight:600; white-space:nowrap; }
.spec-table td:last-child{ color:var(--text-muted); }
.spec-table tr:last-child td{ border-bottom:none; }
@media (max-width:600px){
  .spec-table{ font-size:12.5px; }
  .spec-table th, .spec-table td{ padding:10px 14px; }
}

/* ---- Grid utilities ---- */
.grid{ display:grid; gap:var(--space-6); }
.grid--2{ grid-template-columns:repeat(2,1fr); }
.grid--3{ grid-template-columns:repeat(3,1fr); }
.grid--4{ grid-template-columns:repeat(4,1fr); }
@media (max-width:900px){ .grid--3, .grid--4{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:600px){ .grid--2, .grid--3, .grid--4{ grid-template-columns:1fr; } }

/* Industries page: text+image sections alternate which side the image
   sits on for visual rhythm (inline `order` per section, see
   page-industries.php) — once .grid--2 collapses to one column on
   mobile, that same alternation would flip half the sections to
   image-before-heading, which reads oddly stacked. Forcing text first
   regardless of the desktop side keeps the reading order the same on
   every section. */
@media (max-width:600px){
  .industry-block__text{ order:1; }
  .industry-block__media{ order:2; }
  .industry-block__media img{ height:220px; }
}
