
/* Swav Display is the licensed display face (Mojomox, 19 Aug 2026). ss06 is the house
   setting. SUPERSEDED 2026-08-25: the display face is Courbe Sans. See
   assets/fonts/courbe.css. swav.css and luna.css are still on disk, unreferenced,
   and either is a one-line @import away. */
@import url('../assets/fonts/courbe.css?v=5021db1a');
:root{
  /* ground */
  --bg:#15110c; --ink:#f4efe4; --dim:#9c9284; --faint:#6a6156;
  --paper:#f2efe6; --paper-ink:#15110c; --paper-dim:#5d564c;
  /* roles: orange = brand only. teal = machine voice. */
  --orange:#ff6a1a; --orange-hi:#ff7d33; --fs-cta:0.75rem;
  /* THREE TEALS, BECAUSE ONE CANNOT DO THE JOB. Measured against the two grounds
     this site actually has (see brand/colors.md):

       --teal-bright  12.03:1 on ink,  1.36:1 on paper -- the machine voice, and
                      unreadable on cream
       --teal-deep     5.83:1 on ink,  2.80:1 on paper -- fills and emissive
                      screens; fails as text on cream, which is why it is not
                      the paper teal
       --teal-ink      3.57:1 on ink,  4.58:1 on paper -- the only teal that
                      carries as TEXT on cream

     --teal is not a colour, it is the ground-appropriate one: bright by default,
     ink inside anything cream. Use --teal for teal TEXT and it is right on both
     grounds without anybody choosing. Reach for a named one only when the ground
     is fixed regardless of band -- a filled chip, or a canvas-painted section. */
  --teal-bright:#72E2C9; --teal-deep:#00A286; --teal-ink:#0F7A63;
  --teal:var(--teal-bright);
  /* the ground and ink as raw channels, so html.light can invert everything at once */
  --ink-rgb:244,239,228; --bg-rgb:21,17,12;
  --line:rgba(var(--ink-rgb),.12); --line-paper:rgba(var(--bg-rgb),.14);
  /* the nav pane's metrics -- the homepage's TYPE values, verbatim */
  --nav-w:240px; --nav-px:16px; --nav-py:12px; --tick:38px; --mark-h:16px;
  --fs-menu:.75rem;
  --display:'Courbe Sans',ui-sans-serif,sans-serif; --sans:'Questrial',system-ui,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,monospace;
  --gut:clamp(24px,5vw,88px); --maxw:1220px;
}
/* ---- LIGHT THEME ----------------------------------------------------------
   Same page, inverted. The alternating dark/paper rhythm inverts with it, so the
   bands that were paper become the dark ones and the rhythm survives. */
html.light{
  --bg:#f2efe6; --ink:#15110c; --dim:#5d564c; --faint:#8a8073;
  --paper:#15110c; --paper-ink:#f4efe4; --paper-dim:#9c9284;
  --ink-rgb:21,17,12; --bg-rgb:242,239,230;
  /* cream is this page's DEFAULT ground, not just its alternating band, so teal
     text here has to be the paper-safe one. It was #0E9E86, which measured
     2.93:1 against the cream at 10.5px -- a fail, live, on every kicker on the
     home page. Sections whose black is painted on the CANVAS are the exception
     and set --teal-bright explicitly; see the shell. */
  --teal:var(--teal-ink);
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{background:var(--bg);color:var(--ink);font-family:var(--sans);
  -webkit-font-smoothing:antialiased;overflow-x:hidden}
a{color:inherit;text-decoration:none}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 var(--gut)}

/* ---------- type ---------- */
.kick{font-family:var(--mono);font-size:10.5px;letter-spacing:.22em;text-transform:uppercase;
  color:var(--teal);display:flex;align-items:center;gap:12px;margin-bottom:26px}
.kick::before{content:'';width:26px;height:1px;background:currentColor;flex:0 0 26px}
/* HOUSE DISPLAY SETTING — see assets/fonts/luna.css. Caps, weight 400, the
   face's own forms. 400 IS DELIBERATE: Courbe is a single-weight family, and
   asking for 700 makes the browser synthesise a bold by smearing the outlines,
   which is plainly visible on a single-weight face. No per-glyph borrowing —
   the trial faces need no rescue, so swavR()/swavS() do not run here. */
h1,h2,h3,.pipe h4{
  font-family:var(--display);font-weight:400;font-feature-settings:normal;
  text-transform:uppercase;letter-spacing:.004em;line-height:1.06}
/* a testimonial in caps 700 would shout — quotes stay in the body face */
.quote p{font-family:var(--sans);text-transform:none;font-weight:400}
/* The display scale, currently the authored Swav sizes MINUS 10% PLUS 11%.
   The two are not the same correction and neither is decoration.

   The -10% was for Luna, which sets wide and pushed headings onto extra lines.
   The +11% is for Courbe, which does not: its cap is 0.70em against Luna's
   0.778, so at identical numbers it renders 10% shorter -- and because it is
   also 34% narrower, a headline lost about 41% of its AREA, which is what the
   eye actually reads as size. Cap height alone badly understates it.

   +11% puts the cap back to what was signed off under Luna (70.0 against 70.2
   per 100px nominal) while keeping the hero on two lines; the full restoration
   would be +21% and costs that break. Measured, not estimated.

   Original Swav values if they are ever wanted: 2.1/4.5vw/3.9, 1.6/3vw/2.5,
   1.5/2.4vw/2.1, 1.22, and 64px for the hero in beats.js. */
.h-xl{font-size:clamp(2.1rem,4.5vw,3.9rem);line-height:1.06}
.h-l{font-size:clamp(1.6rem,3vw,2.5rem)}
.h-m{font-size:clamp(1.5rem,2.4vw,2.1rem);line-height:1.08}
.h-s{font-size:1.22rem;line-height:1.15}
/* Swav has no italic — a synthesised oblique on the display face looks broken, so
   emphasis in headings is carried by colour and weight instead */
em{font-style:normal;color:var(--orange)}
h1 em,h2 em,h3 em{font-weight:500}
.lede{font-size:clamp(1.05rem,1.35vw,1.28rem);line-height:1.62;color:var(--dim);max-width:62ch}
.prose p{font-size:1.05rem;line-height:1.68;color:var(--dim);max-width:64ch;margin-bottom:1.15em}
.prose p em{color:var(--ink);font-style:normal;border-bottom:1px solid var(--orange)}
.small{font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}

/* ---------- a post ------------------------------------------------------
   One column, set for READING, which is the only job on this page. The
   measure is the whole design: .prose already caps at 64ch and that is kept,
   because a line much longer than that costs the reader the return sweep and
   a line much shorter costs them the rhythm.

   Cream, not the dark default. The site is dark-first and that is right for
   a landing page you scan; it is wrong for two thousand words you read. */
/* ONE COLUMN WIDTH, AND EVERYTHING OBEYS IT. The rules around the byline used
   to span the wrapper while the prose stopped at its own 64ch, so they ran
   157px past the text on the right -- close enough to look like a mistake
   rather than a decision, which is exactly what it was. The measure now lives
   on the wrapper, once, and the text fills it; the display type is allowed to
   stop SHORT of it (a heading wants a tighter measure than body copy) but
   nothing is allowed to run past it. */
.post{max-width:none}
.post__back{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--paper-dim);text-decoration:none}
.post__back:hover{color:var(--orange)}
.post__meta{display:flex;gap:14px;align-items:center;flex-wrap:wrap;
  margin:clamp(26px,3.5vh,40px) 0 14px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--paper-dim)}
.post__tag{border:1px solid var(--line-paper);padding:6px 9px;font-size:8.5px}
.post h1{max-width:20ch;margin-bottom:16px}
.post__deck{font-family:var(--sans);font-size:1.2rem;line-height:1.5;
  color:var(--paper-ink);max-width:52ch;margin-bottom:clamp(26px,3.5vh,38px)}
/* the byline carries a face, because the people on the wall wrote these and
   the wall is one click away. Same cut-out asset as /about -- no second crop
   to keep in step with the first. */
.post__by{display:flex;align-items:center;gap:13px;padding:18px 0;
  border-top:1px solid var(--line-paper);border-bottom:1px solid var(--line-paper)}
.post__by img{width:40px;height:40px;flex:0 0 40px;border-radius:50%;object-fit:cover;
  background:var(--line-paper);filter:grayscale(1)}
.post__by b{display:block;font-family:var(--mono);font-size:11px;letter-spacing:.1em;
  text-transform:uppercase;font-weight:500;color:var(--paper-ink)}
.post__by span{display:block;font-family:var(--mono);font-size:9px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--paper-dim);margin-top:3px}
.post__body{padding-top:clamp(30px,4.5vh,52px)}
.post__body h2{font-family:var(--display);font-weight:400;
  font-size:clamp(1.3rem,2.1vw,1.75rem);line-height:1.15;
  margin:clamp(34px,4.5vh,52px) 0 16px;max-width:24ch}
.post__body p{font-size:1.05rem;line-height:1.68;color:var(--paper-dim);
  margin-bottom:1.15em}
.post__body ul{list-style:none;margin:0 0 1.4em}
.post__body li{position:relative;padding-left:22px;margin-bottom:.6em;
  font-size:1.05rem;line-height:1.6;color:var(--paper-dim)}
/* a hairline dash rather than a bullet: the site marks list items this way
   everywhere else and a round bullet would be the only one on the site */
.post__body li::before{content:'';position:absolute;left:0;top:.72em;
  width:11px;height:1px;background:var(--orange)}
/* the run-in label pattern this post actually uses -- "AI Usage:", "Discover:" */
.post__body b{color:var(--paper-ink);font-weight:500}
/* one line worth stopping on, set at heading scale in the display face. The
   post already quotes it; this just gives it the room it was asking for. */
.post__pull{font-family:var(--display);font-weight:400;
  font-size:clamp(1.4rem,2.6vw,2rem);line-height:1.2;color:var(--paper-ink);
  margin:clamp(34px,4.5vh,50px) 0;padding-left:24px;
  border-left:2px solid var(--orange)}

/* ---------- nav ---------- */
/* The footer's lockup, and the base every other .brand inherits. This rule used
   to sit here on its own; porting the nav replaced it, and the footer -- which
   also uses .brand -- lost `img{width:19px}` and rendered the favicon at its
   natural 875 units. `.nav .brand` below is more specific and still wins. */
.brand{display:flex;align-items:center;gap:7px;font-family:var(--mono);font-size:13px;
  letter-spacing:.12em;color:var(--ink);text-decoration:none}
.brand img{width:19px;height:19px;display:block}

/* ---- the nav: the homepage's pane, on every page --------------------------
   Solid at the top of the page, glass once anything has moved underneath it.
   Every value is driven from assets/nav.js, which carries the same curve the
   experience engine uses. */
.nav{position:fixed;top:var(--tick);left:0;right:0;margin:0 auto;z-index:50;
  width:min(var(--nav-w),74vw);color:#fff;overflow:hidden;
  display:flex;align-items:center;justify-content:space-between;
  padding:var(--nav-py) var(--nav-px);text-decoration:none;
  background:rgba(0,0,0,var(--nav-a,1));
  -webkit-backdrop-filter:blur(var(--nav-blur,0px)) saturate(var(--nav-sat,100%));
  backdrop-filter:blur(var(--nav-blur,0px)) saturate(var(--nav-sat,100%));
  box-shadow:0 var(--nav-lift,0px) calc(var(--nav-lift,0px)*3.2) rgba(21,17,12,var(--nav-sh,0)),
             inset 0 1px 0 rgba(255,255,255,var(--nav-rim,0))}
/* how far down the page you are, read along the bottom edge of the pane. The
   homepage's own bar, verbatim -- same element, same variables, same fade-in
   with the glass -- so the nav behaves identically wherever you meet it. */
.nav::after{content:'';position:absolute;left:0;bottom:0;height:1px;
  width:calc(var(--nav-prog,0) * 100%);background:var(--orange);
  opacity:var(--nav-prog-a,0)}
.nav button{background:none;border:0;color:#fff;cursor:pointer;line-height:1;
  font-family:var(--mono);font-weight:300;font-size:var(--fs-menu);
  letter-spacing:.08em;text-transform:uppercase}
/* the lockup: `port` set beside the mark, the mark's bottom edge on the word's
   baseline. An inline-block on vertical-align:baseline has one settled rule --
   its baseline IS its bottom margin edge -- which is the whole alignment. */
.nav .brand{--lock-fs:calc(var(--mark-h)*1.12);--lock-gap:calc(var(--mark-h)*0.02);
  display:inline-block;white-space:nowrap;color:inherit;line-height:1;text-decoration:none}
.brand__w{font-family:var(--display);font-weight:400;font-size:var(--lock-fs);
  text-transform:lowercase;line-height:1;letter-spacing:0;font-feature-settings:"ss06" 0}
.brand__m{height:var(--mark-h);width:auto;display:inline-block;
  vertical-align:baseline;margin-left:var(--lock-gap)}
/* CSS beats the fill="" in the file, so the mark takes the colour of the type */
.brand__m path{fill:currentColor}

/* Bare text, no plate, over whatever the page puts under it. */
.login{position:fixed;top:calc(var(--tick) + var(--nav-py));right:var(--tick);z-index:50;
  font-family:var(--mono);font-weight:300;font-size:var(--fs-menu);
  letter-spacing:.08em;text-transform:uppercase;text-decoration:none;
  line-height:1;color:var(--ink);transition:color .22s ease}
/* The alternate ink, for when a light band has scrolled under the login. Which
   bands those are is measured at runtime rather than listed -- see nav.js. The
   experience shell carries the mirror image of this rule (.login.on-dark), for
   the reason written down there. */
.login.on-light{color:var(--paper-ink)}
.login:hover{color:var(--orange)}
@media (max-width:640px){.login{display:none}}

/* nav.js owns the open/close, so there is no CSS transition here: one on the
   same properties would fight the tween and the unfurl would never be seen.
   `perspective` is what makes the items read as hinging down. */
.menu{position:fixed;top:calc(var(--tick) + var(--mark-h) + var(--nav-py)*2);
  left:0;right:0;margin:0 auto;z-index:50;width:min(var(--nav-w),74vw);
  background:#000;transform-origin:top;opacity:0;pointer-events:none;
  overflow:hidden;perspective:600px;perspective-origin:top center}
.menu.open{pointer-events:auto}
.menu a{display:block;padding:13px 26px;color:rgba(255,255,255,.72);text-decoration:none;
  font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  border-top:1px solid rgba(255,255,255,.12);
  transform-origin:left center;will-change:transform,opacity}
.menu a.on{color:#fff}
/* Work in progress: visible, and inert. The dim is done with COLOUR and not
   opacity, deliberately -- the unfurl tween animates opacity from 0 to 1 on every
   item and leaves an inline opacity:1 behind, which would wash the dim straight
   out. The hover tweens write inline colour too, so the drivers exclude .soon
   from those; nothing else in either timeline touches colour. */
.menu a.soon{color:rgba(255,255,255,.3);cursor:default;pointer-events:none}
/* ONE BUTTON, FOR THE WHOLE SITE. This lived here and again in the experience
   shell, with different sizes, weights, padding and border alphas -- so the
   homepage's call to action and /about's were visibly different objects, and a
   fix applied to one silently did not reach the other. The shell's copy is gone;
   this is the definition.

   The border resting at .5 rather than .12 is the accessibility half: at .12 it
   measured 1.29:1 against the cream, and a ui component's boundary needs 3:1 to
   be perceivable. It also makes the hover a deepening rather than a border
   appearing out of nothing.

   HOVER DARKENS THE EDGE ON BOTH. The secondary already did; the primary only
   brightened its fill, so the two behaved differently under the same pointer.
   Ink on the hover orange measures 6.4:1, so the edge reads without shouting. */
.btn{font-family:var(--mono);font-weight:500;font-size:var(--fs-cta);
  letter-spacing:.13em;text-transform:uppercase;
  padding:15px 24px;border:1px solid rgba(var(--ink-rgb),.5);
  /* NO `color` HERE. It INHERITS, and that is what makes it right in both
     bands: inside .paper the section has already flipped to --paper-ink,
     and setting --ink on the button would put light type on the light band.
     Carried over from the shell's copy, where the page ground never flips,
     it blanked /about's secondary button entirely. */
  text-decoration:none;white-space:nowrap;transition:.18s;cursor:pointer}
.btn:hover{border-color:var(--ink)}
.btn--p{background:var(--orange);border-color:var(--orange);color:#160d05}
.btn--p:hover{background:var(--orange-hi);border-color:#160d05}
/* :not(.btn--p), because `.paper .btn` outranks `.btn--p` on specificity and was
   quietly taking the primary's orange border away in every paper band -- /about's
   call to action has never actually had one. And .5 here for the same reason it
   is .5 on the page ground: an outline at .14 is not a perceivable boundary. */
.paper .btn:not(.btn--p){border-color:rgba(var(--bg-rgb),.5)}
.paper .btn:not(.btn--p):hover{border-color:var(--paper-ink)}

/* ---------- placeholders ---------- */
.ph{position:relative;display:flex;flex-direction:column;justify-content:flex-end;gap:5px;
  border:1px dashed rgba(var(--ink-rgb),.28);padding:18px;aspect-ratio:var(--ar,16/9);
  background-image:radial-gradient(rgba(var(--ink-rgb),.13) 1px,transparent 1px);
  background-size:7px 7px;overflow:hidden}
.paper .ph{border-color:rgba(var(--bg-rgb),.3);
  background-image:radial-gradient(rgba(var(--bg-rgb),.16) 1px,transparent 1px)}
.ph__tag{position:absolute;top:0;left:0;font-family:var(--mono);font-size:9px;letter-spacing:.18em;
  text-transform:uppercase;background:var(--teal-bright);color:#04231c;padding:5px 9px;
  /* --teal-bright, not --teal: a filled chip IS its own ground, so it must
     not follow the band. Following it put #04231c on the ink teal at 3.16:1. */
  font-weight:500}
.ph__name{font-family:var(--mono);font-size:11.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink)}
.paper .ph__name{color:var(--paper-ink)}
.ph__note{font-size:12.5px;line-height:1.45;color:var(--dim);max-width:52ch}
.paper .ph__note{color:var(--paper-dim)}

/* ---------- sections ---------- */
.sect{padding:clamp(80px,11vh,150px) 0;border-top:1px solid var(--line);position:relative}
.sect--flush{border-top:none}
.paper{background:var(--paper);color:var(--paper-ink);border-top:none;
  /* re-point the alias once, here, instead of overriding every teal thing
     that can land on a cream band one rule at a time. */
  --teal:var(--teal-ink)}
.paper .lede,.paper .prose p{color:var(--paper-dim)}
.paper .small{color:#7d7568}
.hero{min-height:100vh;display:flex;align-items:center;padding:120px 0 70px;position:relative}
.hero .cols{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.92fr);gap:clamp(32px,5vw,74px);
  align-items:center;width:100%}
.hero .sub{margin-top:24px}
/* single-column hero, after brand/11-swav-homepage: eyebrow rule, big caps, lede, dual CTA,
   and the emblem moved out to its own full-width band below */
.hero--solo{min-height:auto;padding:172px 0 54px;display:block}
.hero--solo .cols{display:block}
.hero--solo h1{max-width:22ch}
.hero--solo .lede{max-width:47ch;margin-top:34px}
.hero--solo .row{margin-top:44px}
.hero--solo .trust{margin-top:54px;padding-top:22px;border-top:1px solid var(--line)}
.hero--solo+.sect--flush{padding-top:6px}
.eyebrow{display:flex;align-items:center;gap:14px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.24em;text-transform:uppercase;color:var(--dim);margin-bottom:42px}
.eyebrow s{width:52px;height:1px;background:currentColor;opacity:.55;flex:none;
  text-decoration:none;display:block}
.row{display:flex;gap:13px;flex-wrap:wrap;align-items:center;margin-top:34px}
.trust{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  line-height:1.85;color:var(--dim)}
.trust b{color:var(--ink);font-weight:500}

/* marquee */
.marq{overflow:hidden;position:relative;padding:6px 0}
.marq::before,.marq::after{content:'';position:absolute;top:0;bottom:0;width:110px;z-index:2}
.marq::before{left:0;background:linear-gradient(90deg,var(--bg),transparent)}
.marq::after{right:0;background:linear-gradient(270deg,var(--bg),transparent)}
.track{display:flex;width:max-content;animation:slide 34s linear infinite}
/* the gap belongs to the SET, as trailing space, so one set is exactly one
   cycle and translateX(-50%) lands on the seam every time -- see marquee() */
.set{display:flex;gap:56px;align-items:center;padding-right:56px}
.track img{height:26px;width:auto;filter:brightness(0) invert(1);opacity:.62}
/* the partner marks are flattened to the ink colour, so they have to flip with the theme.
   FULL opacity on paper: the strip sits over the render, and every character behind it
   was reading through a mark held at .55 -- the logos and the field were the same weight
   and neither one won. */
html.light .track img{filter:brightness(0);opacity:1}
@keyframes slide{to{transform:translateX(-50%)}}

/* cards / grids */
.grid-3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:22px}
.grid-2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:26px}
.card{border:1px solid var(--line);padding:30px 26px 26px;display:flex;flex-direction:column;
  min-height:270px;transition:.2s;position:relative}
a.card:hover{border-color:rgba(var(--ink-rgb),.3);transform:translateY(-3px)}
/* a fill, not just an edge. On a black section an outlined card is a wireframe;
   five per cent of the opposite ground is enough to make it read as a panel
   sitting ON something. --bg-rgb is the page's ground, which is always the
   opposite of .paper's, so this is correct in both themes. */
.paper .card{border-color:var(--line-paper);background:rgba(var(--bg-rgb),.05)}
.card .n{font-family:var(--mono);font-size:11px;letter-spacing:.16em;color:var(--orange);margin-bottom:20px}
.card p{font-size:.96rem;line-height:1.58;color:var(--dim);margin-top:12px}
.paper .card p{color:var(--paper-dim)}
.card .go{margin-top:auto;padding-top:22px;font-family:var(--mono);font-size:10px;letter-spacing:.15em;
  text-transform:uppercase;color:var(--teal)}

/* surfaces table */
.surf{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;background:var(--line);border:1px solid var(--line)}
.surf>div{background:var(--bg);padding:26px 22px}
.paper .surf{background:var(--line-paper);border-color:var(--line-paper)}
.paper .surf>div{background:var(--paper)}
.surf h3{margin-bottom:16px}
.surf dl{font-size:.9rem;line-height:1.55}
.surf dt,.card dt{font-family:var(--mono);font-size:9.5px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--faint);margin-top:16px}
.surf dd,.card dd{color:var(--dim);margin-top:5px}
.paper .surf dd,.paper .card dd{color:var(--paper-dim)}
/* the foundation bar used to butt straight onto .surf, which is why it has no top
   edge of its own. Following a gapped grid it needs one back, and a gap to sit in. */
.grid-3+.foundation{margin-top:22px;border-top:1px solid var(--line)}
.paper .grid-3+.foundation{border-top-color:var(--line-paper)}
/* HUGGING is the point: as a block it stretches the full grid and reads as a
   fourth cell. inline-block shrinks it to its own sentence, and the centred
   column it sits in then centres it. */
.foundation{margin-top:1px;border:1px solid var(--line);border-top:none;padding:18px 22px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--teal)}
.paper .foundation{border-color:var(--line-paper)}

/* pipeline */
.pipe{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:1px;background:var(--line);
  border:1px solid var(--line);margin-bottom:26px}
.pipe>div{background:var(--bg);padding:22px 18px}
.pipe .st{font-family:var(--mono);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--teal);margin-bottom:12px;display:flex;align-items:center;gap:8px}
.pipe .st i{width:5px;height:5px;background:currentColor;border-radius:50%;font-style:normal}
.pipe h4{font-size:1.06rem;margin-bottom:9px}
.pipe p{font-size:.86rem;line-height:1.5;color:var(--dim)}

/* claims / outcomes */
.claims{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;background:var(--line);
  border:1px solid var(--line)}
.claims>li{background:var(--bg);padding:26px 24px;list-style:none}
.paper .claims{background:var(--line-paper);border-color:var(--line-paper)}
.paper .claims>li{background:var(--paper)}
/* NOT the display face. Swav is a display type — its caps R and S are the wrong
   letters without ss06, and at 1.2rem in a grid of four claims it stopped being
   readable and started being decoration. Questrial is the body face and only
   ships at 400, so the weight comes from a hairline stroke rather than a faux
   bold, which on a geometric sans just fills the counters in. */
.claims b{display:block;font-family:var(--sans);font-size:1.36rem;line-height:1.24;
  text-transform:none;letter-spacing:-.004em;margin-bottom:10px;
  -webkit-text-stroke:.4px currentColor}
.claims span{font-size:.94rem;line-height:1.55;color:var(--dim)}
.paper .claims span{color:var(--paper-dim)}
.checks{list-style:none;margin-top:8px}
.checks li{padding:15px 0 15px 30px;border-bottom:1px solid var(--line);position:relative;
  font-size:1rem;line-height:1.5;color:var(--dim);max-width:74ch}
.paper .checks li{border-color:var(--line-paper);color:var(--paper-dim)}
/* Drawn, not a glyph: no webfont on the page carries a tick, and the system
   fallback for one lands somewhere between a dingbat and an emoji. Two borders
   on a rotated box is a tick that always renders and takes the ink colour. */
.checks li::before{content:'';position:absolute;left:3px;top:19px;width:11px;height:6px;
  border:1.6px solid var(--teal);border-top:0;border-right:0;
  transform:rotate(-45deg);border-radius:1px}
.checks li b{color:var(--ink);font-weight:400}
.paper .checks li b{color:var(--paper-ink)}

/* quotes */
/* ---- the capture, presented -----------------------------------------------
   The platform section shows Phoenix as a PHOTOGRAPH of the product, not as a
   render of one: a screenshot on the page is evidence, and evidence wants to be
   legible before it wants to be on-brand. The console in the room still goes
   through the character pass, because in there it is furniture.

   But a screenshot dropped on the page with a hairline round it is a slide, not
   a piece of design. It is mounted instead: BROKEN OUT past the reading column
   so it is plainly the biggest thing in the section, on a dark plinth carrying
   the same instrument furniture as the rest of the page -- the orange edge from
   .quote, a mono meta strip, and the corner ticks the viewport already draws.
   The plinth is dark in both themes on purpose: the product IS dark, and a light
   mat around a dark UI makes the UI look like a hole. */
figure.shot{position:relative;left:50%;translate:-50%;
  width:min(1240px,94vw);margin:clamp(44px,7vh,86px) 0 0}
/* ...except beside copy, where it is the COLUMN, not a break-out. This was
   scoped to #platform -- the homepage section id -- which was correct for
   exactly as long as that was the only two-column home the figure had. The
   moment the same markup went into the platform PAGE's hero, the base rule ran
   unopposed: left:50%, translate -50%, width min(1240px,94vw) -- a figure the
   width of the viewport laid over the hero copy. Scoped to the CONTEXTS now
   (.cols, .split) rather than to one page's id, so any future two-column home
   gets the column behaviour for free. #platform keeps its frame-chrome sizes
   below; those are cosmetic and its own. */
.cols figure.shot,.split figure.shot{position:static;left:auto;translate:none;width:100%;margin:0}
#platform .shot__frame{padding:clamp(10px,1vw,15px)}
#platform .shot__meta{padding-bottom:clamp(8px,.85vw,12px);font-size:8.5px;letter-spacing:.15em}
.shot__frame{position:relative;background:#0b0e13;
  padding:clamp(13px,1.5vw,22px);
  border:1px solid rgba(255,255,255,.10);border-top:2px solid var(--orange);
  box-shadow:0 44px 90px -46px rgba(0,0,0,.62)}
/* the page's own corner ticks, at the two corners that read as a bracket */
.shot__frame::before,.shot__frame::after{content:'';position:absolute;
  width:13px;height:13px;border:1px solid var(--orange);opacity:.7}
.shot__frame::before{left:-1px;bottom:-1px;border-top:0;border-right:0}
.shot__frame::after{right:-1px;bottom:-1px;border-top:0;border-left:0}
.shot__meta{display:flex;align-items:baseline;justify-content:space-between;
  gap:18px;padding:1px 2px clamp(11px,1.2vw,16px);
  font-family:var(--mono);font-size:9.5px;letter-spacing:.17em;
  text-transform:uppercase;color:rgba(244,239,228,.44);white-space:nowrap}
.shot__id{overflow:hidden;text-overflow:ellipsis;text-transform:none;
  letter-spacing:.08em;color:rgba(244,239,228,.3)}
/* --teal-bright, not --teal: the plinth above is dark in BOTH themes on
   purpose, so this one does not follow the band either. */
.shot__ok{color:var(--teal-bright)}
.shot__glass{position:relative;overflow:hidden;
  border:1px solid rgba(255,255,255,.08)}
.shot__glass img{display:block;width:100%;height:auto}
/* the light that a screen throws on its own bezel, and nothing more */
.shot__glass::after{content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(255,255,255,.055),transparent 22%)}
@media (max-width:640px){.shot__id{display:none}}

/* ---- the AI Risk Assessment artifact --------------------------------------
   A recreation of page 1 of the real deliverable, not a picture of one: it stays
   sharp at any size, the numbers are editable, and nothing leaves the repo as an
   image of a customer's data. Every value here is INVENTED -- in the ballpark of
   a real assessment and belonging to nobody.

   Sized in cqw and then in em, so the whole page scales as one object with its
   container. A document is the one thing on this site that does NOT follow the
   theme: it is white paper wherever it appears, because that is what it is. */
.aira-wrap{container-type:inline-size;line-height:1.34}
.aira{background:#fff;color:#15110c;font-family:var(--sans);font-size:1.72cqw;
  aspect-ratio:17/22;padding:2.1em 2.4em 1.4em;display:flex;flex-direction:column;
  box-shadow:0 1px 0 rgba(var(--ink-rgb),.14),0 26px 60px -30px rgba(0,0,0,.5)}
.aira b{font-weight:400;-webkit-text-stroke:.4px currentColor}
.aira .a-top{display:flex;align-items:flex-end;justify-content:space-between;
  border-bottom:.18em solid #15110c;padding-bottom:.7em}
/* port53_logo.svg is the MARK, not a wordmark — it draws the 53 and nothing
   else — so the word is set beside it, which is how the real report letterhead
   is built too. */
.aira .a-logo{display:inline-flex;align-items:center;gap:.03em;font-size:2.05em;
  line-height:1;letter-spacing:-.022em;-webkit-text-stroke:.85px currentColor}
.aira .a-logo img{height:.78em;width:auto;position:relative;top:.015em}
.aira .a-kind{font-family:var(--mono);font-size:.92em;letter-spacing:.19em;font-weight:500}
.aira .a-lead{display:grid;grid-template-columns:1.06fr .94fr;gap:1.5em;margin-top:1.05em}
.aira .a-lead p{margin:0 0 .55em}
.aira .a-score{background:#fdf0e6;border-left:.34em solid #f0500c;padding:.9em 1em}
.aira .a-score .l{font-family:var(--mono);font-size:.9em;letter-spacing:.17em;
  color:#8a6a54;margin-bottom:.5em}
.aira .a-score .v{font-size:1.72em;letter-spacing:.02em;color:#c4b3a6;
  -webkit-text-stroke:.5px currentColor;margin-bottom:.7em}
.aira .a-score .v i{font-style:normal;color:#e0400a}
.aira .a-score .m{font-size:.94em;color:#5d564c}
.aira h4{font-size:1.28em;letter-spacing:.01em;margin:1.05em 0 0;
  -webkit-text-stroke:.5px currentColor;text-transform:uppercase}
.aira .a-rule{border-top:.16em solid #f0500c;margin:.34em 0 .75em}
.aira .a-tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:.9em}
/* DIRECT children only. Unscoped this also hit .k, .n and .s and gave every line
   inside a tile its own black panel and its own orange rule. */
.aira .a-tiles>div{background:#0b0a08;color:#fff;padding:.75em .85em .8em;
  border-top:.24em solid #f0500c;display:flex;flex-direction:column}
.aira .a-tiles .k{font-family:var(--mono);font-size:.84em;letter-spacing:.11em;
  color:#cfc7bb;min-height:2.3em;line-height:1.3}
.aira .a-tiles .n{font-size:2.05em;letter-spacing:-.01em;line-height:1.1;
  margin:.12em 0 .1em;-webkit-text-stroke:.6px currentColor}
.aira .a-tiles .s{font-size:.92em;color:#a9a29a}
.aira dl.a-rows{margin:.8em 0 0}
.aira dl.a-rows>div{display:flex;justify-content:space-between;gap:1.4em;
  align-items:baseline;padding:.4em 0;border-bottom:1px solid rgba(21,17,12,.14)}
.aira dl.a-rows dt{font-size:1.02em}
.aira dl.a-rows dd{margin:0;font-size:1.02em;text-align:right;white-space:nowrap;
  -webkit-text-stroke:.4px currentColor}
.aira dl.a-rows dd em{font-style:italic;color:#7d7568;-webkit-text-stroke:0}
.aira dl.a-rows dd s{text-decoration:none;color:#e0400a;font-size:.9em}
.aira .a-verdict{background:#fdf0e6;border-left:.3em solid #f0500c;
  padding:.7em 1em;margin:.85em 0 0;font-size:1.06em}
.aira .a-verdict i{font-style:normal;color:#e0400a;-webkit-text-stroke:.5px currentColor}
.aira .a-defs{display:grid;grid-template-columns:1fr 1fr;gap:.5em 1.9em;font-size:.95em}
.aira .a-defs p{margin:0 0 .4em;color:#3f3930}
.aira .a-levels{margin-top:.5em;font-size:.95em;color:#3f3930}
.aira .a-levels div{display:flex;gap:.6em;margin-bottom:.3em}
.aira .a-levels s{text-decoration:none;flex:0 0 .72em;height:.72em;border-radius:50%;
  margin-top:.36em}
.aira .a-foot{margin-top:auto;padding-top:.8em;border-top:.16em solid #f0500c;
  display:flex;align-items:center;justify-content:space-between;
  font-family:var(--mono);font-size:.86em;letter-spacing:.14em;color:#7d7568}
.aira .a-foot .a-logo{font-size:1.5em;-webkit-text-stroke:.5px currentColor;
  color:#15110c;margin-right:.55em;vertical-align:middle}

.quotes{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:26px}

/* ---------- a section whose visual sits BESIDE the copy, then BETWEEN it ----
   #platform and #offer are heading + body + a visual. On a desktop the visual
   goes beside the copy. On a phone there is no beside, and both sections were
   holding their two columns anyway -- the mobile rule below only ever collapsed
   `.hero .cols`, and both of these set grid-template-columns INLINE, which no
   media query can outrank. So a 390px screen was showing two columns of about
   170px each and squeezing everything in them.

   Stacked, the visual wants to be under the HEADING rather than after the body:
   it is what the heading is talking about, and a reader should meet it before a
   paragraph of prose rather than after one. That needs three children on a phone
   and two on a desktop, which is what `display:contents` gives -- the copy
   wrapper stops generating a box, so head and body become flex items in their
   own right and `order` can put the visual between them. No duplicated heading,
   and no moving nodes with JS. */
.split{display:grid;align-items:center;
  grid-template-columns:minmax(0,var(--split-a,1.05fr)) minmax(0,var(--split-b,.95fr));
  /* the gap is a variable because the two sections were authored with DIFFERENT
     ones -- 74 on #platform, 64 on #offer -- and unifying them moved the whole
     assessment panel 131px on the desktop. Caught by the geometry guard, which is
     the entire reason it exists. */
  gap:var(--split-gap,clamp(34px,5vw,74px))}
.quote{display:flex;flex-direction:column}
/* The orange rule is now an opt-in, and only the SOLO pull-quote opts in. In the
   three-up it was doing the job the industry chip does -- giving each card a top
   edge -- and two markers for one job read as decoration. On a pillar page the
   quote stands alone beside a bordered claim card and the rule is the only thing
   giving it an edge at all, so there it stays. */
.quote--solo{border-top:2px solid var(--orange);padding-top:22px}
.quote cite{margin-top:auto}
.quote p{font-size:1.24rem;line-height:1.3;margin-bottom:16px}
.quote cite{font-family:var(--mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  font-style:normal;color:var(--faint);line-height:1.8;display:block}
.quote cite b{color:var(--dim);font-weight:500}
.quote .ind{align-self:flex-start;border:1px solid var(--line-paper);
  padding:6px 9px;margin-bottom:15px;font-family:var(--mono);font-size:8.5px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--paper-dim)}
.paper .quote cite b{color:var(--paper-dim)}
/* A cite carrying a face lays out as a row. Scoped to .who rather than to every
   cite: pillar() emits the same element with a bare string inside, and flexing
   that would turn its <br> and <b> into separate anonymous flex items. */
.quote cite.who{display:flex;align-items:center;gap:13px}
.quote .av{width:38px;height:38px;flex:0 0 38px;border-radius:50%;object-fit:cover;
  display:block;background:var(--line)}
.paper .quote .av{background:var(--line-paper)}

/* ---------- the index: blog rows, and the industry blocks ----------------
   Both new pages are TEXT. No post has an image and no industry has a photo,
   and inventing art for them would be worse than not having it -- so neither
   page is a card grid. A card whose picture is missing is a box of air; a row
   is just a row.

   The blog index is a table of contents: number, date, title, tag, and the
   columns are what make it scan -- the eye runs DOWN the dates or down the
   titles rather than across each entry in turn. It reads the same at seven
   posts and at seventy, which is the other reason it is not a grid: a
   three-column wall of seven leaves a hole, and then the hole needs solving
   the way the people wall's did. */
/* pulled out by the row's own inset so the RULES line up with the heading
   above and the archive below, while the text inside sits in from them. */
.ix{border-top:1px solid var(--line);
  margin:0 calc(-1 * clamp(14px,1.6vw,24px))}
.paper .ix{border-color:var(--line-paper)}
/* CENTRED, not baseline-aligned. The rows carry no excerpt, so most are one
   line and either looks the same -- but a title long enough to wrap left the
   date and the tag pinned to its FIRST line with the second hanging below,
   which reads as two rows fused. */
.ixrow{display:grid;grid-template-columns:11rem minmax(0,1fr) 11rem;
  gap:clamp(14px,2.2vw,38px);align-items:center;
  /* INSET FROM THE RULE. The hairline still runs the full width -- it is the
     list's edge -- but the date sat hard against its left end with nothing
     between them, which reads as the text falling off the row. */
  padding:17px clamp(14px,1.6vw,24px);
  border-bottom:1px solid var(--line);text-decoration:none;color:inherit;transition:.18s}
.paper .ixrow{border-color:var(--line-paper)}
.ixrow:hover{background:rgba(var(--ink-rgb),.035)}
.paper .ixrow:hover{background:rgba(var(--bg-rgb),.03)}
.ixrow:hover .ixrow__t{color:var(--orange)}
.ixrow__d{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--faint)}
.paper .ixrow__d{color:var(--paper-dim)}
/* the read time sits under the TITLE. It is a property of the piece, not of
   when it went out, and under the date it read as part of the timestamp. */
.ixrow__r{display:block;font-family:var(--mono);font-size:9px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--faint);margin-top:6px}
.paper .ixrow__r{color:var(--paper-dim)}
/* THE BODY FACE, NOT THE DISPLAY ONE. Thirty titles in Courbe made a wall of
   it. Courbe is right for one heading at the top of a page; a list of thirty
   is not that, and the display face has no business being the page's texture. */
.ixrow__t{display:block;font-family:var(--sans);font-weight:400;
  font-size:clamp(1rem,1.35vw,1.15rem);line-height:1.3;transition:.18s}
.ixrow__g{justify-self:start;font-family:var(--mono);font-size:8.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--faint);border:1px solid var(--line);
  padding:6px 9px;white-space:nowrap}
.paper .ixrow__g{color:var(--paper-dim);border-color:var(--line-paper)}
@media(max-width:900px){
  .ixrow{grid-template-columns:1fr;row-gap:7px;align-items:start}
}

/* MORE AIR ABOVE THE FIRST THING ON THE PAGE. The nav is a floating pane over
   the content, not a bar the content starts below, so a section's normal top
   padding puts the eyebrow almost under it. */
.blogtop{padding-top:clamp(150px,19vh,230px)}
/* The masthead: the page's name, what it is, and the offer -- centred, in that
   order. The eyebrow is gone: it said "Blog" directly above a heading that now
   says Blog, which is the same word twice for no reader.

   Centred here and left-aligned everywhere else on the site on purpose. This is
   a masthead over a list, not a section of a page that continues past it -- the
   content below it is a column of rows, and a centred masthead reads as a title
   over that column rather than as the first item in it. */
.blogintro{text-align:center;margin-bottom:clamp(30px,4.5vh,52px)}
.blogintro h1{margin:0}
.blogintro p{font-family:var(--sans);font-size:1.05rem;line-height:1.5;
  color:var(--paper-dim);margin:14px auto 0;max-width:40ch}
.blogintro .row{justify-content:center;margin-top:clamp(22px,3vh,30px)}
/* the featured post. Its own block rather than a wide row: it is the one item
   on the page that carries a deck and a byline, and forcing those through the
   row grid would have meant a row grid that exists for one item. */
.ixlead{display:block;text-decoration:none;color:inherit;padding:clamp(26px,3.5vw,44px) 0;
  border-top:1px solid var(--line-paper);margin-top:clamp(28px,4vh,46px)}
.ixlead:hover .ixlead__t{color:var(--orange)}
.ixlead__m{display:flex;align-items:center;gap:12px;font-family:var(--mono);
  font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--paper-dim)}
.ixlead__m--under{margin:0 0 14px}
.ixlead__t{display:block;font-family:var(--display);font-weight:400;
  /* 12.71em, not 20ch, and they are the same width to a pixel: Courbe's "0"
     advance measures 0.6354em, so 20ch is 12.708em at every size the clamp
     reaches. `ch` is a GLYPH METRIC, and an element that exists before the face
     lands keeps the cap it resolved against the fallback -- this box measured
     443, 455, 466 and 488 across identical loads. `em` is font-size, which is
     never stale. Same rendering, one less thing that depends on WHEN a font
     arrives. */
  font-size:clamp(1.6rem,3vw,2.4rem);line-height:1.1;max-width:12.71em;
  margin:14px 0 10px;transition:.18s}
.ixlead__x{display:block;font-family:var(--sans);font-size:1.05rem;line-height:1.5;
  color:var(--paper-dim);max-width:56ch}
/* the same cut-out /about uses, so there is no second crop to keep in step */
.ixlead__by{display:flex;align-items:center;gap:11px;margin-top:22px;
  font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase}
.ixlead__by img{width:34px;height:34px;border-radius:50%;object-fit:cover;
  background:var(--line-paper);filter:grayscale(1)}
.ixlead__by b{font-weight:500;color:var(--paper-ink)}
.ixlead__by i{font-style:normal;color:var(--paper-dim)}
/* the archive is navigation, not content, so it is set like navigation */
.ixarch{display:flex;gap:24px;flex-wrap:wrap;padding:32px 0 0;font-family:var(--mono);
  font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.paper .ixarch{color:var(--paper-dim)}
.ixarch a{color:var(--ink);text-decoration:none;border-bottom:1px solid var(--line)}
.paper .ixarch a{color:var(--paper-ink);border-color:var(--line-paper)}
.ixarch a:hover{color:var(--orange);border-color:var(--orange)}

/* The industries read as blocks rather than rows: their copy is a paragraph,
   not a one-line excerpt, and eight tall rows is a page nobody reaches the
   bottom of. Same hairline grid as .tiles, given room to hold prose. */
.inds{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;
  background:var(--line);border:1px solid var(--line)}
.paper .inds{background:var(--line-paper);border-color:var(--line-paper)}
.inds>div{background:var(--bg);padding:clamp(26px,2.6vw,38px)}
.paper .inds>div{background:var(--paper)}
.inds .n{font-family:var(--mono);font-size:10px;letter-spacing:.16em;color:var(--faint)}
.paper .inds .n{color:var(--paper-dim)}
.inds h3{font-family:var(--display);font-weight:400;font-size:clamp(1.15rem,1.7vw,1.5rem);
  line-height:1.15;margin:12px 0 12px}
.inds p{font-family:var(--sans);font-size:.95rem;line-height:1.5;color:var(--dim);max-width:46ch}
.paper .inds p{color:var(--paper-dim)}
/* A block with no copy yet says so, in the same voice the placeholder art
   uses. It is not filled with invented claims about a sector we have not
   written about -- an industry page that says the wrong thing about a
   regulated sector is worse than one that says nothing. */
@media(max-width:760px){.inds{grid-template-columns:1fr}}

/* tiles */
.tiles{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:var(--line);
  border:1px solid var(--line)}
.tiles a{background:var(--bg);padding:26px 20px;display:flex;flex-direction:column;gap:9px;transition:.16s}
.tiles a:hover{background:rgba(var(--ink-rgb),.04)}
.paper .tiles{background:var(--line-paper);border-color:var(--line-paper)}
.paper .tiles a{background:var(--paper)}
.paper .tiles a:hover{background:rgba(var(--bg-rgb),.04)}
.tiles .i{font-family:var(--mono);font-size:9.5px;letter-spacing:.14em;color:var(--faint)}
.tiles .t{font-family:var(--sans);font-size:1.14rem;line-height:1.22;
  text-transform:none;letter-spacing:-.004em;-webkit-text-stroke:.35px currentColor}

/* offer band */
.offer{background:var(--orange);color:#160d05;border-top:none}
.offer .kick{color:rgba(22,13,5,.62)}
.offer .lede{color:rgba(22,13,5,.8);max-width:58ch}
.offer .btn{border-color:rgba(22,13,5,.34);color:#160d05}
.offer .btn:hover{border-color:#160d05}
.offer .btn--p{background:#160d05;border-color:#160d05;color:var(--orange)}
.offer .small{color:rgba(22,13,5,.6)}

/* ---------- the consent banner ----------------------------------------------
   Shown once, until a choice is made; both answers are remembered equally. */
.consent{position:fixed;left:0;right:0;bottom:0;z-index:390;background:var(--bg);
  border-top:1px solid var(--line);padding:16px clamp(18px,4vw,34px);
  display:flex;align-items:center;justify-content:center;gap:22px;flex-wrap:wrap}
.consent p{font-size:.9rem;color:var(--dim);margin:0;max-width:52ch}
.consent a{color:var(--ink);text-decoration:underline}
.consent__row{display:flex;gap:10px}
.consent .btn{padding:9px 16px;font-size:.8rem}

/* a centered narrative section -- the AI shift's treatment, as a CLASS.
   The first pass copied the treatment as inline styles and missed that
   .prose p carries a max-width with no auto margins, so every paragraph
   hugged the left edge of a centered wrap: eyebrow and heading centered,
   body off-center. The homepage never showed it because its paragraphs
   carry inline auto margins individually. One class, every rule together. */
.wrapc{max-width:900px;text-align:center}
.wrapc .kick{justify-content:center}
.wrapc h1,.wrapc h2{margin-left:auto;margin-right:auto}
.wrapc .prose{margin:0 auto}
.wrapc .prose p{margin-left:auto;margin-right:auto}

/* case-study links under an industry block: mono meta, the site's own link
   gesture, separated from the copy by a hairline so they read as an appendix
   to the claim rather than more of it */
.cslinks{margin-top:14px;padding-top:12px;border-top:2px solid var(--orange);
  display:flex;flex-direction:column;gap:7px;align-items:flex-start}
.cslinks span{font-family:var(--mono);font-size:8.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--orange)}
.cslinks a{font-family:var(--sans);font-size:.92rem;color:var(--paper-ink);
  text-decoration:none;border-bottom:1px solid transparent;transition:.18s}
.cslinks a:hover{color:var(--orange);border-bottom-color:var(--orange)}

/* ---------- the lead form ---------------------------------------------------
   One modal for every CTA; assets/lead.js injects the markup. Styled on the
   PAGE ground tokens so it reads correctly over light and dark pages alike --
   the panel is always paper carrying ink, whatever is behind the veil. */
.lead{position:fixed;inset:0;z-index:400;display:none;align-items:center;
  justify-content:center;padding:20px;background:rgba(8,6,4,.55)}
.lead.open{display:flex}
html.lead-open{overflow:hidden}
.lead__panel{position:relative;width:min(460px,100%);max-height:92vh;overflow:auto;
  background:var(--bg);color:var(--ink);border:1px solid var(--line);
  padding:clamp(26px,4vw,40px)}
.lead__x{position:absolute;top:10px;right:14px;border:0;background:none;
  font-size:26px;line-height:1;color:var(--dim);cursor:pointer}
.lead__sub{font-size:.95rem;line-height:1.55;color:var(--dim);margin:14px 0 20px}
.lead__form label{display:block;margin-bottom:14px;font-family:var(--mono);
  font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.lead__form input,.lead__form textarea{display:block;width:100%;margin-top:6px;
  background:none;border:1px solid var(--line);color:var(--ink);
  font-family:var(--sans);font-size:1rem;padding:10px 12px;border-radius:0}
.lead__form input:focus,.lead__form textarea:focus{outline:2px solid var(--orange);
  outline-offset:0;border-color:transparent}
.lead__hp{position:absolute!important;left:-9999px;width:1px;height:1px;overflow:hidden}
.lead__err{color:var(--orange);font-family:var(--sans);font-size:.9rem;
  min-height:1.3em;margin-bottom:10px}
.lead__done p{font-size:1rem;color:var(--dim);margin-top:12px}

/* cta band */
.ctaband{text-align:center;border-top:1px solid var(--line)}
.ctaband .kick{justify-content:center}
.ctaband h2{margin:0 auto;max-width:20ch}
.ctaband .row{justify-content:center}

/* THE MOBILE GATE IS GONE. Below 900px, and on any touch device under 1280,
   every page used to be replaced with "please open this on a desktop". It was
   honest when it was written -- there was no small-screen layout underneath it
   -- but it had become far more conservative than the CSS: measured at 390px,
   not one page overflows and seven of the eight render correctly. The gate was
   hiding a working mobile site.

   What is NOT yet handled is the home page's pinned #operate, which holds 4.2
   screens of scroll on a phone with the room clipped to an unreadable strip.
   That is one section of one page, and it is being fixed rather than hidden.

   The @media block this comment used to introduce was deleted with the gate, and
   its closing brace was left behind. A stray } at the top level of a stylesheet
   is a parse error, and Chrome recovers by discarding input through the end of
   the NEXT block -- so it silently ate the rule below it, .trio, and #soc-for-ai
   rendered its three cards stacked on the desktop from that merge onward. One
   character, one rule, no warning anywhere. build.sh now balances the braces. */

/* ---- Inside SOC for AI ----------------------------------------------------
   Three cards in a row, in a section that scrolls past like any other. It was a
   320vh pin with the deck flying through it (madewithgsap 018) until 2026-08-28;
   that came out because the site was speaking five different motion vocabularies
   and this one was the loudest. `git log -- site/_src/experience/engine.js` has
   the timeline if it is ever wanted back.

   The section is LIGHT from 2026-08-28 and #platform took the dark band it used
   to carry, so the page alternates properly again. The cards need no treatment of
   their own now: on the page's own ground they are just cards. */
.trio{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:22px}
/* A break that only exists once there is room for the line it makes. Below the
   section's own breakpoint the heading wraps on its own and a forced break just
   makes a short third line. splitChars() carries this class onto the <br> it
   rebuilds -- without that the mask pass would turn it into a plain <br> and the
   media query would have nothing to switch off. */
.br-wide{display:none}
@media (min-width:901px){.br-wide{display:inline}}

/* ---- the letter mask -------------------------------------------------------
   Lifted from the experience shell so the About page can use it too. Three
   load-bearing details, all learned the hard way over there:

   A WORD is one unbreakable unit. Every character being an inline-block gives
   the browser a soft-wrap opportunity between each one, so "YOUR" broke as
   "Y / OUR" across a line end. Only the real spaces between words may break.

   The mask needs HEADROOM. Sized to the line box alone it clips whatever the
   glyph puts outside it -- Swav's Y lost the tops of both arms. The padding
   grows the clipping window; the matching negative margin takes that growth
   back out of layout, so the heading sits exactly where it did.

   And .mo-hide is only ever set BY the script, so a page whose JS never runs has
   nothing invisible left behind. */
.wd{display:inline-block;white-space:nowrap;vertical-align:top}
.ltr{display:inline-block;overflow:hidden;vertical-align:top;
  padding:0.17em 0;margin:-0.17em 0}
.ltr>i{display:block;font-style:normal;will-change:transform}
.mo-hide{opacity:0}

/* ---- ABOUT: the field behind the page --------------------------------------
   A fixed canvas UNDER everything. `body` gives up its ground so the canvas can
   be it; `html` keeps one so there is never a white flash before the first
   frame. The team wall keeps its own opaque paper background and simply covers
   the canvas, which is the whole reason this version needs none of the
   band-flipping machinery the first attempt did. Nothing is transparent until
   about.js has confirmed it can draw. */
.fieldbg{position:fixed;inset:0;z-index:-1;pointer-events:none;display:block}
html.fieldbg-on{background:var(--bg)}
html.fieldbg-on body{background:transparent}

/* ---- ONE BLACK ON THIS PAGE -----------------------------------------------
   /about had three, and side by side the lightest read as grey:

     --bg      #15110c   sRGB mean 17.5   the page ground and every dark section
     card face #000000   sRGB mean  0.0   the eighteen team plates
     footer    #100d09   sRGB mean 13.3

   The field lifts the ground a further ~3.7, so it rendered near 21 against
   cards at 0 -- which is exactly why the background looked light beside the wall.

   The odd one out was the CARD, and it was mine: it went flat #000 on the claim
   that the cut-outs' soft edges carry black and would halo over a warm ground.
   That was never tested and it is FALSE -- the semi-transparent edge pixels
   average 55,66,81, the subject's own colour, because the mask was cut from the
   photograph rather than composited onto black.

   So this could have gone either way and it goes DARK: one black, #000, because
   a wall of black plates is what the page is built around and the ground should
   agree with it rather than the other way about. The brand's warm near-black is
   two lines away if it reads too hard -- drop both rules below. */
html.deep{--bg:#000}
html.deep footer{background:#000}

/* ---- ABOUT: the people grid, after madewithgsap 095 ----------------------
   Replaced the 094 orbit and the 056 rails together. Both are gone on purpose:
   the orbit was decoration around copy nobody could read past, and the rails
   put every bio behind `visibility:hidden`, which took them out of the
   accessibility tree and out of find-in-page. Measured before removal, on
   staging: 0 of 12 bios and 3 of 12 names were reachable.

   MOTION MAY REVEAL CONTENT, NEVER GATE IT. Every card and every bio is in the
   DOM at all times here. The flip is `backface-visibility`, which hides a face
   visually while leaving it in the tree, so a screen reader reads all 18 bios
   and Cmd-F finds any surname whichever way a card is facing. Without JS the
   grid is a plain wall of photographs with names on them, and every card still
   flips, because the flip is a CSS class the button toggles rather than a
   tween. */
/* The section is a SCREEN: heading at the top, wall of faces filling what is
   left. min-height rather than height so a short window scrolls instead of
   cropping the bottom row. */
.pgrid{overflow:hidden;min-height:100vh;display:flex;flex-direction:column;
  justify-content:center;padding:clamp(96px,13vh,150px) 0 clamp(40px,7vh,90px)}
/* A FLEX ITEM WITH `margin:auto` ON THE CROSS AXIS SHRINKS TO ITS CONTENT.
   Both children carry `margin:... auto` to centre themselves, which is right in
   normal flow and wrong here: it took the grid to 132px and every card to 0x0.
   The explicit width is what puts the stretch back; max-width still caps it. */
.pgrid>.wrap,.pgrid>.pg{width:100%}
/* the line that replaced the "Where we are" section, and the CTA that replaced
   the closing band. Both were their own full sections; folding them in here is
   what lets the page end on the team rather than on two thin epilogues. Neither
   needs to reintroduce itself -- "Talk to us." works because it sits directly
   under the eighteen people it means. */
.pg__where{max-width:46ch;margin:14px auto 0}
.pg__cta{max-width:1040px;text-align:center;margin-top:clamp(38px,6vh,74px)}
.pg__cta h3{margin-bottom:20px}
.pg__cta .row{justify-content:center}

/* ONE PIXEL. The cards are effectively butted together and the wall reads as a
   single black slab with eighteen people standing in it, the gutter surviving
   only as a hairline rule between them. Trying it at this width deliberately:
   the risk is that eighteen black plates stop reading as cards at all, and the
   hairline is the last thing keeping them apart. */
/* FLEX, NOT GRID, AND THE REASON IS THE LAST ROW. A grid of 1fr columns always
   fills, so a roster that is not a multiple of the column count leaves the
   remainder flush left with a hole beside it -- and the roster changes every
   time somebody joins or leaves, at three different column counts. Flex wrap
   centres whatever is left over, at any count, without anything knowing how many
   people there are. `--pgc` is the row width, so the width arithmetic and the
   breakpoints stay in one place -- and because every row is that width, wrapping
   does almost the whole job on its own -- see .pg__brk below for the one case it
   cannot. The width itself comes from the roster, via _wall_width(). */
.pg{display:flex;flex-wrap:wrap;justify-content:center;gap:1px;
  --pgc:var(--pg-wide, 6); --pgn:6;
  /* THE WALL NARROWS WITH THE ROW, SO THE CARD DOES NOT GROW. A card is the
     container divided by however many share a row, so dropping from six to five
     to keep the last row honest would otherwise make every card 20% bigger --
     which it did, and a bio set to a card that size floats in the middle of it.
     Scaling the container by the same ratio holds the card at the size the
     breakpoint intends, whatever the roster does to the row width. The gutters
     are held out of the ratio because they are a margin, not content. */
  width:calc(var(--gut) * 2 + (100% - var(--gut) * 2) * var(--pgc) / var(--pgn));
  max-width:calc(var(--gut) * 2 + (1180px - var(--gut) * 2) * var(--pgc) / var(--pgn));
  margin:clamp(30px,4.5vh,56px) auto 0;padding:0 var(--gut);
  perspective:1400px}
.pg>.pg__card{flex:0 0 calc((100% - (var(--pgc) - 1) * 1px) / var(--pgc))}
/* The orphan guard, and only that -- see team_grid(). Present at most once per
   breakpoint and usually not at all; display:none takes it out of flex layout
   entirely, so the breakpoint that does not want it does not have it. */
.pg__brk{display:none;flex:0 0 100%;height:0;margin:0}
@media (min-width:1181px){.pg__brk--6{display:block}}
/* A real button: focusable, Enter and Space for free, state announced by
   aria-pressed. Everything a button brings by default has to be reset, and the
   reset is the reason it is worth doing rather than a div with a handler. */
.pg__card{appearance:none;background:none;border:0;padding:0;margin:0;
  font:inherit;color:inherit;text-align:left;cursor:pointer;
  position:relative;aspect-ratio:1/1;transform-style:preserve-3d;
  will-change:transform;z-index:1}
.pg__card:focus-visible{outline:2px solid var(--orange);outline-offset:3px;z-index:250!important}
/* The active card lifts above EVERY row. about.js gives each card a base z that
   descends by row (see `stack()` there) so the overlapping wall keeps its name
   tags readable; these two have to clear all of that, hence the jump. */
.pg__card.is-near{z-index:200!important}
.pg__card.is-open{z-index:300!important}
.pg__inner{position:absolute;inset:0;transform-style:preserve-3d;
  transition:transform .62s cubic-bezier(.2,.9,.25,1)}
.pg__card.is-open .pg__inner{transform:rotateY(180deg)}
/* No stroke. Overlapping cards need SOME separation or the wall turns to mush,
   so a soft shadow does it instead of a line -- it reads as depth, which is what
   overlapping things actually have. */
/* #000, which is now the page's ONLY black -- see the note by html.deep. It was
   set here originally on a claim about the cut-outs haloing over a warm ground,
   which was never tested and turns out to be false. It survives because the
   ground came down to meet it, not because of that. */
/* NO SHADOW AT REST. It was there to separate cards that overlapped; on a square
   wall it only smudges eighteen clean edges. It arrives with the lift instead,
   which is when a card actually is above the others. */
.pg__face{position:absolute;inset:0;backface-visibility:hidden;
  -webkit-backface-visibility:hidden;overflow:hidden;background:#000;
  box-shadow:0 0 0 rgba(8,6,4,0);transition:box-shadow .45s ease}
.pg__card.is-near .pg__face,
.pg__card.is-open .pg__face,
.pg__card:focus-visible .pg__face{
  box-shadow:0 14px 40px rgba(8,6,4,.42), 0 2px 6px rgba(8,6,4,.30)}
.pg__face--back{background:var(--paper)}
/* TWO PHOTOGRAPHS PER CARD, cross-fading.

   At rest: the cut-out, in black and white, on the card's own black. The
   background is gone from the picture ITSELF -- each headshot went through
   Vision's foreground-instance mask (see assets/about/people-cut/README.txt) --
   so what stands there is the person and nothing else. An earlier version did
   this with `brightness(0)` over a full-bleed photograph and got a black SQUARE,
   which read as an image that had failed to load.

   Under the pointer: the original frame, in colour, background and all. So the
   card does not merely light up, it fills in -- the room comes back around the
   person.

   THE TWO ARE THE SAME PIXELS AT THE SAME SIZE. Vision preserves the source
   dimensions, so the cut-out and its original are identical frames and the
   subject does not move by so much as a pixel across the fade. That only holds
   while both carry the SAME object-fit -- `contain` on one and `cover` on the
   other would rescale the person mid-dissolve. Both are `cover`; the sources are
   square within a pixel or two, so nothing is cropped. */
.pg__shot{position:absolute;inset:0;width:100%;height:100%;display:block;
  object-fit:cover;object-position:center;
  transition:opacity .5s cubic-bezier(.2,.8,.3,1)}
.pg__shot--cut{filter:grayscale(1) contrast(1.06)}
.pg__shot--full{opacity:0}
.pg__card.is-near .pg__shot--full,
.pg__card.is-open .pg__shot--full,
.pg__card:focus-visible .pg__shot--full{opacity:1}
/* the name sits ON the photograph, over a scrim rather than a flat bar, so the
   picture runs to the edge of the card and the type still holds its contrast */
/* The scrim stays exactly as it was and needs no state of its own: near-black
   at 86% over a plate that is already black is imperceptible, and the moment the
   photograph arrives it is doing its original job of holding the type off it. */
.pg__tag{position:absolute;left:0;right:0;bottom:0;padding:26px 12px 11px;
  display:block;color:#f4efe4;
  background:linear-gradient(180deg,transparent,rgba(8,6,4,.86) 62%)}
.pg__tag b{display:block;font-family:var(--display);font-weight:400;
  font-size:clamp(.72rem,.92vw,.9rem);line-height:1.1;letter-spacing:.004em}
.pg__tag span{display:block;margin-top:3px;font-family:var(--mono);
  font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;
  color:rgba(244,239,228,.72)}
/* the back: paper, because a wall of dark cards with one light one open reads
   as a hole rather than as a card that turned over */
.pg__face--back{transform:rotateY(180deg);background:var(--paper);
  color:var(--paper-ink);padding:clamp(11px,1vw,16px);
  display:flex;flex-direction:column;gap:8px}
.pg__who b{display:block;font-family:var(--display);font-weight:400;
  font-size:clamp(.7rem,.85vw,.84rem);line-height:1.1}
.pg__who span{display:block;margin-top:2px;font-family:var(--mono);font-size:8px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--paper-dim)}
/* THE OPEN CARD IS SCALED 2.2x, so these sizes are LAYOUT sizes and what you
   read is 2.2 times bigger. 6.6px renders at about 14.5px, which is why the
   numbers here look wrong on their own. The type is set small in layout space so
   the whole bio fits without scrolling; every earlier attempt that kept a normal
   size ended up with a scrolling box inside a card, which is not reading.

   PER BIO, but only ever larger. The rails also fitted each bio to its own box
   and that was exactly backwards -- it set the longest, most substantial bios
   smallest. Here 6.6px is a FLOOR, the size the longest bio already fits at, and
   the scale on each card can only open the type up from there for a shorter one.
   Before this they were all set to the longest one and filled 60-83% of the box,
   median 75%; now it is 83-93%. See _bio_scale() for the arithmetic. */
.pg__bio{flex:1 1 auto;overflow-y:auto;line-height:1.52;
  /* per-bio, so each card fills rather than every card being set to the longest
     one -- see _bio_scale(). The fallback is the old fixed size. */
  font-size:calc(var(--bio, 1) * 6.6px);
  color:var(--paper-dim);scrollbar-width:thin;-webkit-overflow-scrolling:touch}
/* Ten of the eighteen bios are in the person's own voice and eight are written
   about them. Quoting and italicising the spoken ones is what stops the wall
   reading as if it cannot decide -- the reader can see at a glance which are
   somebody talking and which are a description, instead of hearing the voice
   change with no warning between one card and the next. */
.pg__bio--said{font-style:italic}
.pg__card.is-open .pg__face--back{padding:9px 10px}
.pg__card.is-open .pg__who b{font-size:8.5px}
.pg__card.is-open .pg__who span{font-size:5.2px;letter-spacing:.1em}
@media (max-width:1180px){.pg{--pgc:var(--pg-mid, 4);--pgn:4} .pg__brk--4{display:block}}
@media (max-width:760px){.pg{--pgc:2;--pgn:2}}
/* Nothing scatters and nothing is pushed when less motion is asked for. The
   grid is then exactly what it looks like -- a wall of faces -- and the flip,
   which is the part that carries content, still works. */
@media (prefers-reduced-motion:reduce){
  .pg__card{transform:none!important}
  .pg__inner{transition-duration:.001s}
}

/* the About hero ------------------------------------------------------------ */
.ahero{position:relative;min-height:100vh;display:flex;flex-direction:column;
  justify-content:center;
  /* the top pad clears the floating nav; the section centres what is left, so
     the copy sits in the middle of the screen rather than under the pane */
  padding:clamp(120px,16vh,190px) 0 clamp(60px,9vh,110px)}
/* same shrink-to-fit trap as .pgrid above */
.ahero>.wrap{width:100%;position:relative}
/* A SEAT FOR THE COPY. The field is ~1.5% inked and the lede is set in --dim;
   over 1s and 0s it was legible but working for it. A soft radial pool of the
   page's own ground, sized to the copy and feathered to nothing, gives the type
   somewhere to sit without putting a box on the page or thinning the field.

   Only where there IS a field: unmounted -- no WebGL, reduced motion, a failed
   import -- the section is already flat ground and a scrim on top of it would
   just be a smudge. */
html.fieldbg-on .ahero>.wrap::before{content:'';position:absolute;
  left:50%;top:50%;translate:-50% -50%;
  width:min(1180px,132%);height:min(760px,150%);z-index:-1;pointer-events:none;
  /* CLOSEST-SIDE, not the default farthest-corner. Sized to the corners, the
     ellipse is still 45% opaque where it meets the middle of each edge -- and
     an element's edge is a hard line, so the pool ended in four visible
     straight cuts and read as a rectangle. Reaching zero at the nearest sides
     means every boundary is already transparent and there is nothing to see
     the end of. */
  background:radial-gradient(ellipse closest-side at center,
    rgba(0,0,0,.90) 0%, rgba(0,0,0,.78) 42%,
    rgba(0,0,0,.40) 70%, rgba(0,0,0,0) 100%)}

/* related */
.rel{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:22px}
.rel a{border:1px solid var(--line);padding:26px;display:flex;flex-direction:column;gap:10px;transition:.2s}
.rel a:hover{border-color:rgba(var(--ink-rgb),.3);transform:translateY(-3px)}

/* footer
   The ground here is a FIXED near-black that does not invert with the theme, so
   nothing inside it may use a token that does. The experience page runs under
   html.light, where --ink resolves to #15110c -- and the lockup was rendering
   near-black on near-black at a contrast ratio of 1.03, which is to say not
   rendering at all. The footer text sat at 2.68 and both hairlines were black on
   black, so the rules simply were not there.

   Re-declaring the tokens on the element is what fixes all of it at once and
   keeps every rule below written in the normal vocabulary. --line has to be
   restated explicitly: it is declared on :root as rgba(var(--ink-rgb),.12) and
   substituted THERE, so it inherits already-resolved and overriding --ink-rgb
   here would not touch it. Measured after: lockup 16.9, text 6.33, labels 5.0. */
footer{--ink:#f4efe4; --dim:#9c9284; --faint:#8a8073;
  --ink-rgb:244,239,228; --line:rgba(244,239,228,.12);
  border-top:1px solid var(--line);padding:64px 0 46px;background:#100d09}
.fgrid{display:grid;grid-template-columns:1.3fr 1fr 1fr 1fr;gap:34px;margin-bottom:44px}
footer h5{font-family:var(--mono);font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--faint);margin-bottom:16px;font-weight:400}
footer ul{list-style:none;font-size:.92rem;line-height:2;color:var(--dim)}
footer ul a:hover{color:var(--ink)}
.fbot{border-top:1px solid var(--line);padding-top:22px;display:flex;justify-content:space-between;
  gap:20px;flex-wrap:wrap;font-family:var(--mono);font-size:9.5px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--faint)}
.badges{display:flex;gap:9px;flex-wrap:wrap;margin-top:16px}
.badges span{border:1px solid var(--line);padding:7px 10px;font-family:var(--mono);font-size:8.5px;
  letter-spacing:.12em;text-transform:uppercase;color:var(--dim)}
/* ?flat — collapses the full-viewport hero so whole pages can be captured in one image */
.flat .hero{min-height:auto;padding:120px 0 60px}
.flat .hero--solo{padding:150px 0 70px}
.flat .nav{position:absolute}
@media print{*{-webkit-print-color-adjust:exact;print-color-adjust:exact}
  .hero{min-height:auto;padding:60px 0 40px}.nav{position:static}}
@media(max-width:1000px){
  /* .hero .cols is listed as well as bare .cols, NOT redundantly: the base
     rule is `.hero .cols` (0,2,0), and a lone `.cols` here (0,1,0) loses to it
     -- measured: the pillar hero stayed two-column at 500px the moment the
     selector was 'simplified'. Specificity is part of the rule. */
  .cols,.hero .cols,.grid-3,.grid-2,.surf,.pipe,.claims,.quotes,.tiles,.trio,.fgrid,.rel{grid-template-columns:1fr}
  .nav .links{display:none}
  /* one column, and the visual between the heading and the body -- see .split.
     align-items:stretch is not decoration: .split carries `center` for the
     desktop grid, and in a COLUMN flex that is a cross-axis rule, so every child
     shrinks to its content and centres. The heading came out 293px wide and
     centred on a 500px screen, and .aira-wrap -- which is a `container-type:
     inline-size` box whose contents are sized in cqw -- collapsed to ZERO width,
     because a shrink-to-fit container asking its cqw-sized children how wide
     they are has nothing to resolve against. Same family as the margin:auto trap
     in CLAUDE.md, and it fails the same way: every computed style looks sane
     while a whole subtree is 0px. */
  .split{display:flex;flex-direction:column;align-items:stretch;
    gap:clamp(26px,5vw,38px)}
  .split__copy{display:contents}
  .split__head{order:1}
  .split__vis {order:2}
  .split__body{order:3}
  .split__vis > *{width:100%}
  /* TESTIMONIALS GROUP BY PROXIMITY once they are stacked. The industry chip is
     the FIRST child of its own quote, so with a 26px gap between quotes and 15px
     under the chip the two spacings were near enough identical that the chip read
     as belonging to the name above it. Pull the chip onto its quote and push the
     quotes apart: 54 against 10 is not ambiguous. */
  /* THE STRIP RUNS AT THE SAME px/s ON EVERY SCREEN, which reads as crawling
     on a phone: a third of the width means a third as many logos moving, so
     the eye has far less evidence that anything is happening. Shorter cycle,
     same distance -- and the edge fades come in, because 110px of fade on a
     390px screen is 56% of the strip spent fading. */
  .track{animation-duration:18s}
  .marq::before,.marq::after{width:44px}
  .quotes{gap:54px}
  .quote .ind{margin-bottom:10px}
}
