/* AeroIQ base layer — element defaults + accessibility.
   CONSUMES SEMANTIC ROLE TOKENS ONLY (design-token contract): no raw design
   values, and no wrong-family primitives, may appear in this file. */

/* Headings: the heading family (Hanken Grotesk 700 — round-1 D) is projected via
   theme.json styles.elements; tracking tightened here because theme.json
   letterSpacing is disabled as an editor control. */
h1, h2, h3, h4, h5, h6 {
  letter-spacing: var(--aq-track-tight);
  line-height: var(--aq-lh-heading);
}

/* The restricted Geist Mono usage (ADR-0005): eyebrow/kicker + technical labels ONLY. */
.aq-eyebrow {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-eyebrow); /* muted mono on light; dark bands override below */
}

/* On-light muted body text (hero + section ledes) — the Figma greys #52545a/#5a5c5e. */
.aq-muted {
  color: var(--aq-text-muted);
}

/* Prose measure utility — caps line length independent of the section width. */
.aq-measure {
  max-width: var(--aq-measure);
}

/* Explicit hero/display role — NOT every h1 (ordinary h1 = --aq-fs-h1 via theme.json). */
.aq-display {
  font-size: var(--aq-fs-display);
  line-height: var(--aq-lh-tight);
}

/* Links inherit the CONTEXTUAL foreground (contrast on light, white on dark,
   on-brand on orange) instead of the UA default blue. */
a {
  color: inherit;
}

/* The WCAG 1.4.1 non-colour cue applies to INLINE CONTENT links — not to
   navigation, site title, post titles, or button anchors. */
:where(.wp-block-post-content, .aq-rich-text) :where(p, li, blockquote, figcaption) a:not(.wp-element-button) {
  text-decoration-line: underline;
  text-decoration-thickness: var(--aq-link-underline-thickness);
  text-underline-offset: var(--aq-link-underline-offset);
}

/* Controlled inline-link treatment for LIGHT content surfaces only (ADR-0005:
   brand-ink is validated on light). Opt-in via class — never global. */
.aq-link {
  color: var(--aq-content-link-color);
  text-decoration-line: underline;
  text-decoration-thickness: var(--aq-link-underline-thickness);
  text-underline-offset: var(--aq-link-underline-offset);
}
.aq-link:hover,
.aq-link:focus-visible {
  text-decoration-thickness: var(--aq-link-underline-thickness-active);
}

/* ---------- components: header + hero (milestone 1) — tokens only ---------- */

/* THE spacing model (one method, everywhere):
   · between sections — each section parent gets padding-top: the "section"
     preset (--aq-space-section); no bottom padding, no margins. Every gap in
     the page rhythm is therefore identical by construction.
   · inside a section — .aq-stack (flex column + gap); children carry ZERO
     top/bottom margins. No WP default margins, no ad-hoc paddings. */
.aq-stack {
  display: flex;
  flex-direction: column;
  /* sm — owner-set 2026-08-10 (was lg 32px, then md 24px; owner picked the
     16px tier after reviewing both in-browser). */
  gap: var(--aq-space-sm);
}
/* Doubled class outranks WP's layout margin rules regardless of source order —
   gap is the ONLY spacing mechanism inside a stack. */
.aq-stack.aq-stack > * {
  margin-block-start: 0;
  margin-block-end: 0;
}

/* Section rhythm (owner sign-off round 2, 2026-07-26): exactly ONE level per
   section owns BOTH its background (light = none, tint/dark via .aq-band-*) AND
   its symmetric vertical padding. No inline per-section padding, no double
   band wrappers. Every gap between sections is `2 × section` by construction. */
.aq-section {
  padding-block: var(--aq-space-section);
}
/* Same-surface adjacency: when two consecutive sections share a background (no
   colour switch, no divider between them), the boundary should read as ONE
   section of space, not two — so the FOLLOWING section drops its top padding
   (the previous section's bottom padding already owns the gap). Colour-CHANGE
   boundaries keep the full 2×section breathing room. Auto-applies to any order:
   light↔light, tint↔tint, dark↔dark. (This is what the Figma does too — its
   Services section sits at padding-top:0 below the white hero.) */
.aq-section:not([class*="aq-band"]) + .aq-section:not([class*="aq-band"]),
.aq-band-tint + .aq-band-tint,
.aq-band-dark + .aq-band-dark {
  padding-top: 0;
}
/* ---------- SPACING UTILITIES (owner-specified 2026-09-27) ----------

   THE ONLY utilities in this stylesheet, and they exist because the owner asked
   for them by name for hand-editing. Before today there were none, deliberately:
   the same-surface adjacency rule handles every ORDINARY boundary with no class,
   and a section's closing side is meant to be invariant so that adding or
   removing a trailing block cannot change its outer geometry. These are OPT-IN,
   so a page that does not ask for one keeps that invariant.

   Applied through the block's Advanced -> Additional CSS class.

   `!important` on all six, which is the owner's reasoning and it is right: a
   class named for exactly one job must do that job wherever it is used, or it
   is a trap. The first version of `.aq-pb-0` was a doubled class without it; it
   beat `.aq-section` but lost to an inline `style="padding-bottom"`, which is
   precisely what the editor's padding control writes — so any block with a
   padding value set would have taken the class and silently ignored it.
   Applying the class IS the later, more specific instruction.

   `aq-` prefixed because an unprefixed utility can collide with core or plugin
   CSS, and because every class in this theme carries the prefix.

   The `-spacing` pair restores the SECTION rhythm, `--aq-space-section`
   (40px at 390, 80px at 1440) — the same token `.aq-section` itself uses, so
   "spacing" means the site's one spacing step and never an invented value.

   `scripts/qa/section-rhythm.js` HONOURS these: a band carrying one is reported
   as an intentional exception rather than a violation, so using a sanctioned
   utility can never be flagged as the defect it exists to fix. */
.aq-pb-0 {
  padding-block-end: 0 !important;
}
.aq-pt-0 {
  padding-block-start: 0 !important;
}
.aq-mb-0 {
  margin-block-end: 0 !important;
}
.aq-mt-0 {
  margin-block-start: 0 !important;
}
.aq-pb-spacing {
  padding-block-end: var(--aq-space-section) !important;
}
.aq-pt-spacing {
  padding-block-start: var(--aq-space-section) !important;
}
/* Kill WordPress's default block-gap top margins — GLOBAL, all pages. Our
   spacing is 100% deliberate (section padding + stack/grid gaps + component
   padding); WP block-gap is never our mechanism. WP applies a block-gap
   `margin-block-start` (24px fallback) to the children of every layout container
   (`.is-layout-flow` / `.is-layout-constrained`), and every Gutenberg group
   carries one of those classes — so zeroing it on those two classes covers every
   container on every page (post-content, bands, footer, header/main, and any
   future group), with no per-container patching. Doubled class = 0,2,0, which
   beats WP's generated `.wp-container-… > * + *` (0,1,0) regardless of source
   order. `.wp-site-blocks` (template root) is listed explicitly — it has no
   layout class. If prose paragraph spacing is ever needed (e.g. a blog body),
   add it deliberately via a prose utility — never re-enable WP block-gap. */
.wp-site-blocks.wp-site-blocks > *,
.is-layout-flow.is-layout-flow > *,
.is-layout-constrained.is-layout-constrained > * {
  margin-block-start: 0;
}

/* Eyebrow with the orange square marker. */
.aq-eyebrow--dot::before {
  content: "";
  display: inline-block;
  width: var(--aq-space-2xs);
  height: var(--aq-space-2xs);
  margin-right: var(--aq-space-2xs);
  background: var(--aq-brand);
}

/* Display headings cap at a hero-appropriate width. */
.aq-max-heading {
  max-width: var(--aq-heading-max);
}

/* Split row (hero lede+CTA · section head): content left, action bottom-right,
   wraps on small screens. */
.aq-hero-row,
.aq-sec-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--aq-space-lg);
}

/* Inner-page hero (Service detail + Services hub): TWO columns — copy left, image
   right — matching the Figma design (26:8), gap 4rem. This is deliberately NOT the
   homepage hero (which stacks copy above image). Collapses to one column below 64em. */
/* Editorial split (owner model, measured 2026-08-28).
   Tracks: copy takes the REMAINDER, media has a preferred share that can shrink
   and a hard ceiling — so on wide screens every extra pixel goes to the copy
   instead of inflating a supporting photograph (media ran to 718px at the shell).
   `minmax(0, 1fr)` on the copy track, not `1fr`, so a long word or button row
   can never force grid overflow. Gap is the split gutter, not the card gutter. */
/* MEDIA LEADS ON THE LEFT (owner 2026-08-28). The image was on the right by
   default with `--flip` as the alternating exception; those roles are now
   swapped, so most sections read image-first and `--flip` is the image-right
   exception that keeps the alternation available.
   DOM order stays copy-then-media (text first for assistive tech and for the
   stacked layout); `order` moves the media into the first cell. */
/* THE COPY COLUMN STOPS AT THE READING MEASURE; THE MEDIA TAKES THE SURPLUS
   (2026-09-26, owner).

   The media track was capped at 512px and the copy track took everything else,
   so on a wide screen the copy column kept growing while the prose inside it
   stayed at `--aq-measure`. On "Where Dock 3 earns its place" at a 1536px shell
   that was a 992px column holding a 672px paragraph — 320px of dead space
   INSIDE the text column, before the 32px gap, which read as a broken layout.
   It also made the eyebrow (992), heading (768 cap) and checklist (992) each
   stop at a different place, so the right edge jumped on every line.

   The fix is the relationship, not the gap: the media track is now at least
   whatever is left once the copy has reached its measure. Below a 1216px shell
   `min(45%, …)` still wins and the layout is byte-for-byte what it was; above
   it the copy holds at the measure and the image grows into the space it was
   wasting — 512 -> 832px at 1536.

   `--aq-measure` itself is NOT touched. A short lede (the Home hero intro, the
   hub intro) is right to be narrow; the constraint was only wrong where a
   column much wider than the measure was allocated to long-form body copy. */
.aq-hero-split {
  display: grid;
  grid-template-columns:
    minmax(0, max(min(45%, var(--aq-split-media-max)), calc(100% - var(--aq-measure) - var(--aq-space-lg))))
    minmax(0, 1fr);
  gap: var(--aq-space-lg);
  align-items: stretch;
}
.aq-hero-split .aq-hero-media {
  order: -1;
}
/* The copy column is STRETCHED by the row, so this centres the text against a
   taller image instead of pinning it to the top. It only works while
   `align-items: stretch` holds — replacing that with `start` silently disabled
   it (2026-08-28) and the text sat top for a day.

   Matched on POSITION, not on a class: heroes carry `.aq-hero-copy`, but the
   media-split BLOCK emits its copy column as a bare `.aq-stack`, so a
   class-based rule centred the heroes and left every block-built section
   top-aligned (owner 2026-08-28). Whichever child is not the media is the copy.
   Both are flex columns, so `justify-content` is the right axis. */
.aq-hero-copy,
.aq-hero-split > :not(.aq-hero-media) {
  justify-content: center;
}
/* Image RIGHT — the alternating exception. The capped track moves with it;
   reordering alone would leave the ceiling on the wrong column. */
.aq-hero-split--flip {
  grid-template-columns:
    minmax(0, 1fr)
    minmax(0, max(min(45%, var(--aq-split-media-max)), calc(100% - var(--aq-measure) - var(--aq-space-lg))));
}
.aq-hero-split--flip .aq-hero-media {
  order: 0;
}
@media (max-width: 47.99em) {
  /* Stacked: text first, image after — for BOTH variants. */
  .aq-hero-split .aq-hero-media,
  .aq-hero-split--flip .aq-hero-media {
    order: 0;
  }
}
/* Hero subtitle (Figma deck — a heading variable at fs-h3): dark, sits between the
   display title and the muted body lede. */
.aq-hero-subtitle {
  font-family: var(--aq-font-heading);
  font-weight: var(--aq-fw-bold); /* explicit: Sora shipped only a 600 face so the browser matched it regardless; Hanken has a real 400 face, so an unset weight would silently drop to body weight */
  font-size: var(--aq-fs-h3);
  line-height: var(--aq-lh-heading);
  color: var(--aq-text);
}
/* Product spotlight (Figma 31:45): media panel LEFT (narrower ~5fr), text panel RIGHT
   (~7fr). Reuses the .aq-hero-media cover behaviour below; stacks on phones. */
/* Spotlight keeps its MEDIA-FORWARD tracks (7fr media) — that asymmetry is the
   composition, not an oversight, so the editorial cap deliberately does not
   apply here. It does share the split gutter. */
.aq-spotlight {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--aq-space-lg);
  align-items: stretch;
}
.aq-spotlight-body {
  justify-content: center;
}

/* Desktop: the image column matches the text/copy column's height — the media fills its
   grid cell and the image cover-crops (the text column is the only intrinsic-height one,
   so it sets the row height). Shared by the hero split and the product spotlight. */
.aq-hero-split .aq-hero-media,
.aq-hero-split .aq-hero-media figure,
.aq-spotlight .aq-hero-media,
.aq-spotlight .aq-hero-media figure {
  height: 100%;
  margin: 0;
}
.aq-hero-split .aq-hero-media img,
.aq-spotlight .aq-hero-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* THE IMAGE FOLLOWS THE COPY'S HEIGHT — never shorter than the text beside it
   (owner 2026-08-28). The row is stretched, the media fills its cell, and the
   image cover-crops to whatever height results. A PRESENCE FLOOR stops thin
   copy producing a shallow strip; the image grows past it whenever the copy is
   taller.

   RESTORED, not new: this is what the split always did. It was replaced with a
   fixed `aspect-ratio` panel after an A/B that narrowed the media column while
   KEEPING this height rule, then read the resulting crop as this rule's fault.
   The test was wrong, not the rule — and removing it also silently killed the
   vertical centring above, because a `start`-aligned copy column has no spare
   height to centre within.

   The crop cost is real and accepted: a long copy column makes a tall box out of
   a wide photograph. That is a SOURCE problem (feed these sections images that
   survive a portrait crop), not a reason to stop the image tracking its
   partner's height.

   `--sticky` is excluded: the team portrait is a deliberate 1/1 capped treatment
   with its own scroll behaviour. Below 48em nothing here applies — the split
   stacks and the image returns to its natural ratio. */
@media (min-width: 48em) {
  .aq-hero-split:not(.aq-hero-split--sticky) .aq-hero-media,
  .aq-hero-split:not(.aq-hero-split--sticky) .aq-hero-media figure {
    height: 100%;
    min-height: var(--aq-media-split-min);
  }
  .aq-hero-split:not(.aq-hero-split--sticky) .aq-hero-media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

/* Sticky media variant (opt-in via the block's "Sticky media" toggle) — long-form
   sections such as the About team profiles. The image holds in view while the copy
   column scrolls, releasing when the section ends (sticky is bounded by its
   containing block, so the stop needs no JS).
   MUST override two things above: `align-items: stretch` and the media `height: 100%`,
   both of which leave the element no slack to travel in. Gated on viewport HEIGHT too —
   below that the portrait can't be fully visible while stuck, so it degrades to the
   static layout rather than cropping a face. Width is capped because the media column
   is fluid (~718px at the shell), which would otherwise re-create that same problem. */
@media (min-width: 64em) and (min-height: 640px) {
  /* THE STICKY PORTRAIT NEEDS THE ROW CAPPED, NOT THE TRACK GROWN (2026-09-26).
     Its media is hard-capped at `--aq-split-media-max` and justified to the
     column edge, so letting the media TRACK grow past that cap — as the base
     rule above now does — would park the portrait away from the copy with the
     surplus sitting between them. That is the same dead strip, just moved.
     So cap the ROW: copy at its measure, media at its ceiling, and the leftover
     goes to the page margins instead of inside the composition. */
  .aq-hero-split--sticky {
    align-items: start;
    grid-template-columns: min(45%, var(--aq-split-media-max)) minmax(0, 1fr);
    /* `width: 100%` is load-bearing, not decoration. These rows sit inside an
       `.aq-stack`, which is a COLUMN flex container, and an auto inline margin
       on a flex child cancels `align-items: stretch` — the row collapsed to its
       content, 704px inside a 1536px parent, before this was added. */
    width: 100%;
    /* `--aq-space-2xl` because this block is inside the 64em query, where the
       column gutter is the wide one — the row cap has to add the gutter the
       grid actually draws or the portrait stops sitting beside its copy. */
    max-width: calc(var(--aq-measure) + var(--aq-space-2xl) + var(--aq-split-media-max));
    margin-inline: auto;
  }
  .aq-hero-split--sticky.aq-hero-split--flip {
    grid-template-columns: minmax(0, 1fr) min(45%, var(--aq-split-media-max));
  }
  .aq-hero-split--sticky .aq-hero-media {
    position: sticky;
    top: var(--aq-space-xl);
    height: auto;
    width: 100%;
    max-width: var(--aq-split-media-max);
    aspect-ratio: 1 / 1;
    justify-self: end;
  }
  .aq-hero-split--sticky.aq-hero-split--flip .aq-hero-media {
    justify-self: start;
  }
  .aq-hero-split--sticky .aq-hero-media figure {
    height: 100%;
  }
}
/* STACK POINT — 48em (768px) for ordinary splits, owner-directed 2026-08-28:
   1024px was early to collapse two columns that still read well at tablet size.
   Measured through the new band: copy 349px (~44ch) at 768 rising to 481px
   (~60ch) at 1023, no content overflowing its column at any step.

   TWO EXCEPTIONS keep the old 64em stack point, and both are about column width
   rather than taste: `--sticky` is a 1:1 team portrait that would be cropped
   into a 16/10 panel beside a 349px column, and `.aq-spotlight` is
   media-forward (7fr), which would leave its copy at ~280px — below a readable
   measure. They collapse in the block after this one. */
@media (max-width: 47.99em) {
  .aq-hero-split {
    grid-template-columns: minmax(0, 1fr);
  }
  /* Stacked — the image reverts to its natural wide ratio. */
  .aq-hero-split .aq-hero-media,
  .aq-hero-split .aq-hero-media figure {
    height: auto;
  }
  .aq-hero-split .aq-hero-media img {
    position: static;
    height: auto;
  }
  /* OVERLAY heroes are the exception to the natural-ratio rule above. Their tag
     is pinned top-left and the lifecycle strip bottom, and a 2.4:1 source at
     phone width is only ~150px tall — so the two collided on every service hero
     (pre-existing; found 2026-08-28). The panel takes the same shape it has on
     desktop, which gives the overlay room instead of hiding or shrinking it.
     Must follow the rule above: same specificity, so order decides. */
  .aq-hero-split .aq-hero-media--overlay {
    aspect-ratio: var(--aq-split-media-ratio);
  }
  .aq-hero-split .aq-hero-media--overlay img {
    position: absolute;
    inset: 0;
    height: 100%;
    object-fit: cover;
  }
}

/* THE WIDE GUTTER, SIDE BY SIDE ONLY (owner, 2026-09-26).

   A full-width text+media split gets 64px between its columns from 64em up.
   `.aq-pathway` has used that gutter since it was built, while the three splits
   beside it used 32px — the same component grammar with two different gutters,
   which is the inconsistency this removes.

   `column-gap`, not `gap`: the shorthand governs BOTH axes, so raising it would
   also open up the vertical space between text and image once these stack, and
   the stacked and 768-1023 bands are deliberately unchanged. Cards are out of
   scope — this is the text-beside-media gutter, not a grid rhythm. */
@media (min-width: 64em) {
  /* `.aq-featureset-split` is NOT in this list, though it takes the same
     gutter: it is declared 4,400 lines below with a `gap` shorthand, which
     resets `column-gap` and wins on order at equal specificity. Its copy of
     this rule lives beside its own declaration instead — the only place it can
     work (2026-09-26, caught by the owner reading the rendered gutter). */
  .aq-hero-split,
  .aq-spotlight {
    column-gap: var(--aq-space-2xl);
  }
  /* THE TRACKS ARE RESTATED, NOT PARAMETERISED. The media track is worked out
     from what is left once the copy reaches its measure, so it must subtract
     the gutter the grid actually draws — and that gutter changes here. A local
     `--aq-split-gap` would have said it once, but `validate-theme` treats every
     `--aq-*` in this sheet as a design token that must live in tokens.css, and
     a component's internal plumbing is not one. Restating the track is the
     honest trade: two lines that must move together, with this note saying so,
     rather than a token that is not a token. */
  .aq-hero-split {
    grid-template-columns:
      minmax(0, max(min(45%, var(--aq-split-media-max)), calc(100% - var(--aq-measure) - var(--aq-space-2xl))))
      minmax(0, 1fr);
  }
  .aq-hero-split--flip {
    grid-template-columns:
      minmax(0, 1fr)
      minmax(0, max(min(45%, var(--aq-split-media-max)), calc(100% - var(--aq-measure) - var(--aq-space-2xl))));
  }

  /* A HERO IS NOT A CONTENT SECTION (owner, 2026-09-26).

     The rule above stops the copy column growing past the reading measure and
     hands the surplus to the image — right for a section of long-form body
     copy, which is what it was written for. A hero is the opposite shape: a
     display heading and two or three lines of lede, where the copy wants the
     room and the image is support. Applied to both, it put every page hero at
     copy 672 / media 800 — the image taking more than half, which is what the
     owner spotted.

     Heroes therefore keep the proportions they had before that rule: the image
     capped at `--aq-split-media-max`, never more than 45%, and the copy taking
     whatever is left. At a 1536px shell that is 512 / 960 instead of 800 / 672.

     MATCHED ON `h1`, because there is no hero class to match on — the four page
     types build their heroes from four different blocks, and two of them carry
     no modifier at all. The split holding the page's `h1` IS the hero; nothing
     else on a page has one. `:has()` is already load-bearing here (the
     media-less switcher collapse, six other rules), so this adds no new
     baseline requirement. Below 64em the base rule already resolves to these
     same proportions, so this only needs to exist here. */
  .aq-hero-split:has(h1) {
    grid-template-columns: min(45%, var(--aq-split-media-max)) minmax(0, 1fr);
  }
  .aq-hero-split--flip:has(h1) {
    grid-template-columns: minmax(0, 1fr) min(45%, var(--aq-split-media-max));
  }
}

/* The two exceptions named above keep collapsing at 64em: a 1:1 team portrait
   and a 7fr media column both need more width than the tablet band offers. */
@media (max-width: 63.99em) {
  .aq-hero-split--sticky,
  .aq-spotlight {
    grid-template-columns: minmax(0, 1fr);
  }
  /* TEXT FIRST once stacked — the reset must fire wherever the STACK happens,
     and these two stack at 64em while ordinary splits stack at 48em. The
     order-reset lived only in the 48em block, so between 768 and 1023px a
     stacked default profile rendered image-above-text while the flipped one
     rendered text-above-image: the same page, two orders (found 2026-08-29).
     The bug arrived with the image-left default, AFTER the 768/900 validation
     that would have caught it. */
  .aq-hero-split--sticky .aq-hero-media,
  .aq-spotlight .aq-hero-media {
    order: 0;
  }
  .aq-hero-split--sticky .aq-hero-media,
  .aq-hero-split--sticky .aq-hero-media figure,
  .aq-spotlight .aq-hero-media,
  .aq-spotlight .aq-hero-media figure {
    height: auto;
  }
  .aq-hero-split--sticky .aq-hero-media img,
  .aq-spotlight .aq-hero-media img {
    position: static;
    height: auto;
  }
}

/* Hero image overlay (Figma 77:657): top-left service tag + a bottom lifecycle strip
   (Design→…→Independence, current stage in brand), over a bottom scrim so the
   white/orange text stays legible on any photo. */
.aq-hero-media--overlay::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--aq-scrim-photo);
  pointer-events: none;
  z-index: 1;
}
.aq-hero-tag {
  position: absolute;
  z-index: 2;
  top: var(--aq-space-md);
  left: var(--aq-space-md);
  margin: 0;
  color: var(--aq-text-on-dark);
}
.aq-hero-life {
  position: absolute;
  z-index: 2;
  left: var(--aq-space-md);
  right: var(--aq-space-md);
  bottom: var(--aq-space-md);
}
.aq-hero-life-label {
  margin: 0 0 var(--aq-space-2xs);
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-on-dark-muted);
}
.aq-hero-life-stages {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--aq-space-2xs);
  color: var(--aq-text-on-dark);
  font-weight: var(--aq-fw-semibold);
}
.aq-lifestrip-stage.is-current {
  color: var(--aq-brand);
}
.aq-lifestrip-sep {
  color: var(--aq-text-on-dark-muted);
}

/* Hero media panel: rounded image with overlay chips + capability card. */
.aq-hero-media {
  position: relative;
  border-radius: var(--aq-radius-lg);
  overflow: hidden;
}
.aq-hero-media img {
  display: block;
  width: 100%;
  height: auto;
}
.aq-chip {
  position: absolute;
  top: var(--aq-space-md);
  margin: 0;
  padding: var(--aq-space-2xs) var(--aq-space-xs); /* 8px/12px — clean-snap of Figma 9px/14px (was 4/16, wrong ratio) */
  border-radius: var(--aq-radius-pill);
  background: var(--aq-scrim-ink);
  color: var(--aq-text-on-dark);
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
}
.aq-chip--coords {
  left: var(--aq-space-md);
  background: none;
  padding-left: 0;
  padding-right: 0;
}
.aq-chip--tag {
  right: var(--aq-space-md);
}
.aq-chip--tag::before {
  content: "";
  display: inline-block;
  width: var(--aq-space-2xs);
  height: var(--aq-space-2xs);
  margin-right: var(--aq-space-2xs);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-brand);
}
.aq-hero-card {
  position: absolute;
  left: var(--aq-space-md);
  bottom: var(--aq-space-md);
  max-width: var(--aq-card-w);
  padding: var(--aq-space-md);
  border-radius: var(--aq-radius-md);
  background: var(--aq-surface-card);
  color: var(--aq-text);
}
.aq-hero-card .aq-icon-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--aq-space-lg);
  height: var(--aq-space-lg);
  margin-right: var(--aq-space-2xs);
  border-radius: var(--aq-radius-sm);
  background: var(--aq-text);
  vertical-align: middle;
}
.aq-hero-card .aq-icon-tile::before {
  content: "";
  width: var(--aq-space-2xs);
  height: var(--aq-space-2xs);
  transform: rotate(45deg);
  background: var(--aq-brand);
}
.aq-hero-card-title {
  margin: 0;
  font-weight: var(--aq-fw-semibold);
}
.aq-hero-card p {
  margin-top: var(--aq-space-2xs);
  margin-bottom: 0;
  color: var(--aq-text-muted);
  font-size: var(--aq-fs-body-sm);
}
/* Phone: the absolute overlay card covers the shrunken hero image — let it flow
   statically below the image instead of on top of it.

   IT MUST THEN FILL THE SHELL, not sit inside it (owner, 2026-09-30). Flowing it
   statically was right; keeping a horizontal margin was not. The card landed 16px
   narrower than the media directly above it — aligned to nothing — while its own
   white `--aq-surface-card` fill sat on a white page, so the padding and radius
   it carries defined no area at all. The apparent inset read as an unfinished
   container, which is exactly what it was.

   `.aq-hero-media` is already `border-radius: var(--aq-radius-lg)` with
   `overflow: hidden`, and the two children ALREADY touch (the media ends where
   the card begins). So the shell rounds and clips the pair as a single card for
   free — the card needs no radius of its own, and its own 12px was fighting the
   clip it sits inside. A tint is what was missing: `--aq-surface-alt` is the
   role for a light tint over the white base, so the body of the card reads as a
   defined area instead of dissolving into the page.

   Desktop is untouched: above this breakpoint the card is still the absolutely
   positioned overlay on the image, where its own radius and card surface are
   correct. */
@media (max-width: 47.99em) {
  .aq-hero-card {
    position: static;
    max-width: none;
    margin: 0;
    border-radius: 0;
    background: var(--aq-surface-alt);
  }
}

/* Sticky frosted header (owner decision 2026-08-13): the bar pins to the
   viewport and frosts over the content scrolling beneath; separator stays the
   Figma hairline. The blur lives on a ::before pseudo — backdrop-filter on
   .aq-header ITSELF would make it the containing block for the two
   position:fixed dropdown bands (mega + Services), clipping them to the bar.
   Stacking: 50 = above page content, below the enquiry drawer (80); the fixed
   bands (40) are descendants of this context and keep painting above the page.
   `top` offsets by WP's admin-bar var when logged in. Anchor landings are
   compensated globally (scroll-padding-top) so the pinned bar cannot hide
   fragment targets (the no-JS D4 #hub-products contract). */
html {
  scroll-padding-top: calc(var(--aq-header-h) + var(--wp-admin--admin-bar--height, 0px) + var(--aq-space-2xs));
}
/* The pin lives on the template-part WRAPPER, not .aq-header: sticky travel is
   bounded by the parent, and .aq-header's parent (this wrapper) is exactly
   bar-height — zero room, the bar scrolled away (caught live). The wrapper's
   own parent is .wp-site-blocks = full page height. Tag-qualified so the
   footer's wrapper is untouched. */
header.wp-block-template-part {
  position: sticky;
  top: var(--wp-admin--admin-bar--height, 0px);
  z-index: 50;
}
.aq-header {
  position: relative; /* anchors the frost pseudo */
  border-bottom: var(--aq-bw-1) solid var(--aq-hairline-light);
}
.aq-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--aq-surface-glass);
  -webkit-backdrop-filter: blur(var(--aq-blur-glass));
  backdrop-filter: blur(var(--aq-blur-glass));
}
@supports not ((backdrop-filter: none) or (-webkit-backdrop-filter: none)) {
  .aq-header::before {
    background: var(--aq-surface-page);
  }
}

/* Header responsive. The wide 6-link nav + logo + full-label CTA only co-fit on
   desktop; below that the CTA wrapped below as a big floating button (and even the
   nav alone wraps ~600–680px). So below 64em we collapse the nav to its hamburger
   and show a COMPACT CTA ("Let's talk") grouped with the burger — one clean row at
   every width, and a visible tap-to-contact stays. WP's overlayMenu:"mobile" only
   collapses at 600px (too low for this nav width), so the nav breakpoint is raised
   here via a doubled-class override; the opened overlay (is-menu-open) is
   untouched. `.aq-cta-short` is the mobile label. Media-query widths are literal
   (CSS custom properties can't be read in @media conditions); this breakpoint
   exists only because the layout demonstrably fails (design-system §5). */
.aq-cta-short {
  display: none;
}
@media (max-width: 63.99em) { /* below ~1024px — nav can't sit inline with logo + CTA */
  /* Force the hamburger + hide the inline menu (raise WP's 600px breakpoint). */
  .aq-header.aq-header .wp-block-navigation__responsive-container:not(.is-menu-open) {
    display: none;
  }
  .aq-header.aq-header .wp-block-navigation__responsive-container-open:not(.always-shown) {
    display: flex;
    /* The interaction floor applies here too. Core ships this button as a bare
       24×24 icon with no padding, so the PRIMARY mobile navigation control was
       the smallest target on the page while `.wp-block-button__link` already
       met the project's 44px standard (full-site sweep 2026-08-28). The glyph
       is unchanged — only the hit area grows, centred on it. */
    align-items: center;
    justify-content: center;
    min-width: var(--aq-tap-min);
    min-height: var(--aq-tap-min);
  }
  /* THE HIT AREA IS DRAWN (owner, 2026-09-30). The two icon controls were bare
     24px glyphs centred in invisible 44px boxes, so each carried 10px of empty
     space nobody could see: the burger's BOX met the 16px gutter exactly like
     the logo, but its glyph sat at 26px and read as a wrong right margin, and
     the search icon did the same inside the cluster. The target cannot shrink,
     so the box is made visible: the HEADER'S OWN HAIRLINE (the bar's bottom
     border, owner's call) at the button radius. Not the ink outline of
     `.is-style-outline` — three dark boxes competed with the one filled CTA,
     which must stay the primary action. The hairline is the site's existing
     control-edge weight (`.aq-catfilters__select`), and WCAG 1.4.11 does not
     require the edge to reach 3:1 here: the ink glyph identifies the control.
     Desktop is untouched: the burger does not exist there, and the search icon
     sits among text nav links where a bare glyph is right. */
  .aq-header.aq-header .wp-block-navigation__responsive-container-open:not(.always-shown),
  .aq-header .aq-header-search-toggle {
    border: var(--aq-bw-1) solid var(--aq-hairline-light);
    border-radius: var(--aq-radius-md);
  }
  /* logo · (auto gap) · compact CTA · hamburger — never wraps. Scoped under
     .aq-header to outrank the base `.aq-header-row` rule that follows in source.
     ONE gap governs search · CTA · burger, even though the burger is a row child
     and the other two sit in the actions cluster: both gaps are 2xs. The
     cluster's own 2xs never applied — WP prints `:root :where(.is-layout-flex)
     {gap:24px}` at equal specificity AFTER this file, so search→CTA was 24px
     against 8/16px for CTA→burger. The doubled scope here is what makes it hold. */
  .aq-header .aq-header-row,
  .aq-header .aq-header-actions {
    flex-wrap: nowrap;
    gap: var(--aq-space-2xs);
  }
  .aq-cta-long {
    display: none;
  }
  .aq-cta-short {
    display: inline;
  }
  .aq-header .aq-header-actions {
    order: 2;
    margin-left: auto;
  }
  .aq-header .wp-block-navigation {
    order: 3;
  }
  .aq-header .wp-block-button__link {
    padding-inline: var(--aq-space-xs);
    white-space: nowrap; /* the compact label must never wrap beside the search icon */
  }
  .aq-header .aq-btn--arrow .wp-block-button__link::after {
    content: none; /* compact CTA drops the decorative arrow — width is the constraint here */
  }
}

/* Header row: logo · nav · CTA. Padding lives HERE (not template attrs) so the
   scroll-shrink state can own it: past the top of the page header.js sets
   .aq-header-shrunk on <html> and the bar drops to 3xs padding — least content
   covered, every tap target untouched (the CTA button is the height floor).
   No-JS: full-height bar. Reduced-motion: the global kill zeros the transition. */
.aq-header-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--aq-space-md);
  padding-block: var(--aq-space-sm);
  transition: padding var(--aq-motion-fast) var(--aq-ease-standard);
}
html.aq-header-shrunk .aq-header-row {
  padding-block: var(--aq-space-3xs);
}
/* Header logo: the THEME owns the display size (tokens) + the free axis,
   exactly like the footer rule. Never rely on core's `.wp-block-image` styles
   — block themes load those only on pages that render a real image block,
   which is how the logo shipped stretched (142×62) on the catalogue pages.
   The figure margin is zeroed here for the same reason. */
.aq-header-row figure {
  margin: 0;
  line-height: 0; /* no inline strut around the logo — the box is exactly the image */
}
/* The logo is a primary control, so its LINK carries the interaction floor even
   though the mark itself is short (21px tall on narrow phones after the header
   fix). Hit area only — `line-height: 0` still keeps the figure from adding
   descender space around the image. */
.aq-header-row figure a {
  display: flex;
  align-items: center;
  min-height: var(--aq-tap-min);
  line-height: 0;
}
.aq-header-row img {
  display: block;
  width: var(--aq-logo-w);
  height: auto;
}
/* NARROW PHONES (≤25em / 400px): the header row could not fit its four controls.
   Measured at 360px — logo 142 + search/CTA cluster 157 + burger 44 + gaps 32
   = 391px into a 328px row, so the BURGER rendered at x=347–391 and was clipped
   off-screen: the main navigation was unreachable on a common Android width.
   Pre-existing, found by the 2026-08-28 sweep. Freed by trimming the two
   largest discretionary items — the logo and the row gap — rather than dropping
   the search control or the CTA, which would cost a feature. (The row gap is
   now 2xs at every sub-64em width — the one-gap rule above — so only the logo
   still steps here.) */
@media (max-width: 25em) {
  .aq-header-row img {
    width: var(--aq-logo-w-narrow);
  }
}
/* SMALLEST PHONES (≤20em / 320px): even the trimmed row overruns by ~37px, and
   there is nothing left to shave without making the logo illegible. The header
   CTA gives way — deliberately, because an unreachable main menu is a worse
   failure than one missing shortcut: navigation and search both survive, and
   every page still carries its own CTAs in the content and the closing band. */
@media (max-width: 20em) {
  .aq-header .aq-header-actions .wp-block-buttons {
    display: none;
  }
}
/* Desktop (owner 2026-08-21): half the bar padding, and the LOGO becomes the
   height-definer — grown to fill the freed space so the bar stays 76px
   (8 + 60 + 8). Height-driven with width:auto keeps the true 283:62 ratio
   (≈274px wide); align-items:center on the row keeps nav + CTA vertically
   centred against it. Mobile keeps the width-driven 142px logo — a 274px
   mark cannot share a phone row with the icon, CTA and burger. */
@media (min-width: 64em) {
  .aq-header-row {
    padding-block: var(--aq-space-2xs);
  }
  .aq-header-row img {
    width: auto;
    height: var(--aq-logo-h);
  }
}
/* Tight desktop (1024–1179): two co-fits. (1) The SVG lockup's true ratio
   (633:125 ≈ 5.06) is wider than the retired raster at the same height — the
   compact logo step renders ≈202px. (2) PRE-EXISTING wrap found during the
   logo swap: since the search icon joined the actions cluster, the full CTA
   label + 6-item nav + logo no longer co-fit at 1024 (row wrapped to ~129px —
   reproduced on staging with the OLD raster). The compact CTA label (the
   proven sub-64em mechanism) frees the difference; the full nav stays inline
   and the 44px CTA keeps defining the 61px bar. */
@media (min-width: 64em) and (max-width: 73.6875em) {
  .aq-header-row img {
    height: var(--aq-logo-h-compact);
  }
  .aq-cta-long {
    display: none;
  }
  .aq-cta-short {
    display: inline;
  }
}

/* Header ACTIONS cluster (owner refinement 2026-08-21): search icon + CTA
   grouped as ONE flex child of the row — the icon never floats in the
   space-between gap, and future icons (cart, account) join this group. */
.aq-header-actions {
  display: flex;
  align-items: center;
  gap: var(--aq-space-2xs);
}

/* Header search (owner-directed 2026-08-21): icon grouped with the CTA in
   .aq-header-actions; expands to an overlay COVERING the header (absolute
   against the sticky .aq-header); big labelled Close. No-JS: the icon is a
   real /products/ link — the always-visible form lives there. */
.aq-header-search-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--aq-tap-min);
  min-height: var(--aq-tap-min);
  padding: 0;
  border: 0;
  background: none;
  color: var(--aq-text);
  cursor: pointer;
  border-radius: var(--aq-radius-md);
}
.aq-header-search-toggle:hover,
.aq-header-search-toggle:focus-visible {
  color: var(--aq-content-link-color);
}
.aq-header-search-toggle svg {
  width: var(--aq-space-md);
  height: var(--aq-space-md);
}
.aq-header-search[hidden] {
  display: none;
}
.aq-header-search {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  background: var(--aq-surface-page);
}
.aq-header-search__inner {
  display: flex;
  align-items: center;
  gap: var(--aq-space-sm);
  width: 100%;
  padding-inline: var(--aq-gutter);
}
.aq-header-search__inner .aq-catsearch {
  flex: 1;
  min-width: 0;
}
/* One-line bar: the shared form's visible label goes screen-reader-only HERE
   only (the input keeps its accessible name). Same 1px-primitive recipe as
   .screen-reader-text — a11y mechanism, not a design value. */
.aq-header-search__inner .aq-catsearch__label {
  position: absolute;
  width: var(--aq-bw-1);
  height: var(--aq-bw-1);
  margin: calc(var(--aq-bw-1) * -1);
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.aq-header-search__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--aq-tap-min);
  padding-inline: var(--aq-space-md);
  border: var(--aq-bw-2) solid var(--aq-text);
  border-radius: var(--aq-radius-md);
  background: var(--aq-surface-page);
  color: var(--aq-text);
  font-size: var(--aq-fs-body);
  font-weight: var(--aq-fw-semibold);
  cursor: pointer;
  flex: 0 0 auto;
}
.aq-header-search__close:hover,
.aq-header-search__close:focus-visible {
  background: var(--aq-text);
  color: var(--aq-surface-page);
}
.aq-header-search__close-icon {
  display: none;
}
/* PHONES AND TABLETS: ONE ROW, INSIDE THE BAR (owner, 2026-09-30, option A).
   Below ~600px the shared form wrapped input over button inside a fixed 76px
   bar, so the input rose 10px off-screen and the button hung past the hairline
   into the page. Three causes, three fixes:
   1. The overlay already sits one gutter in (it is inset against the header's
      padded row), and the inner added a second gutter — the input began at
      32px against the logo's 16. The inner's padding goes.
   2. The form row may not wrap here; the input is the flexible part.
   3. The big labelled Close costs ~113px a phone cannot spare. It becomes the
      burger's own control — the same ✕ as the menu overlay, in the same 44px
      hairline box, in the same place — and "Close" stays its accessible name.
   Desktop keeps the labelled button. */
@media (max-width: 63.99em) {
  .aq-header-search__inner {
    gap: var(--aq-space-2xs);
    padding-inline: 0;
  }
  .aq-header-search__inner .aq-catsearch__row {
    flex-wrap: nowrap;
  }
  .aq-header-search__close {
    min-width: var(--aq-tap-min);
    padding: 0;
    border: var(--aq-bw-1) solid var(--aq-hairline-light);
  }
  .aq-header-search__close-label {
    position: absolute;
    width: var(--aq-bw-1);
    height: var(--aq-bw-1);
    margin: calc(var(--aq-bw-1) * -1);
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .aq-header-search__close-x {
    display: none;
  }
  .aq-header-search__close-icon {
    display: block;
    width: var(--aq-space-md);
    height: var(--aq-space-md);
  }
}

/* ---------- components: services grid (milestone 2) — tokens only ---------- */

/* Section H2 wraps at the section-heading measure. */
.aq-max-h2 {
  max-width: var(--aq-h2-max);
}

/* Section-head action link: contextual ink with the brand underline as the
   non-colour-plus-accent cue (orange never carries the text itself on light). */
.aq-sec-link {
  font-size: var(--aq-fs-body-sm);
  font-weight: var(--aq-fw-medium);
}
/* These are STANDALONE action links, not links inside a sentence, so WCAG
   2.5.8's inline exception does not cover them — they were 18px tall against a
   24px AA minimum (full-site sweep 2026-08-28). `inline-flex` is what lets a
   min-height apply to an inline element at all.

   ROUND-2 SOURCE ¶19 — the client asked for these to carry real prominence:
   "secondary link should be more prominent, at the moment its only text with no
   button like border or background … use the border and transparent background
   for all those links that are not inline text but are also not primary button
   but are on white background", naming "See the DJI Dock 3 in Products →" on
   Remote Operations.

   The site already had that treatment: `.is-style-outline` is border + a
   transparent fill, the SECONDARY tier between the brand button and a prose
   link. So these adopt it rather than gaining a second bordered look that
   merely resembles it — one secondary-action language on the site.

   WHY THE SELECTOR IS `a` AND NOT THE CLASS. `.aq-sec-link` carries three
   different populations: 9 standalone links (this requirement), 13 card
   affordances, and 1 mega-panel link. Styling the class would have put borders
   around every card's "View pathway →". The card CTAs render as `<span>`,
   because the whole card is the anchor — so restricting to a real `<a>` selects
   exactly the standalone tier. Verified across 15 surfaces: ZERO anchors inside
   cards.

   The target now rises to the full 44px button standard: it looks like a
   button, so it answers like one. The earlier 24px note applied when this rule
   still reached card CTAs; it no longer does. */
.aq-sec-link a {
  display: inline-flex;
  align-items: center;
  gap: var(--aq-space-3xs);
  min-height: var(--aq-tap-min);
  padding-inline: var(--aq-space-sm);
  background: transparent;
  border: var(--aq-bw-1) solid var(--aq-text);
  border-radius: var(--aq-radius-md);
  color: var(--aq-text);
  transition: transform var(--aq-motion-fast) var(--aq-ease-standard),
              box-shadow var(--aq-motion-fast) var(--aq-ease-standard);
}
/* The lift, not a background swap: these sit on white AND on tint bands, and a
   surface-alt hover is literally invisible on a surface-alt band. The lift is
   the hover language the site's buttons already use, so it reads the same and
   works on any surface. */
.aq-sec-link a:hover {
  transform: translateY(calc(var(--aq-lift) * -1));
  box-shadow: var(--aq-shadow-card);
}
.aq-sec-link a:active {
  transform: translateY(0);
}
/* The mega panel's browse link is navigation chrome inside an already-framed
   panel, not a section action after copy. A border there would box a menu item. */
.aq-mega-browse a {
  min-height: var(--aq-tap-aa-min);
  padding-inline: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
}
/* These are CTA/UI links, not prose — opt them out of the content-link underline
   (the base :where() rule). The featured-card .aq-link keeps its own underline
   (brand-ink non-colour cue), so exclude it. */
.aq-sec-link a:not(.aq-link) {
  text-decoration: none;
}
/* Card CTAs ("Enquire →" / "View pathway →") are PLAIN ink + arrow in the design
   — no underline. The section-head link is the only one that carries the orange
   rule (below). The featured-card CTA keeps its own .aq-link (brand-ink + underline,
   the ADR-0005 non-colour cue for orange-on-light). */

/* Section-head link ("Talk to our team →"): ink text with an orange rule set BELOW
   it via border-bottom + padding (Figma: border-b 1px #f2622e, pb 4px). The padding
   IS the gap — text-decoration:underline sits too tight and read as "no gap".

   ⚠ A DIFFERENT TIER FROM THE BORDERED SECONDARY LINK, and it must say so
   explicitly. Requirement ¶19 asks for a border + transparent background on
   standalone links AFTER COPY; this one is a section HEADING's action and
   already has a designed affordance. When the bordered treatment landed on
   `.aq-sec-link a`, this rule out-specified only the bottom edge — so the link
   rendered as a rounded box with an INK border on three sides and an ORANGE
   border along the bottom, and `inline-block` silently killed the new 44px tap
   target (measured on the homepage, owner report 2026-08-31). Resetting the box
   here is what keeps the two tiers from fighting. */
.aq-sec-head .aq-sec-link a {
  display: inline-block;
  min-height: 0;
  padding-inline: 0;
  border: 0;
  border-radius: 0;
  border-bottom: var(--aq-bw-1) solid var(--aq-brand);
  padding-bottom: var(--aq-space-3xs);
}
.aq-sec-head .aq-sec-link a:hover {
  transform: none;
  box-shadow: none;
}
.aq-sec-head .aq-sec-link a:hover,
.aq-sec-head .aq-sec-link a:focus-visible {
  border-bottom-width: var(--aq-bw-2);
}

/* Card grid: fluid columns, never narrower than the card tier; a short last
   row stays left-aligned (design: 3 + 2). */
.aq-grid-cards {
  display: grid;
  gap: var(--aq-space-md);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--aq-card-min), 100%), 1fr));
}

/* Card: warm-white surface + hairline + soft elevation. Flex gap is the ONLY
   internal spacing (the stack model at card scale); doubled classes outrank
   WP layout margins. */
.aq-card {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-sm);
  padding: var(--aq-space-lg);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-card);
  background: var(--aq-surface-card);
  box-shadow: var(--aq-shadow-card);
}
.aq-card.aq-card > *,
.aq-grid-cards.aq-grid-cards > * {
  margin-block-start: 0;
  margin-block-end: 0;
}
.aq-card p {
  color: var(--aq-text-muted);
}

/* Card top row: icon tile left, decorative number right. */
.aq-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.aq-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--aq-space-xl);
  height: var(--aq-space-xl);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-md);
  background: var(--aq-fill-tile);
  color: var(--aq-text);
}
.aq-card-icon svg {
  width: var(--aq-space-md);
  height: var(--aq-space-md);
}
.aq-card-num {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  color: var(--aq-text-faint);
}

/* ---------- components: products grid (milestone 3) — tokens only ---------- */

/* Product card: dark media panel on top, text body below. The card frame
   (border/radius/surface/shadow) comes from .aq-card; padding moves into the
   body so the media panel bleeds to the card edges. */
.aq-pcard {
  padding: 0;
  gap: 0;
  overflow: hidden;
}

/* Media panel: gradient backdrop, centred glyph, bottom-left tag chip. */
.aq-pcard-media {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--aq-media-h);
  color: var(--aq-text-on-dark);
}
.aq-pcard-media svg {
  width: var(--aq-icon-lg);
  height: var(--aq-icon-lg);
}
.aq-pcard-media--azure { background: var(--aq-grad-media-azure); }
.aq-pcard-media--steel { background: var(--aq-grad-media-steel); }
/* A client-supplied image inside an ICON-mode media panel — capability-grid
   cards and homepage product tiles alike (2026-08-29). It fills the panel at
   the ICON height, not the taller `--photo` height, so a grid where some cards
   carry artwork and others still carry the fallback icon keeps its rows
   aligned. Scoped by :not(--photo) rather than by tone, because the tiles use
   four different tones (steel, warm, green, azure) and singling one out would
   have left the other three broken. The tone gradient stays behind the image
   as the backdrop for artwork that does not cover the full box. */
.aq-pcard-media:not(.aq-pcard-media--photo) img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.aq-pcard-media--warm  { background: var(--aq-grad-media-warm); }
.aq-pcard-media--green { background: var(--aq-grad-media-green); }

/* Tag chip: orange dot + mono uppercase label on the ink scrim (hero-chip family). */
.aq-pcard-tag {
  position: absolute;
  left: var(--aq-space-sm);
  bottom: var(--aq-space-sm);
  display: inline-flex;
  align-items: center;
  gap: var(--aq-space-2xs);
  margin: 0;
  padding: var(--aq-space-3xs) var(--aq-space-2xs);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-scrim-ink);
  color: var(--aq-text-on-dark);
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
}
.aq-pcard-tag::before {
  content: "";
  width: var(--aq-space-3xs);
  height: var(--aq-space-3xs);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-brand);
}

/* Body: its own stack; the CTA pins to the bottom so links align across a row. */
.aq-pcard-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: var(--aq-space-2xs);
  padding: var(--aq-space-md);
}
.aq-pcard-body.aq-pcard-body > * {
  margin-block-start: 0;
  margin-block-end: 0;
}
/* A composed card with no picture carries its children directly (it is a plain
   `.aq-card`, not a `.aq-pcard`), so the CTA needs the same push-to-bottom from
   one level up. No new class: the absence of `.aq-pcard` IS the variant. */
.aq-capcard:not(.aq-pcard) > .aq-pcard-cta {
  margin-block-start: auto;
  padding-top: var(--aq-space-sm);
}
.aq-pcard-body.aq-pcard-body > .aq-pcard-cta {
  margin-block-start: auto;
  padding-top: var(--aq-space-sm);
}
/* The card-level muted-paragraph rule must not swallow the CTA link. */
.aq-card .aq-pcard-cta {
  color: var(--aq-text);
}

/* ---------- components: industries grid (milestone 4) — tokens only ---------- */

/* Photo media panel (industries): cover-cropped photo + the required scrim
   (audit §6) so the tag chip always sits on darkened image. */
.aq-pcard-media--photo {
  height: var(--aq-media-photo-h);
}
.aq-pcard-media--photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.aq-pcard-media--photo::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--aq-scrim-photo);
}
.aq-pcard-media--photo .aq-pcard-tag {
  z-index: 1;
}

/* Card hover/focus — interactive cards (.aq-pcard = products + industries, which
   carry a CTA). The Figma showed ONE industry card in this state (orange border +
   orange CTA); read as the hover affordance for all cards, not a first-card
   feature (owner sign-off 2026-07-26). Keyboard users get it via :focus-within.
   The CTA uses brand-ink — raw orange on the white card body is 3.2:1 and fails
   AA, incl. in hover states (ADR-0005). */
.aq-pcard {
  transition: border-color var(--aq-motion-fast) var(--aq-ease-standard),
              transform var(--aq-motion-fast) var(--aq-ease-standard),
              box-shadow var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-pcard:hover,
.aq-pcard:focus-within {
  border-color: var(--aq-hairline-brand);
  transform: translateY(calc(var(--aq-lift-card) * -1));
  box-shadow: var(--aq-shadow-card-hover);
}
.aq-pcard .aq-pcard-cta a,
.aq-pcard .aq-pcard-cta span {
  transition: color var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-pcard:hover .aq-pcard-cta a,
.aq-pcard:focus-within .aq-pcard-cta a,
.aq-pcard:hover .aq-pcard-cta span,
.aq-pcard:focus-within .aq-pcard-cta span {
  color: var(--aq-content-link-color);
  text-decoration: underline;
  text-decoration-color: var(--aq-brand);
  text-underline-offset: var(--aq-link-underline-offset);
}

/* ---------- components: featured pathway (milestone 5) — tokens only ---------- */

/* Full-bleed dark band. Text defaults to on-dark; eyebrows read muted;
   buttons invert (light surface, ink label) — all contextual roles, so
   content markup stays colour-free. */
.aq-band-dark {
  background: var(--aq-surface-dark);
  color: var(--aq-text-on-dark);
}

/* Semi-light full-bleed band (products, process). Same wrapper structure as the
   dark bands, but keeps the normal light-surface text roles (no override) — it is
   the SEMI-LIGHT step in the design's white → semi-light → dark section system. */
.aq-band-tint {
  background: var(--aq-surface-alt);
}
.aq-band-dark .aq-eyebrow {
  color: var(--aq-text-on-dark-muted);
}
.aq-band-dark .wp-block-button__link {
  background: var(--aq-text-on-dark);
  color: var(--aq-text);
}

/* On-dark muted text, opt-in per element (lede, captions). */
.aq-dim {
  color: var(--aq-text-on-dark-muted);
}

/* Two-column pathway split: text · media. Same fluid column mechanism as the
   card grids — 2 columns at shell width, 1 below. */
.aq-pathway {
  display: grid;
  gap: var(--aq-space-2xl);
  align-items: center;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--aq-card-min), 100%), 1fr));
}
.aq-pathway.aq-pathway > * {
  margin-block-start: 0;
  margin-block-end: 0;
}

/* Check bullets: brand check glyph + on-dark text. */
.aq-checklist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-xs);
}
.aq-checklist li {
  display: flex;
  align-items: center;
  gap: var(--aq-space-xs);
}
.aq-checklist svg {
  width: var(--aq-icon-sm);
  height: var(--aq-icon-sm);
  flex-shrink: 0;
  color: var(--aq-brand);
}

/* Rich check list (Why AeroIQ hub band): check glyph + title + description. */
.aq-checklist--rich li {
  align-items: flex-start;
}
.aq-checklist--rich li > div {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-3xs);
}
.aq-checklist--rich .aq-check-desc {
  color: var(--aq-text-on-dark-muted);
  font-size: var(--aq-fs-body-sm);
}

/* Hub Technology ecosystem + Why AeroIQ two-column splits (Figma 17:2 / 18:2). */
/* Same two-column gutter rule as the editorial split: the gap between these
   columns must stay SMALLER than the page gutter beside them, or each column
   groups with the screen edge (was 64px against a 40px gutter — found by the
   2026-08-28 site-wide sweep, same defect the owner spotted on the PDP). */
.aq-tech,
.aq-why-grid {
  display: grid;
  gap: var(--aq-space-lg);
  align-items: start;
}
@media (min-width: 64em) {
  .aq-tech {
    grid-template-columns: 5fr 7fr;
  }
  .aq-why-grid {
    grid-template-columns: 1fr 1fr;
    align-items: center;
  }
}

/* Technology ecosystem numbered rows: orange mono index + title + muted line. */
.aq-tech-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.aq-tech-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--aq-space-md);
  padding: var(--aq-space-md) 0;
  border-top: var(--aq-bw-1) solid var(--aq-hairline-light);
}
.aq-tech-row:first-child {
  border-top: 0;
}
.aq-tech-num {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  color: var(--aq-brand-ink);
}
.aq-tech-body > * {
  margin: 0;
}
.aq-tech-body p {
  margin-top: var(--aq-space-3xs);
  color: var(--aq-text-muted);
}

/* Media panel: rounded cover photo, green-dark scrim, deep shadow,
   glass tag chips top-left + the standard dot chip bottom-left.

   ⚠ The fixed height is a DESKTOP shape. Width is fluid, so on a phone this
   560px panel becomes 358x560 — a PORTRAIT box holding a 1.72 landscape
   graphic, which `cover` then crops to 37% of its width. That matters more
   here than for a photograph: the artwork is a four-panel pipeline strip with
   its stage names printed INTO it (1. Capture · 2. Point Cloud · 3. Surface
   Model · 4. Ground Points), so cropping deletes the content, not just some
   scenery. Found 2026-09-21 by the image contact sheet, after every DOM, axe
   and performance check passed. The phone therefore gets a shape, not a
   height: 16/10 keeps 93% of the width and all four stages stay readable.
   See the mobile rule further down. */
.aq-pathway-media {
  position: relative;
  height: var(--aq-pathway-media-h);
  border-radius: var(--aq-radius-lg);
  overflow: hidden;
  box-shadow: var(--aq-shadow-media);
}
.aq-pathway-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.aq-pathway-media::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--aq-scrim-pathway);
}
.aq-pathway-media .aq-pcard-tag,
.aq-pathway-tags {
  z-index: 1;
}
.aq-pathway-tags {
  position: absolute;
  top: var(--aq-space-sm);
  left: var(--aq-space-sm);
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-2xs);
}
.aq-tag-glass {
  padding: var(--aq-space-3xs) var(--aq-space-2xs);
  border: var(--aq-bw-1) solid var(--aq-hairline-dark);
  border-radius: var(--aq-radius-sm);
  background: var(--aq-fill-glass);
  color: var(--aq-text-on-dark);
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
}

/* ---------- components: process steps (milestone 6) — tokens only ---------- */

/* 5-up step row; the faded hairline runs behind the icon tiles (the tiles'
   opaque surfaces sit over it). Ordered content — the markup is an <ol>. */
.aq-process {
  position: relative;
  display: grid;
  gap: var(--aq-space-md);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--aq-step-min), 100%), 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}
.aq-process::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: calc(var(--aq-space-xl) / 2);
  height: var(--aq-bw-1);
  background: linear-gradient(
    90deg,
    transparent,
    var(--aq-hairline-light) 8%,
    var(--aq-hairline-light) 92%,
    transparent
  );
}
.aq-step {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-2xs);
}
.aq-step.aq-step > * {
  margin-block-start: 0;
  margin-block-end: 0;
}
.aq-step-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: flex-start;
  width: var(--aq-space-xl);
  height: var(--aq-space-xl);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-md);
  background: var(--aq-surface-card);
  box-shadow: var(--aq-shadow-card);
  color: var(--aq-text);
}
.aq-step-icon svg {
  width: var(--aq-space-md);
  height: var(--aq-space-md);
}
.aq-step-num {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  color: var(--aq-text-faint);
  padding-top: var(--aq-space-2xs);
}
.aq-step p {
  color: var(--aq-text-muted);
}

/* Pink "Not sure what fits?" CTA card. Its accent link is brand-INK via
   .aq-link (ADR-0005: raw brand orange fails AA on this surface). */
.aq-pinkcard {
  padding: var(--aq-space-md);
  border-color: var(--aq-hairline-brand);
  background: var(--aq-grad-pink);
}
.aq-pinkcard p {
  color: var(--aq-text-copper);
}
.aq-pinkcard-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--aq-space-xl);
  height: var(--aq-space-xl);
  border: var(--aq-bw-1) solid var(--aq-hairline-brand);
  border-radius: var(--aq-radius-md);
  background: var(--aq-surface-card);
  color: var(--aq-brand);
}
.aq-pinkcard-icon svg {
  width: var(--aq-space-md);
  height: var(--aq-space-md);
}
.aq-pinkcard.aq-pinkcard > .aq-pinkcard-cta {
  margin-block-start: auto;
  padding-top: var(--aq-space-sm);
}
.aq-pinkcard-cta {
  font-size: var(--aq-fs-body-sm);
  font-weight: var(--aq-fw-semibold);
}

/* ---------- components: final CTA + footer (milestone 7) — tokens only ---------- */

/* CTA band: photo behind the dark band, scrim between photo and content.
   The content group must be positioned or the absolutely-positioned scrim
   paints over it. Button labels NEVER go light-on-orange (ADR-0005). */
.aq-cta {
  position: relative;
  overflow: hidden;
}
/* max-width:none — WP's constrained layout caps direct children at
   contentSize; that must not apply to the full-bleed backdrop pair. WP
   prints its layout rule inline AFTER theme styles, so the override needs
   higher specificity (.aq-cta prefix), not just later source order. */
.aq-cta .aq-cta-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  margin: 0;
  object-fit: cover;
}
.aq-cta .aq-cta-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  max-width: none;
  margin: 0; /* WP's sibling block-gap margin would shift the inset box */
  background: var(--aq-cta-scrim);
}
.aq-cta-content {
  position: relative;
}
/* Approved core/button styles (the block-style picker). Dark solid = the default
   theme.json button (black). Brand + Outline below; legacy `.aq-btn-*` classes kept as
   aliases so existing markup keeps working. */
.is-style-brand .wp-block-button__link,
.aq-btn-brand .wp-block-button__link {
  background: var(--aq-brand);
  color: var(--aq-on-brand);
}
/* Outline — contrast-safe on LIGHT surfaces: dark label + border on a transparent fill. */
.is-style-outline .wp-block-button__link {
  background: transparent;
  color: var(--aq-text);
  border: var(--aq-bw-1) solid var(--aq-text);
}
/* Outline on DARK bands, and the legacy dark-surface `.aq-btn-ghost`: white label on a
   glass fill (the light-surface treatment above would be invisible on dark). */
.aq-band-dark .is-style-outline .wp-block-button__link,
.aq-btn-ghost .wp-block-button__link {
  background: var(--aq-fill-glass);
  color: var(--aq-text-on-dark);
  border: var(--aq-bw-1) solid var(--aq-hairline-dark);
}

/* Inset rounded CTA band ("Looking for something different?"): the full-bleed
   .aq-cta treatment (bg image + scrim) contained in a rounded, padded card that
   sits inside the content column rather than bleeding edge-to-edge (Figma 27:201).
   Content stack + action both carry .aq-cta-content so they paint above the
   scrim; the band lays them out content-left / action-right. */
.aq-cta--inset {
  border-radius: var(--aq-radius-lg);
  padding: var(--aq-space-2xl);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--aq-space-lg);
}

/* Detail-hero action row: primary CTA + an optional in-page jump link (Training). */
.aq-hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--aq-space-md);
}
.aq-hero-jump {
  margin: 0;
}

/* Interaction floor: every button meets the project 44px target (--aq-tap-min).
   Computed height was 41.6px (16px × 1.6 line + 2×0.5rem padding) while the
   header comment claimed the CTA was the height floor — now it actually is
   (round-1 L2; project standard, not a WCAG AA requirement). Flex centres the
   label when the floor engages. */
.wp-block-button__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--aq-tap-min);
}

/* Button trailing arrow (Figma: a separate ORANGE glyph, not part of the label).
   Opt-in via .aq-btn--arrow so the ghost button (no arrow in the design) stays clean.
   Orange passes as a graphical element on every button surface (≥3:1); on the orange
   brand fill it flips to ink so it stays visible. */
.aq-btn--arrow .wp-block-button__link::after {
  content: "→";
  margin-left: var(--aq-space-2xs);
  color: var(--aq-brand);
}
.aq-btn-brand.aq-btn--arrow .wp-block-button__link::after,
.is-style-brand.aq-btn--arrow .wp-block-button__link::after {
  color: var(--aq-on-brand);
}

/* ---------- micro-interactions (motion — ADR-0013 slice 1, CSS only) ----------
   Interaction-state feedback. Lifts use transform (compositor); state changes use
   cheap one-off colour transitions. All durations are zeroed by the
   prefers-reduced-motion block below (the master off-switch). */

/* Buttons: lift + soft shadow on hover, press on active, arrow nudges forward. */
.wp-block-button__link {
  transition: transform var(--aq-motion-fast) var(--aq-ease-standard),
              box-shadow var(--aq-motion-fast) var(--aq-ease-standard);
}
.wp-block-button__link:hover {
  transform: translateY(calc(var(--aq-lift) * -1));
  box-shadow: var(--aq-shadow-card);
}
.wp-block-button__link:active {
  transform: translateY(0);
  box-shadow: none;
}
.aq-btn--arrow .wp-block-button__link::after {
  transition: transform var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-btn--arrow .wp-block-button__link:hover::after {
  transform: translateX(var(--aq-nudge));
}

/* 404 page (owner-directed 2026-08-13): a huge decorative status numeral in
   the faint decorative-marks role (same family as the card numbers — never
   copy), over the crash-landed-drone artwork. Template: templates/404.html. */
.aq-404-code {
  margin: 0;
  font-family: var(--aq-font-heading);
  font-weight: var(--aq-fw-bold);
  font-size: var(--aq-fs-404);
  line-height: var(--aq-lh-tight);
  letter-spacing: var(--aq-track-tight);
  color: var(--aq-text-faint);
}
/* The artwork is the section BACKGROUND under the white wash (owner-directed;
   AA record on the wash token). Decorative — meaning lives in the copy, so no
   alt surface is lost. Position keeps the drone (right-of-centre, low) in
   frame while cover-cropping on tall/narrow sections. */
.aq-404-hero {
  background-image: var(--aq-scrim-404), url("../img/404-drone.jpg");
  background-size: cover, cover;
  background-position: center, 70% 65%;
  background-repeat: no-repeat, no-repeat;
}

/* Core paints `background-color: inherit` on every nav <li> (its submenu
   colour-inheritance mechanic). Invisible on solid surfaces — but over the
   frosted bands each item re-paints the translucent white ON the band,
   stacking to a whiter box per item (owner-caught 2026-08-13). The surface
   is the band's job; items paint nothing. */
.wp-block-navigation .wp-block-navigation-item {
  background-color: transparent;
}

/* Nav links: brand-ink accent on hover. */
.wp-block-navigation-item__content {
  transition: color var(--aq-motion-fast) var(--aq-ease-standard);
}
.wp-block-navigation-item__content:hover {
  color: var(--aq-brand-ink);
}
/* On the frosted bar the top-level items sit over unknown scrolled content —
   brand-ink cannot guarantee AA there (tokens.css AA record), so the header's
   top-level hover keeps ink and signals with the underline cue. Panel and
   overlay links (solid surfaces) keep the brand-ink treatment. Desktop-only:
   below 64em the inline row is hidden (burger), and the OPEN overlay is a
   solid surface (excluded via :not). */
@media (min-width: 64em) {
  .aq-header .wp-block-navigation__responsive-container:not(.is-menu-open) .wp-block-navigation__container > .wp-block-navigation-item > .wp-block-navigation-item__content:hover,
  .aq-header .wp-block-navigation__responsive-container:not(.is-menu-open) .wp-block-navigation__container > .wp-block-navigation-item > .wp-block-navigation-item__content:focus-visible {
    color: inherit;
    text-decoration: underline;
  }
}

/* Footer social icons: brand-ink + lift on hover. */
.aq-social {
  transition: color var(--aq-motion-fast) var(--aq-ease-standard),
              transform var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-social:hover {
  color: var(--aq-brand-ink);
  transform: translateY(calc(var(--aq-lift) * -1));
}

/* Footer: brand column + link columns over a hairline, then the legal row.
   Surface = page background (Figma's #f4f3f0 merged — near-dupe). */
.aq-footer {
  /* Padding lives here (not inline preset) so it's token-based and reliable —
     the WP `2xl` spacing preset was silently dropped because its slug started
     with a digit (renamed to `xxl`). Figma footer ≈ 79/40; 2xl/lg = 64/32. */
  padding-block: var(--aq-space-2xl) var(--aq-space-lg);
  border-top: var(--aq-bw-1) solid var(--aq-hairline-light);
  background: var(--aq-surface-alt); /* subtly tinted vs the white content (Figma #f4f3f0) */
}
.aq-footer-grid {
  display: grid;
  gap: var(--aq-space-xl);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--aq-step-min), 100%), 1fr));
  padding-bottom: var(--aq-space-xl);
  border-bottom: var(--aq-bw-1) solid var(--aq-hairline-light);
}
/* The grid was authored for four children; the D-16 Contact column made five,
   and auto-fit wrapped it to a lonely second row (owner: "sitting randomly").
   Five known children get an explicit desktop template — brand wider, four
   equal link columns; below 64em auto-fit wraps as before. */
@media (min-width: 64em) {
  .aq-footer-grid {
    grid-template-columns: minmax(0, 1.5fr) repeat(4, minmax(0, 1fr));
  }
}
.aq-footer-brand {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-sm);
  max-width: var(--aq-card-min);
}
/* Footer logo displays at the header's 142px on the asset's TRUE 283×62 ratio
   (≈142×31). The old width="142" height="45" attrs stretched it ~45% vertically
   — client-caught defect, round-1 L3. */
.aq-footer-brand img {
  display: block;
  width: var(--aq-logo-w);
  height: auto;
}
.aq-footer-brand p {
  margin: 0;
  color: var(--aq-text-muted);
}
/* DJI Enterprise Authorized Dealer badge (round-2 #12; the client's own SVG, black
   ink for the light footer surface). Renders at the supplied asset's OWN 214×48
   size, not shrunk to the logo width — Luke asked for it to be more prominent
   (final review #2), and a badge with DJI usage rules is safest displayed at the
   dimensions it was authored at. `max-width` keeps it inside the brand column,
   which narrows to 207px at the 1024 five-column footer. */
.aq-footer-brand .aq-footer-badge {
  width: var(--aq-badge-w);
  /* `max-width` keeps it inside the 207px brand column the five-up footer
     produces at 1024, where 214px would otherwise overflow by 7px. */
  max-width: 100%;
  height: auto;
  margin-top: var(--aq-space-2xs);
}
.aq-footer-head {
  margin: 0 0 var(--aq-space-sm);
  color: var(--aq-text-muted);
}
.aq-footer-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-xs);
}
.aq-footer-links a {
  color: var(--aq-text-muted);
  text-decoration: none;
}
.aq-footer-links a:hover,
.aq-footer-links a:focus-visible {
  color: var(--aq-text);
}
/* Non-link facts in the Contact column (address, hours) sit in the same list as
   the mailto/tel links. Without this they inherit body colour and read a tier
   louder than the links beside them — one column, two visual weights, which
   looks like an accident rather than a distinction. Matching --aq-text-muted
   keeps the column uniform; the links still separate themselves on hover. */
.aq-footer-contact .aq-footer-links > li {
  color: var(--aq-text-muted);
}
.aq-social-row {
  display: flex;
  gap: var(--aq-space-sm);
  padding-top: var(--aq-space-2xs);
}
.aq-social {
  display: inline-flex;
  color: var(--aq-text);
}
.aq-social svg {
  width: var(--aq-space-lg);
  height: var(--aq-space-lg);
}
.aq-footer-legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-sm);
  align-items: center;
  justify-content: space-between;
  padding-top: var(--aq-space-lg);
}
.aq-footer-copy {
  color: var(--aq-text-muted);
  font-size: var(--aq-fs-body-sm);
}
.aq-credit {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  color: var(--aq-text-muted);
}

/* Visible focus for keyboard users (§23 a11y). DUAL RING: light inner ring +
   dark outer halo — at least one ring contrasts ≥3:1 on light, dark AND orange
   surfaces (browser-verified per surface; currentColor failed for controls on
   dark sections — see design-system §7). */
:focus-visible {
  outline: var(--aq-focus-ring-width) var(--aq-focus-ring-style) var(--aq-focus-ring-color);
  outline-offset: var(--aq-focus-ring-offset);
  box-shadow: var(--aq-focus-ring-halo);
}

/* Scroll-reveal from-state (ADR-0013 slice 2). Reveal targets start hidden ONLY
   when `aq-reveal` is present AND motion is allowed. JS-off or reduced-motion
   users never get this, so content is always visible (progressive enhancement).
   motion.js (GSAP ScrollTrigger) animates opacity + transform back to visible as
   each enters. The hero (LCP image) is deliberately NOT a target — LCP stays
   fast. Transform + opacity only ⇒ no layout shift (elements keep their box).

   GATE IS `aq-reveal`, NOT `.js` (audit M6-H01). `.js` only ever meant
   "JavaScript exists"; hiding content on it meant a single failed motion asset
   left 17/17 targets invisible at HTTP 200. `aq-reveal` is set in <head> for the
   same no-flash behaviour, then REMOVED on window.load unless motion.js has
   confirmed itself ready — so a missing dependency reveals content instead of
   hiding it. See functions.php aeroiq_js_flag(). */
@media (prefers-reduced-motion: no-preference) {
  .aq-reveal .aq-grid-cards > *,
  .aq-reveal .aq-process > *,
  .aq-reveal .aq-footer-grid > * {
    opacity: 0;
    transform: translateY(var(--aq-reveal-rise));
  }
}

/* Hero on-load entrance (ADR-0013 slice 3) — CSS-only, deliberately NOT GSAP:
   above-the-fold content must not wait on the deferred motion bundle, and a CSS
   keyframe plays immediately on load with no JS-load dependency and no flash
   (JS-off still animates). Staggered fade+rise of the hero TEXT only; the hero
   media (the LCP image) is never animated → LCP untouched. Under reduced-motion
   this whole block is inert (no-preference gate), leaving the text at its default
   visible state. */
@keyframes aq-rise-in {
  from { opacity: 0; transform: translateY(var(--aq-reveal-rise)); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: no-preference) {
  .wp-block-post-content > .aq-section:first-child > .aq-eyebrow,
  .wp-block-post-content > .aq-section:first-child > .wp-block-heading,
  .wp-block-post-content > .aq-section:first-child > .aq-hero-row {
    opacity: 0;
    animation: aq-rise-in var(--aq-motion-slow) var(--aq-ease-out) both;
  }
  .wp-block-post-content > .aq-section:first-child > .wp-block-heading {
    animation-delay: var(--aq-stagger);
  }
  .wp-block-post-content > .aq-section:first-child > .aq-hero-row {
    animation-delay: calc(var(--aq-stagger) * 2);
  }
}

/* Reduced motion (§23 a11y). Values here are structural minimums (an effectively
   "off" duration), not design decisions — classified in the contract. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- components: Services build (M2) — tokens only ----------
   Reuses .aq-card / .aq-pcard / .aq-pcard-media / .aq-checklist; adds ONLY the
   controlled density column toggle (§13.4), the capability-card tweaks, and the
   whole-card-clickable Service-card affordance (§11.1 / §14.1). No raw values. */

/* Whole-card-clickable Service card: the .aq-card IS an <a> (rest-state markup
   identical to the homepage card). Hover/focus is the only affordance — no
   permanent "Explore" text — matching the .aq-pcard hover feel. */
.aq-card-link {
  text-decoration: none;
  transition: border-color var(--aq-motion-fast) var(--aq-ease-standard),
              transform var(--aq-motion-fast) var(--aq-ease-standard),
              box-shadow var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-card-link:hover,
.aq-card-link:focus-visible {
  border-color: var(--aq-hairline-brand);
  transform: translateY(calc(var(--aq-lift-card) * -1));
  box-shadow: var(--aq-shadow-card-hover);
}
/* Whole-card pink advice tile: card-level hover thickens the .aq-link cue so
   the ADR-0005 brand-ink treatment keeps its active state (round-1 L12). */
.aq-card-link:hover .aq-link,
.aq-card-link:focus-visible .aq-link {
  text-decoration-thickness: var(--aq-link-underline-thickness-active);
}

/* capability-grid density — the controlled 2-/3-col growth toggle (§13.4), kept
   SEPARATE from the semantic pair layout. Overrides .aq-grid-cards' auto-fit
   template (later source order + equal specificity). Media-query widths are
   literal (custom properties can't be read in @media conditions); both densities
   collapse toward one column on phones. */
.aq-grid-density-comfortable {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.aq-grid-density-compact {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 63.99em) {
  .aq-grid-density-compact {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 47.99em) {
  .aq-grid-density-comfortable,
  .aq-grid-density-compact {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Capability card: the .aq-card / .aq-pcard frame + labelled lists + output chips.
   The glyph media panel matches the PHOTO panel height so icon cards and image
   cards share ONE media ratio (owner note). Lists use dot markers (checks belong
   to the Value checklist, not here); "Outputs you can produce" render as chip tags
   — all matching the Figma scenario card (§14.2). */
.aq-capcard--glyph .aq-pcard-media {
  height: var(--aq-media-photo-h);
}
/* Emphasised capability card (e.g. "Managed by AeroIQ"): the .aq-card frame on a
   dark surface with on-dark text roles — reuses the .aq-band-dark tokens so the
   card markup stays colour-free. Body text + bullets read on-dark-muted. */
.aq-capcard--emphasis {
  background: var(--aq-surface-dark);
  border-color: var(--aq-hairline-dark);
  color: var(--aq-text-on-dark);
}
.aq-capcard--emphasis p,
.aq-capcard--emphasis .aq-caplist li {
  color: var(--aq-text-on-dark-muted);
}
.aq-capcard-group + .aq-capcard-group {
  margin-top: var(--aq-space-sm);
}
.aq-caplist-label {
  display: flex;
  gap: var(--aq-space-2xs);
  align-items: baseline;
  margin: 0 0 var(--aq-space-2xs);
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-faint);
}
.aq-caplist-more {
  color: var(--aq-brand-ink);
}
/* Disclosure for lists too long to show whole — see capability-grid/render.php.
   Native <details>, so it works with JavaScript off and is keyboard-operable
   and announced as expanded/collapsed without any ARIA of ours. */
.aq-caplist-rest {
  margin-top: var(--aq-space-2xs);
}
.aq-caplist-rest > .aq-caplist,
.aq-caplist-rest > .aq-chips {
  margin-top: var(--aq-space-2xs);
}
.aq-caplist-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--aq-space-2xs);
  cursor: pointer;
  color: var(--aq-brand-ink);
  font-size: var(--aq-fs-body-sm);
  /* The native triangle is replaced by the caret below so it can take the
     brand colour and rotate; the element stays a real <summary>, so focus,
     keyboard operation and the expanded/collapsed announcement are unchanged
     and the global :focus-visible ring applies. */
  list-style: none;
}
.aq-caplist-toggle::-webkit-details-marker {
  display: none;
}
.aq-caplist-toggle::after {
  content: "";
  width: var(--aq-space-2xs);
  height: var(--aq-space-2xs);
  border-right: var(--aq-bw-2) solid currentColor;
  border-bottom: var(--aq-bw-2) solid currentColor;
  transform: rotate(45deg) translateY(calc(var(--aq-space-3xs) * -0.5));
  transition: transform var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-caplist-rest[open] > .aq-caplist-toggle::after {
  transform: rotate(-135deg) translateY(calc(var(--aq-space-3xs) * -0.5));
}
.aq-caplist-toggle:hover {
  text-decoration: underline;
}
.aq-caplist-toggle-less {
  display: none;
}
.aq-caplist-rest[open] > .aq-caplist-toggle .aq-caplist-toggle-more {
  display: none;
}
.aq-caplist-rest[open] > .aq-caplist-toggle .aq-caplist-toggle-less {
  display: inline;
}
.aq-caplist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-2xs);
}
.aq-caplist li {
  display: flex;
  /* Not `center`: an item that wraps to two lines would float its dot between
     them. Anchor to the first line and nudge the dot to that line's optical
     middle instead — every list reads the same whether an item wraps or not. */
  align-items: flex-start;
  gap: var(--aq-space-xs);
  color: var(--aq-text-muted);
}
.aq-caplist li::before {
  content: "";
  flex: 0 0 auto;
  width: var(--aq-space-3xs);
  height: var(--aq-space-3xs);
  margin-top: calc((var(--aq-lh-body) * 1em - var(--aq-space-3xs)) / 2);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-brand);
}
.aq-chips {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-2xs);
}
.aq-chip-tag {
  padding: var(--aq-space-3xs) var(--aq-space-xs);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-pill);
  font-size: var(--aq-fs-body-sm);
  color: var(--aq-text-muted);
  background: var(--aq-surface-alt);
}

/* Semantic PAIR layout (exactly-two options: Training Foundation · Servicing
   approaches). Distinct from the density toggle by NAME (§6 / §13.4) — a pair is
   never a growable collection. Two columns at shell, one on phones. */
.aq-layout-pair {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 47.99em) {
  .aq-layout-pair {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Lifecycle per-stage service mapping (hub "How it fits together" — §12.1): a
   small mono label under each process step. brand-ink is AA-safe on the light
   tint band (ADR-0005). */
.aq-step-map {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-brand-ink);
}

/* ---------- components: platform hub (Products P1) — tokens only ---------- */

/* Breadcrumbs: REMOVED sitewide (owner, 2026-08-09) — markup + styles retired
   together; placement to be reconsidered later (restore point: git history). */

/* Hub hero media: enterprise packshots are contain-fitted, never cropped
   (design-gate QA note 2), on the card surface with controlled padding. */
.aq-hero-media--contain img {
  object-fit: contain;
  padding: var(--aq-space-md);
  background: var(--aq-surface-card);
}

/* Hub-Group navigation (D4): a semantic list of real links. Selected state is
   aria-current="page" — brand-ink label + brand underline. No tab ARIA. */
.aq-hubnav ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-lg);
  margin: 0;
  padding: 0;
  list-style: none;
  border-bottom: var(--aq-bw-1) solid var(--aq-hairline-light);
}
.aq-hubnav a {
  display: block;
  padding-block: var(--aq-space-sm);
  margin-bottom: calc(var(--aq-bw-1) * -1);
  color: var(--aq-text);
  text-decoration: none;
  border-bottom: var(--aq-space-3xs) solid transparent;
  transition: color var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-hubnav a:hover,
.aq-hubnav a:focus-visible {
  color: var(--aq-content-link-color);
}
.aq-hubnav a[aria-current="page"] {
  color: var(--aq-content-link-color);
  border-bottom-color: var(--aq-brand);
}

/* Product card (whole-card link on the .aq-pcard family). Media contains the
   packshot (never crops) and hosts the stock chip; the meta row pins price
   left / CTA right at the card foot. */
.aq-prodcard-media {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--aq-media-photo-h);
  background: var(--aq-surface-card);
}
/* Product-card media now matches every OTHER card on the site: edge to edge,
   cover, focal-adjustable. Until 2026-09-21 this card contain-fitted EVERY
   image inside a padded white box while .aq-pcard-media--photo covered its
   photographs — one media height, two treatments, and the owner read it as a
   design inconsistency before it was ever an image problem. A packshot still
   needs contain; it opts in per image via --contain below. */
.aq-prodcard-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--aq-focal-x, 50%) var(--aq-focal-y, 50%);
  box-sizing: border-box; /* no global reset: content-box made 100%+padding overflow the media box (owner DevTools catch 2026-08-21) */
}
/* The exception, set per IMAGE not per card: an object photographed on
   seamless white, where cropping the edges clips the product. Classified from
   the image's own border by docs/recovery/classify-image-fit.php and pinned by
   an editor with `_aeroiq_img_fit_lock`. */
.aq-prodcard-media--contain img {
  object-fit: contain;
  padding: var(--aq-space-md);
}
/* THE SAME OVERFLOW THE OWNER CAUGHT ON 2026-08-21, on a second component.
   `.aq-prodcard-media img` carries `box-sizing: border-box` with that exact
   note; `.aq-pcard-media--photo img` never needed it while it only ever
   cropped. Giving the composer's cards the shared fit classifier (2026-09-23,
   so the Matrice 400 payload packshots stop being sliced) made it need it: a
   100%-height image plus this padding is 48px taller than its 200px box, and
   every payload bled onto the copy below. Scoped to the contain case, so no
   existing cover card changes. */
.aq-pcard-media--photo.aq-prodcard-media--contain img {
  box-sizing: border-box;
}

/* Phone: the pathway panel follows the ARTWORK's shape instead of a desktop
   height, so the four labelled stages survive. 16/10 keeps 93% of the width;
   the shipped 358x560 portrait box kept 37% and cut two stage names in half.
   Scoped to the narrow breakpoint only — the desktop panel is unchanged. */
@media (max-width: 47.99em) {
  .aq-pathway-media {
    height: auto;
    aspect-ratio: 16 / 10;
  }
}

/* ---------- FOCAL CONTROL for every covered image (2026-09-21) -------------
   Until now nothing in this stylesheet set `object-position`, so every crop
   fell back to the browser default of dead centre. On a portrait photograph of
   people that keeps torsos and discards whatever the picture is actually OF —
   the Drone Training hero cropped a drone-flying photo down to two people
   standing in a field, which is how the owner found this.

   fit mode, container shape and focal point are THREE separate controls. This
   is the third. Cover rules read the pair below; a component or a single
   instance overrides by setting --aq-focal-y (and rarely --aq-focal-x) on the
   media element or any ancestor, because the right focal point depends on the
   SHAPE it is being cropped into, not just on the photograph. The same picture
   legitimately wants different values in a wide card and a tall hero, which is
   why this is a per-use custom property and not one value stored per file.

   Defaults are 50%/50%, so anything not deliberately tuned is unchanged. */
.aq-hero-split .aq-hero-media img,
.aq-spotlight .aq-hero-media img,
.aq-hero-split .aq-hero-media--overlay img,
.aq-pcard-media img,
.aq-pcard-media--photo img,
.aq-dcard-media img,
.aq-pathway-media img,
.aq-submenu-thumb img {
  object-position: var(--aq-focal-x, 50%) var(--aq-focal-y, 50%);
}

/* Focal values for the client photographs whose subject the centre crop was
   losing. Keyed on the file rather than an attachment class because
   `wp_get_attachment_image()` does not emit `wp-image-<id>`, and scoped by
   COMPONENT because the same picture needs a different value in a different
   shape — `about-approach-training` is the same photograph as the Drone
   Training hero in a much wider box, and takes its own number.

   Each value was chosen by looking at the rendered result, not computed: the
   test is whether the photograph still says what it is of. For the training
   photo that means the DRONE stays in frame with the operators — at the
   shipped 50% it was cropped to two people standing in a field.

   This is the launch-week mechanism, deliberately small and reviewable. The
   durable version is a focal control on the attachment with a per-use
   override, which is recorded as post-launch work — it needs an admin UI and
   this does not. */
/* Heroes — 16:10, so ~47% of a 3:4 portrait survives. 27% lifts the window to
   hold the operators AND the aircraft. Owner-tested in the browser first. */
.aq-hero-media img[src*="client-service-drone-training"] { --aq-focal-y: 27%; }
.aq-hero-media img[src*="contact-banner"]                { --aq-focal-y: 27%; }

/* Cards — landscape sources, subject high or low in frame. */
.aq-pcard-media img[src*="Aircraft-Platforms"]           { --aq-focal-y: 67%; }
.aq-pcard-media img[src*="Dock-Systems"]                 { --aq-focal-y: 70%; }
.aq-pcard-media img[src*="Main-Industry-Photo-Construct"] { --aq-focal-y: 65%; }

/* ⚠ The one case focal position CANNOT fix, recorded so nobody re-tunes it:
   `about-approach-training` is the SAME portrait photograph as the Drone
   Training hero, but this card is ~3.3:1, which shows only ~23% of the image
   height. The drone sits at 8–22% and the operators at 38–75% — a 23% window
   cannot hold both, at any focal value. 44% is the best available composition
   (both operators, head to waist, horizon level); the drone is simply out of
   reach in this shape. The real fixes are a taller card or a landscape
   training photograph — the client already supplied one
   (`svc-training-introductory-training-and-new-equipment`, 2000×1500). That is
   a content choice, so it is the owner's, not ours. */
.aq-pcard-media img[src*="about-approach-training"]      { --aq-focal-y: 44%; }
.aq-prodcard-media--none {
  background: var(--aq-surface-alt);
}
.aq-prodcard-media--none .aq-eyebrow {
  color: var(--aq-text-eyebrow);
}
.aq-prodcard-stock {
  position: absolute;
  top: var(--aq-space-sm);
  right: var(--aq-space-sm);
  padding: var(--aq-space-3xs) var(--aq-space-xs);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-surface-card);
  font-size: var(--aq-fs-body-sm);
  color: var(--aq-text);
}
.aq-prodcard-stock--out {
  color: var(--aq-text-muted);
}
.aq-prodcard .aq-pcard-body h3 {
  color: var(--aq-text);
}
.aq-prodcard-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--aq-space-sm);
  margin-block-start: auto;
  padding-top: var(--aq-space-sm);
}
/* Same unit as the PDP: .aq-prodcard-meta is justify-content: space-between
   (price left, CTA right), so the amount and its label must be ONE child or the
   label pushes the CTA out of position. */
.aq-prodcard-price-group {
  display: inline-flex;
  flex-direction: column;
  gap: var(--aq-space-3xs);
}
.aq-prodcard-price {
  font-weight: var(--aq-fw-medium);
  color: var(--aq-text);
}
.aq-prodcard-tax {
  font-size: var(--aq-fs-body-sm);
  font-weight: var(--aq-fw-regular);
  color: var(--aq-text-muted);
}
.aq-prodcard-cta {
  color: var(--aq-text);
  transition: color var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-prodcard:hover .aq-prodcard-cta,
.aq-prodcard:focus-within .aq-prodcard-cta {
  color: var(--aq-content-link-color);
  text-decoration: underline;
  text-decoration-color: var(--aq-brand);
  text-underline-offset: var(--aq-link-underline-offset);
}

/* Product code (SKU) — mono metadata line on the card (approved card slot). */
.aq-prodcard-code {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-muted);
}

/* Mobile Hub-Group nav: the approved treatment is a HORIZONTAL SCROLLER —
   no wrap, ≥44px targets, server links (no JS). Active-link auto-scroll is an
   optional later enhancement; basic no-JS navigation is the contract. */
@media (max-width: 63.99em) {
  .aq-hubnav ul {
    flex-wrap: nowrap;
    overflow-x: auto;
    /* overflow-x:auto computes overflow-y:auto too, and each link's -1px
       underline margin overflows the list by exactly 1px — enough for
       Windows (classic scrollbars) to draw a vertical scrollbar beside the
       tabs. Measured 2026-09-17: scrollHeight 62 / clientHeight 61.
       Client report: "an up-down scroll wheel" (Zane, 2026-09-16). */
    overflow-y: hidden;
  }
  .aq-hubnav a {
    white-space: nowrap;
    min-height: var(--aq-tap-min);
    display: flex;
    align-items: center;
  }
}

/* ---------- platform hub — owner-browser feedback batch (2026-08-08) ---------- */

/* Visually-hidden utility (live-region announcements). The classic 1px box
   reuses the 1px primitive — it is an a11y mechanism, not a design value. */
.screen-reader-text {
  position: absolute;
  width: var(--aq-bw-1);
  height: var(--aq-bw-1);
  margin: calc(var(--aq-bw-1) * -1);
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  border: 0;
  white-space: nowrap;
}

/* Contain-fit actually wins now: the earlier `.aq-hero-media--contain img`
   (0,1,1) LOST to the base `.aq-hero-split .aq-hero-media img` (0,2,1) — the
   cropped aircraft the owner saw. Matched specificity, later in file. */
.aq-hero-split .aq-hero-media--contain img {
  object-fit: contain;
  padding: var(--aq-space-md);
  background: var(--aq-surface-card);
  box-sizing: border-box; /* same content-box overflow family */
}

/* Split media needs real presence even against short copy: the hub hero and
   narrative media boxes were collapsing to the copy column's height, cropping
   packshots into shallow strips. */
.aq-hub-hero .aq-hero-media,
.aq-hub-narrative .aq-hero-media,
.aq-cat-hero .aq-hero-media {
  min-height: var(--aq-media-split-min);
}

/* Product grid: card geometry NEVER depends on product count. The shared
   `.aq-grid-cards` auto-fit collapses empty tracks (1 item = full-width
   banner, 2 items = 50/50) — rejected at owner review. Fixed tracks here,
   scoped to the product grid only. */
.aq-prodgrid,
.aq-hub-related .aq-grid-cards {
  grid-template-columns: 1fr;
}
@media (min-width: 48em) {
  .aq-prodgrid,
  .aq-hub-related .aq-grid-cards {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 64em) {
  .aq-prodgrid,
  .aq-hub-related .aq-grid-cards {
    grid-template-columns: repeat(3, 1fr);
  }
}


/* ---------- products overview (P2) — nav-card family + fixed grids ---------- */

/* Nav-card media: contain-fit packshot on the card surface (never cropped);
   the D2 variant centres its mono availability label instead. */
.aq-navcard-media {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--aq-media-h);
  background: var(--aq-surface-card);
}
.aq-navcard-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: var(--aq-space-sm);
  box-sizing: border-box; /* same content-box overflow as .aq-prodcard-media img */
}
.aq-navcard-media .aq-eyebrow {
  color: var(--aq-text-eyebrow);
  text-align: center;
  padding-inline: var(--aq-space-sm);
}
.aq-navcard .aq-pcard-body h3 {
  color: var(--aq-text);
}
.aq-navcard-cta {
  color: var(--aq-text);
  margin-block-start: auto;
  padding-top: var(--aq-space-sm);
  transition: color var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-navcard:hover .aq-navcard-cta,
.aq-navcard:focus-within .aq-navcard-cta {
  color: var(--aq-content-link-color);
  text-decoration: underline;
  text-decoration-color: var(--aq-brand);
  text-underline-offset: var(--aq-link-underline-offset);
}

/* D2 — visible, dashed, explicitly NOT a link: no shadow, no CTA, alt surface. */
.aq-navcard--d2 {
  background: var(--aq-surface-alt);
  border-style: dashed;
  box-shadow: none;
  padding: 0;
  gap: 0;
}
.aq-navcard--d2 .aq-navcard-media {
  background: transparent;
}
.aq-navcard--d2 .aq-pcard-body {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-2xs);
  padding: var(--aq-space-md);
}
.aq-navcard-d2note {
  color: var(--aq-text-muted);
  font-size: var(--aq-fs-body-sm);
}

/* Category + platform grids: FIVE fixed tracks at desktop — geometry never
   depends on how many render (the count-independence rule). */
.aq-catgrid,
.aq-railgrid {
  grid-template-columns: 1fr;
}
@media (min-width: 48em) {
  .aq-catgrid,
  .aq-railgrid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 64em) {
  .aq-catgrid,
  .aq-railgrid {
    grid-template-columns: repeat(5, 1fr);
  }
}

/* Drones-view platform band (round-1 §9.3, owner design review 2026-08-20):
   CONTRACTS to the publishable hub count — a deliberate exception to the
   five-track count-independence rule above, which stays the overview rail's
   geometry. An inline cluster hugging its two cards today; grows
   card-by-card as hubs become publishable. */
.aq-railgrid--hug {
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-md);
}
.aq-railgrid--hug > .aq-card {
  flex: 0 1 var(--aq-card-min);
  max-width: var(--aq-card-min);
  width: 100%;
}

/* Industry #products section (round-1 §9.4): everything reuses existing
   vocabulary; only the category group-label rhythm is new.

   PADDING, not margin — and that is the whole reason this looked wrong.
   `.aq-stack.aq-stack > *` zeroes margin-block on every child because "gap is
   the ONLY spacing mechanism inside a stack", so the `margin: var(--aq-space-sm) 0 0`
   this rule used to carry had never once applied. Every group label sat on the
   stack's uniform 16px, identical to the space between a label and its own
   grid, so a new group never read as a new group (owner-reported 2026-09-27).
   Padding is not zeroed, so it survives the stack.

   The FIRST label is exempt: it follows the section's h2, which already has the
   stack gap. Only h3 children of the stack carry this class, so `:first-of-type`
   addresses exactly the first group. */
.aq-ind-products-group {
  font-size: var(--aq-fs-h3);
}
.aq-ind-products-group:not(:first-of-type) {
  padding-block-start: var(--aq-space-md);
}

/* ---------- product category view (P2b) ---------- */

/* Disambiguation context under the code line — only rendered on title
   collisions, sourced from Membership (build plan §15). */
.aq-prodcard-context {
  color: var(--aq-text-muted);
  font-size: var(--aq-fs-body-sm);
}

/* Server pagination (core paginate_links list markup). Links get real tap
   targets; the current page carries the brand underline (hubnav idiom). */
.aq-pagination ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--aq-space-2xs);
  list-style: none;
  margin: 0;
  padding: 0;
}
.aq-pagination .page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--aq-tap-min);
  min-height: var(--aq-tap-min);
  padding-inline: var(--aq-space-xs);
  color: var(--aq-text);
  text-decoration: none;
  border-bottom: var(--aq-space-3xs) solid transparent;
  transition: color var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-pagination a.page-numbers:hover,
.aq-pagination a.page-numbers:focus-visible {
  color: var(--aq-content-link-color);
}
.aq-pagination .page-numbers.current {
  color: var(--aq-content-link-color);
  border-bottom-color: var(--aq-brand);
}
.aq-pagination .page-numbers.dots {
  color: var(--aq-text-muted);
}

/* ---------- product detail (P3) ---------- */

/* Gallery/summary split — gallery leads (LCP surface); stacks below 64em.
   Same track model as the editorial split (gallery capped and shrinkable, the
   summary takes the remainder) but on the PDP's own, larger ceiling: the
   packshot is the content here, not a supporting photo. The gallery keeps its
   contain-fit behaviour and `align-items: start` — no aspect-ratio panel, since
   packshots must never be cropped. */
.aq-pdp-split {
  display: grid;
  grid-template-columns: min(45%, var(--aq-pdp-gallery-max)) minmax(0, 1fr);
  gap: var(--aq-space-lg);
  align-items: start;
}
@media (max-width: 47.99em) {
  .aq-pdp-split {
    grid-template-columns: 1fr;
  }
}
/* In the new tablet band the gallery track is only ~325–433px wide, and the
   presence floor below (26rem/416px) would stand a contain-fit packshot in a
   tall, mostly-empty box. The floor earns its keep once the gallery is wide;
   here the panel hugs the image instead. Phone and desktop are unaffected. */
@media (min-width: 48em) and (max-width: 63.99em) {
  .aq-pdp-primary {
    min-height: 0;
  }
}

/* Primary panel: packshots contain-fit on the card surface, never cropped. */
.aq-pdp-primary {
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--aq-media-split-min);
  border-radius: var(--aq-radius-card);
  background: var(--aq-surface-card);
  overflow: hidden;
}
.aq-pdp-primary img {
  width: 100%;
  height: auto;
  max-height: var(--aq-pdp-media-max);
  object-fit: contain;
  padding: var(--aq-space-md);
  box-sizing: border-box; /* same content-box overflow family as the other padded
                             image recipes: 100% + 2×24px padding made the packshot
                             48px WIDER than its panel, so `overflow:hidden` was
                             clipping 24px off each side of the product photo. */
}

/* Large viewer (2026-09-17): the image on stage is a real link to the full file
   (no-JS baseline); gallery.js opens PhotoSwipe from it. The link is the same
   box as its image, with a zoom cursor as the only hint. The library's own
   stylesheet (loaded on first click) styles the viewer; `.aq-pswp` carries the
   one AeroIQ adjustment — its icons in the theme's on-dark ink, not pure white. */
.aq-pdp-stage-link {
  display: block;
  width: 100%;
  cursor: zoom-in;
  border-radius: var(--aq-radius-card);
}
.aq-pdp-stage-link:focus-visible {
  outline: var(--aq-bw-2) solid var(--aq-brand);
  outline-offset: var(--aq-space-3xs);
}
.aq-pswp {
  --pswp-icon-color: var(--aq-text-on-dark);
  --pswp-bg: var(--aq-surface-dark); /* opaque (gallery.js bgOpacity 1): the same on-dark surface
                                        as the hub bands; a translucent backdrop let the sticky
                                        header's frosted layer composite through (measured) */
}

/* The gallery is a grid item; a nowrap thumbnail row would otherwise set its
   min-content width to 37 tiles (measured: a 3047px stage at 390px), so the
   item must be allowed to shrink. Same family as every scroller-in-a-grid fix. */
.aq-pdp-gallery {
  min-width: 0;
  max-width: 100%;
}

/* Stage: the primary panel plus the prev/next arrows (gallery v2, 2026-09-17).
   The arrows ship `hidden` and gallery.js reveals them; with JS off the
   thumbnails are the only controls, exactly as before. */
.aq-pdp-stage {
  position: relative;
  touch-action: pan-y; /* a horizontal drag is a swipe, not a scroll: without this
                          Chrome fires pointercancel once it starts panning and the
                          swipe never completes (harness-found, 2026-09-17). */
}
.aq-pdp-stage:focus-visible {
  outline: var(--aq-bw-2) solid var(--aq-brand);
  outline-offset: var(--aq-space-3xs);
  border-radius: var(--aq-radius-card);
}
.aq-pdp-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--aq-tap-min);
  height: var(--aq-tap-min);
  padding: 0;
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-surface-glass-panel);
  color: var(--aq-text);
  box-shadow: var(--aq-shadow-card);
  cursor: pointer;
  transition: color var(--aq-motion-fast) var(--aq-ease-standard), border-color var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-pdp-arrow--prev { left: var(--aq-space-sm); }
.aq-pdp-arrow--next { right: var(--aq-space-sm); }
.aq-pdp-arrow--next svg { transform: scaleX(-1); }
.aq-pdp-arrow:hover,
.aq-pdp-arrow:focus-visible {
  color: var(--aq-brand-ink);
  border-color: var(--aq-brand);
}

/* Thumbnails: real links (no-JS baseline); active carries aria-current.
   Fixed square tiles — an unconstrained a>img{width:100%} chain balloons each
   item to the container width (caught in the C4 sweep). v2: ONE scrolling row
   with snap points instead of wrapping to as many rows as a kit has photos
   (client, 2026-09-16); overflow-y hidden for the same 1px reason as the tab
   bars. gallery.js keeps the current item scrolled into view. */
.aq-pdp-thumbs {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--aq-space-2xs);
  margin: var(--aq-space-sm) 0 0;
  padding: 0 0 var(--aq-space-2xs);
  list-style: none;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}
.aq-pdp-thumbs li {
  flex: 0 0 auto;
  scroll-snap-align: start;
}
.aq-pdp-thumb-video {
  position: relative;
}
.aq-pdp-thumb-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--aq-scrim-ink);
  color: var(--aq-text-on-dark);
  font-size: var(--aq-fs-h3);
}

/* "Photos | Video" toggle under the strip (the DJI Store pattern). */
.aq-pdp-modes {
  display: flex;
  justify-content: center;
  gap: var(--aq-space-xs);
  margin-top: var(--aq-space-sm);
}
.aq-pdp-mode {
  display: inline-flex;
  align-items: center;
  gap: var(--aq-space-2xs);
  min-height: var(--aq-tap-min);
  padding: var(--aq-space-2xs) var(--aq-space-md);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-surface-card);
  color: var(--aq-text);
  font: inherit;
  font-size: var(--aq-fs-body-sm);
  font-weight: var(--aq-fw-medium);
  cursor: pointer;
  transition: background-color var(--aq-motion-fast) var(--aq-ease-standard), color var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-pdp-mode[aria-pressed="true"] {
  background: var(--aq-surface-dark);
  border-color: var(--aq-surface-dark);
  color: var(--aq-text-on-dark);
}
.aq-pdp-mode:hover:not([aria-pressed="true"]),
.aq-pdp-mode:focus-visible {
  border-color: var(--aq-brand);
}

/* Video mode: the stage becomes a 16:9 panel; the facade sits over the poster
   and the player (created by media.js) fills the same box. */
.aq-pdp-stage.is-video .aq-pdp-primary {
  aspect-ratio: 16 / 9;
  min-height: 0;
  background: var(--aq-surface-dark);
}
.aq-pdp-facade-stage {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: var(--aq-radius-card);
  background: var(--aq-scrim-ink);
  color: var(--aq-text-on-dark);
  font-size: var(--aq-fs-display);
  cursor: pointer;
}
.aq-pdp-facade-stage:hover,
.aq-pdp-facade-stage:focus-visible {
  color: var(--aq-brand);
}
.aq-pdp-stage.is-video .aq-pdp-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
}
.aq-pdp-thumbs a {
  display: block;
  width: calc(var(--aq-tap-min) * 1.6);
  height: calc(var(--aq-tap-min) * 1.6);
  border-radius: var(--aq-radius-sm);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  background: var(--aq-surface-card);
  overflow: hidden;
}
.aq-pdp-thumbs a[aria-current="true"] {
  border-color: var(--aq-brand);
}
.aq-pdp-thumbs img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The v1 standalone "Watch the video" button under the gallery is gone (v2 puts
   the video in the gallery); .aq-pdp-facade-icon and .aq-pdp-video stay. */
.aq-pdp-facade-icon {
  color: var(--aq-brand-ink);
}
.aq-pdp-video {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  margin-top: var(--aq-space-sm);
  border-radius: var(--aq-radius-card);
}

/* Identity + meta row. The card stock chip is absolutely positioned for its
   media panel — in the PDP meta row it flows inline instead. */
.aq-pdp-meta {
  display: flex;
  align-items: center;
  gap: var(--aq-space-md);
  flex-wrap: wrap;
}
.aq-pdp-meta .aq-prodcard-stock {
  position: static;
}
/* Price + its GST position are ONE flex child of .aq-pdp-meta (owner decision
   2026-09-21). Stacked, not inline: the meta row's gap also separates the stock
   badge, so an inline label would read as a third badge rather than as part of
   the price. The tight 3xs gap is what binds the two lines together. */
.aq-pdp-price-group {
  display: inline-flex;
  flex-direction: column;
  gap: var(--aq-space-3xs);
}
.aq-pdp-price {
  font-size: var(--aq-fs-h3);
  font-weight: 600;
}
.aq-pdp-tax {
  font-size: var(--aq-fs-body-sm);
  font-weight: var(--aq-fw-regular);
  color: var(--aq-text-muted);
  line-height: var(--aq-lh-tight);
}
.aq-pdp-price--request {
  color: var(--aq-text-muted);
  font-weight: 500;
}

/* Specifications — workbook-shape table; wide content scrolls inside.
   The table reads as a CONTAINED object: the border + radius sit on the scroll
   wrapper (a collapsed table cannot carry a radius itself), which also means the
   rounded corners clip the scroll area. The measure cap moved here with them so
   the box and the content it holds are the same width. */
.aq-spec-scroll {
  overflow-x: auto;
  /* THE ONE-COLUMN CEILING, same as `.aq-faqgroup` (owner, 2026-09-26). A spec
     table is standalone one-column content, so it takes the width every other
     standalone block on the site now takes. It was on its own 52rem ceiling,
     which left it 208px narrower than the accordion directly below it on the
     same page — two boxes of the same kind ending in different places. */
  max-width: var(--aq-heading-max);
  background: var(--aq-surface-page);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-sm);
}
.aq-spec-table {
  width: 100%;
  border-collapse: collapse;
}
.aq-spec-table th,
.aq-spec-table td {
  text-align: left;
  padding: var(--aq-space-sm) var(--aq-space-md);
  border-bottom: var(--aq-bw-1) solid var(--aq-hairline-light);
  vertical-align: top;
}
/* The final row's rule would otherwise double with the container's own border. */
.aq-spec-table tr:last-child th,
.aq-spec-table tr:last-child td {
  border-bottom: 0;
}
.aq-spec-table th[scope="row"] {
  color: var(--aq-text-muted);
  font-weight: 500;
  width: 40%;
}
/* STICKY GROUP HEADINGS WERE TRIED HERE AND DO NOT WORK — do not retry without
   changing the scroll model. At manufacturer scale (measured: 107 rows in 15
   groups = a 10,243px table, ~26 phone screens) a sticky group heading would
   help, but `.aq-spec-scroll` sets `overflow-x: auto`, which makes the box a
   scrollport on BOTH axes; the heading then sticks to a box that never scrolls
   vertically. Measured stuck-at-y of -215px, i.e. scrolled away. There is no
   CSS-only fix that also keeps the horizontal-overflow wrapper wide tables
   need, and the client was explicit that specs need no collapsing or
   interaction — "all I care about is putting them there". Nothing is broken at
   scale; this was an optional aid that the scroll model rules out. */
/* COMPARISON TABLES AND THE NOTES APPENDIX (client hub documents, 2026-09-22).
   All five DONE documents carry both, and neither is prose: the Matrice 4 and
   4D documents compare two models column by column, DJI Terra carries six
   tables including a five-column licence matrix, and every document ends with
   DJI's numbered test conditions. Nothing new is designed here — the composer's
   text section already renders HTML through wp_kses_post, and the workbook
   table above is already the site's table. These three rules are what those two
   shapes need that prose does not. */

/* The composer's "Full width" flag. `.aq-measure` is the 75ch reading column
   and stays the default for text; a licence matrix inside it would scroll
   sideways for no reason, so this releases the cap — and with it the table
   wrapper's own narrower prose cap. */
.aq-prodsec-wide > .aq-spec-scroll,
.aq-prodsec-wide .aq-spec-scroll {
  max-width: 100%;
}
/* The wrapper is a focusable scroll region (see the emitters), so it needs a
   visible focus ring like every other tab stop. */
.aq-spec-scroll:focus-visible {
  outline: var(--aq-focus-ring-width) var(--aq-focus-ring-style) var(--aq-focus-ring-color);
  outline-offset: var(--aq-space-3xs);
}
/* A column-header row. The PDP's specification table is two columns of
   row-label + value and never needed one; a model comparison is meaningless
   without it. Same weight and rule as the group heading so the two table
   shapes read as one component. */
/* A column-headed table sizes its own columns. The 40% row-label width above
   belongs to the PDP's label + value pair, not to a three- or five-column
   comparison. */
.aq-spec-table--cols th[scope="row"] {
  width: auto;
}
.aq-spec-table thead th {
  font-weight: var(--aq-fw-bold);
  color: var(--aq-text);
  border-bottom-color: var(--aq-text);
  vertical-align: bottom;
}
/* Notes. Sixteen to twenty-six numbered manufacturer test conditions belong on
   the page — they are what makes "59 minutes" and "40 km" honest — but not at
   body size under every hub. Closed by default, native <details>, so it works
   with no JavaScript and is reachable by keyboard and screen reader. */
.aq-notes {
  margin-top: var(--aq-space-lg);
  font-size: var(--aq-fs-body-sm);
  color: var(--aq-text-muted);
}
.aq-notes > summary {
  cursor: pointer;
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-eyebrow);
  padding: var(--aq-space-2xs) 0;
}
.aq-notes > summary:focus-visible {
  outline: var(--aq-focus-ring-width) var(--aq-focus-ring-style) var(--aq-focus-ring-color);
  outline-offset: var(--aq-space-3xs);
}
.aq-notes-list {
  margin: var(--aq-space-2xs) 0 0;
  padding-left: 0;
  line-height: var(--aq-lh-body);
  /* Numbered by hand, not by the list — a dropped note must leave its gap
     rather than renumber the references the body copy makes. */
  list-style: none;
}
.aq-notes-num {
  color: var(--aq-text);
  font-variant-numeric: tabular-nums;
}
.aq-notes-list > li {
  margin-bottom: var(--aq-space-2xs);
}
/* A note with the client's own enumerated exceptions — DJI's obstacle-
   avoidance warranty note is written that way. Its clauses carry their own
   "(1)" numerals, so the list supplies none. */
.aq-notes-sub {
  margin: var(--aq-space-3xs) 0 0;
  padding-left: var(--aq-space-sm);
  list-style: none;
}
.aq-notes-pre {
  margin: 0 0 var(--aq-space-2xs);
}

.aq-spec-group th {
  padding-top: var(--aq-space-md);
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-brand-ink);
  border-bottom-color: var(--aq-text);
}

/* Key-spec highlights strip (product-data research 2026-08-28): the flagged
   Core subset as compact stat chips in the PDP summary column. Conditional —
   no flagged rows, no strip. */
.aq-pdp-keyspecs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--aq-keyspec-min), 1fr));
  gap: var(--aq-space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}
.aq-pdp-keyspecs li {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-3xs);
  padding: var(--aq-space-2xs) var(--aq-space-xs);
  background: var(--aq-surface-alt);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-sm);
}
.aq-pdp-keyspec-label {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-muted);
}
.aq-pdp-keyspec-value {
  font-family: var(--aq-font-heading);
  font-weight: var(--aq-fw-bold);
}

/* PDP SECTION TABS — the Platform Hub's bar, reused verbatim (.aq-hubnav), so
   the site has ONE tab interaction rather than two that merely look alike
   (owner 2026-08-28). Only the modifier below is PDP-specific: the bar scrolls
   sideways rather than wrapping, because five section labels are longer than
   the hub's short group names. Panels themselves need no styling — the
   `hidden` attribute does the work, exactly as on the hub. */
.aq-pdp-tabs ul {
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden; /* same 1px underline overflow as .aq-hubnav ul above */
}
.aq-pdp-tabs a {
  white-space: nowrap;
}
.aq-pdp-sections {
  scroll-margin-top: var(--aq-space-2xl);
}

/* In-the-box group labels: the client's "Basic Kit contents" / "Additional in
   the Essentials Combo" lines (a trailing colon in the field marks them). */
.aq-pdp-boxlist .aq-pdp-boxgroup {
  list-style: none;
  margin-top: var(--aq-space-sm);
  padding-left: 0;
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-eyebrow);
}
.aq-pdp-boxlist .aq-pdp-boxgroup::before {
  content: none;
}

/* ---------- FAQ tab (round-2 ¶27) ----------
   A fourth panel beside Overview / In the box / Specifications, holding titled
   GROUPS of plain accordions. Client's words: "something simple easily
   achievable but also simple for client to use" — and explicitly not the
   nested tabs in the screenshot they were shown.

   ONE disclosure layer. The tab is already a disclosure; a second one inside it
   is the thing that was rejected. Native <details>/<summary>, so it opens with
   no JavaScript, is keyboard operable and announces expanded state for free —
   the same pair `.aq-caplist-toggle` already dresses, down to the brand-ink
   caret, so there is no second disclosure language on the site. */
/* Held to the reading measure. The panel is as wide as the page, and a
   full-width summary row puts the caret a screen away from the question it
   belongs to. */
.aq-faqgroup {
  /* A standalone one-column row, so it takes the one-column ceiling rather than
     the prose measure (owner, 2026-09-26). The answers inside no longer carry a
     cap of their own: this container is what controls their width. */
  max-width: var(--aq-heading-max);
}
.aq-faqgroup + .aq-faqgroup {
  margin-block-start: var(--aq-space-xl);
}
.aq-faqgroup-title {
  margin-block: 0 var(--aq-space-2xs);
}
.aq-faq {
  border-block-end: var(--aq-bw-1) solid var(--aq-hairline-light);
}
.aq-faq-q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--aq-space-md);
  min-height: var(--aq-tap-min);
  padding-block: var(--aq-space-2xs);
  cursor: pointer;
  color: var(--aq-text);
  font-weight: var(--aq-fw-medium);
  /* Replaced by the caret below so it can take the brand colour and rotate;
     the element stays a real <summary>, so focus and the expanded/collapsed
     announcement are untouched and the global :focus-visible ring applies. */
  list-style: none;
}
.aq-faq-q::-webkit-details-marker {
  display: none;
}
.aq-faq-q:hover {
  color: var(--aq-content-link-color);
}
.aq-faq-q::after {
  content: "";
  flex: 0 0 auto;
  width: var(--aq-space-2xs);
  height: var(--aq-space-2xs);
  border-right: var(--aq-bw-2) solid var(--aq-brand-ink);
  border-bottom: var(--aq-bw-2) solid var(--aq-brand-ink);
  transform: rotate(45deg) translateY(calc(var(--aq-space-3xs) * -0.5));
  transition: transform var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-faq[open] > .aq-faq-q::after {
  transform: rotate(-135deg) translateY(calc(var(--aq-space-3xs) * -0.5));
}
.aq-faq-a {
  padding-block: 0 var(--aq-space-sm);
  color: var(--aq-text-muted);
}
.aq-faq-a > :first-child {
  margin-block-start: 0;
}
.aq-faq-a > :last-child {
  margin-block-end: 0;
}
/* `hidden` MUST WIN over the panel's own display.
   The hub's panels carry no CSS at all, so the browser's `[hidden]{display:none}`
   governs them. The PDP panels also carry `.aq-stack` (display:flex) for their
   internal spacing — and a class selector outranks the UA attribute rule, so
   every panel rendered at once and the tabs did nothing (2026-08-28). The
   attribute was always correct; only the paint was wrong, which is why a test
   asserting `hasAttribute('hidden')` passed while the page was visibly broken.
   Same shape as the existing `.aq-header-search[hidden]` fix. The hub is listed
   too, so adding a display rule there can never silently break it. */
.aq-pdp-panel[hidden],
.aq-hub-panel[hidden] {
  display: none;
}
/* Panel content starts close to the bar that opened it — a panel IS the tab's
   content, so a full section rhythm between them reads as a separate page
   region. */
.aq-pdp-panel {
  padding-top: var(--aq-space-lg);
}

/* NZ operational note — optional authored aside (verify-before-publish).
   Uses the SHIPPED boxed-content recipe (tint surface + hairline + uniform
   radius), the same one the key-spec chips directly above it use.
   It previously had an orange left bar and a radius on only two corners — a
   generic blockquote idiom borrowed from outside this design system: the theme
   contained exactly ONE `border-left` and ONE asymmetric `border-radius`, both
   in this rule, and the design system defines no callout pattern (owner
   challenge 2026-08-28). The orange accent was also redundant — the eyebrow's
   dot already carries it. */
.aq-pdp-nznote {
  background: var(--aq-surface-alt);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-sm);
  padding: var(--aq-space-md) var(--aq-space-lg);
}
.aq-pdp-nznote p:last-child {
  margin-bottom: 0;
}

/* Search results — result ROWS, not a bare browser list (2026-08-28). Header
   search is a first-class control, so this surface gets the same card language
   as the rest of the catalogue. */
.aq-searchgroup + .aq-searchgroup {
  margin-top: var(--aq-space-2xl);
}
.aq-searchlist {
  display: grid;
  gap: var(--aq-space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}
.aq-searchrow {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--aq-space-3xs) var(--aq-space-md);
  padding: var(--aq-space-sm) var(--aq-space-md);
  background: var(--aq-surface-page);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-sm);
  text-decoration: none;
  color: inherit;
}
.aq-searchrow .aq-eyebrow,
.aq-searchrow-title,
.aq-searchrow-excerpt {
  grid-column: 1;
}
.aq-searchrow-title {
  font-family: var(--aq-font-heading);
  font-weight: var(--aq-fw-bold);
}
.aq-searchrow-excerpt {
  color: var(--aq-text-muted);
  font-size: var(--aq-fs-body-sm);
}
.aq-searchrow-cue {
  grid-column: 2;
  grid-row: 1 / -1;
  color: var(--aq-brand-ink);
}
.aq-searchrow:hover,
.aq-searchrow:focus-visible {
  border-color: var(--aq-brand);
}

/* Compatibility facts + hub links — two sections, two sources. */
.aq-pdp-compat {
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}
.aq-pdp-compat li {
  padding: var(--aq-space-3xs) var(--aq-space-sm);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-surface-card);
  font-size: var(--aq-fs-body-sm);
}
.aq-pdp-hubs ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-lg);
  margin: 0;
  padding: 0;
  list-style: none;
}
.aq-pdp-hubs a {
  display: inline-flex;
  align-items: center;
  min-height: var(--aq-tap-min);
  color: var(--aq-content-link-color);
  text-decoration: none;
}
.aq-pdp-hubs a:hover,
.aq-pdp-hubs a:focus-visible {
  text-decoration: underline;
}

/* ---------- products mega menu (P4) ---------- */

/* The header row anchors the desktop dropdown. */
.aq-header-row {
  position: relative;
}

/* Products toggle (JS-replaced link — no-JS keeps the plain link). Core's
   own toggle classes + a CLONED core submenu icon keep it visually identical
   to the Services toggle; the icon flips with the open state (core idiom). */
.aq-mega-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--aq-space-3xs);
  min-height: var(--aq-tap-min);
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.aq-mega-btn .wp-block-navigation__submenu-icon {
  display: inline-flex;
  transition: transform var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-mega-open .aq-mega-btn .wp-block-navigation__submenu-icon {
  transform: rotate(180deg);
}

/* Desktop (≥64em): a full-width band under the header (Figma 6:84). The
   panel is position:FIXED — core navigation wraps its items in FOUR
   position:relative ancestors (item/container/dialog/responsive/nav), so any
   absolute panel gets trapped at nav width (caught live: overlapping
   columns). Fixed escapes the whole chain; mega.js measures the header
   bottom into --aq-mega-top on each open and closes the panel on scroll. */
@media (min-width: 64em) {
  .aq-mega-panel {
    position: fixed;
    left: 0;
    right: 0;
    top: var(--aq-mega-top, 4.5rem);
    z-index: 40;
    background: var(--aq-surface-page);
    border-top: var(--aq-bw-1) solid var(--aq-hairline-light);
    border-bottom: var(--aq-bw-1) solid var(--aq-hairline-light);
    box-shadow: var(--aq-shadow-card);
  }
  .aq-mega-inner {
    display: flex;
    flex-direction: column;
    gap: var(--aq-space-md);
    /* Bottom space now belongs to .aq-mega-foot, which follows this block. */
    padding-block: var(--aq-space-lg) var(--aq-space-md);
    /* padding-inline comes from THE shared shell rule below — never set here. */
  }
  /* The band's footer. It sits OUTSIDE .aq-mega-inner precisely so the
     menu-item anchor inset does not reach it — owner directive 2026-08-31:
     the bottom bar keeps its original full-width position while the menu
     items move to the anchor. padding-inline is the plain shell gutter, from
     the shared rule below. */
  .aq-mega-foot {
    /* Owner 2026-09-05: the gap between the hairline and the link text must
       equal the gap between the text and the panel's bottom edge. The row's
       links are min-height tap targets with centred text, so their intrinsic
       half-leading is symmetric — equality therefore reduces to matching this
       bottom padding with .aq-mega-assist's padding-top. */
    padding-block: 0 var(--aq-space-sm);
  }
  /* v2 (round-1 B §9.1, owner design review 2026-08-20): the placed grid
     becomes a flex column — the LAUNCH state is the primary composition, and
     capacity is the SAME composition with more items (the cluster gains
     columns), never a different layout. */
  .aq-mega-browse {
    margin: 0;
  }
  /* The cluster SPREADS across the shell rather than packing left.
     As a wrapping flex row the columns bunched against the left edge and left
     roughly half of a 1440px panel empty, with the page ghosting through the
     blank half (owner screenshots 2026-08-31). A grid gives every column an
     equal share of the width it already occupies, so the panel reads as a
     composition instead of an offcut. auto-fit keeps the LAUNCH state (few
     columns) and the capacity state (more columns) the same composition, which
     is the rule this cluster was built on. */
  .aq-mega-cluster {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--aq-mega-col-min), 1fr));
    align-items: start;
    gap: var(--aq-space-md) var(--aq-space-2xl);
  }
  .aq-mega-cluster .aq-mega-col {
    min-width: 0;
  }
  /* Assistance row v3 (owner 2026-09-05): "Talk to us about fit" LEFT,
     "Browse all Products" RIGHT — plain DOM order, the old row-reverse went
     with the retired D2 mention. The same grammar the two nav bands now use:
     content · hairline · browse action bottom-right. */
  .aq-mega-assist {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--aq-space-sm) var(--aq-space-2xl);
    border-top: var(--aq-bw-1) solid var(--aq-hairline-light);
    padding-top: var(--aq-space-sm);
  }
  .aq-mega-assist .aq-mega-fit,
  .aq-mega-assist .aq-mega-browse {
    margin: 0;
  }
  /* PEER columns (owner live-review 2026-08-21): categories sit IN the cluster
     beside platforms/industries/brands, none of the dimensions reading as "the
     main architecture". The ROW treatment that used to live here is now on
     `.aq-mega-tile` itself and applies to every column at every width. */
}

/* THE shared shell rule — Milestone A owns this geometry permanently; B consumes
   it and never redefines it (client-feedback-round-1 L5). PRECISION: this rule
   targets the fixed bands + the header-search overlay inner (its consumers —
   mega inner · submenu container · search overlay). The header
   is NOT in this selector — its edge comes from core's alignwide layout math
   (max(root gutter, centring space at wideSize)), which is the SAME formula, so
   all three edges land identical (probe-proven 1024–1920; they diverged
   32/48/80 @1440 before this rule). If core's layout math ever changes,
   re-measure the three edges — never assume the header follows this rule. */
@media (min-width: 64em) {
  .aq-mega-inner,
  .aq-mega-foot,
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container,
  .aq-header-search__inner {
    padding-inline: max(var(--aq-gutter), calc((100% - var(--aq-shell-max)) / 2));
  }
  /* THE DROPDOWN CONTENT STARTS UNDER ITS OWN MENU ITEM.
     Client request, verbatim: "Sub-Menu items needs to start from aligning left
     with main menu item". Read previously as the shared SHELL edge, which is a
     different edge entirely — 41px, while "Services" sits at 460px, so every
     panel began 419px left of the trigger that opened it.

     The band stays full width (owner 2026-08-10); only the content moves.
     mega.js measures the open trigger into --aq-mega-align.

     CLAMPED, because alignment must yield to legibility: never left of the
     shell gutter, and never so far right that the panel has less than
     --aq-mega-content-min to work with. A far-right item on a narrow desktop
     therefore stops short of perfect alignment rather than crushing its own
     columns. The right edge is untouched and stays on the shell. */
  .aq-mega-inner,
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container {
    padding-inline-start: clamp(
      max(var(--aq-gutter), calc((100% - var(--aq-shell-max)) / 2)),
      var(--aq-mega-align, 0px),
      max(var(--aq-gutter), calc(100% - var(--aq-mega-content-min)))
    );
  }
}

/* Desktop dropdown FAMILY: the Services submenu was core's raw default
   (generic rgba border, black links, zero padding) — one design language
   for both dropdowns: the panel's surface tokens on the submenu box, the
   panel's link treatment, and the Browse-all first entry styled exactly
   like its Products twin. */
@media (min-width: 64em) {
  /* Owner 2026-08-10: the Services dropdown is a FULL-WIDTH BAND like the
     Products panel (same fixed mechanism escaping core's positioned-ancestor
     chain; the top anchor is one root-level --aq-mega-top set by mega.js for
     both). Items flow in columns of MAX FOUR; the inner content aligns to
     the same shell constraint as the mega panel. */
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container {
    position: fixed;
    left: 0;
    right: 0;
    top: var(--aq-mega-top, 4.5rem);
    z-index: 40;
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: repeat(4, min-content);
    justify-content: start;
    column-gap: var(--aq-space-2xl);
    row-gap: 0;
    background: var(--aq-surface-page);
    border-top: var(--aq-bw-1) solid var(--aq-hairline-light);
    border-bottom: var(--aq-bw-1) solid var(--aq-hairline-light);
    box-shadow: var(--aq-shadow-card);
    /* Bottom padding matches the browse footer's padding-top (owner
       2026-09-05 spacing equality — see the li:first-child rule below). */
    padding-block: var(--aq-space-lg) var(--aq-space-sm);
    /* padding-inline comes from THE shared shell rule above — never set here. */
  }
  /* "Browse all X →" is a PANEL ACTION, not a list entry — the same thing
     "Browse all Products" is in the mega panel, where it is a sibling ABOVE
     the columns. Here the markup is one flat <ul> (core's submenu), so it was
     landing in the first cell of column 1: the item columns then started at
     different heights and the first entry of column 2 sat level with the
     browse link (owner catch 2026-09-02: "shop all in products menu is on top
     and other items horizontally align, but that's not the case in the other
     two dropdowns").

     Giving the grid an explicit column count lets the first row span it. The
     four template rows above then read as: row 1 = the browse link, rows 2-4 =
     three items per column — which is exactly 6 items in two balanced columns
     for both Services and Industries. A seventh item would open an implicit
     third column and the link would span the first two; degraded, not broken.
     Column flow is unchanged, so items still read DOWN each column as before. */
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container {
    /* `max-content`, NOT `minmax(0, max-content)`: the zero minimum let the
       first track collapse to the 64px media slot on Industries, so the
       labels wrapped and column 2 overlapped it (measured both ways; plain
       max-content reproduces the item column positions exactly).
       The trailing 1fr is a FILLER track (owner 2026-09-05): the browse
       footer below spans 1/-1, and without the filler that span would stop at
       the packed max-content columns instead of reaching the shell edge — the
       hairline would be as short as the item block, where the Products
       panel's runs the full content width. Items never enter the filler while
       they fit the 3 template rows; a seventh item would, which is the same
       documented degraded-not-broken state as before. */
    grid-template-columns: max-content max-content 1fr;
    grid-template-rows: repeat(3, min-content);
  }
  /* "Browse all X →" is the band's FOOTER ACTION now (owner 2026-09-05):
     bottom-right, under a full-width hairline — the same grammar as the
     Products panel's foot. Same equality rule: padding above the link
     (--aq-space-sm) matches the band's bottom padding below it. The item
     columns keep 3 rows each, so Services' and Industries' six items still
     balance three-per-column exactly as they did with the old top row. */
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > li:first-child {
    grid-row: 4;
    grid-column: 1 / -1;
    display: flex;
    justify-content: flex-end;
    margin-block-start: var(--aq-space-md);
    padding-top: var(--aq-space-sm);
  }
  /* The footer hairline spans the SHELL, exactly like the Products panel's
     (owner 2026-09-05: "the bottom line needs to be across as it is in the
     product menu" — and the follow-up screenshots after the first attempt).
     ⚠ IT CANNOT HANG OFF THE LI AT ALL: a border stops at the content inset,
     and an absolutely positioned pseudo of the li resolves against the LI,
     because CORE makes nav items position:relative for submenu positioning —
     which is exactly how attempt one painted a line 40px into the inset, level
     with the link text. The owner's screenshots caught it; the probe did not,
     because it computed the position from the assumed containing block instead
     of measuring the painted box.
     The CONTAINER's own ::after has no such ambiguity — its containing block
     is the fixed container itself. left/right reuse the shell-gutter
     expression; the vertical offset is pure tokens: band bottom padding
     (--aq-space-sm) + the anchor's tap height (--aq-tap-min) + the li's
     padding-top (--aq-space-sm) = the li's border-top line. Holds while the
     browse label stays one line, which a ~160px label at ≥64em always is. */
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container::after {
    content: '';
    position: absolute;
    left: max(var(--aq-gutter), calc((100% - var(--aq-shell-max)) / 2));
    right: max(var(--aq-gutter), calc((100% - var(--aq-shell-max)) / 2));
    /* -2px: measured optical correction, not taste. Pixel-scanning the
       rendered footer (DOM-bounded, underline excluded) read the bands at
       35px line->glyph-top vs 33px glyph-bottom->band-bottom, where the
       Products foot reads a symmetric 34/34 — the band's grid row rounds the
       anchor seat a pixel lower. Dropping the line two pixels makes the band
       33/33. Re-measure before ever changing this. */
    bottom: calc(var(--aq-space-sm) * 2 + var(--aq-tap-min) - 2px);
    border-top: var(--aq-bw-1) solid var(--aq-hairline-light);
  }
  /* The anchor arrives full-width (measured 858px for a 150px label), so
     flex-end had nothing to push and the label sat LEFT inside a stretched
     box. Content-size it; the li's flex-end then lands it on the shell edge. */
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > li:first-child > a.wp-block-navigation-item__content {
    flex: 0 0 auto;
    width: auto;
    /* The item rows carry padding-block: --aq-space-3xs so their 64px media
       slots don't abut — the lone footer link doesn't, and that 8px was
       inflating the anchor to 52px against the 44px (--aq-tap-min) the
       container ::after's offset counts. Zeroed; and the TYPE matches the
       Products foot action (.aq-sec-link: body-sm/medium) so the centred
       text's half-leading — and therefore the painted line→text and
       text→bottom gaps — are the SAME NUMBER on all three menus. Measured:
       bands were 22/30, then 26/26; with both fixes every footer reads 30/30
       at 1280, 1440 and 1920. */
    padding-block: 0;
    font-size: var(--aq-fs-body-sm);
    font-weight: var(--aq-fw-medium);
    /* `normal`, not a token: it is what the Products foot action computes
       (18px ink at body-sm), and the last 2px of cross-menu drift was the
       band inheriting body 1.6 instead. Same source, same number. */
    line-height: normal;
  }
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container .wp-block-navigation-item {
    margin: 0;
    padding: 0;
  }
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > .wp-block-navigation-item > a.wp-block-navigation-item__content {
    display: inline-flex;
    align-items: center;
    min-height: var(--aq-tap-min);
    /* Was `padding: 0`. Rows now carry a 64px media slot, and with zero block
       padding consecutive slots abut into one continuous grey column. This
       rule OWNS the padding — setting it from a lower-specificity selector
       elsewhere silently lost. */
    padding-block: var(--aq-space-3xs);
    padding-inline: 0;
    color: var(--aq-text);
  }
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > .wp-block-navigation-item > a.wp-block-navigation-item__content:hover,
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > .wp-block-navigation-item > a.wp-block-navigation-item__content:focus-visible {
    color: var(--aq-content-link-color);
  }
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > li:first-child > a.wp-block-navigation-item__content {
    color: var(--aq-content-link-color);
  }
}

/* Category tiles (desktop): thumbnail + label cards per Figma 6:84. */
/* A COLUMN of rows, not a wrapping tile grid. The column behaviour used to be
   supplied by the cats-only override; with the row promoted to the component
   the list has to own it, or the items wrap two-up and the column reads as a
   grid again. */
.aq-mega-tiles {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-2xs);
  margin: var(--aq-space-2xs) 0 0;
  padding: 0;
  list-style: none;
}
/* THE menu row — media left, label beside it. ONE row for every menu item in
   every menu (owner directive 2026-08-31, verbatim: "for all menu items I want
   image left and text next to it as it was already for Product categories …
   apply it on all menu items").

   This is not new. It was `.aq-mega-col--cats .aq-mega-tile` inside the desktop
   media query, scoped to a single column — which is exactly why drone platforms
   rendered as bare text links and industries as large photo cards, and why the
   three columns read as three unrelated designs. The rule is promoted to the
   component and the column override is gone.

   No card chrome: the row is a link in a list, not a tile on a surface. Hover
   moves the LABEL, because there is no longer a border to move. */
.aq-mega-tile {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--aq-space-xs);
  min-height: var(--aq-tap-min);
  padding: 0;
  text-decoration: none;
  color: var(--aq-text);
  transition: color var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-mega-tile:hover,
.aq-mega-tile:focus-visible {
  color: var(--aq-content-link-color);
}
/* The slot renders even when empty, so a row with no artwork keeps the same
   indent and every label in the column starts on one line. */
.aq-mega-tile-media {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--aq-space-2xl);
  height: var(--aq-space-2xl);
  background: var(--aq-fill-tile);
  border-radius: var(--aq-radius-sm);
  overflow: hidden;
}
/* `cover`, for packshot and photograph alike, because the slot is square and
   both sources are now requested at a size that survives a square crop —
   `thumbnail` for a square packshot, `medium` for a landscape photograph. The
   bug being remembered here was `thumbnail` (WP's own 150×150 crop of a
   2000×1333 photo) floated small by `contain`; owner screenshots 2026-08-31. */
.aq-mega-tile-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Submenu destination thumbnails (round-2 item 11) — the Services and
   Industries dropdowns. Deliberately smaller than a mega tile: these sit in a
   dense link list, not a browse band, so the image identifies the destination
   without becoming the row. `cover`, not `contain`, because these are
   photographs rather than the packshots the tiles hold. A destination with no
   image simply has no span, and the row keeps its existing geometry. */
/* The core submenus (Services, Industries) take the SAME row as the mega
   panel — owner directive 2026-08-31. Identical slot geometry to
   `.aq-mega-tile-media`: a 64px square at --aq-radius-sm, 12px from its label,
   holding the indent whether or not a picture exists. Two histories here: it
   was 44×29, which read as a smudge; then 3:2, which was its own shape. It is
   now simply the menu row's slot, because there is one row. */
.aq-submenu-thumb {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--aq-space-2xl);
  height: var(--aq-space-2xl);
  margin-inline-end: var(--aq-space-xs);
  border-radius: var(--aq-radius-sm);
  overflow: hidden;
  background: var(--aq-fill-tile);
}
.aq-submenu-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.wp-block-navigation__submenu-container .wp-block-navigation-item__content {
  display: flex;
  align-items: center;
}
.aq-mega-tile-label {
  font-size: var(--aq-fs-body-sm);
}

/* Browse-all first entries — one treatment in BOTH dropdowns. */
.aq-mega-browse a {
  color: var(--aq-content-link-color);
  text-decoration: none;
}
.aq-mega-browse a:hover,
.aq-mega-browse a:focus-visible {
  text-decoration: underline;
}

/* D2 mention — a relationship, not a destination. */
.aq-mega-d2 {
  color: var(--aq-text-muted);
  font-size: var(--aq-fs-body-sm);
}

/* Hub links + the fit link. */
.aq-mega-list {
  display: flex;
  flex-direction: column;
  margin: var(--aq-space-2xs) 0 0;
  padding: 0;
  list-style: none;
}
.aq-mega-list a,
.aq-mega-fit a {
  display: inline-flex;
  align-items: center;
  min-height: var(--aq-tap-min);
  color: var(--aq-text);
  text-decoration: none;
}
.aq-mega-list a:hover,
.aq-mega-list a:focus-visible {
  color: var(--aq-content-link-color);
}
.aq-mega-fit a {
  color: var(--aq-content-link-color);
}
.aq-mega-fit a:hover,
.aq-mega-fit a:focus-visible {
  text-decoration: underline;
}

/* Frosted dropdown bands (owner 2026-08-13): the desktop bands wear the same
   glass as the bar — and inherit the same AA consequence (tokens.css record):
   orange/muted cannot hold contrast over an unknown scrolled backdrop, so on
   glass every accent renders INK with underline as the affordance. The mobile
   overlay (<64em) is a solid surface and keeps the brand-ink family; category
   tiles stay solid cards ON the glass. Direct backdrop-filter is safe here —
   the bands have no fixed/absolute descendants to contain. */
@media (min-width: 64em) {
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container,
  .aq-mega-panel {
    background: var(--aq-surface-glass-panel);
    -webkit-backdrop-filter: blur(var(--aq-blur-glass));
    backdrop-filter: blur(var(--aq-blur-glass));
  }
  @supports not ((backdrop-filter: none) or (-webkit-backdrop-filter: none)) {
    .wp-block-navigation .has-child .wp-block-navigation__submenu-container,
    .aq-mega-panel {
      background: var(--aq-surface-page);
    }
  }
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > .wp-block-navigation-item > a.wp-block-navigation-item__content:hover,
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > .wp-block-navigation-item > a.wp-block-navigation-item__content:focus-visible,
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > li:first-child > a.wp-block-navigation-item__content,
  .aq-mega-panel .aq-mega-browse a,
  .aq-mega-panel .aq-mega-fit a,
  .aq-mega-panel .aq-mega-list a:hover,
  .aq-mega-panel .aq-mega-list a:focus-visible,
  .aq-mega-panel .aq-mega-d2,
  .aq-mega-panel .aq-eyebrow {
    color: var(--aq-text);
  }
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > li:first-child > a.wp-block-navigation-item__content,
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > .wp-block-navigation-item > a.wp-block-navigation-item__content:hover,
  .wp-block-navigation .has-child .wp-block-navigation__submenu-container > .wp-block-navigation-item > a.wp-block-navigation-item__content:focus-visible,
  .aq-mega-panel .aq-mega-browse a,
  .aq-mega-panel .aq-mega-fit a,
  .aq-mega-panel .aq-mega-list a:hover,
  .aq-mega-panel .aq-mega-list a:focus-visible {
    text-decoration: underline;
  }
}

/* Mobile overlay gutters + GROUPING (owner, P4 review). The grouping METHOD
   is the project's shipped grouped-link-list — the FOOTER (aq-footer-head /
   aq-footer-links): eyebrow group header + clustered muted links (ink on
   hover) + pure spacing. No borders, no bands — that vocabulary belongs to
   page sections, not link lists. Hierarchy, one vocabulary, two levels:
     top-level nav items  = eyebrow treatment in INK (actionable anchors);
     sub-group headers    = the muted eyebrow (identical to aq-footer-head);
     children             = the footer-links treatment (muted -> ink, xs gap).
   The overlay dialog carries the SAME --aq-gutter as root content padding. */
@media (max-width: 63.999em) {
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-dialog {
    padding: var(--aq-space-md) var(--aq-gutter) var(--aq-space-lg);
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container {
    gap: var(--aq-space-xl);
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container > .wp-block-navigation-item {
    display: flex;
    flex-direction: row; /* core forces column in the overlay — label+chevron sit inline */
    flex-wrap: wrap;
    align-items: center;
    align-self: stretch; /* core's justification var shrink-centers items otherwise */
    gap: 0 var(--aq-space-3xs);
  }
  /* Core stretches its overlay toggle full-width, wrapping the sibling
     chevron to the next line — pin toggles to content width so label and
     chevron sit inline on both items. */
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation-item__content {
    width: auto;
    flex: 0 0 auto;
  }
  /* Tap target: core's overlay toggles measured 65x19 — under this site's own
     --aq-tap-min and under the WCAG 2.5.8 floor, while the Products button
     beside them was already 44. The chevron is a SIBLING of the toggle, so it
     cannot be sized into the button; mega.js delegates the tap from the whole
     header row instead, and this gives the row the height to match. */
  /* ...and the plain top-level LINKS take the same row height (owner,
     2026-09-30). Only the toggles had it, so About and Contact were 19px rows
     while the three toggles were 44: a 68px pitch above, 43px below, and two
     primary links under the tap floor. Child links are untouched — this is
     scoped to the top level. */
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container > .wp-block-navigation-item > a.wp-block-navigation-item__content {
    display: inline-flex;
    align-items: center;
    min-height: var(--aq-tap-min);
  }
  /* THE CLOSE SITS WHERE THE OPEN WAS (owner, 2026-09-30). Core pins its ✕ in
     the very corner as a bare 24×24 target, while the burger that opened the
     overlay is a 44px hairline box one gutter in and one bar-padding down — so
     the thumb that opened the menu found nothing to close it with. Same box,
     same place: the bar's padding (and its shrunk padding) and the gutter. The
     dialog is the positioning context and starts at the viewport top, as the
     header does. */
  /* STICKY, not absolute (owner, 2026-09-30): with a panel expanded the
     overlay scrolls (Products: ~970px of menu in an 844px phone), and an
     absolute ✕ scrolled away with it — the way out was off-screen exactly when
     the menu was longest. NOT `fixed` either: core's open animation leaves an
     identity transform on the overlay container, which makes that scroller
     the containing block, so a fixed ✕ still scrolled away (measured). Sticky
     inside the scroller holds it; the negative margins take it back out of
     flow — top brings its rest position from the dialog's md padding up to the
     bar's sm, bottom returns its height AND that lift, so the list does not move. Right edge:
     the dialog's own gutter padding + margin-left:auto. The page fill keeps
     scrolled menu text from showing through. Admin-bar term = the burger's own
     sticky offset (0 for visitors). */
  .aq-header .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-close {
    position: sticky;
    z-index: 3;
    top: calc(var(--wp-admin--admin-bar--height, 0px) + var(--aq-space-sm));
    right: auto;
    margin: calc(var(--aq-space-sm) - var(--aq-space-md)) 0 calc((var(--aq-tap-min) + var(--aq-space-sm) - var(--aq-space-md)) * -1) auto;
    background: var(--aq-surface-page);
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--aq-tap-min);
    height: var(--aq-tap-min);
    border: var(--aq-bw-1) solid var(--aq-hairline-light);
    border-radius: var(--aq-radius-md);
  }
  html.aq-header-shrunk .aq-header .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-close {
    top: calc(var(--wp-admin--admin-bar--height, 0px) + var(--aq-space-3xs));
    margin-top: calc(var(--aq-space-3xs) - var(--aq-space-md));
    margin-bottom: calc((var(--aq-tap-min) + var(--aq-space-3xs) - var(--aq-space-md)) * -1);
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation-submenu__toggle {
    min-height: var(--aq-tap-min);
  }
  /* Collapsed accordions need their affordance (owner): the chevron shows on
     BOTH toggles — core's sibling icon (Services) and the in-button clone
     (Products) — label-adjacent like desktop, rotating when open. */
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__submenu-icon {
    display: inline-flex;
    color: var(--aq-text);
    transition: transform var(--aq-motion-fast) var(--aq-ease-standard);
  }
  /* Core pulls the SIBLING icon -.6em into padding our toggle no longer
     has — reset that case only (the in-button clone keeps its .25em). */
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation-submenu__toggle + .wp-block-navigation__submenu-icon {
    margin-left: 0;
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation-submenu__toggle[aria-expanded="true"] + .wp-block-navigation__submenu-icon,
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .aq-mega-open .wp-block-navigation__submenu-icon {
    transform: rotate(180deg);
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__submenu-container,
  .wp-block-navigation__responsive-container.is-menu-open .aq-mega-panel {
    flex-basis: 100%;
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container > .wp-block-navigation-item > a,
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container > .wp-block-navigation-item > button {
    font-family: var(--aq-font-mono);
    font-size: var(--aq-fs-eyebrow);
    letter-spacing: var(--aq-track-eyebrow);
    text-transform: uppercase;
    color: var(--aq-text);
  }
  /* Core force-expands submenu children inside the overlay even in
     open-on-click mode — inconsistent with the Products accordion. Drive
     visibility off core's OWN aria-expanded binding instead. */
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-submenu > .wp-block-navigation__submenu-container {
    display: none;
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-submenu > .wp-block-navigation-submenu__toggle[aria-expanded="true"] ~ .wp-block-navigation__submenu-container {
    display: flex;
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__submenu-container {
    padding: var(--aq-space-sm) 0 0 var(--aq-space-lg);
    gap: var(--aq-space-xs);
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__submenu-container a {
    padding: 0;
    color: var(--aq-text-muted);
  }
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__submenu-container a:hover,
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__submenu-container a:focus-visible {
    color: var(--aq-text);
  }
  /* The Products item keeps link + (hidden) button + full-width panel. */
  .wp-block-navigation__responsive-container.is-menu-open .aq-mega-item {
    display: flex;
  }
}

/* Mobile (<64em): the panel is ALWAYS-EXPANDED inside core's overlay —
   the SHIPPED consistency bar (owner, 2026-08-10): Services renders its
   children expanded, plain, indented, no chevron and no band, so Products
   does exactly the same. Indent + row rhythm mirror the measured core
   submenu (children at 32px; 24px group rhythm = --aq-space-md). The
   desktop disclosure button is hidden here; nothing toggles. */
@media (max-width: 63.999em) {
  .wp-block-navigation .aq-mega-item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
  }
  .aq-mega-panel {
    flex-basis: 100%;
  }
  .aq-mega-inner {
    display: flex;
    flex-direction: column;
    gap: var(--aq-space-md);
    padding: var(--aq-space-sm) 0 0 var(--aq-space-lg);
  }
  .aq-mega-panel .aq-eyebrow {
    margin: 0 0 var(--aq-space-sm);
    color: var(--aq-text-muted);
  }
  .aq-mega-browse {
    margin: 0; /* the p's 14px block margins stacked onto the panel gap */
  }
  .aq-mega-tiles {
    display: flex;
    flex-direction: column;
    gap: var(--aq-space-xs);
    margin: 0;
  }
  .aq-mega-tile,
  .aq-mega-list a {
    color: var(--aq-text-muted);
  }
  .aq-mega-tile:hover,
  .aq-mega-tile:focus-visible,
  .aq-mega-list a:hover,
  .aq-mega-list a:focus-visible {
    color: var(--aq-text);
  }
  /* The media slot used to be `display: none` here, which is why the overlay
     was a text-only list while the desktop panel carried imagery. Same row at
     every width now (owner directive 2026-08-31) — so the slot stays, and the
     row keeps its 44px floor rather than collapsing to 0. */
  .aq-mega-list {
    gap: var(--aq-space-xs);
    margin-top: 0;
  }
  .aq-mega-list a,
  .aq-mega-fit a {
    min-height: 0;
    padding: 0;
  }
  .aq-mega-fit {
    margin: var(--aq-space-xs) 0 0;
  }
}

/* ONE PANEL GEOMETRY FOR BOTH ACCORDIONS (owner's phone review, 2026-09-30).
   Scoped to the OPEN overlay below 64em, so the desktop dropdown and mega
   panel cannot be reached by any of it. The Products panel is the reference
   (heading→content 16, 32px indent, 12px rows); Services now follows it.

   1. GROUP HEADINGS BELONG TO THE GROUP BELOW. The three product groups were
      plain blocks with no gap, so each eyebrow started on the exact pixel
      where the previous group's last tile ended (0 above, 16 below) and read
      as that group's footer. 32px above every heading after the first.
   2. THE PANEL FOOTER SITS IN THE PANEL. "Talk to us about fit" / "Browse all
      Products" are outside .aq-mega-inner, so they fell back to the top-level
      16px edge while the panel content is at 48; they were also 24px rows
      under the tap floor. Indented to the panel and 44px each; set apart from
      the last group like a group.
   3. SERVICES MATCHES PRODUCTS. Its links inherited the desktop dropdown's
      8/16 padding (thumbnails 16px right of Products', 80px pitch vs 76) and
      its list opened 24px below the toggle instead of 16. */
@media (max-width: 63.999em) {
  .wp-block-navigation__responsive-container.is-menu-open .aq-mega-col + .aq-mega-col {
    margin-top: var(--aq-space-lg);
  }
  .wp-block-navigation__responsive-container.is-menu-open .aq-mega-foot {
    margin-top: var(--aq-space-lg);
    padding-left: var(--aq-space-lg);
  }
  .wp-block-navigation__responsive-container.is-menu-open .aq-mega-foot .aq-mega-fit {
    margin: 0;
  }
  .wp-block-navigation__responsive-container.is-menu-open .aq-mega-foot a {
    min-height: var(--aq-tap-min);
  }
  .aq-header .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-submenu > .wp-block-navigation__submenu-container {
    gap: var(--aq-space-xs);
    padding: var(--aq-space-sm) 0 0 var(--aq-space-lg);
  }
  .aq-header .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-submenu > .wp-block-navigation__submenu-container > .wp-block-navigation-item > a.wp-block-navigation-item__content {
    min-height: var(--aq-tap-min);
    padding: 0;
  }
}

/* =========================================================================
   CONTACT (D6 reference layout — contact-ux-requirements §6b) + the Model-B
   enquiry drawer/sheet (E1/E2) and the ONE Fluent form's field vocabulary
   (plan §6a: styled here, tokens only; the plugin styler is never exposed).
   ========================================================================= */

/* --- page: form card + rail ------------------------------------------- */
.aq-contact-main {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: var(--aq-space-lg);
  align-items: start;
}
.aq-contact-card {
  background: var(--aq-surface-card);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-card);
  padding: var(--aq-space-lg);
}
.aq-contact-card-title,
.aq-contact-rail-title {
  margin: 0 0 var(--aq-space-md);
  font-size: var(--aq-fs-h3);
}
.aq-contact-rail-title {
  margin-bottom: var(--aq-space-sm);
}
.aq-contact-detail {
  margin: 0 0 var(--aq-space-sm);
  display: grid;
  gap: var(--aq-space-3xs);
}
.aq-contact-detail-label {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-eyebrow);
}
a.aq-contact-detail-value {
  color: var(--aq-content-link-color);
}
@media (max-width: 63.99em) {
  .aq-contact-main {
    grid-template-columns: 1fr;
  }
}

/* --- "Enquiring about" context chip (E1/E2 + resolved page journey) ---- */
.aq-enquiry-chip {
  display: flex;
  align-items: center;
  gap: var(--aq-space-xs);
  background: var(--aq-surface-alt);
  border-radius: var(--aq-radius-md);
  padding: var(--aq-space-xs);
  margin: 0 0 var(--aq-space-md);
}
.aq-enquiry-chip-thumb {
  width: var(--aq-tap-min);
  height: var(--aq-tap-min);
  object-fit: contain;
  border-radius: var(--aq-radius-sm);
  background: var(--aq-surface-card);
}
.aq-enquiry-chip-text {
  display: grid;
  gap: var(--aq-space-3xs);
}
.aq-enquiry-chip-eyebrow {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-eyebrow);
}
.aq-enquiry-chip-name {
  font-size: var(--aq-fs-body-sm);
  font-weight: var(--aq-fw-medium);
}

/* --- Fluent form field vocabulary (page card + embed; tokens only) ----- */
/* FF wraps its fields in a <fieldset> (a11y mode) — the grid lives THERE,
   not on the <form>, or the field groups are grandchildren and never grid. */
.aq-contact-card .frm-fluent-form fieldset,
.aq-embed-body .frm-fluent-form fieldset {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--aq-space-md);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.aq-contact-card .frm-fluent-form .ff-el-group,
.aq-embed-body .frm-fluent-form .ff-el-group {
  grid-column: 1 / -1;
  margin: 0;
}
.aq-contact-card .frm-fluent-form .ff-el-group.aq-f-half,
.aq-embed-body .frm-fluent-form .ff-el-group.aq-f-half {
  grid-column: auto / span 1;
}
.aq-contact-card .ff-el-input--label label,
.aq-embed-body .ff-el-input--label label {
  font-size: var(--aq-fs-body-sm);
  font-weight: var(--aq-fw-medium);
  color: var(--aq-text);
}
.aq-contact-card .ff-el-form-control,
.aq-embed-body .ff-el-form-control {
  width: 100%;
  min-height: var(--aq-tap-min);
  padding: var(--aq-space-2xs) var(--aq-space-xs);
  font-family: var(--aq-font-body);
  font-size: var(--aq-fs-body);
  color: var(--aq-text);
  background: var(--aq-surface-card);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-sm);
}
.aq-contact-card .ff-el-form-control:focus,
.aq-embed-body .ff-el-form-control:focus {
  border-color: var(--aq-text);
}
.aq-contact-card .ff-el-form-control::placeholder,
.aq-embed-body .ff-el-form-control::placeholder {
  color: var(--aq-text-muted);
  opacity: 1;
}
/* D4 — error voice = the locked ink orange (approved frame; no minted red). */
.aq-contact-card .ff-el-is-error .ff-el-form-control,
.aq-embed-body .ff-el-is-error .ff-el-form-control {
  border-color: var(--aq-color-error);
}
.aq-contact-card .error.text-danger,
.aq-embed-body .error.text-danger,
.aq-contact-card .ff-errors-in-stack,
.aq-embed-body .ff-errors-in-stack {
  color: var(--aq-color-error);
  font-size: var(--aq-fs-body-sm);
}
.aq-contact-card .ff-message-error,
.aq-embed-body .ff-message-error {
  color: var(--aq-color-error);
  border: var(--aq-bw-1) solid var(--aq-color-error);
  border-radius: var(--aq-radius-sm);
  padding: var(--aq-space-xs) var(--aq-space-sm);
}
/* D5 — success confirmation. */
.aq-contact-card .ff-message-success,
.aq-embed-body .ff-message-success {
  background: var(--aq-surface-alt);
  border-radius: var(--aq-radius-card);
  padding: var(--aq-space-lg);
  font-size: var(--aq-fs-lede);
}
/* Submit = the brand action (never light-on-orange; ADR-0005). Four classes
   out-specify FF's injected `form.fluent_form_N .ff-btn-submit:not(...)` (0,3,1). */
.aq-contact-card .frm-fluent-form .ff-btn-submit.ff_btn_style,
.aq-embed-body .frm-fluent-form .ff-btn-submit.ff_btn_style {
  min-height: var(--aq-tap-min);
  padding: var(--aq-space-2xs) var(--aq-space-md);
  font-family: var(--aq-font-body);
  font-size: var(--aq-fs-body);
  font-weight: var(--aq-fw-medium);
  color: var(--aq-on-brand);
  background: var(--aq-brand);
  border: 0;
  border-radius: var(--aq-radius-md);
  cursor: pointer;
  transition: transform var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-contact-card .frm-fluent-form .ff-btn-submit.ff_btn_style:hover,
.aq-embed-body .frm-fluent-form .ff-btn-submit.ff_btn_style:hover {
  transform: translateY(calc(-1 * var(--aq-lift)));
}
/* Form foot (D6): turnstile left · submit right; stacks on mobile. The widget
   is data-size=flexible (contact.php) and never wider than its container. */
.aq-contact-card .ff-el-turnstile,
.aq-embed-body .ff-el-turnstile {
  margin: 0;
  max-width: 100%;
}
.aq-contact-card .ff-el-turnstile iframe,
.aq-embed-body .ff-el-turnstile iframe {
  max-width: 100%;
}
.aq-contact-card .frm-fluent-form .ff_submit_btn_wrapper,
.aq-embed-body .frm-fluent-form .ff_submit_btn_wrapper {
  grid-column: auto / span 1;
  justify-self: end;
  align-self: center;
}
.aq-contact-card .frm-fluent-form .ff-el-group:has(.ff-el-turnstile) {
  grid-column: auto / span 1;
}
@media (max-width: 47.99em) {
  /* M-frame gutter (16px) — also keeps Turnstile's 300px minimum inside the card. */
  .aq-contact-card {
    padding: var(--aq-space-sm);
  }
  .aq-contact-card .frm-fluent-form fieldset,
  .aq-embed-body .frm-fluent-form fieldset {
    grid-template-columns: 1fr;
  }
  .aq-contact-card .frm-fluent-form .ff-el-group.aq-f-half,
  .aq-embed-body .frm-fluent-form .ff-el-group.aq-f-half,
  .aq-contact-card .frm-fluent-form .ff_submit_btn_wrapper,
  .aq-embed-body .frm-fluent-form .ff_submit_btn_wrapper {
    grid-column: 1 / -1;
    justify-self: stretch;
  }
}

/* --- E1 drawer / E2 sheet (enquiry.js overlay) ------------------------- */
html.aq-enquiry-open {
  overflow: hidden;
}
.aq-enquiry {
  position: fixed;
  inset: 0;
  z-index: 80; /* above the header + mega bands */
}
.aq-enquiry-scrim {
  position: absolute;
  inset: 0;
  background: var(--aq-scrim-ink);
  animation: aq-enquiry-fade var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-enquiry-panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(var(--aq-drawer-max), 100%);
  display: flex;
  flex-direction: column;
  background: var(--aq-surface-page);
  box-shadow: var(--aq-shadow-media);
  animation: aq-enquiry-in var(--aq-motion-base) var(--aq-ease-out);
}
.aq-enquiry-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--aq-space-sm);
  padding: var(--aq-space-md) var(--aq-space-lg);
  border-bottom: var(--aq-bw-1) solid var(--aq-hairline-light);
}
.aq-enquiry-title {
  margin: 0;
  font-size: var(--aq-fs-h3);
}
.aq-enquiry-close {
  min-width: var(--aq-tap-min);
  min-height: var(--aq-tap-min);
  border: 0;
  background: transparent;
  font-size: var(--aq-fs-lede);
  color: var(--aq-text);
  cursor: pointer;
}
.aq-enquiry-frame {
  flex: 1;
  width: 100%;
  border: 0;
}
@keyframes aq-enquiry-in {
  from { transform: translateX(100%); }
}
@keyframes aq-enquiry-fade {
  from { opacity: 0; }
}

/* --- embed shell (the iframe document) --------------------------------- */
body.aq-embed {
  background: var(--aq-surface-page);
}
.aq-embed-body {
  padding: var(--aq-space-lg);
}
.aq-embed-body .frm-fluent-form fieldset {
  grid-template-columns: 1fr; /* E1/E2 draw a single column in the drawer */
}
.aq-embed-body .frm-fluent-form .ff-el-group.aq-f-half,
.aq-embed-body .frm-fluent-form .ff_submit_btn_wrapper {
  grid-column: 1 / -1;
  justify-self: stretch;
}
@media (max-width: 47.99em) {
  .aq-embed-body {
    padding: var(--aq-space-sm);
  }
}

/* --- Industry detail (approved D2/D3) ----------------------------------- */
.aq-industry-apps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--aq-space-sm) var(--aq-space-lg);
}
.aq-industry-apps li {
  padding-left: var(--aq-space-md);
  position: relative;
}
.aq-industry-apps li::before {
  content: "\2713";
  position: absolute;
  left: 0;
  color: var(--aq-brand-ink);
}
.aq-industry-svcs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--aq-space-md);
}
.aq-industry-svc {
  background: var(--aq-surface-card);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-card);
  padding: var(--aq-space-md);
}
.aq-industry-svc h3 {
  margin: 0 0 var(--aq-space-2xs);
  font-size: var(--aq-fs-h3);
}
@media (max-width: 63.99em) {
  .aq-industry-apps,
  .aq-industry-svcs {
    grid-template-columns: 1fr;
  }
}

/* Industry composed-body vocabulary (owner-correction recomposition). */
.aq-industry-checks,
.aq-industry-defs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--aq-space-sm);
}
.aq-industry-checks li {
  padding-left: var(--aq-space-md);
  position: relative;
}
.aq-industry-checks li::before {
  content: "\2713";
  position: absolute;
  left: 0;
  color: var(--aq-brand-ink);
}
.aq-industry-defs li {
  color: var(--aq-text-muted);
  border-bottom: var(--aq-bw-1) solid var(--aq-hairline-light);
  padding-bottom: var(--aq-space-sm);
}
.aq-industry-defs li:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}
.aq-industry-defs li strong {
  color: var(--aq-text);
}
.aq-industry-outputs {
  margin: 0;
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-eyebrow);
}

/* --- Industry v3 (approved design; Services-file frames) ----------------- */
.aq-ind-stat {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
  gap: var(--aq-space-xl);
  align-items: center;
  background: var(--aq-surface-alt);
  border-radius: var(--aq-radius-card);
  padding: var(--aq-space-lg) var(--aq-space-xl);
}
.aq-ind-stat-number {
  margin: 0;
  font-family: var(--aq-font-heading);
  font-size: var(--aq-fs-display);
  font-weight: var(--aq-fw-bold);
  line-height: var(--aq-lh-tight);
  color: var(--aq-text);
}
/* Statement variant (2026-09-17, client: the text-only "Why drones" points read
   "very small"): when an industry has no numeric figure, the statement itself
   takes the figure slot at H2 scale in the heading face. Deliberately NOT the
   display size the numerals use - a five-word phrase at 4.5rem would dwarf the
   band - and sentence case, not the eyebrow's uppercase mono. */
.aq-ind-stat-statement {
  margin: 0;
  font-family: var(--aq-font-heading);
  font-size: var(--aq-fs-h2);
  font-weight: var(--aq-fw-bold);
  line-height: var(--aq-lh-tight);
  color: var(--aq-text);
  text-wrap: balance;
}
.aq-ind-stat-label,
.aq-uc-num,
.aq-uc-outputs,
.aq-ind-wf-label,
.aq-dcard-label,
.aq-ind-table-head th {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-eyebrow);
  margin: 0;
}
.aq-uc-num {
  color: var(--aq-brand-ink);
}
.aq-ind-stat-kicker {
  margin: 0;
  font-family: var(--aq-font-heading);
  font-size: var(--aq-fs-h3);
  font-weight: var(--aq-fw-bold);
}
.aq-uc-row {
  display: grid;
  grid-template-columns: var(--aq-space-xl) minmax(0, 1fr) minmax(0, 1.2fr);
  grid-template-areas: "num title desc" ". outputs desc";
  column-gap: var(--aq-space-lg);
  row-gap: var(--aq-space-2xs);
  padding-block: var(--aq-space-sm);
  border-top: var(--aq-bw-1) solid var(--aq-hairline-light);
}
.aq-uc-num { grid-area: num; }
.aq-uc-title { grid-area: title; margin: 0; font-size: var(--aq-fs-h3); }
.aq-uc-desc { grid-area: desc; margin: 0; color: var(--aq-text-muted); font-size: var(--aq-fs-body-sm); }
.aq-uc-outputs { grid-area: outputs; }
.aq-ind-table {
  /* A real <table> laid out with grid rows: the semantics come from the
     elements, the appearance from these rules, unchanged. */
  display: block;
  border-collapse: collapse;
  width: 100%;
  background: var(--aq-surface-card);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-md);
  padding: var(--aq-space-2xs) var(--aq-space-md);
}
.aq-ind-table-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.8fr) minmax(0, 1.3fr) var(--aq-space-3xl);
  gap: var(--aq-space-md);
  padding-block: var(--aq-space-sm);
  border-top: var(--aq-bw-1) solid var(--aq-hairline-light);
}
.aq-ind-table thead,
.aq-ind-table tbody { display: block; }
.aq-ind-table thead .aq-ind-table-row { border-top: 0; }
.aq-ind-table tbody .aq-ind-table-row:first-child { border-top: 0; }
.aq-ind-table-row th,
.aq-ind-table-row td { text-align: left; font-weight: inherit; font-size: var(--aq-fs-body-sm); color: var(--aq-text-muted); }
.aq-ind-table-row .aq-ind-table-output { color: var(--aq-text); font-weight: var(--aq-fw-medium); font-size: var(--aq-fs-body); }
.aq-ind-table-row .aq-ind-table-acc {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  color: var(--aq-brand-ink);
}
.aq-ind-table-row .aq-ind-table-acc--na {
  color: var(--aq-text-muted);
}

/* Deliverable cards (round-1 C / L10) — the visual layer over the SAME rows
   the semantic table renders below it. The media slot is fixed-ratio; a row
   without an image shows the DESIGNED placeholder slot until real output
   imagery lands (tracked launch item — never fabricated imagery). */
.aq-dcard-media {
  aspect-ratio: 16 / 9;
  border-radius: var(--aq-radius-md);
  overflow: hidden;
}
.aq-dcard-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.aq-dcard-media--slot {
  display: grid;
  place-items: center;
  background: var(--aq-fill-tile);
  border: var(--aq-bw-1) dashed var(--aq-hairline-light);
}
.aq-dcard-media--slot span {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-muted); /* faint fails AA (1.97:1) on the tile fill — axe-caught */
}
.aq-dcard-title {
  margin: 0;
  font-size: var(--aq-fs-h3);
}
.aq-dcard-list {
  margin: 0;
  padding-inline-start: var(--aq-space-md);
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-2xs);
  color: var(--aq-text-muted);
  font-size: var(--aq-fs-body-sm);
}
.aq-dcard .aq-dcard-acc {
  margin-block-start: auto; /* card foot; outranks the .aq-card child reset by cascade order */
  align-self: flex-start;
  padding: var(--aq-space-2xs) var(--aq-space-xs);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-pill);
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-brand-ink);
}
.aq-ind-wf-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(calc(var(--aq-space-3xl) * 2), 1fr));
  gap: var(--aq-space-md) var(--aq-space-lg);
}
.aq-ind-wf-value { margin: 0; font-weight: var(--aq-fw-medium); }
@media (max-width: 63.99em) {
  .aq-ind-stat { grid-template-columns: 1fr; gap: var(--aq-space-lg); padding: var(--aq-space-md); }
  .aq-uc-row { grid-template-columns: var(--aq-space-xl) minmax(0, 1fr); grid-template-areas: "num title" ". desc" ". outputs"; }
  .aq-ind-table-row { grid-template-columns: 1fr; gap: var(--aq-space-3xs); }
  .aq-ind-table-head { display: none; }
  /* Stacked rows have no ACCURACY column label — an empty-value dash would
     read as stray debris, so it renders only where a real figure exists. */
  .aq-ind-table-acc--na { display: none; }
}

/* ---------------------------------------------------------------------------
   Catalogue retrieval — filters + search (finalisation-launch-plan §10.5)
   Controls are real links/forms (no-JS baseline), so these are link styles,
   not widget styles. `.aq-facet` is deliberately NOT `.aq-chip`: that class is
   an absolutely-positioned card overlay and reusing it would break the cards.
--------------------------------------------------------------------------- */
.aq-catfilters {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-md);
  padding-block: var(--aq-space-lg);
  border-bottom: var(--aq-bw-1) solid var(--aq-hairline-light);
}
.aq-catsearch {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-2xs);
}
.aq-catsearch__label {
  font-size: var(--aq-fs-eyebrow);
  font-weight: var(--aq-fw-semibold);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-muted);
}
.aq-catsearch__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-2xs);
}
.aq-catsearch__input {
  flex: 1 1 var(--aq-field-min);
  min-width: 0;
  padding: var(--aq-space-2xs) var(--aq-space-xs);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-md);
  background: var(--aq-surface-card);
  color: var(--aq-text);
  font-family: var(--aq-font-body);
  font-size: var(--aq-fs-body);
  min-height: var(--aq-tap-min);
}
.aq-catsearch__submit {
  flex: 0 0 auto;
  min-height: var(--aq-tap-min);
  padding-inline: var(--aq-space-md);
  border: var(--aq-bw-1) solid transparent;
  border-radius: var(--aq-radius-md);
  background: var(--aq-surface-dark);
  color: var(--aq-text-on-dark);
  font-family: var(--aq-font-body);
  font-size: var(--aq-fs-body-sm);
  font-weight: var(--aq-fw-medium);
  cursor: pointer;
}
/* Hover matches the theme's buttons — a lift, NOT a colour flip. The previous
   `background: var(--aq-brand)` kept white text, giving 3.2:1 and breaking the
   design-system rule "never light text on brand orange" in a state axe does
   not exercise. Keeping the dark fill keeps it at 18.9:1 in every state. */
.aq-catsearch__submit:hover,
.aq-catsearch__submit:focus-visible {
  transform: translateY(calc(var(--aq-lift) * -1));
  box-shadow: var(--aq-shadow-card);
}
@media (prefers-reduced-motion: reduce) {
  .aq-catsearch__submit:hover,
  .aq-catsearch__submit:focus-visible { transform: none; }
}

.aq-catfilters__groups {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-sm);
}
.aq-catfilters__group {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.aq-catfilters__legend {
  padding: 0;
  margin-bottom: var(--aq-space-2xs);
  font-size: var(--aq-fs-eyebrow);
  font-weight: var(--aq-fw-semibold);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-muted);
}
.aq-catfilters__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-3xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The facet control itself. 44px min target — the same touch rhythm the
   Products front was corrected to at P4. */
.aq-facet {
  display: inline-flex;
  align-items: center;
  gap: var(--aq-space-3xs);
  min-height: var(--aq-tap-min);
  padding: var(--aq-space-3xs) var(--aq-space-xs);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-surface-card);
  color: var(--aq-text);
  font-size: var(--aq-fs-body-sm);
  text-decoration: none;
  transition: background-color 120ms var(--aq-ease-out), border-color 120ms var(--aq-ease-out);
}
.aq-facet:hover,
.aq-facet:focus-visible {
  border-color: var(--aq-brand);
  background: var(--aq-surface-alt);
  text-decoration: none;
}
/* Selected: orange INK on a light surface with a brand-weight border — NOT
   white on brand orange. `--aq-brand` (#f2622e) is a fill/accent token whose
   own definition says "never normal text on light", and white on it measures
   3.2:1, failing AA and the design-system rule "never light text on brand
   orange; orange text on light only via orange-ink". brand-ink is 5.18:1. */
.aq-facet.is-selected {
  border-color: var(--aq-brand-ink);
  border-width: var(--aq-bw-2);
  background: var(--aq-surface-page);
  color: var(--aq-brand-ink);
  font-weight: var(--aq-fw-medium);
}
/* Selected state must survive forced-colours / high-contrast, where the
   background is overridden and colour alone would carry no meaning. */
.aq-facet.is-selected::after {
  content: "×";
  margin-left: var(--aq-space-3xs);
  font-weight: var(--aq-fw-semibold);
}
.aq-facet__count {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  /* text-muted (6.5:1), NOT text-faint: a facet count is meaningful text, and
     text-faint is 1.99:1 — its own token definition says "decorative marks
     ... never copy". Caught by axe at AA. */
  color: var(--aq-text-muted);
}
.aq-facet.is-selected .aq-facet__count {
  color: var(--aq-brand-ink);
}

/* §9.17 — the compact filter form: one native select per dimension + Apply.
   A plain GET form is the no-JS baseline; the chip-list rules above it stay
   for the transition and cost nothing unused. */
.aq-catfilters__form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--aq-space-sm);
  align-items: flex-end;
}
.aq-catfilters__field {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-2xs);
}
/* The field is a <label>: a plugin stylesheet gives labels a 0.7em bottom
   margin, which lifted the field 11px off the flex line and left Apply
   sitting visibly lower than the selects (owner catch 2026-08-21). Scoped
   double for specificity over the plugin's own scoped rule. */
.aq-catfilters__form .aq-catfilters__field {
  margin: 0;
}
.aq-catfilters__label {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-eyebrow);
}
.aq-catfilters__select {
  min-height: var(--aq-tap-min);
  padding: var(--aq-space-2xs) var(--aq-space-xs);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-md);
  background: var(--aq-surface-card);
  color: var(--aq-text);
  font: inherit;
  font-size: var(--aq-fs-body-sm);
}
.aq-catfilters__apply {
  min-height: var(--aq-tap-min);
  padding: var(--aq-space-2xs) var(--aq-space-md);
  border: var(--aq-bw-1) solid var(--aq-surface-dark);
  border-radius: var(--aq-radius-md);
  background: var(--aq-surface-dark);
  color: var(--aq-text-on-dark);
  font: inherit;
  font-size: var(--aq-fs-body-sm);
  cursor: pointer;
}

/* §9.17 — slim catalogue header: products above the fold. The 26rem hero
   split is gone on the catalogue landing pages; this compact tint band
   replaces it. */
.aq-section.aq-cat-hero--slim {
  padding-block: var(--aq-space-lg);
}
/* A slim header owns a TIGHT boundary: the colour-change 2× gap that the
   adjacency system correctly keeps between full sections reads as dead space
   after a compact listing header (owner review 2026-08-21). */
.aq-cat-hero--slim + .aq-section {
  padding-block-start: var(--aq-space-lg);
}
/* Retrieval mode nests the filters panel inside the already-padded catalogue
   wrapper — zero only the TOP (the wrapper owns the space above); the
   panel's own bottom lg stays, because it is what keeps the hairline
   border-bottom off the filter row (owner catch 2026-08-21: zeroing both
   sides left the border sticking to the filters on the overview only). */
.aq-catbrowse .aq-catfilters {
  padding-block: 0 var(--aq-space-lg);
}
/* The result count + Clear all live IN the filter row (owner review
   2026-08-21: as an orphan line above the results heading it pushed the
   products down); wraps beneath the selects on narrow screens. */
.aq-catfilters__form .aq-catfilters__status {
  margin-left: auto;
  align-self: center;
}
/* The results title keeps a consistent 32px off the filters' bottom border
   (owner catch: it TOUCHED on category pages — the same-surface adjacency
   rule zeroes .aq-cat-body's section top there; the overview supplies 16px
   via the wrapper's stack gap, so its body adds only the other sm). */
/* Doubled classes: the same-surface adjacency rule weighs (0,4,0) with its
   :not() arguments, so these must match it to win (house idiom — see the
   WP block-gap kill above). */
.aq-catfilters.aq-catfilters + .aq-cat-body.aq-cat-body {
  padding-block-start: var(--aq-space-lg);
}
.aq-catbrowse .aq-catfilters + .aq-cat-body.aq-cat-body {
  padding-block-start: var(--aq-space-sm);
}
/* A lone section-link row (the industry-page products CTA) between two full
   sections floated in ~2× section padding — both neighbours tighten around
   it (owner screenshot review 2026-08-21). The row carries its own bottom
   breathing so it reads as the PREVIOUS section's closing line rather than
   an orphan touching the next band (second review, same day). */
/* SAME-COLOUR ONLY, and the `:not([class*="aq-band"])` is the design rule, not
   a naming coincidence: there are exactly TWO band classes in this stylesheet,
   `aq-band-tint` and `aq-band-dark`, both of which paint a background, and the
   collapse rule at the top of this file already uses this same test to mean
   "carries a band background". The row itself is transparent, so it merges with
   transparent neighbours and keeps its full distance from coloured ones.

   Without the scope, the tightening also applied across a COLOUR CHANGE, which
   the rule near line 98 says must keep the full 2x rhythm. Measured 2026-09-27:
   five Industry pages opened their tinted Deliverables band at 32px instead of
   80px (owner-reported). Public Safety was correct at 32px and still is, because
   its Deliverables band carries no tint — that boundary really is same-colour,
   and the distinction is preserved rather than flattened. */
.aq-section:not([class*="aq-band"]):has(+ .wp-block-group > .aq-ind-products-cta) {
  padding-block-end: var(--aq-space-lg);
}
.wp-block-group:has(> .aq-ind-products-cta) {
  padding-block-end: var(--aq-space-lg);
}
.wp-block-group:has(> .aq-ind-products-cta) + .aq-section:not([class*="aq-band"]) {
  padding-block-start: var(--aq-space-lg);
}

.aq-catfilters__status {
  margin: 0;
  font-size: var(--aq-fs-body-sm);
  color: var(--aq-text);
}
.aq-catfilters__clear {
  margin-left: var(--aq-space-2xs);
  /* Same 24px AA target floor as the other standalone action links — this one
     is not an `.aq-sec-link`, so it did not inherit it. */
  display: inline-flex;
  align-items: center;
  min-height: var(--aq-tap-aa-min);
}
/* §9.16 shop→solutions guidance line (renders only when exactly one industry
   filter is active). */
.aq-catfilters__guide {
  margin: 0;
  font-size: var(--aq-fs-body-sm);
}

.aq-catempty {
  padding: var(--aq-space-xl) 0;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .aq-facet { transition: none; }
}
/* Empty-state CTA: a plain link, styled here. `.aq-btn-ghost` is a WRAPPER
   class in this theme (it targets a nested .wp-block-button__link), so using
   it on a bare <a> would silently render unstyled. */
.aq-catempty__cta {
  display: inline-flex;
  align-items: center;
  min-height: var(--aq-tap-min);
  padding-inline: var(--aq-space-md);
  border: var(--aq-bw-1) solid var(--aq-hairline-light);
  border-radius: var(--aq-radius-md);
  color: var(--aq-text);
  text-decoration: none;
}
.aq-catempty__cta:hover,
.aq-catempty__cta:focus-visible {
  border-color: var(--aq-brand);
}

/* Footer legal links sit with the copyright line, not in the column grid —
   they are site-wide obligations, not navigation (site-ia: Privacy + Terms,
   footer-linked, INDEX). */
.aq-footer-legal-links {
  display: inline-flex;
  gap: var(--aq-space-md);
  margin-left: var(--aq-space-md);
}
.aq-footer-legal-links a {
  color: var(--aq-text-muted);
  text-decoration: none;
}
.aq-footer-legal-links a:hover,
.aq-footer-legal-links a:focus-visible {
  color: var(--aq-text);
}

/* ---------- editorial product collection (M6) ----------
   The collection is configured with `displayLayout: list`, so WooCommerce adds
   no layout of its own and the site keeps ONE card grid rather than carrying a
   second product grid that merely looks similar.

   That configuration IS the fix. The first attempt left Woo on its default
   `flex` layout and tried to out-specify it: a one-product collection then laid
   out at 173px inside a 1440 viewport instead of filling its row, because Woo
   sizes items with
   `.wc-block-product-template.is-flex-container.is-flex-container.columns-3 > li
   { width: calc(33.33% - .83em) }`. Woo DOUBLES its own class, so the obvious
   override ties on specificity and loses on load order, and escalating further
   is a war that gets re-fought at every Woo release. Asking the block not to
   apply a layout is the durable answer.

   `.aq-grid-cards` uses auto-fit, so one, two or many products all compose
   without a per-count rule. */
.wc-block-product-template.aq-grid-cards {
  display: grid;
  /* auto-FILL, not the shared grid's auto-fit. An editorial collection can hold
     a single product, and auto-fit collapses the empty tracks so that one card
     stretched the full 1360px — a stretched card, not an intentional one.
     auto-fill keeps the tracks, so one product reads as one card in a row that
     could hold three. Scoped here rather than changed on .aq-grid-cards, which
     every other card grid on the site depends on. */
  grid-template-columns: repeat(auto-fill, minmax(min(var(--aq-card-min), 100%), 1fr));
}
/* The row is a <ul>; the grid track owns the width, not list furniture. */
.wc-block-product-template.aq-grid-cards > li {
  width: auto;
  max-width: none;
  list-style: none;
  margin: 0;
}

/* ---------- headline figures (round-2 §12.4) ----------
   The one content structure the eight DJI reference pages use that this
   palette could not express: a short band of capability figures read at a
   glance — 30 kg payload, 28 km range, 55-min flight time. Found on six of the
   eight, so a recurring primitive rather than one page's flourish.

   Same label/value semantics as the PDP key-spec chips (.aq-pdp-keyspecs),
   deliberately: this is that content at section scale, not a second way of
   saying it. The chips sit in a narrow summary column and stay chip-sized;
   this runs the content width, so the figure takes the h2 role and the label
   the eyebrow role. Auto-fit, so two figures and six both compose. */
.aq-statband {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--aq-statband-min), 1fr));
  gap: var(--aq-space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}
.aq-statband li {
  display: flex;
  flex-direction: column;
  gap: var(--aq-space-3xs);
  padding-block-start: var(--aq-space-xs);
  border-block-start: var(--aq-bw-2) solid var(--aq-hairline-light);
}
.aq-stat-value {
  font-family: var(--aq-font-heading);
  font-weight: var(--aq-fw-bold);
  font-size: var(--aq-fs-h2);
  line-height: var(--aq-lh-heading);
  /* Figures line up column to column; they are numbers in a row. */
  font-variant-numeric: tabular-nums;
}
.aq-stat-label {
  font-family: var(--aq-font-mono);
  font-size: var(--aq-fs-eyebrow);
  letter-spacing: var(--aq-track-eyebrow);
  text-transform: uppercase;
  color: var(--aq-text-muted);
}

/* ---------- feature switcher (round-2 item 4) ----------
   The behaviour the client pointed at on DJI's product pages — a list of
   feature titles with one open, and a media panel beside it that changes with
   the selection. The BEHAVIOUR is the reference; the surface is ours (item 2:
   AeroIQ design system, no dark mode).

   Structure is the site's own split: a narrow nav track and a media/body track,
   stacking below the split breakpoint like every other split here. */
.aq-featureset-split {
  display: grid;
  gap: var(--aq-space-lg);
}
@media (min-width: 64em) {
  .aq-featureset-split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    align-items: start;
    /* The side-by-side gutter, same as the other splits — see the note beside
       `.aq-hero-split`. Column only: the stacked row gap stays at `lg`. */
    column-gap: var(--aq-space-2xl);
  }
  /* No feature carries media, so there is no second track to reserve. The
     renderer omits the panel column entirely; without this the single list
     would sit in the 1fr column and leave 1.4fr of empty page beside it —
     which is exactly what the Matrice 400 hub showed. */
  .aq-featureset-split--solo {
    grid-template-columns: minmax(0, 1fr);
    /* Collapsed to one column, so it IS a one-column row and takes that
       ceiling — see .aq-faqgroup (owner, 2026-09-26). */
    max-width: var(--aq-heading-max);
  }
  /* The same collapse, but per SELECTED STATE rather than per section.
     `--solo` only fires when NO feature carries media. In a mixed switcher the
     track is reserved for the section, so selecting a state that has nothing
     to show left a large empty column beside the text and read as a failed
     load. Ten states across Dock 3, Matrice 4D, Matrice 4 and DJI Terra did
     that; the four Dock 3 Adaptability states looked right only because their
     whole section is solo.

     `hub-nav.js` moves the `hidden` attribute between panels, so keying on the
     visible panel follows the selection with no JavaScript change. Declarations
     match `--solo` deliberately: the corrected states should be
     indistinguishable from the four that were already right.

     Nothing is invented to fill the gap — missing client media stays missing.
     This only stops the layout claiming a panel exists when it does not. */
  .aq-featureset-split:has(.aq-feature-panel:not([hidden])[data-aq-media="0"]) {
    grid-template-columns: minmax(0, 1fr);
    max-width: var(--aq-heading-max);
  }
}
/* An empty panel should occupy no space at any width, including the
   single-column layout below 64em where it would otherwise add a blank row. */
.aq-feature-panel[data-aq-media="0"] {
  display: none;
}
.aq-featnav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
/* The active marker is a rule on the inline-start edge — the same device the
   reference uses to show which item is open, drawn in the brand ink.

   The rule sits on the LI, not the A, because the open item's body now renders
   under its own title (departure 4) and the marker has to span both. On the A
   it stopped at the title and left the body outside its own bracket. */
.aq-featnav li {
  border-inline-start: var(--aq-bw-2) solid var(--aq-hairline-light);
  transition: border-color var(--aq-motion-fast) var(--aq-ease-standard);
}
/* The marker keys off `is-current`, and hub-nav.js MOVES that class in the same
   place it moves `aria-current` — so there is still one source of truth, it is
   just the script rather than the selector.
   `:has(> a[aria-current="page"])` was tried first and looked correct: it
   matched the right item and the rule was live in the sheet. It still painted
   the wrong one, because the border kept its load-time value when the attribute
   changed — a :has() invalidation gap, verified in the browser rather than
   reasoned about. A class toggle invalidates reliably everywhere. */
.aq-featnav li.is-current {
  border-inline-start-color: var(--aq-brand);
}
.aq-featnav li > a {
  display: block;
  padding: var(--aq-space-sm) 0 var(--aq-space-sm) var(--aq-space-md);
  color: var(--aq-text-muted);
  text-decoration: none;
  font-size: var(--aq-fs-h3);
  line-height: var(--aq-lh-heading);
  transition: color var(--aq-motion-fast) var(--aq-ease-standard);
}
.aq-featnav li > a:hover {
  color: var(--aq-text);
}
.aq-featnav li > a[aria-current="page"] {
  color: var(--aq-text);
  font-weight: 700;
}
/* The open item's body: indented to its title, and given the space the title's
   own bottom padding would otherwise have supplied.

   The title→body tightening is taken from the TITLE'S padding, not a negative
   margin on the body. The negative margin slid the body 8px up inside the
   link's box, so the keyboard focus ring — drawn at that box — ran straight
   through the body's first line (owner's phone, 2026-09-30). Same spacing,
   no overlap. Keyed on `is-current` (the class hub-nav.js moves; see the
   :has() invalidation note above) and only where a body exists — the renderer
   omits an empty one, and a title-only item keeps its full padding. The :has()
   here tests a child that never changes, not the attribute that does. */
.aq-featnav li > .aq-feature-body {
  margin: 0;
  padding: 0 0 var(--aq-space-sm) var(--aq-space-md);
}
.aq-featnav li.is-current:has(> .aq-feature-body) > a {
  padding-block-end: calc(var(--aq-space-sm) - var(--aq-space-2xs));
}
.aq-feature-panels {
  display: grid;
  gap: var(--aq-space-sm);
}
/* Luke's final review item 9: "The changing videos on the hub pages can we have
   a smoother transition in the video change?"

   MEASURED FIRST, and it changed the design twice.

   The original fix here was `@keyframes aq-panel-in` on the INCOMING panel. It
   was live and it was nearly invisible, because a switcher change moves the
   `hidden` attribute on three things at once and only one of them eased:

       nav <li> active marker   border-color 150ms   (already eased)
       nav <a> label            color 150ms          (already eased)
       the body copy            nothing — snapped
       the media panel          300ms fade IN only

   So the eye saw tab jump, copy jump, old video vanish, new video appear — and a
   fade buried inside that read as nothing. The owner said as much: "I don't see
   any transition in effect anywhere." An earlier note in products-detail.php
   records the same complaint from 2026-09-01: "these should be just very smooth".

   THE HANDOFF IS NOW FADE-OUT → SWAP → FADE-IN, on the two STABLE containers
   that survive a state change: `.aq-feature-panels` (the media viewport) and the
   nav body. `hub-nav.js` adds `.is-swapping` to the section, waits out the short
   fade, moves `hidden` as it always did, then releases the class so the new
   state fades in.

   WHY NOT A TRUE CROSS-FADE. It would need both panels rendered at once, and
   pausing hidden loops relies on `display:none` removing the box — an
   IntersectionObserver cannot tell a transparent panel from an on-screen one, so
   every clip in the section would decode together. That is the exact defect the
   owner had fixed on 2026-09-11 and which `video-behaviour.js` asserts. The
   `hidden` lifecycle is untouched here.

   ASYMMETRIC ON PURPOSE: out on `--aq-motion-fast` so the control answers at
   once, in on `--aq-motion-base` because arriving can afford to be gentle.

   Reduced motion needs no rule of its own — the master off-switch zeroes every
   transition-duration, and `hub-nav.js` skips the sequencing entirely, so the
   state changes instantly with no dead time. */
.aq-feature-panels,
.aq-featnav [data-hub-group] {
  transition: opacity var(--aq-motion-base) var(--aq-ease-out);
}
.aq-featureset.is-swapping .aq-feature-panels,
.aq-featureset.is-swapping .aq-featnav [data-hub-group]:not([hidden]) {
  opacity: 0;
  transition-duration: var(--aq-motion-fast);
}
.aq-feature-panel .aq-hero-media {
  border-radius: var(--aq-radius-md);
  overflow: hidden;
}
.aq-feature-panel .aq-hero-media img {
  width: 100%;
  height: auto;
  display: block;
}
.aq-feature-body {
  margin: 0;
  color: var(--aq-text-muted);
  /* The switcher body is ONE escaped paragraph — the renderer allows no HTML
     in it, deliberately. Several of the client's hub items run to two or three
     paragraphs, which would otherwise collapse into one run-on block. This
     honours the blank lines they wrote without opening the field to markup. */
  white-space: pre-line;
}

/* ---------- shared media: background video + click-to-play facade ----------
   Round-2 item 5. These sit inside the existing media boxes (.aq-hero-media,
   .aq-pcard-media) and must fill them exactly as the <img> they replace does,
   so no component needed a layout change to gain video. */
.aq-media-bg,
.aq-media-player {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
}
/* Hold the box at the CLIP's own ratio (--aq-media-ratio, printed per video by
   aeroiq_core_media_box_attrs). A <video> borrows its intrinsic size from the
   POSTER until metadata arrives, so a poster of a different shape resized the
   box after the content was already visible — measured 2026-09-30 at 197px on
   the Home hero, above the fold, parent included. Pinning the ratio means
   neither the poster nor the metadata can move it.

   `auto` where the property is unset, so nothing else changes. And `height:100%`
   above still wins wherever a parent actually sets a height, which is the only
   case `object-fit: cover` was ever doing its job in. */
.aq-media-bg {
  aspect-ratio: var(--aq-media-ratio, auto);
}
.aq-media-facade {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
}
.aq-media-facade > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The control sits over its own poster. It is a real button, so it keeps the
   global focus ring and works from the keyboard without anything extra. */
.aq-facade-btn {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--aq-space-2xs);
  width: 100%;
  border: 0;
  cursor: pointer;
  background: var(--aq-scrim-ink);
  color: var(--aq-text-on-dark);
  font-family: inherit;
  font-size: var(--aq-fs-body-sm);
}
/* The scrim is already the darkest step we have, so hover feedback comes from
   the control rather than the surface — and it is motion the reduced-motion
   block below already switches off. */
.aq-facade-btn:hover .aq-facade-icon {
  transform: scale(1.06);
}
.aq-facade-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--aq-tap-min);
  height: var(--aq-tap-min);
  border-radius: var(--aq-radius-pill);
  background: var(--aq-brand);
  color: var(--aq-text-on-dark);
}
/* Reduced motion: media.js pauses the loop and the poster frame stands in, so
   nothing here needs to hide it — but the transition goes. */
.aq-facade-icon {
  transition: transform var(--aq-motion-fast) var(--aq-ease-standard);
}
@media (prefers-reduced-motion: reduce) {
  .aq-facade-icon {
    transition: none;
    transform: none;
  }
}
