/* ===== novarus.css ===== */
/* ==========================================================================
   ASCENIX  ·  light / glass
   An AI company. White base, golden accent, liquid glass, generous whitespace.
   Everything that moves is transform or opacity only, so no animation ever
   costs a layout pass. Type is Inter: heavy and tight on headings, quiet on body.
   ========================================================================== */

/* ============================ TOKENS ============================ */
:root{
  --white:#FFFFFF;
  --canvas:#FAF9F6;   /* warm off-white, never pure #FFF */
  --surface:#FFFFFF;
  --surface-2:#F5F5F3;
  --surface-3:#EFEFEC;
  --ink:#16161A;
  --ink-2:#3A3A40;

  /* golden, not electric: warmer and quieter than a signal yellow */
  --gold:#111112;
  --gold-lift:#2A2A2E;
  --gold-deep:#111112;      /* the only one legible as text on white */
  --gold-wash:#F2F2F1;

  --muted:#65656E;
  --faint:#8E8E96;
  --line:rgba(22,22,26,.09);
  --line-2:rgba(22,22,26,.15);
  --ok:#1F9254; --warn:#111112; --bad:#C0392B;

  /* glass */
  --glass:rgba(255,255,255,.62);
  --glass-strong:rgba(255,255,255,.82);
  --glass-line:rgba(255,255,255,.75);
  --blur:saturate(180%) blur(22px);

  /* three light shadows read expensive; one heavy one reads cheap */
  --sh-1:0 1px 2px rgba(22,22,26,.05);
  --sh-2:0 1px 2px rgba(22,22,26,.04), 0 6px 16px -6px rgba(22,22,26,.08);
  --sh-3:0 1px 3px rgba(22,22,26,.04), 0 12px 32px -12px rgba(22,22,26,.12);
  --sh-4:0 2px 6px rgba(22,22,26,.05), 0 24px 60px -24px rgba(22,22,26,.18);
  --sh-gold:0 10px 30px -10px rgba(17,17,18,0.120);

  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:20px; --s6:24px;
  --s8:32px; --s10:40px; --s12:48px; --s16:64px; --s20:80px; --s24:96px;
  --band:clamp(64px,7.5vw,124px);

  --maxw:1240px; --maxw-wide:1400px; --pad-x:clamp(20px,5vw,64px);

  --f:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
  --f-mono:"JetBrains Mono",ui-monospace,"SF Mono",Consolas,monospace;

  --h1:clamp(2.6rem,6vw,5.4rem);
  --h2:clamp(2rem,4vw,3.4rem);
  --h3:clamp(1.4rem,2.2vw,2rem);
  --lh-tight:1.03; --lh-snug:1.14; --lh-body:1.6;
  --tr-tight:-.035em; --tr-x:-.045em;

  --r-s:12px; --r:18px; --r-l:26px; --r-xl:34px; --r-pill:999px;

  /* short, and the overshoot curve is what makes it feel physical */
  --t-fast:130ms; --t:200ms; --t-slow:340ms;
  --spring:cubic-bezier(.34,1.56,.64,1);
  --ease:cubic-bezier(.32,.72,0,1);

  --z-dock:90; --z-nav:100; --z-menu:900;
}

/* ============================ BASE ============================ */
*,*::before,*::after{ box-sizing:border-box; margin:0; padding:0; -webkit-tap-highlight-color:transparent; }
html{ -webkit-text-size-adjust:100%; scrollbar-width:none; -ms-overflow-style:none; }
*::-webkit-scrollbar{ width:0; height:0; }
body{
  background:var(--canvas); color:var(--ink);
  font-family:var(--f); font-size:17px; line-height:var(--lh-body); font-weight:400;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility; overflow-x:clip;
}
img,svg,video,canvas{ display:block; max-width:100%; }
a{ color:inherit; text-decoration:none; }
button{ font:inherit; color:inherit; background:none; border:0; cursor:pointer; }
ul{ list-style:none; }
h1,h2,h3,h4{ font-family:var(--f); font-weight:700; letter-spacing:var(--tr-tight); line-height:var(--lh-snug); text-wrap:balance; color:var(--ink); }
h1{ font-weight:800; letter-spacing:var(--tr-x); line-height:var(--lh-tight); }
p{ text-wrap:pretty; }
::selection{ background:var(--gold); color:var(--ink); }
:where(a,button,input,select,textarea,[tabindex]):focus-visible{
  outline:2px solid var(--gold-deep); outline-offset:3px; border-radius:8px;
}
.mono{ font-family:var(--f-mono); font-size:.72rem; font-weight:500; letter-spacing:.1em; text-transform:uppercase; }
.accent{ color:var(--gold-deep); }

.skip{
  position:fixed; top:-120px; left:var(--s4); z-index:10000;
  background:var(--ink); color:#fff; padding:var(--s3) var(--s5);
  font-size:.9rem; border-radius:var(--r-s); transition:top var(--t) var(--ease);
}
.skip:focus{ top:var(--s4); }

.container{ width:100%; max-width:var(--maxw); margin-inline:auto; padding-inline:var(--pad-x); }
.container--wide{ max-width:var(--maxw-wide); }
.band{ position:relative; padding-block:var(--band); background:var(--canvas); }
.band--light{ background:var(--white); }
.band > .container{ position:relative; z-index:1; }

.lede{ font-size:clamp(1.1rem,1.5vw,1.35rem); line-height:1.5; color:var(--muted); max-width:56ch; }
.grain{ display:none; }

/* ============================ ORGANIC BACKGROUND ============================
   Two warm fields and one cool one, drifting on long cycles. Low enough that
   you register movement without ever looking at it. Transform only. */
.amb{ position:absolute; inset:0; overflow:hidden; pointer-events:none; z-index:0; }
.amb i{
  position:absolute; display:block; border-radius:50%;
  filter:blur(70px); opacity:.55; will-change:transform;
  background:radial-gradient(circle at 50% 50%, rgba(17,17,18,0.105) 0%, rgba(17,17,18,0.000) 70%);
  animation:drift var(--d,52s) ease-in-out infinite alternate;
}
.amb i:nth-child(1){ width:52vw; height:52vw; left:-12%; top:-18%; --d:56s; }
.amb i:nth-child(2){
  width:42vw; height:42vw; right:-10%; top:22%; --d:44s; animation-direction:alternate-reverse;
  background:radial-gradient(circle at 50% 50%, rgba(255,206,78,.26) 0%, rgba(255,206,78,0) 70%);
}
.amb i:nth-child(3){
  width:36vw; height:36vw; left:40%; bottom:-16%; --d:64s;
  background:radial-gradient(circle at 50% 50%, rgba(176,198,255,.22) 0%, rgba(176,198,255,0) 68%);
}
@keyframes drift{
  from{ transform:translate3d(0,0,0) scale(1); }
  to{ transform:translate3d(6vw,-5vh,0) scale(1.18); }
}
.amb::after{ content:none; }

/* ============================ BUTTONS ============================ */
.btn{
  position:relative; display:inline-flex; align-items:center; justify-content:center; gap:10px;
  padding:15px 26px; border-radius:var(--r-pill);
  background:var(--ink); color:#fff;
  font-size:1rem; font-weight:600; letter-spacing:-.01em; white-space:nowrap;
  box-shadow:var(--sh-2);
  transition:transform var(--t) var(--spring), box-shadow var(--t) var(--ease), background var(--t) var(--ease);
}
.btn:hover{ transform:translateY(-2px); box-shadow:var(--sh-3); }
.btn:active{ transform:translateY(0) scale(.98); transition-duration:80ms; }
.btn__arrow{ width:15px; height:15px; transition:transform var(--t) var(--spring); }
.btn:hover .btn__arrow{ transform:translateX(3px); }
.btn--gold{ background:var(--gold); color:var(--ink); box-shadow:var(--sh-gold); }
.btn--gold:hover{ background:var(--gold-lift); }
.btn--ghost{
  background:var(--glass-strong); color:var(--ink);
  border:1px solid var(--line-2); box-shadow:var(--sh-1);
  backdrop-filter:var(--blur); -webkit-backdrop-filter:var(--blur);
}
.btn--ghost:hover{ background:#fff; }

.link-mono{
  display:inline-flex; align-items:center; gap:7px;
  font-size:.9rem; font-weight:600; color:var(--ink);
  padding-bottom:2px; border-bottom:1.5px solid var(--gold);
  transition:color var(--t) var(--ease), border-color var(--t) var(--ease);
}
.link-mono:hover{ color:var(--gold-deep); border-color:var(--gold-deep); }
.led{
  width:7px; height:7px; border-radius:50%; background:var(--gold); flex:none;
  box-shadow:0 0 0 0 rgba(17,17,18,0.120); animation:pulse 2.6s var(--ease) infinite;
}
@keyframes pulse{
  0%{ box-shadow:0 0 0 0 rgba(17,17,18,0.120); }
  70%{ box-shadow:0 0 0 9px rgba(17,17,18,0.000); }
  100%{ box-shadow:0 0 0 0 rgba(17,17,18,0.000); }
}

/* ============================ HEADER ============================
   No wordmark. A compact glass monogram left, the phone right. That is all. */
.nav{
  position:fixed; top:0; left:0; right:0; z-index:var(--z-nav);
  display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  padding:18px var(--pad-x); pointer-events:none;
  transition:padding var(--t-slow) var(--ease);
}
.nav > *{ pointer-events:auto; }
.nav[data-stuck="true"]{ padding-block:11px; }
.nav__island{
  display:flex; align-items:center; border-radius:var(--r-pill);
  background:var(--glass); border:1px solid var(--glass-line);
  backdrop-filter:var(--blur); -webkit-backdrop-filter:var(--blur);
  box-shadow:var(--sh-2);
  transition:background var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease);
}
.nav[data-stuck="true"] .nav__island{ background:var(--glass-strong); box-shadow:var(--sh-3); }
.nav__brand{ padding:0; width:46px; height:46px; justify-content:center; }
.nav__brand svg{ width:24px; height:24px; }
.nav__pill{ display:none; }
.nav__side{ padding:5px 5px 5px 6px; gap:var(--s2); }
.nav__tel{
  display:inline-flex; align-items:center; gap:8px; padding:0 14px 0 12px;
  font-size:.95rem; font-weight:600; color:var(--ink); white-space:nowrap;
  font-family:var(--f); letter-spacing:0; text-transform:none;
  transition:color var(--t) var(--ease);
}
.nav__tel:hover{ color:var(--gold-deep); }
.nav .btn{ padding:11px 20px; font-size:.92rem; }

.burger{ display:none; width:42px; height:42px; place-items:center; border-radius:var(--r-pill); }
.burger i{ display:block; width:17px; height:1.8px; background:var(--ink); border-radius:2px;
  transition:transform 320ms var(--spring), opacity 160ms linear; }
.burger i + i{ margin-top:4px; }
.burger[aria-expanded="true"] i:nth-child(1){ transform:translateY(5.8px) rotate(45deg); }
.burger[aria-expanded="true"] i:nth-child(2){ opacity:0; }
.burger[aria-expanded="true"] i:nth-child(3){ transform:translateY(-5.8px) rotate(-45deg); }

.menu{
  position:fixed; inset:0; z-index:var(--z-menu);
  background:rgba(255,255,255,.88); backdrop-filter:var(--blur); -webkit-backdrop-filter:var(--blur);
  display:flex; flex-direction:column; justify-content:center; padding:96px var(--pad-x) var(--s12);
  opacity:0; visibility:hidden; transition:opacity 260ms var(--ease), visibility 260ms;
}
.menu[data-open="true"]{ opacity:1; visibility:visible; }
.menu ul{ display:grid; gap:var(--s2); }
.menu li a{
  display:flex; align-items:baseline; gap:var(--s5);
  font-size:clamp(2rem,8vw,2.8rem); font-weight:700; letter-spacing:var(--tr-x);
  padding-block:12px; border-bottom:1px solid var(--line); color:var(--ink);
}
.menu li a span{ font-family:var(--f-mono); font-size:.68rem; color:var(--gold-deep); letter-spacing:.1em; }
.menu__foot{ margin-top:var(--s12); display:grid; gap:var(--s3); color:var(--muted); }
.menu__foot a{ color:var(--ink); }

/* ============================ DOCK ============================
   macOS behaviour: the label pops ABOVE the icon rather than widening the
   pill. Nothing animates width, so every frame is composited and the bounce
   lands instantly. */
.dock{ display:none; }
@media (hover:hover) and (min-width:761px){
  .dock{
    position:fixed; left:50%; bottom:24px; z-index:var(--z-dock);
    display:flex; align-items:center; gap:6px; padding:9px;
    border-radius:var(--r-pill);
    background:var(--glass); border:1px solid var(--glass-line);
    backdrop-filter:var(--blur); -webkit-backdrop-filter:var(--blur);
    box-shadow:var(--sh-4); transform:translateX(-50%);
  }
  .dock a{
    position:relative; display:grid; place-items:center;
    width:46px; height:46px; border-radius:var(--r-pill); color:var(--ink-2);
    transition:transform 220ms var(--spring), background 150ms var(--ease), color 150ms var(--ease);
    will-change:transform;
  }
  .dock a svg{ width:20px; height:20px; }
  .dock a span{
    position:absolute; bottom:calc(100% + 12px); left:50%;
    padding:7px 13px; border-radius:var(--r-pill);
    background:var(--ink); color:#fff;
    font-size:.8rem; font-weight:600; letter-spacing:-.01em; white-space:nowrap;
    box-shadow:var(--sh-3);
    opacity:0; transform:translate(-50%,6px) scale(.9);
    transition:opacity 130ms var(--ease), transform 240ms var(--spring);
    pointer-events:none;
  }
  .dock a span::after{
    content:""; position:absolute; top:100%; left:50%; margin-left:-4px;
    border:4px solid transparent; border-top-color:var(--ink);
  }
  .dock a:hover, .dock a:focus-visible{
    transform:translateY(-8px) scale(1.16); background:var(--gold-wash); color:var(--ink);
  }
  .dock a:hover span, .dock a:focus-visible span{ opacity:1; transform:translate(-50%,0) scale(1); }
  .dock a:active{ transform:translateY(-3px) scale(1.05); transition-duration:90ms; }
  /* neighbours lift a little too, the way a real dock does */
  .dock a:hover + a{ transform:translateY(-3px) scale(1.07); }
  .dock a[aria-current="page"]{ color:var(--ink); background:var(--gold-wash); }
  .dock a[aria-current="page"]::after{
    content:""; position:absolute; bottom:5px; left:50%; width:4px; height:4px; margin-left:-2px;
    border-radius:50%; background:var(--gold-deep);
  }
  .foot{ padding-bottom:calc(var(--s16) + 56px); }
}

/* ============================ HERO ============================ */
.hero{
  position:relative; min-height:100svh; display:flex; align-items:center;
  padding:clamp(120px,15vh,168px) 0 clamp(56px,8vh,96px); overflow:hidden; background:var(--white);
}
.hero::before, .hero::after{ content:none; }
.hero__grid{
  position:relative; z-index:1; width:100%;
  display:grid; grid-template-columns:minmax(0,1fr) clamp(320px,32vw,440px);
  gap:clamp(32px,5vw,80px); align-items:center;
}
.hero h1{ font-size:var(--h1); max-width:15ch; margin-bottom:var(--s5); }
.hero__cta{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--s3); margin-bottom:var(--s8); }

/* ---- the proof phone, glass on white ---- */
/* column, not row: the caption belongs under the panel, not beside it */
.proof{ position:relative; width:min(340px,100%); }
.proof::before{
  content:""; position:absolute; inset:-12% -18%;
  background:radial-gradient(50% 50% at 50% 50%, rgba(17,17,18,0.105), transparent 70%); filter:blur(14px);
}
.proof [data-s]{ opacity:0; transform:translateY(8px); }
.rm .proof [data-s], .no-js .proof [data-s]{ opacity:1; transform:none; }
.rm .proof__scene:not(:first-child), .no-js .proof__scene:not(:first-child){ display:none; }
.proof__ticks{ display:flex; justify-content:center; gap:6px; padding-top:10px; }
.proof__ticks i{ width:16px; height:3px; border-radius:2px; background:var(--line-2); transition:background var(--t) var(--ease); }
.proof__ticks i[data-on="true"]{ background:var(--gold); }
.proof__tag--a{ top:-4%; right:-11%; }
.proof__tag--b{ bottom:-4%; left:-11%; background:var(--gold); border-color:transparent; }

/* ============================ SECTION HEAD ============================ */
.head{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--s6);
  flex-wrap:wrap; margin-bottom:clamp(28px,3.4vw,48px); border-top:0; padding-top:0; }
.head::before{ content:none; }
.cutline{ display:none; }

/* ============================ LAYER CARDS ============================ */
.layer{
  position:relative; display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:clamp(28px,3.4vw,56px); align-items:center;
  padding:clamp(26px,3vw,44px); border-radius:var(--r-xl);
  background:var(--white); border:1px solid var(--line); box-shadow:var(--sh-2);
  transition:transform var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease);
}
.layer:hover{ transform:translateY(-3px); box-shadow:var(--sh-3); }
.layer::before{ content:none; }
.layer:nth-child(even){ direction:rtl; }
.layer:nth-child(even) > *{ direction:ltr; }
.layer h3{ font-size:var(--h3); margin-bottom:var(--s4); max-width:20ch; }
.layer p{ color:var(--muted); max-width:52ch; margin-bottom:var(--s5); font-size:1.0625rem; }

/* ---- preview slab ---- */
.slab{ position:relative; perspective:1600px; min-height:0; display:grid; place-items:center; }
.slab__body{
  position:relative; width:100%; max-width:420px; aspect-ratio:16/11; border-radius:var(--r-l);
  background:var(--glass-strong); border:1px solid var(--glass-line);
  backdrop-filter:var(--blur); -webkit-backdrop-filter:var(--blur); box-shadow:var(--sh-4);
  transform:rotateY(var(--ry,-6deg)) rotateX(var(--rx,2deg));
  transition:transform 600ms var(--ease);
}
.slab__body::after{ content:none; }

/* ============================ MATH BAND ============================ */
.math{ position:relative; overflow:hidden; background:var(--white); }

/* ============================ STEPS ============================ */
.steps{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:var(--s5); align-items:start; }
.step{ position:relative; padding:var(--s6); border-radius:var(--r-l);
  background:var(--white); border:1px solid var(--line); box-shadow:var(--sh-1);
  transition:transform var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease); }
.step:hover{ transform:translateY(-4px); box-shadow:var(--sh-3); }
.step h3{ font-size:1.1rem; margin-bottom:var(--s2); }
.step p{ color:var(--muted); font-size:.95rem; }

/* ============================ PLATE / PRICES ============================ */

/* ============================ TIERS ============================ */
.tiers{ display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:var(--s5); align-items:start; }
.tier{ position:relative; display:flex; flex-direction:column; gap:var(--s4);
  padding:var(--s6); border-radius:var(--r-xl);
  background:var(--white); border:1px solid var(--line); box-shadow:var(--sh-2);
  transition:transform var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease); }
.tier:hover{ transform:translateY(-4px); box-shadow:var(--sh-3); }
.tier .btn{ width:100%; }

/* ============================ TABLES ============================ */

/* ============================ NOTES / LINKS / SEQ ============================ */
.note{ padding:var(--s6); border-radius:var(--r-l); background:var(--white); border:1px solid var(--line); box-shadow:var(--sh-1); }
.note::before{ content:none; }
.note h3{ font-size:1.05rem; margin-bottom:var(--s2); }
.note p{ color:var(--muted); font-size:.96rem; }

.links{ display:grid; grid-template-columns:repeat(auto-fit,minmax(270px,1fr)); gap:var(--s4); }

/* ============================ FAQ ============================ */
.qa__item{ border-bottom:1px solid var(--line); }
.qa__q{ width:100%; display:flex; align-items:flex-start; justify-content:space-between; gap:var(--s6);
  padding-block:22px; text-align:left; font-size:1.08rem; font-weight:600; letter-spacing:-.02em;
  transition:color var(--t) var(--ease); color:var(--ink); }
.qa__q:hover{ color:var(--gold-deep); }
.qa__item[data-open="true"] .qa__q{ color:var(--gold-deep); }
.qa__a{ overflow:hidden; height:0; transition:height 340ms var(--ease); }
.qa__a > div{ padding-bottom:22px; color:var(--muted); max-width:70ch; }

/* ============================ FORM ============================ */
.form{ display:grid; gap:var(--s4); }
.field{ display:grid; gap:7px; }
.field > span{ font-size:.86rem; font-weight:500; color:var(--ink-2); font-family:var(--f); letter-spacing:0; text-transform:none; }
.field input, .field select, .field textarea{
  width:100%; padding:14px 16px; border-radius:var(--r-s); font-size:16px;
  background:var(--surface-2); border:1px solid transparent; color:var(--ink); font-family:var(--f);
  transition:background var(--t) var(--ease), border-color var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.field textarea{ min-height:120px; resize:vertical; }
.field input:focus, .field select:focus, .field textarea:focus{
  background:var(--white); border-color:var(--gold); box-shadow:0 0 0 4px var(--gold-wash); outline:none;
}
.field input::placeholder, .field textarea::placeholder{ color:var(--faint); }

/* ============================ CLOSE ============================ */
.close{ position:relative; overflow:hidden; background:var(--white); padding-block:clamp(72px,9vw,140px); }
.close::before{ content:""; position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(52% 60% at 50% 112%, rgba(17,17,18,0.105), transparent 70%); }
.close__inner{ position:relative; z-index:1; display:grid;
  grid-template-columns:minmax(0,1.3fr) minmax(0,.7fr); gap:clamp(28px,4vw,72px); align-items:end; }
.close h2{ font-size:clamp(2rem,4.6vw,3.8rem); max-width:14ch; margin-top:var(--s3); }
.close__side{ display:grid; gap:var(--s5); justify-items:start; }
.close p{ color:var(--muted); max-width:44ch; }
.close__cta{ display:grid; gap:var(--s3); width:min(100%,300px); }
.close .mono{ color:var(--gold-deep); }

/* ============================ FOOTER ============================ */
.foot{ background:var(--surface-2); color:var(--ink); padding-block:clamp(44px,5vw,68px) var(--s6); border-top:1px solid var(--line); }
.foot__grid{ display:grid; grid-template-columns:minmax(150px,.9fr) minmax(0,2fr) minmax(0,.9fr) minmax(0,1.05fr);
  gap:clamp(24px,3vw,48px); padding-bottom:var(--s8); border-bottom:1px solid var(--line); }
.foot__brand{ display:flex; align-items:center; align-self:start; }
.foot__brand svg{ width:34px; height:34px; }
.foot__col h2, .foot__col h4{ font-size:.82rem; font-weight:600; color:var(--faint); margin-bottom:var(--s4);
  font-family:var(--f); letter-spacing:0; text-transform:none; }
.foot__col ul{ display:grid; gap:10px; }
.foot__col--wide ul{ grid-template-columns:repeat(2,minmax(0,1fr)); column-gap:var(--s5); }
.foot__col a, .foot__col li{ color:var(--ink-2); font-size:.96rem; transition:color var(--t) var(--ease); }
.foot__col a:hover{ color:var(--gold-deep); }
.foot__base{ display:flex; justify-content:space-between; align-items:center; gap:var(--s5); flex-wrap:wrap;
  padding-top:var(--s5); color:var(--faint); }

/* ============================ HIDDEN LEGACY ============================ */
.rail{ display:none; }
.cursor{ display:none; }
.pre{ display:none; }

/* ============================ CALL BAR (phone) ============================ */
.callbar{ display:none; }
@media (max-width:760px){
  .callbar{
    position:fixed; left:0; right:0; bottom:0; z-index:80;
    display:grid; grid-template-columns:1fr 1fr; gap:10px;
    padding:12px 16px calc(12px + env(safe-area-inset-bottom));
    background:var(--glass-strong); backdrop-filter:var(--blur); -webkit-backdrop-filter:var(--blur);
    border-top:1px solid var(--line);
  }
  .callbar__btn{ display:flex; align-items:center; justify-content:center; gap:8px; min-height:52px;
    border-radius:var(--r-pill); font-size:1rem; font-weight:600; }
  .callbar__btn svg{ width:17px; height:17px; }
  .callbar__btn--call{ background:var(--gold); color:var(--ink); }
  .callbar__btn--cta{ background:var(--ink); color:#fff; }
  body{ padding-bottom:84px; }
}

/* ============================ REVEALS ============================ */
.js [data-rv]{ opacity:0; transform:translateY(18px);
  transition:opacity 700ms var(--ease), transform 700ms var(--ease);
  transition-delay:calc(var(--d,0) * 70ms); }
.js [data-rv].is-in{ opacity:1; transform:none; }

/* ============================ RESPONSIVE ============================ */
@media (max-width:1080px){
  .hero__grid{ grid-template-columns:1fr; gap:var(--s10); }
  .layer{ grid-template-columns:1fr; }
  .layer:nth-child(even){ direction:ltr; }
  .steps{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .close__inner{ grid-template-columns:1fr; align-items:start; }
}
@media (max-width:900px){
  .foot__grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .foot__brand, .foot__col--wide{ grid-column:1 / -1; }
}
@media (max-width:760px){
  :root{ --band:clamp(48px,8vw,72px); }
  .nav{ padding:12px var(--pad-x); }
  .nav .btn{ display:none; }
  .nav__tel{ display:none; }
  .burger{ display:grid; }
  .nav__brand{ width:42px; height:42px; }
  .nav__side{ padding:0; }
  .hero{ min-height:auto; padding-top:104px; }
  .hero h1{ max-width:100%; }
  .hero__cta{ width:100%; }
  .hero__cta .btn{ flex:1; }
  .steps{ grid-template-columns:1fr; }
  .layer{ padding:var(--s5); border-radius:var(--r-l); }
  .slab__body{ max-width:300px; aspect-ratio:auto; }
  .proof{ width:min(310px,88%); }
  .proof__tag--a{ right:-2%; top:-3%; }
  .proof__tag--b{ left:-2%; bottom:-3%; }
  .foot__col a, .foot__col li{ display:flex; align-items:center; min-height:44px; }
  .foot__col ul{ gap:0; }
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.01ms !important; transition-duration:.01ms !important; }
  .amb i{ animation:none; }
  .js [data-rv]{ opacity:1 !important; transform:none !important; }
}

/* ==========================================================================
   PAGE HEADER (inner pages)
   This was the break: .phero had no rules at all, so every inner page put its
   h1 at 18px from the top, underneath an 82px fixed nav, at full container
   width running under the phone pill.
   ========================================================================== */
.phero{
  position:relative; overflow:hidden; background:var(--white);
  padding:clamp(132px,17vh,180px) 0 clamp(48px,6vw,84px);
  border-bottom:1px solid var(--line);
}
.phero__inner{ position:relative; z-index:1; }
.phero__eyebrow{
  display:inline-flex; align-items:center; gap:9px; margin-bottom:var(--s5);
  padding:8px 16px; border-radius:var(--r-pill);
  background:var(--surface-2); color:var(--ink-2);
  font-family:var(--f); font-size:.82rem; font-weight:500; letter-spacing:0; text-transform:none;
}
.phero__eyebrow::after{ content:none; }
.phero h1{ font-size:clamp(2.2rem,4.4vw,3.9rem); max-width:17ch; margin-bottom:var(--s5); }
.phero .lede{ max-width:60ch; }

/* ==========================================================================
   BRAND TILE
   The monogram was a small gold chip floating inside a white circle, which
   read as a mistake. The island IS the tile now: a gold rounded square with
   the N knocked out, the same mark as the favicon.
   ========================================================================== */
.nav__brand{
  width:44px; height:44px; padding:0; border-radius:13px;
  background:var(--gold); border:0; box-shadow:var(--sh-2);
  backdrop-filter:none; -webkit-backdrop-filter:none;
  transition:transform var(--t) var(--spring), box-shadow var(--t) var(--ease);
}
.nav[data-stuck="true"] .nav__brand{ background:var(--gold); box-shadow:var(--sh-2); }
.nav__brand:hover{ transform:scale(1.06) rotate(-3deg); box-shadow:var(--sh-3); }
.nav__brand svg{ width:44px; height:44px; border-radius:13px; }
.foot__brand svg{ width:38px; height:38px; border-radius:11px; }

/* the dock steps out of the way when you scroll down, back when you scroll up */
@media (hover:hover) and (min-width:761px){
  .dock{ transition:transform 320ms var(--ease), opacity 240ms var(--ease); }
  .dock[data-hide="true"]{ transform:translateX(-50%) translateY(140%); opacity:0; pointer-events:none; }
}

/* ==========================================================================
   PINNED CAPABILITIES  ·  the home page signature
   Seven cards stacked vertically was just a long scroll. Here the visual pins
   in the middle of the viewport and swaps as the copy moves past it, so the
   whole story happens in one screen. Sticky, not scroll hijacking: the wheel
   behaves exactly as the browser intends.
   ========================================================================== */
.pin{ position:relative; display:grid; grid-template-columns:28px minmax(0,1fr) minmax(0,1fr); gap:clamp(24px,4vw,72px); }
.pin__dot{
  width:8px; height:8px; padding:0; border-radius:50%; background:var(--line-2);
  transition:transform 260ms var(--spring), background 200ms var(--ease);
}
.pin__dot[data-on="true"]{ background:var(--gold); transform:scale(1.7); }
.pin__dot:hover{ background:var(--gold-deep); }
.pin__step{
  min-height:76vh; display:flex; flex-direction:column; justify-content:center;
  padding-block:var(--s8);
  opacity:.32; transform:translateY(0);
  transition:opacity 420ms var(--ease);
}
.pin__step[data-on="true"]{ opacity:1; }
.pin__step h3{ font-size:clamp(1.6rem,2.8vw,2.5rem); margin-bottom:var(--s4); max-width:18ch; }
.pin__step p{ color:var(--muted); font-size:1.0625rem; max-width:46ch; margin-bottom:var(--s5); }
.pin__card{
  position:absolute; inset:0;
  opacity:0; transform:translateY(22px) scale(.97);
  transition:opacity 460ms var(--ease), transform 620ms var(--ease);
  pointer-events:none;
}
.pin__card[data-on="true"]{ opacity:1; transform:none; pointer-events:auto; }
.pin__card .slab__body{ transform:rotateY(-7deg) rotateX(2deg); max-width:none; width:100%; }

@media (max-width:1080px){
  /* on a narrow screen the pin becomes an ordinary stack, which is the right
     behaviour: there is no room to hold a visual beside the copy */
  .pin{ grid-template-columns:1fr; gap:0; }
  .pin__step{
    min-height:0; opacity:1; padding:var(--s6); margin-bottom:var(--s4);
    background:var(--white); border:1px solid var(--line); border-radius:var(--r-l); box-shadow:var(--sh-2);
  }
}
@media (prefers-reduced-motion:reduce){
  .pin__step{ opacity:1; }
  .pin__card{ transition:none; }
}

@media (min-width:1081px){ .pin__step{ min-height:64vh; } }

/* ==========================================================================
   V3 LAYER  ·  rhythm, texture, and the Jarvis
   Added 2026-07-26. The page was one long white scroll. These are the pieces
   that give it a pulse: a warm paper base with real grain, near-black
   statement bands that break the white up, a slow ambient field behind the
   hero, and a working Jarvis demo that explains the company faster than any
   paragraph could.
   ========================================================================== */

:root{
  --void:#0C0D10;          /* near-black, never #000 */
  --void-2:#16171B;
  --paper:#FAF9F6;
}

/* ---------------------------------------------------------------- GRAIN --
   Dead-flat colour reads as render output. A few percent of noise over the
   whole page is the cheapest way to make it read as a made object. */
.grain{
  position:fixed; inset:0; z-index:1; pointer-events:none;
  opacity:.038; mix-blend-mode:multiply;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}
html[data-band="dark"] .grain{ mix-blend-mode:screen; opacity:.05; }
@media (prefers-reduced-motion:reduce){ .grain{ opacity:.025; } }

/* ------------------------------------------------------------ DARK BAND --
   Every token the light design uses is re-pointed inside a dark band, so any
   component dropped in here inverts correctly without a second stylesheet. */
.band--dark{
  --ink:#F4F2ED;
  --ink-2:#C9C7C0;
  --muted:#9C9A93;
  --faint:#7E7C76;
  --canvas:#0C0D10;
  --surface:#16171B;
  --surface-2:#1C1E23;
  --surface-3:#23252B;
  --line:rgba(255,255,255,.10);
  --line-2:rgba(255,255,255,.17);
  --glass:rgba(255,255,255,.06);
  --glass-strong:rgba(255,255,255,.10);
  --glass-line:rgba(255,255,255,.14);
  --gold-deep:#FFFFFF;     /* on dark the deep gold flips to a light gold */
  --gold-wash:rgba(17,17,18,0.042);
  --sh-2:none; --sh-3:none; --sh-4:0 30px 80px -40px rgba(0,0,0,.8);
  background:#0C0D10; color:var(--ink);
  position:relative; isolation:isolate;
}
.band--dark::before{
  content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:
    radial-gradient(70% 120% at 18% 0%, rgba(255,255,255,0.056), transparent 60%),
    radial-gradient(50% 90% at 92% 100%, rgba(255,255,255,0.034), transparent 62%);
}

/* the statement type that earns a full dark band */
.stmt{
  font-size:clamp(2.2rem,5.4vw,4.6rem);
  line-height:1.02; letter-spacing:var(--tr-x); font-weight:700;
  text-wrap:balance; max-width:18ch;
}
.stmt em{ font-style:normal; color:var(--gold); }
.stmt--wide{ max-width:26ch; }

/* --------------------------------------------------------- AMBIENT FIELD --
   A slow drift of gold points behind the hero. Never an object you look at,
   always a texture you look through. */
.ambient{
  position:absolute; inset:0; z-index:0; pointer-events:none;
  mask-image:radial-gradient(120% 90% at 50% 40%, #000 40%, transparent 78%);
  -webkit-mask-image:radial-gradient(120% 90% at 50% 40%, #000 40%, transparent 78%);
}
.ambient canvas{ display:block; width:100%; height:100%; }
@media (prefers-reduced-motion:reduce){ .ambient{ display:none; } }

/* -------------------------------------------------------------- JARVIS --
   The signature. A conversation that types itself out, so a stranger knows
   what we sell in about four seconds without reading a paragraph. */
.jarvis{
  position:relative; z-index:2; width:100%; max-width:430px;
  border-radius:var(--r-l); overflow:hidden;
  background:var(--surface); border:1px solid var(--line);
  box-shadow:var(--sh-4);
  display:flex; flex-direction:column;
}
.band--dark .jarvis{ border-color:var(--line-2); }
@keyframes pulseDot{
  0%{ box-shadow:0 0 0 0 rgba(31,146,84,.5); }
  70%{ box-shadow:0 0 0 7px rgba(31,146,84,0); }
  100%{ box-shadow:0 0 0 0 rgba(31,146,84,0); }
}
.jarvis__biz{
  margin-left:auto; font-family:var(--f-mono); font-size:10px;
  letter-spacing:.1em; text-transform:uppercase; color:var(--faint);
}

.jarvis__log{
  display:flex; flex-direction:column; gap:10px; justify-content:flex-end;
  padding:var(--s5); min-height:292px;
}
.msg{
  max-width:84%; padding:10px 14px; border-radius:16px; font-size:14.5px;
  line-height:1.45; opacity:0; transform:translateY(8px);
  animation:msgIn .42s cubic-bezier(.16,1,.3,1) forwards;
}
@keyframes msgIn{ to{ opacity:1; transform:none; } }
.msg[data-from="them"]{
  align-self:flex-start; background:var(--surface-2); color:var(--ink);
  border-bottom-left-radius:5px;
}
.msg[data-from="ai"]{
  align-self:flex-end; background:var(--ink); color:var(--canvas);
  border-bottom-right-radius:5px;
}
.band--dark .msg[data-from="ai"]{ background:var(--gold); color:#231A02; }
.msg b{ font-weight:650; }

/* the three dot thinking indicator */
.msg--typing{ display:flex; gap:4px; align-items:center; padding:13px 15px; }
.msg--typing i{
  width:6px; height:6px; border-radius:50%; background:currentColor; opacity:.4;
  animation:blip 1.25s ease-in-out infinite;
}
.msg--typing i:nth-child(2){ animation-delay:.16s; }
.msg--typing i:nth-child(3){ animation-delay:.32s; }
@keyframes blip{
  0%,60%,100%{ opacity:.25; transform:translateY(0); }
  30%{ opacity:1; transform:translateY(-3px); }
}

/* the caption under the demo that names what just happened */
.jarvis__cap{
  margin-top:var(--s4); font-size:13.5px; color:var(--muted);
  display:flex; align-items:center; gap:8px; max-width:430px;
}
.jarvis__cap i{ width:5px; height:5px; border-radius:50%; background:var(--gold); flex:none; }

/* ------------------------------------------------------- PLAIN ENGLISH --
   Three statements of what we actually do, as a staircase rather than three
   equal cards. Each step sits lower and further right than the last. */
.plain{ display:grid; gap:0; margin-top:var(--s10); }
.plain__row{
  display:grid; grid-template-columns:auto 1fr auto; align-items:baseline;
  gap:clamp(14px,3vw,40px); padding:clamp(20px,2.6vw,34px) 0;
  border-top:1px solid var(--line);
}
.plain__row:last-child{ border-bottom:1px solid var(--line); }
.plain__no{
  font-family:var(--f-mono); font-size:11px; letter-spacing:.12em;
  color:var(--gold-deep); padding-top:.5em;
}
.plain__t{
  font-size:clamp(1.35rem,2.9vw,2.3rem); font-weight:650;
  letter-spacing:var(--tr-tight); line-height:1.12;
}
.plain__t span{
  color:var(--muted); font-weight:400; display:block;
  font-size:.6em; margin-top:9px; letter-spacing:-.01em; line-height:1.5;
}
.plain__row:nth-child(2) .plain__t{ padding-left:clamp(0px,3vw,52px); }
.plain__row:nth-child(3) .plain__t{ padding-left:clamp(0px,6vw,104px); }
.plain__tag{
  font-family:var(--f-mono); font-size:10px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--faint); white-space:nowrap;
}
@media (max-width:700px){
  .plain__row{ grid-template-columns:auto 1fr; }
  .plain__tag{ display:none; }
  .plain__row:nth-child(2) .plain__t,
  .plain__row:nth-child(3) .plain__t{ padding-left:0; }
}

/* --------------------------------------------------------- INNER PAGES --
   Each inner page gets its own spine so they stop reading as one template
   stacked five times. */

/* SERVICES: a sticky numbered index beside a scrolling detail column */
.idx{
  display:grid; grid-template-columns:minmax(210px,.8fr) 1.2fr;
  gap:clamp(24px,5vw,80px); align-items:start;
}
.idx__list{ position:sticky; top:118px; display:grid; gap:0; }
.idx__item{
  display:flex; align-items:baseline; gap:12px; padding:12px 0;
  border-top:1px solid var(--line); color:var(--faint);
  font-size:clamp(1rem,1.5vw,1.2rem); font-weight:600; letter-spacing:-.02em;
  transition:color .3s var(--ease), padding-left .4s var(--ease);
  text-align:left; width:100%; background:none; cursor:pointer;
}
.idx__item span{ font-family:var(--f-mono); font-size:10.5px; letter-spacing:.1em; }
.idx__item[aria-current="true"]{ color:var(--ink); padding-left:10px; }
.idx__item[aria-current="true"] span{ color:var(--gold-deep); }
.idx__panels{ display:grid; gap:clamp(44px,7vw,104px); }
.idx__panel h3{
  font-size:clamp(1.6rem,3.2vw,2.5rem); letter-spacing:var(--tr-tight); line-height:1.08;
}
.idx__panel > p{
  color:var(--muted); margin-top:var(--s4); max-width:52ch;
  font-size:1.02rem; line-height:1.65;
}
.idx__spec{ margin-top:var(--s6); display:grid; gap:0; counter-reset:spec; }
.idx__spec li{
  list-style:none; counter-increment:spec; display:grid;
  grid-template-columns:auto 1fr; gap:14px; padding:11px 0;
  border-top:1px solid var(--line); font-size:.95rem; color:var(--ink-2);
}
.idx__spec li::before{
  content:counter(spec,decimal-leading-zero);
  font-family:var(--f-mono); font-size:10px; color:var(--faint);
  letter-spacing:.1em; padding-top:.3em;
}
@media (max-width:860px){
  .idx{ grid-template-columns:1fr; }
  .idx__list{ position:static; }
}

/* PRICING: one slab with a real toggle, not four price cards */
.calc{
  display:grid; grid-template-columns:1.1fr .9fr; gap:clamp(24px,4vw,56px);
  align-items:center; padding:clamp(22px,4vw,48px);
  border-radius:var(--r-xl); background:var(--surface); border:1px solid var(--line);
  box-shadow:var(--sh-3);
}
.band--dark .calc{ border-color:var(--line-2); }
.calc__opt{
  display:grid; grid-template-columns:20px 1fr auto; align-items:center; gap:14px;
  padding:14px 17px; border-radius:var(--r-s); border:1px solid var(--line-2);
  background:transparent; text-align:left; width:100%; cursor:pointer;
  transition:border-color .22s var(--ease), background .22s var(--ease);
}
.calc__opt:hover{ border-color:var(--gold); }
.calc__opt[aria-pressed="true"]{ border-color:var(--gold); background:var(--gold-wash); }
.calc__big{
  font-size:clamp(2.6rem,6vw,4.2rem); font-weight:700; letter-spacing:var(--tr-x);
  line-height:1; font-variant-numeric:tabular-nums;
}
.calc__sub{ color:var(--muted); margin-top:10px; font-size:.95rem; }
@media (max-width:800px){ .calc{ grid-template-columns:1fr; } }

/* WORK: a standards ledger, numbered rows on hairlines */
.ledger{ counter-reset:led; }
.ledger__row{
  counter-increment:led; display:grid; grid-template-columns:auto 1fr auto;
  gap:clamp(14px,3vw,36px); align-items:start;
  padding:clamp(18px,2.4vw,30px) 0; border-top:1px solid var(--line);
}
.ledger__row:last-child{ border-bottom:1px solid var(--line); }
.ledger__row::before{
  content:counter(led,decimal-leading-zero);
  font-family:var(--f-mono); font-size:11px; letter-spacing:.12em;
  color:var(--gold-deep); padding-top:.45em;
}
.ledger__t{
  font-size:clamp(1.1rem,2.1vw,1.55rem); font-weight:600;
  letter-spacing:-.02em; line-height:1.2;
}
.ledger__d{ color:var(--muted); margin-top:7px; font-size:.97rem; line-height:1.6; max-width:62ch; }
.ledger__mark{
  display:grid; place-items:center; width:26px; height:26px; border-radius:50%;
  border:1px solid var(--gold); color:var(--gold-deep); margin-top:.2em;
}
.ledger__mark svg{ width:12px; height:12px; }
@media (max-width:640px){ .ledger__mark{ display:none; } }

/* CONTACT: two panes, the form beside a live reply */
.split{
  display:grid; grid-template-columns:1.05fr .95fr;
  gap:clamp(28px,5vw,72px); align-items:start;
}
@media (max-width:880px){ .split{ grid-template-columns:1fr; } }

/* the answer half of the pricing slab */

/* section eyebrows sit above a heading and need the gap set once */
.eyebrow{ display:block; margin-bottom:var(--s4); color:var(--gold-deep); }

/* ------------------------------------------------------------ TAP SIZE --
   Apple's floor is 44px. These were the four controls sitting under it on a
   phone: the burger, the inline "what is included" links, the small mono
   link in the hero, and the footer wordmark. Padding rather than height, so
   the whole area is hittable rather than just the glyphs. */
@media (max-width:900px){
  .burger{ width:44px; height:44px; }
  .link-mono{ display:inline-flex; align-items:center; min-height:44px; }
  .foot__brand{ display:inline-flex; align-items:center; min-height:44px; }
  a.mono, button.mono{ display:inline-flex; align-items:center; min-height:44px; }
}


/* ===== novarus-v4.css ===== */
/* ==========================================================================
   ASCENIX V4  ·  the product is the hero
   2026-07-26. Loads after novarus.css and overrides it.

   Every value here is measured, not remembered. Sources are the Mobbin brain
   (613 marketing sections across 208 brands, plus computed CSS read off 15
   live premium sites): warm off-white over pure white, hero type big and
   TIGHT at weight 510 rather than 700, one accent only, pills for chrome and
   8-16px for panels, and no bounce easing anywhere on production chrome.
   ========================================================================== */

:root{
  /* warm paper beats pure white. measured: Cursor #F7F7F4, Lattice #F7F6F2,
     Vercel #FAFAFA, Notion #F6F5F4 */
  --paper:#F7F6F2;
  --paper-2:#EFEEE9;
  --canvas:#F7F6F2;

  /* near-black, warm, never #000 for large fields */
  --void:#0B0B0C;
  --void-2:#131315;
  --void-3:#1B1B1E;

  --ink:#1A1917;
  --ink-2:#4A4843;

  /* one accent. gold stays: it is the brand, and amber/orange is the hue
     family that actually leads the corpus at 22.6% */
  --gold:#111112;
  --gold-deep:#8A5D02;

  /* the two measured house curves. nothing in the corpus uses overshoot on
     production chrome, so the old spring is retired here. */
  --ease-ui:cubic-bezier(.4,0,.2,1);
  --ease-expo:cubic-bezier(.165,.84,.44,1);
  --spring:cubic-bezier(.4,0,.2,1);
  --t:150ms;

  /* radii: pills for interactive chrome, 8-16 for panels, 2-6 for dense UI */
  --r-s:6px; --r:12px; --r-l:16px; --r-xl:20px; --r-pill:9999px;
}

body{ background:var(--paper); }

/* ---------------------------------------------------------------- TYPE --
   Inter Variable at 510. Linear ships exactly this: a non-stock axis value in
   a font everyone has, which reads as considered rather than default. The
   measured hero median across 15 premium sites is weight 400-510 and tracking
   -0.024em, NOT the 700-900 that a generic grotesk needs. */
h1,h2,h3,h4,.stmt,.calc__big{
  font-variation-settings:"wght" 510;
  font-weight:510;
  letter-spacing:-.024em;
}
.hero h1{
  font-size:clamp(2.9rem,6.4vw,4.5rem);
  line-height:1.02;
  letter-spacing:-.028em;
  font-variation-settings:"wght" 510;
  font-weight:510;
}
.stmt{ font-weight:510; font-variation-settings:"wght" 510; letter-spacing:-.026em; }

/* the italic-emphasis move: one word set apart while the rest stays roman.
   the single most-copied premium signal in the corpus. */
.em-word{ color:var(--gold-deep); font-style:normal; }
.band--dark .em-word{ color:var(--gold); }

/* ------------------------------------------------------- DOCUMENT SPINE --
   The page is numbered like a spec document, Firecrawl style. This is the
   structural signal that separates it from the alternating-bands spine used
   on the last builds. */
.docnum{
  display:flex; align-items:center; gap:14px;
  font-family:var(--f-mono); font-size:10.5px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--faint);
  margin-bottom:var(--s6);
}
.docnum::after{ content:""; flex:1; height:1px; background:var(--line); }
.docnum b{ color:var(--gold-deep); font-weight:500; }
.band--dark .docnum b{ color:var(--gold); }

/* --------------------------------------------------------- CENTERED HERO --
   A1 Centered Declaration. The confidence signal in the corpus is that the
   less sits in the hero, the bigger the brand reads: headline, one line of
   sub, ONE pill, microcopy. The product goes BELOW, full width. */
.hero{ min-height:auto; padding:clamp(120px,17vh,190px) 0 clamp(48px,7vh,84px); }
.hero__grid{ display:block; }
/* The measure has to live on the h1, not on this container. A ch unit
   resolves against the element's OWN font-size, so 19ch here computed against
   16px body text (about 155px) and broke the headline to one word per line. */
.hero__center{
  max-width:min(940px,100%); margin:0 auto; text-align:center;
  position:relative; z-index:2;
}
.hero__center h1{ max-width:17ch; margin-inline:auto; }
.hero__center .lede{
  max-width:44ch; margin:var(--s6) auto 0; text-align:center;
  font-size:clamp(1.02rem,1.5vw,1.2rem); color:var(--muted); line-height:1.55;
}
.hero__center .hero__cta{ justify-content:center; margin-top:var(--s8); }
/* the centre column is clamped to 19ch for the headline, so anything that is
   not the headline has to opt out of that measure explicitly */
.hero__micro{
  max-width:40ch; margin:var(--s4) auto 0;
  font-size:12.5px; color:var(--faint);
  font-family:var(--f-mono); letter-spacing:.04em;
}
.hero__center > .eyebrow-pill{ margin-inline:auto; }

/* the eyebrow is a bordered pill, per the measured convention */
.eyebrow-pill{
  display:inline-flex; align-items:center; gap:8px; margin-bottom:var(--s6);
  padding:6px 14px 6px 10px; border-radius:var(--r-pill);
  border:1px solid var(--line-2); background:rgba(255,255,255,.6);
  font-size:12.5px; color:var(--ink-2); letter-spacing:-.005em;
}
.eyebrow-pill b{ font-weight:600; color:var(--ink); }
.eyebrow-pill i{
  width:5px; height:5px; border-radius:50%; background:var(--gold); font-style:normal;
}
.eyebrow-pill svg{ width:12px; height:12px; opacity:.4; }

/* primary CTA = solid near-black pill, white text. brand colour is NOT the
   default primary in the corpus; it is reserved for consumer brands. */
.btn--dark{
  background:var(--void); color:#F7F6F2; border:0;
  border-radius:var(--r-pill); padding:0 26px; height:50px;
  font-size:15px; font-weight:550; letter-spacing:-.01em;
  transition:transform var(--t) var(--ease-ui), opacity var(--t) var(--ease-ui);
}
.btn--dark:hover{ opacity:.86; }
.btn--dark:active{ transform:scale(.985); }

/* ============================================================== CONSOLE ===
   THE SIGNATURE. A full-bleed near-black operations board where four lanes
   run at once: calls, quotes, follow ups and bookings. The movement is the
   concurrency, not a clock, and any row opens to show the real exchange
   underneath it. It answers "what does it actually do" by showing a shift's
   worth of work already done, rather than one chat window.
   ========================================================================== */
/* dot grid, the texture for this build. replaces the grain used last time. */

/* four lanes running at once */

.ev{
  border:1px solid rgba(255,255,255,.09); border-radius:9px;
  background:rgba(255,255,255,.035); padding:9px 11px;
  text-align:left; width:100%; color:inherit; cursor:pointer;
  opacity:0; transform:translateY(10px);
  animation:evIn .5s var(--ease-expo) forwards;
  transition:background var(--t) var(--ease-ui), border-color var(--t) var(--ease-ui);
}
@keyframes evIn{ to{ opacity:1; transform:none; } }
.ev:hover{ background:rgba(255,255,255,.075); border-color:rgba(17,17,18,0.120); }

/* the expanded transcript, the payoff for clicking a row */

/* full-bleed: the console breaks the container, which is the page's one
   deliberate container break */

/* ------------------------------------------------------------ COMPARISON --
   Naming the enemy. The cleanest version in the corpus is a two column panel
   with the right side tinted. */
.versus{
  display:grid; grid-template-columns:1fr 1fr; gap:0;
  border:1px solid var(--line); border-radius:var(--r-l); overflow:hidden;
  background:var(--surface);
}
.versus__col{ padding:clamp(20px,3vw,34px); }
.versus__col + .versus__col{
  border-left:1px solid var(--line);
  background:linear-gradient(180deg, rgba(17,17,18,0.025), rgba(17,17,18,0.007));
}
.versus h3{ font-size:1.05rem; margin-bottom:var(--s5); letter-spacing:-.02em; }
.versus h3 span{
  font-family:var(--f-mono); font-size:9.5px; letter-spacing:.13em; text-transform:uppercase;
  color:var(--faint); display:block; margin-bottom:6px; font-weight:400;
}
.versus ul{ display:grid; gap:11px; }
.versus li{
  list-style:none; display:grid; grid-template-columns:16px 1fr; gap:11px;
  font-size:.95rem; color:var(--ink-2); line-height:1.5;
}
.versus li::before{
  content:"\00d7"; font-family:var(--f-mono); color:var(--faint);
  text-align:center; line-height:1.5;
}
.versus__col + .versus__col li::before{ content:"\2713"; color:var(--gold-deep); }
@media (max-width:720px){
  .versus{ grid-template-columns:1fr; }
  .versus__col + .versus__col{ border-left:0; border-top:1px solid var(--line); }
}

/* ------------------------------------------------------------- PLAN CARD --
   One product, one price. A single card reads more confident than three tiers
   when there is genuinely one thing being sold. */
.plan{
  display:grid; grid-template-columns:1.15fr .85fr; gap:0;
  border-radius:var(--r-xl); overflow:hidden; border:1px solid var(--line);
  background:var(--surface);
}
.plan__main{ padding:clamp(24px,3.4vw,40px); }
.plan__side{
  padding:clamp(24px,3.4vw,40px);
  background:var(--void); color:#EDECE8; display:flex; flex-direction:column; justify-content:center;
}
.plan__price{
  font-family:var(--f-mono); font-size:clamp(2.4rem,5vw,3.4rem); font-weight:500;
  letter-spacing:-.04em; line-height:1; font-variant-numeric:tabular-nums; color:#F7F6F2;
}
.plan__price sub{ font-size:.3em; font-weight:400; color:#8B8A85; bottom:0; margin-left:4px; letter-spacing:.02em; }
.plan__note{ margin-top:var(--s4); font-size:.9rem; color:#A5A39D; line-height:1.55; }
.plan__list{ display:grid; gap:0; margin-top:var(--s5); }
.plan__list li{
  list-style:none; display:grid; grid-template-columns:auto 1fr; gap:12px;
  padding:11px 0; border-top:1px solid var(--line); font-size:.95rem; color:var(--ink-2);
}
.plan__list li::before{
  content:"\2713"; color:var(--gold-deep); font-family:var(--f-mono); font-size:.85rem; padding-top:.15em;
}
@media (max-width:800px){ .plan{ grid-template-columns:1fr; } }

/* -------------------------------------------------------------- RETIRE ----
   The v3 pieces this build replaces. Kept as overrides rather than deleted
   from novarus.css so the inner pages that still use them keep working. */
.hero .ambient{ opacity:.55; }
.grain{ opacity:.022; }

/* ---------------------------------------------------------------- DOCK ----
   Always up. The hide-on-scroll behaviour is gone from the script; this makes
   sure a stale attribute can never translate it off screen again. */
.dock[data-hide="true"],
.dock[data-hide]{ transform:translateX(-50%) !important; opacity:1 !important; }

/* ======================================================= BACKGROUND MOTION ==
   The field used to sit inside the hero, so all movement stopped as soon as
   you scrolled past it. It is now fixed behind the whole page. */
.hero{ position:relative; z-index:1; }
main > section{ position:relative; z-index:1; }

/* A fixed layer cannot show through an opaque near-black band, so the dark
   sections carry their own drift. Two soft gold masses on long offset loops,
   translate only, which stays on the compositor. */
.band--dark::before{
  background:
    radial-gradient(46% 60% at 22% 18%, rgba(255,255,255,0.090), transparent 62%),
    radial-gradient(38% 52% at 84% 76%, rgba(255,255,255,0.056), transparent 64%);
  animation:aurora 26s var(--ease-ui) infinite alternate;
  will-change:transform;
}
@keyframes aurora{
  0%{   transform:translate3d(-3%, 2%, 0) scale(1.04); }
  50%{  transform:translate3d(4%, -3%, 0) scale(1.12); }
  100%{ transform:translate3d(-2%, 4%, 0) scale(1.06); }
}

/* the console is the one dark anchor on the page, so it gets a slow sweep of
   its own rather than sitting perfectly still */
@keyframes sweep{
  0%{   transform:translate3d(-28%, 6%, 0); }
  50%{  transform:translate3d(26%, -8%, 0); }
  100%{ transform:translate3d(-28%, 6%, 0); }
}

@media (prefers-reduced-motion:reduce){
  .band--dark::before,.console::after{ animation:none; }
}


/* ===== novarus-v5.css ===== */
/* ==========================================================================
   ASCENIX V5  ·  monochrome, liquid, and a real product UI
   2026-07-26. Loads last and overrides v4.

   Three moves:
   1. Colour comes out. The corpus offers two winning poles and says never land
      between them: scream ONE saturated colour, or remove colour and out-craft.
      This is the second pole. The only colour left is the delta on the
      dashboard, which is the one place 1,434 measured product screens allow it.
   2. The background is liquid glass: soft masses merged with a gooey filter,
      drifting behind a frosted pane. Transform only, so it stays composited.
   3. The hero visual is an actual product dashboard, built to the measured
      conventions in 06-product-ui-and-dashboards.md rather than invented.
   ========================================================================== */

:root{
  --paper:#FBFBFA;
  --paper-2:#F4F4F3;
  --canvas:#FBFBFA;
  --surface:#FFFFFF;

  --void:#0B0B0C;
  --void-2:#141416;
  --void-3:#1D1D20;

  --ink:#111112;
  --ink-2:#42424A;
  --muted:#6B6B73;
  --faint:#97979E;

  --line:rgba(17,17,18,.09);
  --line-2:rgba(17,17,18,.16);

  /* the accent is now ink. every gold token resolves to a neutral so nothing
     downstream has to be hunted down and rewritten. */
  --gold:#111112;
  --gold-lift:#2A2A2E;
  --gold-deep:#111112;
  --gold-wash:rgba(17,17,18,.05);

  /* the single exception, per 6.3: the delta on a KPI tile is the only place
     colour is allowed, and only as small text. */
  --up:#1B7F4B;
  --down:#B4382C;
}

body{ background:var(--paper); color:var(--ink); }

/* gold survived in a few v3/v4 rules as literal hex, so neutralise those */
.em-word{ color:var(--ink); }
.band--dark .em-word{ color:#FFFFFF; }
.band--dark{ --gold:#FFFFFF; --gold-deep:#FFFFFF; --gold-wash:rgba(255,255,255,.08); }
.docnum b{ color:var(--ink); }
.band--dark .docnum b{ color:#FFFFFF; }
.plain__no{ color:var(--muted); }
.band--dark .plain__no{ color:rgba(255,255,255,.55); }
.jarvis__cap i{ background:var(--ink); }
.band--dark .jarvis__cap i{ background:#FFFFFF; }
.eyebrow-pill i{ background:var(--ink); }
.btn--gold{ background:var(--ink); color:#FBFBFA; }
.btn--gold:hover{ opacity:.86; filter:none; }
.ledger__mark{ border-color:var(--line-2); color:var(--ink); }
.versus__col + .versus__col{ background:rgba(17,17,18,.028); }
.versus__col + .versus__col li::before{ color:var(--ink); }
.calc__opt[aria-pressed="true"]{ border-color:var(--ink); background:rgba(17,17,18,.04); }
.idx__item[aria-current="true"] span{ color:var(--ink); }
.plan__list li::before{ color:var(--ink); }

/* ====================================================== LIQUID GLASS BG ====
   Four soft masses drift on long, offset loops inside a container that is
   blurred hard and then contrast-boosted. The blur plus contrast is what
   welds them into one liquid body instead of four circles, and the frosted
   pane over the top is what makes it read as glass rather than as a gradient.
   ========================================================================== */
.liquid{
  position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden;
  background:var(--paper);
}
.goo-def{ position:absolute; width:0; height:0; }
.liquid__goo{
  position:absolute; inset:-18%;
  filter:url(#goo);
  opacity:.85;
}
.liquid__blob{
  position:absolute; border-radius:50%;
  background:radial-gradient(circle at 50% 50%, #D6D6DD 0%, #D6D6DD 52%, rgba(214,214,221,0) 72%);
  will-change:transform;
}
.liquid__blob:nth-child(1){ width:46vw; height:46vw; left:-6vw;  top:2vh;
  animation:drift1 34s ease-in-out infinite; }
.liquid__blob:nth-child(2){ width:38vw; height:38vw; right:-4vw; top:22vh;
  animation:drift2 41s ease-in-out infinite; }
.liquid__blob:nth-child(3){ width:32vw; height:32vw; left:26vw;  bottom:-8vh;
  animation:drift3 29s ease-in-out infinite; }
.liquid__blob:nth-child(4){ width:27vw; height:27vw; right:20vw; top:-6vh;
  animation:drift4 47s ease-in-out infinite; }

@keyframes drift1{
  0%,100%{ transform:translate3d(0,0,0) scale(1); }
  33%{ transform:translate3d(16vw,10vh,0) scale(1.18); }
  66%{ transform:translate3d(6vw,26vh,0) scale(.9); }
}
@keyframes drift2{
  0%,100%{ transform:translate3d(0,0,0) scale(1.05); }
  40%{ transform:translate3d(-20vw,14vh,0) scale(.86); }
  70%{ transform:translate3d(-8vw,-8vh,0) scale(1.2); }
}
@keyframes drift3{
  0%,100%{ transform:translate3d(0,0,0) scale(.95); }
  50%{ transform:translate3d(-14vw,-20vh,0) scale(1.25); }
}
@keyframes drift4{
  0%,100%{ transform:translate3d(0,0,0) scale(1); }
  45%{ transform:translate3d(-10vw,22vh,0) scale(1.16); }
  80%{ transform:translate3d(8vw,6vh,0) scale(.88); }
}

/* the pane. this is the "glass" half: it sits over the liquid and frosts it,
   which is what stops the movement reading as a cheap animated gradient. */
.liquid__pane{
  position:absolute; inset:0;
  backdrop-filter:blur(34px) saturate(0%);
  -webkit-backdrop-filter:blur(34px) saturate(0%);
  background:linear-gradient(180deg, rgba(251,251,250,.30), rgba(251,251,250,.58));
}

/* the old particle field is retired by this build */
.ambient,.ambient--page{ display:none !important; }

@media (prefers-reduced-motion:reduce){
  /* match the specificity of the per-blob rules above, otherwise nth-child
     wins and the animation keeps running */
  .liquid__blob:nth-child(1),.liquid__blob:nth-child(2),
  .liquid__blob:nth-child(3),.liquid__blob:nth-child(4){ animation:none; }
  .liquid__goo{ opacity:.55; }
}
/* the gooey filter is expensive on low-power phones, so drop to a plain wash */
@media (max-width:700px){
  .liquid__goo{ filter:blur(38px); opacity:.55; }
  .liquid__pane{ backdrop-filter:blur(24px); -webkit-backdrop-filter:blur(24px); }
}

/* ============================================================== PRODUCT UI ==
   A real dashboard, built to the measured conventions: light canvas, grouped
   sidebar with tiny all-caps section labels, breadcrumb, a date-range control
   row, 4 KPI tiles, one big chart panel plus a side panel, then a table.
   Product-UI density, which is materially tighter than marketing density:
   body 13-14px, labels 10-12px, radius 4-8px, hairlines instead of shadows.
   ========================================================================== */

/* ---- window chrome, so it reads as software rather than as a web section -- */

/* ---- sidebar --------------------------------------------------------- */

/* ---- main ------------------------------------------------------------- */

/* ---- KPI tiles: label, big number, delta, sparkline -------------------- */

/* ---- panels ----------------------------------------------------------- */

/* the source breakdown: a bar list, not a pie */

/* ---- table ------------------------------------------------------------ */
/* a new row slides in every few seconds: the only motion inside the product */
@keyframes rowIn{ from{ opacity:0; transform:translateY(-6px); } to{ opacity:1; transform:none; } }

/* status is a tinted pill, never coloured text */
@keyframes uiPulse{
  0%,100%{ box-shadow:0 0 0 0 rgba(27,127,75,.5); }
  60%{ box-shadow:0 0 0 6px rgba(27,127,75,0); }
}

/* ---- responsive ------------------------------------------------------- */

/* ------------------------------------------------------------ HERO WASH ----
   v3 painted a warm radial behind the hero. With the palette monochrome that
   read as a stain in the corners, so the hero is now clean and the only thing
   moving behind it is the liquid layer. */
.hero::before,.hero::after{ display:none !important; }
.close::before,.close::after{ display:none !important; }

/* ------------------------------------------------------------ AMB RETIRED --
   novarus.js injects a .amb layer of coloured blobs into every band (v3). Its
   gold and blue gradients are set inline in the stylesheet, which is why the
   palette audit on backgroundColor missed them and a warm cast survived on the
   right of the hero. The liquid layer replaces it entirely. */
.amb{ display:none !important; }

/* -------------------------------------------------- LET THE LIQUID THROUGH --
   The liquid is a fixed layer at z-index 0, but the page sections were painting
   opaque white on top of it at z-index 1, so none of it was visible. Only the
   full-width section backgrounds go transparent: cards, panels and the dark
   bands stay opaque, because those are surfaces rather than page background. */
body{ background:transparent; }
main,
.hero,
.band--light,
.phero,
section.close:not(.band--dark){ background:transparent !important; }

/* the dark bands stay opaque on purpose, and carry their own drift */
.band--dark{ background:#0B0B0C !important; }

/* -------------------------------------------------------- DARK BAND INK ----
   v3 set a warm off-white for text on dark. Neutral now, to match the rest. */
.band--dark{ --ink:#F2F2F3; --ink-2:#C4C4C7; --muted:#9B9B9F; --faint:#7C7C80; }

/* ------------------------------------------------------- INVISIBLE BUTTONS --
   Two separate collapses, one symptom. The mobile call button took
   background:var(--gold) with color:var(--ink) and v5 maps both to #111112, so
   it rendered as a blank black pill with the icon gone too (it inherits
   currentColor). The dark-band CTA took background:var(--ink), and .band--dark
   re-points --ink to near-white, so it was white on white at 1.08:1.
   Fixed separately on purpose: sharing a variable is what caused this. */
.callbar__btn--call{
  background:var(--ink); color:#FBFBFA; border:1px solid var(--ink);
}
.callbar__btn--call svg{ color:#FBFBFA; }
.band--dark .btn--gold{ background:#FBFBFA; color:#0B0B0C; }
.band--dark .btn--gold svg{ color:#0B0B0C; }

/* -------------------------------------------------------------- SELECTION --
   ::selection was gold on ink, both now #111112, so dragging over text turned
   it into a black redaction bar. */
::selection{ background:#111112; color:#FBFBFA; }
::-moz-selection{ background:#111112; color:#FBFBFA; }

/* -------------------------------------------------------- TOUCH TABLET NAV --
   The dock was gated behind (hover:hover), and the burger only existed below
   761px. Every iPad reports hover:hover false and is wider than 760, so dock,
   burger and call bar all computed display:none and the tablet had no way to
   reach another page except the footer. Navigation must never depend on a
   hover capability. */
@media (min-width:761px){
  .dock{
    position:fixed; left:50%; bottom:24px; z-index:var(--z-dock);
    display:flex; align-items:center; gap:6px; padding:9px;
    border-radius:var(--r-pill);
    background:rgba(251,251,250,.92); border:1px solid var(--line-2);
    backdrop-filter:saturate(180%) blur(18px);
    -webkit-backdrop-filter:saturate(180%) blur(18px);
    transform:translateX(-50%);
  }
}
/* keep the burger available on touch tablets as a second route */
@media (max-width:1000px) and (pointer:coarse){
  .burger{ display:flex !important; }
}

/* ==========================================================================
   AUDIT PASS 2
   ========================================================================== */

/* S11 .h2 was used on five section heads and defined nowhere, so it fell to the
   UA default 1.5em and measured the same 25.5px at 390 and at 1920. */
.h2{
  font-size:clamp(1.75rem, 1.2rem + 2.2vw, 2.75rem);
  font-weight:510; font-variation-settings:"wght" 510;
  letter-spacing:-.024em; line-height:1.1;
}

/* S12 section headlines sat flush on the paragraph under them, so the lede's
   first line jammed into a 74px headline's descenders. */
.stmt, .h2{ margin-bottom:var(--s5); }
.stmt + .lede, .h2 + .lede{ margin-top:0; }

/* S13 .versus h3 span also matched the nested product token, so the right
   eyebrow stacked to three lines and the two columns went a row out of step. */
.versus h3 > span{ display:block; }
.versus h3 [data-product]{ display:inline; }

/* S18 --faint was #97979E: 2.90:1 on white and 2.66:1 on the footer, failing AA
   in 29 places at under 24px. Verified against the footer, not white. */
:root{ --faint:#6E6E76; }

/* S14 the dock sat in the middle of the reading column and half-erased body
   copy through its blur. Hide-on-scroll is NOT coming back (Journey asked for
   it to stay up), so instead it moves off the reading column, becomes properly
   opaque so anything behind it is cleanly occluded rather than smeared, and
   gets out of the way on short viewports. */
@media (min-width:761px){
  .dock{
    left:auto; right:24px; transform:none;
    background:rgba(251,251,250,.97);
    box-shadow:0 2px 8px rgba(17,17,18,.06), 0 18px 40px -24px rgba(17,17,18,.28);
  }
  .dock[data-hide="true"],.dock[data-hide]{ transform:none !important; }
}
@media (max-height:700px){ .dock{ display:none; } }
.hero{ padding-bottom:clamp(72px,10vh,120px); }

/* S15 the hero H1 is the LCP element and was being faded in over 700ms with a
   per-step delay, measuring 1652-1896ms desktop against 676ms without. Reveals
   stay everywhere below the fold. */
.hero h1[data-rv],
.hero .lede[data-rv],
.hero .hero__cta[data-rv],
.hero .hero__micro[data-rv],
.hero .eyebrow-pill[data-rv]{
  opacity:1 !important; transform:none !important; transition:none !important;
}

/* S28 the sticky nav transitioned padding, forcing about 90 layout passes per
   threshold crossing. Same visual result from a transform. */
.nav__island{ transition:transform 200ms var(--ease-ui), background 200ms var(--ease-ui); }

/* S29 the aurora moved 7/255 at most across a 26 second leg: a permanent
   compositor layer for motion nobody can perceive. */
.band--dark::before{ animation:none; will-change:auto; }

/* S25 the reduced-motion reset squashed duration but left iteration count, so
   three infinite animations kept running: about 270 style recalcs a second on
   the page a vestibular-sensitive user is most likely to be on. */
@media (prefers-reduced-motion:reduce){
  /* NOT animation:none. That resets animation-fill-mode too, and any element
     whose resting state is opacity:0 until its animation lands then stays
     invisible. It blanked the whole chat demo, which is the flagship product
     shot, for exactly the users least able to tolerate a broken page. */
  *,*::before,*::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    animation-delay:0s !important;
    transition-duration:.01ms !important;
    scroll-behavior:auto !important;
  }
  /* the purely decorative layers can genuinely stop */
  .lines__a,.lines__b,.shapes i,.liquid__blob,.hero::after,
  .hero__cta .btn--dark::after,.band--dark::before{ animation:none !important; }
}

/* ------------------------------------------------------------ CONTACT FORM --
   S8: there was no form anywhere on the site. Labels above the field, never a
   placeholder standing in for one, and 16px inputs so iOS does not zoom the
   page on focus. */
.cform{ display:grid; gap:var(--s4); margin-top:var(--s8); max-width:34rem; }
.cform__row{ display:grid; grid-template-columns:1fr 1fr; gap:var(--s4); }
@media (max-width:560px){ .cform__row{ grid-template-columns:1fr; } }
.cform .field{ display:grid; gap:6px; min-width:0; }
.cform .field > span{ font-size:13px; font-weight:550; color:var(--ink-2); }
.cform input,.cform textarea{
  width:100%; min-height:46px; padding:12px 13px;
  border:1px solid var(--line-2); border-radius:8px;
  background:#FFFFFF; color:var(--ink);
  font-family:var(--f); font-size:16px; line-height:1.45;
  transition:border-color 150ms var(--ease-ui), box-shadow 150ms var(--ease-ui);
}
.cform textarea{ resize:vertical; }
.cform input::placeholder,.cform textarea::placeholder{ color:var(--faint); }
.cform input:focus,.cform textarea:focus{
  outline:none; border-color:var(--ink); box-shadow:0 0 0 3px rgba(17,17,18,.12);
}
.cform .btn{ justify-self:start; }
.cform__note{ font-size:13px; color:var(--muted); }
.cform__note a{
  color:var(--ink); text-decoration:underline; text-underline-offset:2px;
  display:inline-block; min-height:44px; line-height:44px;
}
.cform__pot{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

/* Name and email are the two we cannot answer without, so the other two are
   labelled optional rather than the required pair being starred. Asterisks
   make a form look like a tax return; "optional" reads as permission. */
.cform .field > span i{
  font-style:normal; font-weight:450; color:var(--faint);
  font-size:12px; letter-spacing:.01em;
}
/* Both of these live inside the field itself rather than after the row. The row
   is a two column grid, so anything appended to it becomes a third cell and the
   note about the email lands underneath the phone box instead. Being spans
   inside the label, they also have to outrank `.cform .field > span`, which is
   the rule that styles the field's own caption. */
.cform .field > span.cform__why{
  font-size:12.5px; font-weight:450; color:var(--muted); line-height:1.4;
}
.cform .field > span.cform__err{
  font-size:12.5px; font-weight:500; color:var(--bad); line-height:1.4;
}
/* The invalid state is its own look, not a red tint of the normal one, so it
   survives being glanced at rather than read. */
.cform input[aria-invalid="true"]{
  border-color:var(--bad); box-shadow:0 0 0 3px rgba(192,57,43,.12);
}

/* S33: the mock table cuts off on phones with no affordance on the cut edge */

/* ------------------------------------------------------- FOOTER STACKING --
   The footer is outside <main>, so the "let the liquid through" rule that
   raised every section to z-index 1 never reached it, and the fixed liquid
   layer painted over the whole thing. Anything at the page level that must sit
   above the background needs to say so explicitly. */
.foot{ position:relative; z-index:1; }
.callbar{ position:fixed; z-index:var(--z-nav); }

/* ==========================================================================
   DENSITY, SHAPES AND CHOREOGRAPHY
   2026-07-26. Three notes from Journey in one pass: everything sat too far
   apart, the page needed moving parts and geometry rather than only the liquid,
   and sections needed to arrive rather than just appear.
   ========================================================================== */

/* ------------------------------------------------------------- DENSITY ----
   The band was clamp(64px, 7.5vw, 124px), which put a single idea on a whole
   1920x1080 screen. Journey's stated target is roughly one and a half to two
   things per screen. */
:root{
  --band:clamp(44px, 4.6vw, 80px);
  --s10:32px; --s12:38px; --s16:50px; --s20:62px; --s24:74px;
}
.hero{ padding:clamp(96px,13vh,150px) 0 clamp(36px,5vh,64px); }
.plain__row{ padding:clamp(15px,1.9vw,24px) 0; }
.plain{ margin-top:var(--s8); }
.stmt, .h2{ margin-bottom:var(--s4); }
.ledger__row{ padding:clamp(14px,1.8vw,22px) 0; }
.idx__panels{ gap:clamp(32px,5vw,72px); }
.docnum{ margin-bottom:var(--s4); }
.hero__center .lede{ margin-top:var(--s4); }
.hero__center .hero__cta{ margin-top:var(--s6); }

/* -------------------------------------------------------------- SHAPES ----
   Thin geometric figures drifting behind the content. Monochrome hairlines, so
   they read as drafting geometry rather than as decoration, and they give the
   background structure the liquid alone could not. Transform only. */
.shapes{
  position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden;
  opacity:.5;
}
.shapes i{
  position:absolute; display:block; border:1px solid rgba(17,17,18,.13);
  border-radius:50%; will-change:transform;
}
.shapes i:nth-child(1){ width:52vmin; height:52vmin; left:-14vmin; top:8vh;
  animation:sh1 46s linear infinite; }
.shapes i:nth-child(2){ width:34vmin; height:34vmin; right:-6vmin; top:38vh;
  animation:sh2 61s linear infinite; }
.shapes i:nth-child(3){ width:24vmin; height:24vmin; left:58vw; bottom:6vh;
  border-radius:22%; animation:sh3 53s linear infinite; }
.shapes i:nth-child(4){ width:15vmin; height:15vmin; left:16vw; bottom:22vh;
  border-radius:22%; animation:sh4 39s linear infinite; }
@keyframes sh1{ to{ transform:rotate(360deg) translate3d(2vmin,0,0); } }
@keyframes sh2{ to{ transform:rotate(-360deg) translate3d(-2vmin,0,0); } }
@keyframes sh3{ to{ transform:rotate(360deg); } }
@keyframes sh4{ to{ transform:rotate(-360deg); } }

/* inside a dark band the same figures flip to light hairlines */
.band--dark{ isolation:isolate; }
.band--dark::after{
  content:""; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background-image:
    linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
  background-size:78px 78px;
  mask-image:radial-gradient(90% 80% at 50% 50%, #000 20%, transparent 78%);
  -webkit-mask-image:radial-gradient(90% 80% at 50% 50%, #000 20%, transparent 78%);
  animation:gridDrift 34s linear infinite;
}
@keyframes gridDrift{ to{ transform:translate3d(78px, 78px, 0); } }

/* -------------------------------------------------------- CHOREOGRAPHY ----
   Sections arrive rather than appearing. One curve, one distance, staggered by
   role, so the page has a rhythm without every element doing the same 600ms
   fade. The hero is exempt because it is the LCP element. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .band:not(.hero) .docnum,
    .band:not(.hero) .stmt,
    .band:not(.hero) > .container > .h2,
    .plain__row,
    .ledger__row,
    .versus__col,
    .plan,
    .calc,
    .idx__panel{
      animation:arrive linear both;
      animation-timeline:view();
      animation-range:entry 8% cover 26%;
    }
    /* the staircase steps in sequence rather than as a block */
    .plain__row:nth-child(2){ animation-range:entry 4% cover 24%; }
    .plain__row:nth-child(3){ animation-range:entry 0% cover 22%; }
  }
}
@keyframes arrive{
  from{ opacity:0; transform:translate3d(0,18px,0); }
  to{ opacity:1; transform:none; }
}

@media (prefers-reduced-motion:reduce){
  .shapes{ display:none; }
  .band--dark::after{ animation:none; }
}
/* the shapes are a desktop flourish: on a phone they crowd a narrow column */
@media (max-width:700px){
  .shapes{ opacity:.32; }
  .shapes i:nth-child(3),.shapes i:nth-child(4){ display:none; }
}

/* ========================================================== PHONE PASS =====
   Two real bugs found by driving a genuine touch device rather than a narrow
   desktop window.
   ========================================================================== */

/* 1. The open menu covered its own close button again. --z-nav was 100 and
      --z-menu 900, so the overlay painted over the burger and the only way out
      was tapping a link. This is the same regression written up in the brain's
      field lessons, so it is fixed here at the token rather than on .menu, and
      the rule is: any full screen overlay toggled by a button must sit BELOW
      that button in z-order. */
:root{ --z-nav:950; }
.nav{ z-index:950; }
.menu{ z-index:900; }
.burger{ position:relative; z-index:2; }

/* 2. Two to four pixels of horizontal scroll on four of five pages. The
      measured guard from the responsive study, which also stops any future
      flex or grid child refusing to shrink from reintroducing it. clip rather
      than hidden, so position:sticky keeps working. */
html,body{ overflow-x:clip; }
*{ min-width:0; }
img,video,svg,canvas{ max-width:100%; }
/* the mock table is meant to scroll inside its own box, not the page */

/* -------------------------------------------------------- TOUCH TARGETS --
   The footer column links and the nav phone link were 19px and 24px tall. They
   were missed before because the earlier rule keyed off class names and these
   anchors have none, and off a width breakpoint when the real signal is the
   input device. A mouse can hit 19px; a thumb cannot. */
@media (pointer: coarse){
  .foot__col a,
  .foot__brand,
  .nav__tel{
    display:inline-flex; align-items:center; min-height:44px;
  }
  .foot__col li{ line-height:1; }
}

/* ================================================= THE MOCK ASSEMBLES ======
   The product shot used to appear whole. It now builds the way a real
   dashboard loads. mock.js holds it in the armed state until it scrolls into
   view, then releases it and drives the counting, the drawing and the stagger.
   ========================================================================== */

@keyframes mockSlide{ from{ opacity:0; transform:translate3d(-10px,0,0); } to{ opacity:1; transform:none; } }
@keyframes mockRise { from{ opacity:0; transform:translate3d(0,12px,0); }  to{ opacity:1; transform:none; } }
@keyframes mockPop  { from{ opacity:0; transform:scale(.94); }             to{ opacity:1; transform:none; } }
@keyframes mockRow  { from{ opacity:0; transform:translate3d(0,-7px,0); }  to{ opacity:1; transform:none; } }

/* the window itself lands first, so the chrome is there to build inside */
@keyframes mockFrame{
  from{ opacity:0; transform:translate3d(0,26px,0) scale(.985); }
  to{ opacity:1; transform:none; }
}

/* the live dot only starts pulsing once the thing is assembled */

/* ============================================== MORE BACKGROUND MOVEMENT ===
   The liquid was too polite on the first screen. The masses are bigger and
   travel further, a slow sweep crosses the hero, and the geometry drifts more
   noticeably. All transform and opacity.
   ========================================================================== */
.liquid__goo{ opacity:1; }
.liquid__blob{
  background:radial-gradient(circle at 50% 50%, #CFCFD8 0%, #CFCFD8 54%, rgba(207,207,216,0) 74%);
}
.liquid__blob:nth-child(1){ width:58vmax; height:58vmax; animation-duration:26s; }
.liquid__blob:nth-child(2){ width:46vmax; height:46vmax; animation-duration:31s; }
.liquid__blob:nth-child(3){ width:38vmax; height:38vmax; animation-duration:23s; }
.liquid__blob:nth-child(4){ width:32vmax; height:32vmax; animation-duration:35s; }
.liquid__pane{
  background:linear-gradient(180deg, rgba(251,251,250,.18), rgba(251,251,250,.46));
  backdrop-filter:blur(28px) saturate(0%);
  -webkit-backdrop-filter:blur(28px) saturate(0%);
}

/* a slow light sweep across the first screen, the way light moves over glass */
.hero::after{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  display:block !important;
  background:linear-gradient(104deg, transparent 34%, rgba(255,255,255,.55) 50%, transparent 66%);
  animation:sweepHero 13s ease-in-out infinite;
  will-change:transform;
}
@keyframes sweepHero{
  0%,100%{ transform:translate3d(-42%,0,0); }
  50%{ transform:translate3d(42%,0,0); }
}

.shapes{ opacity:.72; }
.shapes i{ border-color:rgba(17,17,18,.16); }
.shapes i:nth-child(1){ animation-duration:30s; }
.shapes i:nth-child(2){ animation-duration:38s; }
.shapes i:nth-child(3){ animation-duration:33s; }
.shapes i:nth-child(4){ animation-duration:25s; }

@media (prefers-reduced-motion:reduce){
  .hero::after{ animation:none; opacity:0; }
}

/* the explicit confirmation that replaces the form on success */
.cform__done{ display:grid; gap:10px; justify-items:start; padding:var(--s6) 0; }
.cform__done svg{ width:26px; height:26px; color:var(--ink); }
.cform__done h3{ font-size:1.3rem; letter-spacing:-.024em; }
.cform__done p{ color:var(--muted); font-size:.97rem; max-width:44ch; }
.cform__done a{ color:var(--ink); text-decoration:underline; text-underline-offset:2px; }

/* ==========================================================================
   ROUND 3  ·  dock, density, CTA weight, and line movement
   ========================================================================== */

/* -------------------------------------------------------------- THE DOCK --
   Bottom right was out of the reading column but also out of sight, so nobody
   knew where to go. Top centre is the first place the eye lands after the
   headline, and it is thinner so it reads as navigation rather than as a
   floating widget. */
@media (min-width:761px){
  .dock{
    top:18px; bottom:auto; left:50%; right:auto;
    transform:translateX(-50%);
    padding:5px; gap:3px;
    background:rgba(251,251,250,.86);
    border:1px solid var(--line);
    backdrop-filter:saturate(180%) blur(20px);
    -webkit-backdrop-filter:saturate(180%) blur(20px);
    box-shadow:0 1px 2px rgba(17,17,18,.04), 0 10px 28px -18px rgba(17,17,18,.24);
  }
  .dock[data-hide="true"],.dock[data-hide]{ transform:translateX(-50%) !important; }
  .dock a{ width:38px; height:38px; }
  .dock a svg{ width:17px; height:17px; }
  /* the label pops BELOW now, because the dock sits at the top */
  .dock a span{ bottom:auto; top:calc(100% + 10px); }
  .dock a span::after{ top:auto; bottom:100%; transform:translateX(-50%) rotate(180deg); }
  /* the hero has to clear it */
  .hero{ padding-top:clamp(118px,15vh,168px); }
}

/* ------------------------------------------------------------ TALK TO US --
   The primary CTA was a quiet dark pill in a very quiet page. It is the only
   thing on the first screen we want pressed, so it now carries real weight. */
.btn--dark{
  height:56px; padding:0 32px; font-size:16px; font-weight:600;
  box-shadow:0 1px 2px rgba(17,17,18,.16), 0 12px 28px -12px rgba(17,17,18,.45);
}
.btn--dark:hover{ opacity:1; transform:translateY(-1px); box-shadow:0 2px 4px rgba(17,17,18,.18), 0 18px 36px -14px rgba(17,17,18,.5); }
.btn--dark svg{ width:17px; height:17px; }
/* a slow ring so the eye finds it without anything blinking */
.hero__cta .btn--dark{ position:relative; isolation:isolate; }
.hero__cta .btn--dark::after{
  content:""; position:absolute; inset:-5px; border-radius:inherit; z-index:-1;
  border:1px solid rgba(17,17,18,.22);
  animation:ctaRing 3.6s cubic-bezier(.23,1,.32,1) infinite;
}
@keyframes ctaRing{
  0%{ opacity:0; transform:scale(.94); }
  35%{ opacity:.7; }
  100%{ opacity:0; transform:scale(1.09); }
}

/* -------------------------------------------------------------- DENSITY --
   Still too much air between 01 and 02, and between 03 and 04. */
.band{ padding-block:clamp(34px,3.4vw,60px); }
#what, #day{ padding-block:clamp(38px,3.8vw,66px); }
.plain{ margin-top:var(--s6); }
.stmt{ margin-bottom:var(--s3); }

/* ---------------------------------------------------------- LINE FIELD --
   Contour lines drifting behind the page, the way a topographic map reads.
   Two layers at different speeds so it has depth, drawn as SVG so the lines
   stay hairline at any zoom, and translated only. */
.lines{
  position:fixed; inset:0; z-index:0; pointer-events:none; overflow:hidden;
  opacity:.5;
}
.lines svg{ position:absolute; width:190%; height:190%; left:-45%; top:-45%; }
.lines svg path{ fill:none; stroke:rgba(17,17,18,.16); stroke-width:1; vector-effect:non-scaling-stroke; }
.lines__a{ animation:lineDriftA 42s linear infinite; }
.lines__b{ animation:lineDriftB 61s linear infinite; opacity:.6; }
@keyframes lineDriftA{
  0%{ transform:translate3d(0,0,0) rotate(0deg); }
  100%{ transform:translate3d(-3%,2%,0) rotate(360deg); }
}
@keyframes lineDriftB{
  0%{ transform:translate3d(0,0,0) rotate(360deg); }
  100%{ transform:translate3d(3%,-2%,0) rotate(0deg); }
}

@media (prefers-reduced-motion:reduce){
  .lines__a,.lines__b{ animation:none; }
  .hero__cta .btn--dark::after{ animation:none; opacity:0; }
}
@media (max-width:700px){ .lines{ opacity:.34; } }

/* the chart's axis labels live in HTML, not inside the stretched SVG, so they
   keep their true proportions at every card width */

/* ---------------------------------------------------------- STICKY RAIL --
   .idx used align-items:start, which sizes the sticky column to its own
   content (369px) inside a 2,476px section, so there was nothing for it to
   stick within and the rail never pinned. Six panels scrolled past an empty
   column. stretch gives the sticky child a full-height parent to travel in. */
.idx{ align-items:stretch; }
.idx > div:first-child{ position:relative; height:100%; }

/* anchor links landed their heading under the fixed dock */
html{ scroll-padding-top:104px; }

/* ==========================================================================
   AUDIT 2  ·  remaining majors
   ========================================================================== */

/* 23. The CTA inside the pricing plan was near-black on a near-black panel,
   1.042:1. #price is a .band--light containing a dark .plan__side, so the
   .band--dark inversion selector never reached it. Invert on the panel, not
   on the band. */
.plan__side .btn,
.plan__side .btn--gold{ background:#FBFBFA; color:#0B0B0C; }
.plan__side .btn svg{ color:#0B0B0C; }

/* 24. The mock's table was masked with a scroll fade between 621 and 900px,
   where it does not actually scroll, so the VALUE column was faded out for no
   reason. Only fade when there is genuinely something hidden. */

/* 21. Two reveal systems drove the same elements: the scroll-timeline "arrive"
   animation and the older data-rv class. Doubled elements scrubbed and
   reversed on scroll-up while their neighbours stayed put. data-rv yields. */
/* 2026-07-27, REPLACED. The block above described the right problem and then
   picked the wrong cure. Forcing `opacity:1 !important` does not make the two
   systems agree, it switches BOTH of them off: an !important declaration beats
   a CSS animation, so `arrive` never applied to any element that also carried
   data-rv. Measured on the live page, eight elements as far as 3,000px below
   the fold sat at opacity 1 before a single scroll. Journey: "I don't want it
   to play the animation until I scroll down."

   The fix is to let the view timeline win and take data-rv out of the way,
   which is what "data-rv yields" was supposed to mean. The JS reveal only ever
   sets .is-in, and neither that nor the base opacity:0 carries !important, so
   the animation now outranks both on its own. Nothing here needs to force
   anything. */

/* 22. The gooey filter costs nothing on a real GPU (measured 269fps with and
   without) but collapses to about 9fps on software raster, which is what a VM
   or a remote desktop gets. Keep the signature, and let the page swap it for
   the flat wash the pane already declares when the machine cannot afford it. */
html[data-raster="soft"] .liquid__goo{ filter:blur(46px); opacity:.5; }
html[data-raster="soft"] .lines{ display:none; }
html[data-raster="soft"] .shapes i{ animation:none; }


/* ===== novarus-v6.css ===== */
/* ==========================================================================
   ASCENIX  ·  v6  ·  GROWTH MOTION AND THE FOOTER SEAM
   2026-07-27.

   Two notes from Journey, plus the motion findings from the second audit.

   1. "I do want a little more like landscaping and stuff with like growing."
      Everything on the site was either a fade-up or an infinite loop. Nothing
      extended, drew, or grew. That is the difference between motion that reads
      authored and motion that reads sprayed on, and it is the whole reason the
      page kept feeling static to him no matter how much drift was added.

   2. "The footer, I don't like how it just switches from white to black a lot.
      Maybe have either a gradient or something. I need more animation for the
      text. So slide ends, blur ends, gradient ends, glass, more glass."

   Everything here is transform and opacity only, on a scroll-driven timeline
   where possible, so none of it costs a layout pass or a main-thread frame.
   Loads last, after novarus-v5.css.
   ========================================================================== */

/* ==========================================================================
   1. GROWTH, NOT FADE
   ========================================================================== */

/* --------------------------------------------------------------------------
   1.1 THE SECTION RULES DRAW THEMSELVES

   .docnum::after is the hairline that runs from the section number to the
   right edge. It is the site's spine: every numbered band on every page has
   one, so animating it is the cheapest way to make the whole document read as
   something being drawn rather than something being revealed.

   scaleX on a view() timeline is compositor-only. No JS, no observer, and it
   scrubs backwards on scroll-up for free, which an IntersectionObserver reveal
   cannot do.
   -------------------------------------------------------------------------- */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .docnum::after{
      transform-origin:left center;
      animation:ruleGrow linear both;
      animation-timeline:view();
      /* the range is written in cover, not entry, and that is not a style
         choice. The rule is a 1px tall pseudo element, so its entry range is
         effectively zero length: measured, `entry 8% cover 26%` reached
         progress 1 while the rule was still 600px down a 900px viewport, which
         means it finished drawing before it was ever read. In cover units a
         1px element traverses the viewport linearly, so 30% to 62% puts the
         draw between 70% and 38% of the way down the screen, which is where
         the eye actually is when the section arrives. */
      animation-range:cover 30% cover 62%;
    }
    /* the closing band centres its number, so the rule should meet in the
       middle rather than arriving from one side */
    .close .docnum::after,
    .docnum[style*="center"]::after{ transform-origin:center; }
  }
}
@keyframes ruleGrow{ from{ transform:scaleX(0); } to{ transform:scaleX(1); } }

/* --------------------------------------------------------------------------
   1.2 THE SERVICE PANELS GROW A STEM

   The six panels on the services index were six identical fade-ups, which is
   the exact tell the anti-slop gate exists to catch. Each now grows a hairline
   down its left edge as it enters while the panel itself lifts 12px. Two
   properties, one curve, and it reads as something extending downward: the
   "landscaping" note, made literal.
   -------------------------------------------------------------------------- */
.idx__panel{ position:relative; }
.idx__panel::before{
  content:"";
  position:absolute; left:calc(var(--s5) * -1); top:4px; bottom:4px; width:1px;
  background:linear-gradient(to bottom, var(--ink) 0%, var(--line) 78%, transparent 100%);
  transform:scaleY(0); transform-origin:top center;
  opacity:.55;
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .idx__panel::before{
      animation:stemGrow linear both;
      animation-timeline:view();
      animation-range:entry 4% cover 34%;
    }
  }
}
/* without a view timeline the stem would sit at scaleY(0) forever, so it has
   to be visible by default and only animated where the timeline exists */
@supports not (animation-timeline: view()) {
  .idx__panel::before{ transform:none; }
}
@media (prefers-reduced-motion: reduce) {
  .idx__panel::before{ transform:none !important; animation:none !important; }
}
@keyframes stemGrow{ from{ transform:scaleY(0); } to{ transform:scaleY(1); } }

/* the stem needs room to sit outside the text, and on a phone there is none */
@media (max-width: 860px){
  .idx__panel::before{ left:-14px; }
}
@media (max-width: 520px){
  .idx__panel::before{ display:none; }
}

/* --------------------------------------------------------------------------
   1.3 THE HERO ARRIVES, WITHOUT TOUCHING THE LCP NODE

   v5 removed the hero reveal outright because it pushed LCP from 676ms to
   1652-1896ms. That decision stands: the h1 is the LCP element and it is not
   animated here. The three elements around it are not on the LCP path, so
   staggering them costs nothing measurable and gives the first screen the
   entrance it was missing.

   Deliberately not animating font-variation-settings. Weight changes glyph
   advance widths, which reflows the headline every frame, which is the exact
   cost the LCP note was written to avoid.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .hero__center > .eyebrow-pill,
  .hero__center > .lede,
  .hero__center > .hero__cta{
    animation:heroIn 460ms cubic-bezier(.16,1,.3,1) both;
  }
  .hero__center > .eyebrow-pill{ animation-delay:60ms; }
  .hero__center > .lede{ animation-delay:150ms; }
  .hero__center > .hero__cta{ animation-delay:230ms; }

  /* the headline itself gets a wipe on a wrapper, which does not touch the
     text node's own layout or paint and so leaves LCP where it is */
  .hero__center > h1{ animation:heroWipe 620ms cubic-bezier(.16,1,.3,1) both; }
}
@keyframes heroIn{
  from{ opacity:0; transform:translate3d(0,10px,0); }
  to{ opacity:1; transform:none; }
}
@keyframes heroWipe{
  from{ clip-path:inset(0 0 34% 0); }
  to{ clip-path:inset(0 0 -12% 0); }
}

/* --------------------------------------------------------------------------
   1.4 THE LIQUID DRIFTS ON TWO FREQUENCIES

   Each blob was on a single period, so the loop point became visible after
   about twenty seconds and the whole background started reading mechanical.
   Composing a second, faster, smaller oscillation whose period is not an
   integer multiple of the first is how natural motion hides its seams.

   It works because `translate` is its own animatable property in modern CSS:
   the existing drift keyframes keep `transform`, these take `translate`, and
   the two compose on the compositor at no extra cost.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .liquid__blob:nth-child(1){ animation:drift1 34s ease-in-out infinite, swayA 13s ease-in-out infinite; }
  .liquid__blob:nth-child(2){ animation:drift2 41s ease-in-out infinite, swayB 17s ease-in-out infinite; }
  .liquid__blob:nth-child(3){ animation:drift3 29s ease-in-out infinite, swayC 11s ease-in-out infinite; }
  .liquid__blob:nth-child(4){ animation:drift4 47s ease-in-out infinite, swayD 19s ease-in-out infinite; }
}
@keyframes swayA{ 0%,100%{ translate:0 0; } 50%{ translate:2.2vw -1.4vh; } }
@keyframes swayB{ 0%,100%{ translate:0 0; } 50%{ translate:-1.8vw 2vh; } }
@keyframes swayC{ 0%,100%{ translate:0 0; } 50%{ translate:1.5vw 1.9vh; } }
@keyframes swayD{ 0%,100%{ translate:0 0; } 50%{ translate:-2.4vw -1.1vh; } }

/* --------------------------------------------------------------------------
   1.5 PAINT PROPERTIES, ANIMATED FOREVER

   Four infinite keyframes animated box-shadow spread. On a real GPU that is
   free; on the software raster a VM or remote desktop gets, every frame is a
   repaint of the shadow. The correct pattern was already in the codebase at
   v5:805 (ctaRing scales a ::after), so these follow it.
   -------------------------------------------------------------------------- */
@keyframes pulse{
  0%,100%{ transform:scale(1);   opacity:1; }
  50%    { transform:scale(1.5); opacity:.35; }
}
@keyframes pulseDot{
  0%,100%{ transform:scale(1);   opacity:1; }
  50%    { transform:scale(1.6); opacity:.4; }
}
@keyframes uiPulse{
  0%,100%{ transform:scale(1);   opacity:1; }
  50%    { transform:scale(1.55); opacity:.38; }
}

/* ==========================================================================
   2. THE FOOTER SEAM
   ========================================================================== */

/* --------------------------------------------------------------------------
   2.1 ONE COLOUR, ARRIVED AT GRADUALLY

   Journey: "I don't like how it just switches from white to black a lot."

   Three of the five pages close on a dark band and then hard-cut to a light
   footer, so the eye crosses the same edge twice in one screen. The footer is
   now deep ink on every page, which removes one of the two crossings outright,
   and the remaining one is a gradient rather than a line.

   The transition zone is a ::before on the footer, so it does not need a
   wrapper element and it cannot affect the layout of anything above it.
   :has() lets the page ask what colour the last band actually was, which means
   this works on all five pages without per-page markup.
   -------------------------------------------------------------------------- */
/* THE SEAM, THIRD ATTEMPT, and the second one taught the real lesson.

   Attempt two ramped paper to ink and Journey was right that it still did not
   go smoothly into it. Two separate faults, and only one of them was the ramp:

   1. It ramped COLOUR, from a hard coded paper. The page behind the footer is
      never exactly paper, because the liquid layer tints it, so the very first
      stop was already a step change and the top of the gradient read as a
      line. Ramping ALPHA instead cannot produce an edge at any starting
      colour: ink simply fades in over whatever is actually there.

   2. It was 320px long but the content only cleared 150px of it, so the top
      row of footer links sat on mid grey and lost most of its contrast. Worse
      than the hard cut it replaced.

   So: ink fades in on an eased alpha ramp, the ramp starts 140px ABOVE the
   footer so the transition uses space that already exists instead of demanding
   more padding, and at that overlap point the alpha is 0.043, which is
   invisible over the section above. The easing is the same cubic as before,
   which is what keeps the middle from turning into fog. */
.foot{
  background:transparent;
  color:#FFFFFF;
  border-top:0;
  isolation:isolate;
  /* NOT overflow:hidden any more: the ramp deliberately bleeds upward */
}
.foot > .container{ position:relative; z-index:1; }

.foot::before{
  content:"";
  position:absolute; left:0; right:0; top:-140px; bottom:0; z-index:0;
  pointer-events:none;
  background:linear-gradient(to bottom,
    rgba(17,17,18,0)     0px,
    rgba(17,17,18,.001)  40px,
    rgba(17,17,18,.008)  80px,
    rgba(17,17,18,.027) 120px,
    rgba(17,17,18,.064) 160px,
    rgba(17,17,18,.125) 200px,
    rgba(17,17,18,.216) 240px,
    rgba(17,17,18,.343) 280px,
    rgba(17,17,18,.512) 320px,
    rgba(17,17,18,.729) 360px,
    rgba(17,17,18,1)    400px);
}
/* Banding is the other half of "not smooth": eleven stops over 400px is about
   36px per step, and a near flat ramp bands visibly at that size. A very light
   dither breaks the steps up, the same trick the .grain layer already uses on
   the rest of the page. */
.foot::after{
  content:"";
  position:absolute; left:0; right:0; top:-140px; height:420px; z-index:0;
  pointer-events:none; opacity:.5; mix-blend-mode:overlay;
  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='.85' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.05'/%3E%3C/svg%3E");
}
/* WHEN THE PAGE ALREADY CLOSED ON A DARK BAND.

   The first version painted the footer in --ink #111112 and added a faint
   white lift at the join. Both were wrong. The closing band is --void #0B0B0C,
   so ink against it is a 6/255 step, which is small enough to look like a
   rendering artefact and large enough to see, and the lift drew a highlight
   along exactly the line it was meant to hide.

   The right answer for two dark surfaces meeting is that they are the same
   surface: same colour, no seam element at all. The glass panel is what
   separates the footer from the band above it, which is enough. */
main:has(> .band:last-of-type.band--dark) + .foot,
main:has(> .band.close:last-of-type) + .foot{ background:var(--void); }
main:has(> .band:last-of-type.band--dark) + .foot::before,
main:has(> .band.close:last-of-type) + .foot::before,
main:has(> .band:last-of-type.band--dark) + .foot::after,
main:has(> .band.close:last-of-type) + .foot::after{ display:none; }

/* The padding has to clear the part of the ramp that is still light, or the
   first row of links sits on mid grey. The ramp runs 400px from 140px above
   the footer, so it reaches ink 260px into the footer itself, and the glass
   panel starts below that. */
.foot{ padding-block:clamp(200px,20vw,280px) var(--s6); }
main:has(> .band:last-of-type.band--dark) + .foot,
main:has(> .band.close:last-of-type) + .foot{
  /* nothing to clear when both sides are ink */
  padding-block:clamp(96px,10vw,150px) var(--s6);
}

/* --------------------------------------------------------------------------
   2.2 THE FOOTER'S OWN COLOURS

   The base rules pointed at --faint, --ink-2 and --line, all of which are
   tuned for paper. On ink they land somewhere between invisible and muddy.
   -------------------------------------------------------------------------- */
.foot__grid{ border-bottom:1px solid rgba(255,255,255,.13); }
.foot__col h2, .foot__col h4{ color:rgba(255,255,255,.52); }
.foot__col a, .foot__col li{ color:rgba(255,255,255,.78); }
.foot__col a:hover{ color:#FFFFFF; }
.foot__base{ color:rgba(255,255,255,.45); }
.foot__brand svg rect{ fill:#FFFFFF; }
.foot__brand svg path{ stroke:var(--ink); }

/* focus rings were drawn in --ink, which is now the background */
.foot a:focus-visible{ outline:2px solid #FFFFFF; outline-offset:3px; border-radius:3px; }

/* --------------------------------------------------------------------------
   2.3 GLASS AROUND IT

   "more glass around it." A single pane, low contrast, sitting behind the link
   columns so the footer reads as a surface rather than a flat black rectangle.
   Gated on backdrop-filter support: without it the fallback is the plain ink,
   which is the correct degradation.
   -------------------------------------------------------------------------- */
@supports (backdrop-filter: blur(1px)) {
  .foot__grid{
    position:relative;
    padding:clamp(26px,3vw,40px) clamp(20px,2.4vw,34px) var(--s8);
    border-radius:20px;
    background:rgba(255,255,255,.035);
    backdrop-filter:blur(14px) saturate(115%);
    border:1px solid rgba(255,255,255,.09);
    border-bottom:1px solid rgba(255,255,255,.13);
  }
}

/* --------------------------------------------------------------------------
   2.4 THE TEXT ARRIVES

   "I need more animation for the text. So slide ends, blur ends."

   Each column stem-grows the same way the service panels do, and its contents
   slide up out of a blur. Staggered by column, then by row inside the wide
   column, so the footer resolves left to right instead of all at once.
   -------------------------------------------------------------------------- */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .foot__brand,
    .foot__col{
      animation:footIn 620ms cubic-bezier(.16,1,.3,1) both;
      animation-timeline:view();
      animation-range:entry 0% cover 22%;
    }
    .foot__col--wide li{
      animation:footRow 500ms cubic-bezier(.16,1,.3,1) both;
      animation-timeline:view();
      animation-range:entry 4% cover 26%;
    }
    .foot__col--wide li:nth-child(2){ animation-range:entry 6%  cover 27%; }
    .foot__col--wide li:nth-child(3){ animation-range:entry 8%  cover 28%; }
    .foot__col--wide li:nth-child(4){ animation-range:entry 10% cover 29%; }
    .foot__col--wide li:nth-child(5){ animation-range:entry 12% cover 30%; }
    .foot__col--wide li:nth-child(6){ animation-range:entry 14% cover 31%; }
    .foot__base{
      animation:footIn 620ms cubic-bezier(.16,1,.3,1) both;
      animation-timeline:view();
      animation-range:entry 10% cover 34%;
    }
  }
}
@keyframes footIn{
  from{ opacity:0; transform:translate3d(0,16px,0); filter:blur(7px); }
  to  { opacity:1; transform:none;                  filter:blur(0); }
}
@keyframes footRow{
  from{ opacity:0; transform:translate3d(0,8px,0); filter:blur(4px); }
  to  { opacity:1; transform:none;                 filter:blur(0); }
}

/* the phone drops the callbar over the footer, so it keeps its extra padding */
@media (max-width: 720px){
  .foot{ padding-bottom:calc(var(--s16) + 56px); }
  .foot::before{ height:120px; }
}

/* --------------------------------------------------------------------------
   2.5 THE CALLBAR AND DOCK OVER AN INK FOOTER

   Both float above the page and were drawn assuming paper underneath.
   -------------------------------------------------------------------------- */
.foot{ --gold-deep:#FFFFFF; --gold:#FFFFFF; --line:rgba(255,255,255,.13); }

/* ==========================================================================
   3. REDUCED MOTION

   v5 wrote a reduced-motion block that lost to :nth-child specificity and so
   never applied. Everything added above is listed explicitly, and the two
   selectors that need to beat a :nth-child rule carry !important for that
   reason and no other.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .docnum::after{ animation:none; transform:none; }
  .idx__panel::before{ animation:none; transform:none; }
  .hero__center > .eyebrow-pill,
  .hero__center > .lede,
  .hero__center > .hero__cta,
  .hero__center > h1{ animation:none; opacity:1; transform:none; clip-path:none; }
  .liquid__blob:nth-child(1),
  .liquid__blob:nth-child(2),
  .liquid__blob:nth-child(3),
  .liquid__blob:nth-child(4){ animation:none !important; translate:none !important; }
  .foot__brand, .foot__col, .foot__col--wide li, .foot__base{
    animation:none; opacity:1; transform:none; filter:none;
  }
}

/* ==========================================================================
   4. ARRIVING ON A PAGE
   2026-07-27. Journey: "maybe have some intro animations when I switch pages,
   so I switch to services and instead of just being a static page have a
   little bit of business, a little bit of movement."

   Two layers, and they are independent on purpose. The first is the page
   itself introducing its own header, which works in every browser. The second
   is the browser morphing one document into the next, which is newer and is
   pure enhancement: where it is missing you still get the first.
   ========================================================================== */

/* -------------------------------------------------------------------------
   4.1 THE HEADER INTRODUCES ITSELF

   The inner pages all share .phero and all of them were completely static on
   arrival: eyebrow, headline and lede simply existed. Same stagger and same
   curve as the home hero, so moving between them feels like one site rather
   than five, and the rule under the header draws itself so the arrival ends
   with the same growth gesture the rest of the page uses.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .phero__eyebrow{ animation:heroIn 520ms cubic-bezier(.16,1,.3,1) both 40ms; }
  .phero h1     { animation:pheroHead 720ms cubic-bezier(.16,1,.3,1) both 110ms; }
  .phero .lede  { animation:heroIn 560ms cubic-bezier(.16,1,.3,1) both 240ms; }

  /* the page's bottom rule sweeps in from the left under the text */
  .phero{ position:relative; }
  .phero::after{
    content:""; position:absolute; left:0; right:0; bottom:-1px; height:1px;
    background:var(--ink); opacity:.22; transform-origin:left center;
    animation:ruleGrow 900ms cubic-bezier(.16,1,.3,1) both 300ms;
  }
}
@keyframes pheroHead{
  from{ opacity:0; transform:translate3d(0,16px,0); clip-path:inset(0 0 42% 0); }
  to  { opacity:1; transform:none;                  clip-path:inset(0 0 -14% 0); }
}

/* The first content section after the header comes up a beat later, so the
   page resolves top to bottom instead of landing all at once.

   The adjacent selector needs the .cutline hop: every inner page carries a
   leftover `<div class="cutline">` between the header and the first section.
   It is `display:none` in novarus.css:408 and does nothing, but it is still an
   element, so `.phero + .band` matches on none of the four pages. */
@media (prefers-reduced-motion: no-preference) {
  .phero + .band > .container,
  .phero + section > .container,
  .phero + .cutline + .band > .container,
  .phero + .cutline + section > .container{
    animation:heroIn 620ms cubic-bezier(.16,1,.3,1) both 330ms;
  }
}

/* -------------------------------------------------------------------------
   4.2 THE BROWSER MORPHS ONE PAGE INTO THE NEXT

   Cross document view transitions. Two declarations and no JavaScript, no
   router, no framework: the browser holds the old page, fetches the new one
   and cross fades between them, which is what stops a click reading as a
   white flash and a reload.

   Deliberately NOT naming individual elements as shared: a morphing logo or
   nav across a five page marketing site is where this trick starts looking
   like a demo. One clean cross fade with a little vertical travel is the
   version that reads expensive.
   ------------------------------------------------------------------------- */
@view-transition{ navigation: auto; }

::view-transition-old(root){
  animation:vtOut 240ms cubic-bezier(.4,0,1,1) both;
}
::view-transition-new(root){
  animation:vtIn 420ms cubic-bezier(.16,1,.3,1) 60ms both;
}
@keyframes vtOut{
  from{ opacity:1; transform:none; }
  to  { opacity:0; transform:translate3d(0,-14px,0); }
}
@keyframes vtIn{
  from{ opacity:0; transform:translate3d(0,20px,0); }
  to  { opacity:1; transform:none; }
}
@media (prefers-reduced-motion: reduce){
  ::view-transition-old(root),
  ::view-transition-new(root){ animation:none; }
}

/* -------------------------------------------------------------------------
   4.3 THE MOCK WAITS TO BE REACHED

   Journey: "I don't want it to play the animation until I scroll down for the
   dashboard part." Holding only the contents left a visible empty window
   frame at the bottom of the first screen, which is why it used to be allowed
   to play on load instead. Holding the frame too means there is nothing
   half-built on screen at any point: paper, then the whole thing assembling.
   ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce){
  .phero__eyebrow, .phero h1, .phero .lede,
  .phero + .band > .container, .phero + section > .container{
    animation:none; opacity:1; transform:none; clip-path:none;
  }
  .phero::after{ animation:none; transform:none; }
}

/* ==========================================================================
   5. THE HIDDEN STATE HAS TO BE DECLARED, NOT ASSUMED
   2026-07-27.

   This is the piece that was actually wrong, and it is a genuine trap in
   scroll-driven animations.

   `animation: arrive linear both` with `animation-timeline: view()` does NOT
   hold an element at its from-keyframe while it is off screen. Before the
   element enters its range the timeline's progress is unresolved (null), the
   animation does not apply at all, and the element simply renders its own
   declared style. So `both` looks like it is filling backwards and is not.

   Measured on the live home page at scroll 0: six .plain__row elements, all
   carrying the same arrive animation on a real ViewTimeline, all with progress
   null. Three rendered at opacity 0 and three at opacity 1. The only
   difference was that the first three also carried data-rv, whose base rule
   declares opacity 0, and the other three had nothing to fall back to. Half
   the page's reveals were working by accident, on a rule written for a
   different system.

   So the from-state is declared here explicitly. Gated on @supports, because
   a browser without view timelines must never be handed an element that is
   hidden with nothing to reveal it.
   ========================================================================== */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .band:not(.hero) .docnum,
    .band:not(.hero) .stmt,
    .band:not(.hero) > .container > .h2,
    .plain__row,
    .ledger__row,
    .versus__col,
    .plan,
    .calc,
    .idx__panel{
      opacity:0;
      transform:translate3d(0,18px,0);
    }

    /* The header of an inner page is above the fold by definition, so it must
       not be caught by the rule above: it runs on a time based animation with
       its own from-state and would be double hidden. */
    .phero .stmt,
    .phero .h2{ opacity:1; transform:none; }
  }
}

/* ==========================================================================
   6. A UNIFORM BACKGROUND
   2026-07-27. Journey: "There's a line where the dashboard is sometimes, like
   when the gradient's going across, there's like lines that show up, and I
   don't really like that, it needs to be uniform all the way throughout."

   Two layers were fighting the liquid, and neither is visible on the machine I
   test on, which is why they survived this long: the raster probe sets
   data-raster="soft" here and the soft-raster fallback already hides them. On
   a real GPU they both render.

   1. `.lines` is 33 contour paths at rgba(17,17,18,.16), fixed to the
      viewport, rotating on two timers. Hairlines that cross the whole page,
      including straight over the dashboard, and they read as artefacts rather
      than as landscape because they never relate to anything on the page.

   2. `.hero::after` sweeps a 55% white band across the first screen every 13
      seconds. On a light page that band has visible boundaries, and where it
      crossed the contour lines it lit them up. That is the "gradient going
      across" making "lines show up": the two together.

   The liquid stays. It is the movement that has no edges: four soft masses
   under a frosted pane, nothing in it can ever draw a line. That is what
   uniform means here, and it is still moving.
   ========================================================================== */
.lines{ display:none !important; }
.hero::after{ display:none !important; }

/* `.shapes` deliberately stays. Journey asked for it directly ("it needs to
   have a tiny bit more modernism with shapes etc.") and it is soft geometry
   rather than hairlines, so it is not what he is pointing at here. If it turns
   out to be, it goes the same way as .lines. */

/* ==========================================================================
   7. THE HERO MOVES WITH THE SCROLL
   Journey: "when you scroll down the dashboard comes up and the text moves up
   a little bit, it should have movement like that."

   The dashboard already rises because it assembles on arrival. The hero was
   completely fixed: it just scrolled off at exactly the speed of the page. Now
   the text drifts up faster than the scroll and softens as it goes, so the two
   move at different rates and the first screen has depth instead of being one
   flat sheet sliding away.

   On a view() timeline, so it is compositor only and scrubs both ways.
   ========================================================================== */
/* This uses scroll(root), not view(), and that is not interchangeable here.

   `view()` resolves against the nearest ancestor SCROLL CONTAINER, and
   `overflow:hidden` establishes one even though it never scrolls. `.hero`
   carries overflow:hidden, so a view() timeline on anything inside it is
   measured against the hero box rather than the page and is effectively
   frozen. Measured: with `.hero__center` sitting entirely above the viewport
   at -362 to -7, fully exited by any reading, the animation reported
   currentTime -14.9% and progress 0, so nothing moved.

   scroll(root block) names the document scroller explicitly and cannot be
   captured by an ancestor. The range is in absolute pixels because the hero is
   571px tall, so 520px of scroll is the whole of its departure. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero__center{
      animation:heroPark linear both;
      animation-timeline:scroll(root block);
      animation-range:0px 520px;
    }
  }
}
@keyframes heroPark{
  from{ transform:translate3d(0,0,0);      opacity:1; }
  to  { transform:translate3d(0,-90px,0);  opacity:.25; }
}

/* ==========================================================================
   8. THE HERO PARAGRAPH, TIGHTER AND MORE CENTRED
   "I want it to also have a shorter paragraph... and I want you to center it a
   little more."

   The copy is shorter in the markup. This narrows the measure so the two lines
   sit as a tight block under the headline rather than spanning nearly the full
   width of the headline above it, which is what stopped it reading as centred
   even though it always was.
   ========================================================================== */
.hero__center .lede--tight{
  max-width:38ch;
  margin-inline:auto;
  text-wrap:balance;
}
@media (max-width:640px){
  .hero__center .lede--tight{ max-width:30ch; }
}

@media (prefers-reduced-motion: reduce){
  .hero__center{ animation:none; transform:none; opacity:1; }
}

/* ==========================================================================
   9. THE FIRST SCREEN
   2026-07-27. Journey: two buttons instead of one, the headline properly
   centred, the hamburger smaller, and fewer words if they are not earning
   their place.
   ========================================================================== */

/* ---- 9.1 the second button ----
   The hero had one call-to-action and it was a phone number, which asks for
   the largest possible commitment from someone who has read two sentences.
   The second is the low commitment path to the same place: the price. */
.btn--line{
  background:transparent; color:var(--ink);
  box-shadow:inset 0 0 0 1px color-mix(in oklab, var(--ink) 22%, transparent);
  transition:box-shadow var(--t) var(--ease), background var(--t) var(--ease),
             transform 140ms var(--ease);
}
.btn--line:hover{
  background:color-mix(in oklab, var(--ink) 4%, transparent);
  box-shadow:inset 0 0 0 1px color-mix(in oklab, var(--ink) 40%, transparent);
}
.btn--line:active{ transform:scale(.98); }
.btn--line svg{ transition:transform 260ms var(--ease); }
.btn--line:hover svg{ transform:translateX(3px); }

.hero__cta{
  display:flex; align-items:center; justify-content:center;
  gap:var(--s3); flex-wrap:wrap;
}
/* on a narrow phone they stack full width rather than wrapping into a ragged
   two-and-a-bit line */
@media (max-width:520px){
  .hero__cta{ flex-direction:column; align-items:stretch; }
  .hero__cta .btn{ justify-content:center; }
}

/* ---- 9.2 actually centred ----
   .hero is min-height:100svh with align-items:center, so it looks like it
   centres, but the top and bottom padding are different (150 top against 64
   bottom in v5) and asymmetric padding inside a centring flex container pushes
   the content down by half the difference. Matching them is what makes the
   headline sit on the middle of the screen. */
.hero{
  /* novarus.css asked for min-height:100svh and novarus-v4.css:87 overrode it
     to auto, so the hero was only as tall as its own content: 603px inside an
     805px viewport. Centring inside a box shorter than the screen cannot put
     anything on the middle of the screen, which is why the headline measured
     117px high no matter what the padding did. The full height comes back, and
     the symmetric padding then actually means something. */
  min-height:100svh;
  padding-top:clamp(88px, 11vh, 128px);
  padding-bottom:clamp(88px, 11vh, 128px);
}
/* a short landscape phone should scroll rather than crush the type */
@media (max-height:520px){
  .hero{ min-height:auto; }
}

/* ---- 9.3 the hamburger ----
   "the three lines need to be way smaller because they look kind of stupid."
   It was a 42px pill holding 17px bars, which reads as a big empty button with
   a small mark rattling around inside it. The bars now nearly fill it and the
   whole control is tighter, while the hit area stays at 44px through padding
   rather than through a visible box. */
@media (max-width:900px){
  .burger{
    width:34px; height:34px;
    padding:0; border:0; background:none;
  }
  .burger i{ width:15px; height:1.4px; }
  .burger i + i{ margin-top:3.5px; }
  .burger[aria-expanded="true"] i:nth-child(1){ transform:translateY(4.9px) rotate(45deg); }
  .burger[aria-expanded="true"] i:nth-child(3){ transform:translateY(-4.9px) rotate(-45deg); }
  /* the tap target stays 44px without the button looking 44px */
  .burger::after{
    content:""; position:absolute; inset:-5px; border-radius:50%;
  }
  .burger{ position:relative; }
}

/* ---- 9.4 the price is not decoration ----
   `.plain__tag { display:none }` under 700px hid every price on the services
   strip on every phone. Not shrunk: gone. Price is the most scanned thing on
   the page and most of this audience is on a phone, so it now sits under the
   service name where it can be read. */
@media (max-width:700px){
  .plain__tag{
    display:block !important;
    grid-column:2; margin-top:6px;
    font-size:12px; letter-spacing:.02em; text-transform:none;
    color:var(--ink-2); white-space:normal;
  }
}

/* ---- 9.5 the dashboard floats up as you reach it ----
   "when you start scrolling down the dashboard comes up and floats in an
   animation type thing."

   It already assembled itself on arrival. What it did not do was move: it sat
   at a fixed point in the document while the page slid past it. Now the whole
   window rises and settles as you scroll toward it, so the assembly happens on
   something that is arriving rather than on something that was already there.

   scroll(root block), not view(), for the same reason as the hero: the section
   above carries overflow:hidden and view() would resolve against that box
   instead of the page. Range in absolute pixels because the mock sits at a
   known depth. */
@keyframes mockFloat{
  from{ transform:translate3d(0, 70px, 0) scale(.965); }
  to  { transform:translate3d(0, 0, 0)    scale(1); }
}
/* the hold state has to keep winning while the mock is waiting to be reached,
   or the float would reveal a window that has not built itself yet */
