/* ═══════════════════════════════════════════════════════════════════════
   AI-enablement prototype — shared stylesheet.

   Design system lifted from P4 "Cinematic" (v04) rather than re-invented:
   same tokens, same type scale, same light-page/dark-stage rule. What is
   added here is what a multi-page site needs and a single-page direction
   did not — a mobile drawer, status pills, a tool list, and an article
   layout.

   Brand is two tokens. Swapping --brand-name and --brand-domain changes
   every page; nothing else hardcodes either.
   ═══════════════════════════════════════════════════════════════════════ */

:root{
  --brand-name:"Kaimā";
  --brand-domain:"www.experiencekaima.ai";

  --surface:#FFFFFF;
  --off:#F4F6F8;
  --ink:#0E1418;
  --ink-soft:#37424B;
  --muted:#5A6670;
  --hair:#E1E6EA;
  --abyss:#04070E;

  --deep:#0B3A4A;
  --teal:#12697F;
  --cyan-lift:#5FD8F2;
  --gold:#8A6212;
  --gold-lift:#F0C469;

  --fs-meta:14px;
  --fs-base:18px;
  --fs-lead:clamp(19px,1.4vw,23px);
  --fs-h2:clamp(30px,3.4vw,52px);
  --fs-h1:clamp(38px,5.2vw,84px);

  --serif:ui-serif,Georgia,"Iowan Old Style","Palatino Linotype",Palatino,"Times New Roman",serif;
  --sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;

  --rail:clamp(18px,3.6vw,72px);
  --ease:cubic-bezier(.22,.61,.36,1);

  /* ── elevation ────────────────────────────────────────────────────────
     Two tokens, one behaviour, used by every card on the site: a card sits
     off the page at rest and lifts under the pointer. Defined here rather
     than per-component so the whole page cannot drift into some boxes
     having a shadow and others not — which is the thing that reads as
     ad-hoc rather than designed.

     Two layers each, and both are needed. The tight, near-opaque layer is
     the contact edge that makes the card look like it is ON something; the
     wide, heavily-negative-spread layer is the cast shadow that gives it
     height. One layer alone reads either as a border or as fog.

     Tinted with the ink colour rather than pure black: a neutral-black
     shadow on a warm-white page goes grey and dirty. */
  --rest:0 1px 2px rgba(14,20,24,.05), 0 6px 16px -10px rgba(14,20,24,.20);
  --raise:0 2px 6px rgba(14,20,24,.07), 0 22px 44px -18px rgba(14,20,24,.30);
  --hair-strong:#CBD5DB;
  --card-r:8px;
}

/* Every card lifts the same distance, at the same speed, with the same
   easing. Applied via a shared rule rather than repeated per component so
   "consistent down the page" is enforced by the stylesheet instead of by
   whoever edits it next. `will-change` is deliberately absent: six promoted
   layers on the tool grid cost more than the animation is worth.

   ── the lift uses `translate`, not `transform`, and it has to ───────────
   Every card on this site also carries .rv, the reveal-on-scroll class,
   which ends at `.rv.in{transform:none}` near the foot of this file. That
   is TWO classes — exactly the specificity of `.tool:hover` — so at equal
   weight the later rule wins and the hover lift silently did nothing. The
   shadow deepened, the card stayed put, and nothing anywhere reported it:
   the auditor never hovers, and a screenshot at rest looks identical.

   Raising the hover to `.tool.tool:hover` would have won the tie and left
   the collision in place for whoever adds the next card. `translate` is a
   separate property from `transform`, so the reveal keeps `transform` to
   itself and the two stop competing at all. They compose cleanly if they
   ever do overlap. Baseline since 2022; where it is unsupported the card
   simply does not rise, which is a degradation and not a break. */
.tool,.num,.who,.route,.row,.aside{
  transition:translate .28s var(--ease),box-shadow .28s var(--ease),
             border-color .28s var(--ease),background .28s var(--ease)}
/* The reduced-motion opt-out for these lifts is NOT here. It lives in the
   existing prefers-reduced-motion block at the foot of this file, because a
   media query carries no extra specificity — `.tool:hover` inside one still
   weighs 0,2,0, so placed above `.tool:hover` it would lose the tie on source
   order and do exactly nothing. It was written here first and was silently
   dead. Keep every override below what it overrides. */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{background:var(--surface);color:var(--ink);font-family:var(--sans);
  font-size:var(--fs-base);line-height:1.6;-webkit-font-smoothing:antialiased;overflow-x:hidden}
.wrap{width:100%;padding-inline:var(--rail)}
:focus-visible{outline:2.5px solid var(--teal);outline-offset:3px;border-radius:5px}
img{max-width:100%;height:auto}

/* ═════════ nav ═════════ */
.bar{
  position:sticky;top:0;z-index:60;background:var(--surface);
  border-bottom:1px solid var(--hair);
  display:flex;align-items:center;gap:clamp(12px,2vw,30px);padding:0 var(--rail);
  transition:background .3s var(--ease),border-color .3s var(--ease),box-shadow .3s var(--ease);
}

/* ── the bar over the stage ──────────────────────────────────────────────
   On a page that opens with the stage, the bar is not a band above the
   picture — it sits ON it, so the smoke drifts behind the words. Fixed
   rather than sticky, because sticky keeps its space in the flow and the
   stage would still begin below it.

   Only pages that HAVE a stage get this: .has-stage is on <body>, set in the
   markup rather than by script, so there is no flash of a dark-on-white bar
   before JS runs. ai-for-business.html has no stage and keeps the solid bar.

   .solid is added by script once the stage has scrolled past. Below that
   point there is no image behind the bar, and white-on-white is not a
   readable nav. */
.has-stage .bar{position:fixed;left:0;right:0;top:0;background:transparent;border-bottom-color:transparent}

/* The copy has to clear the bar, and a fixed height cannot guarantee that:
   .stage.short is about 374px, and on platform.html and services.html the
   heading block is tall enough that a bottom-anchored copy started ABOVE the
   bar and ran under the nav. Raising the height would have fixed those two
   pages at that one viewport width and broken again at the next.

   So on stage pages the copy stops being absolutely positioned and becomes
   the flex child of a bottom-aligned column with the bar's height as padding.
   The stage keeps its height as a MINIMUM and grows if the copy needs more
   room, which means no width, no font size and no amount of copy can put a
   heading back under the nav. */
/* Same full-view height as a MINIMUM, so the stage still grows when the
   copy needs more room than the viewport has — which is the whole reason
   this block exists and is why the height goes to auto here. A short
   landscape window therefore scrolls rather than clipping the headline. */
.has-stage .stage{display:flex;flex-direction:column;justify-content:flex-end;
  height:auto;min-height:100vh;min-height:100svh;padding-top:86px}
/* Short stages are a FIXED height, not a minimum — every inner page gets
   the same header. As a minimum they grew to fit their copy, so the header
   was a different height on every page: about and contact sat at the floor
   while platform ran 38px taller and services 70px, purely because their
   hero sentences are longer. Clicking between pages, the header jumped.
   About and contact were the ones already at the intended size, so that
   size is what the rest now take. */
/* height for equality, min-height:fit-content as the floor that stops it
   ever overlapping the nav.

   The previous version set min-height:0 and shipped the exact bug the old
   note warned about. 52vh on a SHORT window is a small number — at 700px
   tall it is 364px — and the copy does not shrink to match, so it
   overflowed upward through the padding and the eyebrow landed on top of
   the brand lockup. It was verified across four WIDTHS and no short
   HEIGHT, which is the one axis that decides this.

   fit-content resolves the two demands instead of trading them: the box
   takes the clamp when the copy fits inside it, so every inner header is
   the same height at any normal window, and grows to the copy when it
   does not, so the overlap is impossible rather than merely unlikely. */
.has-stage .stage.short{height:clamp(360px,52vh,600px);min-height:fit-content}
/* On a desktop window the floor is 470px — about and contact's own height
   at a normal viewport, which is the size that was asked for.
   Without it the headers are only equal on TALL windows: 52vh of an 810px
   window is 421px, which about and contact fit and platform does not, so
   platform grew to 468 and the jump was back. The floor is just above the
   tallest inner copy, so every page takes it and none has to grow.
   Not applied below 1100px: on a phone, or a short laptop window in that
   range, a 470px header is most of the screen, and there equality matters
   less than the copy having somewhere to go. */
@media (min-width:1100px){
  .has-stage .stage.short{height:max(470px,min(52vh,600px))}
}
.has-stage .stage-copy{position:relative;z-index:3;left:auto;right:auto;bottom:auto;
  padding-bottom:clamp(34px,5.4vh,80px)}

/* The platform-fleet illustration used to sit here, absolutely positioned
   in the empty right half of this header. It has moved down the page into
   the "why we build our own" section — see .why-split below. In the header
   it could only ever be a backdrop behind the headline; in a column of its
   own it is the subject, at a size where the racks and the crews are
   actually legible. */
.has-stage .bar.solid{background:var(--surface);border-bottom-color:var(--hair);
  box-shadow:0 12px 34px -28px rgba(0,0,0,.6)}

.has-stage .bar:not(.solid) .brand{color:#FFF}
.has-stage .bar:not(.solid) .brand-s{color:rgba(255,255,255,.74)}
.has-stage .bar:not(.solid) .barlinks a,
.has-stage .bar:not(.solid) .bar-right a{color:rgba(255,255,255,.84)}
.has-stage .bar:not(.solid) .barlinks a[aria-current],
.has-stage .bar:not(.solid) .bar-right a:hover{color:var(--cyan-lift)}
/* the hovered word goes WHITE, not cyan: the plume behind it is already cyan,
   and cyan on cyan is the one combination that loses the word */
.has-stage .bar:not(.solid) .barlinks a:hover{color:#FFF;
  text-shadow:0 0 3px rgba(4,7,14,.95),0 1px 4px rgba(4,7,14,.9),0 0 22px rgba(95,216,242,.7)}
.has-stage .bar:not(.solid) .barlinks a[aria-current]{border-bottom-color:var(--cyan-lift)}
/* the wordmark tile is a dark square — on the stage that is a hole, not a mark */
.has-stage .bar:not(.solid) .brandmark{background:transparent;border-color:rgba(255,255,255,.24)}
.has-stage .bar:not(.solid) .brandmark rect{fill:rgba(255,255,255,.10)}
.has-stage .bar:not(.solid) .burger{border-color:rgba(255,255,255,.44);background:rgba(4,7,14,.44)}
.has-stage .bar:not(.solid) .burger i{background:#FFF}
/* the words sit on moving material, so they carry their own ground */
.has-stage .bar:not(.solid) .brand,
.has-stage .bar:not(.solid) .barlinks a,
.has-stage .bar:not(.solid) .bar-right a{text-shadow:0 1px 14px rgba(4,7,14,.92),0 1px 3px rgba(4,7,14,.8)}
/* the plume is behind the glass, so the word must not also gain a box */
.has-stage .bar:not(.solid) .barlinks a{position:relative}
.brand{display:flex;align-items:center;gap:12px;padding:14px 0;margin-right:clamp(14px,2.4vw,44px);
  text-decoration:none;color:var(--ink)}
.brandmark{width:34px;height:34px;border-radius:9px;flex:none;background:var(--abyss);
  border:1px solid rgba(20,24,29,.12)}
.brand-n{font-family:var(--serif);font-size:20px;font-weight:700;letter-spacing:-.02em;line-height:1;
  display:block}
.brand-n::after{content:var(--brand-name)}
.brand-s{font-size:var(--fs-meta);font-weight:600;color:var(--muted);line-height:1;margin-top:5px;display:block}
.barlinks{display:flex;gap:clamp(8px,1.1vw,20px);list-style:none;align-items:stretch}
.barlinks a{
  font-size:15px;color:var(--ink-soft);text-decoration:none;
  display:inline-flex;align-items:center;min-height:52px;border-bottom:3px solid transparent;
  transition:color .25s var(--ease),border-color .25s var(--ease)}
.barlinks a:hover{color:var(--teal)}
.barlinks a[aria-current]{color:var(--teal);border-bottom-color:var(--teal)}
.bar-right{margin-left:auto;display:flex;gap:14px;align-items:center}
.bar-right a{font-size:var(--fs-meta);color:var(--ink-soft);text-decoration:none;min-height:44px;
  display:inline-flex;align-items:center}
.bar-right a:hover{color:var(--teal);text-decoration:underline}

/* the drawer — P4 v04 added one; a nav that simply vanishes under 1080px
   is a dead end on the width most visitors arrive at */
.burger{display:none;margin-left:auto;width:46px;height:46px;min-height:46px;border:1px solid var(--hair);
  background:var(--surface);border-radius:4px;cursor:pointer;color:var(--ink);
  align-items:center;justify-content:center;gap:4px;flex-direction:column}
.burger i{display:block;width:20px;height:2px;background:var(--ink);transition:transform .25s var(--ease),opacity .2s}
.burger[aria-expanded="true"] i:nth-child(1){transform:translateY(6px) rotate(45deg)}
.burger[aria-expanded="true"] i:nth-child(2){opacity:0}
.burger[aria-expanded="true"] i:nth-child(3){transform:translateY(-6px) rotate(-45deg)}
.scrim{position:fixed;inset:0;background:rgba(4,7,14,.5);z-index:70;opacity:0;pointer-events:none;
  transition:opacity .3s var(--ease)}
.scrim.on{opacity:1;pointer-events:auto}
.drawer{
  position:fixed;top:0;right:0;bottom:0;width:min(88vw,380px);z-index:80;background:var(--surface);
  transform:translateX(102%);transition:transform .34s var(--ease);
  padding:22px;overflow-y:auto;box-shadow:-24px 0 60px -30px rgba(0,0,0,.5)}
.drawer.on{transform:none}
.drawer h2{font-family:var(--serif);font-size:20px;color:var(--deep);margin-bottom:6px}
.drawer a{display:block;padding:15px 4px;min-height:44px;font-size:17px;color:var(--ink-soft);
  text-decoration:none;border-bottom:1px solid var(--hair)}
.drawer a:hover,.drawer a[aria-current]{color:var(--teal)}
.drawer .dclose{width:46px;height:46px;border:1px solid var(--hair);background:var(--surface);
  border-radius:4px;cursor:pointer;font-size:20px;color:var(--ink);margin-bottom:14px}
@media (max-width:1080px){
  .barlinks{display:none}
  .bar-right{display:none}
  .burger{display:flex}
}

/* ═════════ the stage ═════════ */
/* The home stage fills the first view exactly.
   It was clamp(540px,88vh,1040px), and both ends of that were visible as a
   defect: 88vh always left a 12vh strip of the next section showing under
   the fold, and the 1040px ceiling put the same strip back on any window
   taller than that regardless of the vh.

   svh, not vh or dvh. The ask is that the INITIAL view is the whole page,
   and on a phone the initial view is the one with the browser chrome still
   showing — that is the small viewport. dvh would fill more and then
   resize the hero as the chrome hides during scroll; vh ignores the chrome
   and overflows behind it. The plain vh line above stays as the fallback
   for engines that do not know svh, where it is the closest honest guess.

   Only index.html carries a bare .stage — every other page uses
   .stage.short, which is untouched below. */
.stage{position:relative;height:100vh;height:100svh;
  background:var(--abyss);overflow:hidden}
.stage.short{height:clamp(360px,52vh,600px)}
#gl{position:absolute;inset:0;width:100%;height:100%;display:block}
.stage-fallback{position:absolute;inset:0;
  background:
    radial-gradient(1200px 700px at 30% 40%, #14607A 0%, transparent 62%),
    radial-gradient(900px 600px at 74% 66%, #0A2F52 0%, transparent 60%),
    linear-gradient(160deg,#04070E 0%,#071726 52%,#04070E 100%)}
.stage::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:linear-gradient(180deg,rgba(4,7,14,.34) 0%,transparent 26%,transparent 46%,rgba(4,7,14,.86) 100%)}
.stage-copy{position:absolute;z-index:3;left:0;right:0;bottom:clamp(34px,5.4vh,80px);padding-inline:var(--rail)}
.eyebrow{font-size:var(--fs-meta);font-weight:700;letter-spacing:.13em;text-transform:uppercase;
  color:var(--cyan-lift);margin-bottom:18px}
h1{font-family:var(--serif);font-size:var(--fs-h1);line-height:1.02;letter-spacing:-.03em;
  font-weight:700;color:#FFF;max-width:17ch;text-wrap:balance}
/* Inner pages take the smaller headline, and this is what MAKES the fixed
   short-stage height above safe rather than merely tidy.
   The rule it replaced kept a minimum height precisely because "a fixed
   height cannot guarantee the copy clears the bar" — measured, that was
   right: at 1887px the fixed box clipped platform by 51px, because an
   84px headline of any real length wraps to three lines and no header of
   this height can hold that. Equal headers therefore require a headline
   that fits one, so the root cause is fixed rather than the symptom.
   --fs-h1 stays on the home page, where the stage is the full viewport and
   has the room. It is also the ordinary hierarchy: the landing headline is
   the largest type on the site, an inner page's heading is not.
   Placed below the rule it overrides, per the note at the top of this file. */
/* 22ch here first, inherited from the home headline's 17ch, and it forced
   platform's 60-character heading onto three lines — which pushed the
   eyebrow up behind the transparent nav. A wider measure is the fix rather
   than a smaller type size: at 30ch the same heading sets in two lines and
   the whole block drops clear. The narrow measure exists to stop a SHORT
   headline running the full width of a wide screen, and 30ch still does
   that. */
.stage.short h1{font-size:clamp(30px,3.6vw,54px);max-width:30ch}
.stage-sub{font-size:var(--fs-lead);color:rgba(255,255,255,.86);max-width:56ch;margin-top:20px;text-wrap:pretty}
.stage-cta{display:flex;flex-wrap:wrap;gap:14px;align-items:center;margin-top:30px}
.btn{font-size:16px;font-weight:600;text-decoration:none;border-radius:2px;
  padding:15px 30px;min-height:52px;display:inline-flex;align-items:center;gap:10px;
  transition:transform .3s var(--ease),background .3s var(--ease),box-shadow .3s var(--ease)}
.btn-solid{background:#FFF;color:var(--ink)}
.btn-solid:hover{transform:translateY(-2px);box-shadow:0 16px 34px -18px rgba(0,0,0,.9)}
.btn-line{color:#FFF;border:1px solid rgba(255,255,255,.5)}
.btn-line:hover{background:rgba(255,255,255,.1);transform:translateY(-2px)}
.btn-ink{background:var(--deep);color:#FFF}
.btn-ink:hover{transform:translateY(-2px);box-shadow:0 16px 34px -20px rgba(11,58,74,.9)}
.btn-ghost{color:var(--deep);border:1px solid var(--hair)}
.btn-ghost:hover{background:var(--off);transform:translateY(-2px)}
.btn .ar{transition:transform .3s var(--ease)}
.btn:hover .ar{transform:translateX(4px)}
.stage-note{font-size:var(--fs-meta);color:rgba(255,255,255,.72);flex-basis:100%}
/* Below the bar on stage pages, not level with it: the bar now sits ON the
   stage, and at the old top offset this circle landed on top of the
   Assessment link. */
.has-stage .motion-toggle{top:clamp(84px,calc(68px + 3vh),112px)}
.motion-toggle{
  position:absolute;z-index:5;right:var(--rail);top:clamp(20px,3vh,34px);
  width:46px;height:46px;border-radius:50%;cursor:pointer;
  background:rgba(4,7,14,.62);border:1px solid rgba(255,255,255,.48);
  color:#FFF;display:grid;place-items:center;font-size:14px;
  backdrop-filter:blur(8px);transition:background .25s var(--ease)}
.motion-toggle:hover{background:rgba(4,7,14,.86)}

/* ═════════ sections ═════════ */
section{padding-block:clamp(52px,6.5vw,104px)}
h2.sec{font-family:var(--serif);font-size:var(--fs-h2);line-height:1.06;letter-spacing:-.028em;
  font-weight:700;color:var(--deep);max-width:22ch;text-wrap:balance}
.sec-note{font-size:var(--fs-lead);color:var(--ink-soft);max-width:66ch;margin-top:18px;text-wrap:pretty}
.sec-note+.sec-note{margin-top:14px;font-size:var(--fs-base)}
.band{background:var(--off)}
.dark{background:var(--abyss);color:#FFF}
.dark h2.sec{color:#FFF}
.dark .sec-note{color:rgba(255,255,255,.78)}

/* ═════════ the platform fleet ════════════════════════════════════════
   Copy on the left, the fleet on the right, in the half of the section
   that was empty. The art is decorative — aria-hidden — so it says
   nothing the copy does not already say; it is there because a page
   claiming six systems in production should show something running.

   Two columns, not a background image: as a background it would be
   behind the text at some width and cropped at every other one. As a
   column it has a box of its own, and the single-column fallback below
   is then just the two blocks stacking. */
.why-split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.04fr);
  gap:clamp(30px,3.6vw,68px);align-items:center}
.why-copy h2.sec{max-width:19ch}
.why-copy .sec-note{max-width:54ch}
@media (max-width:1000px){
  .why-split{grid-template-columns:minmax(0,1fr);gap:clamp(30px,5vw,48px)}
  .why-copy .sec-note{max-width:66ch}
}

.fleet{position:relative;isolation:isolate;border-radius:20px;overflow:hidden;
  background:var(--abyss);box-shadow:0 44px 90px -62px rgba(4,7,14,.9)}
.fleet-art{position:relative;z-index:3;display:block;width:100%;height:auto}

/* The sky behind it — the hero's palette and the hero's slow smoke, in
   CSS. Deliberately NOT a second WebGL canvas: the shader in site.js is
   wired to one canvas and one set of controls (quality governor, pause
   button, pointer aim), so a second live context would mean either
   duplicating that machinery or splitting it, and a mid-page decoration
   does not earn either. This echoes the hero; it does not clone it. */
.fleet-sky{position:absolute;inset:0;z-index:1;
  background:
    radial-gradient(120% 92% at 28% 20%, #14607A 0%, transparent 58%),
    radial-gradient(112% 82% at 78% 76%, #0A2F52 0%, transparent 56%),
    linear-gradient(160deg,#04070E 0%,#071726 54%,#04070E 100%)}
.fleet-sky::before,.fleet-sky::after{content:"";position:absolute;
  border-radius:50%;filter:blur(48px);opacity:.5}
.fleet-sky::before{left:-14%;top:-20%;width:68%;height:92%;
  background:radial-gradient(circle,rgba(23,114,145,.9),transparent 68%)}
.fleet-sky::after{right:-16%;bottom:-24%;width:74%;height:96%;
  background:radial-gradient(circle,rgba(14,64,102,.95),transparent 66%)}

/* A discharge. Straight, thin, bright core, wide glow — which is what the
   shader's bolts read as at this size once they are through the smoke. */
.fb{position:absolute;z-index:2;width:3px;border-radius:2px;opacity:0;
  background:linear-gradient(180deg,rgba(223,246,255,0) 0%,#DFF6FF 18%,
    var(--cyan-lift) 64%,rgba(95,216,242,0) 100%);
  box-shadow:0 0 22px 7px rgba(95,216,242,.6),0 0 62px 20px rgba(95,216,242,.26)}
.fb1{left:19%;top:-6%;height:56%;rotate:7deg}
.fb2{right:24%;top:-10%;height:64%;rotate:-6deg}

@media (prefers-reduced-motion:no-preference){
  .fleet-sky::before{animation:fleet-drift 26s ease-in-out infinite}
  .fleet-sky::after{animation:fleet-drift 34s ease-in-out infinite reverse}
  .fb1{animation:fb-fire 11s linear infinite}
  .fb2{animation:fb-fire 13.7s linear infinite -4.2s}
}
/* The fleet itself no longer animates — owner's call, so the picture can
   carry the detail rather than the motion. The blinking beacons, the
   tether flow and the window swell are gone from here AND their hooks are
   gone from the markup; a class with no rule left behind is how a
   stylesheet starts lying about what it does.

   The sky behind it still moves. That was asked for separately ("some of
   the top animation effect behind") and is a different thing: atmosphere
   behind the art, not the art itself. */
@keyframes fleet-drift{0%,100%{translate:0 0}50%{translate:6% -4%}}
/* Two strikes per cycle, at uneven offsets, so it never reads as a loop. */
@keyframes fb-fire{
  0%,100%{opacity:0}
  1.4%{opacity:.9} 2.4%{opacity:.14} 3.2%{opacity:.72} 4.6%{opacity:0}
  46%{opacity:0} 47.3%{opacity:.62} 48.1%{opacity:.1} 49%{opacity:.44} 50.6%{opacity:0}
}

/* ruled row list — P4's register, reused rather than re-cut as cards */
.rows{margin-top:clamp(30px,3.6vw,54px);border-top:1px solid var(--hair)}
.row{
  display:grid;gap:clamp(14px,2vw,40px);align-items:start;
  grid-template-columns:88px minmax(0,1.05fr) minmax(0,1.35fr);
  padding:clamp(22px,2.6vw,34px) 0;border-bottom:1px solid var(--hair);
  border-radius:var(--card-r);text-decoration:none;color:inherit;position:relative}
/* The row's own `transition:background` used to live here and, being later in
   the file at equal specificity, silently beat the shared elevation rule at
   the top — so the row alone would have changed colour without lifting.
   Removed rather than extended: one transition, declared once.

   Hover goes to --surface, not --off: these rows sit on white on the homepage
   and on the grey band on services, and a lifted card has to be lighter than
   its ground on both. The rule under a lifted row is hidden, because a card
   in the air does not keep a line ruled across its foot.

   The row keeps zero side padding, so the lifted card spans exactly the
   content width and its "01" still lines up with the section heading. Padding
   it and pulling the list back out by the same amount was the alternative and
   was worse: the row would then escape .wrap, which is a real defect the
   auditor checks for, to buy nothing the eye can see. */
.row:hover{background:var(--surface);translate:0 -3px;box-shadow:var(--raise);
  border-bottom-color:transparent;z-index:2}
.row-n{font-family:var(--serif);font-size:26px;color:var(--gold);line-height:1}
.row-t{font-size:clamp(20px,1.6vw,25px);font-weight:600;letter-spacing:-.015em;color:var(--deep)}
.row-d{font-size:16.5px;line-height:1.6;color:var(--ink-soft);max-width:76ch}
.row-go{font-size:var(--fs-meta);font-weight:700;color:var(--teal);margin-top:12px;display:inline-block}
.row-go span{display:inline-block;transition:transform .3s var(--ease)}
.row:hover .row-go span{transform:translateX(5px)}
@media (max-width:820px){.row{grid-template-columns:56px minmax(0,1fr)}.row-d{grid-column:1/-1}}

/* status pills — every product claim on this site carries its state, because
   an unlabelled claim about a half-built tool is the thing we sell against */
.pill{display:inline-block;font-size:14px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  border-radius:999px;padding:5px 11px;vertical-align:middle;margin-left:10px;white-space:nowrap}
.pill-live{color:#0A5C3C;background:#DCF5E9;border:1px solid #A8E0C6}
.pill-build{color:#7A4A08;background:#FBF0DA;border:1px solid #E9D4A5}
.pill-draw{color:#3D4A7A;background:#E6EAF8;border:1px solid #BFC8EA}
.flag{display:inline-block;font-size:var(--fs-meta);font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:#8A5A1A;background:#FBF0DA;border:1px solid #E9D4A5;
  border-radius:999px;padding:9px 17px;margin-top:20px}

/* tool grid
   Was a hairline slab — gap:1px over a --hair ground, overflow:hidden, so the
   six tools were cells of one continuous rectangle. That construction cannot
   carry a shadow at all: there is no space between cards for one to fall
   into, and overflow:hidden clips whatever does escape. So the slab is gone
   and these are real separated cards. */
.tools{display:grid;gap:clamp(14px,1.5vw,24px);margin-top:clamp(28px,3.4vw,48px);
  grid-template-columns:repeat(auto-fit,minmax(min(330px,100%),1fr))}
/* Six tools on one grid: auto-fit lands them 4+2 between roughly 1300 and
   1900px, which reads as two items nobody finished. Capped at three columns
   above the breakpoint so the set is always 3+3. The paired grids on the
   platform page keep plain .tools and stay two-up. */
.tools-6{grid-template-columns:repeat(auto-fit,minmax(min(330px,100%),1fr))}
@media (min-width:1100px){ .tools-6{grid-template-columns:repeat(3,minmax(0,1fr))} }
.tool{background:var(--surface);border:1px solid var(--hair);border-radius:var(--card-r);
  box-shadow:var(--rest);padding:clamp(24px,2.4vw,34px);display:flex;flex-direction:column;gap:12px}
.tool:hover{translate:0 -3px;box-shadow:var(--raise);border-color:var(--hair-strong)}
.tool-h{font-size:21px;font-weight:600;color:var(--deep);letter-spacing:-.014em}
.tool-k{font-size:var(--fs-meta);font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
.tool p{font-size:16.5px;line-height:1.58;color:var(--ink-soft);max-width:62ch}

/* The chips were content-width flex items, so every card ended in a ragged
   line that stopped wherever the last word did — four cards ending 3+1, two
   ending 2+1, no two lines the same length. Read as leftovers rather than a
   set.
   `flex:1 1 auto` makes each line justify to both edges, so a line is a
   deliberate row rather than a rag; equal min-height and centred text make
   the chips read as one component repeated. Widths still vary with the
   label, which is correct — forcing identical widths would stretch "Live
   preview" to match "Per-client workspaces" and lose the alignment the
   justification just bought. */
.tool ul{list-style:none;display:flex;flex-wrap:wrap;gap:8px;margin-top:auto;padding-top:16px}
.tool li{flex:1 1 auto;display:flex;align-items:center;justify-content:center;text-align:center;
  min-height:36px;font-size:14px;line-height:1.3;color:var(--ink-soft);background:var(--off);
  border:1px solid var(--hair);border-radius:4px;padding:6px 12px}

/* number strip — same slab-to-cards change as .tools above, and for the same
   reason. These sit on the off-white band, where a white card with a shadow
   is the whole point: the number should stand off the page, not be a panel
   ruled out of it. */
.nums{display:grid;gap:clamp(14px,1.5vw,24px);margin-top:clamp(28px,3.4vw,48px);
  grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr))}
.num{background:var(--surface);border:1px solid var(--hair);border-radius:var(--card-r);
  box-shadow:var(--rest);padding:clamp(24px,2.6vw,36px)}
.num:hover{translate:0 -3px;box-shadow:var(--raise);border-color:var(--hair-strong)}
.num b{display:block;font-family:var(--serif);font-size:clamp(32px,3.2vw,48px);line-height:1;
  letter-spacing:-.03em;color:var(--deep)}
.num i{display:block;font-style:normal;font-size:var(--fs-meta);font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted);margin-top:11px}
.num p{font-size:16px;line-height:1.55;color:var(--ink-soft);margin-top:13px;max-width:54ch}

/* steps */
.steps{display:grid;gap:22px;margin-top:clamp(28px,3.4vw,48px);
  grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr))}
.step{border-top:3px solid var(--teal);padding-top:20px}
.step b{display:block;font-size:var(--fs-meta);font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:var(--teal)}
.step h3{font-family:var(--serif);font-size:25px;color:var(--deep);margin-top:10px;letter-spacing:-.02em}
.step p{font-size:16.5px;line-height:1.58;color:var(--ink-soft);margin-top:10px;max-width:58ch}

/* people */
.duo{display:grid;gap:22px;margin-top:clamp(26px,3.2vw,44px);
  grid-template-columns:repeat(auto-fit,minmax(min(340px,100%),1fr))}
.who{background:var(--surface);border:1px solid var(--hair);border-radius:var(--card-r);
  box-shadow:var(--rest);padding:26px;display:flex;gap:20px}
.who:hover{translate:0 -3px;box-shadow:var(--raise);border-color:var(--hair-strong)}
.who-av{width:76px;height:76px;flex:none;border-radius:4px;overflow:hidden}
.who-n{font-size:21px;font-weight:600;color:var(--deep);letter-spacing:-.012em}
.who-r{font-size:var(--fs-meta);font-weight:700;color:var(--teal);margin-top:6px}
.who-b{font-size:16px;line-height:1.55;color:var(--muted);margin-top:12px;max-width:60ch}

/* honesty block — deliberately plain
   It keeps the static shadow but NOT the hover lift, and the split is the
   point: raising something under the pointer says "this does something", and
   this block is prose, not a control. Left flat it would have been the one
   unraised box on a page of raised ones, which reads as an oversight rather
   than as restraint. Its gold left rule stays untouched — that is what makes
   it plain, not the absence of depth. */
.plain{border:1px solid var(--hair);border-left:3px solid var(--gold-lift);
  border-radius:var(--card-r);box-shadow:var(--rest);
  padding:clamp(22px,2.4vw,32px);margin-top:clamp(26px,3vw,42px);background:var(--surface);max-width:88ch}
.plain h3{font-family:var(--serif);font-size:25px;color:var(--deep);letter-spacing:-.02em}
.plain p{font-size:16.5px;line-height:1.62;color:var(--ink-soft);margin-top:12px;max-width:76ch}

/* article layout */
.article{display:grid;gap:clamp(26px,4vw,64px);align-items:start;
  grid-template-columns:minmax(0,1fr) minmax(0,300px)}
@media (max-width:980px){.article{grid-template-columns:minmax(0,1fr)}}
/* ── the wide-screen fix, 6 Sep 2026 ──────────────────────────────────────
   `minmax(0,1fr)` gives the first track every pixel the viewport grows by,
   and the prose inside it is capped at 74ch, so the surplus became a hole:
   318px between the text and the sidebar at 1440, 764px at 1920, 1,398px at
   2560. The text hugged the left rail and the Related panel was stranded
   against the right one.

   Nothing caught it because `_verify-doc.mjs` measures whether `.wrap` fills
   the shell, and it does, at every width. That is a question about the
   container. Where the content sits INSIDE the container is a different
   question and nothing was asking it.

   Above 1400px the tracks are sized to their content instead of to the
   viewport and the grid is centred, which is what responsive-full-width.md
   prescribes for wide screens: reading column capped, side rails widen. The
   whitespace ends up symmetric either side rather than pooled in the middle.

   Below 1400px this changes nothing. Those widths were signed off on
   5 September and are deliberately left alone. */
@media (min-width:1400px){
  .article{grid-template-columns:minmax(0,74ch) minmax(0,320px);
    justify-content:center}
}
.prose{max-width:74ch}
.prose h2{font-family:var(--serif);font-size:clamp(26px,2.6vw,38px);color:var(--deep);
  letter-spacing:-.024em;line-height:1.12;margin-top:clamp(30px,3.4vw,48px)}
.prose h3{font-size:22px;font-weight:600;color:var(--deep);margin-top:28px}
.prose p{margin-top:16px;color:var(--ink-soft);text-wrap:pretty}
.prose ul,.prose ol{margin-top:16px;padding-left:22px;color:var(--ink-soft)}
.prose li{margin-top:9px}
.prose strong{color:var(--ink)}
.prose a{color:var(--teal)}
/* The aside is a panel the reader parks beside the article, not something to
   lift — so static shadow only, same as .plain. Its ground stays --off rather
   than white: it is a sidebar, and making it a white card would give it the
   same weight as the prose it is meant to sit beside. */
.aside{position:sticky;top:96px;border:1px solid var(--hair);border-radius:var(--card-r);
  box-shadow:var(--rest);padding:24px;background:var(--off)}
.aside h3{font-size:var(--fs-meta);font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
.aside a{display:block;padding:10px 0;min-height:44px;font-size:16px;color:var(--ink-soft);
  text-decoration:none;border-bottom:1px solid var(--hair);display:flex;align-items:center}
.aside a:last-of-type{border-bottom:0}
.aside a:hover{color:var(--teal)}
.crumb{font-size:var(--fs-meta);color:var(--muted);margin-bottom:14px}
.crumb a{color:var(--muted)}

/* ── image slots ───────────────────────────────────────────────────────────
   A slot is NOT an image. It is a labelled gap that says what belongs in it
   and that nothing has been supplied yet, per Nick's ruling of 6 Sep 2026:
   build the capability, mark the slots, source the images to match afterwards.

   The design constraint that drives every choice below: it must be impossible
   to mistake for a rendered image at a glance, in a screenshot, or in a PDF
   print. So it is dashed rather than solid, it carries the word "not supplied"
   in its own line rather than in a caption somebody might skim past, and it
   never reserves a picture-shaped area with a background that could read as a
   loading image.

   `.shot-cap` is the caption the real image will keep. It is written now, with
   the slot, because a caption composed six weeks later against an image
   somebody else chose is how a page ends up claiming something it does not
   show.

   Sizes: every declaration here is at or above the 14px floor, which is
   measured by `_verify-doc.mjs` at four widths. --fs-meta IS 14px, so it is
   the floor and not a value to go below.

   No transition and no lift. A slot is not a card and must not behave like
   one; `.shot` is deliberately absent from the shared elevation rule at the
   head of this file. */
.shot{margin-top:28px}
.shot-slot{border:2px dashed var(--hair-strong);border-radius:var(--card-r);
  background:var(--off);padding:clamp(18px,2.6vw,32px);
  display:flex;flex-direction:column;gap:10px;align-items:flex-start}
.shot-flag{font-size:var(--fs-meta);font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--gold);display:inline-flex;
  align-items:center;gap:9px;line-height:1.3}
.shot-flag::before{content:"";width:9px;height:9px;border-radius:50%;
  background:var(--gold-lift);flex:none}
.shot-what{font-size:16.5px;line-height:1.6;color:var(--ink-soft);max-width:70ch}
.shot-what strong{color:var(--ink)}
.shot-cap{font-size:16px;line-height:1.6;color:var(--muted);margin-top:12px;
  max-width:70ch;text-wrap:pretty}
.shot-cap b{color:var(--ink-soft);font-weight:600}

/* close + footer */
.close{background:var(--abyss);color:#FFF;padding-block:clamp(52px,6.5vw,104px);position:relative;overflow:hidden}
.close::before{content:"";position:absolute;inset:0;
  background:radial-gradient(820px 420px at 16% 12%,rgba(95,216,242,.16),transparent 62%),
             radial-gradient(700px 360px at 86% 88%,rgba(240,196,105,.11),transparent 62%)}
.close .wrap{position:relative}
.close h2{font-family:var(--serif);font-size:var(--fs-h2);line-height:1.06;letter-spacing:-.028em;
  font-weight:700;color:#FFF;max-width:20ch;text-wrap:balance}
.close p{font-size:var(--fs-lead);color:rgba(255,255,255,.78);max-width:58ch;margin-top:18px;text-wrap:pretty}
.close-row{display:flex;flex-wrap:wrap;gap:14px;align-items:center;margin-top:32px}

footer{padding:34px var(--rail) 52px;font-size:var(--fs-meta);color:var(--muted)}
footer p{max-width:82ch}
.fl{display:flex;flex-wrap:wrap;gap:22px;padding-bottom:18px;border-bottom:1px solid var(--hair);margin-bottom:18px}
.fl a{color:var(--ink-soft);text-decoration:none;padding:6px 0;min-height:44px;display:inline-flex;align-items:center}
.fl a:hover{color:var(--teal);text-decoration:underline}
footer p+p{margin-top:8px}

/* reveal */
.js .rv{opacity:0;transform:translateY(22px);transition:opacity .8s var(--ease),transform .8s var(--ease)}
.rv.in{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important}
  .rv{opacity:1;transform:none}
  .motion-toggle{display:none}
  /* Cards keep their resting shadow and their hover shadow — depth is not
     motion. Only the rise is dropped. */
  .tool:hover,.num:hover,.who:hover,.route:hover,.row:hover{translate:none}
}
