/* ============================================
   gs-header — TWO-header pattern (static + sliding sticky).

   The page renders the header markup TWICE inside base.html:
     1. .gs-header-host--static  — sits in normal document flow at
                                   the top of the page (below the
                                   announcement bar if present).
                                   Transparent bg by default so the
                                   header reads as chrome over the
                                   dark hero behind it. White text +
                                   white logo variant. Scrolls away
                                   with the page on scroll.
     2. .gs-header-host--sticky  — position: fixed at the top of the
                                   viewport, hidden via translateY
                                   (-100%) initially. JS adds .is-active
                                   when scrollY > static-header-bottom,
                                   sliding the header into view from
                                   above. Always white bg + dark text
                                   + colored logo.

   Modifier:
     headerVariant: solid (page frontmatter) → forces the static
     header into the white state too. Use on pages whose first
     section is light/white where a transparent header would be
     invisible (legal, careers light hero, about-us, etc.).
   ============================================ */

/* ─── Mega-nav backdrop (page-blur overlay) ─────────────────
   When the mega-nav opens, blur + dim the rest of the page so the
   panel reads as a "command-palette" surface focused on its
   content (same blur recipe as the search modal). The backdrop is
   a pseudo-element on body — no markup or JS change needed; it
   exists permanently but transitions opacity in and out based on
   the mega trigger's aria-expanded state via :has().

   Scoping: the [data-nav-target="offerings"] selector matches ONLY
   the mega trigger (flyouts don't carry data-nav-target), so the
   small Community / Resources / Company / language dropdowns
   don't fire the backdrop. Same scoping rule we already use for
   the header's dark-bg flip.

   Fade timing mirrors the panel itself — 0.2s opacity in/out with
   the visibility-delay technique so the backdrop stays painted
   during the fade-out animation. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  background: rgba(2, 19, 36, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  /* z-index sits between page content (auto/0) and the header
     hosts (40/50) + mega panel (60), so the blur covers the body
     but the header chrome + panel stay crisp on top. */
  z-index: 30;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* Fade duration deliberately longer than the mega panel itself
     (0.18s) — the backdrop is atmosphere, the panel is content.
     The slower bg dim lets the eye settle into the focused state
     instead of slamming on/off in lockstep with the panel. */
  transition:
    opacity 0.4s ease,
    visibility 0s linear 0.4s;
}

body:has([data-nav-target="offerings"] [aria-expanded="true"])::before {
  opacity: 1;
  visibility: visible;
  /* Open direction — visibility flips immediately so the fade-in
     starts from frame 0, just like the mega panel itself. */
  transition:
    opacity 0.4s ease,
    visibility 0s linear 0s;
}

/* ─── Header host wrappers ──────────────────────────────────── */

.gs-header-host {
  /* The wrapper carries position + visibility; the inner header
     markup is the same for both variants. */
}

/* Static — position: absolute so the (transparent) header OVERLAYS
   the hero below it. If it sat in normal flow, the header would be
   between bar and hero on white body bg → transparent → invisible
   white text on white. Absolute positioning scrolls away with the
   document naturally (unlike fixed, which stays in viewport).
   `top` is 0 by default; pushed down to clear the announcement bar
   when body.has-announcement is set by base.html. */
.gs-header-host--static {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
}

body.has-announcement .gs-header-host--static {
  /* Sit just below the announcement bar so the header still overlays
     the hero without covering the bar. The bar publishes its own
     height via --announcement-h (announcement.css) so the offset
     here can't drift out of sync with the bar's actual rendered
     height. */
  top: var(--announcement-h);
}

/* Sticky header gets the same offset — once the announcement bar
   sticks to the viewport top, the sticky header has to slide in
   BELOW it instead of overlapping. Same `--announcement-h` token
   so the two stay in sync. */
body.has-announcement .gs-header-host--sticky {
  top: var(--announcement-h);
}

/* ─── Solid variant: skip the two-tier swap ──────────────────
   On pages whose first section is light (white/grey hero), the
   "transparent header that scrolls away, then a white one slides
   in" choreography is the wrong UX — there's nothing to read
   *over*, just a white chrome strip floating above a white hero.
   Switch to a single fixed header that's stuck to the top from
   page load, no swap. The hero's existing top padding (~7rem)
   already leaves clearance because the static header was
   position:absolute (out of flow) too — fixed is the same
   layout, just doesn't scroll away.

   1. Static wrapper goes from absolute → fixed (stays at top).
   2. The sliding sticky duplicate is hidden — only one header
      should be in play here, otherwise both would render once
      header-scroll.js fires.

   `body.has-announcement` still pushes the static down by
   --announcement-h via the rule above, so the fixed header docks
   below the announcement bar just like in the default flow. */
.gs-header-host--static.gs-header-host--solid {
  position: fixed;
}

body:has(.gs-header-host--solid) .gs-header-host--sticky {
  display: none;
}

@media (max-width: 767px) {
  /* Bar is hidden on mobile (announcement.css), so both static
     AND sticky headers dock back at the top regardless of
     has-announcement. */
  body.has-announcement .gs-header-host--static,
  body.has-announcement .gs-header-host--sticky {
    top: 0;
  }
}

/* Sticky — fixed at viewport top (or just below the sticky
   announcement bar when the bar is present). Hidden above viewport
   by default; slides in via translateY when .is-active is added by
   header-scroll.js. Always white bg + dark text.

   With phase-2 sticky announcement bar: top offset shifts to
   var(--announcement-h) on body.has-announcement so the sticky
   header sits BELOW the bar instead of overlapping it. Mobile
   (≤767px, announcement hidden) reverts to top: 0. */
.gs-header-host--sticky {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  transform: translateY(-100%);
  /* Visibility uses a delayed transition so the slide-OUT animation
     plays before the element goes invisible. With instant visibility
     toggling, the header would just disappear at the moment the
     class is removed even though the transform is still animating
     to translateY(-100%). 0s duration + 0.25s delay = visibility
     flips AT THE END of the slide-out. On slide-IN (.is-active),
     the rule below overrides delay to 0s so visibility flips
     immediately and the slide-in animates from the start. */
  visibility: hidden;
  transition:
    transform 0.25s ease,
    visibility 0s linear 0.25s;
}

.gs-header-host--sticky.is-active {
  transform: translateY(0);
  visibility: visible;
  transition:
    transform 0.25s ease,
    visibility 0s linear 0s;
}

/* ─── Base header — inner chrome (logo + nav + actions) ─────── */

.gs-header {
  position: relative;             /* positioning context for the mega-nav panel */
  /* Header padding is published as a custom property so flyout
     dropdowns can position themselves flush with the header's
     bottom border (top: calc(100% + var(--gs-header-pad-y))). The
     flyouts are anchored to .gs-nav__item which is sized to the
     trigger pill — they don't know about the header's outer
     padding without this hook. Mobile override below replaces both
     the padding and the variable in one rule. */
  --gs-header-pad-y: 0.85rem;
  padding: var(--gs-header-pad-y) 1.5rem;
  background: transparent;
  /* CLOSE direction — wait 0.4s (matches the mega-nav drawer's
     clip-path retract duration), then snap to transparent in 0s.
     Without this delay the bg fades back to transparent the moment
     aria-expanded flips to false, leaving the drawer mid-close
     against a transparent header — a visible "flash" the user
     perceives as the header collapsing prematurely. Open direction
     overrides to instant in the :has() rule below. */
  transition: background 0s linear 0.4s;
}

/* When the MEGA-NAV is open inside the transparent static header,
   fill the header with dark navy so it visually unifies with the
   full-width dark mega panel below — feels like the chrome
   "depth-shifts" into a solid state for the big interaction. The
   small flyout dropdowns (Community/Resources/Company) intentionally
   DON'T trigger this — they're compact floating cards, not a
   full-width strip, so the header should stay transparent and let
   the flyout read as a standalone panel against the hero.
   Scoped by `[data-nav-target]`, which is only present on the
   mega-nav trigger (header.html). Sticky + solid-mode headers stay
   white because their panels are white too. */
.gs-header-host--static:not(.gs-header-host--solid):has([data-nav-target] [aria-expanded="true"]) .gs-header {
  background: var(--color-bg-dark);
  /* OPEN direction — instant. The drawer extends from an already-
     navy header (no fade-in mid-extend), so the chrome reads as
     one continuous surface from the very first frame of the open
     animation. */
  transition: background 0s linear 0s;
}

/* Inside the sticky wrapper, the header always wears the "white"
   look. Inside the static wrapper, the header is transparent and
   light-text — UNLESS the page has `headerVariant: solid`, in
   which case the static wrapper gets `.gs-header-host--solid` and
   the inner header switches to the white-look too. */
.gs-header-host--sticky .gs-header,
.gs-header-host--static.gs-header-host--solid .gs-header {
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
}

.gs-header__inner {
  max-width: var(--content-max);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 2rem;
}

/* ─── Logo — two variants, swapped per wrapper state ──────── */

.gs-header__logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  flex-shrink: 0;
  position: relative;
}

.gs-header__logo-img {
  height: 32px;
  width: auto;
  display: block;
  transition: opacity 0.25s ease;
}

/* Stack both logos in the same slot — opacity controls which is
   visible per wrapper state. */
.gs-header__logo-img--dark {
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
}

/* Sticky wrapper + solid-mode static wrapper → show the dark
   (colored) logo. Default static wrapper → light (white) logo. */
.gs-header-host--sticky .gs-header__logo-img--light,
.gs-header-host--static.gs-header-host--solid .gs-header__logo-img--light {
  opacity: 0;
}
.gs-header-host--sticky .gs-header__logo-img--dark,
.gs-header-host--static.gs-header-host--solid .gs-header__logo-img--dark {
  opacity: 1;
}

/* ─── Primary nav ──────────────────────────────────────────── */

.gs-header__nav {
  display: flex;
  /* Tighter gap than before — each trigger now owns horizontal
     padding (0.85rem each side) for the pill hover, so the gap
     between items effectively grows. Reducing the container gap to
     0.25rem brings the text-to-text spacing back near the original
     ~24px while letting each pill have breathing room around its
     label. */
  gap: 0.25rem;
  margin-left: auto;
}

/* Each top-level nav item is a [data-nav-dropdown] container with a
   trigger button + a hidden panel (mega-nav or flyout). The trigger
   button is styled to look identical to the previous text-link nav. */
.gs-nav__item {
  position: relative;
  display: flex;
  align-items: center;
}

.gs-nav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: transparent;
  border: 0;
  /* Horizontal padding makes room for the pill bg on hover/active.
     Full radius gives the pill shape regardless of label length. */
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  font: inherit;
  font-size: 0.95rem;
  font-weight: var(--weight-medium);
  color: #fff;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.gs-header-host--sticky .gs-nav__link,
.gs-header-host--static.gs-header-host--solid .gs-nav__link {
  color: var(--color-fg);
}

/* Hover + open-state pill bg — same treatment as the sublink hovers
   so the affordance feels unified across all nav surfaces. Two
   variants based on the header background underneath:
   - dark/transparent static header → translucent white wash
   - white sticky/solid header        → light grey bg-muted
   Text color stays the same in both states; no coral shift. */
.gs-nav__link:hover,
.gs-nav__link[aria-expanded="true"] {
  background: rgba(255, 255, 255, 0.08);
}

.gs-header-host--sticky .gs-nav__link:hover,
.gs-header-host--sticky .gs-nav__link[aria-expanded="true"],
.gs-header-host--static.gs-header-host--solid .gs-nav__link:hover,
.gs-header-host--static.gs-header-host--solid .gs-nav__link[aria-expanded="true"] {
  background: var(--color-bg-muted);
}

.gs-nav__chev {
  width: 10px;
  height: 10px;
  flex-shrink: 0;
  opacity: 0.8;
  transition: transform 0.2s ease;
}

.gs-nav__link[aria-expanded="true"] .gs-nav__chev {
  transform: rotate(180deg);
}

/* Hover-bridge — closes the visual gap between the trigger and the
   panel below it so the panel doesn't snap shut when the user moves
   the cursor from one to the other. Pseudo-element on the item
   itself, sized to span the gap. */
.gs-nav__item::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 16px;
  /* Only "lit up" when the panel is open — otherwise it would absorb
     mouse events on the empty area below every nav item. */
  pointer-events: none;
}
.gs-nav__item:hover::after,
.gs-nav__item:focus-within::after {
  pointer-events: auto;
}

/* ─── Dropdown panel base ──────────────────────────────────── */

.gs-nav__panel {
  position: absolute;
  /* Flyouts are anchored to .gs-nav__item (sized to the trigger
     pill), so `top: 100%` would sit flush with the pill bottom —
     14px ABOVE the header's actual bottom border. Adding the
     header's vertical padding pushes the panel down to land
     exactly on the header's bottom edge: border-on-border.
     Mega-nav doesn't need this — it's positioned relative to
     .gs-header itself, so its `top: 100%` already hits header
     bottom. */
  top: calc(100% + var(--gs-header-pad-y));
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 16px;
  box-shadow: 0 16px 40px rgba(0, 27, 49, 0.12);
  z-index: 60;
  /* Base = CLOSED visual state. Removing [hidden] flips to open via
     the :not([hidden]) rule below; re-adding [hidden] reverts to
     this state and the close animation plays in reverse. */
  opacity: 0;
  transform: translateY(-6px);
  /* Keep it out of the a11y tree + the focus order while closed. */
  visibility: hidden;
  /* Close-direction transition. visibility flips to `hidden` AFTER
     the 0.18s opacity/transform animation finishes (0s linear with
     a 0.18s delay), so the panel stays painted while it fades out.
     Without this delay, `visibility: hidden` would apply instantly
     when [hidden] is re-added — the panel would snap off-screen
     and the close fade would never be visible. Open direction
     overrides the visibility delay to 0s in :not([hidden]) below. */
  transition:
    opacity 0.18s ease,
    transform 0.18s ease,
    visibility 0s linear 0.18s;
}

.gs-nav__panel[hidden] {
  /* The HTML `hidden` attr applies {display: none} via UA stylesheet;
     override to keep the panel in the layout tree so transitions can
     play. Visual state is set by the base rule above. */
  display: block;
  pointer-events: none;
}

.gs-nav__panel:not([hidden]) {
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
  /* Open-direction transition — visibility flips immediately (0s
     delay) so the opacity/transform fade-in starts from frame 0
     instead of waiting 0.18s. */
  transition:
    opacity 0.18s ease,
    transform 0.18s ease,
    visibility 0s linear 0s;
}

/* ─── Flyout variant (Community / Resources / Company) ────── */

.gs-nav__panel--flyout {
  /* Anchored under the trigger, sized to its content. */
  list-style: none;
  margin: 0;
  padding: 0.5rem;
  min-width: 220px;
  left: 0;
  /* Flyouts stack ABOVE the mega panel (base z-60). Without this,
     rapidly hovering from Offerings → Community while the mega is
     mid-close means the flyout opens at z-60 too — but DOM order
     puts the mega's element later (sibling of __inner, vs. flyout
     nested inside the item), so the still-painting mega ends up
     painting over the freshly-opened flyout. Bumping flyouts to
     70 keeps them on top regardless of DOM order during the
     overlap window. Same rule covers the language picker, which
     also carries .gs-nav__panel--flyout. */
  z-index: 70;
}

.gs-nav__panel--flyout li {
  margin: 0;
}

.gs-nav__panel--flyout a {
  display: block;
  padding: 0.6rem 0.85rem;
  border-radius: 8px;
  color: var(--color-fg);
  text-decoration: none;
  font-size: 0.95rem;
  /* Regular weight (400) — these are dense lists of secondary
     destinations, not primary CTAs. Heavier weight reads as visual
     noise when there are 3-4 links stacked together. */
  font-weight: var(--weight-regular);
  transition: background 0.15s ease, color 0.15s ease;
}

/* Hover = background highlight only, no color shift. The coral shift
   on top of the bg highlight was double-signalling and made the link
   feel "clicked" before activation. */
.gs-nav__panel--flyout a:hover {
  background: var(--color-bg-muted);
}

/* ─── Mega-nav variant (Offerings) ─────────────────────────── */

/* Full-width strip below the header — NOT a centered card. Lives
   as a sibling of .gs-header__inner so it can span the entire
   viewport via left:0; right:0 (anchored to .gs-header which is
   position: relative). Slides down on open via translateY; no
   horizontal motion. */
.gs-nav__panel--mega {
  top: 100%;
  left: 0;
  right: 0;
  width: auto;
  /* No card chrome — flat strip with a bottom border to separate
     it from the page below. */
  border-radius: 0;
  border: 0;
  border-bottom: 1px solid var(--color-border);
  /* Top divider — present in the box model always (so its 1px
     doesn't change the panel's content geometry between states),
     but transparent by default. The variant-scoped :has() rules
     below color it in when the mega-nav is open and let it snap
     back to transparent the moment the close is triggered —
     instantly, without waiting for the drawer's clip-path
     animation to finish. This is the key to the "no stranded
     hairline on a closing drawer" effect. */
  border-top: 1px solid transparent;
  box-shadow: 0 12px 24px rgba(0, 27, 49, 0.06);
  /* Horizontal padding mirrors .gs-header (0.85rem 1.5rem) so the
     centered panel-inner aligns with .gs-header__inner — logo's left
     edge lines up with the mega content's left edge. Without this,
     the panel sits flush to viewport and panel-inner's own padding
     pushes content inboard by an extra 1.5rem. */
  padding: 0 1.5rem;

  /* ── Drawer reveal animation ─────────────────────────────────
     The mega-nav doesn't fade or slide — it UNFURLS like a real
     drawer extending downward from the header. The clip-path is
     `inset(top right bottom left)`; animating the BOTTOM value
     from 100% (fully clipped) to 0% (fully revealed) makes the
     panel emerge progressively from its top edge.

     Why clip-path over max-height: the panel's children stay
     laid out at full size throughout — no compression, no
     reflow, no jumping when content fade-in begins. The reveal
     is a pure visual mask.

     Choreography pairs with .gs-nav__panel-inner opacity below
     for a two-beat animation:
       OPEN:  drawer extends (0 → 0.4s), then content fades in
              (0.4s → 0.7s) on the now-fully-revealed surface
       CLOSE: content snaps invisible (no transition), then
              drawer clips back up (0 → 0.4s)

     Base values + transitions here express the CLOSED state and
     close-direction timing. Override block in :not([hidden])
     below handles open. */
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 100% 0);
  visibility: hidden;
  transition:
    clip-path 0.4s ease,
    visibility 0s linear 0.4s;
}

.gs-nav__panel--mega:not([hidden]) {
  /* Fully revealed — clip-path bottom inset goes from 100% → 0%. */
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 0% 0);
  visibility: visible;
  /* Open direction — clip-path animates over 0.4s, visibility
     flips immediately so the drawer is paintable from frame 0. */
  transition:
    clip-path 0.4s ease,
    visibility 0s linear 0s;
}

/* Content inside the drawer — fades in AFTER the drawer is fully
   extended, snaps out INSTANTLY on close.

   Asymmetric direction: only the open-direction rule declares an
   opacity transition. On close the :not([hidden]) rule stops
   matching, opacity reverts to 0 with NO transition in scope, so
   the content disappears in one frame — letting the drawer
   close back up onto an already-empty surface. */
.gs-nav__panel--mega .gs-nav__panel-inner {
  opacity: 0;
}

.gs-nav__panel--mega:not([hidden]) .gs-nav__panel-inner {
  opacity: 1;
  /* 0.4s delay matches the drawer's expand duration above, so
     content fade-in begins at the moment the drawer finishes
     extending. 0.3s fade duration is brisk — the content has
     already had its "stage entrance" via the drawer. */
  transition: opacity 0.3s ease 0.4s;
}

.gs-nav__panel--mega .gs-nav__panel-inner {
  max-width: var(--content-max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 2.5rem;
  /* Vertical padding only — horizontal spacing comes from the
     panel's own padding (above), so this inner block mirrors
     .gs-header__inner's centering exactly. */
  padding: 2.5rem 0;
}

.gs-nav__mega-cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}

.gs-nav__mega-col {
  /* Each column = a labeled group of links. */
}

.gs-nav__mega-col-title {
  font-size: 0.8125rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* Use the lightest fg tier (muted) — body-tier was too dark next
     to the bold uppercase weight; titles read as a second row of
     links instead of a section label. Muted gives a clear "this is
     metadata above the links" register, matching what the dark
     variant gets via rgba(255,255,255,0.65) below. */
  color: var(--color-fg-muted);
  margin: 0 0 0.5rem;
}

.gs-nav__mega-col-title a {
  color: inherit;
  text-decoration: none;
  transition: color 0.15s ease;
}
/* No coral shift on hover — col-title links read as section headers,
   not interactive items; tinting them on hover felt visually noisy.
   The category text remains static; the linked sub-items below
   carry the hover affordance. */

.gs-nav__mega-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.gs-nav__mega-list li {
  margin: 0;
}

.gs-nav__mega-list a {
  display: block;
  /* Horizontal padding + matching negative margin so the visible
     hover box extends past the text on both sides while text-start
     still aligns with the col-title above. Vertical padding bumped
     to 0.55rem (from 0.4rem) — the original spacing felt too tight
     between adjacent links; a touch more vertical room makes each
     row easier to read and tap. */
  padding: 0.55rem 0.6rem;
  margin: 0 -0.6rem;
  border-radius: 6px;
  color: var(--color-fg);
  text-decoration: none;
  font-size: 0.95rem;
  transition: background 0.15s ease, color 0.15s ease;
}

.gs-nav__mega-list a:hover {
  background: var(--color-bg-muted);
}

.gs-nav__mega-footer {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--color-border);
}

.gs-nav__mega-tagline {
  font-size: 0.8125rem;
  color: var(--color-fg-body);
  margin: 0;
}

.gs-nav__mega-aside {
  display: flex;
  align-items: stretch;
  justify-content: stretch;
}

/* ─── Latest-blog card (sits in the mega-nav aside) ────────
   Compact card with visible chrome (border + bg). Short banner
   image up top, then padded body with eyebrow + NEW chip, title,
   and a date / "Read blog →" meta row. The whole anchor is the
   hover target — bg tint + arrow slide animate in tandem.
   Light variant lives here; dark variant overrides further down. */

.gs-nav__mega-blog-card {
  display: flex;
  flex-direction: column;
  /* 80% of the aside width — keeps the card as a compact teaser
     rather than letting it dominate the right column. margin-left:
     auto pushes the freed 20% to the LEFT side, which puts the
     breathing room next to the link columns (visually nicer than
     a gap at the page edge). */
  width: 80%;
  margin-left: auto;
  border: 1px solid var(--color-border);
  border-radius: 12px;
  background: var(--color-bg);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: background 0.18s ease, border-color 0.18s ease;
}

.gs-nav__mega-blog-card:hover {
  background: var(--color-bg-muted);
}

.gs-nav__mega-blog-cover {
  /* No fixed height — image is shown at its natural aspect ratio
     at the card's full width. Cropping the cover to a 100px strip
     buried the visual interest of the blog hero artwork. */
  width: 100%;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.05);
}

.gs-nav__mega-blog-cover img {
  width: 100%;
  height: auto;
  display: block;
  /* Subtle zoom on card hover for a "live preview" feel — small
     enough to not draw attention to itself, big enough to read
     as intentional. */
  transition: transform 0.4s ease;
}

.gs-nav__mega-blog-card:hover .gs-nav__mega-blog-cover img {
  transform: scale(1.04);
}

.gs-nav__mega-blog-body {
  padding: 0.85rem 1rem 0.95rem;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

/* Eyebrow + NEW badge sit on the same row. */
.gs-nav__mega-blog-eyebrow-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.gs-nav__mega-blog-eyebrow {
  font-size: 0.7rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-fg-muted);
}

/* NEW chip — small coral pill calling attention to fresh content.
   Coral chosen over a green/blue "info" tint because it matches
   the brand-accent CTA color we use elsewhere, so the affordance
   reads as "this is a clickable highlight" not a status badge. */
.gs-nav__mega-blog-new {
  display: inline-block;
  background: var(--color-brand-accent);
  color: #fff;
  font-size: 0.6rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.12rem 0.45rem;
  border-radius: 4px;
  line-height: 1.4;
}

.gs-nav__mega-blog-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: var(--weight-semibold);
  line-height: 1.35;
  color: var(--color-fg);
  /* Single-line cap with ellipsis. Long titles get visually
     truncated rather than wrapping — the card's vertical rhythm
     stays predictable no matter which post is featured. The
     anchor's title attribute (not set here, but easy to add via
     a `title` attr in header.html bound to the blog post title) would expose
     the full text on hover if needed. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gs-nav__mega-blog-meta {
  margin: 0.15rem 0 0;
  display: flex;
  align-items: center;
  /* Default flex-start — the CTA is the only child of this row
     now (date was removed), so left-aligned is correct. */
  gap: 0.5rem;
  font-size: 0.78rem;
}

/* "Read blog →" — text CTA, lives at the left of the meta row.
   Bold + currentColor border-free, with an arrow that slides
   right on hover (matches the cs-cta and customer-band CTAs
   elsewhere on the site). */
.gs-nav__mega-blog-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--color-fg);
  font-weight: var(--weight-semibold);
}

.gs-nav__mega-blog-arrow {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  transition: transform 0.18s ease;
}

.gs-nav__mega-blog-card:hover .gs-nav__mega-blog-arrow {
  transform: translateX(3px);
}

/* ─── Dark variant — dropdowns inside the transparent static header
   (i.e. on dark-hero pages where the header reads as chrome over
   the hero). Flips the panel chrome to dark-navy so it matches
   the header's visual register instead of slamming a white slab
   under a transparent dark header. Scoped via the host modifier;
   the sticky header + solid-mode static header both stay light. */

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__panel {
  background: var(--color-bg-dark);
  border-color: rgba(255, 255, 255, 0.12);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.3);
}

/* Mega panel — full-width strip on dark navy. The bottom hairline
   travels with the drawer's clip-path (animates closed with it).
   The TOP hairline (the seam-divider between header and panel) is
   controlled by the :has() rules below, NOT here — so it can
   snap on/off independently of the drawer's expand/retract.

   Why `border-top-color: transparent` is restated here: the
   higher-specificity host rule above (.gs-header-host--static...
   .gs-nav__panel) uses the `border-color` shorthand, which sets
   ALL four sides INCLUDING top — overriding the base mega's
   `border-top: 1px solid transparent`. Without this explicit
   restate, the top hairline stays painted in the dark variant
   when the :has() rule stops matching, defeating the snap-out. */
.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__panel--mega {
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  border-top-color: transparent;
}

/* Divider — top hairline appears INSTANTLY when the mega-nav opens
   and disappears INSTANTLY the moment close is triggered (no
   transition is declared on border-top-color in either direction,
   so changes snap in one frame). The drawer's clip-path retract
   then plays over 0.4s with no stranded hairline on top. */
.gs-header-host--static:not(.gs-header-host--solid):has([data-nav-target] [aria-expanded="true"]) .gs-nav__panel--mega {
  border-top-color: rgba(255, 255, 255, 0.12);
}

/* White-variant divider — same snap-in/snap-out pattern as dark,
   colored in --color-border to match the rest of the light-mode
   chrome (sticky header bottom border + flyout borders). */
.gs-header-host--sticky:has([data-nav-target] [aria-expanded="true"]) .gs-nav__panel--mega,
.gs-header-host--static.gs-header-host--solid:has([data-nav-target] [aria-expanded="true"]) .gs-nav__panel--mega {
  border-top-color: var(--color-border);
}

/* Mega-nav text inside the dark variant. */
.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-col-title,
.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-col-title a {
  color: rgba(255, 255, 255, 0.65);
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-list a {
  color: #fff;
}

/* Dark-variant hover for mega-list links — translucent white wash
   instead of a coral text shift, mirroring the flyout treatment. */
.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-list a:hover {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-footer {
  border-top-color: rgba(255, 255, 255, 0.12);
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-tagline {
  color: rgba(255, 255, 255, 0.65);
}

/* Latest-blog card — dark-variant overrides. Card chrome flips
   to a translucent-white border + slightly-lighter bg tint so the
   card visually separates from the dark mega panel surface
   without slamming a white box in. Text/eyebrow/meta tints
   mirror the rest of the dark mega-nav (white + 55/65% white).
   The NEW chip keeps its coral fill in both variants — that's
   its job. */
.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-blog-card {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.12);
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-blog-card:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.2);
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-blog-eyebrow {
  color: rgba(255, 255, 255, 0.55);
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-blog-title {
  color: #fff;
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-blog-date {
  color: rgba(255, 255, 255, 0.55);
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-blog-cta {
  color: #fff;
}

/* "Learn more" CTA — .gs-btn--secondary uses color: inherit, which
   on the dark mega-nav would inherit the body's dark grey from
   somewhere up the tree and read as low-contrast against the navy
   bg. Force white text + border so the button reads as crisp
   outlined-on-dark. Mirror the white-wash hover used by mega-list
   links so the affordance feels unified. */
.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-footer .gs-btn--secondary {
  color: #fff;
}
.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__mega-footer .gs-btn--secondary:hover {
  background: rgba(255, 255, 255, 0.08);
}

/* Flyout links — white text, coral hover, no background tint
   (the dark panel itself carries the look). */
.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__panel--flyout a {
  color: #fff;
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__panel--flyout a:hover {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
}

/* ─── Right-side actions ───────────────────────────────────── */

.gs-header__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.gs-header__icon-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;                       /* default (static) */
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.gs-header__icon-btn:hover {
  background: rgba(255, 255, 255, 0.12);
}

.gs-header-host--sticky .gs-header__icon-btn,
.gs-header-host--static.gs-header-host--solid .gs-header__icon-btn {
  color: var(--color-fg);
}

.gs-header-host--sticky .gs-header__icon-btn:hover,
.gs-header-host--static.gs-header-host--solid .gs-header__icon-btn:hover {
  /* Bg lightens; icon color stays at the inherited fg color (dark)
     instead of flipping to brand blue. Coral or blue tint on the
     icon felt over-signalled — the bg shift alone is enough hover
     affordance and reads cleaner against the white header. */
  background: var(--color-bg-muted);
}

.gs-header__icon-btn svg {
  width: 18px;
  height: 18px;
  display: block;
}

.gs-header__lang {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.75rem;
  border-radius: 999px;
  border: 0;
  background: transparent;
  color: #fff;
  font: inherit;
  font-size: 0.9rem;
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.gs-header__lang:hover,
.gs-header__lang[aria-expanded="true"] {
  background: rgba(255, 255, 255, 0.12);
}

.gs-header-host--sticky .gs-header__lang,
.gs-header-host--static.gs-header-host--solid .gs-header__lang {
  color: var(--color-fg);
}

.gs-header-host--sticky .gs-header__lang:hover,
.gs-header-host--sticky .gs-header__lang[aria-expanded="true"],
.gs-header-host--static.gs-header-host--solid .gs-header__lang:hover,
.gs-header-host--static.gs-header-host--solid .gs-header__lang[aria-expanded="true"] {
  background: var(--color-bg-muted);
}

.gs-header__lang-globe {
  width: 16px;
  height: 16px;
  display: block;
}

.gs-header__lang-chev {
  width: 10px;
  height: 10px;
  display: block;
  opacity: 0.7;
  transition: transform 0.2s ease;
}

/* Chevron rotates 180° when the language dropdown is open — same
   affordance as the main nav links. */
.gs-header__lang[aria-expanded="true"] .gs-header__lang-chev {
  transform: rotate(180deg);
}

/* ─── Language flyout — compact 2-option picker ────────────── */

.gs-nav__panel--lang {
  /* Right-aligned (override base flyout's left: 0) because the
     trigger sits in the right-side actions cluster — a left-aligned
     panel would push off-screen on narrow viewports. */
  left: auto;
  right: 0;
  min-width: 180px;
}

/* Lang picker options. Phase 2 changed these from <button> to <a>
   so right-click + new-tab works, anchors carry their own keyboard
   semantics, no JS needed for plain navigation. CSS targets both
   button + a via the .gs-nav__lang-option class so the picker
   chrome doesn't care which element type the option uses. */
.gs-nav__panel--lang .gs-nav__lang-option {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  background: transparent;
  border: 0;
  padding: 0.6rem 0.85rem;
  border-radius: 8px;
  color: var(--color-fg);
  text-decoration: none;
  font: inherit;
  font-size: 0.9rem;
  font-weight: var(--weight-regular);
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease;
}

.gs-nav__panel--lang .gs-nav__lang-option:hover {
  background: var(--color-bg-muted);
}

/* Code chip (EN / DE) — bolder than the label, fixed-width
   feel so both rows align cleanly. */
.gs-nav__lang-code {
  font-weight: var(--weight-bold);
  font-size: 0.8125rem;
  letter-spacing: 0.03em;
  min-width: 1.5rem;
}

.gs-nav__lang-name {
  flex: 1;
}

/* Active-language marker — checkmark via ::after on the button.
   Color is intentionally NOT set so it inherits from the button's
   own color (dark text on light variant, white on dark variant).
   Picking an accent color here made it read as a separate UI
   element instead of confirming the active row. */
.gs-nav__panel--lang .gs-nav__lang-option[aria-current="true"]::after {
  content: '✓';
  font-weight: var(--weight-bold);
}

/* Dark variant — when the lang flyout opens from the transparent
   static header, mirror the existing dark-panel chrome treatment. */
.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__panel--lang .gs-nav__lang-option {
  color: #fff;
}

.gs-header-host--static:not(.gs-header-host--solid) .gs-nav__panel--lang .gs-nav__lang-option:hover {
  background: rgba(255, 255, 255, 0.08);
}

/* CTA — orange-gradient pill in both states. */
.gs-header__cta {
  margin-left: 0.25rem;
}

/* ─── Hamburger button ────────────────────────────────────────
   Visible only at ≤1024px (tablet + mobile). The three line spans
   animate into an X via aria-expanded="true". Both header copies
   carry their own hamburger but share one mobile-menu overlay
   (singleton at body root). */

.gs-header__hamburger {
  /* Hidden on desktop — only the @media block below shows it. */
  display: none;
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: #fff;
  cursor: pointer;
  align-items: center;
  justify-content: center;
  transition: background 0.2s ease, color 0.2s ease;
}

.gs-header__hamburger:hover {
  background: rgba(255, 255, 255, 0.12);
}

/* Color flips to dark on sticky/solid headers — same pattern as
   .gs-header__icon-btn next to it. */
.gs-header-host--sticky .gs-header__hamburger,
.gs-header-host--static.gs-header-host--solid .gs-header__hamburger {
  color: var(--color-fg);
}
.gs-header-host--sticky .gs-header__hamburger:hover,
.gs-header-host--static.gs-header-host--solid .gs-header__hamburger:hover {
  background: var(--color-bg-muted);
}

/* The three-line stack — absolutely positioned so each line can
   animate from horizontal-line to X-arm independently. */
.gs-header__hamburger-lines {
  position: relative;
  width: 20px;
  height: 14px;
  display: block;
}

.gs-header__hamburger-lines span {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
  transition: top 0.25s ease, transform 0.25s ease, opacity 0.2s ease;
}

.gs-header__hamburger-lines span:nth-child(1) { top: 0; }
.gs-header__hamburger-lines span:nth-child(2) { top: 6px; }
.gs-header__hamburger-lines span:nth-child(3) { top: 12px; }

/* Open state — collapse the top + bottom lines into the middle
   and rotate them into an X; fade the middle out. */
.gs-header__hamburger[aria-expanded="true"] .gs-header__hamburger-lines span:nth-child(1) {
  top: 6px;
  transform: rotate(45deg);
}
.gs-header__hamburger[aria-expanded="true"] .gs-header__hamburger-lines span:nth-child(2) {
  opacity: 0;
}
.gs-header__hamburger[aria-expanded="true"] .gs-header__hamburger-lines span:nth-child(3) {
  top: 6px;
  transform: rotate(-45deg);
}

/* ─── Mobile-menu overlay ────────────────────────────────────
   Full-viewport dark-navy overlay that takes over below the header
   when the hamburger is tapped. Uses `position: fixed; inset: 0`
   but with `z-index: 39` — that's intentionally LOWER than the
   header hosts (40 + 50), so the header stays visually on top.
   The menu's dark-navy bg shows through the static header (which
   is transparent over the hero) — so on dark-hero pages the header
   reads as a continuous chrome strip with the menu beneath. On
   the white sticky header the menu's bg starts cleanly below the
   header's opaque white surface. Either way, the hamburger / X
   button + search icon stay tappable during the menu interaction.
*/

.gs-mobile-menu {
  /* Theme tokens — overridden by .gs-mobile-menu--light below for
     the white-header variant. All menu chrome reads from these
     custom properties instead of hardcoded colors, so flipping the
     variant only requires changing the variables. */
  --menu-bg: var(--color-bg-dark);
  --menu-fg: #fff;
  --menu-fg-muted: rgba(255, 255, 255, 0.55);
  --menu-divider: rgba(255, 255, 255, 0.08);
  --menu-hover-bg: rgba(255, 255, 255, 0.06);
  --menu-hover-bg-strong: rgba(255, 255, 255, 0.08);
  --menu-lang-pill-bg: rgba(255, 255, 255, 0.06);
  --menu-lang-active-bg: rgba(255, 255, 255, 0.14);
  --menu-social-bg: rgba(255, 255, 255, 0.06);
  --menu-social-bg-hover: rgba(255, 255, 255, 0.12);
  --menu-social-fg: rgba(255, 255, 255, 0.65);

  position: fixed;
  inset: 0;
  z-index: 39;
  background: var(--menu-bg);
  /* The menu itself is the scrolling container — views + footer
     live as direct children in normal flow. This means the footer
     starts right after the visible view's content, instead of
     being pinned to the viewport bottom by a flex layout. */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Body-scroll lock is applied by header-menu.js via the
     gs-mobile-menu-open class on the html AND body elements (both needed —
     browsers vary on which element handles the document scroll). */
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.25s ease,
    visibility 0s linear 0.25s;
}

.gs-mobile-menu:not([hidden]) {
  opacity: 1;
  visibility: visible;
  transition:
    opacity 0.25s ease,
    visibility 0s linear 0s;
}

.gs-mobile-menu[hidden] {
  /* Keep the element in the layout tree so the fade-out transition
     plays — actual hidden visual state comes from the base rule
     above. */
  display: block;
}

/* When the menu is open, lock scroll on BOTH html and body. Browsers
   vary on which element handles the document scroll — most modern
   browsers scroll the html element, but some legacy or quirks-mode contexts
   scroll the body. Locking both is the only reliable way to prevent
   page-behind scrolling on every browser. */
html.gs-mobile-menu-open,
body.gs-mobile-menu-open {
  overflow: hidden;
}

/* Hard prevent any horizontal scroll on the body. Mobile menus, fixed
   positioned overlays, and edge-case section widths can occasionally
   push the document past viewport width and produce a phantom right-
   side gutter. Clipping at the body keeps the page bounded.

   IMPORTANT: `overflow-x: clip` (not `hidden`) — `hidden` promotes
   the html element to be the scroll container and breaks every `position:
   sticky` element across the site (customer-story sidebar, learn-
   page progress bar, listing filter bars). `clip` provides the
   same visual clipping but does NOT create a scroll container, so
   sticky positioning continues to work as authored.

   ALSO IMPORTANT — `overflow-y` must be declared explicitly here
   too (`visible`, the normal default), even though that's a no-op
   value change on desktop browsers. Per the CSS Overflow spec,
   `overflow-x`/`overflow-y` are a computed pair: if one axis is
   `visible` and the other isn't, `visible` is supposed to compute
   to `auto` on that axis too — but iOS Safari has a known bug where
   `overflow-x: clip` set alone (`overflow-y` left at its implicit
   default) doesn't reliably apply the clip on the x-axis at all.
   Setting both explicitly avoids relying on that computed-pair
   resolution and makes the clip actually take effect on iOS
   Safari. `overflow-y: visible` keeps normal page scroll intact —
   it's not `auto`/`scroll`, so it doesn't create a new scroll
   container either. */
html,
body {
  overflow-x: clip;
  overflow-y: visible;
}

/* ─── Individual views ─────────────────────────────────────
   No slide, no transform — drilling in just swaps which view's
   [hidden] attribute is set. Native UA stylesheet handles
   display: none for hidden views. Each view is content-sized;
   the menu container is the scroll context, so the shared footer
   flows naturally after the visible view's content. */

.gs-mobile-menu__view {
  box-sizing: border-box;
  /* Padding-top clears the header bar only — when the menu is open,
     the static header is shifted to top: 0 (see rule below), so
     the announcement bar's 48px offset doesn't apply. Single value
     works at every breakpoint. */
  padding: calc(var(--gs-header-h, 64px) + 0.75rem) 1.5rem 0;
}

/* While the mobile menu is open, anchor the static header back to
   viewport top — overriding the `body.has-announcement .gs-header
   -host--static { top: 48px }` rule. The announcement bar lives
   behind the menu (z-35 < z-39), so reserving its 48px above the
   header leaves an empty navy strip at the top of the menu — the
   header reads as "floating" mid-page. Snapping the header up
   means logo + X sit at viewport top, flush with the menu chrome. */
body.gs-mobile-menu-open .gs-header-host--static {
  top: 0;
}

/* ─── Root screen — category list (drill-in buttons) ──────── */

.gs-mobile-menu__cats {
  display: flex;
  flex-direction: column;
  /* Small inter-card gap instead of dividing borders — borders
     don't follow border-radius, so a per-button border-bottom
     would draw a straight line across the rounded corners. A
     subtle gap reads as separation without the geometry conflict. */
  gap: 0.25rem;
  /* Negative horizontal margin pulls the cards out to within
     0.75rem of the viewport edges (matches the sublink hover
     pattern), giving the rounded hover boxes more presence
     while keeping the labels visually anchored to the screen's
     content-area inset. */
  margin: 0 -0.75rem;
}

.gs-mobile-menu__cat {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: transparent;
  border: 0;
  border-radius: 10px;
  padding: 1rem 0.75rem;
  color: var(--menu-fg);
  font: inherit;
  font-size: 1.15rem;
  font-weight: var(--weight-semibold);
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease;
}

.gs-mobile-menu__cat:hover {
  background: var(--menu-hover-bg);
}

/* Right-pointing chevron — indicates "drill into a sub-page".
   No rotation animation; the slide handles the visual feedback. */
.gs-mobile-menu__cat-chev {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  opacity: 0.5;
  transition: transform 0.15s ease, opacity 0.15s ease;
}

.gs-mobile-menu__cat:hover .gs-mobile-menu__cat-chev {
  opacity: 1;
  transform: translateX(2px);
}

/* ─── Sub-screen header (back button + section title) ─────── */

.gs-mobile-menu__back {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: transparent;
  border: 0;
  padding: 0.4rem 0.6rem 0.4rem 0.2rem;
  margin-bottom: 0.5rem;
  margin-left: -0.2rem;
  border-radius: 8px;
  color: var(--menu-fg-muted);
  font: inherit;
  font-size: 0.9rem;
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.gs-mobile-menu__back:hover {
  background: var(--menu-hover-bg);
  color: var(--menu-fg);
}

.gs-mobile-menu__back svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.gs-mobile-menu__sub-title {
  margin: 0 0 1rem;
  font-size: 1.5rem;
  font-weight: var(--weight-bold);
  color: var(--menu-fg);
  line-height: 1.2;
}

/* Group title row — uppercase preheadline with a hairline divider
   trailing to the right edge of the screen content. flex layout so
   the ::after pseudo-element can take the remaining width. Looks
   like a magazine-style section heading. */
.gs-mobile-menu__group-title {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0 0.4rem;
  font-size: 0.75rem;
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--menu-fg-muted, rgba(255, 255, 255, 0.5));
}

.gs-mobile-menu__group-title::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--menu-divider, rgba(255, 255, 255, 0.08));
}

.gs-mobile-menu__group-title a {
  color: inherit;
  text-decoration: none;
}

.gs-mobile-menu__group-title:first-child {
  margin-top: 0.25rem;
}

.gs-mobile-menu__list {
  list-style: none;
  margin: 0 0 0.25rem;
  padding: 0;
}

.gs-mobile-menu__list a {
  display: block;
  position: relative;
  /* Horizontal padding + matching negative margin form the same
     pill-bg hover as the mega-nav sublinks: visible hover box
     extends past the text on both sides while the text alignment
     stays put. Extra right padding (2rem) leaves room for the
     chevron rendered via ::after below. */
  padding: 0.6rem 2rem 0.6rem 0.75rem;
  margin: 0 -0.75rem;
  border-radius: 8px;
  color: var(--menu-fg);
  text-decoration: none;
  font-size: 1rem;
  font-weight: var(--weight-regular);
  transition: background 0.15s ease;
}

.gs-mobile-menu__list a:hover {
  background: var(--menu-hover-bg-strong);
}

/* Right-pointing chevron on every sub-page link — same visual
   vocabulary as the category buttons on root. CSS mask-image lets
   us recolor the SVG via background-color (otherwise the data URI
   would need a hardcoded stroke color that wouldn't switch with
   the light/dark variant). Subtle opacity by default; brighter +
   slides right 2px on hover. */
.gs-mobile-menu__list a::after {
  content: '';
  position: absolute;
  right: 0.75rem;
  top: 50%;
  width: 10px;
  height: 10px;
  margin-top: -5px;
  background-color: var(--menu-fg);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><polyline points='4,2 8,6 4,10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/></svg>") no-repeat center / contain;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'><polyline points='4,2 8,6 4,10' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/></svg>") no-repeat center / contain;
  opacity: 0.5;
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.gs-mobile-menu__list a:hover::after {
  opacity: 1;
  transform: translateX(2px);
}

/* ─── Menu footer (language, CTA, socials, legal) ──────────── */

.gs-mobile-menu__footer {
  /* Shared singleton in normal flow — appears right after the
     visible view's content. Short views (Community, Resources,
     Company) show the footer high on the page; long views
     (Offerings) push it past the fold for scroll-reveal. No flex
     anchor — that's intentional, so we don't paint empty space
     above the footer when content is short. */
  display: flex;
  flex-direction: column;
  gap: 1rem;
  /* Top margin sets minimum air between view content and footer.
     Side padding matches view content alignment. Bottom padding
     adds safe-area inset for iOS home-bar overlap. */
  margin-top: 2rem;
  padding: 1.25rem 1.5rem calc(1.25rem + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--menu-divider, rgba(255, 255, 255, 0.08));
}

/* Language picker — two-button pill row matching the desktop
   dropdown's active-state visual. Uses the same [data-lang-option]
   hooks so lang-toggle.js wires it up automatically. */
.gs-mobile-menu__lang {
  display: inline-flex;
  background: var(--menu-lang-pill-bg);
  border-radius: 999px;
  padding: 0.25rem;
  gap: 0.25rem;
  align-self: flex-start;
}

/* Both <a> and <button> are valid markup here — the mobile menu
   uses <a> (so right-click / new-tab / native nav work) when the
   page has alternateUrls; legacy <button> path stays valid too. */
.gs-mobile-menu__lang > a,
.gs-mobile-menu__lang button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: transparent;
  border: 0;
  padding: 0.45rem 0.95rem;
  border-radius: 999px;
  color: var(--menu-fg-muted);
  font: inherit;
  font-size: 0.875rem;
  font-weight: var(--weight-medium);
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
}

.gs-mobile-menu__lang > a:hover,
.gs-mobile-menu__lang button:hover {
  color: var(--menu-fg);
}

.gs-mobile-menu__lang > a[aria-current="true"],
.gs-mobile-menu__lang button[aria-current="true"] {
  background: var(--menu-lang-active-bg);
  color: var(--menu-fg);
}

.gs-mobile-menu__lang-code {
  font-weight: var(--weight-bold);
  font-size: 0.8rem;
  letter-spacing: 0.03em;
}

/* Contact us — primary brand CTA, full width so it's an obvious
   final conversion path at the bottom of the menu. .gs-btn--primary
   gives the gradient pill style; we override width here. */
.gs-mobile-menu__cta {
  width: 100%;
  text-align: center;
  justify-content: center;
}

/* Socials row — same icon vocabulary as the footer. */
.gs-mobile-menu__socials {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 0.75rem;
}

.gs-mobile-menu__socials a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--menu-social-bg);
  color: var(--menu-social-fg);
  transition: background 0.15s ease, color 0.15s ease;
}

.gs-mobile-menu__socials a:hover {
  background: var(--menu-social-bg-hover);
  color: var(--menu-fg);
}

.gs-mobile-menu__socials svg {
  width: 16px;
  height: 16px;
  display: block;
}

.gs-mobile-menu__legal {
  margin: 0;
  font-size: 0.85rem;
}

.gs-mobile-menu__legal a {
  color: var(--menu-fg-muted);
  text-decoration: none;
  transition: color 0.15s ease;
}

.gs-mobile-menu__legal a:hover {
  color: var(--menu-fg);
}

/* ─── Light variant — applied by JS when the visible header is
   white (sticky-active OR static in solid mode). Flips every menu
   color token to a light palette; structure + layout are
   unchanged. The chevron mask-image inherits `background-color`
   from var(--menu-fg) so it recolors automatically. */

.gs-mobile-menu--light {
  --menu-bg: var(--color-bg);
  --menu-fg: var(--color-fg);
  --menu-fg-muted: var(--color-fg-muted);
  --menu-divider: var(--color-border);
  /* All interactive bgs use the brand's pale-blue tokens — the same
     light-variant treatment the desktop nav flyouts + language
     dropdown use. `--color-bg-muted` (#f0f7f9) for idle / hover
     surfaces; `--color-bg-muted-deep` (#e1edf5) for the "stronger"
     step (social hover where the circle already starts tinted). */
  --menu-hover-bg: var(--color-bg-muted);
  --menu-hover-bg-strong: var(--color-bg-muted);
  --menu-lang-pill-bg: var(--color-bg-muted);
  --menu-lang-active-bg: var(--color-fg);
  --menu-social-bg: var(--color-bg-muted);
  --menu-social-bg-hover: var(--color-bg-muted-deep);
  --menu-social-fg: var(--color-fg-muted);
}

/* Light variant — the lang-active option needs WHITE text against
   its dark pill bg (inverse of dark variant where text is white on
   translucent-white). Override the inherited --menu-fg here. */
.gs-mobile-menu--light .gs-mobile-menu__lang > a[aria-current="true"],
.gs-mobile-menu--light .gs-mobile-menu__lang button[aria-current="true"] {
  color: #fff;
}

/* ─── Mobile / tablet breakpoint ─────────────────────────────
   Single breakpoint at 1024px swaps desktop nav for hamburger.
   At this width the four nav items + actions cluster start fighting
   for room — moving everything into the menu keeps the chrome
   clean. Search button stays visible because Cmd+K doesn't exist
   on touch devices; it's the only way to access search. */
@media (max-width: 1024px) {
  .gs-header__nav,
  .gs-header__lang {
    display: none;
  }
  /* CTA needs the combined `.gs-btn.gs-header__cta` selector to win
     over btn.css's `.gs-btn { display: inline-flex }` — btn.css
     loads AFTER header.css in base.html, so at equal specificity
     (one class) the later one wins by source order. Doubling up
     the classes bumps specificity to 2, which beats `.gs-btn`. */
  .gs-btn.gs-header__cta {
    display: none;
  }
  .gs-header__hamburger {
    display: inline-flex;
  }
  /* The mega-nav panel is a SIBLING of .gs-header__inner (see
     header.html) so it isn't touched by `.gs-header__nav {
     display: none }` above — that only hides the trigger button,
     not the panel itself. `.gs-nav__panel[hidden] { display: block
     }` (base rule, needed so the desktop close-fade can animate)
     then keeps the panel in the layout tree at every width. On
     mobile the trigger is gone and the panel can never be opened,
     but its 3-column grid content (`.gs-nav__mega-cols`, plus the
     blog card aside) was still sitting in the DOM at `display:
     block`, invisible (opacity/visibility) but at full desktop
     min-content width — wider than any phone viewport. That
     phantom width is what pushed `.gs-header` (and the document)
     past 393px, producing the horizontal scrollbar / right-side
     gutter on mobile Safari despite `overflow-x: clip` on html/body
     (see note below on why clip alone didn't catch it).
     Force it back to `display: none` here so it drops out of the
     layout tree entirely at the same breakpoint its trigger
     disappears — mirrors the flyouts, which have no such override
     because they're small enough to never have caused this. */
  .gs-nav__panel--mega {
    display: none !important;
  }
  /* Without the nav in the flex flow, the actions group has no
     `margin-left: auto` upstream to push it to the right (the nav
     itself carried that, but it's display:none here). Restate
     auto-margin on actions so search + hamburger anchor to the
     right edge of the header instead of crowding next to the logo. */
  .gs-header__actions {
    margin-left: auto;
  }

  /* At tablet/mobile widths the static-transparent header gets a
     solid navy bg instead of the desktop see-through-the-hero
     treatment. Reason: the mobile menu opens directly behind it
     (z-39 < header z-40), so a transparent header lets the menu
     content (back button, category list) bleed through and overlap
     the logo. Solid bg anchors the chrome cleanly above the menu.
     Sticky-active + static-solid headers already have a white bg
     from their own variant rules, so no changes needed there. */
  .gs-header-host--static:not(.gs-header-host--solid) .gs-header {
    background: var(--color-bg-dark);
  }

  /* New site (header-new.html): see-through over the hero like on
     desktop, and solid only while the mobile menu is open - which is
     the one case the solid bg above exists for. */
  .gs-header-host--static:not(.gs-header-host--solid) .gs-header.gs-header--new {
    background: transparent;
    transition: none; /* no delay either way - the shared 0.4s is for the desktop mega-nav */
  }

  /* Opening: stay transparent while the menu fades in underneath (0.25s),
     then snap to the same colour as the menu - so no flash. */
  .gs-header-host--static:not(.gs-header-host--solid):has([data-menu-toggle][aria-expanded="true"]) .gs-header.gs-header--new {
    background: var(--color-bg-dark);
    transition: background 0s linear 0.25s;
  }
}

/* Header padding stays consistent across breakpoints — earlier we
   tightened it at ≤767px (smaller pad-y + horizontal + inner gap)
   to "save space," but the jump caused a visible logo/hamburger
   shift right at the 767/768 boundary on resize. At 320px+ the
   default 0.85rem × 1.5rem padding still leaves plenty of room
   for logo + search + hamburger, so the override wasn't earning
   its keep. Keeping a single padding value across all widths. */
