/* ============================================================================
   Fluxaro, Foil direction: the multi-page layer.

   base.css carries the direction Mike picked (paper ground, Cormorant display,
   the foil mechanism, the hairline grid). This file adds everything the full
   site needs that a one-page preview did not: a preloader that hands off to the
   hero, cross-document transitions, a real mobile nav, the work grid, the app
   device frames, the testimonial system, insights, forms and case studies.

   Palette rule, unchanged and non-negotiable: gold is a surface, a hairline, a
   large display fill, or the light in the footage. Never body text, never a
   gradient, never a glow. Readable gold is --brass (#8A6A1F, 5.05:1 on paper).
   ========================================================================== */

:root{
  --nav-h:72px;
  --shadow-1:0 1px 2px rgba(22,19,13,.05), 0 6px 18px rgba(22,19,13,.05);
  --shadow-2:0 2px 6px rgba(22,19,13,.06), 0 18px 48px rgba(22,19,13,.09);
  --shadow-lift:0 8px 20px rgba(22,19,13,.08), 0 30px 70px rgba(22,19,13,.12);
}

/* ---------------------------------------------------------------------------
   1. NO LOADING SCREEN, NO COVER.
   There was a preloader here and a full-page transition band under it. Both
   are gone: these are static documents with a preloaded hero poster, so a
   counter and a curtain were manufacturing a wait that did not exist. What is
   left is the browser's own navigation plus the View Transition below, which
   cross-fades the two documents without ever covering the page.
   ------------------------------------------------------------------------- */
@view-transition{navigation:auto}
@media (prefers-reduced-motion:no-preference){
  ::view-transition-old(root){animation:vt-out .32s cubic-bezier(.3,0,.8,.15) both}
  ::view-transition-new(root){animation:vt-in .46s cubic-bezier(.05,.7,.1,1) both}
}
@keyframes vt-out{to{opacity:0;transform:translateY(-10px)}}
@keyframes vt-in{from{opacity:0;transform:translateY(12px)}}

/* ---------------------------------------------------------------------------
   3. NAV: active route, mobile drawer.
   Primary nav items are real routes, never anchors, so the active state is a
   genuine wayfinding signal rather than a scroll position.
   ------------------------------------------------------------------------- */
.nav__links a[aria-current="page"]{color:var(--ink)}
.nav__links a[aria-current="page"]::after{transform:scaleX(1)}

.burger{
  display:none;width:44px;height:44px;align-items:center;justify-content:center;
  background:none;border:1px solid var(--rule);cursor:pointer;
}
.burger span{display:block;width:17px;height:1px;background:var(--ink);position:relative}
.burger span::before,.burger span::after{content:"";position:absolute;left:0;width:17px;height:1px;background:var(--ink);
  transition:transform .3s var(--e-move),opacity .2s}
.burger span::before{top:-5px}
.burger span::after{top:5px}
.burger[aria-expanded="true"] span{background:transparent}
.burger[aria-expanded="true"] span::before{transform:translateY(5px) rotate(45deg)}
.burger[aria-expanded="true"] span::after{transform:translateY(-5px) rotate(-45deg)}

.navdraw{
  position:fixed;inset:var(--nav-h) 0 0;z-index:95;background:var(--paper);
  display:flex;flex-direction:column;justify-content:center;gap:var(--s-2);
  padding:var(--s-8) var(--gutter);
  clip-path:inset(0 0 100% 0);transition:clip-path .62s var(--e-out);
}
.navdraw[data-open="true"]{clip-path:inset(0 0 0 0)}
.navdraw a{
  font-family:var(--serif);font-size:clamp(2rem,9vw,3.2rem);line-height:1.12;
  color:var(--ink);text-decoration:none;display:block;padding:var(--s-2) 0;
  border-bottom:1px solid var(--rule);
}
.navdraw a[aria-current="page"]{color:var(--brass)}
.navdraw__foot{margin-top:var(--s-6);font-family:var(--mono);font-size:var(--fs-label);
  letter-spacing:.14em;text-transform:uppercase;color:var(--grey)}

@media (max-width:860px){
  .nav__links{display:none}
  .burger{display:inline-flex}
}
@media (min-width:861px){
  .navdraw{display:none}
}

/* ---------------------------------------------------------------------------
   4. SHARED PRIMITIVES used across routes.
   ------------------------------------------------------------------------- */
.lede{font-size:var(--fs-lead);line-height:1.5;color:var(--ink-2);max-width:var(--measure)}
.kicker{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.16em;
  text-transform:uppercase;color:var(--brass)}
.dim{color:var(--grey)}
.center{text-align:center}
.stack>*+*{margin-top:var(--s-4)}

/* Page masthead, used by every route except home. */
.mast{padding-top:calc(var(--nav-h) + clamp(56px,9vw,120px));padding-bottom:clamp(40px,6vw,72px)}
.mast__k{display:flex;align-items:center;gap:var(--s-4);margin-bottom:var(--s-5)}
.mast__k .rule{flex:1}
.mast h1{margin:0 0 var(--s-5)}
.mast__meta{display:flex;flex-wrap:wrap;gap:var(--s-3) var(--s-6);margin-top:var(--s-6);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;
  text-transform:uppercase;color:var(--grey)}

/* Buttons. The primary owns its own text colour so an ancestor link rule can
   never grey it out, which is the classic silent CTA failure. */
.btn2{
  display:inline-flex;align-items:center;gap:var(--s-3);min-height:48px;
  padding:0 var(--s-5);background:var(--ink);color:var(--paper) !important;
  border:1px solid var(--ink);text-decoration:none;
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;text-transform:uppercase;
  transition:background-color var(--d-hover) var(--e-move),color var(--d-hover) var(--e-move);
}
.btn2:hover{background:var(--brass);border-color:var(--brass);color:var(--paper) !important}
.btn2--ghost{background:transparent;color:var(--ink) !important;border-color:var(--ink)}
.btn2--ghost:hover{background:var(--ink);color:var(--paper) !important}
.btn2:focus-visible{outline:2px solid var(--brass);outline-offset:3px}

/* Magnetic wrapper: the button itself never moves more than 6px, which reads
   as attention rather than as a toy. */
[data-mag]{display:inline-block;will-change:transform}

/* ---------------------------------------------------------------------------
   5. WORK GRID and WORK CARD.
   The screenshots are real captures of live deployments. Each card states the
   live host, so a reader can check the claim themselves.
   ------------------------------------------------------------------------- */
.grid2{display:grid;gap:var(--s-8) var(--s-7);grid-template-columns:repeat(auto-fit,minmax(min(100%,420px),1fr))}
.grid3{display:grid;gap:var(--s-7) var(--s-6);grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr))}

.wc{position:relative;display:block;text-decoration:none;color:inherit;isolation:isolate}
/* display:block is load-bearing on every one of these. They are <span>s inside an
   <a>, and aspect-ratio does not apply to a non-replaced INLINE element, so the
   16/10 box was silently ignored: the shot resolved to 558x581, nearly square,
   and object-fit:cover then cut the left and right off every screenshot. The
   work looked broken, which on a portfolio is the worst possible bug. */
.wc__shot{
  position:relative;display:block;overflow:hidden;background:var(--paper-2,#F6F2E9);
  border:1px solid var(--rule);aspect-ratio:16/10;
}
.wc__shot img{
  display:block;width:100%;height:100%;object-fit:cover;object-position:top center;
  transform:scale(1.02);transition:transform 1.1s var(--e-out),filter .6s var(--e-move);
  filter:saturate(.96);
}
.wc:hover .wc__shot img{transform:scale(1.055);filter:saturate(1.03)}
/* The curtain that uncovers a card on scroll entry. --wcv is "how covered",
   100 = fully covering, 0 = slid up and out. The earlier formula was inverted,
   so the tween finished with the curtain back OVER the screenshot and every
   card rendered blank. The inline-display bug was hiding this one.
   wcv 100 -> translateY(0) covering. wcv 0 -> translateY(-100%) gone. */
.wc__shot::after{
  content:"";position:absolute;inset:0;background:var(--paper);
  transform:translateY(calc((100 - var(--wcv,0)) * -1%));
}
.wc__meta{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-4);
  margin-top:var(--s-5);padding-bottom:var(--s-3);border-bottom:1px solid var(--rule)}
.wc__name{display:block;font-family:var(--serif);font-size:var(--fs-h3);line-height:1.1;color:var(--ink)}
.wc__n{display:block;flex:none;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;color:var(--brass)}
.wc__what{display:block;margin:var(--s-3) 0 0;font-size:var(--fs-sm);line-height:1.5;color:var(--ink-2);max-width:52ch}
/* Host always sits on its own line. Left inline it wrapped into the end of the
   description on some cards and not others, which read as a layout accident. */
.wc__host{display:flex;align-items:center;gap:8px;margin-top:var(--s-4);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.1em;color:var(--grey);
  transition:color .25s var(--e-move)}
.wc__host::before{content:"";width:14px;height:1px;background:var(--leaf);flex:none;
  transition:width .35s var(--e-move)}
.wc:hover .wc__host::before{width:26px}
.wc:hover .wc__host{color:var(--brass)}
.wc:focus-visible{outline:2px solid var(--brass);outline-offset:6px}

/* Feature card: the screenshot gets the room, the copy sits beside it. Used for
   the lead project so the work opens big instead of starting as a thumbnail. */
.wc--feature{display:grid;grid-template-columns:1fr;gap:var(--s-5);margin-bottom:var(--s-8)}
@media (min-width:920px){
  .wc--feature{grid-template-columns:1.42fr .58fr;gap:var(--s-8);align-items:center}
  .wc--feature .wc__meta{margin-top:0}
}
.wc--feature .wc__name{font-size:clamp(1.9rem,3.2vw,2.75rem)}
.wc--feature .wc__what{font-size:var(--fs-body);max-width:44ch}
.wc--feature .wc__shot{aspect-ratio:16/10}

/* A quiet index line under the grid: the count, stated. */
.wc__count{display:flex;align-items:baseline;gap:var(--s-4);margin-top:var(--s-7);
  padding-top:var(--s-4);border-top:1px solid var(--rule);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.13em;
  text-transform:uppercase;color:var(--grey)}
.wc__count b{font-family:var(--serif);font-size:1.6rem;color:var(--ink);letter-spacing:0;font-weight:400}

/* App screenshot gallery. The ops console is the only software here with a
   publishable UI, so it gets shown properly rather than as one thumbnail. */
.appshots{display:grid;gap:var(--s-4);margin-top:var(--s-6);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,230px),1fr))}
.appshot{margin:0;border:1px solid var(--rule);background:var(--white);overflow:hidden}
.appshot img{display:block;width:100%;height:auto;transition:transform .9s var(--e-out)}
.appshot:hover img{transform:scale(1.03)}
.appshot figcaption{padding:10px var(--s-4);border-top:1px solid var(--rule);background:var(--paper);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.11em;text-transform:uppercase;color:var(--grey)}

/* Architecture diagrams. Two of the four systems are behind authentication and
   hold real client data, so there is no screenshot to publish. An empty frame
   with a placeholder chip in it was the honest answer but a dead one: it made
   the section that sells the software look unfinished. A drawing of what the
   code actually does is equally honest and actually says something. */
.diag{display:block;width:100%;height:auto;background:var(--paper)}
.diag text{font-family:var(--mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;fill:var(--ink-2)}
.diag .dg-k{fill:var(--brass)}
.diag .dg-box{fill:var(--white);stroke:var(--rule);stroke-width:1}
.diag .dg-box--key{stroke:var(--brass)}
/* fill:none on EVERY stroked path. An SVG <path> fills with black by default,
   so a two-segment leader line without it paints as a solid black wedge. */
.diag path{fill:none}
.diag .dg-line{stroke:var(--rule);stroke-width:1;fill:none}
.diag .dg-line--gold{stroke:var(--leaf);stroke-width:1;fill:none}
.diag .dg-cut{stroke:var(--grey);stroke-width:1;stroke-dasharray:3 3;fill:none}
.dev__diagwrap{padding:var(--s-6) var(--s-5);background:var(--paper)}

/* ---------------------------------------------------------------------------
   ROTATOR. One component, used by the app row and the testimonial row.
   Continuous side travel, pauses on hover and on keyboard focus, draggable,
   and under reduced motion it stops being a conveyor and becomes an ordinary
   horizontal scroller the reader controls.
   ------------------------------------------------------------------------- */
.rot{position:relative;overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent)}
.rot__track{display:flex;gap:var(--s-5);width:max-content;will-change:transform}
.rot__track > *{flex:0 0 auto}
.rot:focus-visible{outline:2px solid var(--brass);outline-offset:4px}
.rot[data-drag="true"]{cursor:grabbing}
.rot[data-drag="false"]{cursor:grab}
@media (prefers-reduced-motion:reduce){
  .rot{overflow-x:auto;scroll-snap-type:x mandatory;-webkit-mask-image:none;mask-image:none}
  .rot__track{width:auto}
  .rot__track > *{scroll-snap-align:start}
}
/* No JS: fall back to a plain scroller rather than a frozen clipped row. */
html.no-js .rot{overflow-x:auto;-webkit-mask-image:none;mask-image:none}

/* App tile. The icons are thin-stroke inline SVG, drawn for these four
   systems: no icon font, no emoji, nothing borrowed. */
.appicon{
  display:flex;flex-direction:column;gap:var(--s-4);width:clamp(232px,26vw,290px);
  padding:var(--s-6);border:1px solid var(--rule);background:var(--white);
  text-decoration:none;color:inherit;position:relative;
  transition:border-color .3s var(--e-move),transform .5s var(--e-out);
}
.appicon:hover{border-color:var(--brass);transform:translateY(-3px)}
.appicon:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
.appicon__ico{width:58px;height:58px;border:1px solid var(--rule);background:var(--paper);
  display:grid;place-items:center;transition:border-color .3s var(--e-move)}
.appicon:hover .appicon__ico{border-color:var(--brass)}
.appicon__ico svg{width:30px;height:30px;stroke:var(--brass);stroke-width:1;fill:none;
  stroke-linecap:round;stroke-linejoin:round}
.appicon__n{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;color:var(--grey)}
.appicon__t{font-family:var(--serif);font-size:1.32rem;line-height:1.15;color:var(--ink);margin:0}
.appicon__d{font-size:var(--fs-sm);line-height:1.5;color:var(--ink-2);margin:0}
.appicon__go{margin-top:auto;font-family:var(--mono);font-size:var(--fs-label);
  letter-spacing:.12em;text-transform:uppercase;color:var(--brass);display:flex;align-items:center;gap:8px}
.appicon__go::before{content:"";width:14px;height:1px;background:var(--leaf);
  transition:width .35s var(--e-move)}
.appicon:hover .appicon__go::before{width:26px}

/* Testimonial card inside the rotator. Sized to what an empty card actually
   holds: at 260px tall the placeholder chips floated in a void and read as a
   rendering gap rather than as a slot waiting to be filled. */
.rot .tst__card{width:clamp(300px,34vw,430px);min-height:200px;justify-content:space-between;gap:var(--s-5)}
.rot .tst__q{font-size:clamp(1.1rem,1.8vw,1.4rem);max-width:none}

/* Tag row */
.tags{display:flex;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--s-4)}
.tag{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-2);border:1px solid var(--rule);padding:5px 9px}

/* ---------------------------------------------------------------------------
   6. APPS: device frames and the pinned app scene.
   A browser chrome drawn in CSS, not an image, so it stays crisp and weighs
   nothing. The dark app screenshots sit inside it as artifacts on the paper.
   ------------------------------------------------------------------------- */
.dev{position:relative;border:1px solid var(--rule);background:var(--white);box-shadow:var(--shadow-2)}
.dev__bar{display:flex;align-items:center;gap:var(--s-3);padding:10px var(--s-4);
  border-bottom:1px solid var(--rule);background:var(--paper)}
.dev__dots{display:flex;gap:6px}
.dev__dots i{width:8px;height:8px;border:1px solid var(--rule);border-radius:50%;display:block}
/* min-width:0 is load-bearing. A flex item defaults to min-width:auto, so this
   nowrap URL refused to shrink below its text width and pushed the whole page
   into horizontal scroll at phone width. */
.dev__url{flex:1;min-width:0;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.06em;color:var(--grey);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dev__view{display:block;width:100%;height:auto}
.dev__cap{margin-top:var(--s-3);font-family:var(--mono);font-size:var(--fs-label);
  letter-spacing:.1em;text-transform:uppercase;color:var(--grey)}

/* Phone frame, for the mobile captures. */
.phone{position:relative;width:min(240px,64vw);margin-inline:auto;border:1px solid var(--rule);
  border-radius:26px;padding:8px;background:var(--white);box-shadow:var(--shadow-2)}
.phone img{display:block;width:100%;height:auto;border-radius:18px}

/* The pinned app scene: the frame holds still, the panels swap under it. */
.apps{position:relative}
.apps__stage{display:grid;gap:var(--s-7);grid-template-columns:1fr;align-items:start}
/* Grid and flex children default to min-width:auto, which means "never shrink
   below your content". The browser-frame URL inside the panel is nowrap, so
   that default propagated all the way up and pushed the page into horizontal
   scroll at phone width. Every link in that chain has to opt out. */
.apps__stage > *,.apps__panels,.apps__panel{min-width:0}
.dev,.dev__bar{max-width:100%;min-width:0}
@media (min-width:1000px){
  .apps__stage{grid-template-columns:minmax(300px,.82fr) 1.18fr;gap:var(--s-9)}
  .apps__rail{position:sticky;top:calc(var(--nav-h) + var(--s-7))}
}
.apps__item{padding:var(--s-5) 0;border-top:1px solid var(--rule);cursor:pointer;
  transition:opacity .4s var(--e-move)}
.apps__item[aria-selected="false"]{opacity:.42}
.apps__item:hover{opacity:.85}
.apps__item[aria-selected="true"]{opacity:1}
.apps__n{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;color:var(--brass)}
.apps__t{font-family:var(--serif);font-size:var(--fs-h3);line-height:1.12;margin:var(--s-2) 0 0;color:var(--ink)}
.apps__d{margin:var(--s-2) 0 0;font-size:var(--fs-sm);color:var(--ink-2);max-width:46ch}
.apps__progress{height:1px;background:var(--rule);margin-top:var(--s-4);position:relative;overflow:hidden}
.apps__progress i{position:absolute;inset:0;background:var(--leaf);transform:scaleX(var(--ap,0));
  transform-origin:left center}
.apps__panel{display:none}
.apps__panel[data-active="true"]{display:block}

/* Spec list reused on platform and case-study routes. */
.sl{margin:0;padding:0;list-style:none;border-top:1px solid var(--rule)}
.sl li{display:grid;grid-template-columns:minmax(96px,var(--sl-k,140px)) 1fr;gap:var(--s-4);
  padding:var(--s-3) 0;border-bottom:1px solid var(--rule);align-items:baseline}
.sl b{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;text-transform:uppercase;
  color:var(--brass);font-weight:500}
.sl span{font-size:var(--fs-sm);color:var(--ink-2)}
@media (max-width:560px){ .sl li{grid-template-columns:1fr;gap:4px} }

/* ---------------------------------------------------------------------------
   7. TESTIMONIALS.
   Built in full, and deliberately empty. Every quote is a PLACEHOLDER chip
   until Mike supplies real ones. A studio selling rigour to regulated buyers
   cannot open with invented praise, so the honest empty state IS the design.
   ------------------------------------------------------------------------- */
.tst{position:relative}
.tst__q{
  font-family:var(--serif);font-size:clamp(1.55rem,3.6vw,2.9rem);line-height:1.2;
  color:var(--ink);max-width:22ch;margin:0;
}
.tst__q::before{content:"\201C";color:var(--leaf);margin-right:.1em}
.tst__q::after{content:"\201D";color:var(--leaf)}
.tst__by{display:flex;align-items:center;gap:var(--s-4);margin-top:var(--s-6)}
.tst__av{width:52px;height:52px;flex:none;border:1px solid var(--rule);background:var(--paper);
  display:grid;place-items:center;font-family:var(--mono);font-size:var(--fs-label);color:var(--grey)}
.tst__who b{display:block;font-family:var(--sans);font-weight:500;font-size:var(--fs-body);color:var(--ink)}
.tst__who span{display:block;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.11em;
  text-transform:uppercase;color:var(--grey);margin-top:3px}
.tst__rail{display:flex;gap:var(--s-6);overflow-x:auto;scroll-snap-type:x mandatory;
  padding-bottom:var(--s-4);scrollbar-width:thin}
.tst__card{flex:0 0 min(520px,86vw);scroll-snap-align:start;border:1px solid var(--rule);
  background:var(--white);padding:var(--s-6);display:flex;flex-direction:column;gap:var(--s-4)}
.tst__empty{
  border:1px dashed var(--brass);padding:var(--s-6);background:var(--paper);
  display:flex;flex-direction:column;gap:var(--s-3);
}
.tst__empty p{margin:0;font-size:var(--fs-sm);color:var(--ink-2);max-width:56ch}

/* ---------------------------------------------------------------------------
   8. INSIGHTS.
   ------------------------------------------------------------------------- */
.ins__filters{display:flex;flex-wrap:wrap;gap:var(--s-2);margin-bottom:var(--s-7)}
.ins__filter{
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;text-transform:uppercase;
  border:1px solid var(--rule);background:none;color:var(--ink-2);padding:8px 12px;cursor:pointer;min-height:38px;
  transition:border-color .2s var(--e-move),color .2s var(--e-move),background-color .2s var(--e-move);
}
.ins__filter[aria-pressed="true"]{border-color:var(--ink);background:var(--ink);color:var(--paper)}
.ins__filter:hover{border-color:var(--brass);color:var(--brass)}
.ins__filter[aria-pressed="true"]:hover{color:var(--paper)}
.ins__filter:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

.post{display:block;text-decoration:none;color:inherit;padding:var(--s-5) 0;
  border-bottom:1px solid var(--rule);position:relative}
.post__top{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s-3) var(--s-5);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;text-transform:uppercase;color:var(--grey)}
.post__cat{color:var(--brass)}
.post__t{font-family:var(--serif);font-size:clamp(1.28rem,2.2vw,1.85rem);line-height:1.16;
  margin:var(--s-3) 0 0;color:var(--ink);max-width:34ch}
.post__d{margin:var(--s-2) 0 0;font-size:var(--fs-sm);color:var(--ink-2);max-width:66ch}
.post__go{position:absolute;right:0;top:var(--s-5);font-family:var(--mono);font-size:var(--fs-label);
  color:var(--brass);opacity:0;transform:translateX(-8px);transition:opacity .3s,transform .3s var(--e-move)}
.post:hover .post__go{opacity:1;transform:translateX(0)}
.post:hover .post__t{color:var(--brass)}
.post:focus-visible{outline:2px solid var(--brass);outline-offset:4px}
.post[hidden]{display:none}

/* ---------------------------------------------------------------------------
   9. FORMS.
   ------------------------------------------------------------------------- */
.form{display:grid;gap:var(--s-5);max-width:640px}
.field{display:grid;gap:var(--s-2)}
.field label{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.13em;
  text-transform:uppercase;color:var(--ink-2)}
.field input,.field textarea,.field select{
  font-family:var(--sans);font-size:var(--fs-body);color:var(--ink);
  background:var(--white);border:1px solid var(--rule);padding:13px var(--s-4);min-height:50px;
  transition:border-color .2s var(--e-move),box-shadow .2s var(--e-move);
}
.field textarea{min-height:150px;resize:vertical;line-height:1.55}
.field input:focus,.field textarea:focus,.field select:focus{
  outline:none;border-color:var(--brass);box-shadow:0 0 0 3px rgba(138,106,31,.13)}
.field input:focus-visible,.field textarea:focus-visible,.field select:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
.field__hint{font-size:var(--fs-sm);color:var(--grey)}
.field__err{font-size:var(--fs-sm);color:#8C2F1D;min-height:1.2em}
.field[data-invalid="true"] input,.field[data-invalid="true"] textarea{border-color:#8C2F1D}
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.form__note{font-size:var(--fs-sm);color:var(--grey);max-width:56ch}
.form__status{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;
  text-transform:uppercase;color:var(--brass);min-height:1.4em}

/* --------------------------------------------------------------------------
   10. CASE STUDY.
   ------------------------------------------------------------------------- */
.cs__hero{position:relative;border:1px solid var(--rule);overflow:hidden;background:var(--paper-2,#F6F2E9)}
.cs__hero img{display:block;width:100%;height:auto}
.cs__facts{display:grid;gap:var(--s-6);grid-template-columns:repeat(auto-fit,minmax(min(100%,180px),1fr));
  padding:var(--s-6) 0;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
.cs__fact b{display:block;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.13em;
  text-transform:uppercase;color:var(--brass);font-weight:500;margin-bottom:var(--s-2)}
.cs__fact span{font-size:var(--fs-sm);color:var(--ink-2)}
.cs__pair{display:grid;gap:var(--s-7);grid-template-columns:1fr}
@media (min-width:900px){ .cs__pair{grid-template-columns:1fr 1fr;gap:var(--s-8)} }
.cs__next{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-5);
  padding-top:var(--s-6);border-top:1px solid var(--rule);text-decoration:none;color:inherit}
.cs__next:hover .cs__next-t{color:var(--brass)}
.cs__next-t{font-family:var(--serif);font-size:var(--fs-h3);color:var(--ink)}

/* ---------------------------------------------------------------------------
   11. MARQUEE. Velocity-reactive: it leans into the scroll and settles.
   ------------------------------------------------------------------------- */
.mq{overflow:hidden;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);
  padding:var(--s-5) 0;-webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent)}
.mq__in{display:flex;gap:var(--s-7);width:max-content;will-change:transform}
.mq__it{display:inline-flex;align-items:center;gap:var(--s-4);font-family:var(--serif);
  font-size:clamp(1.3rem,2.6vw,2.1rem);color:var(--ink-2);white-space:nowrap}
.mq__it i{display:block;width:5px;height:5px;background:var(--leaf);flex:none}

/* ---------------------------------------------------------------------------
   12. COUNTERS. Only ever wrapped around a sourced figure or a placeholder.
   ------------------------------------------------------------------------- */
.num{font-family:var(--serif);font-size:clamp(2.4rem,6vw,4.4rem);line-height:.95;color:var(--ink);
  font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.numk{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;text-transform:uppercase;
  color:var(--grey);margin-top:var(--s-3);display:block;max-width:24ch}

/* ---------------------------------------------------------------------------
   13. CURSOR.
   This was a crosshair reticle, and the OS arrow is never hidden here, so it
   sat on top of the arrow and read as a targeting gimmick on a page that is
   otherwise very quiet. Now it is one hairline ring that trails the pointer
   and stays out of the arrow's way: it accents the cursor rather than
   competing with it. Gold is a hairline here, never a fill, which keeps it
   inside the palette rule. Hidden on touch and under reduced motion.
   ------------------------------------------------------------------------- */
.cur{position:fixed;top:0;left:0;z-index:150;pointer-events:none;
  width:34px;height:34px;margin:-17px 0 0 -17px;opacity:0;will-change:transform;
  transition:opacity .3s var(--e-move)}
.cur__ring{position:absolute;inset:0;border:1px solid var(--leaf);border-radius:50%;
  opacity:.75;
  transition:transform .4s var(--e-move),opacity .3s var(--e-move),border-color .3s var(--e-move)}
html.cur-on .cur{opacity:1}

/* Over anything clickable the ring opens and takes the readable gold, which is
   the only state change it makes. */
html.cur-link .cur__ring{transform:scale(1.26);border-color:var(--brass);opacity:1}

/* Pressed: it contracts. The only motion that is not a trail. */
html.cur-down .cur__ring{transform:scale(.78)}
html.cur-down.cur-link .cur__ring{transform:scale(1.04)}

/* Over a text field the ring would fight the I-beam, so it stands down. */
html.cur-text .cur{opacity:0}

@media (hover:none),(pointer:coarse){ .cur{display:none} }
@media (prefers-reduced-motion:reduce){ .cur{display:none} }

/* ---------------------------------------------------------------------------
   14. FOOTER.
   ------------------------------------------------------------------------- */
/* No top margin: every preceding .section already carries --section-pad at the
   bottom, and stacking both left roughly 450px of dead paper above the footer. */
.ftr{border-top:1px solid var(--rule);padding-block:clamp(56px,7vw,96px) var(--s-7)}
.ftr__grid{display:grid;gap:var(--s-7);grid-template-columns:repeat(auto-fit,minmax(min(100%,190px),1fr))}
.ftr h3,.ftr__h{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;text-transform:uppercase;
  color:var(--grey);margin:0 0 var(--s-4);font-weight:500}
.ftr ul{list-style:none;margin:0;padding:0;display:grid;gap:var(--s-3)}
.ftr a{color:var(--ink-2);text-decoration:none;font-size:var(--fs-sm)}
.ftr a:hover{color:var(--brass)}
.ftr a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
.ftr__bot{display:flex;flex-wrap:wrap;gap:var(--s-4) var(--s-6);justify-content:space-between;
  margin-top:clamp(48px,6vw,80px);padding-top:var(--s-5);border-top:1px solid var(--rule);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.11em;text-transform:uppercase;color:var(--grey)}

/* ---------------------------------------------------------------------------
   15. REVEAL BASE STATES.
   Held in CSS under html.js only, so with JS off or the CDN blocked every one
   of these resolves to fully visible. Content is never gated behind motion.
   ------------------------------------------------------------------------- */
html.js [data-rise]{opacity:0;transform:translateY(22px)}
html.js [data-fade]{opacity:0}
html.js .wc__shot{--wcv:100}
@media (prefers-reduced-motion:reduce){
  html.js [data-rise],html.js [data-fade]{opacity:1;transform:none}
  html.js .wc__shot{--wcv:0}
  .pre{display:none}
}

/* ---------------------------------------------------------------------------
   16. RHYTHM.
   base.css puts --section-pad on BOTH edges of every .section, so two adjacent
   sections stacked up to 352px of dead paper between them. That is not
   generous whitespace, it is a gap you scroll through wondering if the page
   broke. One pad per boundary instead, and a real gap under a headline so the
   lede stops colliding with the descenders above it.
   ------------------------------------------------------------------------- */
.section{padding-block:var(--section-pad) 0}
.section--tight{padding-block:clamp(64px,8vw,120px) 0}
.section:last-of-type{padding-bottom:clamp(64px,8vw,120px)}
main > .mq + .section{padding-top:clamp(64px,8vw,120px)}

.fs--h2{margin-bottom:var(--s-6)}
.fs--h2 + .lede,.fs--h2 + p{margin-top:0}
.mast h1.fs{margin-bottom:var(--s-5)}
.eyebrow{margin-bottom:var(--s-6)}

/* ---------------------------------------------------------------------------
   17. INLINE-ELEMENT CORRECTIONS.
   Same defect class as .wc__shot: these are <span>s carrying block-only
   properties. On a non-replaced inline element the browser silently discards
   vertical margin and padding, ignores aspect-ratio, and will not clip with
   overflow, so the spacing an author wrote simply never happens. Found by
   sweeping every route for computed display:inline against block-only styles
   rather than by spotting them one at a time.
   ------------------------------------------------------------------------- */
.stated__t,.stated__d,.post__t,.post__d{display:block}
.apps__progress{display:block}

/* Skip link stays reachable above every fixed layer. */
.skip:focus{z-index:210}

/* Respect the viewport gutter everywhere, including the tables and code blocks
   that are the only things allowed to be wider than the body. */
.scrollx{overflow-x:auto;-webkit-overflow-scrolling:touch}

/* ===========================================================================
   18. FILM HERO
   The hero is the footage now, full bleed, rather than a plate beside the type.
   Legibility over it is bought with a paper wash, not a dark box: the footage
   is a bright white cleanroom, so ink on a lightened plate reads far better
   than white text would. One hue, low chroma, which keeps it inside the
   gradient ban (that ban is about rainbow sweeps, not a single-hue scrim).
   ========================================================================= */
.hero--film{position:relative;min-height:100vh;min-height:100svh;display:flex;
  align-items:flex-end;overflow:hidden;padding-bottom:clamp(40px,7vh,88px)}
.hero--film .hero__film{position:absolute;inset:0;z-index:0;background:var(--paper) center/cover no-repeat}
.hero--film .hero__film video{width:100%;height:100%;object-fit:cover;display:block}
html.no-autoplay .hero--film .hero__film video{display:none}
.hero--film .hero__wash{position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(100deg,rgba(252,251,248,.95) 0%,rgba(252,251,248,.88) 34%,
    rgba(252,251,248,.55) 62%,rgba(252,251,248,.28) 100%)}
.hero--film .hero__grainwash{position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(to top,rgba(252,251,248,.9) 0%,rgba(252,251,248,0) 38%)}
.hero--film .wrap{position:relative;z-index:2;width:100%}
.hero--film .hero__meta{margin-bottom:clamp(18px,3vh,30px)}
.hero--film .hero__lead{max-width:46ch}
.hero--film .plate__cap{position:absolute;right:var(--gutter);bottom:clamp(18px,3vh,30px);z-index:2;
  display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end;
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;
  text-transform:uppercase;color:var(--grey)}
@media (max-width:720px){
  .hero--film .hero__wash{background:linear-gradient(170deg,rgba(252,251,248,.94) 0%,
    rgba(252,251,248,.86) 46%,rgba(252,251,248,.62) 100%)}
  .hero--film .plate__cap{display:none}
}

/* The scroll cue, redrawn. The old one bounced a dot up and down on a loop,
   which reads as a toy. Now a single brass segment runs the length of a
   hairline and fades out at the bottom: the same information, stated once. */
.hero__cue{display:flex;align-items:center;gap:var(--s-4);margin-top:clamp(24px,4vh,44px);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.22em;
  text-transform:uppercase;color:var(--grey)}
.hero__cueline{position:relative;display:block;width:1px;height:54px;background:var(--rule);
  overflow:hidden;flex:none}
.hero__cueline::after{content:"";position:absolute;left:0;width:1px;height:18px;
  background:var(--brass);animation:cueRun 2.6s cubic-bezier(.4,0,.2,1) infinite}
.hero__cuedot{display:none}
@keyframes cueRun{
  0%{transform:translateY(-20px);opacity:0}
  22%{opacity:1}
  78%{opacity:1}
  100%{transform:translateY(54px);opacity:0}
}
@media (prefers-reduced-motion:reduce){
  .hero__cueline::after{animation:none;height:54px;opacity:.5}
}

/* ===========================================================================
   19. WORK CARDS THAT PLAY
   Each card is a recorded scroll-through of the live deployment, and the frame
   itself is the link to that deployment. The card is no longer a single <a>:
   a link inside a link is invalid, and the case study still needs its own
   destination, so the frame and the case study are two separate anchors.
   ========================================================================= */
.wc__open{display:block;text-decoration:none;color:inherit}
.wc__open:focus-visible{outline:2px solid var(--brass);outline-offset:6px}
.wc__shot video{display:block;width:100%;height:100%;object-fit:cover;object-position:top center;
  transform:scale(1.02);transition:transform 1.1s var(--e-out)}
.wc:hover .wc__shot video{transform:scale(1.045)}
/* This label sits on top of moving footage, so it was being read against
   whatever frame happened to be behind it: over Emberfell's amber pour it was
   barely there. The chip is now fully opaque paper with ink text, which is a
   fixed pair rather than one that changes 24 times a second. */
.wc__play{
  position:absolute;left:var(--s-4);bottom:var(--s-4);z-index:3;
  display:flex;align-items:center;gap:8px;padding:7px 11px;
  background:var(--paper);border:1px solid var(--rule);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.11em;
  text-transform:uppercase;color:var(--ink);
  box-shadow:0 1px 3px rgba(22,19,13,.12);
  transition:border-color .3s var(--e-move),color .3s var(--e-move);
}
@media (max-width:620px){
  .wc__play{left:10px;bottom:10px;padding:5px 8px;gap:6px;letter-spacing:.08em}
}
.wc:hover .wc__play{border-color:var(--brass);color:var(--brass)}
.wc__play i{width:6px;height:6px;background:var(--leaf);border-radius:50%;display:block;flex:none}
.wc__links{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-3) var(--s-5);margin-top:var(--s-4)}
.wc__host,.wc__case{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.1em;
  text-decoration:none;transition:color .25s var(--e-move)}
.wc__host{color:var(--grey)}
.wc__case{color:var(--brass)}
.wc__host:hover,.wc__case:hover{color:var(--ink)}
.wc__host:focus-visible,.wc__case:focus-visible{outline:2px solid var(--brass);outline-offset:3px}

/* ===========================================================================
   20. OPS CONSOLE GRID. Ten real screens, captured on mock fixtures.
   ========================================================================= */
.opsgrid{display:grid;gap:var(--s-4);grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr));
  margin-top:var(--s-6)}
.opsshot{margin:0;border:1px solid var(--rule);background:var(--white);overflow:hidden}
.opsshot img{display:block;width:100%;height:auto;transition:transform .9s var(--e-out)}
.opsshot:hover img{transform:scale(1.025)}
.opsshot figcaption{display:flex;justify-content:space-between;gap:var(--s-3);
  padding:9px var(--s-4);border-top:1px solid var(--rule);background:var(--paper);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.11em;
  text-transform:uppercase;color:var(--grey)}
.opsshot figcaption b{color:var(--brass);font-weight:500}

/* ===========================================================================
   21. TOUCH TRAIL
   Ported from the DEX proposal deck, restyled for paper. There is no cursor on
   a phone, so a swipe leaves brass marks that dissolve in the order they were
   drawn. pointer-events:none throughout so it never eats a tap or a scroll,
   and it is off entirely under reduced motion.
   ========================================================================= */
/* The water under the finger (assets/ripple.js, canvas.ripple) draws over the
   page, not over a form someone is filling in or a menu they are choosing
   from. */
html.modal-open .ripple,.navdraw[data-open="true"] ~ .ripple{display:none !important}
/* Its own rule: an engine without :has() drops the whole list it sits in. */
html:has(.navdraw[data-open="true"]) .ripple{display:none !important}

/* Phone motion (site.js, wireTouchMotion). The pictures in frames are driven
   by the scroll there, so the desktop hover transitions on them would smear
   every step into a lag; touch reveals travel further so they register at arm's
   length. The water under the finger is assets/ripple.js. */
@media (hover:none) and (pointer:coarse) and (prefers-reduced-motion:no-preference){
  html.js [data-rise]{transform:translateY(36px)}
  .wc__shot img,.wc__shot video,.appshot img,.opsshot img,a.scr .scr__body img,.scr__body img,
  .cs__hero img,.ppl__shot img,.appicon__scr img,.emb__frame img{transition:none}
}

/* Feature card body wrapper. Divs, not spans: the card holds a <p>, which is
   flow content and gets hoisted straight out of a <span> by the parser. */
.wc__body{display:flex;flex-direction:column;justify-content:center}

/* The film hero's scroll cue. Two corrections against the inherited rule:
   base.css right-aligns the cue (it lived in a narrow column before), which
   parked it under the plate caption; and a --rule hairline with a grey label
   is invisible over the brightest part of the footage. */
.hero--film .hero__cue{justify-content:flex-start;color:var(--ink-2)}
.hero--film .hero__cueline{background:rgba(22,19,13,.16)}
.hero--film .hero__cueline::after{background:var(--brass)}

/* ===========================================================================
   22. THE FILM HERO, RE-BALANCED
   The first pass washed the footage at 95% opacity across the left of the
   frame, so about 5% of the video survived and the hero read as flat cream.
   Measured instead of guessed: behind the headline the footage hits pure
   black in places (1.13:1 against ink) while averaging 8.63:1, so a scrim is
   genuinely needed, but only enough to lift the darkest pixel to L 0.205,
   which is where ink clears 4.5:1. That is roughly half paper, not all of it.

   So: hold the copy to the left of the frame, keep the scrim strong only
   under the glyphs, and let the right of the frame run as near-clear footage
   that is visibly moving.
   ========================================================================= */
.hero--film .hero__wash{
  background:linear-gradient(100deg,
    rgba(252,251,248,.80) 0%,
    rgba(252,251,248,.74) 40%,
    rgba(252,251,248,.52) 62%,
    rgba(252,251,248,.12) 82%,
    rgba(252,251,248,.04) 100%);
}
.hero--film .hero__grainwash{
  background:linear-gradient(to top,rgba(252,251,248,.72) 0%,rgba(252,251,248,0) 26%);
}
/* Slightly smaller display so the longest line lands inside the scrimmed
   left side rather than running across the clear footage. */
.hero--film .fs--display{font-size:clamp(2.5rem,6.5vw,6.3rem)}
.hero--film .hero__lead{max-width:40ch}

@media (max-width:860px){
  /* Stacked layout: the copy sits over the middle of the frame, so the scrim
     has to be vertical rather than horizontal. */
  .hero--film .hero__wash{
    background:linear-gradient(172deg,
      rgba(252,251,248,.58) 0%,
      rgba(252,251,248,.80) 38%,
      rgba(252,251,248,.86) 100%);
  }
  .hero--film .fs--display{font-size:clamp(2.4rem,9.4vw,3.6rem)}
}

/* The eyebrow row was full-width, so its right end ran out over the clear
   footage. Shrink the box to its content: the line is short, and this keeps it
   inside the scrimmed side instead of trailing across the bright plate. */
.hero--film .hero__meta{width:-webkit-max-content;width:max-content;max-width:100%}

/* The hero footage now carries its own camera move (a forward dolly, measured
   at 15.62/255 mean inter-frame change against 0.25 for the first ambient
   version), so the CSS push that stood in for motion is gone: a scale ramp on
   top of a dolly reads as a zoom fighting the camera. A trace of scale stays
   only to hide the edges as the ping-pong turns around. */
.hero--film .hero__vid{transform:scale(1.02);transform-origin:58% 50%}
@media (prefers-reduced-motion:reduce){
  .hero--film .hero__vid{transform:none}
}

/* ===========================================================================
   23. SPECIMEN CONTENT
   Mike asked to see the testimonial block filled so he can judge the design.
   These quotes are invented. They are marked three ways so they cannot reach
   a client by accident: a data attribute, the literal token SPECIMEN in the
   markup, and a visible brass tag on the section itself.
   ========================================================================= */
.specimen{position:relative}
.specimen__tag{
  display:inline-flex;align-items:center;gap:9px;margin-bottom:var(--s-5);
  padding:7px 12px;border:1px dashed var(--brass);color:var(--brass);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;text-transform:uppercase;
}
.specimen__tag i{width:6px;height:6px;background:var(--brass);border-radius:50%;flex:none}
[data-specimen="true"]{position:relative}

/* Quote cards, filled. */
.rot .tst__card{justify-content:flex-start;gap:var(--s-5)}
.tst__q{font-style:normal}
.tst__stars{display:flex;gap:4px;margin-bottom:var(--s-2)}
.tst__stars i{width:9px;height:9px;background:var(--leaf);display:block;flex:none;
  clip-path:polygon(50% 0,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%)}
.tst__av{font-family:var(--serif);font-size:1.05rem;color:var(--brass);background:var(--paper)}

/* ===========================================================================
   24. SCOPE MODAL
   "Request a scope" opens in place instead of navigating away. Focus is
   trapped while open, Escape closes, the trigger gets focus back, and the
   underlying page cannot scroll behind it.
   ========================================================================= */
.modal{position:fixed;inset:0;z-index:180;display:none}
.modal[data-open="true"]{display:block}
.modal__veil{position:absolute;inset:0;background:rgba(22,19,13,.34);opacity:0}
.modal__box{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:min(680px,calc(100vw - 2*var(--gutter)));max-height:min(88vh,860px);overflow:auto;
  background:var(--paper);border:1px solid var(--rule);box-shadow:var(--shadow-lift);
  padding:clamp(24px,4vw,48px);
}
.modal__edge{position:absolute;left:0;right:0;top:0;height:2px;background:var(--leaf)}
.modal__x{
  position:absolute;top:14px;right:14px;width:40px;height:40px;display:grid;place-items:center;
  background:none;border:1px solid var(--rule);cursor:pointer;color:var(--ink);
}
.modal__x:hover{border-color:var(--brass);color:var(--brass)}
.modal__x:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.modal__x svg{width:14px;height:14px;stroke:currentColor;stroke-width:1.4;fill:none}
.modal h2{font-family:var(--serif);font-size:clamp(1.7rem,3.4vw,2.5rem);line-height:1.08;
  margin:0 0 var(--s-4);color:var(--ink)}
.modal .form{max-width:none}
@media (max-width:620px){
  .modal__box{top:auto;bottom:0;left:0;transform:none;width:100%;max-height:92vh;
    border-left:0;border-right:0;border-bottom:0}
}
html.modal-open,html.modal-open body{overflow:hidden}

/* ===========================================================================
   PACKAGE EXCHANGE (.sx)
   Three packages, one panel each. The figures are industry research, so each
   one carries its source in the markup: a number with no attribution is the
   thing this studio refuses to ship.
   ========================================================================= */
.sx{margin-top:var(--s-8)}
.sx__tabs{display:flex;flex-wrap:wrap;gap:var(--s-3);margin-bottom:var(--s-7);padding:0;border:0}
.sx__tab{
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-2);background:transparent;border:1px solid var(--rule);
  padding:14px 20px;cursor:pointer;position:relative;overflow:hidden;
  transition:color var(--d-hover) var(--e-move),border-color var(--d-hover) var(--e-move)}
.sx__tab::after{content:"";position:absolute;left:0;bottom:0;height:2px;width:100%;
  background:var(--brass);transform:scaleX(0);transform-origin:left center;
  transition:transform .5s var(--e-move)}
.sx__tab:hover{border-color:var(--brass);color:var(--ink)}
.sx__tab:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
.sx__tab[aria-selected="true"]{color:var(--ink);border-color:var(--brass)}
.sx__tab[aria-selected="true"]::after{transform:scaleX(1)}

.sx__panel{border-top:1px solid var(--rule);padding-top:var(--s-7)}
html.js .sx__panel[hidden]{display:none}
.sx__grid{display:grid;gap:var(--s-7);grid-template-columns:1.25fr .75fr;align-items:start}
@media (max-width:900px){.sx__grid{grid-template-columns:1fr}}

.sx__stats{display:grid;gap:var(--s-6);grid-template-columns:repeat(auto-fit,minmax(min(100%,210px),1fr));min-width:0}
.sxs{min-width:0}
.sxs__n{display:block;font-family:var(--serif);font-size:clamp(2.6rem,6.2vw,4.2rem);line-height:.92;
  color:var(--ink);letter-spacing:-.02em;font-weight:300}
.sxs__n i{font-style:normal;font-size:.44em;letter-spacing:0;color:var(--brass);margin-left:.08em;
  vertical-align:.5em}
.sxs__bar{display:block;height:2px;background:var(--rule);margin:var(--s-4) 0 var(--s-3);position:relative;overflow:hidden}
.sxs__bar i{position:absolute;inset:0 auto 0 0;display:block;background:var(--leaf);
  width:var(--sxw,0%);transform-origin:left center;transform:scaleX(var(--sxs,0))}
.sxs__k{display:block;font-size:var(--fs-sm);line-height:1.5;color:var(--ink-2)}
.sxs__src{display:block;margin-top:var(--s-3);font-family:var(--mono);font-size:var(--fs-label);
  line-height:1.5;letter-spacing:.04em;color:var(--brass)}
.sxs__src a{color:var(--brass);text-underline-offset:3px}

.sx__side{min-width:0;border-left:1px solid var(--rule);padding-left:var(--s-6)}
@media (max-width:900px){.sx__side{border-left:0;border-top:1px solid var(--rule);padding-left:0;padding-top:var(--s-6)}}
.sx__sk{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-2);margin:0 0 var(--s-4)}
.sx__inc{list-style:none;margin:0;padding:0}
.sx__inc li{display:grid;grid-template-columns:18px 1fr;gap:var(--s-3);align-items:start;
  font-size:var(--fs-sm);line-height:1.55;color:var(--ink-2);padding:10px 0;border-bottom:1px solid var(--rule)}
.sx__inc li::before{content:"";width:9px;height:9px;margin-top:.45em;border:1px solid var(--brass);
  transform:rotate(45deg)}
.sx__note{margin-top:var(--s-5);font-family:var(--mono);font-size:var(--fs-label);line-height:1.6;
  letter-spacing:.03em;color:var(--ink-2)}

/* ===========================================================================
   OFFER BAND (.offer)
   ========================================================================= */
.offer{position:relative;margin-top:var(--s-8);border:1px solid var(--rule);background:var(--white);
  padding:var(--s-7) var(--s-6);overflow:hidden}
.offer::before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--leaf)}
.offer__k{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.16em;text-transform:uppercase;
  color:var(--brass);margin:0 0 var(--s-4)}
.offer__h{font-family:var(--serif);font-size:clamp(1.5rem,3.1vw,2.35rem);line-height:1.12;color:var(--ink);
  margin:0;font-weight:300;max-width:22ch}
.offer__h sup{color:var(--brass);font-size:.5em;vertical-align:super}
.offer__d{margin:var(--s-4) 0 0;font-size:var(--fs-sm);line-height:1.6;color:var(--ink-2);max-width:56ch}
.offer__fine{margin:var(--s-5) 0 0;font-size:.72rem;line-height:1.55;color:var(--ink-2);max-width:62ch}
.offer__fine sup{color:var(--brass)}

/* ===========================================================================
   SCREEN FRAME (.scr)
   Holds either a real capture or a drawn schematic. The caption always says
   which of the two it is, because a drawing presented as a screenshot is a lie
   with extra steps.
   ========================================================================= */
.scr{display:block;margin:0;border:1px solid var(--rule);background:var(--white);overflow:hidden}
.scr__chrome{display:flex;align-items:center;gap:var(--s-3);padding:9px 12px;border-bottom:1px solid var(--rule);
  background:var(--paper)}
.scr__dots{display:flex;gap:5px;flex:0 0 auto}
.scr__dots i{width:7px;height:7px;border:1px solid var(--rule);border-radius:50%;display:block}
.scr__host{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.06em;color:var(--ink-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.scr__body{display:block;position:relative;background:var(--white);line-height:0}
.scr__body img{display:block;width:100%;height:auto}
.scr__body svg{display:block;width:100%;height:auto}
.scr__cap{display:block;margin:var(--s-3) 0 0;font-family:var(--mono);font-size:var(--fs-label);
  line-height:1.5;letter-spacing:.04em;color:var(--ink-2)}
.scr__cap b{color:var(--brass);font-weight:400}

/* ===========================================================================
   SCHEMATIC (.sch)
   Ink line art on paper. Strokes draw themselves in on scroll; the resting
   state under html.no-js is the finished drawing.
   ========================================================================= */
.sch{background:var(--paper)}
.sch .l{fill:none;stroke:var(--ink);stroke-width:1;stroke-linecap:square;vector-effect:non-scaling-stroke}
.sch .h{fill:none;stroke:var(--rule);stroke-width:1;vector-effect:non-scaling-stroke}
.sch .g{fill:none;stroke:var(--leaf);stroke-width:1.4;vector-effect:non-scaling-stroke}
.sch .fillg{fill:var(--leaf);stroke:none}
.sch .fillp{fill:var(--paper);stroke:none}
.sch .fillw{fill:var(--white);stroke:none}
.sch text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;letter-spacing:.06em;
  fill:var(--ink-2);text-transform:uppercase}
.sch text.t{font-size:10.5px;fill:var(--ink)}
.sch .bar{fill:var(--ink);opacity:.86}
.sch .bar--g{fill:var(--leaf);opacity:1}
/* No resting state is declared here on purpose. The drawing is complete in the
   markup, and the motion layer hides the strokes itself at wire time, so a
   schematic can never be left half-drawn by a script that failed to load. */

/* ===========================================================================
   FIGURE ROW (.figrow) and app tile thumbnails
   ========================================================================= */
.figrow{display:grid;gap:var(--s-6);grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr));
  margin-top:var(--s-7)}
.figrow--3{grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr))}

.appicon__scr{display:block;border:1px solid var(--rule);background:var(--paper);overflow:hidden;
  margin-bottom:var(--s-5);line-height:0;
  transition:border-color var(--d-hover) var(--e-move)}
.appicon:hover .appicon__scr{border-color:var(--brass)}
.appicon__scr svg,.appicon__scr img{display:block;width:100%;height:auto}

/* A drawn screen sitting beside its own prose. */
.appsplit{display:grid;gap:var(--s-7);grid-template-columns:1fr 1fr;align-items:center;margin-top:var(--s-8)}
.appsplit:nth-of-type(even) .appsplit__fig{order:2}
@media (max-width:860px){.appsplit{grid-template-columns:1fr}.appsplit:nth-of-type(even) .appsplit__fig{order:0}}
.appsplit__fig{min-width:0}
.appsplit__txt{min-width:0}
.appsplit__n{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;color:var(--brass);
  display:block;margin-bottom:var(--s-3)}
.appsplit__t{font-family:var(--serif);font-size:var(--fs-h3);line-height:1.12;color:var(--ink);
  margin:0 0 var(--s-4);font-weight:400}
.appsplit__d{font-size:var(--fs-sm);line-height:1.6;color:var(--ink-2);margin:0 0 var(--s-5);max-width:48ch}
.sxs__n b{font-weight:inherit}

/* App tiles carry a drawn screen now, so they are wider: a schematic squeezed
   into a 232px column is texture, not a picture of anything. The icon glyph
   moves inline with the name instead of sitting on its own row. */
.appicon{width:clamp(282px,38vw,392px)}
.appicon__head{display:flex;align-items:center;gap:var(--s-4)}
.appicon__head .appicon__ico{width:40px;height:40px;flex:none;margin:0}
.appicon__head .appicon__ico svg{width:21px;height:21px}
.appicon__head .appicon__n{margin-left:auto}

/* A screen frame that is itself a link (the About strip) still has to be a
   block, and must not inherit the link underline onto its chrome. */
a.scr{text-decoration:none;color:inherit;transition:border-color var(--d-hover) var(--e-move)}
a.scr:hover{border-color:var(--brass)}
a.scr:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
a.scr .scr__body img{transition:transform 1s var(--e-out)}
a.scr:hover .scr__body img{transform:scale(1.03)}
.figrow .scr__body{aspect-ratio:16/10;overflow:hidden}
.figrow .scr__body img{width:100%;height:100%;object-fit:cover;object-position:top center}

/* ===========================================================================
   ARTICLE (.art)
   Long-form body for the ported posts. Measure is capped hard: these run to
   two thousand words and the rest of the site is set in a wide grid, so
   without a measure the lines would be unreadable.
   ========================================================================= */
.art{max-width:68ch}
.art__meta{display:flex;flex-wrap:wrap;gap:var(--s-3) var(--s-5);margin:0 0 var(--s-6);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-2)}
.art__meta .art__cat{color:var(--brass)}
.art h2{font-family:var(--serif);font-size:clamp(1.5rem,2.9vw,2.15rem);line-height:1.18;
  color:var(--ink);font-weight:400;margin:var(--s-8) 0 var(--s-4)}
.art h3{font-family:var(--serif);font-size:clamp(1.2rem,2vw,1.5rem);line-height:1.2;
  color:var(--ink);font-weight:500;margin:var(--s-7) 0 var(--s-3)}
.art h4{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;
  text-transform:uppercase;color:var(--brass);margin:var(--s-6) 0 var(--s-3)}
.art p{font-size:var(--fs-body);line-height:1.68;color:var(--ink-2);margin:0 0 var(--s-5)}
.art ul,.art ol{margin:0 0 var(--s-5);padding-left:0;list-style:none}
.art li{position:relative;padding-left:var(--s-6);margin-bottom:var(--s-3);
  font-size:var(--fs-body);line-height:1.62;color:var(--ink-2)}
.art li::before{content:"";position:absolute;left:0;top:.62em;width:9px;height:9px;
  border:1px solid var(--brass);transform:rotate(45deg)}
.art ol{counter-reset:art}
.art ol li{counter-increment:art}
.art ol li::before{content:counter(art);border:0;transform:none;top:0;
  font-family:var(--mono);font-size:var(--fs-label);color:var(--brass);letter-spacing:.08em}
.art blockquote{margin:var(--s-6) 0;padding-left:var(--s-5);border-left:2px solid var(--leaf);
  font-family:var(--serif);font-size:clamp(1.15rem,2vw,1.45rem);line-height:1.4;color:var(--ink)}
.art pre{overflow-x:auto;padding:var(--s-5);border:1px solid var(--rule);background:var(--white);
  font-family:var(--mono);font-size:.82rem;line-height:1.55;color:var(--ink);margin:0 0 var(--s-5)}
.art a{color:var(--brass);text-underline-offset:3px}
.art hr{border:0;border-top:1px solid var(--rule);margin:var(--s-8) 0}
.art > *:first-child{margin-top:0}

/* The ported note: these were written for the previous audience, and that is
   stated on every one of them rather than quietly left to be inferred. */
.art__ported{border:1px solid var(--rule);border-left:2px solid var(--leaf);
  background:var(--white);padding:var(--s-5);margin:0 0 var(--s-7)}
.art__ported p{font-size:var(--fs-sm);line-height:1.6;margin:0;color:var(--ink-2)}
.art__ported b{color:var(--ink);font-weight:500}

/* Previous and next, so a reader who finishes one has somewhere to go. */
.art__nav{display:grid;gap:var(--s-5);grid-template-columns:1fr 1fr;margin-top:var(--s-9);
  border-top:1px solid var(--rule);padding-top:var(--s-6)}
@media (max-width:700px){.art__nav{grid-template-columns:1fr}}
.art__nav a{display:block;text-decoration:none;color:inherit}
.art__nav a:hover .art__navt{color:var(--brass)}
.art__navk{display:block;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-2);margin-bottom:var(--s-2)}
.art__navt{display:block;font-family:var(--serif);font-size:1.15rem;line-height:1.25;color:var(--ink);
  transition:color var(--d-hover) var(--e-move)}
.art__nav .art__next{text-align:right}
@media (max-width:700px){.art__nav .art__next{text-align:left}}

/* ===========================================================================
   CONSENT
   A real checkbox, unticked by default, required, and never styled to look
   optional. The privacy page claims a lawful basis for holding an enquiry, so
   the form has to actually ask for one.
   ========================================================================= */
.consent{display:grid;grid-template-columns:auto 1fr;gap:var(--s-3);align-items:start}
.consent input[type="checkbox"]{
  width:18px;height:18px;margin:2px 0 0;flex:none;accent-color:var(--brass);cursor:pointer}
.consent label{font-family:var(--sans);font-size:var(--fs-sm);line-height:1.55;color:var(--ink-2);
  letter-spacing:0;text-transform:none;cursor:pointer}
.consent a{color:var(--brass);text-decoration:underline;text-underline-offset:3px}
.consent .field__err{grid-column:2}
.field[data-invalid="true"] .consent input[type="checkbox"]{outline:2px solid #8C2F1D;outline-offset:2px}

/* ===========================================================================
   CATALOGUE (.cat)
   The services list, selectable. fluxaro.tech lists the same features but they
   are inert: this one keeps a count and hands the selection to the scope form,
   which is the thing Mike asked for originally.
   ========================================================================= */
.catgrp{margin-top:var(--s-8)}
.catgrp__h{display:flex;align-items:baseline;gap:var(--s-4);padding-bottom:var(--s-3);
  border-bottom:1px solid var(--rule)}
.catgrp__n{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;color:var(--brass)}
.catgrp__t{font-family:var(--serif);font-size:var(--fs-h3);line-height:1.1;color:var(--ink);margin:0;font-weight:400}
.catgrp__d{margin:var(--s-3) 0 0;font-size:var(--fs-sm);color:var(--ink-2)}

.cat{display:grid;gap:0;margin-top:var(--s-4)}
.cat__item{display:grid;grid-template-columns:22px 1fr;gap:var(--s-4);align-items:start;
  padding:var(--s-4) 0;border-bottom:1px solid var(--rule);cursor:pointer;
  transition:background-color var(--d-hover) var(--e-move)}
.cat__item:hover{background:var(--white)}
.cat__item input{width:18px;height:18px;margin:3px 0 0;accent-color:var(--brass);cursor:pointer}
.cat__t{display:block;font-size:var(--fs-body);line-height:1.35;color:var(--ink)}
.cat__d{display:block;margin-top:4px;font-size:var(--fs-sm);line-height:1.5;color:var(--ink-2)}
.cat__item input:checked ~ span .cat__t{color:var(--brass)}
.cat__item:focus-within{outline:2px solid var(--brass);outline-offset:2px}

/* The running tally. Sticks to the bottom on a phone, where the list is long. */
.cart{position:sticky;bottom:0;z-index:40;margin-top:var(--s-8);
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-4) var(--s-5);
  padding:var(--s-5);border:1px solid var(--brass);background:var(--paper);
  box-shadow:var(--shadow-1)}
.cart__n{font-family:var(--serif);font-size:2rem;line-height:1;color:var(--ink)}
.cart__k{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-2)}
.cart__sp{flex:1 1 auto}
.cart__clear{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-2);background:none;border:0;cursor:pointer;
  text-decoration:underline;text-underline-offset:3px}
.cart__clear:hover{color:var(--brass)}
@media (max-width:620px){.cart{gap:var(--s-3)}.cart__n{font-size:1.5rem}}

/* No prices anywhere: quote-only is the standing rule, so the catalogue states
   what you get and the number is a conversation. */
.cat__note{margin-top:var(--s-5);font-family:var(--mono);font-size:var(--fs-label);
  line-height:1.6;letter-spacing:.03em;color:var(--ink-2)}

/* ===========================================================================
   AUDIT
   The result table is the point, so it is built to be read rather than to
   impress: pass and fail are a word and a mark, never a colour alone.
   ========================================================================= */
.aud__form{display:grid;gap:var(--s-4);grid-template-columns:1fr auto;align-items:end;max-width:640px}
@media (max-width:620px){.aud__form{grid-template-columns:1fr}}
.aud__out{margin-top:var(--s-8)}
.aud__score{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s-4) var(--s-6);
  padding-bottom:var(--s-5);border-bottom:1px solid var(--rule)}
.aud__n{font-family:var(--serif);font-size:clamp(3rem,8vw,5rem);line-height:.9;color:var(--ink)}
.aud__n i{font-style:normal;font-size:.36em;color:var(--brass);margin-left:.1em;vertical-align:.7em}
.aud__meta{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.1em;color:var(--ink-2);line-height:1.7}
.aud__list{list-style:none;margin:var(--s-6) 0 0;padding:0}
.aud__row{display:grid;grid-template-columns:76px 1fr;gap:var(--s-4);padding:var(--s-4) 0;
  border-bottom:1px solid var(--rule);align-items:start}
.aud__mark{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;text-transform:uppercase}
.aud__row[data-pass="true"] .aud__mark{color:var(--brass)}
.aud__row[data-pass="false"] .aud__mark{color:#8C2F1D}
.aud__lab{display:block;font-size:var(--fs-body);color:var(--ink);line-height:1.35}
.aud__det{display:block;margin-top:3px;font-size:var(--fs-sm);color:var(--ink-2);line-height:1.5;
  word-break:break-word}
.aud__note{margin-top:var(--s-5);font-family:var(--mono);font-size:var(--fs-label);
  line-height:1.6;color:var(--ink-2)}
.aud__err{padding:var(--s-5);border:1px solid #8C2F1D;background:var(--white);
  font-size:var(--fs-sm);color:var(--ink)}
.aud__busy{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;
  text-transform:uppercase;color:var(--brass)}

/* ===========================================================================
   FOUNDERS and FAQ
   ========================================================================= */
.fnd{display:grid;gap:var(--s-7);grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));
  margin-top:var(--s-8)}
.fnd__p{min-width:0;border-top:1px solid var(--rule);padding-top:var(--s-5)}
.fnd__mark{display:flex;align-items:center;justify-content:center;width:62px;height:62px;
  border:1px solid var(--rule);background:var(--white);margin-bottom:var(--s-5);
  font-family:var(--serif);font-size:1.5rem;color:var(--brass)}
.fnd__n{font-family:var(--serif);font-size:var(--fs-h3);line-height:1.1;color:var(--ink);margin:0 0 var(--s-2);font-weight:400}
.fnd__r{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;text-transform:uppercase;
  color:var(--brass);display:block;margin-bottom:var(--s-4)}
.fnd__d{font-size:var(--fs-sm);line-height:1.6;color:var(--ink-2);margin:0}

.faq{margin-top:var(--s-8);border-top:1px solid var(--rule)}
.faq__i{border-bottom:1px solid var(--rule)}
.faq__q{display:flex;align-items:flex-start;gap:var(--s-4);width:100%;text-align:left;
  padding:var(--s-5) 0;background:none;border:0;cursor:pointer;
  font-family:var(--serif);font-size:clamp(1.1rem,1.9vw,1.4rem);line-height:1.3;color:var(--ink)}
.faq__q:hover{color:var(--brass)}
.faq__q:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
.faq__m{flex:none;width:14px;height:14px;position:relative;margin-top:.42em}
.faq__m::before,.faq__m::after{content:"";position:absolute;background:var(--brass);
  transition:transform .32s var(--e-move)}
.faq__m::before{left:0;right:0;top:6px;height:1px}
.faq__m::after{top:0;bottom:0;left:6px;width:1px}
.faq__q[aria-expanded="true"] .faq__m::after{transform:scaleY(0)}
.faq__a{overflow:hidden}
.faq__a[hidden]{display:none}
.faq__a p{font-size:var(--fs-body);line-height:1.65;color:var(--ink-2);max-width:66ch;
  margin:0 0 var(--s-5)}

/* The live embeds. An iframe of a real deployment, not a picture of one. */
.emb{display:block;border:1px solid var(--rule);background:var(--white);overflow:hidden}
.emb__frame{position:relative;display:block;width:100%;aspect-ratio:16/10;overflow:hidden;background:var(--paper)}
.emb__frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0;
  transform:scale(.5);transform-origin:top left;width:200%;height:200%}
.emb__bar{display:flex;align-items:center;gap:var(--s-3);padding:10px 12px;border-top:1px solid var(--rule)}
.emb__host{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.06em;color:var(--brass);
  text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.emb__host:hover{text-decoration:underline;text-underline-offset:3px}
.emb__live{margin-left:auto;display:flex;align-items:center;gap:6px;font-family:var(--mono);
  font-size:var(--fs-label);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-2)}
.emb__live i{width:6px;height:6px;border-radius:50%;background:var(--leaf);display:block}

/* The client log in sits beside the CTA, quieter than it, because it is for
   people who already bought rather than people deciding to. */
.nav__login{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-2);text-decoration:none;margin-right:var(--s-5);
  transition:color var(--d-hover) var(--e-move)}
.nav__login:hover{color:var(--brass)}
.nav__login:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
@media (max-width:980px){.nav__login{display:none}}

/* The ported posts cite their sources as bare URLs, and a 100-character Twilio
   link does not wrap on its own: at 390px it pushed the column to 458 and the
   whole page scrolled sideways. Break anywhere rather than overflow. */
.art p,.art li,.art h2,.art h3,.art blockquote{overflow-wrap:anywhere;word-break:break-word}
.art__navt{overflow-wrap:anywhere}
.figrow .emb__frame a{display:block;width:100%;height:100%}
.figrow .emb__frame img{width:100%;height:100%;object-fit:cover;object-position:top center}

/* ===========================================================================
   FOUNDER PORTRAITS
   Real photographs, or an honest empty plate saying one is missing. Never a
   generated face and never a stock headshot standing in for a founder.
   ========================================================================= */
.ppl{display:grid;gap:var(--s-8);grid-template-columns:repeat(auto-fit,minmax(min(100%,280px),1fr));
  margin-top:var(--s-8)}
.ppl__p{min-width:0}
.ppl__shot{display:block;position:relative;aspect-ratio:4/5;overflow:hidden;
  border:1px solid var(--rule);background:var(--white);margin-bottom:var(--s-5)}
.ppl__shot img{display:block;width:100%;height:100%;object-fit:cover;object-position:center top;
  filter:saturate(.95);transform:scale(1.01);
  transition:transform 1.1s var(--e-out),filter .6s var(--e-move)}
.ppl__p:hover .ppl__shot img{transform:scale(1.04);filter:saturate(1.02)}
/* the empty plate, when no photograph has been supplied yet */
.ppl__shot--none{display:flex;align-items:center;justify-content:center;background:var(--paper)}
.ppl__shot--none span{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-2);text-align:center;padding:var(--s-5);line-height:1.7}
.ppl__n{font-family:var(--serif);font-size:clamp(1.4rem,2.6vw,2rem);line-height:1.1;color:var(--ink);
  margin:0 0 var(--s-2);font-weight:400}
.ppl__r{display:block;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;
  text-transform:uppercase;color:var(--brass);margin-bottom:var(--s-4)}
.ppl__d{font-size:var(--fs-sm);line-height:1.6;color:var(--ink-2);margin:0 0 var(--s-4)}
/* The degree line. Asked for as "a lot easier to see": serif at reading size
   on a brass rule, not a mono caption a visitor skims past. */
.ppl__deg{display:flex;flex-direction:column;gap:3px;margin:0 0 var(--s-4);
  padding:2px 0 2px var(--s-4);border-left:2px solid var(--brass)}
.ppl__deg b{font-family:var(--serif);font-weight:500;font-size:clamp(1.1rem,1.7vw,1.3rem);
  line-height:1.25;color:var(--ink)}
.ppl__deg span{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-2)}
/* Home: the same people as a compact row, photo beside the words. */
.ppl--row .ppl__p{display:grid;grid-template-columns:clamp(104px,12vw,150px) minmax(0,1fr);
  gap:var(--s-5);align-items:start}
.ppl--row .ppl__shot{margin:0}

/* ===========================================================================
   OUR OWN PRODUCTS (.prod)
   Distinct from client work and from the internal platform: these are things
   Fluxaro owns and ships under its own name. One is live, the others are not,
   and the card says which rather than implying all three are running.
   ========================================================================= */
.prodgrid{display:grid;gap:var(--s-6);grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));
  margin-top:var(--s-8)}
.prod{position:relative;min-width:0;display:flex;flex-direction:column;gap:var(--s-4);
  padding:var(--s-6);border:1px solid var(--rule);background:var(--white);
  text-decoration:none;color:inherit;
  transition:border-color var(--d-hover) var(--e-move),transform .5s var(--e-out)}
a.prod:hover{border-color:var(--brass);transform:translateY(-3px)}
a.prod:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
.prod__st{display:inline-flex;align-items:center;gap:6px;align-self:flex-start;
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.14em;text-transform:uppercase}
.prod__st i{width:6px;height:6px;border-radius:50%;display:block}
.prod--live .prod__st{color:var(--brass)}
.prod--live .prod__st i{background:var(--leaf)}
.prod--soon .prod__st{color:var(--ink-2)}
.prod--soon .prod__st i{background:transparent;border:1px solid var(--grey)}
.prod__n{font-family:var(--serif);font-size:clamp(1.35rem,2.4vw,1.85rem);line-height:1.12;
  color:var(--ink);margin:0;font-weight:400}
.prod__d{font-size:var(--fs-sm);line-height:1.55;color:var(--ink-2);margin:0}
.prod__host{margin-top:auto;font-family:var(--mono);font-size:var(--fs-label);
  letter-spacing:.06em;color:var(--brass)}
.prod--soon{background:var(--paper);border-style:dashed}
.prod--soon .prod__n{color:var(--ink-2)}

/* The screen chrome is a flex row, and a flex item's default min-width:auto
   means the host label refuses to shrink below its text. At 320px that pushed
   the page 12px wide and broke WCAG reflow. Same trap as the apps panel. */
.scr__chrome{min-width:0}
.scr__host{min-width:0;flex:1 1 auto}
.scr,.scr__body{min-width:0;max-width:100%}

/* Phone header: wordmark, CTA and burger on one row at every width from 320
   to 860. The CTA's wrapper used to shrink, so from 401 to 480 (iPhone 16 and
   17, every Plus and Pro Max) the label broke onto two or three lines and
   squeezed the burger. Nothing in the row shrinks now; the type steps down
   instead, and never below 12px. The bar is exactly --nav-h tall, so the menu
   drawer, which hangs from --nav-h, meets it with no gap and no overlap. */
@media (max-width:860px){
  .nav,.nav.is-stuck{padding-block:calc((var(--nav-h) - 44px) / 2)}
  .nav__in > *{flex-shrink:0}
  .nav__cta{white-space:nowrap;min-height:44px}
  .wordmark{padding-block:14px;margin-block:-14px}
}
@media (max-width:520px){
  .nav__in{gap:var(--s-4)}
  .wordmark{font-size:1.05rem;letter-spacing:.1em}
  .nav__cta{padding:0 12px;font-size:.75rem;letter-spacing:.08em}
}
@media (max-width:374px){
  .nav__in{gap:10px}
  .wordmark{font-size:.9rem;letter-spacing:.07em}
  .nav__cta{padding:0 9px;letter-spacing:.04em}
}
/* Phone on its side: 390px of height, so the bar gives some back. */
@media (max-height:500px) and (orientation:landscape){
  :root{--nav-h:60px}
}

/* ==========================================================================
   25. DEMONSTRATION SCREENS. /demo/analytics and /demo/portal.
   Both render synthetic fixtures so the two platform pages can show a
   capture instead of a drawing. Every surface here carries the demo flag:
   if one of these screenshots ever escapes without it, the label is wrong,
   not the caption underneath it.
   ========================================================================== */
.dflag{
  display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;
  border:1px solid var(--brass);background:var(--white);
  padding:var(--s-3) var(--s-4);margin-bottom:var(--s-5);
}
.dflag__d{width:6px;height:6px;background:var(--brass);flex:0 0 auto}
.dflag__t{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink);margin:0}
.dflag__s{font-family:var(--sans);font-size:var(--fs-sm);color:var(--ink-2);margin:0}

/* ---- app shell ---------------------------------------------------------- */
.dash{border:1px solid var(--rule);background:var(--white)}
.dash__top{
  display:flex;align-items:center;gap:var(--s-4);flex-wrap:wrap;
  padding:var(--s-4) var(--s-5);border-bottom:1px solid var(--rule);
  background:var(--paper);
}
.dash__mark{font-family:var(--serif);font-size:1.15rem;color:var(--ink);letter-spacing:.01em}
.dash__ws{
  display:inline-flex;align-items:center;gap:var(--s-3);
  border:1px solid var(--rule);background:var(--white);
  padding:var(--s-2) var(--s-3);
  font-family:var(--sans);font-size:var(--fs-sm);color:var(--ink);
}
.dash__ws::after{content:"";width:0;height:0;border-left:4px solid transparent;
  border-right:4px solid transparent;border-top:5px solid var(--grey)}
.dash__sp{flex:1 1 auto}
.dash__pill{
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.08em;
  text-transform:uppercase;color:var(--grey);
  border:1px solid var(--rule);padding:var(--s-2) var(--s-3);white-space:nowrap;
}
.dash__av{width:30px;height:30px;flex:none;border:1px solid var(--rule);
  display:grid;place-items:center;font-family:var(--mono);font-size:var(--fs-label);
  color:var(--brass);background:var(--white)}

.dash__nav{display:flex;gap:0;border-bottom:1px solid var(--rule);overflow-x:auto}
.dash__nav span{
  font-family:var(--sans);font-size:var(--fs-sm);color:var(--grey);
  padding:var(--s-3) var(--s-5);white-space:nowrap;border-right:1px solid var(--rule);
}
.dash__nav span[data-on]{color:var(--ink);box-shadow:inset 0 -2px 0 var(--brass)}

.dash__body{padding:var(--s-5)}

/* ---- kpi row ------------------------------------------------------------ */
.dkpi{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;background:var(--rule);
  border:1px solid var(--rule);margin-bottom:var(--s-5)}
@media (min-width:760px){.dkpi{grid-template-columns:repeat(4,1fr)}}
.dkpi__i{background:var(--white);padding:var(--s-4)}
.dkpi__k{display:block;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.09em;
  text-transform:uppercase;color:var(--grey);margin-bottom:var(--s-3)}
/* Cormorant defaults to old-style figures, which renders 1 as something very
   close to a capital I. Elegant in a headline, wrong on a dashboard where the
   whole point is reading the number. Lining and tabular, so columns align too. */
.dkpi__v{display:block;font-family:var(--serif);font-size:1.75rem;line-height:1;color:var(--ink);
  font-variant-numeric:lining-nums tabular-nums;font-feature-settings:"lnum" 1,"tnum" 1}
.dkpi__d{display:block;font-family:var(--mono);font-size:var(--fs-label);color:var(--ink-2);margin-top:var(--s-2)}
.dkpi__d[data-dir="up"]{color:#2F6B37}
.dkpi__d[data-dir="down"]{color:#8A3324}

/* ---- panels ------------------------------------------------------------- */
.dgrid{display:grid;gap:var(--s-5)}
/* A grid track sized auto takes its minimum from its content, so one nowrap
   cell in the invoice table drags the whole column past a 320px viewport.
   min-width:0 lets the track shrink; the table then scrolls inside its own
   card rather than the page scrolling sideways. */
.dgrid>*,.dcard,.dash{min-width:0}
@media (min-width:900px){.dgrid--2{grid-template-columns:1fr 1fr}}
.dcard{border:1px solid var(--rule)}
.dcard__h{display:flex;align-items:baseline;gap:var(--s-3);flex-wrap:wrap;
  padding:var(--s-4);border-bottom:1px solid var(--rule);background:var(--paper)}
.dcard__t{font-family:var(--sans);font-weight:500;font-size:var(--fs-sm);color:var(--ink);margin:0}
.dcard__n{font-family:var(--mono);font-size:var(--fs-label);color:var(--grey);margin:0 0 0 auto}
.dcard__b{padding:var(--s-4);overflow-x:auto;-webkit-overflow-scrolling:touch}
.dchart{display:block;width:100%;height:auto}

/* ---- tables ------------------------------------------------------------- */
.dtbl{width:100%;border-collapse:collapse;font-family:var(--sans);font-size:var(--fs-sm)}
.dtbl th{
  text-align:left;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.09em;
  text-transform:uppercase;color:var(--grey);font-weight:400;
  padding:0 var(--s-3) var(--s-3) 0;border-bottom:1px solid var(--rule);
}
.dtbl td{padding:var(--s-3) var(--s-3) var(--s-3) 0;border-bottom:1px solid var(--rule);color:var(--ink-2);vertical-align:top}
.dtbl tr:last-child td{border-bottom:0}
.dtbl td b{font-weight:500;color:var(--ink)}
.dtbl__n{text-align:right;font-family:var(--mono);font-size:var(--fs-sm);padding-right:0;white-space:nowrap;color:var(--ink-2)}
/* .dtbl th already sets alignment at the same specificity, so the header cell
   of a numeric column has to say so explicitly or it falls back to left. */
.dtbl th.dtbl__n{text-align:right}
.dbar{display:block;height:4px;background:var(--rule);margin-top:6px}
.dbar i{display:block;height:100%;background:var(--brass)}

/* ---- status tags -------------------------------------------------------- */
.dtag{display:inline-block;font-family:var(--mono);font-size:var(--fs-label);
  letter-spacing:.07em;text-transform:uppercase;padding:2px var(--s-2);
  border:1px solid var(--rule);color:var(--ink-2);white-space:nowrap}
.dtag[data-s="open"]{border-color:var(--brass);color:var(--brass)}
.dtag[data-s="done"]{color:#2F6B37;border-color:#A9C4AC}
.dtag[data-s="due"]{color:#8A3324;border-color:#D6B3AC}

/* ---- project phase rail ------------------------------------------------- */
.dstep{display:grid;gap:var(--s-3)}
.dstep__i{display:grid;grid-template-columns:22px 1fr auto;gap:var(--s-3);align-items:start}
.dstep__m{width:11px;height:11px;margin-top:4px;border:1px solid var(--grey);border-radius:50%}
.dstep__i[data-on] .dstep__m{background:var(--brass);border-color:var(--brass)}
.dstep__i[data-now] .dstep__m{border-color:var(--brass);box-shadow:0 0 0 3px rgba(138,106,31,.17)}
.dstep__n{font-family:var(--sans);font-size:var(--fs-sm);color:var(--ink)}
.dstep__d{display:block;font-family:var(--mono);font-size:var(--fs-label);color:var(--grey);margin-top:2px}
.dstep__w{font-family:var(--mono);font-size:var(--fs-label);color:var(--grey);white-space:nowrap}

/* ---- chat thread -------------------------------------------------------- */
.dchat{display:grid;gap:var(--s-4)}
.dchat__m{border:1px solid var(--rule);padding:var(--s-3) var(--s-4);background:var(--white)}
.dchat__m[data-who="studio"]{background:var(--paper)}
.dchat__w{display:block;font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.07em;
  text-transform:uppercase;color:var(--grey);margin-bottom:var(--s-2)}
.dchat__t{margin:0;font-family:var(--sans);font-size:var(--fs-sm);color:var(--ink-2)}

@media (max-width:620px){
  .dash__body{padding:var(--s-4)}
  .dkpi__v{font-size:1.45rem}
  .dash__top{padding:var(--s-3) var(--s-4)}
}


/* ===========================================================================
   LAUNCH FIXES, October 2026
   Each rule names the defect it answers.
   ========================================================================= */

/* The hidden attribute always wins. .cart{display:flex} overrode it, so a
   "0 selected" bar covered the catalogue from first load. */
[hidden]{display:none!important}

/* Button pairs wrap as a row with a gap. An inline margin-left left the second
   button 16px off the first once the pair stacked on a phone. */
.ctas{display:flex;flex-wrap:wrap;gap:var(--s-4);align-items:center}

/* The ruled grid frames the page instead of crossing the reading column
   (Enzo: hard to read). The three inner rules belong to the opening plate:
   they draw in with the hero or the page title, then fade once you scroll
   into content, where they used to run through words. The two margin rules
   stay, moved out into the gutter so they never touch the first letter of a
   line. The full-bleed rotators sat under the rules; they join the content
   layer. (A paper text-shadow knockout was tried first: WebKit paints a
   zero-blur shadow as shifted glyph copies, so it cannot cover a word gap.) */
.rot{z-index:2}
.gridlines__v{transition:opacity .7s var(--e-move)}
html.grid-quiet .gridlines__v:nth-child(2),
html.grid-quiet .gridlines__v:nth-child(3),
html.grid-quiet .gridlines__v:nth-child(4){opacity:0}
@media (min-width:861px){
  .gridlines__v:nth-child(1){left:calc(var(--gutter) - 28px)}
  .gridlines__v:nth-child(5){right:calc(var(--gutter) - 28px)}
}
@media (max-width:860px){
  .gridlines__v:nth-child(1){left:calc(var(--gutter) * .45)}
  .gridlines__v:nth-child(5){right:calc(var(--gutter) * .45)}
}

/* A lone last card in a two-up grid left half a row empty; it spans instead. */
.grid2 > :last-child:nth-child(odd):not(:first-child){grid-column:1/-1}

/* One nav breakpoint (the links and the burger used 880 and 860, so 861 to
   880 had neither), the CTA and burger pushed right, and the CTA label short
   enough to stay on one line on a phone. */
@media (max-width:860px){.nav__in > [data-mag]{margin-left:auto}}
@media (max-width:520px){.nav__ctax{display:none}}

/* Services ticked on /services, shown where the quote is written. */
.modal__sel{margin:0 0 var(--s-5);padding:var(--s-4) var(--s-5);border:1px solid var(--rule);background:var(--paper)}
.modal__selk{font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;text-transform:uppercase;
  color:var(--brass);margin:0 0 var(--s-3)}
.modal__sell{list-style:none;margin:0;padding:0;display:grid;gap:6px}
.modal__sell li{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s-4);
  font-size:var(--fs-sm);line-height:1.45;color:var(--ink)}
.modal__selx{flex:none;background:none;border:0;padding:0;cursor:pointer;font-family:var(--mono);
  font-size:var(--fs-label);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2);
  text-decoration:underline;text-underline-offset:3px}
.modal__selx:hover,.modal__selx:focus-visible{color:var(--brass)}

/* Phones: the rotators become a swipe row that snaps to whole cards. The
   conveyor was always caught mid-travel, so the first card read as sliced
   off at the left edge (Enzo's screenshot). site.js skips the conveyor here.
   Any touch screen gets the swipe row too, not only narrow ones: on an iPad
   held sideways the conveyor's pointer drag fought the finger's own scroll.
   The 2px of block padding keeps the cards' bottom border inside the
   scroller, which otherwise clipped it. */
@media (max-width:860px),(pointer:coarse){
  .rot:not(.is-conveyor){overflow-x:auto;scroll-snap-type:x mandatory;-webkit-mask-image:none;mask-image:none;
    padding-inline:var(--gutter);scroll-padding-inline:var(--gutter);padding-block:2px;
    scrollbar-width:none;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch}
  .rot::-webkit-scrollbar{display:none}
  .rot__track{width:max-content}
  .rot:not(.is-conveyor) .rot__track > *{scroll-snap-align:start}
  /* Phones now run the conveyor (site.js marks it): the row travels on its
     own, so it keeps the desktop edge fade, which turns the card cut by the
     screen edge into one fading in or out. Block padding keeps the cards'
     bottom border inside the clipped row. */
  .rot.is-conveyor{padding-block:2px}
}
@media (max-width:860px){
  .appicon{width:min(82vw,340px)}
}

/* Phone hero: a landscape loop under object-fit:cover showed only the left
   fittings, cut at the edge, under a heavy scrim. Frame the machined column
   and keep the scrim strong only where the copy sits (lower half). */
@media (max-width:860px){
  .hero--film .hero__film video{object-position:72% 50%}
  .hero--film .hero__film{background-position:72% 50%}
  .hero--film .hero__wash{
    background:linear-gradient(180deg,
      rgba(252,251,248,.16) 0%,
      rgba(252,251,248,.26) 36%,
      rgba(252,251,248,.80) 47%,
      rgba(252,251,248,.88) 100%);
  }
}

/* iOS draws its own play glyph over a video whose autoplay was refused (Low
   Power Mode). The poster stands in; the glyph should not. */
video::-webkit-media-controls-start-playback-button{display:none!important;-webkit-appearance:none}

/* A select is as wide as its longest option, and a grid item will not shrink
   below its content: "Consulting or other professional services" pushed
   /contact to 330px in a 320px viewport. Form controls fill their column. */
.field > *{min-width:0}
.field input,.field textarea,.field select{width:100%;max-width:100%;box-sizing:border-box}

/* Safari draws a native select at its own 28px height and ignores the 50px
   min-height every other field has, so on an iPhone the sector picker sat
   half the height of the inputs around it. Own the appearance in every engine
   and draw the chevron with two gradients (no image, nothing for the CSP). */
.field select{
  -webkit-appearance:none;appearance:none;border-radius:0;padding-right:44px;
  background-color:var(--white);
  background-image:linear-gradient(45deg,transparent 50%,var(--ink-2) 50%),linear-gradient(135deg,var(--ink-2) 50%,transparent 50%);
  background-position:calc(100% - 22px) 50%,calc(100% - 17px) 50%;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;
}

/* Touch screens: a finger needs about 44px. Footer and card links were 19 to
   22px tall and the blog filters 38px. Only coarse pointers get the bigger
   boxes, so the desktop layout keeps its density. */
@media (pointer:coarse){
  .ftr ul{gap:0}
  .ftr li > a,.ftr p > a{display:inline-flex;align-items:center;min-height:44px}
  .wc__links{row-gap:0}
  .wc__host,.wc__case{min-height:44px;display:inline-flex;align-items:center}
  .ins__filter{min-height:44px}
  .mail a,.cs__fact a,.stated__d a,.sl a{display:inline-block;padding-block:12px;margin-block:-12px}
}

/* "Read" on a blog card only appeared on hover, so a phone never showed it. */
@media (hover:none){
  .post__go{position:static;display:block;margin-top:var(--s-3);opacity:1;transform:none}
}

/* A dialog or the menu focused as a whole (opened by touch) needs no ring:
   it is plainly open. Its controls keep their own focus styles. */
.modal__box:focus,.navdraw:focus{outline:none}

/* The quote dialog and the phone menu scroll on their own. Lenis is stopped
   while either is open, and a stopped Lenis cancels every touchmove and wheel
   that does not come from a data-lenis-prevent element, so on a phone the
   form below the fold and the send button could not be reached. The markup
   carries the attribute; these keep the scroll inside the panel. */
.modal__box,.navdraw{overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
@media (max-width:620px){
  .modal__box{max-height:92vh;max-height:92svh}
}
/* The menu centred its links in whatever height it had and could not scroll,
   so on a phone held sideways half the links sat off the bottom. */
.navdraw{overflow-y:auto}
@media (max-height:520px){
  .navdraw{justify-content:flex-start;padding-block:24px}
  /* Auto margins centre the list when it fits and fall to zero when not. */
  .navdraw > :first-child{margin-top:auto}
  .navdraw > :last-child{margin-bottom:auto}
  .navdraw a{font-size:clamp(1.4rem,7vh,2rem);padding:6px 0}
}

/* Small phone layout repairs found in the device review. */
/* Card links: the host link carried its own top margin inside a flex row, so
   "Case study" sat higher than the address beside it. */
.wc__links .wc__host{margin-top:0}
/* Post cards: half the meta rows fit on one line and half wrapped the reading
   time alone onto a second; the category takes its own line on phones so every
   card reads the same. */
@media (max-width:519px){.post__cat{flex-basis:100%}}
/* Course status chip: wrapped chips started 12px in from the text column. */
@media (max-width:560px){.co__st{display:flex;width:max-content;margin:var(--s-2) 0 0}}
/* Privacy: a list's last rule and the next block's first rule drew an empty
   row between them. */
.sl + .stated > .stated__item:first-child{border-top:0}
/* Degree lines: no year alone on its own line. */
.ppl__deg span{text-wrap:balance}

/* Second device-review batch. */
/* Grid rules: on a phone only the two margin rules remain; the centre rule ran
   straight through the masthead copy. (The fifth, right-hand rule was styled
   but never in the markup, so the frame had a left edge and no right one.) */
@media (max-width:860px){.gridlines__v:nth-child(3){display:none}}
/* The dialog's height on a desktop with a browser toolbar. */
.modal__box{max-height:min(88vh,860px);max-height:min(88svh,860px)}
@media (max-width:620px){.modal__box{max-height:92vh;max-height:92svh}}
/* One word alone on a second line under an eyebrow rule. */
.eyebrow > span:last-child{text-wrap:balance}
/* Hero meta: on a phone the separators ended a line with a stray dot. */
@media (max-width:520px){.hero__meta .sep{display:none}}
/* Figure titles cut with an ellipsis at 360. */
@media (max-width:420px){.scr__chrome{gap:var(--s-2)}.scr__host{letter-spacing:.02em}}
/* Footer on a tablet: three groups and the fourth alone on a second row. */
@media (min-width:700px) and (max-width:1023px){.ftr__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}
/* /platform index: "A-01" broke into "A-" and "01", and the description and
   stack columns started at a different x on every row. One fixed set of tracks
   for every row, and the code never breaks. */
.wk__n{white-space:nowrap}
@media (min-width:900px){.wk__a{grid-template-columns:7.5rem minmax(0,1.1fr) minmax(0,1.25fr) 15rem}}
/* Demo portal on a phone: invoice numbers broke at the hyphen and the tables
   lost their width to nested padding. */
.dtbl__id{white-space:nowrap}
@media (max-width:420px){.dash__body{padding:var(--s-3)}.dcard__b{padding:var(--s-3)}.dtbl td:last-child .dtag{white-space:normal;text-align:center}.dstep__i{grid-template-columns:18px 1fr}.dstep__w{grid-column:2}}

/* The console captures are 2100px wide and shrink to a 94px strip on a phone,
   so each one opens full size on a tap, where it can be zoomed. */
.opsshot__a{display:block;cursor:zoom-in}
.opsshot__a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}

/* Touch screens keep :hover after a tap, so a tapped card stayed zoomed or
   lifted until the next tap somewhere else. The hover motion is for a mouse. */
@media (hover:none){
  .wc:hover .wc__shot img,.wc:hover .wc__shot video,.appshot:hover img,.opsshot:hover img,
  a.scr:hover .scr__body img,.ppl__p:hover .ppl__shot img{transform:none;filter:none}
  .appicon:hover,a.prod:hover{transform:none}
  .wk__a:hover .wk__name{transform:none}
}

/* Mid widths (phone on its side, tablet upright): three stats went two and one,
   and four case facts three and one, each leaving an empty half row. Every
   .grid3 holds three figures, so it keeps three columns from 600px. */
@media (min-width:600px) and (max-width:1023px){
  .grid3{grid-template-columns:repeat(3,minmax(0,1fr))}
  .cs__facts{grid-template-columns:repeat(2,minmax(0,1fr))}
}
/* Phone on its side: a 16:10 work card was taller than the 390px screen, so no
   one ever saw a whole one. The recording is top-anchored, so a shorter frame
   only trims the bottom of the footage. */
@media (orientation:landscape) and (max-height:520px){
  .wc__shot{aspect-ratio:auto;height:calc(100svh - var(--nav-h) - 40px)}
}

/* Selected services in the quote dialog: each Remove was a 17px line of text
   with rows 26px apart. On touch, each row is a 44px target and the rows sit
   on hairlines so they read as a list, not a squeeze. */
@media (pointer:coarse){
  .modal__sell{gap:0}
  .modal__sell li{align-items:center;min-height:44px;border-bottom:1px solid var(--rule)}
  .modal__sell li:last-child{border-bottom:0}
  .modal__selx{min-height:44px;padding-inline:var(--s-3);margin-right:calc(-1 * var(--s-3))}
}

/* Focus handed back after a tap: kept, but not drawn. */
[data-quiet-focus]:focus{outline:none}

/* Third device-review batch (320px phones). */
/* Display headings and eyebrow labels: no single word alone on a last line.
   Both heading layers balance, so the foil still sits exactly on the ink. */
.fs__base,.fs__foil{text-wrap:balance}
@media (max-width:359px){
  .eyebrow{gap:var(--s-3);letter-spacing:.12em}
  .eyebrow__rule{flex-basis:24px}
  .btn2{letter-spacing:.06em;padding-inline:var(--s-4)}
}
/* Blog card meta: date and reading time stay together. */
.post__top > span:last-child{white-space:nowrap}
/* Founder cards under 360px: photo above the text, not a strip beside it. */
@media (max-width:359px){
  .ppl--row .ppl__p{grid-template-columns:1fr}
  .ppl--row .ppl__shot{width:120px}
}
/* Testimonials on a phone: the card filled the row, so it read as one static
   quote. A slice of the next card shows that the row moves. */
@media (max-width:860px){.rot .tst__card{flex:0 0 min(520px,78vw)}}
/* Demo portal tables at 320: keep the state column whole. */
@media (max-width:420px){.dtbl td,.dtbl th{padding-right:var(--s-2)}}
/* Hero labels over the footage on a phone: small grey mono on brass and
   steel. A soft paper halo keeps them legible without dimming the film. */
@media (max-width:860px){
  .hero__meta{text-shadow:0 0 2px rgba(252,251,248,.95),0 0 9px rgba(252,251,248,.9)}
}

/* Blog list on a tablet: titles held to 34ch left half of every row empty. */
@media (min-width:700px) and (max-width:1023px){.post__t{max-width:56ch}}

/* "Open my email app" under a form that handed its request to the visitor's
   mail app: its own line, a full-size button. */
.form__status .form__mail{display:flex;width:max-content;max-width:100%;margin-top:var(--s-4);letter-spacing:.12em}
/* Under the hand-off: the address and two copy buttons, in plain sentence
   case so they read as help, not as the status label above them. */
.form__status .form__alt{display:block;margin-top:var(--s-5);font-family:var(--sans);font-size:var(--fs-sm);
  letter-spacing:normal;text-transform:none;color:var(--ink-2);line-height:1.6}
.form__alt a{color:var(--ink);text-decoration:underline;text-underline-offset:3px;word-break:break-all}
.form__copies{display:flex;flex-wrap:wrap;gap:var(--s-3);margin-top:var(--s-3)}
.form__copy{min-height:44px;padding:0 var(--s-4);background:transparent;border:1px solid var(--ink);color:var(--ink);
  font-family:var(--mono);font-size:var(--fs-label);letter-spacing:.12em;text-transform:uppercase;cursor:pointer}
.form__copy:hover{border-color:var(--brass);color:var(--brass)}
.form__copy:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
