/* ===============================================================
   KOI BRIDGE — styles.css
   Single stylesheet, organised into bands:
     1. TOKENS
     2. RESET
     3. PRIMITIVES
     4. COMPONENTS   (later tasks)
     5. SECTIONS     (later tasks)
     6. RESPONSIVE   (empty by convention — see band 6's own header)
     7. MOTION
   Add new rules to the band they belong to, EXCEPT media queries:
   a media query lives with the component or section it modifies (in
   band 3, 4 or 5), not in band 6. Band 6 stays an empty marker of
   that convention. Do not scatter otherwise.
   =============================================================== */


/* ===============================================================
   1. TOKENS
   Deep ink with a blue undertone. Indigo carries structure,
   koi coral carries action — the two accents echo a koi carp
   without any literal imagery.
   =============================================================== */

:root {
  /* --- Surfaces (deep ink, blue undertone) --- */
  --c-bg:            #070b14;
  --c-surface:       #0d1424;
  --c-surface-2:     #131c31;
  --c-border:        rgba(148, 176, 255, 0.12);
  --c-border-strong: rgba(148, 176, 255, 0.24);

  /* --- Text ---
     A three-step ramp. Every step clears WCAG AA (4.5:1) for normal-size
     text on every surface token, so any of the three is safe for body
     copy anywhere on the site — no exceptions to remember:

                        --c-bg    --c-surface   --c-surface-2
       --c-text         17.55:1     16.39:1        15.13:1   AAA
       --c-text-muted    8.25:1      7.70:1         7.11:1   AAA
       --c-text-dim      5.27:1      4.92:1         4.54:1   AA

     Pick by hierarchy, not by contrast: --c-text for content,
     --c-text-muted for supporting copy, --c-text-dim for captions,
     meta and labels. */
  --c-text:          #eef2fb;
  --c-text-muted:    #9aa8c7;
  --c-text-dim:      #7884a2;

  /* --- Accents: indigo (structure) + koi coral (action) --- */
  --c-indigo:        #6b7cff;
  --c-indigo-soft:   #a3adff;
  --c-koi:           #ff6b3d;
  --c-koi-soft:      #ff9a72;

  /* --- Type --- */
  --f-display: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
  --f-body:    "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Fluid scale - clamp(min, preferred, max) */
  --t-xs:   0.8125rem;
  --t-sm:   0.9375rem;
  --t-base: 1.0625rem;
  --t-lg:   clamp(1.125rem, 0.4vw + 1.05rem, 1.3125rem);
  --t-xl:   clamp(1.375rem, 1vw + 1.1rem, 1.75rem);
  --t-2xl:  clamp(1.75rem, 2vw + 1.2rem, 2.5rem);
  --t-3xl:  clamp(2.25rem, 3.4vw + 1.2rem, 3.75rem);
  --t-4xl:  clamp(2.75rem, 5vw + 1.1rem, 5rem);

  /* --- Space (4px base) --- */
  --s-1: 0.25rem;  --s-2: 0.5rem;   --s-3: 0.75rem;
  --s-4: 1rem;     --s-6: 1.5rem;   --s-8: 2rem;
  --s-12: 3rem;    --s-16: 4rem;    --s-24: 6rem;
  --s-32: 8rem;

  /* --- Shape & depth --- */
  --r-sm: 8px;  --r-md: 14px;  --r-lg: 22px;  --r-full: 999px;
  --shadow-card: 0 1px 2px rgba(0,0,0,.4), 0 12px 32px -12px rgba(0,0,0,.6);
  --shadow-glow: 0 0 60px -12px rgba(107, 124, 255, 0.45);

  /* --- Layout --- */
  --w-page: 1180px;
  --w-prose: 68ch;

  /* --- Motion --- */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur:  260ms;
}


/* ===============================================================
   2. RESET
   =============================================================== */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchors on a dark page: keep the native highlight subtle. */
  color-scheme: dark;
  /* Sideways-scroll guard. `clip` rather than `hidden`: hidden would
     also clip any descendant that scrolls its own overflow and,
     more importantly, would suppress the containing-block behaviour
     `position: sticky` depends on, silently breaking the sticky
     header. `clip` prevents the scrollbar without doing either. */
  overflow-x: clip;
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd, fieldset {
  margin: 0;
}

body {
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--f-body);
  font-size: var(--t-base);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

/* Headings default to the display face. The .h-* primitives below
   set the sizes that matter; this only keeps unclassed headings
   (card titles, list headings) inside the same voice. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--f-display);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

p, li { text-wrap: pretty; }

img, svg, video, canvas {
  display: block;
  max-width: 100%;
}

img, video { height: auto; }

/* Colour is inherited so links pick up their context. Underlines are
   left to the component: navigation and buttons carry none, prose
   links must set their own so they stay distinguishable. */
a {
  color: inherit;
  text-decoration: none;
}

/* Lists that carry a class are structural, not prose. */
ul[class], ol[class] {
  list-style: none;
  padding: 0;
}

input, button, textarea, select {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
}

button { cursor: pointer; }

textarea { resize: vertical; }

fieldset {
  border: 0;
  padding: 0;
}

table {
  border-collapse: collapse;
  width: 100%;
}

hr {
  border: 0;
  border-top: 1px solid var(--c-border);
}

::selection {
  background: var(--c-koi);
  color: var(--c-bg);
}

/* The accessibility contract for every interactive element on the
   site. Koi on ink measures 6.96:1, well past the 3:1 that WCAG
   2.2 asks of a focus indicator. Never remove this. */
:where(a, button, input, textarea, select, [tabindex]):focus-visible {
  outline: 2px solid var(--c-koi);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}


/* ===============================================================
   3. PRIMITIVES
   Layout, type and the two or three shapes the whole site is
   assembled from. Everything here is used by name elsewhere.
   =============================================================== */

/* --- Layout ---------------------------------------------------- */

.container {
  width: min(100% - 2.5rem, var(--w-page));
  margin-inline: auto;
}

.section {
  padding-block: clamp(var(--s-16), 9vw, var(--s-32));
}

.section--tight {
  padding-block: clamp(var(--s-12), 5vw, var(--s-16));
}

/* --- Type ------------------------------------------------------ */

/* The section mark. A short koi rule set on the baseline of the
   label — a current-line, the point where a new section starts.
   The wide tracking here is the deliberate counterweight to the
   tight tracking on .h-display. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-family: var(--f-display);
  font-size: var(--t-xs);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-koi);
}

.eyebrow::before {
  content: "";
  flex: none;
  width: 1.75rem;
  height: 2px;
  border-radius: var(--r-full);
  background: currentColor;
}

.h-display {
  font-family: var(--f-display);
  font-size: var(--t-3xl);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.h-section {
  font-family: var(--f-display);
  font-size: var(--t-2xl);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.lede {
  font-size: var(--t-lg);
  line-height: 1.6;
  color: var(--c-text-muted);
  max-width: var(--w-prose);
}

/* --- Grid ------------------------------------------------------
   auto-fit + minmax, so every grid reflows on its own and the
   responsive band stays close to empty. min() keeps the track from
   overflowing a viewport narrower than the minimum. */

.grid {
  display: grid;
  gap: var(--s-6);
}

.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }

/* --- Card ------------------------------------------------------
   Lit from above: a 1px inset highlight on the top edge does the
   work a gradient border would, at a fraction of the noise. */

.card {
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  padding: var(--s-8);
  box-shadow: inset 0 1px 0 rgba(207, 222, 255, 0.06), var(--shadow-card);
}

/* --- Buttons ---------------------------------------------------
   Pills. The primary sets ink on koi (6.96:1) rather than white on
   koi (2.83:1, which fails AA) — and reads hotter for it. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-6);
  border: 1px solid transparent;
  border-radius: var(--r-full);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: 0.01em;
  text-align: center;
  text-decoration: none;
  background: transparent;
  color: var(--c-text);
  cursor: pointer;
  transition:
    transform var(--dur) var(--ease),
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--primary {
  background: var(--c-koi);
  color: var(--c-bg);
  border-color: var(--c-koi);
}

.btn--primary:hover {
  background: var(--c-koi-soft);
  border-color: var(--c-koi-soft);
}

.btn--ghost {
  background: transparent;
  color: var(--c-text);
  border-color: var(--c-border-strong);
}

.btn--ghost:hover {
  background: var(--c-surface-2);
  border-color: var(--c-indigo);
}

.btn--lg {
  padding: var(--s-4) var(--s-8);
  font-size: var(--t-base);
}

/* --- Accessibility helpers ------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: var(--s-2);
  left: var(--s-2);
  z-index: 100;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-sm);
  background: var(--c-koi);
  color: var(--c-bg);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 500;
  transform: translateY(-200%);
  transition: transform var(--dur) var(--ease);
}

.skip-link:focus,
.skip-link:focus-visible {
  transform: translateY(0);
}


/* ===============================================================
   4. COMPONENTS
   Header, footer, nav, forms, badges. Added in later tasks.
   =============================================================== */

/* --- Logo ------------------------------------------------------
   A "KB" monogram: the K's two arms reach across a gap and land on
   the B's stem — a span anchored on a pier, which is the only
   bridge reference the brand needs. Ink knocked out of an
   indigo→koi tile, the same figure/ground logic as .btn--primary
   (ink on koi, never white on koi).

   Hex values are duplicated as SVG presentation attributes so the
   mark still renders if the stylesheet fails; the classes below
   are what actually paints it, keeping the tokens authoritative. */

.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  flex: none;
  color: var(--c-text);
}

.logo__mark {
  flex: none;
  width: 2.375rem;
  height: 2.375rem;
  transition: filter var(--dur) var(--ease);
}

.logo__stop--indigo { stop-color: var(--c-indigo); }
.logo__stop--koi    { stop-color: var(--c-koi); }
.logo__glyph        { stroke: var(--c-bg); }

.logo:hover .logo__mark {
  filter: drop-shadow(0 0 14px rgba(107, 124, 255, 0.5));
}

.logo__word {
  font-family: var(--f-display);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

/* --- Header ----------------------------------------------------
   Transparent at rest so the hero runs to the top of the viewport;
   .site-header--scrolled (added by JS) drops the ink bar in. The
   transparent border at rest reserves the 1px the scrolled state
   adds, so nothing shifts when the class lands. */

/* Consequence of the sticky header: anchored sections must not
   land underneath it. Both are gated on `js` because without it the
   header carries the whole nav in the flow and is too tall to pin. */
html.js { scroll-padding-top: 5.5rem; }

.site-header {
  z-index: 50;
  border-bottom: 1px solid transparent;
  transition:
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.js .site-header {
  position: sticky;
  top: 0;
}

.site-header--scrolled {
  background: rgba(7, 11, 20, 0.82);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--c-border);
}

.site-header__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
  padding-block: var(--s-4);
}

.site-header__cta {
  flex: none;
  margin-inline-start: auto;
  padding: var(--s-2) var(--s-4);
  font-size: var(--t-xs);
}

/* --- Main navigation -------------------------------------------
   Two mobile presentations. The base one assumes no JavaScript:
   .site-nav wraps onto its own line under the logo, in the flow,
   always visible, and no toggle is rendered — a control that cannot
   work should not exist. Once JavaScript announces itself by
   putting `js` on <html>, the same element becomes the dropdown
   panel that .nav-toggle opens. */

.site-nav {
  order: 3;
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-4);
  margin-inline-start: calc(var(--s-3) * -1);
  padding-block: 0 var(--s-1);
}

.js .site-nav {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  order: 0;
  flex-basis: auto;
  display: none;
  flex-direction: column;
  flex-wrap: nowrap;
  gap: var(--s-1);
  margin-inline-start: 0;
  padding: var(--s-4) var(--s-6) var(--s-6);
  background: var(--c-surface);
  border-bottom: 1px solid var(--c-border);
  box-shadow: var(--shadow-card);
}

.js .site-nav--open { display: flex; }

.site-nav a {
  position: relative;
  display: block;
  padding: var(--s-3);
  border-radius: var(--r-sm);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--c-text-muted);
  transition:
    color var(--dur) var(--ease),
    background-color var(--dur) var(--ease);
}

.site-nav a:hover {
  color: var(--c-text);
  background: var(--c-surface-2);
}

/* --- Nav toggle -------------------------------------------------
   Hidden until JavaScript exists to make it work. The open state is
   driven by aria-expanded, not a second class, so the visual and
   the accessibility tree can never disagree. */

.nav-toggle {
  display: none;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--c-text);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.js .nav-toggle { display: inline-flex; }

.nav-toggle:hover {
  background: var(--c-surface-2);
  border-color: var(--c-indigo);
}

.nav-toggle__bars {
  position: relative;
  display: block;
  width: 1.25rem;
  height: 2px;
  border-radius: var(--r-full);
  background: currentColor;
  transition: background-color var(--dur) var(--ease);
}

.nav-toggle__bars::before,
.nav-toggle__bars::after {
  content: "";
  position: absolute;
  left: 0;
  width: 1.25rem;
  height: 2px;
  border-radius: var(--r-full);
  background: currentColor;
  transition: transform var(--dur) var(--ease);
}

.nav-toggle__bars::before { top: -6px; }
.nav-toggle__bars::after  { top: 6px; }

.nav-toggle[aria-expanded="true"] .nav-toggle__bars {
  background: transparent;
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars::before {
  transform: translateY(6px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars::after {
  transform: translateY(-6px) rotate(-45deg);
}

/* Narrow phones: the wordmark goes, the mark stays. Clipped rather
   than display:none so the logo link keeps its accessible name. */
@media (max-width: 26rem) {
  .logo__word {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

/* Desktop: the panel becomes a row between the logo and the CTA,
   and the toggle disappears. The koi rule under a hovered link is
   the same device .eyebrow uses to mark where something starts. */
@media (min-width: 56.25rem) {
  .site-header__inner { gap: var(--s-8); }

  /* Both selectors, so the rule outranks the `.js` panel block
     above whether or not JavaScript is running. */
  .site-nav,
  .js .site-nav {
    position: static;
    order: 0;
    flex-basis: auto;
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--s-6);
    margin-inline: auto;
    padding: 0;
    background: none;
    border: 0;
    box-shadow: none;
  }

  .site-nav a {
    padding: var(--s-2) 0;
    border-radius: 0;
  }

  .site-nav a:hover { background: none; }

  .site-nav a::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 0;
    height: 2px;
    border-radius: var(--r-full);
    background: var(--c-koi);
    transition: width var(--dur) var(--ease);
  }

  .site-nav a:hover::after,
  .site-nav a:focus-visible::after { width: 100%; }

  .site-header__cta {
    margin-inline-start: 0;
    padding: var(--s-3) var(--s-6);
    font-size: var(--t-sm);
  }

  .nav-toggle,
  .js .nav-toggle { display: none; }
}

/* --- Footer -----------------------------------------------------
   Four columns on desktop, one on a phone. auto-fit does the
   in-between widths; the explicit track list only kicks in once
   there is room for all four. */

.site-footer {
  border-top: 1px solid var(--c-border);
  padding-block: clamp(var(--s-12), 6vw, var(--s-16)) var(--s-8);
}

.footer__grid {
  gap: var(--s-8) var(--s-6);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
}

.footer__pitch {
  margin-top: var(--s-4);
  max-width: 30ch;
  font-size: var(--t-sm);
  color: var(--c-text-muted);
}

.footer__contact {
  margin: var(--s-6) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  font-size: var(--t-sm);
}

.footer__contact a {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--c-text-muted);
  transition: color var(--dur) var(--ease);
}

.footer__contact a:hover { color: var(--c-koi-soft); }

.footer__contact svg { flex: none; }

.footer__heading {
  margin-bottom: var(--s-4);
  font-family: var(--f-display);
  font-size: var(--t-xs);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--c-text-dim);
}

.footer__links {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  font-size: var(--t-sm);
}

.footer__links a {
  color: var(--c-text-muted);
  transition: color var(--dur) var(--ease);
}

.footer__links a:hover { color: var(--c-text); }

.footer__legal {
  font-style: normal;
  font-size: var(--t-sm);
  line-height: 1.9;
  color: var(--c-text-muted);
}

.footer__bottom {
  margin-top: clamp(var(--s-12), 5vw, var(--s-16));
  padding-top: var(--s-6);
  border-top: 1px solid var(--c-border);
  font-size: var(--t-xs);
  color: var(--c-text-dim);
}

@media (min-width: 56.25rem) {
  .footer__grid { grid-template-columns: 1.6fr 1fr 1fr 1.2fr; }
}


/* ===============================================================
   5. SECTIONS
   Hero, services, products, process, about, contact. Added in
   later tasks.
   =============================================================== */

/* --- Hero -------------------------------------------------------
   One column of copy and one decorative figure. The figure is
   `position: absolute` at every width, so it contributes no layout
   height anywhere — on a phone it simply sits behind the copy.

   Top padding is lighter than .section's because the header sits
   above it in the flow and already supplies the air; the bottom
   keeps the full section rhythm. Same specificity as .section, so
   this only wins by living later in the file — keep the bands in
   order. */

.hero {
  position: relative;
  overflow: hidden; /* the mobile figure is wider than the page */
  padding-block:
    clamp(var(--s-12), 7vw, var(--s-24))
    clamp(var(--s-16), 9vw, var(--s-32));
}

.hero__inner { position: relative; }

/* The figure follows the copy in the DOM, so without an explicit
   z-index it would paint on top of it — tinting the glyphs as well
   as the background, which is not the compositing the contrast
   figures below assume. The copy is stacked above; the figure stays
   strictly behind. */
.hero__copy {
  position: relative;
  z-index: 1;
}

/* The one place --t-4xl is used. The scale has a step above
   .h-display precisely for the page's opening statement; the
   primitive itself is untouched. */
.hero__title {
  margin-top: var(--s-6);
  font-size: var(--t-4xl);
}

.hero__lede { margin-top: var(--s-6); }

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  margin-top: var(--s-8);
}

/* Small print under the buttons: --c-text-dim is 5.27:1 on --c-bg,
   and 4.57:1 over the brightest colour the figure can put behind it
   at its 10% mobile opacity. Both clear AA. */
.hero__reassure {
  margin-top: var(--s-4);
  font-size: var(--t-sm);
  color: var(--c-text-dim);
}

/* --- Hero figure ------------------------------------------------
   Mobile first: centred behind the copy, oversized, and knocked
   back to 10%. That number is not taste — it is the ceiling at
   which every text token on this page still clears WCAG AA over the
   figure's brightest stroke (--c-indigo-soft #a3adff):

     over #a3adff at 10% on --c-bg  →  composite rgb(23, 27, 44)
       --c-text        15.23:1
       --c-text-muted   7.16:1
       --c-text-dim     4.57:1
       --c-koi          6.04:1   (the eyebrow)

   0.11 already puts --c-text-dim at 4.49:1, under AA. Don't. */

.hero__visual {
  position: absolute;
  z-index: 0;
  top: 50%;
  left: 50%;
  width: min(120%, 40rem);
  transform: translate(-50%, -50%);
  opacity: 0.10;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
}

/* The glow. A CSS radial pair — indigo above the deck for
   structure, a low koi wash over the water — inside a box-shadow
   halo. Inset from the figure's edges so the 60px blur stays clear
   of the text column on desktop. */
.hero__visual::before {
  content: "";
  position: absolute;
  inset: 6% 8% 14%;
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 34%, rgba(107, 124, 255, 0.30), transparent 62%),
    radial-gradient(ellipse 70% 42% at 50% 84%, rgba(255, 107, 61, 0.16), transparent 72%);
  box-shadow: var(--shadow-glow);
}

.bridge {
  position: relative;
  width: 100%;
  height: auto;
}

/* Tokens are authoritative; the presentation attributes in the
   markup are only there so the figure still reads if the stylesheet
   fails. Same contract as .logo. */
.bridge__deck,
.bridge__arch,
.bridge__piers { stroke: var(--c-indigo); }

.bridge__hangers { stroke: var(--c-indigo-soft); }

.bridge__water { stroke: var(--c-koi); }

/* One wavelength is 80 user units and each path runs from -80 to
   560, so a -80px drift lands on an identical crest: the loop is
   seamless. Keyframes live in band 7. */
.bridge__wave {
  animation: kb-wave-drift 18s linear infinite;
}

.bridge__wave--2 {
  animation-duration: 27s;
  animation-direction: reverse;
}

.bridge__wave--3 { animation-duration: 36s; }

/* Desktop: the figure moves out of the copy and into its own
   column at full strength. The grid reserves that column even
   though it is empty — fr tracks are sized from free space, not
   content — so the copy can never run under the arch, where light
   text on solid --c-indigo would only reach 3.15:1. */
@media (min-width: 56.25rem) {
  .hero__inner {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.78fr);
    gap: var(--s-12);
  }

  .hero__visual {
    top: 50%;
    left: auto;
    right: 0;
    width: min(42%, 30rem);
    transform: translateY(-50%);
    opacity: 1;
  }
}

/* --- Trust strip ------------------------------------------------
   Three verified facts, hairline-separated: stacked on a phone,
   three columns from 900px. Bordered top and bottom so it reads as
   a plinth under the hero rather than a fourth CTA. */

.trust {
  background: var(--c-surface);
  border-block: 1px solid var(--c-border);
}

.trust__list {
  margin: 0;
  display: grid;
}

.trust__item {
  padding-block: var(--s-4);
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--c-text-muted); /* 7.70:1 on --c-surface */
}

.trust__item + .trust__item {
  border-top: 1px solid var(--c-border);
}

@media (min-width: 56.25rem) {
  .trust__list { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  .trust__item {
    padding-block: var(--s-6);
    padding-inline-end: var(--s-8);
  }

  .trust__item + .trust__item {
    border-top: 0;
    border-inline-start: 1px solid var(--c-border);
    padding-inline-start: var(--s-8);
  }
}

/* --- Services -----------------------------------------------------
   Four `.card`s in the shared `.grid--4`. Everything but the hover
   lift and the icon/title/text rhythm inside the card — background,
   border, radius, padding, shadow — comes from `.card` itself; this
   only adds to it, never restates it. */

.services__title {
  margin-top: var(--s-6);
}

.services__grid {
  margin-top: clamp(var(--s-8), 4vw, var(--s-12));
}

.services__card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  transition:
    transform var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.services__card:hover {
  transform: translateY(-4px);
  border-color: var(--c-border-strong);
}

.services__icon {
  margin-bottom: var(--s-4);
  color: var(--c-indigo);
}

.services__card-title {
  margin-bottom: var(--s-3);
  font-size: var(--t-lg);
}

/* --- Method ---------------------------------------------------------
   Four steps, numbered rather than carded — the ghosted digit carries
   the sequence so the eye reads a path, not four interchangeable
   tiles. `-webkit-text-stroke` only paints in Chromium/Safari; the
   plain solid `color` set first is what every other engine (Firefox
   included) actually renders, so the number is never invisible —
   only less "ghosted", solid instead of outlined, where the property
   is unsupported. */

.method__title {
  margin-top: var(--s-6);
}

.method__grid {
  position: relative;
  margin-top: clamp(var(--s-8), 4vw, var(--s-12));
}

.method__step {
  position: relative;
  z-index: 1;
}

.method__number {
  display: block;
  margin-bottom: var(--s-4);
  font-family: var(--f-display);
  font-weight: 700;
  line-height: 1;
  font-size: clamp(2.25rem, 3vw + 1.5rem, 3.25rem);
  color: var(--c-border-strong);
}

@supports (-webkit-text-stroke: 1px black) {
  .method__number {
    color: transparent;
    -webkit-text-stroke: 1px var(--c-border-strong);
  }
}

.method__step-title {
  margin-bottom: var(--s-3);
  font-size: var(--t-lg);
}

/* The rule only draws once the grid actually renders four columns
   (see the site's other 56.25rem/900px breakpoints); below that the
   steps stack and a horizontal line has nothing left to connect.
   z-index 0 keeps it behind the steps (each lifted to z-index 1
   above), so it reads as a line running through the row rather than
   over the ghosted numbers. */
@media (min-width: 56.25rem) {
  .method__grid::before {
    content: "";
    position: absolute;
    z-index: 0;
    top: 1.6rem;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--c-border);
  }
}

/* --- Why Koi Bridge ---------------------------------------------
   The site's central argument, so it gets a heavier frame than a
   .card: full container width, a --c-surface-2 fill and a koi rule
   down the left edge. The left corners are squared so that rule
   reads as a spine rather than a rounded-off accent stripe.

   Body copy composes `.lede`, which sets --c-text-muted — 7.11:1 on
   --c-surface-2, see the token table up in band 1. */

.why__panel {
  position: relative;
  padding: clamp(var(--s-8), 5vw, var(--s-12)) clamp(var(--s-8), 6vw, var(--s-16));
  background: var(--c-surface-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  border-left: 4px solid var(--c-koi);
}

.why__title {
  margin-top: var(--s-4);
}

.why__body {
  margin-top: var(--s-6);
}

/* Horizontal on wide screens, stacked on a phone — auto-fit would
   also work here, but a plain flex row keeps three items from ever
   collapsing to a lonely single column the way a grid track can. */
.why__points {
  margin: 0;
  margin-top: var(--s-8);
  padding-top: var(--s-8);
  border-top: 1px solid var(--c-border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-6);
}

.why__point {
  flex: 1 1 14rem;
  padding-left: var(--s-4);
  border-left: 2px solid var(--c-border-strong);
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--c-text-muted); /* 7.11:1 on --c-surface-2 */
}

.why__cta {
  margin-top: var(--s-8);
  font-size: var(--t-sm);
}

/* A prose link needs its own underline per the reset's contract —
   koi on --c-surface-2 measures 5.99:1, koi-soft 8.17:1 on hover. */
.why__cta a {
  color: var(--c-koi);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  transition: color var(--dur) var(--ease);
}

.why__cta a:hover { color: var(--c-koi-soft); }

/* --- Products -----------------------------------------------------
   Two `.card`s in the shared `.grid--2`. Background, border, radius,
   padding and shadow all come from `.card`; this only adds the
   internal rhythm and pins the link row to the bottom of the card
   so both cards' actions line up even if the copy runs to different
   lengths. */

.products__title {
  margin-top: var(--s-4);
}

.products__lede {
  margin-top: var(--s-4);
}

.products__grid {
  margin-top: clamp(var(--s-8), 4vw, var(--s-12));
}

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

.product-card__name {
  font-size: var(--t-xl);
}

.product-card__tagline {
  margin-top: var(--s-2);
  font-size: var(--t-sm);
  color: var(--c-text-dim); /* 4.92:1 on --c-surface */
}

.product-card__desc {
  margin-top: var(--s-4);
  font-size: var(--t-sm);
  color: var(--c-text-muted); /* 7.70:1 on --c-surface */
}

.product-card__list {
  margin: var(--s-6) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  font-size: var(--t-sm);
  color: var(--c-text-muted);
}

.product-card__list li {
  position: relative;
  padding-left: var(--s-4);
}

.product-card__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 6px;
  height: 6px;
  border-radius: var(--r-full);
  background: var(--c-koi);
}

.product-card__actions {
  margin-top: auto;
  padding-top: var(--s-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4) var(--s-6);
}

.product-card__actions a {
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--c-text);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  transition: color var(--dur) var(--ease);
}

.product-card__actions a:hover { color: var(--c-koi-soft); }

/* --- Tech stack ---------------------------------------------------
   A `.section--tight` band of plain text pills — deliberately not
   `.btn`: these are inert labels, not actions, so they carry no
   hover or focus treatment of their own. */

.stack__title {
  font-size: var(--t-xl);
}

.stack__list {
  margin: var(--s-6) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

.stack__pill {
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-full);
  background: var(--c-surface);
  font-family: var(--f-display);
  font-size: var(--t-xs);
  font-weight: 500;
  color: var(--c-text-muted); /* 7.70:1 on --c-surface */
}


/* --- FAQ ------------------------------------------------------------
   Native <details>/<summary>, no JavaScript. Each summary contains a
   single <h3> (see the banner comment in index.php on why), so the
   chevron below is drawn with a pseudo-element rather than a real
   SVG — a second child would break that content model. */

.faq__title {
  margin-top: var(--s-6);
}

.faq__list {
  margin-top: clamp(var(--s-8), 4vw, var(--s-12));
  max-width: var(--w-prose);
}

.faq__item {
  border-bottom: 1px solid var(--c-border);
}

.faq__item:first-child {
  border-top: 1px solid var(--c-border);
}

.faq__question {
  position: relative;
  display: block;
  padding: var(--s-6) var(--s-12) var(--s-6) 0;
  cursor: pointer;
  list-style: none; /* Firefox's native disclosure marker */
}

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

.faq__question-text {
  font-size: var(--t-base);
  font-weight: 600;
}

/* A rotating chevron, drawn from two borders rather than an SVG —
   see the band comment above for why. [open] lives on the <details>,
   not the <summary>, so the rule targets the ancestor. */
.faq__question::after {
  content: "";
  position: absolute;
  top: 50%;
  right: var(--s-2);
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--c-text-muted);
  border-bottom: 2px solid var(--c-text-muted);
  transform: translateY(-70%) rotate(45deg);
  transition: transform var(--dur) var(--ease);
}

.faq__item[open] .faq__question::after {
  transform: translateY(-30%) rotate(-135deg);
}

/* <summary> is natively focusable but is not one of the elements the
   reset's global focus-ring selector names (band 2), so it needs its
   own rule here — same tokens, same ring, just scoped. */
.faq__question:focus-visible {
  outline: 2px solid var(--c-koi);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

.faq__answer {
  padding-bottom: var(--s-6);
  padding-right: var(--s-12);
}

/* --- Contact ----------------------------------------------------
   Two columns: the form (wider) and a direct-contact `.card` on the
   right, stacked on a phone. `.card` supplies background, border,
   radius, padding and shadow; this only adds the ratio and the
   internal rhythm. Field text and placeholder colours reuse the
   --c-surface pairs already computed in the band 1 token table
   (--c-text 16.39:1, --c-text-dim 4.92:1) rather than restating
   them. */

.contact__inner {
  display: grid;
  gap: var(--s-12);
}

.contact__title {
  margin-top: var(--s-4);
}

.contact__lede {
  margin-top: var(--s-6);
}

.form {
  margin-top: var(--s-8);
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}

/* Hidden by offset, never display:none — some bots specifically
   check for that. tabindex="-1" on the input inside (see markup)
   removes it from the tab order, which is what makes aria-hidden on
   this wrapper safe rather than an accessibility smell. */
.hp {
  position: absolute;
  left: -9999px;
}

.form__row {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.form__label {
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--c-text-muted); /* 8.25:1 on --c-bg */
}

.form__optional {
  font-weight: 400;
  color: var(--c-text-dim); /* 5.27:1 on --c-bg */
}

.form__input {
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-sm);
  background: var(--c-surface);
  color: var(--c-text); /* 16.39:1 on --c-surface */
  transition: border-color var(--dur) var(--ease);
}

.form__input::placeholder {
  color: var(--c-text-dim); /* 4.92:1 on --c-surface */
}

.form__input:focus-visible {
  border-color: var(--c-indigo);
}

textarea.form__input { resize: vertical; }

/* The live region the handler writes into. Empty until then, so it
   takes no vertical space and announces nothing. */
.form__status {
  margin: 0;
  font-size: var(--t-sm);
  color: var(--c-text-muted); /* 8.25:1 on --c-bg */
}

/* Colour is a second signal only: the message itself always says
   what happened, so neither state depends on seeing the hue. */
.form__status--error {
  color: var(--c-koi); /* 6.97:1 on --c-bg */
}

.form__status--success {
  color: var(--c-indigo-soft); /* 9.36:1 on --c-bg */
}

.contact__card-title {
  font-size: var(--t-lg);
}

.contact__list {
  margin: var(--s-4) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  font-size: var(--t-sm);
}

/* Koi on --c-surface measures 6.50:1 (same method as the reset's
   "koi on ink" and the hero's koi-on-figure comments — koi on
   --c-surface-2 is already logged at 5.99:1 by .why__cta a). */
.contact__list a {
  color: var(--c-koi);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  transition: color var(--dur) var(--ease);
}

.contact__list a:hover { color: var(--c-koi-soft); }

.contact__address {
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid var(--c-border);
  font-style: normal;
  font-size: var(--t-sm);
  line-height: 1.7;
  color: var(--c-text-muted); /* 7.70:1 on --c-surface */
}

.contact__note {
  margin-top: var(--s-4);
  font-size: var(--t-sm);
  color: var(--c-text-dim); /* 4.92:1 on --c-surface */
}

@media (min-width: 56.25rem) {
  .contact__inner {
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
    align-items: start;
  }
}

/* --- Product pages ------------------------------------------------
   Shared `.product-*` vocabulary for /prestaseo/ and /clickboost/
   (Tasks 9 and 10) — one template, two products, so nothing here may
   name either product. Everything composes an existing primitive
   (.section, .card, .btn, .grid) or an already-generic component
   class from elsewhere in this band (.hero__actions, .product-card__
   list/actions from the home page's Products section); this block
   only adds what those do not already cover. */

/* Hero: a breadcrumb, an eyebrow, the product name as the page's
   only h1, a tagline, a lede paragraph, then the shared
   `.hero__actions` row. No card, no figure — simpler than the home
   page's hero by design. */
.product-hero__back {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-6);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--c-text-muted); /* 8.25:1 on --c-bg */
  transition: color var(--dur) var(--ease);
}

.product-hero__back:hover { color: var(--c-koi); }

.product-hero__title { margin-top: var(--s-4); }

/* The one-line tagline sits above the longer lede, set in the
   display face so it reads as a strapline rather than body copy. */
.product-hero__tagline {
  margin-top: var(--s-4);
  font-family: var(--f-display);
  font-size: var(--t-lg);
  font-weight: 500;
  color: var(--c-text-muted); /* 8.25:1 on --c-bg */
}

.product-hero__lede { margin-top: var(--s-6); }

/* Generic spacing hooks reused by every section below the hero, so
   a section only ever needs an .eyebrow + this title class + this
   body class — no per-section title/grid rule to duplicate. */
.product-section__title { margin-top: var(--s-6); }

.product-section__body { margin-top: clamp(var(--s-8), 4vw, var(--s-12)); }

/* --- Key benefits: three plain items, no card, each set off by a
   top rule so they still read as distinct blocks stacked on a
   phone. --- */
.product-benefit {
  padding-top: var(--s-6);
  border-top: 2px solid var(--c-border-strong);
}

.product-benefit__title {
  margin-bottom: var(--s-2);
  font-size: var(--t-lg);
}

/* --- Feature grid: six `.card`s in the shared `.grid--3`, same
   icon/title/text rhythm as the home page's `.services__card` —
   composed here under its own name because the product pages reuse
   that rhythm, not because the shape differs. --- */
.product-feature {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  transition:
    transform var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.product-feature:hover {
  transform: translateY(-4px);
  border-color: var(--c-border-strong);
}

.product-feature__icon {
  margin-bottom: var(--s-4);
  color: var(--c-indigo);
}

.product-feature__title {
  margin-bottom: var(--s-3);
  font-size: var(--t-lg);
}

.product-feature__desc {
  font-size: var(--t-sm);
  color: var(--c-text-muted); /* 7.70:1 on --c-surface */
}

/* --- Screenshot placeholder: a bordered, dashed 16:9 panel. Zero
   raster assets — this is the whole "screenshot" until real
   marketing assets exist. --- */
.product-shot {
  aspect-ratio: 16 / 9;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-8);
  background: var(--c-surface);
  border: 2px dashed var(--c-border-strong);
  border-radius: var(--r-lg);
}

.product-shot__label {
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 500;
  text-align: center;
  color: var(--c-text-dim); /* 4.92:1 on --c-surface */
}

/* --- Pricing: three `.card` tiers in the shared `.grid--3`. Bullets
   and the CTA row reuse `.product-card__list` / `.product-card__
   actions` verbatim rather than restating them under a new name. --- */
.product-tier {
  position: relative;
  display: flex;
  flex-direction: column;
}

/* Overrides only `.card`'s border colour; the inset highlight and
   `--shadow-card` it also sets are left alone. */
.product-tier--featured {
  border: 2px solid var(--c-koi);
}

/* Ink on koi, same pairing as `.btn--primary` — 6.96:1. */
.product-tier__badge {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: var(--s-1) var(--s-4);
  border-radius: var(--r-full);
  background: var(--c-koi);
  color: var(--c-bg);
  font-family: var(--f-display);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.product-tier__name { font-size: var(--t-lg); }

.product-tier__price {
  margin-top: var(--s-4);
  font-family: var(--f-display);
  font-size: var(--t-2xl);
  font-weight: 700;
}

.product-tier__price-suffix {
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--c-text-muted); /* 7.70:1 on --c-surface */
}

/* --- Closing CTA band: a bordered plinth, same idea as the home
   page's `.trust` strip, centred rather than in columns. --- */
.product-cta {
  background: var(--c-surface);
  border-block: 1px solid var(--c-border);
  text-align: center;
}

.product-cta__inner {
  max-width: var(--w-prose);
  margin-inline: auto;
}

/* `.hero__actions` is a flex row, so the section's own text-align
   does not reach it — centred explicitly instead. */
.product-cta__inner .hero__actions { justify-content: center; }


/* ===============================================================
   6. RESPONSIVE
   Deliberately empty. Every media query on the site lives inside
   band 3, 4 or 5, directly alongside the rule it adjusts, rather
   than being collected here — co-locating a breakpoint with the
   component it changes makes it far less likely someone edits one
   and misses the other. This band is kept as a marker of that
   convention, not a home for new rules: do not add media queries
   here, and do not move the existing ones out of their components
   to "fill" it.
   =============================================================== */


/* ===============================================================
   7. MOTION
   Every animation on the site must be switchable off from here.
   =============================================================== */

/* The hero water. Deliberately written with a `to` keyframe only
   and no animation-fill-mode: the element's base style carries no
   transform, so when the rule below collapses the duration to
   0.01ms and the single iteration ends, the wave falls back to
   translateX(0) — its drawn position — instead of being frozen at
   the -80px end state. A `forwards` fill here would leave the
   figure permanently offset for reduced-motion users. */
@keyframes kb-wave-drift {
  to { transform: translateX(-80px); }
}

/* Scroll reveal. Sections marked [data-reveal] in the markup start
   shifted and transparent; assets/js/main.js adds .is-visible to each
   one as it scrolls into view.

   Scoped to `html.reveal`, which main.js's reveal block adds as its
   own first statement — NOT to `html.js`, which header.php sets
   synchronously during parse. The distinction is the whole safety
   argument: `js` only promises that JavaScript is enabled, so a
   deferred main.js that fails to fetch, is blocked by an extension,
   or throws in an earlier block would leave the class set and the
   sections hidden with nothing left to un-hide them. `reveal` is
   written by the same code that removes it, so the hidden state
   cannot outlive its own undo.

   Two further guards: the hero carries no [data-reveal] at all, so
   the first viewport renders unconditionally, and the reduced-motion
   block below cancels the hidden state outright in CSS, independent
   of the script. */
.reveal [data-reveal] {
  opacity: 0;
  transform: translateY(1rem);
  transition:
    opacity 600ms var(--ease),
    transform 600ms var(--ease);
}

.reveal [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .btn:hover,
  .btn:active { transform: none; }

  /* Not a shortened animation — no hidden state at all. The blanket
     rule above only collapses the transition; without this the
     sections would still start at opacity 0 and wait on the script. */
  .reveal [data-reveal] {
    opacity: 1;
    transform: none;
  }
}
