/* ============================================
   Agent Platform product page (/capabilities/agent-platform/).

   Only the pieces the page needs that the shared catalog does not
   already provide. Everything else on that page reuses existing
   components: gs-uc-hero, gs-wgs-section, gs-wgs-rows,
   gs-wgs-features, gs-uc-challenges, gs-customer-logos, gs-faq,
   gs-cta, gs-btn.

   Every value comes from tokens.css. The two-accent rule holds:
   blue is the interactive accent, coral is brand-editorial and
   never means click-me (see PATTERNS.md §2).

   New here:
     .gs-ap-hero__*      hero eyebrow
     .gs-ap-proof        hero proof row - icon + claim, above the
                         logo strip (replaced the trust chips)
     .gs-ap-ph           labelled placeholder frame (hero visual,
                         diagram, video, screenshots). Temporary:
                         delete each frame as the real asset lands.
     .gs-ap-trust__line  caption above the customer-logo marquee
                         nested in the hero
     .gs-ap-row__*       stacked paragraphs + contextual link
                         inside a gs-wgs-row
     .gs-ap-dg__*        the two inline-SVG product diagrams
     .gs-ap-oss__*       "built on open source" strip + logo slots
     .gs-ap-shots        3-up screenshot strip with captions
     .gs-ap-blast__*     nested "blast radius" rings + legend
     .gs-ap-deploy       four deployment targets as 2x2 cards
     .gs-ap-infra__*     sovereignty prose + the Kubernetes note,
                         left column of the two-column section 6
     .gs-ap-paths__*     dominant founding-customer block plus the
                         deliberately subordinate OSS strip
     .gs-ap-ref__*       angled-split reference cards
   ============================================ */

/* ─── 1. Hero ─────────────────────────────── */

/* More breathing room below the hero than the shared
   .gs-uc-hero--centered default (4rem). This hero carries three
   stacked blocks the other centered heroes don't - proof row, logo
   marquee, trust caption - so the last line of small grey text was
   landing close to the next section's edge.

   Desktop only, and scoped to this page: the shared rule stays the
   default for every other hero using it, and on mobile 4rem is
   already generous relative to the viewport. */
@media (min-width: 768px) {
  .gs-ap-hero {
    padding-bottom: 6rem;
  }
}

/* Mobile hero corrections, all four scoped to this hero so the
   shared .gs-uc-hero--centered rules stay the default for the nine
   other heroes using them. */
@media (max-width: 767px) {
  /* Top clearance. The shared rule's 6rem, plus the 3.5rem intro
     margin it also sets, put 152px of empty navy above the eyebrow -
     tuned for a desktop viewport, far too much of a phone screen.
     4.5rem + 2rem = 104px still clears the ~64px fixed mobile
     header with a visible band to spare. */
  .gs-ap-hero {
    padding-top: 4.5rem;
  }

  .gs-ap-hero .gs-wgs-section__intro {
    margin-top: 2rem;
  }

  /* Let the headline use the full measure. base.css sets
     `text-wrap: balance` on every heading, which keeps the line
     count but evens the line LENGTHS - so a 3-line headline in a
     342px column comes out as three ~210px lines, reading as though
     it has a wide inset on both sides. Greedy wrapping fills each
     line instead.

     Desktop keeps balance: there the headline takes one or two
     lines, which is exactly the case balance improves. */
  .gs-ap-hero .gs-wgs-section__title {
    text-wrap: normal;
  }

  /* Slightly narrower gutter than the section default's 1.5rem, to
     give the headline back another 16px of measure. Applied to the
     section, not the title, so the lede and proof row stay aligned
     with it. */
  .gs-ap-hero {
    padding-left: 1rem;
    padding-right: 1rem;
  }
}

/* Full-width stacked CTAs on phones. The shared row already stacks
   at this width but stays fit-content, so both buttons sat at the
   width of the longer label with dead space either side; on a phone
   a primary action should span the column.

   Scoped to this hero on purpose - .gs-uc-hero__actions is shared
   with three live pages, and widening their CTAs is a separate
   decision from this page's. */
@media (max-width: 480px) {
  .gs-ap-hero .gs-uc-hero__actions {
    width: 100%;
  }
}

/* Eyebrow, as a pill. Takes the translucent-white treatment the
   three trust chips used before they became the icon proof row, so
   the product name gets a container of its own instead of being the
   quietest text in the hero.

   Safe here in a way it was not down by the CTAs: this sits above
   the h1 with nothing button-shaped near it, and there is only ONE
   of them - a single pill reads as a label, where a row of three
   read as a set of controls.

   Type stays medium weight and muted-tier white rather than
   full-strength, so the h1 underneath is still the loudest thing in
   the viewport. */
.gs-ap-hero__eyebrow {
  display: inline-block;
  /* 13px. Matches gs-customer-band__kicker on the customers
     overview page - the de-facto small-type size across the theme
     (25 uses), so this eyebrow is consistent with it rather than
     with the rarer 0.8rem. */
  font-size: 0.8125rem;
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.9);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  /* Right padding carries an extra 0.08em to swallow the trailing
     letter-space the last character emits, which would otherwise
     make the text sit visibly left of centre inside the pill. */
  padding: 0.35rem calc(0.85rem + 0.08em) 0.35rem 0.85rem;
  margin: 0 0 1.25rem;
}

/* Smaller on phones: at 13px with 0.08em tracking the pill runs
   most of the way across a 390px screen and competes with the h1
   instead of introducing it. Tracking is held so it still reads as
   an eyebrow.

   This block has to sit AFTER the rule above, not with the other
   mobile overrides higher up the file - both selectors are a single
   class, so on equal specificity source order decides, and placed
   earlier it silently lost. */
@media (max-width: 767px) {
  .gs-ap-hero__eyebrow {
    font-size: 0.6875rem;
    padding: 0.3rem calc(0.7rem + 0.08em) 0.3rem 0.7rem;
    margin-bottom: 1rem;
  }
}

/* Proof row. Mirrors the homepage hero's stat row
   (.gs-hero__stats in hero-homepage.css): divider rule above,
   evenly-spaced columns, small muted label. Three columns here
   rather than the homepage's four, and an icon where the homepage
   puts a numeral - these claims are facts, not metrics.

   Deliberately NOT reusing .gs-hero__stats: that component is
   built around a numeral in the value slot, with a mobile rule
   that shrinks the type to force four across. Sharing it would
   mean bending it to a case it was not designed for. Same visual
   language, separate implementation.

   Replaced the pill chips that lived under the CTAs until
   2026-08-26. As filled, bordered, fully-rounded boxes 32px below
   two identically-shaped buttons they read as a second row of
   controls; the divider-and-row form has no such collision. */
.gs-ap-proof {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Narrower than the homepage's 800px: three columns spread over
     that width left the icons floating far apart, and the row has
     to read as one group rather than three separate marks. The
     divider is sized to the same width, so it tracks automatically. */
  gap: 0.75rem;
  max-width: 560px;
  margin: 3rem auto 0;
  /* Sits centred in the band between the divider above and the logo
     strip below. Slightly less padding here than the strip's
     padding-top below it (1.75 vs 2.25rem) rather than an equal
     split, because the folder and shield glyphs are inset a few px
     from the top of their 40px SVG box - matching the numbers
     exactly would leave the row looking low. */
  padding: 1.75rem 0 0;
  /* Matches the homepage hero divider, which is intentionally
     brighter than --color-border-dark: it is a feature line, not a
     structural separator. */
  border-top: 1px solid rgba(255, 255, 255, 0.2);
}

.gs-ap-proof__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
}

/* Occupies the homepage stat-value slot, so it carries that slot's
   weight: full-strength white against the muted label below. */
.gs-ap-proof__icon {
  display: flex;
  color: var(--color-fg-on-dark);
}

.gs-ap-proof__label {
  display: block;
  font-size: var(--text-sm);
  color: var(--color-fg-muted-on-dark);
  /* Each label carries a hard break in the markup so all three sit
     on two lines with the same break points. Left to the browser,
     only the longest label wrapped, and it orphaned "audited" alone
     in the middle column. */
}

/* The hard breaks are a MOBILE fix only. On desktop each label has
   ~186px of column and fits on one line, so the break is switched
   off there - display: none on a <br> removes the line break it
   would otherwise force. */
@media (min-width: 768px) {
  .gs-ap-proof__break {
    display: none;
  }
}

@media (max-width: 767px) {
  .gs-ap-proof {
    margin-top: 2rem;
    /* 2rem here matches .gs-hero__stats' own mobile padding-top. */
    padding-top: 2rem;
  }

  /* Sized in the markup via width/height attributes, so the mobile
     step-down has to come from CSS. The homepage drops its stat
     value 36px -> 24px; icons carry less detail than a numeral at
     the same box, so this steps down more gently. */
  .gs-ap-proof__icon svg {
    width: 34px;
    height: 34px;
  }

  /* Same values .gs-hero__stat-label uses at this breakpoint. */
  .gs-ap-proof__label {
    font-size: 0.75rem;
    line-height: 1.3;
  }
}

/* ─── 2. Placeholder frames ───────────────── */

/* TEMPORARY. Every visual on this page that is not one of the two
   section-3 diagrams is an explicitly labelled placeholder until
   the real assets land (plan Appendix B items 1, 2, 3, 6). Dashed
   border and a monospace label so it
   can never be mistaken for a finished design decision, and so a
   stray one is obvious in review. Delete each frame with its
   markup as the asset arrives; delete this whole block when the
   last one is gone. */
.gs-ap-ph {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.75rem;
  border: 2px dashed var(--color-brand-accent);
  border-radius: 16px;
  background: rgba(246, 89, 84, 0.04);
  padding: 1.5rem;
  text-align: left;
}

.gs-ap-ph__label {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-brand-accent);
  margin: 0;
}

.gs-ap-ph__spec {
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--color-fg-body);
  margin: 0;
}

/* 16:9, matching the demo video that replaces it. */
.gs-ap-ph--video {
  aspect-ratio: 16 / 9;
  max-width: var(--content-frame);
  margin: 0 auto 3.5rem;
  align-items: center;
  text-align: center;
}

.gs-ap-ph--video .gs-ap-ph__spec {
  max-width: 34rem;
}

.gs-ap-ph--shot {
  aspect-ratio: 4 / 3;
}

/* ─── 3. Trust bar ────────────────────────── */

/* The shared marquee's padding-top: 3rem is left alone here - the
   proof row now sits between the CTAs and the logos, so that space
   is occupied rather than empty. (An override was needed briefly
   while the chips lived above the H1 and nothing filled the slot.) */
.gs-ap-hero .gs-customer-logos--pill {
  padding-top: 2.25rem;
}

/* One line, directly above the marquee it belongs to. It carries
   the honesty framing (these are Kubernetes customers, not Agent
   Platform customers) plus the two credibility facts that used to
   sit in a second line below the logos.

   Deliberately at the small muted tier rather than body size: it
   is a caption for the logo strip, and at 16px medium it competed
   with the lede four rows above it for the same attention. Quieter
   also reads as more attached, which is what the plan asks of the
   framing line. */
.gs-ap-trust__line {
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--color-fg-muted-on-dark);
  text-align: center;
  max-width: var(--content-header);
  /* Sits BELOW the marquee (Dominik, 2026-08-25). Reads as a caption
     for the logos rather than an introduction to them, and it stops
     the eye hitting two lines of small text before it reaches the
     logos themselves. */
  margin: 1.5rem auto 0;
  text-wrap: balance;
}

/* ─── 4. Product rows ─────────────────────── */

/* .gs-wgs-row__desc ships with margin: 0 because it is normally
   a single paragraph. Tool Access and Agent Runtime each need
   several, so space them here rather than changing the shared
   rule that ~20 other pages rely on. */
.gs-ap-products .gs-wgs-row__desc + .gs-wgs-row__desc {
  margin-top: 1rem;
}

.gs-ap-row__link {
  font-size: var(--text-base);
  margin: 1.25rem 0 0;
  /* Two pills per panel now, so this stopped being a text line. */
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* .gs-btn is inline-flex but declares no gap, so a mark placed before
   the label sits flush against it without this. */
.gs-ap-row__link .gs-btn {
  gap: 0.5rem;
}

/* Lucide renders at 24px by default, which overpowers a --sm pill. */
.gs-ap-row__link .gs-btn svg {
  width: 16px;
  height: 16px;
}

/* The GitHub mark is a white PNG on a transparent ground, and these are
   outlined pills on white. brightness(0) maps every channel to zero
   while leaving alpha alone, so it comes out black with its anti-aliased
   edges intact - invert(1) shifts those edge pixels and leaves a halo. */
/* The width/height ATTRIBUTES on this img are 32, not 16: HubSpot builds
   its resize URL from them, so 16 got us a true 16x16 file with nothing
   spare on a 2x display. Asking for 32 and showing it at 16 here is
   crisp, and works with the rewrite rather than fighting it. */
.gs-ap-row__link-mark {
  width: 16px;
  height: 16px;
  filter: brightness(0);
}

/* The link inside is a .gs-btn--secondary pill, so it brings its own
   type, padding, border and hover. Nothing to style here.

   It used to be blue text (var(--color-accent)) - the only place on
   the site that coloured a link, since base.css sets
   a { color: inherit }. The outlined pill replaces it: an outbound
   link out of a product panel is a call to action, and gets the
   shape of one.

   --secondary takes its colour from currentColor, so on this light
   section it comes out as a dark outline with dark text, and would
   invert automatically if this section ever went dark. */

/* ─── 4b. Section 3 diagrams ──────────────────

   Both product diagrams are hand-authored inline SVG rather than
   an exported image: they stay crisp at any width, the text in
   them is real text (so AI crawlers and screen readers read it,
   which an exported PNG would hide), and they pick up the design
   tokens below instead of baking colors into an asset.

   Sized so the viewBox width matches the rendered width in a
   .gs-wgs-row media column (~520px), which keeps the 14px labels
   at their true 14px rather than scaling them down into
   illegibility.

   Color logic follows the two-accent rule: neutral for the
   customer's own systems, blue for the platform component doing
   the work (the interactive/structural accent), coral for the
   payoff moment (brand-editorial, static, never a click target).
   ------------------------------------------------------------ */

/* ─── 4a. Product switch ──────────────────────

   Two-tab segmented control for the Tool Access / Agent Runtime
   panels. Behaviour is the shared tabs.js; only the skin is here,
   because the shared gs-wgs-tabs__* skin is a dark-background
   scroll rail meant for many tabs. */

/* No card around the switch and panels. A white bordered box was
   tried (matching the homepage calculator's config card) at both
   full frame width and 960px, and read as an awkward frame either
   way: the diagram is much taller than the copy beside it, so the
   card's lower-right corner was always visibly empty. The toggle
   keeps the calculator's styling below - that part worked. */

.gs-ap-switch {
  display: grid;
  grid-auto-flow: column;
  /* Equal columns so the two tabs match width regardless of label
     length - same reasoning as the hero's side-by-side CTAs. */
  grid-auto-columns: 1fr;
  width: fit-content;
  margin: 0 auto 4rem;
  padding: 0.5rem;                  /* 8px, as .roi-toggle-group */
  gap: 0.25rem;
  /* White track with a hairline border, rather than the
     calculator's translucent grey fill. The calculator's toggle sits
     inside a white card, where a grey track is what separates it
     from its surroundings; this one sits directly on the muted
     section background, so it needs to read as a raised control
     against that instead. */
  background: var(--color-bg);
  /* One step darker than --color-border (#e3e6ec), which was too
     faint to hold the control's edge against the muted section.
     #d7dfe6 is not arbitrary: it is the same hairline the diagrams
     in this section use (--gs-ap-dg-line), so the control and the
     drawing below it share one line colour. Not referenced as that
     var because it is scoped to .gs-ap-dg. */
  border: 1px solid #d7dfe6;
  border-radius: 999px;
}

.gs-ap-switch__tab {
  appearance: none;
  border: 0;
  background: none;
  cursor: pointer;
  /* Icon + label on one line. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: inherit;
  font-size: 0.9rem;                /* as .roi-toggle-label */
  font-weight: var(--weight-medium);
  color: var(--color-fg);
  padding: 0.625rem 1.75rem;        /* 10px 28px, as .roi-toggle-label */
  border-radius: 999px;
  white-space: nowrap;
  transition: all 0.2s ease;
}

/* Icons carry the accent on the inactive tab, so the unselected
   option still reads as live rather than disabled. The SVGs use
   stroke="currentColor", so setting `color` here is enough. */
.gs-ap-switch__tab svg {
  color: var(--color-accent);
}

/* On the active pill the icon goes white with the label - the blue
   fill is already carrying the accent, and a blue icon on it would
   barely register. */
.gs-ap-switch__tab[aria-selected="true"] svg {
  color: #fff;
}

/* Preview-the-pick affordance, same light blue the calculator uses
   on hover - and the same fill as .gs-ap-dg__box--key inside the
   diagrams. */
.gs-ap-switch__tab:hover {
  background: #e8f4ff;
}

/* Active pill: solid blue, white text, soft layered lift. Keyed off
   aria-selected, which tabs.js already maintains, so there is no
   second source of truth. The calculator gets this from a checked
   radio; same visual result, different mechanism. */
.gs-ap-switch__tab[aria-selected="true"] {
  background: var(--color-accent);
  color: #fff;
  box-shadow:
    0 2px 4px rgba(0, 38, 69, 0.08),
    0 6px 14px rgba(0, 38, 69, 0.12);
}

.gs-ap-switch__tab:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}


/* Tighten the space around the toggle below desktop. The gap above
   comes from the shared intro's 2.5rem bottom margin, the gap below
   from the switch's own 4rem - both sized for a desktop viewport,
   where they read as breathing room. Stacked on a phone or tablet
   they leave the control floating in the middle of the screen with
   nothing near it.

   Scoped to this section so the shared intro margin is untouched
   everywhere else. */
@media (max-width: 900px) {
  .gs-ap-products .gs-wgs-section__intro {
    margin-bottom: 1.5rem;
  }

  .gs-ap-switch {
    margin-bottom: 2rem;
  }

  /* row.css centres .gs-wgs-row__content once the row stacks at
     900px. That works for a one- or two-line caption under an image,
     but these panels carry three-line paragraphs, and centred body
     copy leaves both edges ragged with no left edge for the eye to
     return to.

     Scoped to this section: the shared rule still centres stacked
     rows everywhere else. The same argument probably applies to some
     of those, but that is a separate call. */
  .gs-ap-switch-shell .gs-wgs-row__content {
    text-align: left;
  }
}

/* White card around the diagram column only. A card around the whole
   section was tried first and rejected - its lower-right corner was
   always empty, because the copy is shorter than the drawing. This
   one wraps just the drawing, so it has no empty corner to expose,
   and it earns its place: the diagram's internal regions are pale
   blue, and on the section's muted blue-grey background those two
   tints barely separated. On white they read as regions.

   Desktop width is whatever the two-column row gives it; see the
   tablet cap below, which is scoped so it cannot touch that. */
.gs-ap-switch-shell .gs-wgs-row__media {
  background: var(--color-bg);
  border: 1.5px solid var(--color-border);
  border-radius: 12px;
  padding: 1.75rem;
}

/* Tablet only. At 900px the shared row collapses to one column, so
   this card would stretch to the full section width - the same
   diagram in a frame far wider than the one it gets on desktop.
   Capped at the width the two-column row gives it at the widest
   desktop ((1120 frame - 56 gap) / 2) and centred.

   Deliberately inside a max-width query rather than on the rule
   itself: above 900px the row still controls the width, so desktop
   cannot be affected by this number whatever it is. */
@media (max-width: 900px) {
  .gs-ap-switch-shell .gs-wgs-row__media {
    max-width: 532px;
    margin-inline: auto;
  }

  /* Hold the drawing at its desktop size rather than letting it size
     itself off the stacked container - capping the card alone left it
     rendering noticeably smaller here than on desktop. width pins it;
     max-width: 100% still lets it shrink on a phone, where the column
     is genuinely narrower than 460. */
  .gs-ap-switch-shell .gs-ap-dg {
    width: 460px;
    max-width: 100%;
  }
}

/* .gs-wgs-row sets its own display, which beats the [hidden]
   attribute's UA display: none - so the inactive panel would stay
   visible. See CLAUDE.md: anything toggled via [hidden] needs its
   own rule. */
.gs-ap-switch-shell .gs-wgs-row[hidden] {
  display: none;
}

.gs-ap-dg {
  /* Diagram hairline. tokens.css has no border token yet; this is
     the same value the card components use for their default
     border, so the diagrams sit in the same visual family. */
  --gs-ap-dg-line: #d7dfe6;

  display: block;
  width: 100%;
  height: auto;
  /* 460px - midway between the 520px viewBox width (which rendered
     1:1 and stretched every box, since the nodes were drawn against a
     smaller size) and the 400px this was first pinned to.

     400px came from the width the two-column row gives the diagram at
     the 900px collapse point. That fixed the stretching but read
     small on a wide desktop, so this splits the difference: still
     tighter than 1:1, but the labels land at ~12.4px on screen
     (460/520) rather than ~10.8px.

     An SVG scales as one piece, so width buys height with it - the
     diagram now runs a little past the copy column beside it. The row
     is centre-aligned, so that overhang splits top and bottom instead
     of dangling underneath. Mobile is unaffected: the phone column is
     narrower than this, so width: 100% still binds there. */
  max-width: 460px;
  margin: 0 auto;
}

.gs-ap-dg__box {
  fill: var(--color-bg);
  stroke: var(--gs-ap-dg-line);
  stroke-width: 1.5;
}

/* The platform component doing the governing. Blue, because it is
   the structural element of the picture. */
.gs-ap-dg__box--key {
  fill: #e8f4ff;                    /* same light blue as .gs-wgs-feature__icon */
  stroke: var(--color-accent);
  stroke-width: 2;
}

/* Grouping container (the workflow engine, the block/channel
   shelves). Reads as a region, not as another node. */
.gs-ap-dg__panel {
  fill: var(--color-bg-muted-deep);
  stroke: none;
}

/* The GitOps property strip: coral edge, because it is a brand
   promise about how everything ships, not a node in the flow. */
.gs-ap-dg__strip {
  fill: #ffe9e7;                    /* same light coral as .gs-wgs-pillar__icon */
  stroke: none;
}

.gs-ap-dg__line {
  fill: none;
  stroke: var(--color-fg-muted);
  stroke-width: 1.5;
}

.gs-ap-dg__head {
  fill: var(--color-fg-muted);
  stroke: none;
}

/* Lucide "coins", inline inside the SVG rather than via the
   data-lucide runtime - that swaps elements in the HTML document and
   cannot reach inside an svg's shadow of its own. Scaled 16/24, so
   the stroke is set above 2 to survive being scaled down with it. */
.gs-ap-dg__coin {
  fill: none;
  stroke: var(--color-brand-accent);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Diagram labels sit at --text-sm. This is one of the genuinely
   exceptional cases the typography foundation allows for going
   under 16px: they are captions inside a graphic, not body copy,
   and 16px would force most boxes to three lines. */
.gs-ap-dg__t {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  fill: var(--color-fg-body);
}

.gs-ap-dg__t--strong {
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
  fill: var(--color-fg);
}

/* Separator between the gateway's three properties, now that they
   sit on one line instead of stacked. Blue, not coral: this is structural
   punctuation inside a diagram, and coral is reserved for
   brand-editorial emphasis (PATTERNS.md §2) - it is already doing
   that job on "cheaper every run" lower in the same drawing. */
.gs-ap-dg__sep {
  fill: var(--color-accent);
  font-weight: var(--weight-bold);
}

.gs-ap-dg__t--eyebrow {
  font-size: 0.75rem;               /* 12px: the region label, quieter than the nodes it groups */
  font-weight: var(--weight-medium);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  fill: var(--color-fg-muted);
}

.gs-ap-dg__t--chip {
  font-weight: var(--weight-bold);
  fill: var(--color-brand-accent);
}

/* ─── 4c. Built on open source ────────────────

   One strip under both diagrams. The plan allows component names
   exactly once on the page and never as the primary vocabulary,
   so this is that once: the diagrams above stay in product
   language and the OSS proof lives here, where the platform lead
   looking for it will find it.
   ------------------------------------------------------------ */

/* Sits inside a tab panel's copy column now, not full-width under
   both diagrams - so no --content-frame cap, no auto centring, and
   left-aligned to the copy above it rather than centred. */
.gs-ap-oss {
  margin: 2rem 0 0;
  padding-top: 1.5rem;
  border-top: 1.5px solid #d7dfe6;
  text-align: left;
}

.gs-ap-oss__label {
  /* 13px, matching the hero eyebrow and gs-customer-band__kicker -
     the theme's de-facto small-type size. Was --text-sm (14px). */
  font-size: 0.8125rem;
  font-weight: var(--weight-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-fg-muted);
  margin: 0 0 1rem;
}

.gs-ap-oss__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Smaller than the full-width version these were: in a copy column
   at the old size the three pills filled the width and read as a
   second row of controls under the CTA. */
.gs-ap-oss__item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.75rem;
  background: var(--color-bg);
  border: 1px solid #e3e6ec;
  border-radius: 999px;
}

/* Logo slot. Was a dashed empty box while the marks were pending;
   all three landed 2026-08-26, so the placeholder border is gone. */
.gs-ap-oss__mark {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gs-ap-oss__mark img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

.gs-ap-oss__name {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--color-fg);
}

/* ─── 5. Screenshot strip ─────────────────── */

/* ─── Demo video ──────────────────────────────

   Sizes are container-relative, not fixed px: the frame is 1120 on
   desktop and full-bleed on a phone, and a play button that looks right
   at 1120 is a 34px dot at 390. `container-type: inline-size` lets
   everything inside size against the FRAME rather than the viewport. */
/* Two columns: headline + description left, video right. The video keeps
   `container-type: inline-size`, so the play button, its rings and the
   caption all scale down with the narrower column on their own - the only
   thing this grid has to get right is the column ratio. */
.gs-ap-demo__split {
  display: grid;
  grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
  gap: 4rem;
  align-items: center;
  max-width: var(--content-frame);
  margin: 0 auto;
}

/* The section's heading lives in this column, so it keeps the full h2
   size - there is no centred intro above it any more. Left-aligned
   because .gs-wgs-section__intro, which is what centres a section title,
   is deliberately not in the tree here. */
.gs-ap-demo__text .gs-wgs-section__title {
  margin-bottom: 1.25rem;
}

/* Deliberately two separate classes with no class in common: if the short
   one also carried .gs-ap-demo__copy, the two display rules below would be
   equal specificity and source order alone would decide which won. */
.gs-ap-demo__copy,
.gs-ap-demo__copy-short {
  font-size: var(--text-base);
  line-height: 1.65;
  color: var(--color-fg-muted);
  margin: 0;
  max-width: 34rem;
}

.gs-ap-demo__copy-short {
  display: none;
}

@media (max-width: 900px) {
  .gs-ap-demo__split {
    grid-template-columns: 1fr;
    gap: 2rem;
  }

  /* Centred once stacked, to match every other stacked intro on the page. */
  .gs-ap-demo__text {
    text-align: center;
  }

  /* Swap the long description for the single-sentence version. */
  .gs-ap-demo__copy {
    display: none;
  }

  .gs-ap-demo__copy-short {
    display: block;
    margin: 0 auto;
  }
}

.gs-ap-video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 16px;
  overflow: hidden;
  container-type: inline-size;
  box-shadow: 0 18px 40px rgba(0, 38, 69, 0.16);
}

.gs-ap-video__poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Playing state, set by video-inline.js once the poster has been swapped
   for a real <video>. The recording is exactly 16/9 like the frame, so
   `contain` fills it with no letterbox bars. */
.gs-ap-video__player {
  position: absolute;
  inset: 0;
  z-index: 4;
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
  background: #000;
}

.gs-ap-video--playing::after {
  content: none;
}

.gs-ap-video--playing .gs-ap-video__poster,
.gs-ap-video--playing .gs-ap-video__btn,
.gs-ap-video--playing .gs-ap-video__label,
.gs-ap-video--playing .gs-ap-video__meta {
  display: none;
}

/* Scrim. Without it a white play button vanishes on a light poster, and
   the poster can be swapped later without the button breaking.

   Static. A hover fade that revealed the poster was tried and dropped
   2026-08-31 - the scrim is what guarantees the play button and the
   caption stay legible, so it should not come and go. */
.gs-ap-video::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 27, 49, 0.22), rgba(0, 27, 49, 0.5));
  pointer-events: none;
}

.gs-ap-video__btn {
  position: absolute;
  inset: 0;
  margin: auto;
  z-index: 3;
  width: clamp(64px, 8.6cqw, 96px);   /* 96px at the 1120 frame */
  aspect-ratio: 1;
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
  /* One place to flip on hover - the mark and the rings both read these. */
  --gs-ap-video-mark: var(--color-fg);
  --gs-ap-video-pulse: rgba(255, 255, 255, 0.55);
}

.gs-ap-video__btn:hover,
.gs-ap-video__btn:focus-visible {
  --gs-ap-video-mark: var(--color-brand-accent);
  --gs-ap-video-pulse: rgba(246, 89, 84, 0.8);
}

.gs-ap-video__btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 6px;
  border-radius: 50%;
}

.gs-ap-video__disc {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: 50%;
  background: #fff;
  display: grid;
  place-items: center;
  box-shadow: 0 6px 20px rgba(0, 27, 49, 0.4);
  /* .gs-btn's own hover values, so this feels like every other button. */
  transition: transform 0.1s ease-out, box-shadow 0.2s ease;
}

/* Scaled on the DISC, not the button: the hit area must not move under
   the cursor, or the pointer slips off at the edges. */
.gs-ap-video__btn:hover .gs-ap-video__disc,
.gs-ap-video__btn:focus-visible .gs-ap-video__disc {
  transform: scale(1.06);
}

.gs-ap-video__btn:hover .gs-ap-video__disc {
  box-shadow: 0 10px 30px rgba(0, 27, 49, 0.55);
}

.gs-ap-video__disc svg {
  width: 46%;                        /* of the disc - at 34% it read as a dot */
  height: 46%;
  display: block;
  fill: var(--gs-ap-video-mark);
  transition: fill 0.2s ease;
}

/* Border width is container-relative too, or the rings thicken relative
   to the disc as the frame shrinks. */
.gs-ap-video__ring {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: 50%;
  border: clamp(1.5px, 0.18cqw, 2px) solid var(--gs-ap-video-pulse);
  transition: border-color 0.2s ease;
  animation: gs-ap-video-ripple 2.8s ease-out infinite;
}

/* Half a cycle behind, so a new ripple leaves as the last one travels. */
.gs-ap-video__ring:nth-of-type(2) {
  animation-delay: 1.4s;
}

@keyframes gs-ap-video-ripple {
  0%   { transform: scale(1);   opacity: 0.65; }
  100% { transform: scale(2.1); opacity: 0; }
}

/* Product name, top-left. Container-relative like everything else in the
   frame, and a step up from the caption below it so the two read as a
   title and its subtitle rather than two equal notes. */
.gs-ap-video__label {
  position: absolute;
  left: 4%;
  top: 4%;
  z-index: 3;
  color: var(--color-fg-on-dark);
  font-weight: var(--weight-bold);
  /* Player-title scale, not caption scale: a video title overlay reads as
     a title, so ~21px in this 623px column and ~30px if the frame ever
     goes full width. The 18px floor is what mobile lands on. */
  font-size: clamp(18px, 3.3cqw, 30px);
  letter-spacing: -0.01em;
  /* Wrap rather than run under the play button if the frame gets narrower
     than any width checked here. */
  max-width: 78%;
  text-shadow: 0 1px 8px rgba(0, 27, 49, 0.7);
}

/* At 320px the frame is only 272px wide and the 18px floor wraps to two
   lines, which runs the label into the play button. */
@media (max-width: 360px) {
  .gs-ap-video__label {
    font-size: 15px;
  }
}

.gs-ap-video__meta {
  position: absolute;
  left: 4%;
  bottom: 4%;
  z-index: 3;
  color: var(--color-fg-on-dark);
  font-weight: var(--weight-medium);
  font-size: clamp(11px, 1.2cqw, 14px);
  text-shadow: 0 1px 8px rgba(0, 27, 49, 0.7);
}

@media (prefers-reduced-motion: reduce) {
  .gs-ap-video__ring { animation: none; }
  .gs-ap-video__disc { transition: none; }
}

.gs-ap-shots {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 3.5rem;
  /* The full frame. This was inset to 840 while the row sat directly under
     the video and read as its continuation; the step headlines separate
     the two now, so the narrowing bought nothing and cost screenshot
     width. `margin` is the shorthand on purpose - a separate margin-top
     above it would be overwritten. */
  max-width: var(--content-frame);
  /* Hairline separating the video block above from the screenshot row.
     Carried on this element's own border-top rather than an <hr>, so it
     always spans exactly the frame the row is sized to. 3.5rem either
     side of the line, matching .gs-uc-cs-row's row divider. */
  margin: 3.5rem auto 0;
  padding-top: 3.5rem;
  border-top: 1px solid var(--color-border);
}

.gs-ap-shot {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* `.gs-ap-shot` above sets `display: flex`, and an author declaration beats
   the UA sheet's `[hidden] { display: none }` - so without this rule
   tabs.js sets `hidden` and nothing happens. Ordering is load-bearing:
   this must come after the base rule, and the min-width block after this,
   since all three carry the same weight and source order decides. */
.gs-ap-shot[hidden] {
  display: none;
}

/* Above 900px this is not a tab set at all: no tablist, every panel shown.
   One set of markup, two layouts.

   901, not 768: the tab styles live in this section's existing
   `max-width: 900px` query - the breakpoint where three 16:9 columns stop
   fitting and the row used to stack. A 768 boundary here left 768-900px
   applying BOTH queries, so the tablist showed while all three panels
   were still visible. */
@media (min-width: 901px) {
  .gs-ap-shots__tablist {
    display: none;
  }

  .gs-ap-shot[hidden] {
    display: flex;
  }
}

/* Light-mode captures on a white section, so the hairline is doing real
   work - without it they have no edge and dissolve into the background. */
.gs-ap-shot__img {
  display: block;
  width: 100%;
  height: auto;
  /* NO aspect-ratio and NO object-fit: the captures are final and must not
     be re-framed. Forcing 16/9 with `cover` silently cropped them a second
     time. `height: auto` lets each render at its true proportions - all
     three share a ratio, so the row still aligns. */
  border: 1px solid var(--color-border);
  border-radius: 4px;
  /* The theme's resting card shadow at the SAME offsets and blur, with
     only the alpha reduced (.08/.12 -> .05/.07). Shrinking the geometry
     as well made it smaller rather than softer, which is not the same
     thing. Two layers kept: tight for the edge, wide for the lift. */
  box-shadow:
    0 2px 4px rgba(0, 38, 69, 0.05),
    0 6px 14px rgba(0, 38, 69, 0.07);
}

/* The trigger wraps the image. `display: block` and zero padding so it
   occupies exactly the image's box - a default button would add its own
   padding, border and background around it. */
.gs-ap-shot__trigger {
  display: block;
  position: relative;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  margin-top: 0.9rem;
  border-radius: 4px;
  overflow: hidden;
  line-height: 0;
}

.gs-ap-shot__trigger:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
}

/* Replaces the hover lift. A lift only hints that something happens; the
   overlay says what. Hidden from assistive tech - the button's own
   aria-label already carries it. */
.gs-ap-shot__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  background: rgba(0, 27, 49, 0.72);
  color: var(--color-fg-on-dark);
  opacity: 0;
  transition: opacity 0.25s ease;
}

.gs-ap-shot__trigger:hover .gs-ap-shot__overlay,
.gs-ap-shot__trigger:focus-visible .gs-ap-shot__overlay {
  opacity: 1;
}

.gs-ap-shot__overlay svg {
  width: 26px;
  height: 26px;
  stroke-width: 1.75;
}

.gs-ap-shot__overlay-t {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: 1;
}

/* ─── Lightbox ────────────────────────────────

   Native <dialog>. ::backdrop is the browser's own layer, so no extra
   element and no scroll-lock hack is needed. */
.gs-ap-lightbox {
  width: min(92vw, 1400px);
  max-width: none;
  max-height: 92vh;
  /* Centred explicitly. The UA sheet's `margin: auto` only centres when
     the box has a definite size in both axes; this one is content-sized
     vertically, so it pinned to the top. */
  position: fixed;
  inset: 0;
  margin: auto;
  height: fit-content;
  padding: 0;
  border: 0;
  background: none;
  overflow: visible;
}

.gs-ap-lightbox::backdrop {
  background: rgba(0, 27, 49, 0.88);
}

/* Image, then the control row under it.

   Scoped to [open] and NOT to the bare element: the UA sheet hides a
   closed dialog with `display: none`, so an unscoped `display: flex`
   overrides that and paints the arrows and close button onto the page. */
.gs-ap-lightbox[open] {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* prev / caption / next as one centred cluster, so the arrows read as
   controls for the caption rather than furniture at the dialog's edges. */
.gs-ap-lightbox__bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
}

/* The image sizes ITSELF within the limits, rather than being stretched
   to 100% width and letterboxed back down. `width: 100%` + `max-height`
   made the box wider than the image's ratio on any viewport short enough
   for the height cap to bind, and object-fit: contain then painted white
   bars either side - 94px each at 820px tall, 177px at 700px.
   With auto sizing the box IS the image, so bars are impossible and no
   background is needed behind it. */
.gs-ap-lightbox__img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 78vh;
  margin: 0 auto;
  border-radius: 6px;
}

.gs-ap-lightbox__cap {
  /* One line. A 46ch cap was keeping the cluster tight but wrapped the
     longest caption in two; at 16px the longest of the three is ~470px,
     so the whole cluster still comes to ~600px in a 1325px dialog. */
  white-space: nowrap;
  margin: 0;
  color: var(--color-fg-on-dark);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  text-align: center;
  text-wrap: pretty;
}

.gs-ap-lightbox__close,
.gs-ap-lightbox__nav {
  display: grid;
  place-items: center;
  width: 44px;                      /* 44px minimum touch target */
  height: 44px;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: var(--color-fg-on-dark);
  cursor: pointer;
  transition: background 0.2s ease;
}

.gs-ap-lightbox__close:hover,
.gs-ap-lightbox__nav:hover {
  background: rgba(255, 255, 255, 0.24);
}

.gs-ap-lightbox__close:focus-visible,
.gs-ap-lightbox__nav:focus-visible {
  outline: 2px solid var(--color-fg-on-dark);
  outline-offset: 3px;
}

/* `fixed`, so this is the corner of the VIEWPORT rather than of the
   dialog - the dialog is centred and content-sized, so its corner moves
   with the image. */
.gs-ap-lightbox__close {
  position: fixed;
  top: 24px;
  right: 24px;
}

.gs-ap-lightbox__close svg,
.gs-ap-lightbox__nav svg { width: 22px; height: 22px; }

/* Below ~1500px the side arrows would sit off-screen, so they move inside
   the frame and the close button tucks into the corner. */
/* No room for one line on a phone - wrapping beats overflowing. */
@media (max-width: 767px) {
  .gs-ap-lightbox__cap {
    white-space: normal;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gs-ap-shot__overlay { transition: none; }
}

/* Step label. The number is a blue chip, not a coral dot: coral plus a
   connecting rail is section 7's four-week timeline, and repeating that
   device here would say these three screens are a timeline in time. They
   are a sequence of actions that take seconds. */
.gs-ap-shot__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.4rem;
  font-size: var(--text-h4);         /* 22px - enough presence to act as the
                                        break between the video and the row */
  font-weight: var(--weight-bold);
  color: var(--color-fg);
}

.gs-ap-shot__num {
  display: grid;
  place-items: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #e8f4ff;               /* same light blue as .gs-wgs-feature__icon */
  color: var(--color-accent);
  font-size: 0.8125rem;
  font-weight: var(--weight-bold);
}

/* Left-aligned now that it sits under a left-aligned headline. `pretty`
   rather than `balance`: ragged-right text wants orphan control, not
   even line lengths - balance is for centred type. All three captions
   are written to wrap to exactly two lines at the ~340px column - edit any
   of them and the three images fall out of alignment. */
.gs-ap-shot__caption {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-fg-body);
  margin: 0;
  text-wrap: pretty;
}

/* Same white as the panel's copy, not the accent blue it started as.
   Blue made it read as a fifth chip under the four blue ones above it;
   matching the text makes it part of the panel instead of an object
   sitting in it. flex-shrink so it never squeezes as the column
   narrows. */
.gs-ap-governance__closing-icon {
  display: flex;
  flex-shrink: 0;
  /* Sits the icon on the first line's cap height rather than its
     ascender. */
  margin-top: 0.125rem;
  color: rgba(255, 255, 255, 0.85);
}

.gs-ap-governance__closing-icon svg {
  width: 18px;
  height: 18px;
  stroke-width: 2;
}

/* ─── 6. Value cards + commitments band ───── */

/* Six cards sit on the shared 3-col feature grid unchanged: the
   base component is already repeat(3, 1fr), and the bigger title
   comes from card.css's own --lg-title modifier in the markup. No
   override needed here. */

/* ─── 7. Containment layers ───────────────── */

/* Damp the dark-waves background image behind this section only. The
   drawing here is a lot of thin dashed strokes and small type, and the
   waves were reading through them - two sets of curved shapes competing
   in the same space.

   Done as a navy overlay rather than by touching the shared
   .gs-wgs-section--dark rule: that image is on every dark section on
   the site, and nothing else needs damping. A background-image cannot
   take opacity directly, hence the pseudo-element - and the inner
   wrapper needs its own positioning, or the absolutely-positioned
   overlay would paint on top of the content instead of under it. */
.gs-ap-governance {
  position: relative;
}

.gs-ap-governance::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--color-bg-dark);
  /* Overlay opacity, so LOWER = waves more visible. 0.45 lets a little
     more of the illustration through than the 0.55 this started at. */
  opacity: 0.45;
  pointer-events: none;
}

.gs-ap-governance .gs-wgs-section__inner {
  position: relative;
}

/* More air below the intro than the shared 2.5rem. What follows here is
   a drawing rather than more prose, and it needs a clear break from the
   lede - otherwise the caption and the top pair of blocked boxes read as
   a continuation of the paragraph above them. Scoped to this section so
   the shared intro spacing is untouched everywhere else. */
.gs-ap-governance .gs-wgs-section__intro {
  margin-bottom: 4.5rem;
}


/* Blast radius drawn as AREA. This replaced a nested-rings drawing on
   2026-08-26: the rings said Tools contains Identity contains Network
   contains Pod, and those four are independent controls, not shells -
   the picture asserted a structure the content does not have. See the
   markup comment for the full reasoning.

   The agent is the one blue-filled element, the same "this is the thing
   under discussion" role blue plays in the section 3 diagrams. Coral
   appears only on the denial reasons - that is the single place
   something is being refused, and coral is the brand-editorial accent,
   not a UI state, so it is used here as emphasis rather than as a
   status colour. */
.gs-ap-blast {
  display: grid;
  grid-template-columns: 500px 1fr;
  /* Wide gutter: the drawing needs air around it before the copy
     starts, and it separates two things that are read separately
     rather than scanned across. */
  gap: 6rem;
  align-items: center;
  max-width: var(--content-frame);
  margin: 0 auto;
}

.gs-ap-blast__svg {
  display: block;
  width: 100%;
  height: auto;
  /* 500, just under the 520 viewBox width, so the drawing renders
     close to 1:1. An SVG scales as one piece, so the type scales with
     it - the denial reasons are the smallest thing in here and land
     near 9.6px at this width. 460 was tried first and put them at
     8.8px, which was the floor. */
  max-width: 500px;
  /* Centred in its cell. Without this the drawing sits flush left once
     the row stacks and the cell grows wider than 500px. */
  margin-inline: auto;
}

.gs-ap-blast__caption {
  font-family: var(--font-sans);
  font-size: 0.6875rem;              /* 11px: sits above the drawing, not in it */
  font-weight: var(--weight-medium);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  fill: var(--color-accent-on-dark);
}

/* The boundary. Dashed because it is declared rather than physical. */
.gs-ap-blast__bound {
  stroke: var(--color-accent-on-dark);
  stroke-width: 1.5;
  stroke-dasharray: 6 5;
}

/* The four control chips, on the circle's cardinal points. Identical to
   .gs-ap-blast__key in the legend, so a reader can match one to the
   other - that pairing is what stops the legend reading as four
   unsupported claims beside an unrelated picture. */
.gs-ap-blast__chip rect {
  fill: var(--color-bg-dark);
  stroke: rgba(0, 159, 255, 0.5);
  stroke-width: 1;
}

.gs-ap-blast__chip text {
  font-family: var(--font-mono);
  font-size: 0.6875rem;              /* 11px, as the legend chip */
  fill: var(--color-accent-on-dark);
}

/* Reachable. Neutral, because being inside the boundary is the default
   state of everything the agent is allowed to touch. */
.gs-ap-blast__allow {
  fill: rgba(255, 255, 255, 0.08);
  stroke: rgba(255, 255, 255, 0.32);
  stroke-width: 1;
}

.gs-ap-blast__allow-t {
  font-family: var(--font-sans);
  font-size: 0.75rem;                /* 12px: node labels inside a graphic */
  fill: var(--color-fg-on-dark);
}

.gs-ap-blast__link {
  fill: none;
  stroke: var(--color-fg-muted-on-dark);
  stroke-width: 1.2;
}

/* Identical to a permitted connector as far as the wall - the request
   is legitimate right up to the point it is refused, and drawing it
   differently from the start would give the outcome away early. */
.gs-ap-blast__deny {
  fill: none;
  stroke: var(--color-fg-muted-on-dark);
  stroke-width: 1.2;
}

/* Past the wall: dashed and dim, because this stretch never happens. */
.gs-ap-blast__deny--past {
  stroke: rgba(255, 255, 255, 0.22);
  stroke-dasharray: 3 3;
}

.gs-ap-blast__x {
  fill: none;
  stroke: var(--color-brand-accent);
  stroke-width: 2.5;
  stroke-linecap: round;
  opacity: 0;                        /* the dot becomes this; see 7a */
}

/* Out of reach - drawn at full strength, not faded. They were at 0.5
   opacity with a hover lift to 1; dropped 2026-08-26. Two reasons: the
   denial reason is the most interesting text in the drawing and should
   not need a hover to read, and hover does not exist on touch, so half
   the audience only ever saw the faded state.

   What separates these from the reachable boxes is now the dashed
   border and the coral reason, not dimness - which is the more honest
   signal anyway: they are excluded, not inactive. */
.gs-ap-blast__blk rect {
  fill: none;
  /* Translucent white. Coral was tried, to match the denial reason
     inside the box, and rejected - it made the boxes too loud for
     something the reader is meant to notice second. */
  stroke: rgba(255, 255, 255, 0.3);
  stroke-width: 1;
  stroke-dasharray: 3 3;
}

.gs-ap-blast__blk-t {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  /* Brighter than --color-fg-muted-on-dark (#A8B5C2), which read too
     dark once these boxes stopped being faded. Not full white: they are
     the things the agent CANNOT reach, so they should not out-shout the
     reachable boxes, which sit at --color-fg-on-dark. The blue cast of
     the muted token was also part of why it looked dim - plain white at
     80% reads brighter than a blue-grey of similar luminance. */
  fill: rgba(255, 255, 255, 0.8);
}

.gs-ap-blast__blk-why {
  font-family: var(--font-mono);
  font-size: 0.625rem;               /* 10px: a footnote on a footnote */
  fill: var(--color-brand-accent);
}

.gs-ap-blast__agent {
  fill: #e8f4ff;                     /* same light blue as .gs-wgs-feature__icon */
  stroke: var(--color-accent-on-dark);
  stroke-width: 2;
}

.gs-ap-blast__agent-t {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: var(--weight-bold);
  fill: var(--color-bg-dark);
}

.gs-ap-blast__dot {
  fill: var(--color-fg-on-dark);
  opacity: 0;
}

/* ─── 7a. Motion ──────────────────────────────

   Three things move, all decoration: the perimeter turns, the agent
   beats, and each beat releases a dot down every connector. Nothing is
   only visible while moving, so the reduced-motion stop below costs no
   information - and the plan's AEO rule holds, since every label is
   server-rendered SVG text either way. */

@keyframes gs-ap-orbit    { to { transform: rotate(360deg); } }
@keyframes gs-ap-counter  { to { transform: rotate(-360deg); } }

/* The pulse period is 3.75s, not 2s, and that is load-bearing. A chip
   sits over the refusal point for 2.02s; at a 2s pulse the gap between
   two X appearances was only 1.28s, so a chip was ALWAYS parked on the X
   when it appeared. 3.75s opens a 3.03s gap. That exact figure matters:
   crossings happen every 7.5s and the period must divide it, or the
   alignment drifts instead of repeating - 2.5s was the next candidate and
   its gap is still too short.
   Every ABSOLUTE duration below is unchanged - the beat is still ~0.5s,
   the dots still travel for 1.16s, the X still shows for 0.72s. Only the
   rest between beats grew, which is why the percentages look arbitrary:
   they are the old 2s timings rescaled onto 3.75s.
   Speeding the ring to 16s was the alternative, and was rejected - it
   changed the pace and left only 2.3deg of margin against 6.1deg here. */

/* A beat, not a breath. The window is 26% of a 2s cycle, so the pulse
   keeps a ~0.5s shape with ~1.5s of rest - narrowing the window would
   speed the beat up rather than lengthen the gap. */
@keyframes gs-ap-beat {
  0%   { transform: scale(1); }
  5.333%  { transform: scale(1.09); }
  13.867%  { transform: scale(1); }
  100% { transform: scale(1); }
}

/* Deltas match the connector paths exactly:
     M235,289 L194,209  ->  (-41, -80)
     M285,289 L326,209  ->  (+41, -80)
     M260,270 L260,305  ->  (  0, +35)
   Each dot holds at its destination at opacity 0 for the rest of the
   cycle, so the reset back to the pill is never seen. */
@keyframes gs-ap-emit-a {
  0%, 7.467%   { transform: translate(0, 0);        opacity: 0; }
  11.733%       {                                    opacity: 1; }
  32%       {                                    opacity: 1; }
  38.4%, 100% { transform: translate(-41px, -80px); opacity: 0; }
}
@keyframes gs-ap-emit-b {
  0%, 7.467%   { transform: translate(0, 0);        opacity: 0; }
  11.733%       {                                    opacity: 1; }
  32%       {                                    opacity: 1; }
  38.4%, 100% { transform: translate(41px, -80px);  opacity: 0; }
}
.gs-ap-blast__ring {
  animation: gs-ap-orbit 30s linear infinite;
  /* Phase-lock. See the note above the keyframes: this is what puts each
     chip's pass over the refusal point inside the window where the X is
     dark. Verified over 300s - closest approach 18.2deg, 12.1deg needed. */
  animation-delay: -26.485s;
  transform-origin: 260px 250px;
}

/* Each chip counter-rotates about its OWN centre at the same duration,
   which keeps its text upright while it orbits. Without this, "network"
   arrives upside-down at the foot of the circle. */
.gs-ap-blast__chip {
  animation: gs-ap-counter 30s linear infinite;
  /* Must match the ring's delay exactly, or the counter-rotation stops
     cancelling the ring and the labels tip over as they orbit. */
  animation-delay: -26.485s;
}

.gs-ap-blast__chip--n { transform-origin: 260px  70px; }
.gs-ap-blast__chip--e { transform-origin: 440px 250px; }
.gs-ap-blast__chip--s { transform-origin: 260px 430px; }
.gs-ap-blast__chip--w { transform-origin:  80px 250px; }

.gs-ap-blast__beat {
  animation: gs-ap-beat 3.75s ease-in-out infinite;
  transform-origin: 260px 309px;
}

/* Deliberately a soft crossfade, not a clean handover: the dot dims
   over the last third of its run as the X rises through it, so the two
   overlap for ~8% of the cycle. A version where the dot arrived at full
   opacity and then popped out was tried and read as too abrupt - losing
   energy on the way to the wall is the better picture of the refusal.
   The dot's own opacity stops mattering the moment the X is up, which is
   why the X can start before the dot has technically landed. */
@keyframes gs-ap-emit-deny {
  0%, 7.467%   { transform: translate(0, 0);     opacity: 0; }
  11.733%       {                                 opacity: 1; }
  32%       {                                 opacity: 1; }
  38.4%, 100% { transform: translate(0, 101px); opacity: 0; }
}

@keyframes gs-ap-deny-x {
  0%, 34.133%   { opacity: 0; }
  38.4%       { opacity: 1; }
  49.067%       { opacity: 1; }
  53.333%, 100% { opacity: 0; }
}

.gs-ap-blast__dot--a { animation: gs-ap-emit-a 3.75s ease-out infinite; }
.gs-ap-blast__dot--b { animation: gs-ap-emit-b 3.75s ease-out infinite; }
.gs-ap-blast__dot--deny { animation: gs-ap-emit-deny 3.75s ease-out infinite; }
.gs-ap-blast__x         { animation: gs-ap-deny-x   3.75s ease-out infinite; }

/* Same contract customer-logos.css uses for the marquee. */
@media (prefers-reduced-motion: reduce) {
  .gs-ap-blast__ring,
  .gs-ap-blast__chip,
  .gs-ap-blast__beat,
  .gs-ap-blast__dot,
  .gs-ap-blast__x {
    animation: none;
  }

  .gs-ap-blast__x {
    opacity: 1;
  }
}

/* Tight gaps, so the four read as one list. Stretching them to 3.75rem
   was tried to match the drawing's height and looked wrong - the items
   stopped belonging to each other. */
.gs-ap-blast__legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.gs-ap-blast__item {
  border-left: 2px solid rgba(255, 255, 255, 0.25);
  padding-left: 1rem;
}

.gs-ap-blast__name {
  font-size: var(--text-h5);
  color: var(--color-fg-on-dark);
  margin: 0 0 0.25rem;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* The same chip that sits on the boundary in the drawing. Identical
   values on purpose: it is the link between the two halves. */
.gs-ap-blast__key {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: var(--weight-regular);
  color: var(--color-accent-on-dark);
  border: 1px solid rgba(0, 159, 255, 0.5);
  border-radius: 4px;
  padding: 1px 5px;
}

.gs-ap-blast__text {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: rgba(255, 255, 255, 0.85);
  margin: 0;
  text-wrap: pretty;
}

/* Right column: the four layers, then their conclusion. */
.gs-ap-blast__col {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}

/* Glass panel, the same treatment as the customer-logo strip's inner
   (.gs-customer-logos__inner): 6% white fill, 12% white hairline, 20px
   backdrop blur. The blur is what makes it read as glass rather than a
   flat tint - it picks up the dark-wave background behind the section.

   Was a centred full-width paragraph below the whole grid until
   2026-08-26. In here it reads as the conclusion of the four layers
   rather than of the section, and it takes up the height the legend was
   short against the drawing. */
.gs-ap-governance__closing {
  /* One step below the legend's body copy: this is a closing note on
     the four layers, not a fifth item, so it should not compete with
     them at the same size. */
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: rgba(255, 255, 255, 0.85);
  margin: 0;
  padding: 1.125rem 1.25rem;
  /* Icon aligned to the first line, not centred against the block -
     centring was tried and read worse at three lines of copy. */
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  text-wrap: pretty;
}

/* ─── 8. Deployment targets + sovereignty ─── */

/* Two columns: the argument on the left, four target cards on the right.
   Replaced a centred stack (H2, a pill row, three centred paragraphs) on
   2026-08-26 - see the markup comment for what was wrong with it. */
/* NOT .gs-ap-infra - that class is on the <section> itself, so putting
   the grid on it made the section a two-column grid and crammed all the
   content into its first column. */
.gs-ap-infra__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 5rem;
  align-items: center;
  max-width: var(--content-frame);
  margin: 0 auto;
}

/* Stacks at 900px, the same breakpoint row.css uses, so this section
   collapses in step with the rest of the page.

   This block has to sit AFTER the rule above: both selectors are a
   single class, so on equal specificity source order decides. Placed
   before it, the 1fr 1fr won and the section never stacked. */
@media (max-width: 900px) {
  .gs-ap-infra__grid {
    grid-template-columns: 1fr;
    gap: 3rem;
  }
}

.gs-ap-infra__col {
  min-width: 0;
}

/* The H2 lives inside the column now rather than in a centred
   section__intro, so it needs its own left-aligned margin. */
.gs-ap-infra__title {
  text-align: left;
  margin: 0 0 1.5rem;
}

.gs-ap-infra__p {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-fg-body);
  margin: 0 0 1.25rem;
  text-wrap: pretty;
}

/* The no-Kubernetes note. Was bolded body copy floating at the end of a
   centred stack; it is the section's low-barrier invitation and the most
   interesting sentence in it, so it gets a container.

   Tinted with the same #e8f4ff as the card icon tiles opposite - the
   identical value, so the note and the cards read as one family. Smaller
   than the prose because it is an aside, not a third paragraph. */
.gs-ap-infra__k8s {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 2rem 0 0;
  padding: 0.875rem 1.125rem;
  background: #e8f4ff;              /* same light blue as .gs-ap-deploy__icon */
  border-radius: 12px;
  font-size: 0.8125rem;
  line-height: var(--leading-normal);
  color: var(--color-fg);
}

.gs-ap-infra__k8s-mark {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  display: block;
}

/* Four target cards, 2x2. Deliberately the same card language as
   .gs-wgs-feature in section 4 - border, tinted icon tile, title, small
   muted description - so this introduces no new vocabulary. */
.gs-ap-deploy {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem;
}

.gs-ap-deploy__item {
  padding: 1.5rem;
  background: var(--color-bg);
  border: 1.5px solid var(--color-border);
  border-radius: 14px;
  transition:
    border-color 0.3s ease,
    box-shadow 0.3s ease,
    transform 0.3s ease;
}

/* The theme's standard card hover, copied from .gs-wgs-feature rather
   than reinvented: accent border, a 6px lift, and the inset 0.5px ring
   that optically thickens the border without shifting the layout. These
   cards are not links, so the effect is decorative - the lift signals
   "this is a card" and nothing more. */
.gs-ap-deploy__item:hover {
  border-color: var(--color-accent);
  transform: translateY(-6px);
  box-shadow:
    inset 0 0 0 0.5px var(--color-accent),
    0 12px 24px rgba(0, 38, 69, 0.1);
}

@media (prefers-reduced-motion: reduce) {
  .gs-ap-deploy__item:hover {
    transform: none;
  }
}

.gs-ap-deploy__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: #e8f4ff;              /* same light blue as .gs-wgs-feature__icon */
  border-radius: 10px;
  color: var(--color-accent);
  margin-bottom: 1rem;
}

.gs-ap-deploy__icon svg {
  width: 20px;
  height: 20px;
  stroke-width: 2;
}

.gs-ap-deploy__label {
  font-size: var(--text-h5);
  color: var(--color-fg);
  margin: 0 0 0.35rem;
}

.gs-ap-deploy__desc {
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--color-fg-muted);
  margin: 0;
  text-wrap: pretty;
}

/* ─── FAQ: show-more toggle ────────────────── */

/* Questions 5-9 live inside a native <details>; this styles its
   <summary> as a button. faq.js does not touch it (it binds only
   .gs-faq__item), so the open/close is the browser's. */
/* Flex column so the summary can be ordered after the content: the
   toggle belongs at the bottom of the list it reveals, not above it.
   Closed, only the summary renders, so the order has no effect. */
.gs-faq__more {
  border: 0;
  display: flex;
  flex-direction: column;
}

.gs-faq__more-toggle {
  order: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: fit-content;
  margin: 1.5rem auto 0;
  padding: 0.625rem 1.5rem;
  border: 1.5px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-bg);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--color-fg);
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
  /* The default disclosure triangle would sit beside our own icon. */
  list-style: none;
}

/* Safari renders the marker through a pseudo-element that
   list-style: none does not reach. */
.gs-faq__more-toggle::-webkit-details-marker {
  display: none;
}

.gs-faq__more-toggle:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.gs-faq__more-toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.gs-faq__more-icon {
  display: flex;
  color: var(--color-accent);
  transition: transform 0.2s ease;
}

.gs-faq__more-icon svg {
  width: 16px;
  height: 16px;
  stroke-width: 2.5;
}

/* One icon for both states: rotating a plus 45 degrees makes a close
   cross, so there is no second glyph to keep in sync. */
.gs-faq__more[open] .gs-faq__more-icon {
  transform: rotate(45deg);
}

/* Label swap. Two spans rather than JS, since the state is already
   expressed by [open]. */
.gs-faq__more-hide {
  display: none;
}

.gs-faq__more[open] .gs-faq__more-show {
  display: none;
}

.gs-faq__more[open] .gs-faq__more-hide {
  display: inline;
}

/* ─── 9. Two paths ────────────────────────── */

/* HARD constraint: these two are NOT equal. The founding-customer
   block is full-width and visually dominant; the open source
   strip below is compact and clearly subordinate. Never render
   them as equal columns, tabs, or a side-by-side choice. */
.gs-ap-paths__main {
  max-width: var(--content-header);
  margin: 0 auto 3.5rem;
  text-align: center;
}

.gs-ap-paths__title {
  font-size: var(--text-h2);
  margin: 0 0 1.25rem;
  text-wrap: balance;
}

.gs-ap-paths__body {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-fg-body);
  margin: 0 0 1rem;
  text-wrap: pretty;
}

/* Two ways in - its own full-width dark section now, not a navy panel
   inside the light one. The shared .gs-wgs-section--dark supplies the
   background and the dark-waves illustration, so nothing is redeclared
   here.

   Columns are deliberately unequal: 1.4fr / 1fr. Dominik's hard
   constraint forbids the two paths reading as an equal choice, and
   unequal widths plus unequal treatment (glass + gradient button vs
   dashed outline + text links) is what keeps the founding-customer path
   dominant while still sitting them side by side. */
/* Tighter than the shared section default (5rem top and bottom): the
   section holds two compact panels and no heading, so the full padding
   left a lot of navy above and below them. */
.gs-ap-start {
  padding-top: 3.5rem;
  padding-bottom: 3.5rem;
}

.gs-ap-start__grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 1.5rem;
  align-items: stretch;
}

/* Stacks at 900px. After the rule above on purpose - both selectors are
   a single class, so on equal specificity source order decides. */
@media (max-width: 900px) {
  .gs-ap-start__grid {
    grid-template-columns: 1fr;
  }
}

/* The offer, in glass: 6% white fill, 12% white hairline, 20px backdrop
   blur - the customer-logo strip's treatment. The blur is what makes it
   read as glass rather than a flat tint, because it picks up the waves
   behind it. */
.gs-ap-paths__offer {
  display: flex;
  flex-direction: column;
  /* Centred, matching the dashed box beside it - left-aligned text with
     a left-aligned button read as a different kind of block from its
     own sibling. */
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 1.5rem;
  padding: 1.75rem;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

.gs-ap-paths__offer-text {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: #fff;
  margin: 0;
  text-wrap: pretty;
}

/* Wider than the label needs. In a box of its own the default
   hug-the-text width read as small against the panel around it; the
   extra horizontal padding gives it presence without changing the
   button's height or type. */
.gs-ap-paths__offer-cta {
  flex-shrink: 0;
  padding-left: 3rem;
  padding-right: 3rem;
}

/* The subordinate strip. Bordered, not filled; smaller type; no
   button. It exists so a platform lead can find the repo without
   talking to sales, and it should read exactly that quietly. */
.gs-ap-paths__sub {
  /* The narrower column beside the offer. On-dark values, and dashed
     rather than solid so it reads as the provisional, self-serve path
     next to the glass one. */
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin: 0;
  padding: 1.5rem 1.75rem;
  border: 1.5px dashed rgba(255, 255, 255, 0.22);
  border-radius: 16px;
  text-align: center;
}

/* A <p> now, not an h3 - so the font-weight a heading gave it for free
   has to be declared. */
.gs-ap-paths__sub-title {
  font-size: var(--text-h5);
  font-weight: var(--weight-bold);
  color: #fff;
  margin: 0 0 0.5rem;
}

.gs-ap-paths__sub-body {
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--color-fg-muted-on-dark);
  margin: 0 0 0.75rem;
  text-wrap: pretty;
}

.gs-ap-paths__sub-links {
  font-size: var(--text-sm);
  margin: 0;
}

/* Same treatment as .gs-cta__link in the closing section: white with a
   2px-offset underline, coral on hover. Matching it rather than
   inventing a third link style, and it resolves the two-accent problem
   the earlier coral-at-rest version had - coral is brand-editorial and
   never the resting state of something interactive, but as a hover it
   is fine. */
.gs-ap-paths__sub-links a {
  color: #ffffff;
  font-weight: var(--weight-medium);
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color 0.2s ease;
}

.gs-ap-paths__sub-links a:hover {
  color: var(--color-brand-accent);
}

.gs-ap-paths__sep {
  color: var(--color-fg-muted-on-dark);
  margin: 0 0.5rem;
}

/* ─── 10. Read more (angled-split reference cards) ─────

   A list, not a card grid: the section is built to grow (the first
   Agent Platform customer story slots in later) and a list absorbs
   a new row without a layout decision.

   Each row is an angled split, image left / text right, from
   Dominik's wireframe. The wireframe drew a sharp rectangle with a
   heavy black rule; rendered here in the site's own language
   (16px radius, hairline border, blue hover) so it sits with the
   other cards on the page. The angle is the part that carries
   over, and it is what stops a row of images reading as a table.
   ------------------------------------------------------------ */

.gs-ap-refs {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: var(--content-frame);
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.gs-ap-ref {
  position: relative;               /* for the clipped border overlay below */
  display: grid;
  grid-template-columns: 38% 1fr;
  align-items: stretch;
  min-height: 200px;
  border-radius: 16px;
  overflow: hidden;                  /* clips the media to the radius */
  text-decoration: none;
  background: var(--color-bg);
  transition:
    border-color 0.3s ease,
    box-shadow 0.3s ease,
    transform 0.3s ease;
}

/* The hairline is an OVERLAY, not a real border, so it can stop where the
   image starts instead of wrapping it.

   The media's edge is diagonal - it bleeds 40px into the text column and
   its clip pulls back 80px by the bottom - so the outline has to end on
   that diagonal, which no `border` can do. A bordered pseudo-element
   clipped to the text region can: the clip removes the left side entirely
   and starts the top and bottom edges exactly where the image ends.

   The polygon mirrors .gs-ap-ref__media's own geometry. If that column
   width, bleed or pull-back changes, this has to change with it - see the
   media queries below, which restate it for the tablet values. */
.gs-ap-ref::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border: 1.5px solid #e3e6ec;      /* same hairline as .gs-wgs-feature */
  border-radius: 16px;
  clip-path: polygon(calc(38% + 40px) 0, 100% 0, 100% 100%, calc(38% - 40px) 100%);
  pointer-events: none;
  transition: border-color 0.3s ease;
}

/* CORAL on hover, not blue - these cards navigate off the page.

   The site already treats coral as the outbound-link colour on hover:
   .gs-wgs-section__lede a:hover, .gs-cta__link:hover and
   .gs-ap-paths__sub-links a:hover all do it. This card was the outlier.

   The distinction is coral-on-interaction vs coral-as-static-mark, not
   coral vs blue - the blast-radius X, the denial reasons and the "cheaper
   every run" chip are all coral marks that never move, so there is no
   collision. The four .gs-ap-deploy__item cards stay BLUE on purpose:
   they are not links, so they keep the UI accent.

   Geometry is unchanged from the theme's standard card hover: 6px lift,
   plus the inset 0.5px ring that optically thickens the border without
   shifting layout. */
a.gs-ap-ref:hover {
  transform: translateY(-6px);
  box-shadow: 0 12px 24px rgba(0, 38, 69, 0.1);
}

/* The inset 0.5px ring is gone with the real border - it wrapped the whole
   card, image included, which is the thing being fixed. A 2px overlay
   gives the same optical weight on hover. */
a.gs-ap-ref:hover::after {
  border-width: 2px;
  border-color: var(--color-brand-accent);
}

/* Grey at rest, coral on hover: the arrow answers the border rather than
   competing with it, and gives the gesture a second, quieter signal. */
a.gs-ap-ref:hover .gs-ap-ref__arrow {
  color: var(--color-brand-accent);
}

/* The angle. The media bleeds 40px into the text column and its
   bottom edge pulls back 80px, so the cut crosses the column
   boundary instead of sitting on it. The body's left padding
   (3.5rem) clears the widest point of the cut. */
.gs-ap-ref__media {
  position: relative;
  margin-right: -40px;
  background: var(--color-bg-muted);
  clip-path: polygon(0 0, 100% 0, calc(100% - 80px) 100%, 0 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem 4rem 1.5rem 1.5rem;
}

/* Deliberately (0,2,0), not a bare .gs-ap-ref__media--img: the mobile
   query below re-declares padding on .gs-ap-ref__media, and at equal
   specificity source order would hand that rule the win, re-inserting
   padding around the image at <=767px. Extra specificity makes this
   independent of where it sits in the file. */
.gs-ap-ref .gs-ap-ref__media--img {
  padding: 0;
}

/* Absolutely positioned so the image CANNOT contribute to the card's
   height. In flow, `height: 100%` resolves against a parent whose own
   height is content-driven, so it collapses to auto and the image falls
   back to its intrinsic ratio at `width: 100%` - which made the two
   image cards ~66px taller than the placeholder one, and made their
   heights differ from each other because their ratios differ.
   Out of flow, height comes from the body column and the 200px floor on
   .gs-ap-ref, exactly as it did before the images went in. __media is
   already `position: relative`, so this needs nothing else. */
/* The topic card's cover: brand background illustration plus a title,
   instead of an article image. Padding is dropped for the same reason as
   --img, and the illustration is all dark navy (#00324e down to #002032)
   so white type sits on it without needing a scrim. */
.gs-ap-ref .gs-ap-ref__media--art {
  padding: 0;
  background-size: cover;
  background-position: center;
}

.gs-ap-ref__cover {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  /* Clears the angled cut, which removes 80px from the bottom-right, so
     the label centres in the panel's usable width rather than its box. */
  padding-right: 4.5rem;
  color: var(--color-fg-on-dark);
  font-size: var(--text-h3);
  font-weight: var(--weight-bold);
  line-height: 1;
}

/* 1em, so the mark tracks the label's size automatically - including the
   drop to 22px that --text-h3 makes on mobile. */
.gs-ap-ref__cover-mark {
  height: 1em;
  width: auto;
  display: block;
}

.gs-ap-ref__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Quiet placeholder, not the loud dashed frame used elsewhere:
   these sit in a list of finished-looking rows and a red dashed
   box in each one would dominate the section. */
.gs-ap-ref__ph {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--color-fg-muted);
  text-align: center;
}

.gs-ap-ref__body {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.4rem;
  padding: 1.75rem 3.5rem 1.75rem 3.5rem;
}

.gs-ap-ref__source {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-fg-muted);
}

.gs-ap-ref__title {
  font-size: var(--text-h5);
  color: var(--color-fg);
  margin: 0;
  text-wrap: pretty;
}

.gs-ap-ref__blurb {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--color-fg-body);
  margin: 0;
  text-wrap: pretty;
}

.gs-ap-ref__arrow {
  position: absolute;
  top: 1.75rem;
  right: 1.75rem;
  display: inline-flex;
  /* Muted grey at rest so it does not compete with the title beside it;
     it turns accent on card hover (see above). Needs its own transition
     or the colour snaps while the border eases. */
  color: var(--color-fg-muted);
  transition: color 0.3s ease;
}

.gs-ap-ref__arrow svg {
  width: 20px;
  height: 20px;
  stroke-width: 2;
}

/* The pending-blog-selection row: same shape, coral dashed, and
   not a link. Delete with its markup once filled. */
.gs-ap-ref--ph {
  border: 2px dashed var(--color-brand-accent);
  background: rgba(246, 89, 84, 0.04);
}

.gs-ap-ref--ph .gs-ap-ref__source {
  color: var(--color-brand-accent);
  font-family: var(--font-mono);
}

/* ─── 11. Closing CTA microcopy ───────────── */

/* Breathing room, scoped to THIS page's closer - cta.css is shared by
   every page's closing CTA, so its base values stay put. The stack was
   1rem / 0 / 1.5rem, which read as one clump. */
.gs-ap-cta .gs-cta__title {
  margin-bottom: 1.25rem;
}

.gs-ap-cta .gs-cta__description {
  margin-bottom: 1.75rem;
}

/* Tightest gap in the stack on purpose: these two links are an appendix
   to the button, not a step after it, so they should read as attached to
   it rather than as their own row. */
.gs-ap-cta .gs-cta__secondary {
  margin-top: 1.25rem;
}

/* ============================================
   Tablet / mobile
   ============================================ */

@media (max-width: 900px) {
  /* Tabs below 768px: one panel at a time, so three full-height cards do
     not cost 1071px of scroll. tabs.js sets `hidden` on the inactive
     panels; nothing here needs to fight it. */
  .gs-ap-shots {
    display: block;
    max-width: none;
    /* No divider on mobile: stacked full-width blocks already read as
       separate, and a hairline across a 342px column reads as clutter.
       Plain margin instead of the desktop border-plus-padding pair. */
    border-top: 0;
    margin-top: 3rem;
    padding-top: 0;
  }

  .gs-ap-shots__tablist {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1.5rem;
  }

  .gs-ap-shots__tab {
    flex: 1;
    min-height: 44px;
    padding: 0.6rem 0.5rem;
    border: 1.5px solid var(--color-border);
    border-radius: 999px;
    background: var(--color-bg);
    color: var(--color-fg-body);
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: 1.2;
    cursor: pointer;
    transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
  }

  .gs-ap-shots__tab[aria-selected="true"] {
    border-color: var(--color-accent);
    background: #e8f4ff;             /* same light blue as .gs-wgs-feature__icon */
    color: var(--color-accent);
  }

  .gs-ap-shots__tab:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
  }

  /* The tab label already carries the step name, so repeating it as a
     headline inside the panel is redundant. */
  .gs-ap-shot__head {
    display: none;
  }

  /* 14px here, 16px on desktop. In a single narrow column the caption is
     the only text in the panel and 16px read as heavy; on desktop it sits
     under a 22px headline in a 336px column and needs the weight. */
  .gs-ap-shot__caption {
    font-size: var(--text-sm);
  }

  /* Tapping magnifies ~1.05x here - the lightbox is 92vw, about the same
     width as the panel - so the overlay would promise something it cannot
     deliver. gallery.js disables the trigger at the same breakpoint. */
  .gs-ap-shot__overlay {
    display: none;
  }

  .gs-ap-shot__trigger {
    cursor: default;
  }

  .gs-ap-shots__nav {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1.25rem;
    margin-top: 1.5rem;
  }

  .gs-ap-shots__dot {
    position: relative;
  }

  /* Drawing above the legend rather than beside it. The drawing keeps
     its aspect ratio, so a side-by-side split would squeeze the
     legend text to an unreadable column before the phone breakpoint
     ever arrives.

     justify-items: stretch, not center, so the legend and the glass
     panel both take the full column. They were centred with a 480px
     cap on the legend, which left the four items sitting in a narrow
     measure while the panel below them ran full width - two different
     left edges in the same column. */
  .gs-ap-blast {
    grid-template-columns: 1fr;
    gap: 2.5rem;
  }

  /* The legend and its panel centred as a block, with the text still
     left-aligned inside. Capped so the stack reads as one centred
     column rather than the drawing sitting in the middle of a
     full-bleed wall of text - at tablet width an uncapped legend runs a
     very long measure. */
  .gs-ap-blast__col {
    width: 100%;
    max-width: 640px;
    margin-inline: auto;
  }

  /* Narrower column: shrink the media share and the cut with it,
     so the title still gets a readable measure. */
  .gs-ap-ref {
    grid-template-columns: 34% 1fr;
  }

  /* 34% column, 24px bleed, 48px pull-back. */
  .gs-ap-ref::after {
    clip-path: polygon(calc(34% + 24px) 0, 100% 0, 100% 100%, calc(34% - 24px) 100%);
  }

  .gs-ap-ref__media {
    clip-path: polygon(0 0, 100% 0, calc(100% - 48px) 100%, 0 100%);
    margin-right: -24px;
  }

  .gs-ap-ref__body {
    padding-left: 2.5rem;
    padding-right: 2.5rem;
  }
}

@media (max-width: 767px) {
  .gs-ap-deploy {
    gap: 0.75rem;
  }

  .gs-ap-deploy__item {
    padding: 1.25rem;
  }

  /* Angled split is a desktop/tablet idea. Stacked, the cut would
     slice the image horizontally and read as a rendering bug, so
     the media goes back to a plain 16:9 band on top. */
  .gs-ap-ref {
    grid-template-columns: 1fr;
    min-height: 0;
  }

  /* Stacked here - image above, text below - so the split is horizontal
     rather than diagonal, and the outline does not need clipping at all:
     it becomes a real border on the body, open at the top where the image
     meets it. Same intent as the desktop clip - the outline never wraps
     the image - reached with less machinery, because a straight edge is
     something `border` can express.

     The card's own 16px radius plus `overflow: hidden` already rounds the
     bottom corners, so no radius is needed here. */
  .gs-ap-ref::after {
    display: none;
  }

  .gs-ap-ref__body {
    border: 1.5px solid var(--color-border);
    border-top: 0;
    /* Its own radius. I assumed the card's `overflow: hidden` plus 16px
       radius would round these, but clipping a square border corner
       against a curve just cuts it off - it does not bend it. The border
       has to curve itself. */
    border-radius: 0 0 16px 16px;
    transition: border-color 0.3s ease;
  }

  a.gs-ap-ref:hover .gs-ap-ref__body,
  a.gs-ap-ref:focus-visible .gs-ap-ref__body {
    border-color: var(--color-brand-accent);
  }

  .gs-ap-ref__media {
    margin-right: 0;
    clip-path: none;
    aspect-ratio: 16 / 9;
    padding: 1.5rem;
  }

  /* No angled cut down here, so there is nothing to clear - the offset
     would just push the label off centre. */
  .gs-ap-ref__cover {
    padding-right: 0;
  }

  .gs-ap-ref__body {
    padding: 1.5rem;
  }

  .gs-ap-ref__arrow {
    top: 1.5rem;
    right: 1.5rem;
  }

}

/* No breakpoint-specific type scale inside the diagrams. The labels
   are 14px at every viewport, same as desktop: the drawing is one
   design, and scaling its type per breakpoint made it read as two.

   There used to be a bump at 767px (14 -> 18px and the other two
   tiers with it) to compensate for the SVG being squeezed on a phone.
   Removed 2026-08-26. The trade-off is real and accepted: below about
   520px the drawing does shrink, and the labels shrink with it. */

