/* ==========================================================================
   Global accessibility + consent layer
   Loaded on every page. Owns the font scale, contrast modes, focus rings,
   skip link, accessibility toolbar and cookie banner.
   ========================================================================== */

/* Font scaling is applied to the root font-size, so every rem-based measure
   grows together and grid tracks defined in rem scale with the text instead
   of overflowing it. Layouts using px or vw are unaffected by design. */
:root { --a11y-scale: 1; }
html { font-size: calc(100% * var(--a11y-scale)); }

/* --- Skip link --- */
.a11y-skip {
  position: absolute;
  left: 0.5rem;
  top: -3rem;
  z-index: 2000;
  padding: 0.7rem 1.1rem;
  border-radius: 0 0 8px 8px;
  background: #0f172a;
  color: #ffffff;
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  transition: top 0.15s ease-in-out;
}

.a11y-skip:focus {
  top: 0;
}

/* --- Focus rings ---
   :focus-visible only, so a pointer click does not leave a ring behind while
   keyboard users always get one. Applied broadly rather than per component so
   controls added later inherit it. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid #7c3aed;
  outline-offset: 2px;
  border-radius: 4px;
}

html[data-contrast="high"] :focus-visible,
html[data-contrast="mono"] :focus-visible {
  outline-color: #ffcc00;
  outline-width: 4px;
}

/* --- Tokens this stylesheet uses -------------------------------------- *
 * Every one of these was referenced as var(--name, #fallback) throughout this file
 * and declared nowhere in the project, so the fallback was always what rendered.
 * Two consequences, neither visible from reading the rules:
 *
 *   - html[data-contrast="high"] below could not override them. It redefines the
 *     site's real tokens (--text-main, --text-muted, --accent-*), and since these
 *     rules referenced none of those, high-contrast mode left every element styled
 *     from this file untouched. That covers the capability tabs, the intake form and
 *     its error messages.
 *   - The values could not be checked. Two of them were failing as text on white:
 *     the old --accent-primary #6366f1 was 4.47:1, which does not even clear AA for
 *     normal text, and --danger #c0392b was 5.44:1.
 *
 * The two figures are corrected here, which changes the capability tabs' active
 * colour and the form error text slightly. --text-primary and --text-secondary keep
 * the values they already rendered with.
 * ---------------------------------------------------------------------- */
:root {
  /* The glass header's hairline. Named separately from --border-color because it is
     doing a different job -- a 1px rule under a translucent bar reads heavier than
     the same colour around a card -- and because the request names this token. */
  --border-subtle: #e8edf3;
  --text-primary: #0f172a;    /* 17.4:1 on white */
  --text-secondary: #475569;  /* 7.6:1  -- AAA */
  --accent-primary: #4338ca;  /* 7.9:1  -- was #6366f1 at 4.47:1 */
  /* Text ON the accent, not beside it. White clears AAA at 7.9:1 here; in
     high-contrast mode the accent becomes a pale blue and white collapses to
     1.68:1, so that mode overrides this to black at 12.53:1. */
  --accent-on: #ffffff;
  --danger: #a4161a;          /* 7.8:1  -- was #c0392b at 5.44:1 */
}

/* --- Contrast modes ---
   Applied at the root so they cover every page without each stylesheet
   needing its own variant. */
html[data-contrast="high"] {
  /* Now that the tokens above exist, this mode reaches them. */
  --text-primary: #ffffff;
  --text-secondary: #e6e6e6;
  --accent-primary: #b3c7ff;
  --accent-on: #000000;
  --danger: #ff8a80;
  --bg-primary: #000000;
  --bg-card: #0a0a0a;
  --text-main: #ffffff;
  --text-muted: #e6e6e6;
  --border-color: #ffffff;
  --accent-cyan: #00e5ff;
  --accent-purple: #d0a2ff;
  --accent-indigo: #b3c7ff;
  --success: #7CFFB2;
  --warning: #ffcc00;
  background: #000000;
  color: #ffffff;
}

/* Surfaces: pure black with a 2px white edge, so a card boundary is visible
   rather than inferred from a shadow that high contrast removes. */
html[data-contrast="high"] body,
html[data-contrast="high"] section,
html[data-contrast="high"] header,
html[data-contrast="high"] footer,
html[data-contrast="high"] nav,
html[data-contrast="high"] .card,
html[data-contrast="high"] .glass-card,
html[data-contrast="high"] .pricing-card,
html[data-contrast="high"] .engagement-card,
html[data-contrast="high"] .calculator-card,
html[data-contrast="high"] .modal-container,
html[data-contrast="high"] .consult-dialog,
html[data-contrast="high"] .modal-card,
html[data-contrast="high"] .a11y-panel,
html[data-contrast="high"] .cookie-banner,
html[data-contrast="high"] .policy-section {
  background: #000000 !important;
  background-image: none !important;
  color: #FFFFFF !important;
  box-shadow: none !important;
}

html[data-contrast="high"] .card,
html[data-contrast="high"] .glass-card,
html[data-contrast="high"] .pricing-card,
html[data-contrast="high"] .engagement-card,
html[data-contrast="high"] .calculator-card,
html[data-contrast="high"] .modal-container,
html[data-contrast="high"] .consult-dialog,
html[data-contrast="high"] .modal-card,
html[data-contrast="high"] .a11y-panel,
html[data-contrast="high"] .cookie-banner {
  border: 2px solid #FFFFFF !important;
}

html[data-contrast="high"] .site-header,
html[data-contrast="high"] .nav-more-menu,
html[data-contrast="high"] .mobile-menu {
  background: #000000 !important;
  border-color: #FFFFFF !important;
}

/* Text. Body copy pure white; secondary copy #E5E7EB, which still clears 7:1
   on black and stays distinguishable from primary text. */
html[data-contrast="high"] p,
html[data-contrast="high"] li,
html[data-contrast="high"] td,
html[data-contrast="high"] th,
html[data-contrast="high"] label,
html[data-contrast="high"] dt,
html[data-contrast="high"] dd,
html[data-contrast="high"] h1,
html[data-contrast="high"] h2,
html[data-contrast="high"] h3,
html[data-contrast="high"] h4,
html[data-contrast="high"] h5 {
  color: #FFFFFF !important;
}

html[data-contrast="high"] .section-subtitle,
html[data-contrast="high"] .pricing-tagline,
html[data-contrast="high"] .engagement-blurb,
html[data-contrast="high"] .calc-foot,
html[data-contrast="high"] .addon-note,
html[data-contrast="high"] .policy-hint,
html[data-contrast="high"] .field-hint,
html[data-contrast="high"] .rate-badge-kind,
html[data-contrast="high"] .calc-badge-label,
html[data-contrast="high"] .calc-badge-note,
html[data-contrast="high"] .engagement-label,
html[data-contrast="high"] .stat-label,
html[data-contrast="high"] small {
  color: #E5E7EB !important;
}

/* Nav links kept lighter than body text would allow them to blend into it. */
html[data-contrast="high"] .nav-links a,
html[data-contrast="high"] .nav-more-toggle,
html[data-contrast="high"] a {
  color: #00E5FF !important;
}

html[data-contrast="high"] .nav-burger-bar {
  background: #FFFFFF !important;
}

/* Figures read as data, not prose, so they take the cyan accent. */
html[data-contrast="high"] .price-amount,
html[data-contrast="high"] .price-currency,
html[data-contrast="high"] .price-period,
html[data-contrast="high"] .calc-badge-value,
html[data-contrast="high"] .calc-total,
html[data-contrast="high"] .rate-badge-amount,
html[data-contrast="high"] .stat-value,
html[data-contrast="high"] .role-count {
  color: #00FFFF !important;
  -webkit-text-fill-color: #00FFFF !important;
}

html[data-contrast="high"] .pricing-header h3,
html[data-contrast="high"] .section-title,
html[data-contrast="high"] .modal-title,
html[data-contrast="high"] .hero-title,
html[data-contrast="high"] .engagement-heading {
  /* Stated explicitly rather than relying on inheritance: these carry
     gradient fills, and an inherited colour is invisible behind one. */
  color: #FFFFFF !important;
  -webkit-text-fill-color: #FFFFFF !important;
  background: none !important;
  background-image: none !important;
}

/* Buttons: solid white with black text. A tinted background cannot be relied
   on to clear 7:1 against white label text. */
html[data-contrast="high"] .btn-primary,
html[data-contrast="high"] .btn-submit,
html[data-contrast="high"] .btn-checkout,
html[data-contrast="high"] .engagement-cta,
html[data-contrast="high"] .calc-cta,
html[data-contrast="high"] .cookie-accept,
html[data-contrast="high"] .a11y-btn[aria-pressed="true"] {
  background: #FFFFFF !important;
  background-image: none !important;
  color: #000000 !important;
  border: 2px solid #FFFFFF !important;
  text-shadow: none !important;
}

html[data-contrast="high"] .btn-secondary,
html[data-contrast="high"] .btn-row,
html[data-contrast="high"] .a11y-btn,
html[data-contrast="high"] .cookie-actions button,
html[data-contrast="high"] .btn-tenant-action {
  background: #000000 !important;
  color: #FFFFFF !important;
  border: 2px solid #FFFFFF !important;
}

/* Pills and badges carry meaning through colour, so they are inverted rather
   than flattened to the page background. */
html[data-contrast="high"] .addon-pill,
html[data-contrast="high"] .popular-badge {
  background: #000000 !important;
  color: #00FFFF !important;
  -webkit-text-fill-color: #00FFFF !important;
  border: 2px solid #FFFFFF !important;
}

html[data-contrast="high"] input,
html[data-contrast="high"] select,
html[data-contrast="high"] textarea {
  background: #000000 !important;
  color: #FFFFFF !important;
  border: 2px solid #FFFFFF !important;
}

html[data-contrast="high"] input::placeholder,
html[data-contrast="high"] textarea::placeholder {
  color: #E5E7EB !important;
  opacity: 1;
}

/* Decorative washes and blurs reduce effective contrast; both are removed. */
html[data-contrast="high"] *::before,
html[data-contrast="high"] *::after {
  background-image: none !important;
  backdrop-filter: none !important;
}

html[data-contrast="mono"] {
  filter: grayscale(1) contrast(1.15);
}

/* --- Accessibility toolbar --- */
.a11y-fab {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 1500;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 2px solid #ffffff;
  background: #4f46e5;
  color: #ffffff;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.35);
}

.a11y-panel {
  position: fixed;
  right: 1rem;
  bottom: 4.75rem;
  z-index: 1500;
  width: min(310px, calc(100vw - 2rem));
  padding: 1.1rem;
  border-radius: 12px;
  border: 1px solid #cbd5e1;
  background: #ffffff;
  color: #0f172a;
  box-shadow: 0 18px 44px rgba(15, 23, 42, 0.28);
}

.a11y-panel h2 {
  margin: 0 0 0.85rem;
  font-size: 1rem;
  color: #0f172a;
}

.a11y-group { margin-bottom: 1rem; }

.a11y-group-label {
  display: block;
  margin-bottom: 0.45rem;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #475569;
}

.a11y-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.a11y-btn {
  flex: 1 1 auto;
  min-height: 44px;
  padding: 0.45rem 0.7rem;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  background: #ffffff;
  color: #0f172a;
  font-family: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.a11y-btn[aria-pressed="true"] {
  background: #4f46e5;
  border-color: #4f46e5;
  color: #ffffff;
  font-weight: 700;
}

.a11y-hint {
  margin: 0.5rem 0 0;
  font-size: 0.74rem;
  line-height: 1.5;
  color: #475569;
}

/* --- Cookie consent --- */
.cookie-banner {
  position: fixed;
  left: 1rem;
  right: 1rem;
  bottom: 1rem;
  z-index: 1600;
  max-width: 560px;
  margin: 0 auto;
  padding: 1.2rem 1.3rem;
  border-radius: 14px;
  border: 1px solid #cbd5e1;
  background: #ffffff;
  color: #0f172a;
  box-shadow: 0 20px 50px rgba(15, 23, 42, 0.3);
}

.cookie-banner h2 {
  margin: 0 0 0.5rem;
  font-size: 1.02rem;
}

.cookie-banner p {
  margin: 0 0 0.9rem;
  font-size: 0.86rem;
  line-height: 1.6;
  color: #334155;
}

.cookie-options {
  display: grid;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.cookie-option {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.6rem;
  align-items: start;
  font-size: 0.86rem;
  line-height: 1.5;
}

.cookie-option input { margin-top: 0.2rem; width: 1.05rem; height: 1.05rem; }
.cookie-option small { display: block; color: #475569; font-size: 0.76rem; }

.cookie-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.cookie-actions button {
  flex: 1 1 auto;
  min-height: 44px;
  padding: 0.6rem 1rem;
  border-radius: 8px;
  border: 1px solid #cbd5e1;
  background: #ffffff;
  color: #0f172a;
  font-family: inherit;
  font-size: 0.88rem;
  cursor: pointer;
}

.cookie-actions .cookie-accept {
  background: #4f46e5;
  border-color: #4f46e5;
  color: #ffffff;
  font-weight: 600;
}

/* --- Policy footer --- */
.legal-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0.4rem 0.9rem;
  padding: 1.25rem 1rem;
  font-size: 0.84rem;
  line-height: 1.6;
}

.legal-footer a,
.legal-footer button {
  color: inherit;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.legal-footer .sep { opacity: 0.5; }

@media (prefers-reduced-motion: reduce) {
  .a11y-skip { transition: none; }
}

/* ── Services & delivery timelines, high contrast ───────────────────────────
   The section uses the page's own light surfaces, so these rules only push it
   the rest of the way: muted body copy and the indigo accents become pure
   black on pure white, and borders take over the separating that tint did.
   ------------------------------------------------------------------------- */

html[data-contrast="high"] .svc-band,
html[data-contrast="high"] .svc-card,
html[data-contrast="high"] .svc-card-featured,
html[data-contrast="high"] .svc-roadmap {
  background: #FFFFFF !important;
  color: #000000 !important;
  border-color: #000000 !important;
}

html[data-contrast="high"] .svc-band h2,
html[data-contrast="high"] .svc-band h3,
html[data-contrast="high"] .svc-band h4,
html[data-contrast="high"] .svc-band p,
html[data-contrast="high"] .svc-band li,
html[data-contrast="high"] .svc-eyebrow,
html[data-contrast="high"] .svc-lede,
html[data-contrast="high"] .svc-blurb,
html[data-contrast="high"] .svc-tier,
html[data-contrast="high"] .svc-delivery,
html[data-contrast="high"] .svc-days,
html[data-contrast="high"] .svc-scope {
  color: #000000 !important;
}

html[data-contrast="high"] .svc-tick,
html[data-contrast="high"] .svc-clock { color: #000000 !important; }

html[data-contrast="high"] .svc-tier { background: #FFFFFF !important; border: 2px solid #000000 !important; }
html[data-contrast="high"] .svc-scope a { color: #000000 !important; }

/* The badge and timeline nodes carry meaning, so they stay filled and are
   inverted rather than flattened away. */
html[data-contrast="high"] .svc-popular {
  background: #000000 !important;
  color: #FFFFFF !important;
}

html[data-contrast="high"] .svc-phase { border-color: #000000 !important; }
html[data-contrast="high"] .svc-phase::before {
  background: #000000 !important;
  box-shadow: 0 0 0 4px #FFFFFF !important;
}

@media (max-width: 520px) {
  .a11y-panel { right: 0.5rem; left: 0.5rem; width: auto; }
  .cookie-actions button { flex: 1 1 100%; }
}

/* --- Policy page typography ---
   Measure capped near 70 characters and generous line-height: legal text is
   read in long runs, and machine translation lengthens most European
   languages by 15-30%, which a tighter column would break. */
.policy-page {
  max-width: 780px;
  margin: 0 auto;
  padding: 3rem 1.25rem 4rem;
  color: var(--text-main, #0f172a);
}

.policy-header { margin-bottom: 2.5rem; }

.policy-eyebrow {
  margin: 0 0 0.4rem;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-purple, #7c3aed);
}

.policy-page h1 { font-size: clamp(1.9rem, 4vw, 2.6rem); margin: 0 0 0.75rem; }
.policy-page h2 { font-size: clamp(1.35rem, 3vw, 1.7rem); margin: 0 0 1rem; }
.policy-page h3 { font-size: 1.05rem; margin: 1.75rem 0 0.6rem; }

.policy-lead { font-size: 1.02rem; line-height: 1.7; color: var(--text-muted, #475569); margin: 0 0 0.75rem; }
.policy-updated { font-size: 0.82rem; color: var(--text-muted, #475569); margin: 0 0 1.5rem; }

.policy-toc {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--border-color, #e2e8f0);
  border-radius: 10px;
  font-size: 0.9rem;
}

.policy-section {
  margin-bottom: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border-color, #e2e8f0);
  scroll-margin-top: 1.5rem;
}

.policy-page p,
.policy-page li { font-size: 0.95rem; line-height: 1.75; max-width: 68ch; }
.policy-page ul { padding-left: 1.25rem; margin: 0 0 1rem; }
.policy-page li { margin-bottom: 0.5rem; }

.policy-table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.9rem; }
.policy-table caption { text-align: left; font-weight: 700; padding-bottom: 0.5rem; }
.policy-table th, .policy-table td {
  border: 1px solid var(--border-color, #e2e8f0);
  padding: 0.6rem 0.75rem;
  text-align: left;
  vertical-align: top;
}
.policy-table th { background: var(--bg-primary, #f8fafc); }

.policy-inline-btn {
  background: none; border: none; padding: 0;
  font: inherit; color: var(--accent-cyan, #0e7490);
  text-decoration: underline; cursor: pointer;
}

.policy-form { margin-top: 1.5rem; max-width: 520px; }
.policy-field { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 1rem; }
.policy-field label { font-size: 0.82rem; font-weight: 600; color: var(--text-muted, #475569); }
.policy-field input,
.policy-field select,
.policy-field textarea {
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--border-color, #e2e8f0);
  border-radius: 8px;
  background: var(--bg-card, #ffffff);
  color: var(--text-main, #0f172a);
  font: inherit;
  font-size: 0.92rem;
}
.policy-note { font-size: 0.8rem; color: var(--text-muted, #475569); margin-top: 0.9rem; }
.policy-error {
  padding: 0.7rem 0.85rem; border: 1px solid var(--warning, #b45309);
  border-radius: 8px; color: var(--warning, #b45309); font-size: 0.86rem;
}
.policy-success {
  padding: 1.25rem; border: 1px solid var(--success, #047857);
  border-radius: 10px; margin-top: 1.5rem;
}
.policy-success h3 { margin-top: 0; }

@media (max-width: 520px) {
  .policy-page { padding: 2rem 1rem 3rem; }
  .policy-table { font-size: 0.84rem; }
  .policy-table th, .policy-table td { padding: 0.5rem; }
}

/* Inline hint under an auto-populated field, so an inferred value is visibly
   a suggestion rather than something the visitor is assumed to have chosen. */
.policy-hint {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-muted, #475569);
}

.policy-hint:empty { display: none; }

/* Showcase drawer in high contrast: the mockups are markup, so they follow
   the same rules as the rest of the page rather than needing their own. */
html[data-contrast="high"] .showcase-drawer,
html[data-contrast="high"] .showcase-stage,
html[data-contrast="high"] .mk-frame,
html[data-contrast="high"] .mk-hero,
html[data-contrast="high"] .mk-card,
html[data-contrast="high"] .mk-stat,
html[data-contrast="high"] .mk-role,
html[data-contrast="high"] .mk-tenant,
html[data-contrast="high"] .mk-status li,
html[data-contrast="high"] .mk-form,
html[data-contrast="high"] .mk-field {
  background: #000000 !important;
  color: #FFFFFF !important;
  border-color: #FFFFFF !important;
}

html[data-contrast="high"] .showcase-trigger,
html[data-contrast="high"] .mk-v,
html[data-contrast="high"] .mk-nav {
  color: #00E5FF !important;
}

html[data-contrast="high"] .showcase-chip[aria-pressed="true"] {
  background: #FFFFFF !important;
  color: #000000 !important;
  border: 2px solid #FFFFFF !important;
}

html[data-contrast="high"] .mk-btn {
  background: #FFFFFF !important;
  color: #000000 !important;
}

html[data-contrast="high"] .mk-dot {
  background: #00FFFF !important;
}

/* The dark STRUCTiO sample sets its own colours, so high contrast has to
   reclaim it explicitly or it keeps a slate palette on a black page. */
html[data-contrast="high"] .mk-dark,
html[data-contrast="high"] .mk-dark .mk-panel,
html[data-contrast="high"] .mk-dark .mk-status li,
html[data-contrast="high"] .mk-dark .mk-chip,
html[data-contrast="high"] .mk-dark .mk-asset,
html[data-contrast="high"] .mk-dark .mk-gbtn,
html[data-contrast="high"] .stack-row,
html[data-contrast="high"] .mk-panel,
html[data-contrast="high"] .mk-chip,
html[data-contrast="high"] .mk-asset,
html[data-contrast="high"] .mk-gbtn,
html[data-contrast="high"] .mk-badge {
  background: #000000 !important;
  color: #FFFFFF !important;
  border-color: #FFFFFF !important;
}

html[data-contrast="high"] .mk-dark .mk-nav,
html[data-contrast="high"] .mk-dark .mk-v,
html[data-contrast="high"] .mk-panel-head,
html[data-contrast="high"] .mk-note,
html[data-contrast="high"] .showcase-track,
html[data-contrast="high"] .stack-row dd {
  color: #E5E7EB !important;
}

html[data-contrast="high"] .mk-chip-on {
  background: #FFFFFF !important;
  color: #000000 !important;
}


/* --- Footer separator ---------------------------------------------------- *
 * The pipe is decoration: it carries no meaning a screen reader should read,
 * which is why the markup marks it aria-hidden. These rules stop it behaving
 * like text in the other two ways that matter -- it is excluded from selection,
 * so dragging across the footer to copy a link does not pick up stray pipes, and
 * it is given its own colour token rather than inheriting link colour, which
 * made it read as part of the adjacent link.
 * ------------------------------------------------------------------------- */
.legal-footer .sep {
  opacity: 0.5;
  /* Not a link, so it must not look clickable or be draggable as text. */
  user-select: none;
  -webkit-user-select: none;
  cursor: default;
  /* Symmetrical breathing room independent of the flex gap, so the bar reads as
     link-pipe-link rather than as pairs. */
  padding: 0 0.1rem;
  font-variant-numeric: normal;
}

/* --- Auto-hiding site header -------------------------------------------- *
 * The header slides out of the way when reading downward and returns the moment
 * the reader scrolls up.
 *
 * IT STAYS position: sticky, DELIBERATELY
 * The obvious implementation is position: fixed. This header was sticky on
 * purpose -- style.css records that it was changed from fixed so it would occupy
 * flow space instead of overlaying the page, and the hero's top padding was
 * reduced to match. Going back to fixed would take that space away again and
 * pull every page's first screenful upward underneath the bar, which is the
 * layout shift this feature is supposed to avoid. Sticky plus a transform gets
 * the same visual result: translateY is composited, so the slide costs no
 * layout and no paint, and nothing below the header moves at all.
 *
 * The transform is applied by class, never by inline style, so the scroll
 * handler does exactly one thing per frame and never reads geometry back.
 * ------------------------------------------------------------------------- */
/* The header no longer hides. What was here -- a transform that slid it out of view
   on a downward scroll -- is removed, and with it the reasons it needed guarding: a
   header that is never off-screen cannot obscure focused chrome (WCAG 2.2), cannot
   vanish when a sub-nav link scrolls the reader downward, and cannot flicker on
   trackpad jitter. A locked bar is a simpler promise than a well-defended one. */
.site-chrome {
  transition: background 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}

/* Glass, but only once there is something behind it to be glass over. At the top of
   the page the header sits on the page's own background and the treatment would just
   be a slightly different white. */
.site-header.chrome-scrolled {
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border-subtle);
  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
}

/* backdrop-filter is unevenly supported, and where it is missing a 0.92 alpha
   background leaves whatever scrolls underneath legible through the bar. The opaque
   fallback is the safer failure. */
@supports not ((backdrop-filter: blur(8px)) or (-webkit-backdrop-filter: blur(8px))) {
  .site-header.chrome-scrolled {
    background: rgba(255, 255, 255, 0.99);
  }
}

@media (prefers-reduced-motion: reduce) {
  .site-chrome {
    transition: none;
  }
}


/* --- Rolling sub-navigation --------------------------------------------- *
 * Pinned at the primary header's measured height rather than a constant. The
 * fallback in the var() matters: if --header-height has not been published yet --
 * scripting off, or the first paint before measurement -- the bar still lands in a
 * sensible place instead of on top of the header.
 * ---------------------------------------------------------------------- */
/* The bar's own palette, declared here rather than borrowed.
 *
 * WHY THESE TOKENS EXIST
 * ----------------------
 * The rules below used to read var(--text-secondary, #475569) and
 * var(--accent-primary, #6366f1). Neither token is defined anywhere in this project
 * -- the site's tokens are --text-main, --text-muted and --accent-indigo -- so every
 * colour in this bar silently resolved to its hardcoded fallback. Three things
 * followed from that, all of them invisible to anyone reading the CSS:
 *
 *   1. The dark-scheme block at the end of this section darkened the BACKGROUND and
 *      nothing else, so the link text stayed #475569 on a #2b3243 bar -- about 1.9:1,
 *      which is not legible at all, let alone AAA. This is the reported defect.
 *   2. The active link was #6366f1 on the light bar: 4.45:1. That fails AAA (7:1) and
 *      only scrapes AA. Nobody had noticed because the failure was in the default
 *      state, not the dark one.
 *   3. html[data-contrast="high"] redefines the site's real tokens, and since this bar
 *      referenced none of them, high-contrast mode did not reach the sub-nav at all.
 *
 * Every value below is checked numerically by test/subnav_contrast.test.js, which
 * composites the translucent background over the page and computes the WCAG ratio
 * rather than trusting a hex code to look right.
 */
:root {
  --subnav-bg: rgba(255, 255, 255, 0.88);
  /* The surface the bar is composited over, so the test can compute the real
     contrast rather than assuming an opaque background. */
  --subnav-bg-under: #f8fafc;
  --subnav-fg: #475569;          /* 7.56:1 on the composited light bar */
  --subnav-fg-strong: #0f172a;   /* hover */
  --subnav-active: #4338ca;      /* 7.88:1 -- the old #6366f1 was 4.45:1 */
}

.site-subnav {
  position: sticky;
  top: var(--header-height, 64px);
  z-index: 40;
  border-bottom: 1px solid var(--border-color);
  background: var(--subnav-bg);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  transition: transform 0.3s ease-in-out;
}

/* The rule that used to live here pulled the sub-nav up to the viewport edge when the
   header hid itself. The header no longer hides, so the sub-nav simply docks beneath
   it at --header-height and stays there. */

/* Condensed past the fold, and on anything narrower than a laptop. Both cases are the
   same problem -- two stacked bars eating vertical space that the page needs -- and
   the same answer, so they share a rule rather than diverging. */
:root.chrome-condensed .site-subnav a {
  padding-top: 0.42rem;
  padding-bottom: 0.42rem;
  font-size: 0.78rem;
}

@media (max-width: 1023px) {
  .site-subnav a {
    padding: 0.45rem 0.6rem;
    font-size: 0.78rem;
  }
}

.site-subnav-inner {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 1.25rem;
  overflow-x: auto;
  /* Momentum without a visible scrollbar eating vertical space on narrow
     viewports, where six links will not fit. */
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

.site-subnav ul {
  display: flex;
  gap: 0.25rem;
  list-style: none;
  margin: 0;
  padding: 0;
  white-space: nowrap;
}

.site-subnav a {
  display: inline-block;
  padding: 0.65rem 0.85rem;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--subnav-fg);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.site-subnav a:hover { color: var(--subnav-fg-strong); }

.site-subnav a:focus-visible {
  outline: 3px solid var(--subnav-active);
  outline-offset: -3px;
  border-radius: 4px;
}

/* The active section. Carried by both a colour and an underline, so it is not
   signalled by colour alone. aria-current is the attribute that is announced;
   this selector styles whatever the script marked, so the two cannot disagree. */
.site-subnav a[aria-current="true"] {
  color: var(--subnav-active);
  border-bottom-color: var(--subnav-active);
}

/* Anchored headings clear both bars.
 *
 * WHY THIS IS A CALC AND NOT A CONSTANT
 * -------------------------------------
 * subnav.js publishes --anchor-offset as header + sub-nav + a gap, which is right on a
 * page that HAS a sub-nav. It bails at its first line when there is no [data-subnav]
 * element, though, and index.html and governance.html no longer carry one -- so on the
 * two pages most of the site's anchors live on, nothing ever published a value and this
 * declaration was the only one in effect. A flat 140px there is a guess about a header
 * that is measured accurately three lines away: it was wrong at every breakpoint, wrong
 * again when the nav wrapped, and wrong in the direction that hides the heading someone
 * followed the link to read.
 *
 * So the default is derived from --header-height, which site-chrome.js publishes on
 * every page including those two, plus the sub-nav's height where there is one, plus a
 * gap so the title is clearly below the bar rather than touching it. subnav.js still
 * overrides --anchor-offset inline where it runs, and an inline style wins, so nothing
 * about the measured case changes.
 *
 * --subnav-height defaults to 0 because most pages have no second bar. That default has
 * to be corrected on the pages that do, and not by JavaScript: .site-subnav is sticky
 * from CSS alone, so with scripting off -- or during the first frame before subnav.js
 * measures -- the bar is on screen and nothing has published its height. A flat
 * header + gap there would drop every /about heading underneath it, which is the same
 * bug in the other direction. :has() reads the page's own markup, so the correction
 * applies to exactly the pages carrying a bar and is superseded the moment subnav.js
 * publishes a real measurement.
 *
 * The pixel numbers inside the var() calls are only ever the pre-measurement frame. */
:root {
  --anchor-offset: calc(var(--header-height, 80px) + var(--subnav-height, 0px) + 24px);
}

:root:has([data-subnav]) {
  --subnav-height: 48px;
}

/* --anchor-offset is published by subnav.js as header + sub-nav + a gap, so it
   accounts for BOTH sticky bars rather than just the header. The ids the site
   actually links to are named explicitly as well as covered by section[id]: an anchor
   on a div or an <a id> carries no element selector, and those are the ones that land
   under the bars without anyone noticing. */
section[id],
[id^="about-"],
[id^="gov-"],
#capabilities, #services, #services-timelines, #pricing, #pricing-tiers, #analysis,
#about, #governance, #licensing, #infrastructure, #isolation, #internals,
#presentations, #projects {
  scroll-margin-top: var(--anchor-offset, 140px);
}

@media (prefers-reduced-motion: reduce) {
  .site-subnav { transition: none; }
  .site-subnav a { transition: none; }
}

@media (max-width: 640px) {
  .site-subnav a { padding: 0.55rem 0.6rem; font-size: 0.78rem; }
}

/* Dark surfaces: the translucent light background would wash out over them.
 *
 * The text is flipped with the background here, which is the part that was missing.
 * Previously only the background changed and the dark slate link colour stayed put,
 * which is how a bar of unreadable text shipped.
 *
 * Worth flagging separately: this block darkens the sub-nav on a page whose other
 * surfaces have no dark-scheme variant at all -- the site defines no dark tokens --
 * so in a dark-preferring browser this one bar is dark on an otherwise light page.
 * That is a design decision rather than a bug, and it is left as it was found; only
 * the contrast failure inside it is fixed. Whether the site should have a dark theme
 * is a question worth answering deliberately.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --subnav-bg: rgba(15, 23, 42, 0.88);
    --subnav-bg-under: #f8fafc;
    --subnav-fg: #e2e8f0;        /* 10.39:1 on the composited dark bar */
    --subnav-fg-strong: #ffffff; /* 12.81:1 */
    --subnav-active: #67e8f9;    /* 8.84:1 -- and plainly not the body colour */
  }
}

/* High contrast reaches the bar now. It did not before: this section referenced no
   token that html[data-contrast="high"] redefines, so the mode changed the whole page
   and left the sub-nav exactly as it was. */
html[data-contrast="high"] {
  --subnav-bg: #000000;
  --subnav-bg-under: #000000;
  --subnav-fg: #ffffff;
  --subnav-fg-strong: #ffffff;
  --subnav-active: #00e5ff;
}

/* Monochrome mode is a grayscale filter over the finished page, so it collapses the
   hue difference between the active link and the rest. That is survivable only
   because the active state also carries the 2px underline above -- it was never
   signalled by colour alone, and must not become so. */


/* --- Capabilities: tabbed groups and progressive disclosure ------------- *
 * The tabs are styled from aria-selected rather than from a class alone, so what
 * assistive technology is told and what the eye sees cannot drift apart. The
 * is-active class rides along only for the transition.
 * ---------------------------------------------------------------------- */
.cap-tabs {
  margin: 0 0 1.5rem;
}

.cap-tablist {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  border-bottom: 1px solid var(--border-color);
  padding-bottom: 0.15rem;
}

.cap-tab {
  appearance: none;
  border: 1px solid transparent;
  border-bottom: 2px solid transparent;
  background: none;
  padding: 0.6rem 1rem;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-secondary, #475569);
  cursor: pointer;
  border-radius: 8px 8px 0 0;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.cap-tab:hover { color: var(--text-primary, #0f172a); }

.cap-tab:focus-visible {
  outline: 3px solid var(--accent-primary, #6366f1);
  outline-offset: 2px;
}

.cap-tab[aria-selected="true"] {
  color: var(--accent-primary, #6366f1);
  border-bottom-color: var(--accent-primary, #6366f1);
  background: rgba(99, 102, 241, 0.06);
}

/* Before the script runs there is no aria-selected on anything and every panel is
   visible, which is the deliberate no-JS state. The tablist is hidden until it is
   actually a tablist, so nothing offers a control that does nothing. */
.cap-tabs:not([data-cap-tabs-ready]) .cap-tablist { display: none; }

.cap-panel-desc {
  margin: 0 0 1.25rem;
  font-size: 0.92rem;
  color: var(--text-secondary, #475569);
}

/* Without the tabs, each group needs its own separation on the page. */
.cap-tabs:not([data-cap-tabs-ready]) ~ .cap-panel {
  margin-bottom: 2.5rem;
}

.cap-panel:focus-visible {
  outline: 3px solid var(--accent-primary, #6366f1);
  outline-offset: 6px;
  border-radius: 8px;
}

.cap-card {
  display: flex;
  flex-direction: column;
}

.cap-summary {
  /* The plain-English line. Deliberately larger than the detail beneath it: it is
     the sentence most readers will read, and the only one some will. */
  font-size: 0.98rem;
  line-height: 1.6;
  margin: 0 0 0.9rem;
}

.cap-details {
  margin-top: auto;
  border-top: 1px solid var(--border-color);
  padding-top: 0.7rem;
}

.cap-details > summary {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--accent-primary, #6366f1);
  /* The native marker is replaced, so the open state needs its own indicator --
     see the ::after rule below. list-style on the summary is what Firefox uses. */
  list-style: none;
}

.cap-details > summary::-webkit-details-marker { display: none; }

.cap-details > summary:focus-visible {
  outline: 3px solid var(--accent-primary, #6366f1);
  outline-offset: 3px;
  border-radius: 4px;
}

.cap-details-label::after {
  content: " +";
  font-weight: 700;
}

.cap-details[open] .cap-details-label::after { content: " \2212"; }

/* The technical title of the capability, kept visible on the summary so the
   disclosure says what is behind it rather than only "Learn more". */
.cap-details-hint {
  font-weight: 600;
  font-size: 0.76rem;
  color: var(--text-secondary, #475569);
}

.cap-details-body {
  padding-top: 0.6rem;
}

.cap-details-body p {
  margin: 0;
  font-size: 0.86rem;
  line-height: 1.65;
  color: var(--text-secondary, #475569);
}

@media (prefers-reduced-motion: reduce) {
  .cap-tab { transition: none; }
}

@media (max-width: 640px) {
  .cap-tablist { gap: 0.25rem; }
  .cap-tab { padding: 0.5rem 0.7rem; font-size: 0.82rem; }
}


/* --- Sitewide IP notice -------------------------------------------------- *
 * Deliberately quiet: it is a legal notice that must be present and legible, not a
 * banner competing with the content above it. Kept at the same width as the footer
 * bar so the two read as one block.
 * ---------------------------------------------------------------------- */
.ip-notice {
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 1.25rem 1.75rem;
  font-size: 0.72rem;
  line-height: 1.55;
  color: var(--text-secondary, #64748b);
  text-align: center;
}


/* --- Routed intake form (/about) ----------------------------------------- */
.inquiry-form { margin-top: 1.5rem; }

.inquiry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.1rem 1.25rem;
}

/* Scope details wants the full width whatever the column count. */
.form-field--wide { grid-column: 1 / -1; }

.form-field { display: flex; flex-direction: column; gap: 0.35rem; }

.form-field label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-main, #0f172a);
}

.form-field .req { color: var(--danger, #c0392b); }
.form-field .optional { font-weight: 400; color: var(--text-secondary, #64748b); }

.inquiry-form input,
.inquiry-form select,
.inquiry-form textarea {
  width: 100%;
  padding: 0.65rem 0.75rem;
  font: inherit;
  font-size: 0.92rem;
  color: inherit;
  background: var(--bg-card, #fff);
  border: 1px solid var(--border-color);
  border-radius: 8px;
}

.inquiry-form textarea { resize: vertical; min-height: 8rem; }

.inquiry-form input:focus-visible,
.inquiry-form select:focus-visible,
.inquiry-form textarea:focus-visible {
  outline: 3px solid var(--accent-primary, #6366f1);
  outline-offset: 1px;
  border-color: var(--accent-primary, #6366f1);
}

/* The invalid state is carried by a border AND the message below, never by colour
   alone -- the message is the part a screen reader reads and a colour-blind reader
   relies on. */
.inquiry-form .is-invalid {
  border-color: var(--danger, #c0392b);
  box-shadow: 0 0 0 1px var(--danger, #c0392b);
}

.field-note,
.field-error {
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.45;
}

.field-note { color: var(--text-secondary, #64748b); }

/* Empty by default and holds no space until it has something to say, so the layout
   does not carry gaps waiting for errors that may never come. */
.field-error {
  color: var(--danger, #c0392b);
  font-weight: 600;
}
.field-error:empty { display: none; }

.inquiry-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 1.4rem;
}

.inquiry-status { margin: 0; font-size: 0.85rem; }
.inquiry-status:empty { display: none; }
.inquiry-status.is-error { color: var(--danger, #c0392b); font-weight: 600; }
.inquiry-status.is-ok { color: var(--success, #1b873f); font-weight: 600; }

.inquiry-form button[disabled] { opacity: 0.6; cursor: not-allowed; }


/* --- Licensing model, tiers and the scoping flow ----------------------- *
 * Replaces the per-feature price grid. The colours come from the tokens declared at
 * the top of this file rather than from fallbacks, so html[data-contrast="high"]
 * reaches them.
 * ---------------------------------------------------------------------- */
.licensing-model {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  align-items: stretch;
  gap: 1.25rem;
  margin: 0 0 1.5rem;
}

.licensing-part {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.licensing-kind {
  align-self: flex-start;
  padding: 0.15em 0.6em;
  border-radius: 999px;
  border: 1px solid currentColor;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* The two components are told apart by the word in the pill, not by its colour --
   "One-time" and "Monthly" are the labels. The colour is reinforcement. */
.licensing-kind-once { color: var(--accent-primary); }
.licensing-kind-monthly { color: var(--accent-cyan); }

.licensing-list {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
  align-content: start;
}

.licensing-list li {
  padding-left: 1.1rem;
  position: relative;
  font-size: 0.9rem;
  color: var(--text-secondary);
}

.licensing-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: currentColor;
}

.licensing-why {
  max-width: 70ch;
  margin: 0 auto 2.5rem;
  padding: 1rem 1.15rem;
  border-left: 3px solid var(--accent-primary);
  background: var(--surface-subtle);
  border-radius: 0 8px 8px 0;
  font-size: 0.92rem;
  line-height: 1.65;
}

.tier-grid-heading {
  text-align: center;
  margin: 0 0 1.25rem;
}

/* The tier's headline figure. Tier 1 shows an amount because there is one honest
   amount to show; the others show what the figure is FOR, which is the accurate thing
   to publish before a scope exists. Both use the same block so the three cards do not
   look like two different designs. */
.tier-fee {
  display: grid;
  gap: 0.15rem;
  margin: 0.85rem 0 0.5rem;
  padding: 0.75rem 0.85rem;
  background: var(--surface-subtle);
  border-radius: 10px;
}

.tier-fee-amount {
  font-family: var(--font-heading, inherit);
  font-size: 2.1rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

/* Not an amount, so not sized like one -- a 2.1rem "Upfront setup fee" reads as a
   number at a glance and it is not one. */
.tier-fee-quoted {
  font-size: 1.12rem;
  font-weight: 700;
  letter-spacing: 0;
}

.tier-fee-label { font-size: 0.85rem; color: var(--text-secondary); }

.tier-fee-kind {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent-primary);
}

.tier-position {
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--text-secondary);
  margin: 0.4rem 0 0;
}

.tier-retainer {
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

/* Pinned to the floor so the three CTAs line up regardless of list length, the same
   mechanism the engagement cards used. */
.licensing-tier > .tier-cta { margin-top: auto; }

.tier-cta {
  display: block;
  text-align: center;
  text-decoration: none;
}

/* --- The flow ---------------------------------------------------------- */
.scoping-flow {
  max-width: 900px;
  margin: 2.5rem auto;
  scroll-margin-top: var(--anchor-offset, 140px);
}

.scoping-intro { color: var(--text-secondary); line-height: 1.65; }

.scoping-steps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  list-style: none;
  margin: 1.25rem 0;
  padding: 0 0 0.75rem;
  border-bottom: 1px solid var(--border-color);
  counter-reset: step;
}

.scoping-steps li {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-secondary);
}

/* aria-current="step" is what is announced; this styles whatever carries it, so the
   two cannot disagree. Weight and an underline as well as colour. */
.scoping-steps li[aria-current="step"] {
  color: var(--accent-primary);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

.scoping-steps li[data-step-done="true"]::after {
  content: " ✓";
  color: var(--success);
}

.scoping-group {
  border: 1px solid var(--border-color);
  border-radius: 10px;
  margin: 0 0 1rem;
  padding: 0.9rem 1rem 1rem;
}

.scoping-group legend {
  padding: 0 0.4rem;
  font-size: 0.9rem;
  font-weight: 700;
}

.scoping-group label {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  padding: 0.3rem 0;
  font-size: 0.9rem;
  line-height: 1.5;
  cursor: pointer;
}

/* A 1.05rem control is closer to the 24x24 CSS-pixel target WCAG 2.2 asks for than a
   browser default checkbox, and the whole label is clickable, which is the part that
   actually matters on a phone. */
.scoping-group input[type="checkbox"],
.scoping-group input[type="radio"] {
  width: 1.05rem;
  height: 1.05rem;
  margin: 0.2rem 0 0;
  flex: 0 0 auto;
}

.scoping-warn {
  padding: 0.9rem 1rem;
  border-left: 3px solid var(--warning);
  background: var(--surface-subtle);
  border-radius: 0 8px 8px 0;
  font-size: 0.88rem;
  line-height: 1.6;
}

.scoping-drop {
  border: 2px dashed var(--border-color);
  border-radius: 12px;
  padding: 1.25rem;
  transition: border-color 0.2s ease, background 0.2s ease;
}

.scoping-drop.is-dragover {
  border-color: var(--accent-primary);
  background: var(--surface-subtle);
}

.scoping-drop-label { display: block; font-weight: 700; margin-bottom: 0.5rem; }
.scoping-drop-limits { font-size: 0.82rem; color: var(--text-secondary); margin: 0.5rem 0 0; }

.scoping-file-list {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}

.scoping-file-list li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.6rem;
  background: var(--surface-subtle);
  border-radius: 8px;
  font-size: 0.85rem;
}

.scoping-file-name { overflow-wrap: anywhere; }
.scoping-file-size { color: var(--text-secondary); white-space: nowrap; }

.scoping-verdict:not(:empty) {
  margin: 0 0 1.25rem;
  padding: 0.9rem 1rem;
  border-left: 3px solid var(--accent-primary);
  background: var(--surface-subtle);
  border-radius: 0 8px 8px 0;
  font-size: 0.9rem;
  line-height: 1.6;
}

.scoping-verdict strong { display: block; margin-bottom: 0.2rem; }

.scoping-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 1.4rem;
}

.scoping-error:empty,
.field-error:empty { display: none; }

.scoping-error,
.field-error {
  margin: 0.3rem 0 0;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--danger);
}

.field-hint { margin: 0.3rem 0 0; font-size: 0.82rem; color: var(--text-secondary); }
.req { color: var(--danger); }
.opt { font-weight: 400; color: var(--text-secondary); }

.scoping-notes { margin-top: 1rem; }

.scoping-notes label,
.scoping-flow .inquiry-grid label {
  display: block;
  font-size: 0.88rem;
  font-weight: 600;
  margin-bottom: 0.3rem;
}

.scoping-flow input,
.scoping-flow textarea {
  width: 100%;
  padding: 0.65rem 0.75rem;
  font: inherit;
  font-size: 0.92rem;
  color: inherit;
  background: var(--bg-card, #fff);
  border: 1px solid var(--border-color);
  border-radius: 8px;
}

.scoping-flow textarea { resize: vertical; }

.scoping-flow input:focus-visible,
.scoping-flow textarea:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 1px;
  border-color: var(--accent-primary);
}

.scoping-flow .is-invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 1px var(--danger);
}

.licensing-terms {
  max-width: 80ch;
  margin: 2.5rem auto 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border-color);
  font-size: 0.88rem;
  line-height: 1.7;
  color: var(--text-secondary);
}

.licensing-terms h3 { font-size: 1rem; margin: 0 0 0.75rem; color: var(--text-primary); }
.licensing-terms-links { font-size: 0.85rem; }

@media (max-width: 720px) {
  /* One column: a minimum on the feature lists only leaves dead space. */
  .licensing-list { min-height: 0; }
  .tier-fee-amount { font-size: 1.8rem; }
}

@media (prefers-reduced-motion: reduce) {
  .scoping-drop { transition: none; }
}


/* --- Showcase rows: a real link, not a click handler ------------------- *
 * These rows were divs carrying onclick="window.open(url, '_blank')". Three problems,
 * all of which the anchor below fixes at once:
 *
 *   - A div is not focusable and does not respond to Enter, so the rows could not be
 *     reached or activated from a keyboard at all. Six of the site's outbound links
 *     were mouse-only.
 *   - There was nothing to announce: a screen reader saw a paragraph of text, not a
 *     link, so the rows were invisible to anyone navigating by links.
 *   - window.open(url, '_blank') without noopener hands the opened page a live
 *     window.opener reference back into this one.
 *
 * The stretched pseudo-element keeps the whole row clickable, which is what the click
 * handler was for, while the thing being activated is an ordinary anchor.
 * ---------------------------------------------------------------------- */
.project-row--linked {
  position: relative;
}

.project-row-link {
  color: inherit;
  text-decoration: none;
}

.project-row-link::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Above the row's own background, below anything interactive inside it. */
  z-index: 1;
}

.project-row-link:hover { text-decoration: underline; }

/* The focus ring goes on the row, not on the few words of the title: the row is what
   is being activated, and a ring around the title alone understates the target. */
.project-row--linked:focus-within {
  outline: 3px solid var(--accent-primary);
  outline-offset: 3px;
  border-radius: 12px;
}

/* Anything genuinely interactive inside the row has to sit above the stretched
   pseudo-element or the link swallows its clicks. */
.project-row--linked button,
.project-row--linked a:not(.project-row-link),
.project-row--linked iframe {
  position: relative;
  z-index: 2;
}


/* --- Programmatic focus targets should not draw a ring ----------------- *
 * The global rule above styles [tabindex]:focus-visible with a 3px purple outline.
 * That is right for anything a person can Tab to. It is wrong for an element given
 * tabindex="-1" purely so a script can move focus to it: the reader did not navigate
 * there, so a ring announces a location they are not looking for, and the step
 * heading spans the panel, which is why it read as a purple box drawn around the
 * whole "1. Capability & workflow checklist" container.
 *
 * The focus MOVE is kept -- a screen-reader user otherwise gets no signal that the
 * step changed, since the visual change tells them nothing. Only the ring goes. For a
 * sighted user the panel visibly changing is the feedback, and it is better feedback
 * than an outline.
 *
 * Scoped to headings inside the flow. This deliberately does NOT weaken focus
 * indicators on anything interactive: every input, checkbox, radio, textarea, button
 * and link in this form keeps its ring, which WCAG 2.4.7 requires and which removing
 * would be a regression rather than a tidy-up.
 * ---------------------------------------------------------------------- */
.scoping-step > h4[tabindex="-1"]:focus,
.scoping-step > h4[tabindex="-1"]:focus-visible {
  outline: none;
}

/* Checkboxes and radios sit inside the fieldsets and are the controls a person
   actually operates here, so their indicator is stated explicitly rather than
   inherited -- an outline on a 1.05rem control needs the offset to clear the label
   beside it. */
.scoping-group input[type="checkbox"]:focus-visible,
.scoping-group input[type="radio"]:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 2px;
}

/* The helper link under the estimator intro. */
.scoping-help {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--text-secondary);
}


/* --- Header layout: locked, and intact down to 360px ------------------- *
 * The bar carries four things at the narrowest supported width: the GWiZ brand on the
 * far left, then "Client Portals", "Request Audit" and the burger pinned right. The
 * links and the Google partner badge have already moved into the slide-down panel by
 * then.
 *
 * The rules below are about one failure: a flex row that runs out of room wraps, and a
 * wrapped header doubles in height and pushes the sub-nav and every anchor offset out
 * with it. nowrap prevents the wrap; min-width: 0 is what makes nowrap safe, because a
 * flex item's default min-width: auto refuses to shrink below its content and forces
 * the row wider than the viewport instead -- which is the usual cause of a page that
 * scrolls sideways on a phone.
 * ---------------------------------------------------------------------- */
.site-header .nav-container {
  flex-wrap: nowrap;
  gap: 0.5rem;
  max-width: min(1280px, 100%);
}

.site-header .header-actions {
  flex-wrap: nowrap;
  /* Never the item that gives way: the CTA and the portals menu are the two things
     this bar exists to offer. */
  flex: 0 0 auto;
}

.site-header .logo {
  /* This one may shrink, and it degrades gracefully -- "GWiZ." truncating is survivable
     in a way that a clipped button is not. */
  min-width: 0;
  flex: 0 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.site-header nav {
  min-width: 0;
}

@media (max-width: 420px) {
  .site-header {
    padding: 0.7rem 0.75rem;
  }

  .site-header .nav-container {
    gap: 0.35rem;
  }

  .site-header .header-actions {
    gap: 0.35rem;
  }

  .site-header .btn-header-audit {
    padding: 0.45rem 0.7rem;
    font-size: 0.72rem;
    white-space: nowrap;
  }

  .site-header .portals-toggle {
    padding: 0.45rem 0.5rem;
    font-size: 0.72rem;
    white-space: nowrap;
  }

  .site-header .logo {
    font-size: 1.15rem;
  }
}

/* At the very bottom of the supported range the portals label alone is wider than the
   space left for it. The chevron stays, so the control is still visibly a menu.
   
   The label is clipped rather than removed, which is the distinction that matters: it
   stays in the accessibility tree, so the button's accessible name is still computed
   from that text and a screen reader announces "Client Portals". display: none here
   would leave a button whose only content is a decorative, aria-hidden chevron -- an
   unnamed control, and a WCAG 4.1.2 failure. */
@media (max-width: 360px) {
  .site-header .portals-toggle .portals-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* The last line of defence rather than the fix. Every cause above is addressed
   directly; this stops one overlooked wide child from turning the whole document into
   a sideways-scrolling page, which is a far worse symptom than the element being
   clipped. */
html,
body {
  overflow-x: hidden;
}


/* --- Vendor explainer cards and lightbox -------------------------------- *
 * The cards used to link out to vendor documentation. Sending a prospective client to
 * cloud.google.com mid-page sends them away, and nothing brings them back; a short
 * explainer in place answers the same question without costing the visit. The vendor
 * site stays available as a secondary link, because removing a legitimate reference to
 * make a page tidier is a loss.
 * ---------------------------------------------------------------------- */
.tech-action-wrap {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: auto;
}

.btn-tech-asset {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font: inherit;
  font-size: 0.84rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  background: none;
  border: 1px solid var(--border-color);
  border-radius: 999px;
  padding: 0.45rem 0.9rem;
  color: var(--accent-primary);
}

.btn-tech-asset:hover {
  border-color: var(--accent-primary);
  background: var(--surface-subtle);
}

.btn-tech-asset:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 2px;
}

.tech-play { font-size: 0.72em; }

.tech-vendor-link {
  font-size: 0.78rem;
  color: var(--text-secondary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.vendor-video-dialog {
  width: min(880px, 92vw);
  padding: 0;
  border: 0;
  border-radius: 14px;
  background: var(--bg-card, #fff);
  color: inherit;
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.28);
}

/* The backdrop is the element's own, so it cannot be mis-stacked against the page --
   a dialog opened with showModal() paints in the top layer, above every z-index. */
.vendor-video-dialog::backdrop {
  background: rgba(15, 23, 42, 0.62);
}

.vendor-video-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.15rem 0.75rem;
}

.vendor-video-heading { margin: 0; font-size: 1.02rem; line-height: 1.35; }

/* Whose video this is. These belong to other companies, and a visitor is entitled to
   know whose explanation they are watching rather than assuming it is ours. */
.vendor-video-source {
  margin: 0.2rem 0 0;
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.vendor-video-close {
  flex: 0 0 auto;
  width: 2.2rem;
  height: 2.2rem;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  border: 1px solid var(--border-color);
  border-radius: 50%;
  background: none;
  color: inherit;
}

.vendor-video-close:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 2px;
}

/* 16:9 without the padding-top trick, and without the frame collapsing before the
   iframe is inserted. */
.vendor-video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  background: #000;
}

.vendor-video-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.vendor-video-note {
  margin: 0;
  padding: 0.7rem 1.15rem 1rem;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--text-secondary);
}

@media (max-width: 640px) {
  .tech-action-wrap { gap: 0.5rem; }
  .btn-tech-asset { font-size: 0.8rem; }
}


/* --- Estimator: tier choice, skipped steps, and the deposit ------------- */
.scoping-tier-option {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.55rem 0;
  line-height: 1.55;
  cursor: pointer;
}

.scoping-tier-option strong { display: inline; }

.scoping-note {
  margin: 0.75rem 0 0;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--text-secondary);
}

/* A step this tier does not ask about. Dimmed rather than removed so the progress bar
   keeps a stable shape: a reader who goes back and changes tier sees the step return,
   instead of the bar jumping to a different length under them. line-through carries the
   same meaning without relying on the dimming being noticed. */
.scoping-steps li[data-step-skipped="true"] {
  opacity: 0.45;
  text-decoration: line-through;
}

.scoping-checkout {
  margin-top: 1.5rem;
  padding: 1.15rem 1.25rem;
  border: 1px solid var(--border-color);
  border-left: 3px solid var(--success);
  border-radius: 0 10px 10px 0;
  background: var(--surface-subtle);
}

.scoping-checkout-title { margin: 0 0 0.5rem; font-size: 1rem; }

.scoping-checkout-body {
  margin: 0 0 0.75rem;
  font-size: 0.9rem;
  line-height: 1.65;
}

#scoping-deposit { margin-top: 0.25rem; }


/* --- Integration cards: one button shape, three levels of intent -------- *
 * The six cards carry the same three actions and must look like one component. They
 * did not: .btn-tech-asset was declared in BOTH stylesheets with different values for
 * font-family, colour and margin, so the typography came from style.css and the
 * padding from a11y.css. The rule in style.css is now an empty marker pointing here.
 *
 * The hierarchy is deliberate. A prospective client wants the two-minute explainer; a
 * technical lead evaluating us wants the architecture; neither should have to read the
 * other's link first. So the video is a filled control, and the two text links sit
 * below it at the same weight as each other.
 * ---------------------------------------------------------------------- */
.tech-card {
  /* The action block is pinned to the floor by margin-top: auto below, which needs
     free space to absorb -- and it only has that if the cards are stretched to equal
     height by the grid. */
  height: 100%;
}

/* WHY THIS IS NO LONGER A GRID
 *
 * It was `display: grid; grid-template-columns: 1fr`, with the two secondary links
 * both assigned `grid-row: 2; grid-column: 1` -- the same single cell -- and told
 * apart only by justify-self: start and end. Two items in one cell overlap. They
 * merely looked like a row while the column stayed wide enough that "start" and "end"
 * did not meet; narrow the card, or lengthen either label, and "Architecture notes"
 * and "Vendor site" rendered on top of one another.
 *
 * justify-self cannot create a second column. So the links now have a container of
 * their own that is genuinely two things in a row, and can wrap to two lines when
 * there is not room for one. */
.tech-action-wrap {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  align-items: stretch;
  margin-top: auto;
  padding-top: 1rem;
}

.tech-action-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.35rem 0.75rem;
}

.btn-tech-asset {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.45rem;
  width: 100%;
  /* Identical heights across the six cards even where one label wraps to two lines.
     A fixed height would clip the wrapped one; a minimum equalises the common case and
     still grows for the exception. */
  min-height: 2.6rem;
  padding: 0.5rem 0.9rem;
  font-family: var(--font-heading, inherit);
  font-size: 0.84rem;
  font-weight: 600;
  line-height: 1.3;
  text-align: left;
  cursor: pointer;
  background: none;
  border: 1px solid var(--border-color);
  border-radius: 999px;
  color: var(--accent-primary);
  /* Clears the link row below it. The column gap alone left the pill's border almost
     touching the underlines. */
  margin-bottom: 0.35rem;
  transition: background 0.2s ease, border-color 0.2s ease;
}

.btn-tech-asset:hover {
  border-color: var(--accent-primary);
  background: var(--surface-subtle);
}

.btn-tech-asset:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 2px;
}

.tech-play { flex: 0 0 auto; font-size: 0.72em; }

/* The two secondary links share a row and a weight: one is for a technical lead and
   one is for anybody, and neither outranks the other. */
.tech-action-wrap .tech-arch-link,
.tech-action-wrap .tech-vendor-link {
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-secondary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* No grid placement any more -- the flex row above puts them side by side and wraps
   them when it cannot. min-width: 0 lets a long label shrink and wrap instead of
   forcing the row wider than the card. */
.tech-arch-link,
.tech-vendor-link {
  min-width: 0;
}

.tech-action-wrap .tech-arch-link:hover,
.tech-action-wrap .tech-vendor-link:hover {
  color: var(--accent-primary);
}

.tech-action-wrap .tech-arch-link:focus-visible,
.tech-action-wrap .tech-vendor-link:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 2px;
  border-radius: 3px;
}

/* The 420px stacking rule that used to live here is gone. It reassigned the two links
   to separate grid rows below 420px, and its comment -- "below this the two links would
   collide" -- was the tell: they shared one grid cell at every width, and this only
   moved them apart at the width where the overlap became impossible to miss. The row is
   a flex container now, so flex-wrap drops the second link to its own line exactly when
   there is no room for it, at whatever width that turns out to be. */


/* --- Sub-navigation: retracts on the way down --------------------------- *
 * Only the SECOND bar moves. The primary header stays locked, so the page never loses
 * its navigation entirely and what is reclaimed is one bar's height rather than all of
 * the chrome. On a tablet that is the difference between roughly 120px of permanent
 * furniture and about 64px.
 *
 * translateY is composited: the slide costs no layout and no paint, and nothing below
 * the bar moves. The bar keeps position: sticky rather than becoming fixed, because
 * fixed would take its flow space away and pull the page's first screenful upward
 * underneath the header.
 * ---------------------------------------------------------------------- */
:root.subnav-retracted .site-subnav {
  /* Its own height plus the border, so it clears the viewport edge completely rather
     than leaving a hairline under the header. */
  transform: translateY(calc(-100% - 1px));
}

/* The bar is off-screen, so nothing in it should be reachable by Tab. Without this a
   keyboard user tabs from the header into links they cannot see -- and visibility:
   hidden is what removes them from the tab order; opacity or transform alone does not.
   It is applied only after the slide has finished, so the bar is still visible while
   it moves. */
:root.subnav-retracted .site-subnav {
  visibility: hidden;
  transition: transform 0.3s ease-in-out, visibility 0s linear 0.3s;
}

.site-subnav {
  visibility: visible;
  transition: transform 0.3s ease-in-out, visibility 0s linear 0s;
}

@media (prefers-reduced-motion: reduce) {
  /* Still retracts -- the reclaimed space is the point -- but without the slide. */
  :root.subnav-retracted .site-subnav,
  .site-subnav {
    transition: none;
  }
}


/* --- Pricing intro banner ---------------------------------------------- *
 * The section used to open with a paragraph explaining why there is no per-feature
 * price list, and then three cards of terms. That is a defence before an offer: the
 * first thing a prospective client read was what we will not do. This says what the
 * engagement is, and the explanation keeps its place further down.
 *
 * Composed from the site's existing .badge rather than a new pill, so it cannot drift
 * from the badges elsewhere on the page.
 * ---------------------------------------------------------------------- */
.pricing-intro-banner {
  max-width: 46rem;
  margin: 0 auto 2.5rem;
  padding: 0 1rem;
  text-align: center;
}

/* .badge sets its colour from --accent-cyan, which measures 4.67:1 on the badge's own
   tinted background. That clears AA and not AAA, and at 0.75rem uppercase this is
   small text, so the AAA threshold is 7:1. The accent is darkened here rather than
   site-wide: changing --accent-cyan would move every other use of it at once, which is
   a bigger decision than this banner. */
.badge-accent {
  color: #0c525f;   /* 7.67:1 on the badge fill, 8.41:1 on the page */
  margin-bottom: 1rem;
}

.pricing-title {
  margin: 0 0 0.75rem;
  font-size: clamp(1.6rem, 1.25rem + 1.4vw, 2.1rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
  /* Keeps the two lines close to even instead of leaving one orphaned word. */
  text-wrap: balance;
}

.pricing-description {
  max-width: 40rem;
  margin: 0 auto;
  font-size: 1rem;
  line-height: 1.7;
  color: var(--text-secondary);
  text-wrap: pretty;
}

@media (max-width: 640px) {
  .pricing-intro-banner { margin-bottom: 2rem; text-align: left; }
  .pricing-description { margin-left: 0; }
}


/* --- Navigation drawer -------------------------------------------------- *
 * The burger is present at every width now. The horizontal link bar is gone: it and
 * the mobile panel listed the same destinations, so the header carried two
 * navigations that had to be kept in step by hand.
 *
 * A consequence worth stating: the top-level destinations are now one press further
 * away on a desktop than they were. That is the trade the brief asks for, and it buys
 * a single source of navigation and a header that is the same shape at every size.
 * ---------------------------------------------------------------------- */
.site-header .nav-burger {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  /* 44px, the AAA target size from WCAG 2.5.5. */
  width: 2.75rem;
  height: 2.75rem;
  padding: 0 0.5rem;
  background: none;
  border: 1px solid var(--border-color);
  border-radius: 10px;
  cursor: pointer;
}

.site-header .nav-burger:hover {
  border-color: var(--accent-primary);
  background: var(--surface-subtle);
}

.site-header .nav-burger:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 2px;
}

/* #0f172a on the page: 16.8:1. The bars are the control's only visible content, so
   they are held to the text threshold rather than the 3:1 for non-text -- a burger
   nobody can see is a navigation nobody can find. */
.nav-burger-bar {
  display: block;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: var(--text-primary);
}

/* ABOVE THE INJECTED CHROME, NOT BELOW IT
 *
 * The backdrop and drawer sat at 1100/1101, over the header at 1000 -- which looked
 * right until you remember what a11y.js appends to <body> at runtime: the cookie
 * banner at 1600, and the accessibility panel and its launcher at 1500. All three
 * outranked the drawer, so an open menu had them floating on top of it.
 *
 * They were not clickable while it was open -- the drawer marks every sibling of
 * <body> inert -- which made it worse rather than better: controls painted over a
 * modal panel that do not respond. The drawer now sits above them and below the skip
 * link at 2000, which must stay reachable.
 */
.nav-drawer-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1700;
  background: rgba(15, 23, 42, 0.45);
}

.nav-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 1701;
  width: min(22rem, 88vw);
  padding: 1.25rem;
  overflow-y: auto;
  /* -webkit-overflow-scrolling keeps momentum on iOS, where a panel that stops dead
     reads as a frozen page. */
  -webkit-overflow-scrolling: touch;
  background: var(--bg-card, #fff);
  border-left: 1px solid var(--border-color);
  box-shadow: -12px 0 32px rgba(15, 23, 42, 0.12);
}

/* Above the header (1000) and the sub-nav (40), below the skip link (2000) and the
   modals (10000) -- the same ordering the header itself was corrected to. */

.nav-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
  padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--border-color);
}

.nav-drawer__title { margin: 0; font-size: 1.05rem; }

.nav-drawer__close {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  background: none;
  border: 1px solid var(--border-color);
  border-radius: 50%;
  /* 16.8:1 on the card. */
  color: var(--text-primary);
}

.nav-drawer__close:hover {
  border-color: var(--accent-primary);
  background: var(--surface-subtle);
}

.nav-drawer__close:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 2px;
}

.nav-drawer__links {
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
  display: grid;
  gap: 0.15rem;
}

.nav-drawer__links a {
  display: block;
  /* Comfortably past the 44px target, and the whole row is the target rather than the
     few characters of the word. */
  padding: 0.7rem 0.75rem;
  border-radius: 8px;
  font-weight: 600;
  text-decoration: none;
  color: var(--text-primary);
}

.nav-drawer__links a:hover { background: var(--surface-subtle); }

.nav-drawer__links a:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: -2px;
}

/* The page you are on. Carried by weight and a rule as well as colour, so it is not
   signalled by hue alone. */
.nav-drawer__links a[aria-current="page"] {
  color: var(--accent-primary);
  box-shadow: inset 3px 0 0 var(--accent-primary);
}

.nav-drawer__links--secondary a { font-weight: 500; font-size: 0.94rem; }

.nav-drawer__heading {
  margin: 0 0 0.4rem;
  padding: 0 0.75rem;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-secondary);
}

.nav-drawer__partner {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0.5rem 0.75rem 0;
  font-size: 0.85rem;
  color: var(--text-secondary);
  text-decoration: none;
}

.nav-drawer__partner:hover { color: var(--accent-primary); text-decoration: underline; }

.nav-drawer__partner:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 2px;
  border-radius: 4px;
}

/* The header keeps its shape at every width, so there is no breakpoint at which the
   layout jumps -- the bar contains the same two controls from 360px to 2560px. */
@media (max-width: 420px) {
  .nav-drawer { width: 100vw; border-left: 0; }
}

@media (prefers-reduced-motion: no-preference) {
  .nav-drawer { transition: transform 0.22s ease-out; }
  /* Animated from the attribute rather than a class, so the transition cannot get out
     of step with whether the element is actually hidden. */
  .nav-drawer[hidden] { transform: translateX(100%); }
}

/* ----------------------------------------------------------------------
 * The drawer's action block.
 *
 * Pinned to the foot of the panel, after the destinations. Someone who opened the
 * menu to do something rather than to browse should not have to read past eight
 * links to find the way in, and the two things here that start something -- the
 * implementation request and the portal sign-ins -- were previously indistinguishable
 * from the navigation above them.
 *
 * margin-top: auto does the pinning, so the block sits at the bottom on a tall
 * viewport and simply follows the links on a short one rather than being pushed off
 * the end. That needs the drawer itself to be a column flex container; it already is.
 * ---------------------------------------------------------------------- */
.nav-drawer__cta {
  margin-top: auto;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border-subtle);
}

.nav-drawer__cta-primary {
  display: block;
  margin-bottom: 1.25rem;
  padding: 0.85rem 1rem;
  border-radius: 10px;
  background: var(--accent-primary);
  /* Not a hardcoded white: the accent flips to a pale blue in high-contrast mode,
     where white on it is 1.68:1. --accent-on flips with it. */
  color: var(--accent-on);
  font-weight: 700;
  font-size: 0.95rem;
  text-align: center;
  text-decoration: none;
}

.nav-drawer__cta-primary:hover {
  filter: brightness(1.08);
}

.nav-drawer__cta-primary:focus-visible {
  outline: 3px solid var(--accent-primary);
  outline-offset: 3px;
}

/* The portal list keeps its own spacing rather than the 1.25rem the link groups above
   carry, because it is the last thing in the panel and has nothing to separate from. */
.nav-drawer__cta .nav-drawer__links {
  margin-bottom: 0;
}

/* ----------------------------------------------------------------------
 * Four integration cards, and the viewport band that orphans one.
 *
 * .tech-grid is repeat(auto-fit, minmax(250px, 1fr)) with a 1.5rem gap, which fits
 * four tracks at 1180px and up and two at 834px and below. Between roughly 850px and
 * 1150px it fits exactly three -- so the four cards render 3 + 1, with the last one
 * alone on a row two thirds empty. That band covers iPad landscape and most small
 * laptops.
 *
 * Forcing two columns through that band gives a 2x2 instead. The sequence across
 * viewports becomes 4 -> 2x2 -> 1, and the orphan never appears.
 * ---------------------------------------------------------------------- */
@media (min-width: 700px) and (max-width: 1150px) {
  .tech-grid {
    /* minmax(0, 1fr), not 1fr: a grid item's min-width defaults to auto, and a long
       unbroken word in a card title would otherwise widen its track past the card. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
