/* Moxireel Studio hub - dark base + neon-pink signature (Bricolage Grotesque + Hanken Grotesk) */

/* ---------- fonts (self-hosted) ---------- */
@font-face{font-family:"Bricolage";src:url("fonts/bricolage.a97804dc9f.woff2") format("woff2");font-weight:200 800;font-display:swap;font-style:normal}
@font-face{font-family:"Hanken";src:url("fonts/hanken.e9201eddf1.woff2") format("woff2");font-weight:100 900;font-display:swap;font-style:normal}

/* ---------- tokens ----------
   The whole system lives here. Budgets, enforced:
   - type: ONE modular scale, ratio 1.25, anchored on the 17px body (--t0).
     Ten stops; five text steps (--t-2 to --t2), display continues the same ladder.
     Only exception: the decorative .ghost word (pure ornament, off any text scale).
   - space: 8px scale, eight steps (--s1..--s8). Fluid section padding clamps
     between scale stops only.
   - radii: three (--r1 chips/buttons, --r2 cards/inputs, --r3 pills). 50% = circles.
   - motion: three durations (--fast 150 / --med 300 / --slow 600), ONE easing,
     cubic-bezier(.65,.05,0,1), shared with the lab. Exceptions, all ambient
     loops, all reduced-motion-gated: aurora drift, scroll cue, screenshot pan,
     the 3D build loop's own cycle. (The terminal's cursor blink is gone with
     the terminal.)
   - breakpoints: two - 640 (stack), 880/881 (nav switch). The third, 1023,
     existed only to hide the terminal on tablets and went with it. */
:root{
  --canvas:#0A0A0C;
  --surface:#141419;
  --surface-2:#1C1D24;
  --elev:#101015;
  --ink:#F4F4F6;
  --ink-2:#B9BBC4;
  --muted:#7E828F;
  --line:rgba(255,255,255,.10);
  --line-2:rgba(255,255,255,.17);
  --accent:#FF2E97;
  --accent-2:#FF77BE;
  --accent-ink:#E01C82;      /* accent for light (paper) backgrounds, AA-safe */
  --accent-soft:rgba(255,46,151,.14);
  --paper:#F1EFEA;
  --paper-ink:#26272E;
  --paper-muted:#4A4B55;
  --paper-line:rgba(10,10,12,.14);
  --dark-text:#0A0A0C;
  /* type scale: 17px base x 1.25 */
  --t-2:.68rem;   /* 10.9px - micro caps, chips */
  --t-1:.85rem;   /* 13.6px - labels, small notes */
  --t0:1.0625rem; /* 17px - body */
  --t1:1.33rem;
  --t2:1.66rem;
  --t3:2.08rem;
  --t4:2.59rem;
  --t5:3.24rem;
  --t6:4.05rem;
  --t7:5.07rem;
  --t8:6.34rem; /* hero poster only, next step on the same ladder */
  /* space: 8px scale */
  --s1:8px; --s2:16px; --s3:24px; --s4:40px;
  --s5:64px; --s6:96px; --s7:128px; --s8:192px;
  /* radii */
  /* off-scale exceptions, all optical: 4px chip padding (half-step), 6px icon-dot
     gaps, 1-2px hairlines, negative hit-area/sr-only offsets, em-relative insets */
  --r1:4px; --r2:12px; --r3:999px;
  /* motion: the house curve, same one the lab runs, so the whole site moves
     with one accent. Slow start, fast sweep, soft landing. */
  --fast:.15s; --med:.3s; --slow:.6s;
  --ease:cubic-bezier(.65,.05,0,1);
  --shadow-sm:0 1px 2px rgba(0,0,0,.45),0 12px 28px -18px rgba(0,0,0,.55);
  --shadow-lg:0 1px 2px rgba(0,0,0,.4),0 26px 54px -26px rgba(0,0,0,.75);
  --glow:0 0 0 1px rgba(255,46,151,.35),0 16px 44px -16px rgba(255,46,151,.55);
  --display:"Bricolage",system-ui,-apple-system,Segoe UI,sans-serif;
  --sans:"Hanken",system-ui,-apple-system,Segoe UI,sans-serif;
  --label:var(--sans);  /* tracked caps labels: set in the text face on purpose */
  --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; /* real monospace: terminal, urls, metadata */
  --gutter:clamp(22px,6vw,64px);
  --maxw:1200px;
}
*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;font-variant-numeric:proportional-nums}
/* film grain over the whole canvas: kills the flat-hex-void read on dark panels */
body::after{content:"";position:fixed;inset:-50%;width:200%;height:200%;pointer-events:none;z-index:400;opacity:.045;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.86' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E")}
body{
  background:var(--canvas);color:var(--ink);font-family:var(--sans);
  font-size:17px;line-height:1.6;letter-spacing:0;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
body.lock{overflow:hidden}
a{color:inherit;text-decoration:none}
img{display:block;max-width:100%}
::selection{background:var(--accent);color:var(--dark-text)}
.wrap{width:100%;max-width:var(--maxw);margin:0 auto;padding-left:var(--gutter);padding-right:var(--gutter)}
/* tracking law: negative and scaling with size above ~40px (~ -2% of size),
   near-zero at text sizes, positive+wide on small caps labels */
h1,h2,h3,h4{font-family:var(--display);font-weight:600;letter-spacing:-.015em;line-height:1.15;text-wrap:balance;color:var(--ink)}
p{text-wrap:pretty}
strong{font-weight:600}
.accent{color:var(--accent)}
.stack-b{margin-bottom:clamp(24px,4vh,40px)}
.micronote{margin-top:var(--s2);color:var(--muted);font-size:var(--t-1)}
.micronote a{color:var(--accent)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* scroll progress (thin line; deliberately quiet, never the peak) */
/* The 2px pink scroll-progress bar is gone. Ash called it generic three
   times in one night and he is right: a thin accent progress bar across the
   top is 2019 blog-template furniture, and the site does not need a fuel
   gauge. */

/* The reveal system lives once, near the end of this file: one law,
   600ms / 12px / the house curve, JS-gated so content is always visible if
   JS fails. It used to be defined twice (300ms/10px here, 850ms/30px there)
   because two passes edited it on different nights; one definition now. */
@media (prefers-reduced-motion:reduce){
  *{scroll-behavior:auto!important}
}

/* ---------- nav ---------- */
/* min-height, because it measured 43px: one pixel under the target minimum, on
   every page, and the first thing a keyboard or screen-reader user meets. */
.skip{position:absolute;left:-9999px;top:10px;z-index:220;background:var(--accent);color:var(--dark-text);padding:var(--s1) var(--s2);border-radius:var(--r2);font-size:var(--t0);font-weight:600;
  min-height:44px;display:inline-flex;align-items:center}
.skip:focus{left:12px}
.nav{position:fixed;top:0;left:0;right:0;z-index:80;display:flex;align-items:center;justify-content:space-between;
  padding:var(--s2) var(--gutter);transition:background var(--med) var(--ease),box-shadow var(--med) var(--ease),transform var(--med) var(--ease)}
.nav.solid{background:rgba(10,10,12,.72);backdrop-filter:blur(16px);box-shadow:0 1px 0 var(--line)}
.nav.hide{transform:translateY(-115%)}
.nav:focus-within{transform:none}
/* padding + equal negative margin: the 34px wordmark gets a 50px touch target
   with zero layout shift */
.logo{display:inline-flex;align-items:center;gap:var(--s1);padding:var(--s1);margin:calc(-1*var(--s1));font-family:var(--display);font-weight:700;font-size:var(--t1);letter-spacing:-.02em;color:var(--ink)}
.logo svg{width:26px;height:26px;display:block}
.nav-right{display:flex;align-items:center;gap:clamp(16px,3vw,40px)}
.nav-links{display:flex;gap:clamp(16px,2.4vw,24px);font-family:var(--label);font-size:var(--t-1);letter-spacing:.08em;text-transform:uppercase}
.nav-links a{position:relative;color:var(--ink-2);padding:3px 0;transition:color var(--fast) var(--ease)}
.nav-links a:hover,.nav-links a.active{color:var(--ink)}
/* active marker is neutral: the nav never competes with the page's one pink peak */
.nav-links a.active::after{content:"";position:absolute;left:0;right:0;bottom:-3px;height:2px;border-radius:var(--r1);background:var(--ink)}
.nav-cta{position:relative;padding:var(--s1) var(--s2);border-radius:var(--r1);background:var(--ink);color:var(--dark-text);font-family:var(--label);font-size:var(--t-1);font-weight:600;text-transform:uppercase;letter-spacing:.08em;transition:background var(--fast) var(--ease),box-shadow var(--fast) var(--ease),transform var(--fast) var(--ease)}
/* invisible touch-target extension to 44px; the pill itself stays 38px */
.nav-cta::before{content:"";position:absolute;inset:-4px 0}
.nav-cta:hover{background:var(--accent);box-shadow:var(--glow);transform:translateY(-1px)}
.burger{display:none;width:44px;height:44px;margin:-10px;position:relative;background:none;border:0;cursor:pointer;z-index:90}
.burger span{position:absolute;left:11px;width:22px;height:2px;border-radius:var(--r1);background:var(--ink);transition:var(--med) var(--ease)}
.burger span:nth-child(1){top:16px}.burger span:nth-child(2){top:22px}.burger span:nth-child(3){top:28px}
body.menu-open .burger span:nth-child(1){top:22px;transform:rotate(45deg)}
body.menu-open .burger span:nth-child(2){opacity:0}
body.menu-open .burger span:nth-child(3){top:22px;transform:rotate(-45deg)}
.mobile-menu{position:fixed;inset:0;z-index:70;background:#08080A;color:#fff;display:flex;flex-direction:column;justify-content:center;gap:2px;padding:0 var(--gutter);
  transform:translateY(-100%);transition:transform var(--slow) var(--ease);pointer-events:none}
body.menu-open .mobile-menu{transform:translateY(0);pointer-events:auto}
.mobile-menu a{font-family:var(--display);font-size:clamp(var(--t3),9vw,var(--t5));font-weight:600;letter-spacing:-.02em;padding:var(--s1) 0;color:rgba(255,255,255,.55);transition:color var(--fast) var(--ease),padding-left var(--med) var(--ease)}
.mobile-menu a:hover,.mobile-menu a.active{color:#fff;padding-left:var(--s1)}
.mobile-menu a.active{color:var(--accent)}
.mobile-menu .mm-foot{margin-top:5vh;font-family:var(--label);font-size:var(--t-1);color:rgba(255,255,255,.5);letter-spacing:.02em}
@media(max-width:880px){.nav-links,.nav-cta{display:none}.burger{display:block}}
@media(min-width:881px){.mobile-menu{display:none}}

/* ---------- buttons (bespoke: asymmetric padding + circular "go" node) ---------- */
/* THE BUTTONS WERE THE DEFAULT SHAPE.
   A wide bar with the label on the left and a circled arrow pinned to the
   right is what every framework ships, and stacked full width on a phone it
   is two grey slabs. Three changes, all structural rather than decorative:
   the label and the arrow sit together instead of being pushed to opposite
   ends, the button is only as wide as its words, and the arrow loses its
   circle so it reads as punctuation after the sentence rather than as a
   badge bolted onto the end. */
.btn{position:relative;display:inline-flex;align-items:center;gap:.55em;
  padding:var(--s2) var(--s3);border-radius:var(--r1);font-family:var(--sans);
  font-weight:600;font-size:var(--t0);letter-spacing:-.01em;cursor:pointer;border:1.5px solid transparent;
  transition:transform var(--med) var(--ease),background var(--med) var(--ease),border-color var(--med) var(--ease),color var(--med) var(--ease),box-shadow var(--med) var(--ease);will-change:transform}
/* The chevron that used to sit on the right of every button is deleted, all
   fifteen of them and the nine rules that dressed them. Ash flagged the
   arrow on the hero pair; it was on every button on the site, which made it
   decoration rather than an affordance. A button that says "Get my free
   preview" does not need a mark telling you it goes somewhere. */
.btn:hover{transform:translateY(-2px)}
.btn:active{transform:translateY(0)}

.btn-accent{background:var(--accent);color:var(--dark-text)}
.btn-accent:hover{background:var(--accent-2);box-shadow:var(--glow)}

.btn-fill{background:var(--ink);color:var(--dark-text)}
.btn-fill:hover{box-shadow:0 18px 40px -20px rgba(0,0,0,.8)}

.btn-ghost{border-color:var(--line-2);color:var(--ink);background:transparent}
.btn-ghost:hover{border-color:var(--accent)}

/* ---------- shared bits ---------- */
/* The kicker system is fully dead and deleted: Ash has a standing kill
   order on small uppercase labels above headings, the markup was purged from
   every page, and forty lines of orphaned .kicker / .kn / .kt rules were
   still riding along in this file. Headings stand on their own. */
/* rhythm: sections are NOT one repeated interval - flush after a hero/band,
   tight after a page-hero, paper chapters get their own breathing room.
   He asked for more air and the page wanted it: at the old 64px floor two
   unrelated ideas on a phone read as one block. Defined once, here. */
.sec{padding-top:clamp(88px,15vh,168px);padding-bottom:clamp(88px,15vh,168px)}
.sec--flush{padding-top:0}
.sec--tight{padding-top:clamp(56px,9vh,104px);padding-bottom:clamp(56px,9vh,104px)}
.sec-head{margin-bottom:clamp(32px,4.5vh,56px)}
.sec-title{font-size:clamp(var(--t3),5vw,var(--t5));letter-spacing:-.02em;line-height:.98;max-width:20ch}
.lead{max-width:60ch;margin-top:var(--s3);font-size:clamp(var(--t0),1.4vw,var(--t1));line-height:1.55;color:var(--ink-2)}

/* ---------- hero ----------
   The falling-code canvases that used to live in .hero-bg are gone (see the note
   at the end of this block). What is left is light: two low-alpha radial washes
   in the one accent hue, and the type. */
/* The "WEB STUDIO IN SUNRISE, FLORIDA" kicker is gone from the hero. It was
   a label above a headline that already introduces Ash and the deal two
   lines later, and the location still lives in the footer where addresses
   belong. With it gone the headline owns the top of the page; padding comes
   down a step so the composition tightens instead of leaving a hole. */
.hero{position:relative;overflow:hidden;padding-top:clamp(104px,17vh,160px);padding-bottom:clamp(64px,10vh,96px)}
/* .hero-ink is named here for exactly the reason .hero-bg is, and the comment
   down at .page-hero spells out what happens to a layer left off this list: it
   computes to position:relative, collapses to zero height, and clips its own
   contents away. Two exclusions now, both deliberate. */
.hero>*:not(.hero-bg):not(.hero-ink){position:relative;z-index:2}
.hero-bg{position:absolute;top:0;left:50%;transform:translateX(-50%);width:100vw;height:100%;z-index:0;pointer-events:none;overflow:hidden;
  /* .hero clips to the 1200px wrap, so the light used to get sliced off with a
     visible straight edge. The old dark scrims hid that seam; this fades the
     whole layer out instead, so the light falls off rather than being cut. */
  -webkit-mask-image:linear-gradient(180deg,#000 54%,transparent 97%);mask-image:linear-gradient(180deg,#000 54%,transparent 97%)}
/* glows are ambience, never a moment: low alpha, one hue family (no off-palette purple) */
/* right:9% (was 2%) so the falloff completes inside the 1200px clip on wide
   screens instead of ending on a straight vertical edge */
.hero-glow{position:absolute;width:min(60vw,760px);height:min(60vw,760px);top:-14%;right:9%;border-radius:50%;
  background:radial-gradient(circle,rgba(255,46,151,.13),rgba(255,46,151,0) 66%);filter:blur(34px);will-change:transform}
/* pulled in from left:-8% for the same reason */
.hero-glow.g2{width:min(46vw,520px);height:min(46vw,520px);top:auto;bottom:-22%;right:auto;left:0;
  background:radial-gradient(circle,rgba(255,46,151,.06),rgba(255,46,151,0) 66%)}
/* (The original 16px "floaty" bob was superseded by the aurora circuit at the
   end of this file; the dead keyframes and their two animation lines are
   deleted rather than left to shadow the real ones.) */
.hero-glow{z-index:1}
/* The two dark masks that used to sit on top (a 90deg wash that kept the copy
   column readable over the code, and a 180deg one that faded the code into the
   page) went with the canvases. Over a flat --canvas hero they did nothing but
   dim the glows. */
/* display law: line-height .95, tracking ~ -2.5% at poster size */
.hero h1{font-size:clamp(var(--t4),8vw,var(--t8));line-height:.95;letter-spacing:-.025em;max-width:14ch;text-shadow:0 2px 28px rgba(0,0,0,.55)}
.hero h1 .accent{color:var(--accent)}
.hero-sub{max-width:52ch;margin-top:var(--s3);font-size:clamp(var(--t0),1.4vw,var(--t1));line-height:1.55;color:var(--ink-2)}
/* THE HERO HAD TWO BUTTONS AND NOW HAS ONE.
   They were a filled bar and an outlined bar, the same shape, side by side,
   each with a chevron. On a phone they wrapped into two stacked bars of
   different widths, which reads as an accident rather than a decision, and
   Ash called the shape a framework default. He was right: two equal-weight
   buttons is what a page looks like when nobody decided which action matters.

   One action does matter. "See how it works" is the whole argument of the
   page and it stays a button. The other was a link to a portfolio page
   wearing a button's clothes, so it is a link now, with a rule under it that
   the accent takes on hover. That is a hierarchy instead of a pair, and it
   removes a box from a page that is meant to be decluttered. */
.hero-cta{display:flex;flex-wrap:wrap;align-items:center;
  gap:var(--s3) var(--s4);margin-top:var(--s4)}
/* the quiet alternative: a link, sized like body text, underlined with a real
   rule rather than text-decoration so it can be animated and coloured */
.lnk{position:relative;display:inline-flex;align-items:center;
  min-height:44px;font-size:var(--t0);font-weight:600;letter-spacing:-.01em;
  color:var(--ink-2);transition:color var(--med) var(--ease)}
.lnk::after{content:"";position:absolute;left:0;right:0;bottom:11px;height:1px;
  background:currentColor;opacity:.42;transform-origin:0 50%;
  transition:opacity var(--med) var(--ease),transform var(--med) var(--ease)}
.lnk:hover{color:var(--accent)}
.lnk:hover::after{opacity:1;transform:scaleX(1.04)}
.lnk:focus-visible{color:var(--accent)}
.page-hero{position:relative;overflow:hidden;padding-top:clamp(128px,18vh,192px);padding-bottom:clamp(24px,5vh,64px)}
/* :not(.hero-bg) matters, and its absence was a real bug.
   This rule and .hero-bg{position:absolute} have identical specificity, so
   whichever is written later wins, and this one is later. On every subpage
   the background layer was therefore computing to position:relative, which
   collapsed it to zero height, and since it also carries overflow:hidden it
   was clipping its own contents out of existence.
   Nothing showed it while the layer held only absolutely positioned glows
   with their own offsets. The moment real content went inside it, five
   subpage heroes rendered empty. The home page had the exclusion from the
   start (.hero>*:not(.hero-bg)); the subpages never got it. */
.page-hero>*:not(.hero-bg){position:relative;z-index:2}
.page-hero .hero-glow{opacity:.7}
.page-hero h1{font-size:clamp(var(--t4),6.6vw,var(--t7));line-height:.95;letter-spacing:-.025em;max-width:18ch}
.page-hero h1 .accent{color:var(--accent)}
/* subpage hero atmosphere: faint dot grid fading down, plus a giant ghost page word */
.page-hero .hero-bg::before{content:"";position:absolute;inset:0;z-index:3;
  background-image:radial-gradient(circle,rgba(255,46,151,.075) 1px,transparent 1.5px);background-size:26px 26px;
  -webkit-mask-image:linear-gradient(180deg,#000 0%,transparent 78%);mask-image:linear-gradient(180deg,#000 0%,transparent 78%)}
.ghost{position:absolute;z-index:1;right:-1.5vw;top:8px;font-family:var(--display);font-weight:700;line-height:.9;
  font-size:clamp(7rem,21vw,19rem);letter-spacing:-.03em;color:transparent;
  -webkit-text-stroke:1px rgba(255,46,151,.13);pointer-events:none;user-select:none}
/* Above 1200px the wrap's overflow clip sits inside the visible canvas, and
   it was slicing the ghost word off on a hard vertical line 1.5vw before its
   own edge. Same cure the hero light already uses: fade out just before the
   clip so the word falls off instead of being cut. */
@media(min-width:1201px){
  .ghost{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 1.5vw - 120px),transparent calc(100% - 1.5vw - 10px));
    mask-image:linear-gradient(90deg,#000 calc(100% - 1.5vw - 120px),transparent calc(100% - 1.5vw - 10px))}
}
@media(max-width:640px){.ghost{font-size:clamp(var(--t7),26vw,8rem);right:-4vw;top:14px;-webkit-text-stroke-color:rgba(255,46,151,.10)}}

/* ---------- trust row / stats (display-scale numerals in bordered tiles) ---------- */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--r2);overflow:hidden}
.stat{position:relative;background:var(--canvas);padding:clamp(24px,3.8vw,40px) clamp(16px,3vw,24px)}
.stat::after{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:linear-gradient(90deg,var(--accent),transparent 70%);opacity:0;transition:opacity var(--med) var(--ease)}
.stat:hover::after{opacity:.9}
.stat .num{font-family:var(--display);font-size:clamp(var(--t4),5.4vw,var(--t6));font-weight:650;letter-spacing:-.025em;line-height:1;font-variant-numeric:tabular-nums lining-nums}
.stat .num .u{color:var(--muted);font-size:.42em;font-weight:600;letter-spacing:0}
.stat .lab{margin-top:var(--s2);font-size:var(--t-1);color:var(--muted);line-height:1.5;text-transform:uppercase;letter-spacing:.06em;font-weight:500}
@media(max-width:880px){.stats{grid-template-columns:1fr 1fr}.stat .num{font-size:clamp(var(--t3),9vw,var(--t5))}}

/* ---------- steps (one bordered ledger panel, hairline rows, display numerals) ---------- */
.steps{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:var(--r2);overflow:hidden;background:rgba(255,255,255,.014)}
.step{display:grid;grid-template-columns:clamp(64px,7vw,96px) 1fr;gap:clamp(16px,3vw,24px);align-items:start;
  padding:clamp(24px,3.4vw,40px) clamp(16px,3vw,24px);border-bottom:1px solid var(--line);
  transition:background var(--med) var(--ease)}
.step:last-child{border-bottom:0}
.step:hover{background:rgba(255,255,255,.025)}
.step-n{font-family:var(--display);font-size:clamp(15px,1.6vw,18px);font-weight:650;color:var(--muted);
  line-height:1.3;padding-top:.35em;letter-spacing:-.01em;max-width:9ch}
/* the 01/02 chip prefix died here 15 Aug on Ash's order: numbers were
   decoration, these markers are information (when each step happens) */
.step h3{font-size:clamp(var(--t1),2.2vw,var(--t2));margin-bottom:var(--s1)}
.step p{color:var(--ink-2);font-size:var(--t0);max-width:60ch}
/* the reset zeroes every margin, so a step that runs to a second paragraph
   (the AI disclosure on /why) needs the gap put back explicitly */
.step p+p{margin-top:var(--s1)}
@media(max-width:640px){.step{grid-template-columns:1fr;gap:var(--s1)}.step-n{font-size:15px;padding-top:0}}

/* ---------- feature list ----------
   The 01 / 02 index column is gone. Those numbers ordered nothing (the
   features are not a sequence) and numbered chips as decoration are the
   same disease that already killed .idx on the work cards and .kn on the
   kickers; this was the last place it survived. Feature rows now share one
   grammar with the FAQ rows: hairline, heading, text. The step numbers on
   .steps stay, because steps happen in order and the number is the point. */
/* THE HAIRLINES ARE GONE, AND THAT IS THE FIX.
   Six identical full-width 1px rules stacked down a page do not separate
   anything, they just make it look like a spreadsheet, and Ash called
   them out twice: the lines look weird, and the whole thing is too crowded.
   Both complaints have the same cause. A rule between two blocks is what you
   reach for when there is not enough space between them; put the space in
   and the rule has no job left.

   So the separator is now air, and there is more of it than the rules were
   saving. Nothing replaces them. Specifically NOT a small mark above each
   heading and NOT a short line under each block, which are two of the nine
   template signatures he listed by name. */
.feats{border-top:0}
.feat{padding:clamp(34px,5.6vh,64px) 0;border-bottom:0}
.feat:first-child{padding-top:clamp(20px,3vh,32px)}
.feat h3{font-size:clamp(var(--t1),2.2vw,var(--t2));margin-bottom:var(--s2)}
.feat p{color:var(--ink-2);max-width:58ch}
/* the reset zeroes every margin, so a feature that runs to a second
   paragraph needs the gap put back. The Google one splits deliberately:
   what I do, then the one small thing only the owner can do. */
.feat p+p{margin-top:var(--s2)}
.feat .feat-yours{color:var(--muted)}

/* ---------- (the hero terminal card lived here) ----------------------------
   Deleted: .hero .term, .term-bar, .term-bar i (three traffic-light dots),
   .term-bar .tt, .term-body, .ln/.pr/.ok/.cur, @keyframes blink, .term-chip
   and its pink status dot. A fake command line on a studio's own site tells a
   prospective client, a barber, a roaster, a restaurant owner, nothing they
   can use, and tells a designer exactly which corner of the internet the
   layout came from. The hero now ends on its two buttons. */

/* ---------- (the code-rain canvases lived here) -----------------------------
   Deleted: canvas[data-rain] and everything that served it, the two stacked
   hero canvases (one blurred "deep" layer at .2, one at .42), the CTA bookend
   canvas at .16, the .cta-bg wrapper and its 90deg mask, the hero's two
   legibility masks, the mobile opacity override, and initRain() in app.js
   (~90 lines: glyph pool, per-column drop/speed/density arrays, 30fps rAF loop,
   IntersectionObserver pause, resize rebuild).

   Falling code is a developer's idea of decoration. The people this site sells
   to own barbershops and restaurants; the signal it sent them was "this is a
   programmer's homepage," which is the wrong shop window. It was also, like the
   terminal card above, a thing that gets generated rather than designed.

   Nothing replaced it. The hero already had the two things it needs, a 6.34rem
   headline with one pink word in it, and a wash of light behind it, and the
   CTA still has its own radial (.cta::after). Adding a second gimmick to cover
   the hole left by the first would have been the same mistake in a new costume. */

/* ---------- work cards (faux browser chrome) ---------- */
/* The promo bar is gone from pricing and deleted here. It was the third
   restatement of "from $500, look first, then decide" before the reader ever
   reached the first plan card: the H1 says it, the lead says it, the featured
   card says it, the fineprint says it. Saying it five times is how a page
   stops being believed. */
.work-grid{display:grid;grid-template-columns:1fr 1fr;gap:clamp(16px,2.6vw,24px)}
.work-card{position:relative;display:block;border-radius:var(--r2);overflow:hidden;background:var(--surface);
  border:1px solid var(--line);box-shadow:var(--shadow-sm);transition:transform var(--med) var(--ease),box-shadow var(--med) var(--ease),border-color var(--med) var(--ease)}
.work-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg);border-color:var(--line-2)}
/* The three traffic-light dots are gone from the card's chrome bar. What makes
   a screenshot read as a browser is the address, not the buttons, the bar
   keeps its URL, its rule and its lighter fill, and loses the ornament. */
/* The bar above each shot used to carry a mono URL pill and a big 01 / 02
   index number. The numbers were decoration pretending to be information,
   the same disease as the kicker chips, so they are gone from the markup.
   In their place the bar earns its keep: a small breathing dot marks that
   the thing behind the link is live right now, which happens to be the
   site's whole argument. Pink dot for client sites, neutral for the demo,
   and it breathes slowly rather than blinking, because blinking is an
   alarm and breathing is a heartbeat. */
.work-card .wc-top{display:flex;align-items:center;gap:var(--s1);padding:var(--s1) var(--s2);border-bottom:1px solid var(--line);background:var(--surface-2)}
.work-card .wc-top::after{content:none}
.work-card .wc-top::before{content:none}

@keyframes liveBreath{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.78)}}
@media(prefers-reduced-motion:reduce){.work-card .wc-top::before{animation:none}}
/* the promise two comments up, actually kept: the demo's dot is neutral.
   The CSS made every dot pink while the comment claimed otherwise. */
.work-card .badge.demo+.wc-inner .wc-top::before{background:var(--muted);box-shadow:none}
.work-card .wc-url{font-family:var(--mono);font-size:var(--t-1);color:var(--muted);letter-spacing:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1;background:none;border:0;border-radius:0;padding:0;text-underline-offset:3px;text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color 160ms var(--ease)}.work-card:hover .wc-url{text-decoration-color:var(--accent)}
.work-card .frame{aspect-ratio:16/10;overflow:hidden;background:var(--surface-2)}
.work-card .shot{width:100%;height:100%;object-fit:cover;object-position:center top;
  filter:saturate(.82) brightness(.92);transition:object-position var(--slow) var(--ease),filter var(--med) var(--ease)}
.work-card:hover .shot{object-position:center bottom;filter:none;transition:object-position 6s linear,filter var(--med) var(--ease)}
/* the PPT shot is a 1200x630 crop; centred in a 16/10 frame it slices the
   first letter off their own headline. Anchor it left so their words stay
   whole; the hover pan still takes over. */
img.shot[src*="ppt"]{object-position:left top}
/* full-width showcase variant (the real client, home page) */
/* two showcases stack on the home page; without this they sat border on
   border and the second card's badge straddled the first card's caption row.
   24px: the same clearance the work grid's gap gives a straddling badge. */
.work-card--wide+.work-card--wide{margin-top:var(--s3)}
.work-card--wide .frame{aspect-ratio:16/8}
.work-card--wide h3{font-size:clamp(var(--t1),2.6vw,var(--t3));max-width:24ch}
@media(max-width:640px){.work-card--wide .frame{aspect-ratio:16/12}}
/* demo chip row under the showcase */
.demo-row{display:flex;align-items:center;gap:var(--s1);flex-wrap:wrap;margin-top:var(--s2)}
.demo-row .dr-lab{font-family:var(--label);font-size:var(--t-2);font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-right:var(--s1)}
.demo-row a{display:inline-flex;align-items:center;gap:var(--s1);border:1px solid var(--line-2);border-radius:var(--r3);
  padding:var(--s1) var(--s2);font-size:var(--t0);font-weight:600;color:var(--ink-2);transition:border-color var(--fast) var(--ease),color var(--fast) var(--ease),transform var(--fast) var(--ease)}
.demo-row a:hover{border-color:var(--accent);color:var(--ink);transform:translateY(-2px)}
.demo-row a span{color:var(--muted)}
/* The dashed border came off. Two pills side by side, one solid and one
   dashed, at two different widths, reads as a mistake rather than as a
   distinction, and it was the 'layout slightly off' tell in miniature.
   They are the same kind of thing (a link to more work) so they look the
   same; the difference in weight is carried by the border colour alone. */
.demo-row .dr-all{color:var(--ink-2);border-color:var(--line)}
.demo-row .dr-all:hover{color:var(--ink)}
/* honesty badge straddling the card's top edge + corner index numeral */
.work-card .badge{position:absolute;z-index:3;top:-12px;left:50%;transform:translateX(-50%);white-space:nowrap;
  font-family:var(--sans);font-size:var(--t-2);font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  padding:var(--s1) var(--s2);border-radius:var(--r3)}
/* the glow came off. A pink pill with a halo floating over the top edge of
   a card is the same glowing-pill move that got the Labs band deleted, and
   the line under the screenshot already says 'Real client, live site' in
   words. Flat pink, no halo: it labels, it does not perform. */
.work-card .badge.real{background:var(--accent);color:var(--dark-text);box-shadow:none}
.work-card .badge.demo{background:var(--canvas);color:var(--ink-2);border:1px solid var(--line-2)}
/* idx sits on surface-2: ink-2, not muted, so it clears AA there */
/* .idx is dead: the 01 / 02 / 03 counters on the work cards were the same
   numbered-label disease as the kicker chips, decoration dressed as data.
   Markup spans are deleted; this tombstone stops them coming back. */

.work-card{overflow:visible}
.work-card .wc-inner{border-radius:var(--r2);overflow:hidden;display:block}
.work-card .meta{font-family:var(--sans);font-size:var(--t-1);letter-spacing:0;text-transform:none;color:var(--muted);margin-bottom:var(--s1)}
.work-card .meta .y{color:var(--ink-2)}
/* staggered grid on the work page */
.work-grid--stagger .work-card:nth-child(even){margin-top:clamp(24px,4vh,40px)}
@media(max-width:640px){.work-grid--stagger .work-card:nth-child(even){margin-top:0}}
.work-card .wc{display:flex;justify-content:space-between;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3)}
.work-card h3{font-size:clamp(var(--t1),2.2vw,var(--t2))}
.work-card .go{font-family:var(--sans);font-size:var(--t-1);font-weight:500;text-transform:none;letter-spacing:0;color:var(--muted);white-space:nowrap;transition:color 160ms var(--ease)}
.work-card:hover .go{color:var(--ink)}
.work-card:focus-visible{outline-offset:4px;border-radius:var(--r2)}
@media(max-width:640px){.work-grid{grid-template-columns:1fr}}

/* ---------- band (calm static statement) ---------- */
.band{position:relative;overflow:hidden;background:var(--elev);border:1px solid var(--line);border-radius:var(--r2);padding:clamp(40px,7vw,96px) clamp(24px,5vw,64px)}
.band>*{position:relative}
.band p{font-family:var(--display);font-size:clamp(var(--t2),4.4vw,var(--t5));font-weight:600;letter-spacing:-.02em;line-height:1;max-width:20ch}
.band p .accent{color:var(--accent)}
/* flat neon interlude: the one full-pink moment per page, punctuates the dark scroll */
.band--pink{background:var(--accent);border-color:var(--accent);color:var(--dark-text)}
.band--pink::after{display:none}
.band--pink p{color:var(--dark-text);max-width:22ch;font-size:clamp(var(--t3),5.4vw,var(--t6))}
.band--pink p .accent{color:#fff;text-shadow:0 1px 0 rgba(0,0,0,.18)}

/* ---------- pricing ----------
   Not a 3-card SaaS row: the featured plan is a tall left column, the two
   secondary plans stack beside it. One pink object (the featured card). */
/* THREE RUNGS, NOT TWO, AND THE REASON IS NOT DECORATION.
   With only two prices on a page, almost everybody takes the
   cheaper one, because with only two options the cheap one is simply
   "the normal one". A third in the middle changes what the cheapest option
   means: it stops being the default and becomes the stripped one. People
   avoid the ends of a range. Ash asked for the biggest yes, and the
   biggest yes is a middle that is easy to say yes to. */
.plans{display:grid;grid-template-columns:repeat(3,1fr);grid-auto-rows:auto;gap:var(--s2)}
.plan{display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--line);border-radius:var(--r2);
  padding:clamp(24px,3vw,40px);transition:transform var(--med) var(--ease),box-shadow var(--med) var(--ease),border-color var(--med) var(--ease)}
.plan:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg);border-color:var(--line-2)}
.plan.feat{grid-row:1/span 2;background:linear-gradient(180deg,rgba(255,46,151,.07),transparent 60%),var(--surface);border-color:rgba(255,46,151,.4);box-shadow:var(--glow)}
.plan .pt{font-family:var(--label);font-size:var(--t-1);font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
.plan .pr{font-family:var(--display);font-size:clamp(var(--t4),5.4vw,var(--t6));font-weight:600;letter-spacing:-.025em;line-height:1;margin:var(--s2) 0 0;font-variant-numeric:tabular-nums lining-nums}
/* tracking scales with size: the 80px price gets the most negative value on the page */
.plan.feat .pr{font-size:clamp(var(--t5),6.6vw,var(--t7));line-height:1;letter-spacing:-.03em;color:var(--ink)}
/* "from" is a caps label on EVERY plan that carries one. It was styled only
   on the featured card, so the other two cards rendered their label inline
   at price size: "Site and assistant, from$900" in 64px type. The label is
   muted everywhere and pink only on the featured card, where the page's one
   pink object already lives. */
.plan .pr .from{display:block;font-family:var(--label);font-size:var(--t-1);font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:.4rem}
.plan.feat .pr .from{color:var(--accent)}
/* email copy chip */
.copychip{position:relative;display:inline-flex;align-items:center;gap:var(--s1);margin-top:var(--s2);background:var(--surface);border:1px solid var(--line-2);
  border-radius:var(--r2);padding:var(--s1) var(--s2);font-family:var(--mono);font-size:var(--t-1);color:var(--ink);
  cursor:pointer;transition:border-color var(--fast) var(--ease),box-shadow var(--fast) var(--ease)}
/* invisible touch-target extension: the 34px chip answers to 44px of thumb */
.copychip::after{content:"";position:absolute;inset:-5px 0}
.copychip:hover{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.copychip .cc-ic{color:var(--muted);font-size:var(--t0);line-height:1}
.copychip .cc-done{color:var(--accent);font-family:var(--sans);font-size:var(--t-1);font-weight:700;letter-spacing:.08em;text-transform:uppercase}
/* big proof statement (real client) */
.proofline{font-family:var(--display);font-weight:600;font-size:clamp(var(--t2),4.4vw,var(--t5));line-height:1.05;letter-spacing:-.02em;max-width:24ch}
.proofline .accent{color:var(--accent)}
.proofline-meta{margin-top:var(--s2);font-family:var(--mono);font-size:var(--t-1);letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted);display:flex;gap:var(--s2);flex-wrap:wrap}
.proofline-meta span{display:inline-flex;align-items:center;gap:var(--s2)}
.proofline-meta span+span::before{content:"";width:1px;height:10px;background:currentColor;opacity:.5}
.plan .pr .c{font-size:.42em;font-weight:500;color:var(--muted)}
.plan .pd{color:var(--ink-2);font-size:var(--t0);min-height:3em}
.plan ul{list-style:none;margin:var(--s3) 0;display:flex;flex-direction:column;gap:var(--s1);font-size:var(--t0)}
.plan li{display:flex;gap:var(--s1);align-items:flex-start;color:var(--ink-2)}
.plan li::before{content:"";flex:none;margin-top:.5em;width:6px;height:6px;border-radius:50%;background:var(--muted)}
.plan .btn{margin-top:auto;justify-content:center}
.fineprint{margin-top:var(--s4);color:var(--muted);font-size:var(--t0)}
/* the care plan closes the grid: without this, row two ended on an empty
   dark cell under the third card and the layout read as a missing card.
   Wide also matches its role: the quiet optional strip under the three
   real choices, not a fourth rival column. */
@media(min-width:1081px){.plans .plan:nth-child(4){grid-column:2/-1}}
@media(max-width:1080px){.plans{grid-template-columns:1fr}}

/* ---------- faq ---------- */
.faq{border-top:1px solid var(--line)}
.q{border-bottom:1px solid var(--line)}
.q-h{width:100%;display:flex;justify-content:space-between;align-items:center;gap:var(--s3);
  padding:clamp(16px,3vh,24px) 0;background:none;border:0;font:inherit;color:inherit;text-align:left;cursor:pointer;transition:color var(--fast) var(--ease)}
.q-h:hover h3{color:var(--accent)}
.q-h h3{font-size:clamp(var(--t0),2vw,var(--t1));font-weight:600;transition:color var(--fast) var(--ease)}
.q-ic{flex:none;width:22px;height:22px;position:relative}
.q-ic::before,.q-ic::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--ink-2)}
.q-ic::before{width:14px;height:2px;border-radius:var(--r1)}
.q-ic::after{width:2px;height:14px;border-radius:var(--r1);transition:transform var(--med) var(--ease),opacity var(--med) var(--ease)}
.q.open .q-ic::after{transform:translate(-50%,-50%) rotate(90deg);opacity:0}
/* grid-rows animation: robust to font swap/resize, no JS height math. Padding only when open.
   The answer's paragraphs live inside ONE wrapper (.q-in). They used to be
   direct grid children, and grid-template-rows:0fr only pins the FIRST row,
   so every multi-paragraph answer leaked paragraphs two and three while
   closed. Found by looking at the rendered page, live on /faq for days. */
.q-b{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--med) var(--ease)}
.q.open .q-b{grid-template-rows:1fr}
.q-b .q-in{overflow:hidden;min-height:0}
.q-b p{max-width:64ch;color:var(--ink-2);font-size:var(--t0)}
.q-b p+p{margin-top:var(--s1)}
.q.open .q-b .q-in{padding-bottom:var(--s3)}

/* ---------- cta ---------- */
.cta{position:relative;overflow:hidden;background:var(--elev);border:1px solid var(--line);color:var(--ink);border-radius:var(--r2);
  padding:clamp(40px,7vw,96px) clamp(24px,5vw,64px)}
.cta::after{content:"";position:absolute;width:620px;height:620px;right:-160px;bottom:-300px;border-radius:50%;
  background:radial-gradient(circle,rgba(255,46,151,.16),transparent 64%);filter:blur(26px);pointer-events:none}
.cta>*{position:relative}
.cta h2{font-size:clamp(var(--t3),5.4vw,var(--t6));max-width:16ch;line-height:.98;letter-spacing:-.02em}
.cta h2 .accent{color:var(--accent)}
.cta p{margin-top:var(--s3);max-width:48ch;color:var(--ink-2)}
.cta .btn{margin-top:var(--s4)}

/* ---------- footer ---------- */
.foot{padding-top:clamp(64px,8vh,96px);padding-bottom:var(--s4)}
.foot-top{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:var(--s4);padding-bottom:clamp(24px,5vh,64px);border-bottom:1px solid var(--line)}
.foot-brand{display:flex;align-items:center;gap:var(--s1);font-family:var(--display);font-size:var(--t2);font-weight:700;letter-spacing:-.02em}
.foot-brand svg{width:30px;height:30px}
.foot-cols{display:flex;gap:clamp(40px,5vw,96px);flex-wrap:wrap}
.foot-col .fh{font-family:var(--label);font-size:var(--t-1);text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:var(--s2);font-weight:600}
.foot-col a{display:block;padding:4px 0;color:var(--ink-2);transition:color var(--fast) var(--ease)}
.foot-col a:hover{color:var(--accent)}
.foot-bot{display:flex;justify-content:space-between;flex-wrap:wrap;gap:var(--s2);margin-top:var(--s4);
  font-family:var(--label);font-size:var(--t-1);color:var(--muted);letter-spacing:.01em}
/* Terms and privacy live here and nowhere else. Ash put them in the bottom bar
   on purpose: they are for the person who goes looking, not for the person
   being sold to, so they stay out of the nav, the hero and every CTA. */
.foot-legal{display:flex;gap:var(--s2)}
.foot-legal a{color:var(--muted);transition:color var(--fast) var(--ease)}
.foot-legal a:hover{color:var(--accent)}

/* ---------- form ---------- */
.form{display:grid;grid-template-columns:1fr 1fr;gap:var(--s2);margin-top:var(--s1)}
.form .full{grid-column:1/-1}
.field label{display:block;font-family:var(--label);font-size:var(--t-1);font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:var(--s1)}
.field input,.field textarea,.field select{width:100%;background:var(--surface);border:1.5px solid var(--line-2);border-radius:var(--r2);
  padding:var(--s2) var(--s2);font-family:var(--sans);font-size:var(--t0);color:var(--ink);transition:border-color var(--fast) var(--ease),box-shadow var(--fast) var(--ease)}
.field input::placeholder,.field textarea::placeholder{color:var(--muted)}
.field input:focus,.field textarea:focus,.field select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.field textarea{min-height:152px;resize:vertical}
/* appearance:none stripped the native arrow and left the selects reading as
   text inputs: no hint at all that they open. The chevron comes back drawn
   in-system, muted grey, 12px, clear of the text. */
.field select{appearance:none;-webkit-appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.5 1.75L6 6.25l4.5-4.5' stroke='%237E828F' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right var(--s2) center;padding-right:var(--s4)}
/* error state, only after the visitor has touched the field: the one signal
   colour the site owns, border only, so focus (border + soft ring) still
   reads as its own state */
.field input:user-invalid,.field textarea:user-invalid,.field select:user-invalid{border-color:var(--accent)}
.hp{position:absolute!important;left:-9999px!important;width:1px;height:1px;opacity:0;overflow:hidden;pointer-events:none}
@media(max-width:640px){.form{grid-template-columns:1fr}}

/* ---------- shared micro-objects ---------- */
.em{color:var(--ink);font-weight:600}
/* Hero scroll cue, this was the broken one.
   It was written as `.scue{position:absolute}` (specificity 0,1,0), but
   `.hero>*:not(.hero-bg){position:relative;z-index:2}` (0,2,0) sits above it
   and won, so the cue never left the flow. Three things followed:
     - `left:50%` and `translateX(-50%)` cancelled out on a full-width block,
       so it sat in the left gutter instead of centred;
     - `bottom:14px` became a 14px nudge instead of a bottom pin;
     - it occupied 56px of real layout height under the buttons.
   And the 56px hairline was a flex sibling of the word in a ROW, so a tall
   vertical line stood beside 8px caps instead of falling below them.
   Fixed by raising the selector to `.hero .scue` (0,2,0, and later in the
   file, so it wins the tie) and stacking the column the way a scroll cue is
   actually built: word on top, line falling out of it.

   Pinning it to the bottom of the hero was tried and rejected: this hero is
   867px tall in a 900px window, so a bottom-pinned cue lands level with the
   buttons and reads as a stray label beside them. It belongs in the flow, on
   the same left edge as the kicker, the H1, the paragraph and the buttons , 
   one content column, one alignment, all five measured at the same x. */
.hero .scue{position:static;margin-top:clamp(24px,3.6vh,40px);
  display:inline-flex;flex-direction:column;align-items:flex-start;gap:var(--s1);
  font-family:var(--label);font-size:var(--t-2);letter-spacing:.22em;text-transform:uppercase;color:var(--muted)}
.scue::after{content:"";width:1.5px;height:56px;background:linear-gradient(180deg,var(--line-2),transparent);
  animation:scue 2.2s var(--ease) infinite}
@keyframes scue{0%{transform:translateY(-8px);opacity:0}25%{opacity:1}100%{transform:translateY(14px);opacity:0}}
@media(prefers-reduced-motion:reduce){.scue::after{animation:none;opacity:.7}}
@media(max-width:640px){.hero .scue{display:none}}
/* The "Moxireel build log" strip and its ticking clock are deleted, along with
   the pink dot that led it. See app.js. */

/* sticky pill sub-nav (long pages) */
.subnav{position:fixed;left:50%;bottom:16px;transform:translate(-50%,72px);z-index:60;display:flex;gap:2px;
  background:rgba(16,16,21,.88);backdrop-filter:blur(14px);border:1px solid var(--line-2);border-radius:var(--r3);padding:4px;
  box-shadow:0 16px 40px -12px rgba(0,0,0,.7);transition:transform var(--med) var(--ease),opacity var(--med) var(--ease);opacity:0;pointer-events:none}
.subnav.on{transform:translate(-50%,0);opacity:1;pointer-events:auto}
.subnav a{font-family:var(--label);font-size:var(--t-1);font-weight:600;letter-spacing:.08em;text-transform:uppercase;min-height:44px;display:inline-flex;align-items:center;
  color:var(--ink-2);padding:var(--s1) var(--s2);border-radius:var(--r3);transition:color var(--fast) var(--ease),background var(--fast) var(--ease);white-space:nowrap}
.subnav a:hover{color:var(--ink)}
.subnav a.cur{background:var(--ink);color:var(--dark-text)}
@media(max-width:640px){.subnav a{padding:var(--s1) var(--s1);font-size:var(--t-2)}
  /* the chat launcher lives in the bottom right corner from 640px down and
     overlapped this pill, swallowing the last link. Sit above it. */
  .subnav{bottom:80px}}

/* mobile: work cards become a snap rail with edge peek */
@media(max-width:640px){
  .work-grid{display:grid;grid-auto-flow:column;grid-auto-columns:84%;grid-template-columns:none;
    overflow-x:auto;scroll-snap-type:x mandatory;padding:var(--s2) 4px var(--s1);scrollbar-width:none;margin:0 calc(-1*var(--gutter));
    padding-left:var(--gutter);padding-right:var(--gutter)}
  .work-grid::-webkit-scrollbar{display:none}
  .work-grid .work-card{scroll-snap-align:center}
}

/* ---------- round 5: choreography + rhythm ---------- */
/* cross-document page transitions (progressive, Chromium): short, clean, directional */
@view-transition{navigation:auto}
@media(prefers-reduced-motion:no-preference){
  ::view-transition-old(root){animation:vtOut var(--fast) var(--ease) both}
  ::view-transition-new(root){animation:vtIn var(--med) var(--ease) both}
}
@media(prefers-reduced-motion:reduce){
  ::view-transition-old(root),::view-transition-new(root){animation-duration:.01ms}
}
@keyframes vtOut{to{opacity:0;transform:translateY(-6px)}}
@keyframes vtIn{from{opacity:0;transform:translateY(9px)}}

/* (The hero accent word is fully defined in the neon tube block below; the
   early transparent-fill draft that used to sit here was shadowed by it.) */

/* (The four stat tiles used to stagger from here on transition-delay, keyed off
   an .in class on their container. They carry their own data-reveal now and are
   staggered by the same GSAP batch as everything else, so there is one reveal
   system rather than two.) */

/* paper chapter: the one light band per page. Greyscale rhythm: value contrast
   does the work here, so the band carries almost no pink at all. */
.sec--paper{position:relative;color:var(--paper-ink);padding-top:clamp(64px,10vh,96px);padding-bottom:clamp(64px,10vh,96px)}
.sec--paper::before{content:"";position:absolute;inset:0;left:50%;width:100vw;transform:translateX(-50%);background:var(--paper)}
.sec--paper>*{position:relative}
.sec--paper .sec-title,.sec--paper h3{color:#0A0A0C}
.sec--paper .step p{color:var(--paper-muted)}
.sec--paper .steps{border-color:rgba(10,10,12,.16);background:rgba(255,255,255,.55)}
.sec--paper .step{border-color:rgba(10,10,12,.12)}
.sec--paper .step:hover{background:rgba(10,10,12,.03)}
.sec--paper .step-n{color:#6E707B}
.sec--paper .sec-title .accent{color:var(--accent-ink)}
.sec--paper .lead{color:var(--paper-muted)}
/* paper variants for the faq list and feature list */
.sec--paper .faq{border-top-color:var(--paper-line)}
.sec--paper .q{border-bottom-color:var(--paper-line)}
.sec--paper .q-b p{color:var(--paper-muted)}
.sec--paper .q-ic::before,.sec--paper .q-ic::after{background:var(--paper-ink)}
.sec--paper .q-h:hover h3{color:var(--accent-ink)}
.sec--paper .feats{border-top-color:var(--paper-line)}
.sec--paper .feat{border-bottom-color:var(--paper-line)}
.sec--paper .feat p{color:var(--paper-muted)}
/* the diff card keeps its dark surface on paper: it IS the band's peak */
.sec--paper .diff{box-shadow:0 24px 54px -28px rgba(10,10,12,.45)}

/* ---------- focus ---------- */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r1)}
.btn:focus-visible{outline-offset:3px}

/* ---------- edge light ----------
   Dark panels meeting a darker canvas read flat when all four hairlines match.
   A one-step-brighter top border is the cheapest possible key light: the eye
   reads it as an edge catching the room, not as a style. Pink-bordered panels
   (featured plan, diff, pink band) keep their own edges. */
.plan,.work-card,.stats,.cta,.band:not(.band--pink){border-top-color:var(--line-2)}

/* ---------- mobile ergonomics (S25 ~360-412px) ---------- */
@media(max-width:640px){
  /* This rule was the full-width bars, then it was two auto-width bars of
     deliberately different widths. Both were solving the wrong problem. Two
     buttons of ANY width side by side is the thing that reads as a framework
     default, because the visitor has to choose before they have read anything.
     There is one button here now and a link beside it, so the column has a
     first thing and a second thing instead of a pair. */
  .hero-cta{flex-direction:column;align-items:flex-start;gap:var(--s2)}
  .hero-cta .btn,.cta .btn,.band .btn{width:auto;justify-content:flex-start}
  /* the link sits directly under the button on its own line, indented to the
     button's own left edge so the column has one spine */
  .hero-cta .lnk{margin-left:2px}
  /* line-height 1.7 lifts each link row to 44px of thumb, from 43 */
  .foot-col a{padding:var(--s1) 0;line-height:1.7}
  /* the legal pair sits in the bottom bar, which runs at --t-1 rather than
     body size, so 1.7 only reaches 39px there. 2 lands it on 43, the same
     thumb target the column links above already get. Measured, not guessed. */
  .foot-legal a{padding:var(--s1) 0;line-height:2}
  .foot-bot{margin-top:var(--s3)}
}

/* ---------- differentiator callout: designed per client, no templates ----------
   The one loud "this is why I am different" panel: pink-edged elevated card
   with a corner glow, a live status chip, and a display-scale statement. */
.diff{position:relative;overflow:hidden;background:var(--surface);
  border:1px solid rgba(255,46,151,.38);border-radius:var(--r2);box-shadow:var(--glow);
  padding:clamp(24px,4.2vw,64px) clamp(24px,4vw,64px);margin-bottom:clamp(24px,5vh,64px)}
.diff::after{content:"";position:absolute;width:440px;height:440px;right:-150px;top:-190px;border-radius:50%;
  background:radial-gradient(circle,rgba(255,46,151,.22),transparent 66%);filter:blur(30px);pointer-events:none}
.diff>*{position:relative}
/* .diff-tag is deleted outright: the label was purged from the markup ages
   ago (it was the kicker disease living on a different class) and only its
   orphaned styling was left here. The separator on .proofline-meta stays:
   that one is a divider between two facts, not decoration before a label. */
.diff h3{font-family:var(--display);font-size:clamp(var(--t2),3.4vw,var(--t4));font-weight:600;letter-spacing:-.018em;
  line-height:1.05;max-width:22ch;color:var(--ink)}
.diff h3 .accent{color:var(--accent)}
.diff p{margin-top:var(--s2);max-width:58ch;color:var(--ink-2);font-size:clamp(var(--t0),1.4vw,var(--t1));line-height:1.56}



/* ==========================================================================
   THE PRESS  ::  touch feedback, Moxireel Studio
   ==========================================================================
   Paired with haptics.js. That file vibrates on Android and finds nothing to
   vibrate with on iOS, because Apple has never shipped a web haptics API and
   there is no way around that. So on an iPhone this block is the whole of the
   tactile feedback, and it is written to carry that weight on its own.

   Same selector list as haptics.js, deliberately: nothing on either site
   buzzes without visibly pressing, and nothing visibly presses without
   buzzing where buzzing is possible.

   The press is a 2.8% scale down with the lift taken away, the control goes
   into the surface rather than lighting up. It lands with a 0ms transition and
   releases over 140ms, because a press that eases in feels late and a release
   that snaps feels cheap.

   Under prefers-reduced-motion the scale is dropped and only the colour shift
   survives, so the feedback is still there for anyone who asked for stillness.
   -------------------------------------------------------------------------- */
/* kills the 300ms tap delay that made every one of these feel late */
a,button,summary,label,[role="button"],input,select,textarea{touch-action:manipulation}
@media (hover:none){
  a,button,summary,label,[role="button"]{-webkit-tap-highlight-color:transparent}
}
/* The 140ms release clock is TOUCH ONLY. Unscoped, this rule was overriding
   every authored hover transition on the site: card lifts, button glides and
   plan hovers all snapped at 140ms instead of gliding at their written 300ms,
   because this block sat later in the file with equal specificity. On a
   pointer device the elements keep their own transitions; on touch, where
   hover does not exist, the press release takes over. One curve everywhere. */
@media (hover:none){
  .btn,.nav-cta,.copychip,
  .work-card,.plan,.step,
  .stat,.pill,.q-h,
  .burger,.logo,a[href],
  button,summary,[role="button"]{
  transition-property:transform,opacity,color,background-color,border-color,box-shadow;
  transition-duration:140ms;
  transition-timing-function:var(--ease);
}
}
  .btn,.nav-cta,.copychip,
  .work-card,.plan,.step,
  .stat,.pill,.q-h,
  .burger,.logo,a[href],
  button,summary,[role="button"]{
  -webkit-user-select:none;user-select:none;
}
@media (prefers-reduced-motion:no-preference){
    .btn:active,.nav-cta:active,.copychip:active,
    .plan:active,.step:active,.stat:active,
    .pill:active,.q-h:active,.burger:active,
    .logo:active,a[href]:active,button:active,
    summary:active,[role="button"]:active{
    transform:scale(.972);
  }
    .work-card:active,.plan:active,.pour:active,.gcard:active,.house:active{
    transform:scale(.994);
  }
}
  .btn:active,.nav-cta:active,.copychip:active,
  .work-card:active,.plan:active,.step:active,
  .stat:active,.pill:active,.q-h:active,
  .burger:active,.logo:active,a[href]:active,
  button:active,summary:active,[role="button"]:active{
  color:#FF77BE;opacity:1;
}
.btn:active,.q-h:active,.menu-btn:active,.menu-toggle:active,
.burger:active,.copychip:active{
  background-color:rgba(255,46,151,.10);box-shadow:none;
}
/* the press itself is instant; only the release is timed */
  .btn:active,.nav-cta:active,.copychip:active,
  .work-card:active,.plan:active,.step:active,
  .stat:active,.pill:active,.q-h:active,
  .burger:active,.logo:active,a[href]:active,
  button:active,summary:active,[role="button"]:active{transition-duration:0ms}

/* ==========================================================================
   THE HERO ARTWORK.  ->  .hero-ink   (drawn by _ink/make-ink.py)

   WHAT WAS HERE BEFORE, AND WHY IT IS GONE.
   Two things were, and both drew the same subject: a page assembling itself.
   A stack of divs with border-radius 14px, rotated in 3D at low opacity, and
   a 46KB WebGL2 renderer that replaced them wherever WebGL2 was available.

   Ash looked at the hero on his phone and described the background as
   "faint rounded rectangles floating at an angle on near-black... divs with
   border-radius and low opacity, rotated." That description is exact, and it
   is a description of the FALLBACK, not of the renderer, which tells you the
   renderer was not running on the device he judges the site on. So the site
   was carrying 46KB of JavaScript that he never saw, in front of a fallback
   that gave itself away on sight.

   His diagnosis is the important part and it applies to both of them: work
   generated by code reads as machine-made because it is perfectly regular.
   Every plane had the same radius, the same border, the same opacity ramp.
   Every line the renderer drew was mathematically straight. Regularity is
   the tell, and no amount of extra detail fixes it, because more regular
   detail is more evidence.

   So the subject stays and the making changes completely. The artwork is now
   a drawing: an ink pen simulated stroke by stroke with pressure, tremor,
   ink depletion, pooling at the corners, wicking into paper fibre and colour
   that drifts along the length of a line. No two marks in it are identical
   and nothing in it has a radius. It ships as one 17KB image, so it renders
   the same on every device instead of being one thing on a desktop GPU and
   another thing on his phone.

   mix-blend-mode:screen is not a look, it is what lets the file be opaque.
   Line art with an alpha channel cost three times the bytes; composited onto
   the page's own near-black and screened back over it, the ground drops out
   and only the ink adds. Same result, a third of the weight.
   ========================================================================== */
.hero-ink{
  position:absolute;top:44%;right:0;width:min(34vw,400px);height:auto;
  transform:translateY(-46%);
  z-index:1;pointer-events:none;user-select:none;
  opacity:.9;mix-blend-mode:screen;
  /* The sheet is anchored at its top right, where nothing else on the page is,
     and dissolves toward the bottom left, which is the direction the headline
     and the copy column come from. So the drawing never has an edge running
     alongside the type: it is already gone by the time it gets there. */
  -webkit-mask-image:radial-gradient(125% 125% at 82% 20%,#000 32%,rgba(0,0,0,.52) 66%,transparent 94%);
  mask-image:radial-gradient(125% 125% at 82% 20%,#000 32%,rgba(0,0,0,.52) 66%,transparent 94%)}
@media (max-width:900px){
  /* The old object earned this placement and the reasoning still holds: the
     phone gets it in the dead band above the headline, clear of every word and
     every tap target, never sitting behind the buttons. It fades down-left on
     a straight line here rather than radially, because on a narrow column the
     only direction it can encroach is down onto the first line of the
     headline. */
  .hero-ink{top:-6%;right:-16%;width:min(64vw,300px);transform:none;opacity:.5;
    -webkit-mask-image:linear-gradient(203deg,#000 32%,rgba(0,0,0,.4) 62%,transparent 84%);
    mask-image:linear-gradient(203deg,#000 32%,rgba(0,0,0,.4) 62%,transparent 84%)}
}
@media (max-width:400px){
  .hero-ink{width:min(70vw,268px);right:-22%;opacity:.44}
}

/* ==========================================================================
   "FIRST" IS NOT A NEON TUBE ANY MORE.  ->  .hero h1 .accent

   Ash flagged this word three separate times. Once is taste, three times
   is a verdict, and the rule is that a device which has to be defended more
   than twice is not working whatever its provenance.

   He is also right about why, and it is not that the glow needed tuning.
   Neon in CSS cannot be neon. A real tube has a glass body you can see, tube
   brightness that varies along its length, colour that bleeds onto the wall
   behind it and a shadow it casts. text-stroke plus text-shadow gives you an
   outline of exactly one weight and a symmetrical blur, and the eye reads
   that as an effect rather than as an object every time. Another pass at the
   glow would have been a fourth flag.

   So the neon idea is gone, not improved: no stroke, no bloom, no shadow, no
   warm-up flicker, no idle stutter, and the five flicker variants and the
   script that picked between them are deleted with it.

   The emphasis is typographic now, which is what the headline wanted anyway.
   Bricolage Grotesque carries a real weight axis, 200 to 800. The headline
   runs at 600 and this one word runs at 800 in the accent colour, tracked a
   step tighter because heavier type needs less air. One word, two voices,
   nothing pretending to be a light fitting.
   ========================================================================== */
.hero h1 .accent{
  color:var(--accent);
  font-weight:800;
  letter-spacing:-.038em;
  /* the weight jump alone can look like a mistake at small sizes, so the
     word also sits a hair tighter against its neighbours */
  margin-inline:-.01em}

/* ==========================================================================
   THE REVEAL LAW. One entrance for the whole site.

   600ms on the house curve, 12px of travel, nothing else. An earlier pass
   pushed this to 850ms and 30px chasing "expensive", which put scroll
   reveals on a different clock from the hero (600ms) and broke the three-
   duration budget; the slow-start of the house curve supplies the
   deliberateness now, so the numbers come back inside the law. Section
   heads keep their few degrees of rotation, the same perspective language
   as the hero object, so the page has one idea about depth. Only transform
   and opacity, nothing the compositor cannot do on its own. */
/* ==========================================================================
   THE REVEAL PRE-STATE, AND ONLY THE PRE-STATE.

   The animation itself lives in motion.js now (GSAP + ScrollTrigger), so what
   is left here is two things and nothing else:

     1. the starting condition, so nothing flashes into place before the first
        frame runs;
     2. a .in class that no longer has anything to do with normal operation.
        It is the failsafe path. app.js adds it after 1600ms if motion.js never
        reported in, which is what happens if a vendor file fails to load, and
        the transition on it is what makes that recovery a fade rather than a
        pop. GSAP never adds this class, so the two can never fight.

   The old clip-path hero entrance is deleted rather than left to shadow this.
   It rose each hero line out of an inset mask, which was written when the
   headline had a neon stroke that needed room to not clip. The neon is gone,
   the stroke is gone, and a second entrance animation on the same elements
   would have fought the timeline for control of transform. */
html.js [data-reveal]{opacity:0;transform:translateY(14px)}
html.js [data-reveal].in{opacity:1;transform:none;
  transition:opacity var(--slow) var(--ease),transform var(--slow) var(--ease)}
.sec-head{perspective:1100px}

@media(prefers-reduced-motion:reduce){
  html.js [data-reveal]{opacity:1!important;transform:none!important;transition:none!important}
}

/* ==========================================================================
   THE BACKGROUND BREATHES.  ->  hero aurora upgrade

   The two glows already floated 16px, which reads as a screensaver: same
   spot, same size, forever. An aurora is different because it TRAVELS. The
   light now wanders a long slow circuit around its corner, swells and
   thins, and leans from pink toward violet and back over about a minute,
   so no two glances at the page catch the same sky. A third, deeper light
   sits low-left so the dark has more than one source, which is the actual
   difference between "black page" and "night".

   Still nothing but transform, opacity and a compositor filter on three
   blurred circles. No canvas, no library, no new bytes to download, and
   the whole sky stops under reduced motion.
   ========================================================================== */
@keyframes auroraA{
  0%   {transform:translate3d(0,0,0) scale(1) rotate(0deg)}
  25%  {transform:translate3d(-7vw,4vh,0) scale(1.18) rotate(10deg)}
  50%  {transform:translate3d(-3vw,9vh,0) scale(.92) rotate(-6deg)}
  75%  {transform:translate3d(5vw,3vh,0) scale(1.10) rotate(5deg)}
  100% {transform:translate3d(0,0,0) scale(1) rotate(0deg)}
}
@keyframes auroraB{
  0%   {transform:translate3d(0,0,0) scale(1)}
  33%  {transform:translate3d(6vw,-6vh,0) scale(1.22)}
  66%  {transform:translate3d(-4vw,-2vh,0) scale(.9)}
  100% {transform:translate3d(0,0,0) scale(1)}
}
/* (auroraHue is deleted, not just unused: animating blur+hue re-rasterised
   three large layers every frame and measured at 26fps headless. Movement
   alone reads as weather; the violet third light supplies the colour.) */
.hero-glow{animation:auroraA 38s ease-in-out infinite}
.hero-glow.g2{animation:auroraB 47s ease-in-out infinite reverse}
/* the third light: deep violet, low and far, barely there until the pink
   drifts away and your eye finds it */
.hero-glow.g3{width:min(52vw,600px);height:min(52vw,600px);top:30%;left:-14%;right:auto;bottom:auto;
  background:radial-gradient(circle,rgba(140,120,255,.07),rgba(140,120,255,0) 66%);
  animation:auroraB 71s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){
  .hero-glow,.hero-glow.g2,.hero-glow.g3{animation:none}
}


/* the sky sleeps with the scene: same observer, same reason, frames back
   the moment the hero leaves the screen */
.hero-bg.is-paused .hero-glow{animation-play-state:paused}



/* The Labs band markup came out of the home page on Ash's order the night
   it went in (a glowing pill plus a wall of self-description is the opposite
   of the directive), and its forty lines of styling, raw px values and all,
   are deleted with it. The lab's door is the line inside the services list;
   the lab itself styles its own room. */

/* ==========================================================================
   THE DIVIDER.  ->  .rule-ink

   He asked for the long horizontal line to stop being a long horizontal
   line: "squiggly, artistic, if it's up to you it's up to you." So it is a
   decision, not a hedge.

   It is a pen stroke. The path below was plotted by hand, point by point, by
   eye: run lengths between 30 and 48 units, amplitudes between 0.9 and 3.1,
   and no repeating period anywhere in it. That last part is the whole point.
   A sine wave, or anything else generated from a formula, would wobble
   perfectly regularly, and perfect regularity is exactly what reads as
   machine-made. Irregularity that does not resolve into a pattern is what
   reads as a hand.

   Drawn twice, because a hand never retraces its own line: a white pass and
   a pink one that does not agree with it. Where they part company the mark
   thickens, which is stroke weight varying along a line without a texture
   being needed to fake it.

   non-scaling-stroke so that stretching the box across any width changes the
   length of the stroke and never its weight.
   ========================================================================== */
.rule-ink{position:relative;height:24px;margin:clamp(16px,3vh,28px) 0 0;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 24' preserveAspectRatio='none' fill='none'%3E%3Cpath d='M0,12.6C40,11.2 78,13.9 118,12.1C162,10.1 196,14.6 238,13.2C280,11.8 312,10.2 356,11.9C402,13.6 436,15.4 478,13.6C520,11.9 548,9.4 592,10.9C638,12.4 672,15.1 714,13.9C754,12.8 788,10.3 830,11.4C874,12.6 906,15.2 948,14.2C990,13.2 1022,10.7 1064,11.8C1104,12.9 1138,14.6 1174,13.0C1186,12.5 1194,12.9 1200,12.3' stroke='rgba(255,255,255,.30)' stroke-width='1.7' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3Cpath d='M0,13.3C44,12.2 74,14.4 120,12.9C168,11.3 198,15.2 240,14.0C284,12.7 316,11.1 358,12.7C404,14.4 434,16.0 476,14.3C518,12.7 552,10.4 594,11.7C642,13.1 670,15.6 716,14.6C756,13.7 786,11.2 832,12.2C876,13.2 904,15.7 950,15.0C992,14.3 1020,11.8 1066,12.6C1108,13.4 1136,15.0 1176,13.8C1188,13.4 1195,13.6 1200,13.1' stroke='rgba(255,46,151,.42)' stroke-width='1.1' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-size:100% 100%;
  opacity:.85;pointer-events:none}
@media(max-width:640px){
  /* A 1200 unit path squeezed into 342px flattens the wobble almost out of
     existence. Showing a wider slice of the same stroke at a bigger scale
     keeps the amplitude a hand would have put there, and because the slice
     starts partway along, the phone sees a different stretch of the line
     than the laptop does. */
  .rule-ink{height:22px;opacity:.8;background-size:280% 100%;background-position:22% 50%}
}
