/* =====================================================================
   markdalton.com — immersive redesign, built by Vega, 2026-08-18.
   Hand-written, no build step. Self-hosted Three.js + Inter Variable
   are the only vendored files; everything else is this CSS + vanilla JS.

   DESIGN LANGUAGE, taken from the VEXON reference video (not copied):
   one motion primitive — blur-resolve — applied everywhere something
   arrives. Words ahead of the reading position sit in heavy horizontal
   defocus and pull into sharpness. The particle field is an atmospheric
   mass in the upper frame dissolving to void, not a centred sphere.
   Corner-bracket cards, tiny wide-tracked mono labels, ~90% black/grey,
   restrained icy-blue accent. See js/reveal.js for the shared primitive.
   ===================================================================== */
@font-face{
  font-family:"InterVar";
  src:url("vendor/inter-variable.woff2") format("woff2-variations");
  font-weight:100 900;
  font-style:normal;
  font-display:swap;
}
:root{
  color-scheme: dark;
  --black:      #000000;
  --bg-2:       #050509;
  --bg-3:       #08090d;
  --ink:        #ffffff;
  --ink-dim:    #9aa0a8;
  --ink-faint:  #5c6169;
  --ink-ghost:  #34373c;
  --line:       rgba(255,255,255,.09);
  --line-bright:rgba(255,255,255,.18);
  --accent:     #8fd0e8;
  --accent-dim: #4d7688;
  --glass-deep: #10233f;
  --glass-mid:  #2c5c86;
  /* Added 2026-08-18 for the subscribe form's notice states -- nothing on
     this page needed a success/error colour before that form existed.
     Desaturated on purpose so they sit inside the near-monochrome palette
     rather than reading as an imported look. */
  --ok:         #8fe0b0;
  --warn:       #e0a480;
  --sans: "InterVar", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace;
  --navh: 64px;
  --ease: cubic-bezier(.22,.7,.2,1);
}
*{box-sizing:border-box}
html,body{margin:0;padding:0}
html{background:var(--black)}
body{
  background:var(--black);color:var(--ink-dim);
  font:400 16px/1.6 var(--sans);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  overflow-x:clip;
}
a{color:var(--accent)}
::selection{background:rgba(143,208,232,.28);color:#fff}
img,canvas,svg{display:block;max-width:100%}
h1,h2,h3,p{margin:0}
@media (prefers-reduced-motion:no-preference){ html{scroll-behavior:smooth} }
a:focus-visible,button:focus-visible{outline:2px solid #fff;outline-offset:3px}
.skip{position:absolute;left:-9999px;top:0;background:#fff;color:#000;
  padding:10px 16px;z-index:100;font:700 13px var(--sans)}
.skip:focus{left:12px;top:12px}

/* ---------- micro-label primitive, used everywhere ---------- */
.mlabel{font:600 11px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;
  color:var(--ink-faint)}
.mlabel b,.mlabel .lit{color:var(--accent);font-weight:600}
.bracket{position:relative}
.bracket::before,.bracket::after{content:"";position:absolute;width:9px;height:9px;
  border-color:var(--line-bright);border-style:solid;pointer-events:none}
.bracket::before{top:-1px;left:-1px;border-width:1px 0 0 1px}
.bracket::after{bottom:-1px;right:-1px;border-width:0 1px 1px 0}

/* ---------- nav ----------
   RENAMED 2026-08-19, Wren, to the site-wide shared nav's own class names
   (.brand/.nav-links/.tt/.tt-cta/.nav-disclosure/.disclosure-links) as part
   of unifying business.html's markup with the other 27 pages — see the
   comment on this page's own <nav class="topbar"> for the full story. These
   selectors used to read .word/.nav-desktop/"nav a"; the declarations are
   unchanged (still this page's own tokens, still this page's own numbers —
   the 27 pages' nav CSS was itself ported FROM these), only the selectors
   moved to match markup that is now identical across every page. */
.topbar{position:fixed;top:0;left:0;right:0;z-index:30;height:var(--navh);
  display:flex;align-items:center;justify-content:space-between;
  padding:0 clamp(16px,4vw,40px);
  background:rgba(0,0,0,.62);backdrop-filter:blur(14px) saturate(1.1);
  border-bottom:1px solid var(--line)}
.topbar .brand{font:700 14px/1 var(--sans);letter-spacing:.01em;color:#fff;
  text-decoration:none;display:flex;align-items:baseline;gap:8px}
.topbar .brand b{font:inherit;text-transform:uppercase}
.topbar .brand .v{font:600 9px/1 var(--mono);letter-spacing:.12em;color:var(--ink-faint)}
.topbar .nav-links{display:flex;align-items:center;gap:26px}
.topbar .tt{color:var(--ink-dim);text-decoration:none;font:500 13px/1 var(--sans);
  letter-spacing:.01em}
.topbar .tt:hover{color:#fff}
.topbar .tt[aria-current="page"]{color:#fff}
/* THE CTA — same corner-bracket white button as .btn.primary further down
   this file (see the .btn block), compressed to nav sizing rather than
   re-derived, so "Let's talk" here and the hero's "Build your AI" read as
   the same control language. */
.topbar .tt.tt-cta{position:relative;background:#fff;color:#000;border:1px solid #fff;
  padding:11px 18px;font:600 11.5px/1 var(--mono);letter-spacing:.1em;
  text-transform:uppercase}
.topbar .tt.tt-cta:hover,.topbar .tt.tt-cta:focus-visible,
.topbar .tt.tt-cta[aria-current="page"]{background:var(--accent);border-color:var(--accent);color:#000}
.topbar .tt.tt-cta::before,.topbar .tt.tt-cta::after{content:"";position:absolute;
  width:6px;height:6px;border-color:#000;border-style:solid;opacity:.55;pointer-events:none}
.topbar .tt.tt-cta::before{top:-1px;left:-1px;border-width:1px 0 0 1px}
.topbar .tt.tt-cta::after{bottom:-1px;right:-1px;border-width:0 1px 1px 0}
@media(max-width:620px){ .topbar .tt.tt-cta{padding:9px 14px} }
/* NOW A LOGIN CONTROL -- 2026-08-19, Mark: "change 'AI SYSTEM' in the top
   right to Login buttong".

   WHAT WAS THERE. A .status div reading "AI SYSTEM". It had been a pulsing
   "AI SYSTEM // ONLINE" light until 2026-08-18, when Wren cut the dot and the
   claim because a static file on a CDN cannot know whether anything is online;
   what survived was pure typographic signature that said nothing and did
   nothing. Replacing it with a real control is strictly better -- the far right
   of a top bar is where a returning person looks for the way in, and it was
   spending that slot on decoration.

   SECONDARY ON PURPOSE, and this is the one judgement in it. "Let's talk" is
   the page's conversion and is solid white; a second filled button beside it
   splits the ask in two. So this is the ghost variant of the same control
   language -- identical type, spacing and corner marks, outline instead of
   fill -- which reads as utility rather than as a competing call to action. */
.topbar .tt.tt-login{position:relative;background:transparent;color:var(--ink);
  border:1px solid var(--line-bright);
  padding:11px 18px;font:600 11.5px/1 var(--mono);letter-spacing:.1em;
  text-transform:uppercase}
.topbar .tt.tt-login:hover,.topbar .tt.tt-login:focus-visible{
  border-color:var(--accent);color:var(--accent)}
.topbar .tt.tt-login::before,.topbar .tt.tt-login::after{content:"";
  position:absolute;width:6px;height:6px;border-color:currentColor;
  border-style:solid;opacity:.55;pointer-events:none}
.topbar .tt.tt-login::before{top:-1px;left:-1px;border-width:1px 0 0 1px}
.topbar .tt.tt-login::after{bottom:-1px;right:-1px;border-width:0 1px 1px 0}
@media(max-width:620px){ .topbar .tt.tt-login{padding:9px 14px} }

/* ---------- mobile nav disclosure ----------
   .nav-links is the plain nav, unchanged, shown inline above 860px.
   .nav-disclosure is a SECOND copy of the same links inside a <details>,
   shown only at 860px and below -- see business.html's comment at
   .nav-disclosure for the full reasoning: closed <details> content renders
   inconsistently across the two engines checked on this box when CSS tries
   to force it visible (reliably blocked in Firefox, order-dependent in
   Chromium), so this relies only on <details>'s own native open/closed
   toggle -- which means the nav that needs no tap on desktop has to be a
   separate element, not the same one forced open. The breakpoint below
   makes sure only one copy is ever on screen, or in the accessibility tree,
   at a time. */
.nav-disclosure{display:none}
@media(max-width:860px){
  /* .topbar .nav-links{display:flex} above (two classes, 0-2-0) otherwise
     beats a bare .nav-links selector on its own regardless of source order
     -- confirmed by rendering: without the extra .topbar here the desktop
     nav stayed on screen, wrapped, and sat on top of the hamburger button
     instead of being replaced by it. Matching .topbar's own specificity on
     the way in is what makes this override reliable rather than a
     coincidence of file order. */
  .topbar .nav-links{display:none}
  .nav-disclosure{display:block}
  .nav-disclosure summary{list-style:none;cursor:pointer;
    display:flex;align-items:center;justify-content:center;
    width:44px;height:44px;border:1px solid var(--line-bright);
    border-radius:4px;-webkit-tap-highlight-color:transparent}
  .nav-disclosure summary::-webkit-details-marker{display:none}
  .nav-disclosure summary:focus-visible{outline:2px solid #fff;outline-offset:3px}
  /* Three-bar icon, drawn with one element and its two pseudo-elements
     rather than an SVG -- this build has no icon set on purpose (see the
     no-assets rule this site is built under) and three CSS bars are already
     exact and legible at 18px. */
  .nav-disclosure .bars,.nav-disclosure .bars::before,.nav-disclosure .bars::after{
    content:"";display:block;width:16px;height:1.5px;background:#fff;border-radius:1px}
  .nav-disclosure .bars{position:relative}
  .nav-disclosure .bars::before{position:absolute;left:0;top:-5px}
  .nav-disclosure .bars::after{position:absolute;left:0;top:5px}
  /* Open state turns the three bars into a close mark. Pure CSS off the
     [open] attribute <summary> already toggles natively -- no JS reads or
     sets it. */
  .nav-disclosure[open] summary .bars{background:transparent}
  .nav-disclosure[open] summary .bars::before{top:0;transform:rotate(45deg)}
  .nav-disclosure[open] summary .bars::after{top:0;transform:rotate(-45deg)}
  @media (prefers-reduced-motion:no-preference){
    .nav-disclosure summary .bars::before,.nav-disclosure summary .bars::after{
      transition:transform .16s var(--ease),top .16s var(--ease)}
  }
  /* The open panel itself. Anchored under the bar rather than pushing page
     content down, so opening the menu never moves the hero underneath it.
     .topbar prefixed for the same specificity reason as .nav-links above
     -- .topbar .nav-links{display:flex} is two classes either way. */
  .topbar .nav-disclosure .disclosure-links{display:flex;flex-direction:column;
    position:absolute;top:100%;left:0;right:0;
    background:rgba(0,0,0,.94);backdrop-filter:blur(14px) saturate(1.1);
    border-bottom:1px solid var(--line);
    padding:4px clamp(16px,4vw,40px) 10px}
  .nav-disclosure .disclosure-links .tt{padding:14px 2px;min-height:44px;display:flex;
    align-items:center;border-bottom:1px solid var(--line);color:var(--ink-dim);
    text-decoration:none;font:500 15px/1 var(--sans)}
  .nav-disclosure .disclosure-links .tt:last-child{border-bottom:0}
  .nav-disclosure .disclosure-links .tt:hover,
  .nav-disclosure .disclosure-links .tt:focus-visible{color:#fff}
}

/* ---------- hero ----------
   REMOVED 2026-08-18, Mason: the boot overlay (a ~1.6s particle-sphere
   splash, #boot/#bootCanvas) and the WebGL particle field that used to
   fill this section (#heroCanvas, js/hero.js) — Mark's instruction was to
   ship the immersive redesign "without the immersive part of the top of
   the site." Everything else on the page, including the cards' own WebGL
   glass objects further down, is untouched.
   FIRST PASS left only this gradient up there and Jarvis caught it, the
   same day: rendered at 1834x1100, found ~600px of dead near-black between
   the nav and the headline, and named the cause precisely — this gradient
   was authored as a WebGL-FAILURE DEGRADE ("is this broken?") and was now
   being asked to pass as the design ("is this good?"), which it does not.
   His diagnosis: the bottom-anchored headline only reads as deliberate
   composition when something occupies the space above it; empty, the same
   layout reads as a page that failed to load.
   Widened and brightened slightly from the original fallback values (was
   60%/50% at 50% 20%, #0b1220->#000 70%) so it carries more of the frame
   on its own now that it is load-bearing rather than a rare degrade. */
.hero{position:relative;height:100svh;min-height:560px;overflow:hidden;
  background:radial-gradient(75% 65% at 50% 15%, #0d1626 0%, #000 72%)}
/* THE FIX: a static particle field, assets/home/hero-field.svg, standing in
   for the deleted WebGL one — Mark's own suggestion ("a static particle
   field rendered once as an asset") over the other two options (shrinking
   the hero, or opening cold on the headline with no upper texture), because
   it keeps the exact composition he already approved rather than replacing
   it. Generated once by a throwaway script (mulberry32, seed 2026, 760
   points, /tmp/mason-gen-hero-field.py — not kept in this repo, same as the
   Inter woff2 not being a build step's output either), against the density
   profile hero.js's own comment already documented: mass banked toward the
   top, thinning to near-nothing by the bottom third, full-bleed with a
   soft edge falloff. object-fit:cover + object-position:top so it crops
   from the bottom first on a short/wide viewport, keeping the part of the
   field that actually carries the composition on screen.
   mix-blend-mode:screen lets the brighter points glow against the radial
   gradient underneath rather than sitting on it as flat opaque dots —
   cheap, and it is the one thing here that gets close to "glow" without
   raising a WebGL context. */
.hero-field{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:top center;mix-blend-mode:screen;
  pointer-events:none;transition:opacity 1.1s ease}
/* ============ HALO DROP-IN, added 2026-08-18, Vega/Mason ============
   .hero-field above is the fallback and is UNCHANGED except for the
   transition property just added, so removing .has-halo (a context-loss
   recovery, see halo.js's onContextLost) fades the static field back in at
   the same speed it faded out rather than snapping. .halo-mount is a
   sibling layer that starts invisible; halo-init.js adds .has-halo to
   .hero ONLY after a real WebGL first frame is confirmed drawn. Until that
   happens -- no WebGL, reduced motion, a slow/failed mount, or a context
   loss later in the session -- the page looks exactly like it does today.
   No @media(prefers-reduced-motion) override needed: halo-init.js never
   adds .has-halo when REDUCED is true, so this state is simply never
   reached under reduced motion. */
.halo-mount{position:absolute;inset:0;width:100%;height:100%;
  opacity:0;pointer-events:none;transition:opacity 1.1s ease}
.halo-mount canvas{display:block;width:100%;height:100%}
.hero.has-halo .hero-field{opacity:0}
.hero.has-halo .halo-mount{opacity:1}
.hero .veil{position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(to bottom, rgba(0,0,0,.05) 0%, rgba(0,0,0,0) 30%,
    rgba(0,0,0,.35) 78%, rgba(0,0,0,.86) 100%)}
.hero-copy{position:relative;z-index:2;height:100%;display:flex;flex-direction:column;
  justify-content:flex-end;max-width:1320px;margin:0 auto;
  padding:0 clamp(18px,4vw,48px) clamp(64px,9vw,96px)}
.hero-copy .eyebrow{margin:0 0 20px}
.hero-copy h1{font:700 clamp(38px,7vw,92px)/.98 var(--sans);letter-spacing:-.03em;
  color:var(--ink);max-width:15ch}
.hero-copy h1 .dim{color:#5c626b}
.hero-copy h1 .spark{display:inline-block;color:var(--accent);font-weight:400;
  font-size:.55em;vertical-align:.18em;margin:0 .05em}
.hero-copy .grid{display:flex;justify-content:space-between;align-items:flex-end;
  gap:32px;margin-top:34px;flex-wrap:wrap}
.hero-copy .sub{max-width:58ch;font-size:clamp(14.5px,1.3vw,16.5px);line-height:1.6;
  color:var(--ink-dim)}
.hero-copy .ctas{display:flex;gap:14px;flex-wrap:wrap;margin-top:26px}
.btn{display:inline-flex;align-items:center;gap:8px;font:600 12.5px/1 var(--mono);
  letter-spacing:.1em;text-transform:uppercase;text-decoration:none;padding:15px 22px;
  color:#fff;border:1px solid var(--line-bright);position:relative}
.btn.primary{background:#fff;color:#000;border-color:#fff}
.btn.primary:hover,.btn.primary:focus-visible{background:var(--accent);border-color:var(--accent)}
.btn.ghost{background:transparent}
.btn.ghost:hover,.btn.ghost:focus-visible{border-color:#fff;color:#fff}
.btn::before,.btn::after{content:"";position:absolute;width:7px;height:7px;
  border-color:currentColor;border-style:solid;opacity:.55}
.btn::before{top:-1px;left:-1px;border-width:1px 0 0 1px}
.btn::after{bottom:-1px;right:-1px;border-width:0 1px 1px 0}
.textlink{color:var(--ink-dim);text-decoration:none;font:600 13px/1 var(--sans);
  border-bottom:1px solid var(--line-bright);padding-bottom:3px;align-self:center}
.textlink:hover{color:#fff;border-color:#fff}
@media (prefers-reduced-motion:no-preference){ .btn{transition:background .18s,border-color .18s,color .18s} }

.scrollcue{position:absolute;right:clamp(18px,4vw,48px);bottom:clamp(22px,3vw,30px);
  z-index:2;display:flex;align-items:center;gap:9px;color:var(--ink-faint);
  text-decoration:none}
.scrollcue .ln{width:1px;height:34px;background:linear-gradient(to bottom,var(--ink-faint),transparent)}
@media (prefers-reduced-motion:no-preference){
  .scrollcue .ln{animation:cueline 2.2s ease-in-out infinite}
}
@keyframes cueline{0%{transform:scaleY(.4);transform-origin:top}50%{transform:scaleY(1);transform-origin:top}100%{transform:scaleY(.4);transform-origin:top}}
@media(max-width:640px){ .scrollcue{display:none} }

/* ---------- section shell ---------- */
.section{position:relative;padding:clamp(90px,13vw,150px) clamp(18px,4vw,48px);
  max-width:1320px;margin:0 auto}
.section-head{margin-bottom:clamp(40px,6vw,64px)}
.section-head .mlabel{margin-bottom:16px;display:block}
.section-head h2{font:700 clamp(26px,4.4vw,54px)/1.08 var(--sans);letter-spacing:-.025em;
  color:var(--ink);max-width:20ch}
.section-head h2 .dim{color:#565b62}
.section-index{position:absolute;top:clamp(90px,13vw,150px);right:clamp(18px,4vw,48px);
  font:600 11px/1 var(--mono);letter-spacing:.14em;color:var(--ink-ghost)}
@media(max-width:700px){ .section-index{display:none} }
hr.rule{border:0;border-top:1px solid var(--line);margin:0}

/* =====================================================================
   Section 2 — AI IS BECOMING PERSONAL: DEPLOY -> EVOLVE sequence
===================================================================== */
.seq{display:flex;flex-wrap:wrap;gap:0;border-top:1px solid var(--line)}
.seq .step{flex:1 1 180px;padding:26px 18px 22px 0;border-bottom:1px solid var(--line);
  position:relative}
.seq .step .n{font:600 11px/1 var(--mono);letter-spacing:.14em;color:var(--ink-ghost)}
.seq .step h3{margin-top:14px;font:700 17px/1.2 var(--sans);letter-spacing:-.01em;
  color:var(--ink-ghost);transition:color .5s var(--ease)}
.seq .step p{margin-top:8px;font-size:13px;line-height:1.55;color:var(--ink-ghost);
  max-width:26ch;transition:color .5s var(--ease)}
.seq .step.on h3{color:#fff}
.seq .step.on p{color:var(--ink-dim)}
.seq .step .bar{position:absolute;left:0;right:18px;bottom:-1px;height:2px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;
  transition:transform .6s var(--ease)}
.seq .step.on .bar{transform:scaleX(1)}
@media (prefers-reduced-motion:reduce){
  .seq .step h3,.seq .step p{color:#fff !important}
  .seq .step p{color:var(--ink-dim) !important}
  .seq .step .bar{transform:scaleX(1)}
}

/* =====================================================================
   Section 3 — WHAT I BUILD: three cards with Three.js glass objects
===================================================================== */
.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}
@media(max-width:900px){ .cards{grid-template-columns:1fr} }
.card{position:relative;border:1px solid var(--line);padding:20px 20px 26px;
  background:linear-gradient(180deg, rgba(255,255,255,.02), rgba(255,255,255,0) 40%);
  transition:border-color .3s,background .3s}
.card:hover{border-color:var(--line-bright)}
.card .idx{font:600 11px/1 var(--mono);letter-spacing:.14em;color:var(--ink-ghost)}
.card .stage{position:relative;height:200px;margin:16px 0 4px}
.card .stage canvas{width:100%;height:100%}
.card .pill{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) translateY(6px);
  font:600 11px/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;
  background:rgba(0,0,0,.72);border:1px solid var(--line-bright);color:#fff;
  padding:9px 16px;opacity:0;transition:opacity .25s var(--ease),transform .25s var(--ease);
  pointer-events:none;white-space:nowrap}
.card:hover .pill{opacity:1;transform:translate(-50%,-50%) translateY(0)}
.card h3{font:700 18px/1.25 var(--sans);letter-spacing:-.01em;color:#fff;margin-top:6px}
.card p{margin-top:9px;font-size:13.5px;line-height:1.6;color:var(--ink-dim);max-width:32ch}

/* =====================================================================
   Section 4 — THE DIFFERENCE
===================================================================== */
.difference h2{font:700 clamp(28px,5.4vw,64px)/1.12 var(--sans);letter-spacing:-.03em;
  color:#fff;max-width:16ch}
.difference h2 .grey{color:#565b62}
.diffwords{display:flex;flex-wrap:wrap;gap:12px 12px;margin-top:clamp(44px,6vw,64px)}
.diffwords span{font:600 12.5px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-faint);border:1px solid var(--line);padding:11px 16px}

/* =====================================================================
   Section 5 — YOUR AI. DEPLOYED. — system diagram
===================================================================== */
.diagram{margin-top:8px}
.diagram svg{width:100%;height:auto;overflow:visible}
.diagram .node-label{font:600 10.5px var(--mono);letter-spacing:.08em;text-transform:uppercase;
  fill:var(--ink-dim)}
.diagram .node-label.strong{fill:#fff}

/* =====================================================================
   Section 6 — WHAT THE DEPLOYMENT CAN DO — module grid
===================================================================== */
.modules{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line);
  border:1px solid var(--line)}
@media(max-width:820px){ .modules{grid-template-columns:1fr 1fr} }
@media(max-width:520px){ .modules{grid-template-columns:1fr} }
.module{background:#000;padding:24px 20px 26px;display:flex;flex-direction:column;gap:14px}
.module canvas{width:56px;height:56px}
.module h3{font:700 15.5px/1.3 var(--sans);color:#fff;letter-spacing:-.005em}
.module p{font-size:12.5px;line-height:1.55;color:var(--ink-faint);max-width:30ch}

/* =====================================================================
   Section 7 — HOW IT WORKS — init sequence terminal
===================================================================== */
.initseq{border:1px solid var(--line);background:var(--bg-2);padding:clamp(20px,3vw,32px)}
.initseq .line{display:flex;gap:16px;padding:14px 0;border-bottom:1px solid var(--line);
  align-items:baseline}
.initseq .line:last-child{border-bottom:0}
.initseq .tag{font:600 12px/1 var(--mono);letter-spacing:.1em;color:var(--ink-ghost);
  flex:0 0 auto;min-width:118px}
.initseq .line.on .tag{color:var(--accent)}
.initseq .txt{font:500 14.5px/1.5 var(--sans);color:var(--ink-ghost);transition:color .5s var(--ease)}
.initseq .line.on .txt{color:var(--ink)}
.initseq .desc{font-size:12.5px;color:var(--ink-faint);margin-top:3px;display:block}
@media (prefers-reduced-motion:reduce){
  .initseq .tag{color:var(--accent) !important}
  .initseq .txt{color:var(--ink) !important}
}
@media(max-width:600px){ .initseq .line{flex-direction:column;gap:4px} .initseq .tag{min-width:0} }

/* =====================================================================
   Section 8 — SUBSCRIBE — replaced the $2,500/month section, 2026-08-18.
   Same position and same border-top/border-bottom treatment as the section
   it replaced; no full-bleed grid backdrop, because that pattern read as a
   PRICE STATEMENT and this is not one. See business.html's own comment on
   this section for the full history and the redirect trade-off.
===================================================================== */
.subscribe{position:relative;text-align:center;border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);padding:clamp(80px,12vw,140px) 20px}
.subscribe h2{margin-top:16px;font:700 clamp(28px,4.6vw,48px)/1.14 var(--sans);
  letter-spacing:-.025em;color:#fff;max-width:22ch;margin-left:auto;margin-right:auto}
.subscribe .sub{margin:18px auto 0;max-width:48ch;font-size:15px;line-height:1.65;
  color:var(--ink-dim)}

.subscribe form{position:relative;max-width:420px;margin:34px auto 0;
  padding:26px clamp(18px,3vw,28px);text-align:left;background:var(--bg-2)}
.subscribe form label{display:block;font:700 13.5px/1.3 var(--sans);color:#fff}
.subscribe form .hint{display:block;margin-top:3px;font-size:12.5px;font-weight:400;
  color:var(--ink-faint)}
.subscribe .row{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap}
.subscribe input[type=email]{flex:1 1 200px;min-width:0;min-height:48px;
  padding:13px 14px;background:var(--black);color:#fff;border:1px solid var(--line-bright);
  /* 16px minimum is not a style choice: iOS Safari zooms the whole viewport
     on a focused input smaller than that, and does not zoom back out. */
  font:400 16px/1.4 var(--sans)}
.subscribe input[type=email]::placeholder{color:var(--ink-ghost)}
.subscribe input:focus-visible{outline:2px solid #fff;outline-offset:2px}
.subscribe .row .btn{padding:15px 20px}

/* THE HONEYPOT. Off-screen, never display:none — a scraper that checks for
   display:none is exactly the one this trap needs to catch, and display:none
   is the pattern it already knows to skip. aria-hidden on the wrapper and
   tabindex="-1" on the field keep this from catching a keyboard or
   screen-reader user, which would make it worse than no honeypot at all.
   Identical properties to learn.html's own .hp on purpose — one trap, one
   set of rules, described in both places; change one, change both. */
.subscribe .hp{position:absolute;left:-9999px;top:auto;width:1px;height:1px;
  overflow:hidden}

.subscribe .terms{list-style:none;margin:22px auto 0;padding:0;max-width:420px;
  text-align:left}
.subscribe .terms li{position:relative;padding:9px 0 9px 18px;font-size:12.5px;
  line-height:1.55;color:var(--ink-faint);border-top:1px solid var(--line)}
.subscribe .terms li:first-child{border-top:0}
.subscribe .terms li::before{content:"";position:absolute;left:0;top:16px;
  width:5px;height:1px;background:var(--ink-ghost)}

/* Server-rendered into {{UPDATES_NOTICE}} — see _notice_html in server.py.
   Not populated in the normal flow on THIS page (the form posts to /learn,
   which is where the confirmation is shown — see business.html's comment on
   this section), but styled anyway: a page with a notice class and no rule
   for it is a silent visual bug waiting for the day someone wires an outcome
   through to it. */
.subscribe .notice{max-width:420px;margin:22px auto 0;padding:16px 18px;
  text-align:left;background:var(--bg-2);border:1px solid var(--line)}
.subscribe .notice b{display:block;font:700 14px/1.3 var(--sans);color:#fff}
.subscribe .notice p{margin-top:5px;font-size:13px;color:var(--ink-dim)}
.subscribe .notice.good{border-color:var(--ok)}
.subscribe .notice.good b{color:var(--ok)}
.subscribe .notice.bad{border-color:var(--warn)}
.subscribe .notice.bad b{color:var(--warn)}

/* "Prefer to talk instead?" line — ADDED 2026-08-30, see business.html's
   comment beside it. Same terms-list typography (12.5px, --ink-faint),
   the link in --accent so it reads as the one actionable word in the line. */
.subscribe .talk-instead{margin:22px auto 0;max-width:420px;text-align:left;
  font-size:12.5px;line-height:1.6;color:var(--ink-faint)}
.subscribe .talk-instead a{color:var(--accent);font-weight:600}

/* STATIC-EXPORT FALLBACK — deploy-pages.py's build_static_business() swaps
   the live <form> for this note + link, because Cloudflare Pages cannot
   mint the signed anti-forgery token server.py's subscribe form needs (see
   that function's own comment). FOUND BY VANCE, 2026-09-02: this class had
   no rule anywhere this page's stylesheet reaches, so the deployed static
   homepage showed a bare, unstyled paragraph exactly where the form used
   to be -- confusing plumbing left in view. Scoped under .subscribe
   because that is the only place either class is ever injected. */
.subscribe .note{max-width:420px;margin:34px auto 0;text-align:left;
  font-size:13.5px;line-height:1.6;color:var(--ink-faint)}
.subscribe .cta{display:inline-block;margin-top:14px;padding:15px 22px;
  background:#fff;color:#000;font:600 12.5px/1 var(--mono);letter-spacing:.1em;
  text-transform:uppercase;text-decoration:none;border:1px solid #fff}
.subscribe .cta:hover,.subscribe .cta:focus-visible{background:var(--accent);
  border-color:var(--accent)}

/* =====================================================================
   Section 9 — MEET MARK
===================================================================== */
.meet{display:grid;grid-template-columns:1.1fr 1fr;gap:clamp(30px,6vw,80px);align-items:center}
@media(max-width:820px){ .meet{grid-template-columns:1fr} }
.meet h2{font:700 clamp(28px,4.6vw,50px)/1.12 var(--sans);letter-spacing:-.025em;color:#fff}
.meet p{margin-top:18px;font-size:15.5px;line-height:1.7;color:var(--ink-dim);max-width:46ch}
.meet .sig{margin-top:26px;font:600 12px/1 var(--mono);letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-faint)}
/* No portrait: the only "Mark" image asset on this box is an AI-composited
   triptych (a fabricated cyborg face spliced between two real photos) —
   not an honest portrait, and exactly the "cheesy AI imagery" the brief
   rules out. Rather than ship that or generate a new one, this panel is a
   technical readout instead: real facts, no invented metrics, styled like
   everything else on the page. See the build report. */
.meet-mark{position:relative;aspect-ratio:1/1;border:1px solid var(--line);
  background:var(--bg-2);overflow:hidden}
.meet-mark canvas{position:absolute;inset:0;width:100%;height:100%}
.meet-mark .opdata{position:absolute;left:0;right:0;bottom:0;padding:22px clamp(18px,3vw,26px);
  background:linear-gradient(to top, rgba(5,5,9,.92) 0%, rgba(5,5,9,.5) 70%, rgba(5,5,9,0) 100%)}
.meet-mark .mlabel{margin-bottom:8px}
.meet-mark .opname{font:700 21px/1.2 var(--sans);color:#fff;letter-spacing:-.01em;margin-bottom:14px}
.meet-mark dl{margin:0;display:grid;gap:7px}
.meet-mark dl>div{display:flex;justify-content:space-between;gap:14px;
  border-top:1px solid var(--line);padding-top:7px;font:500 12.5px/1.4 var(--mono)}
.meet-mark dt{color:var(--ink-ghost);letter-spacing:.04em}
.meet-mark dd{margin:0;color:var(--ink-dim);text-align:right}
.meet-mark dd.lit{color:var(--accent)}

/* MEET MARK — three-fact strip. ADDED 2026-08-30, additive merge from
   mdcom-v2's About section (Mark's keep-list item: "I build the systems I
   wish I had."). The heading and first paragraph above were already live,
   word for word — only the two paragraphs that follow it in the bio, and
   this strip, are the genuinely new content from that source. Same tokens
   as .meet-mark and .modules above: hairline grid on --line, black tiles,
   no new visual language introduced. Sits between the bio and the .sig
   line, same position it holds in the source. */
.meet-facts{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;
  background:var(--line);border:1px solid var(--line);margin-top:24px;max-width:560px}
.meet-facts div{background:#000;padding:16px 18px}
.meet-facts .mlabel{margin-bottom:8px;color:var(--accent)}
.meet-facts p{margin:0;font-size:12px;line-height:1.6;color:var(--ink-faint);max-width:none}
@media(max-width:620px){.meet-facts{grid-template-columns:1fr;max-width:420px}}

/* =====================================================================
   FAQ — "Before you email me." ADDED 2026-08-30, additive merge from
   mdcom-v2's About section (Mark's keep-list, named by its own heading).
   Only 3 of the source's 6 questions are here — the other 3 each stated a
   commitment (a delivery timeline, a training/data-handling promise) that
   is not already made anywhere on this site; held out and flagged rather
   than shipped, per the condition on this item. No section-index badge,
   same reasoning as SUBSCRIBE and MEET MARK above: not one of the seven
   process beats. Native <details>/<summary> — same mechanism the mobile
   nav disclosure already uses on this site, for the same reason (real
   open/closed state with no JS required to drive it).
===================================================================== */
.faq{border-top:1px solid var(--line)}
.faq-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;
  background:var(--line);border:1px solid var(--line);margin-top:clamp(30px,4vw,44px)}
.faq-item{background:#000;padding:24px 26px}
.faq-item summary{cursor:pointer;list-style:none;display:flex;
  align-items:flex-start;justify-content:space-between;gap:16px;
  font:700 15.5px/1.35 var(--sans);color:#fff}
.faq-item summary::-webkit-details-marker{display:none}
.faq-item summary::after{content:"+";font:600 16px/1 var(--mono);
  color:var(--ink-faint);flex-shrink:0}
.faq-item[open] summary::after{content:"\2212";color:var(--accent)}
.faq-item p{margin-top:14px;font-size:13.5px;line-height:1.7;
  color:var(--ink-dim);max-width:58ch}
/* NO ORPHAN CELL ON AN ODD COUNT. This list is 3 questions tonight and is
   about to change size (three more are pending a decision elsewhere) --
   :last-child:nth-child(odd) is what catches every count without a special
   case per number: on 3 or 5 it spans the otherwise-empty second slot in
   the final row; on 4 or 6 it is even-positioned and this rule does not
   match at all, so the grid stays a plain even set of pairs. Tested at
   3/4/5/6 rather than assumed -- see the build report. */
.faq-item:last-child:nth-child(odd){grid-column:1 / -1}
@media(max-width:700px){
  .faq-grid{grid-template-columns:1fr}
  .faq-item:last-child:nth-child(odd){grid-column:auto}
}

/* =====================================================================
   Section 10 / footer — end of init sequence
===================================================================== */
.finalask{text-align:center;padding-top:clamp(70px,10vw,120px)}
.finalask h2{font:700 clamp(26px,4.4vw,46px)/1.15 var(--sans);letter-spacing:-.02em;
  color:#fff;max-width:22ch;margin:0 auto}
.finalask .btn{margin-top:32px}
/* THE HAND-ROLLED FOOTER THAT USED TO LIVE HERE IS GONE, 2026-09-03,
   crawlability pass. business.html carried its own bespoke <footer
   class="sitefoot"> with three link groups (9 links, no /visualizers, no
   /chatbots, no /connect-and-run-local) while every other page on the site
   shares one footer (four groups, ten-plus links) via add-footer.py. This
   was invisible to that script's own idempotency check, which looks for the
   substring "sitefoot" -- present here only because this page's OWN class
   happened to share the name, not because the real shared footer had ever
   been applied. business.html now carries the actual shared markup (see its
   own <head> for the token bridge this needs) and these rules -- .sitefoot,
   .footgrid, .footbase, .initline -- are unused. Left deleted rather than
   dead: a rule nothing selects is exactly the kind of thing that quietly
   comes back on the next redesign because someone finds it and assumes it
   is still load-bearing. */

/* ---------- shared: blur-resolve reveal primitive (js/reveal.js) ---------- */
.rword{display:inline-block;filter:blur(11px);opacity:0;
  transform:translateZ(0);
  transition:filter .82s var(--ease), opacity .7s var(--ease)}
.rin .rword{filter:blur(0);opacity:1}

/* FAILSAFE, added 2026-08-18 by Jarvis. The words above start at opacity:0 and
   are un-hidden by js/reveal.js adding .rin when an IntersectionObserver fires.
   That is fine until it isn't: reveal.js wraps the words FIRST and reveals them
   SECOND, so any failure between those two steps — a thrown observer, a browser
   that never fires it, a scroll position it does not expect — leaves the most
   important sentence on the page permanently invisible, on a page that looks
   otherwise completely healthy. A blank hero reads as a broken site, and there
   is no error anywhere to find.
   So the transition is also driven by a plain CSS animation that arrives at the
   same end state on its own. If .rin lands first the animation is redundant and
   invisible; if it never lands, the text still appears. It cannot un-reveal
   something already revealed, because both paths end at blur(0)/opacity:1.
   Deliberately slower than the observer path so it never races it and wins. */
@keyframes rword-failsafe{to{filter:blur(0);opacity:1}}
.rword{animation:rword-failsafe .6s var(--ease) 2.4s forwards}
.rblock{animation:rword-failsafe .6s var(--ease) 2.4s forwards}
.rin .rword,.rin.rblock,.rin .rblock{animation:none}
.rblock{filter:blur(9px);opacity:0;transition:filter .8s var(--ease),opacity .7s var(--ease)}
.rin.rblock,.rin .rblock{filter:blur(0);opacity:1}
@media (prefers-reduced-motion:reduce){
  .rword,.rblock{filter:none !important;opacity:1 !important;transition:none !important}
}
/* perf fallback: device tier "low" swaps blur for plain fade (see reveal.js) */
body.tier-low .rword{filter:none;transform:translateY(10px);transition:opacity .5s var(--ease),transform .5s var(--ease)}
body.tier-low .rin .rword{transform:translateY(0)}
body.tier-low .rblock{filter:none;transform:translateY(10px)}
body.tier-low .rin.rblock,body.tier-low .rin .rblock{transform:translateY(0)}

.rmnote{position:fixed;bottom:14px;left:14px;z-index:50;font:600 10px/1 var(--mono);
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink-ghost);
  border:1px solid var(--line);padding:7px 10px;display:none}
body.rm .rmnote{display:block}

/* perf HUD, dev only */
#perfhud{position:fixed;top:70px;left:14px;z-index:99;font:600 10.5px/1.5 var(--mono);
  color:#8fe08f;background:rgba(0,0,0,.7);border:1px solid var(--line);padding:7px 10px;
  display:none;white-space:pre}
#perfhud.on{display:block}

/* .no-webgl no longer touches the hero (see the comment at .hero above) —
   it still exists as a class, set by main.js's probe and by cards.js's own
   WebGL-construction guard, but nothing in this file keys off it anymore.
   Left named here rather than silently dropped so a future reader who
   greps for it finds this note instead of nothing. */
