:root{
  --ink:#0B0E0A;
  --paper:#F2EFE6;
  --lime:#A5C715;
  --signal:#E4761B;
  --slate:#2A312B;
  --fg:#F2EFE6;
  --bg:#0B0E0A;
  --rule:rgba(242,239,230,.16);

  --measure:46rem;
  --wide:64rem;
}
*{box-sizing:border-box;margin:0;padding:0}
html{
  scroll-behavior:smooth;-webkit-text-size-adjust:100%;
  /* Stops the overscroll bounce at the top of the page. On iPad that
     rubber-band is one of the gestures Safari reads as "give me the browser
     chrome back", which is why scrolling up kept dropping the page out of
     fullscreen. It also kills pull-to-refresh, which this page has no use for.
     Not a complete cure — iOS reserves the right to leave fullscreen on its
     own — but it removes the gesture that was triggering it. */
  overscroll-behavior-y:none;
}
body{
  background:var(--bg);color:var(--fg);
  font-family:"Source Serif 4",Georgia,serif;
  font-size:19px;line-height:1.55;
  overflow-x:hidden;
  transition:background-color .12s linear,color .12s linear;
}
/* Nothing scrolls while the loader is up — the page behind it is still
   assembling and a scroll during that lands somewhere arbitrary. */
html.loading,html.loading body{overflow:hidden}

/* ================= the layer stack =================
   The whole page is five fixed planes with the document running through the
   middle of them. Getting one of these z-indexes wrong is what puts footage on
   top of the headline, so they are declared together rather than scattered.

     -6  #backdrop   video filling the viewport
     -5  #grade      duotone + scrim over the footage
     -2  #spotlight  pointer light
     -1  #plate      hero still / no-video fallback
      0  #gl         the particle field
      1  main        the words
      3  .mark       fixed brand and the two chips
     40  #stars      shooting stars, over everything
     60  #ai         the assistant
     90  #loader     the way in                                            */

#backdrop{
  position:fixed;inset:0;z-index:-6;
  overflow:hidden;pointer-events:none;
  --bd-fade:1;
  opacity:var(--bd-fade);
}
/* One of these per clip. --k is the cross-fade, written by stage.js. */
.bd-clip{
  position:absolute;inset:0;
  --k:0;
  opacity:var(--k);
  transition:opacity 1.1s ease;
  background-size:cover;background-position:center;
}
/* The clip fills the viewport and is centred on it. Aspect ratio is allowed to
   crop rather than letterbox — a band of black at the top of a full-screen
   backdrop is much more noticeable than a slightly tighter crop. */
.bd-clip video{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;object-position:center;
  transform:scale(1.06);
  opacity:0;transition:opacity 1s ease;
}
.bd-clip video.on{opacity:1}

#grade{position:fixed;inset:0;z-index:-5;pointer-events:none;--bd-fade:1;opacity:var(--bd-fade)}
/* Multiply the footage toward ink, then screen the lime back into the
   highlights: the scene ends up made of the page's own two colours, which is
   what stops it reading as stock video playing behind a website. */
.grade-duo{
  position:absolute;inset:0;
  background:
    radial-gradient(120% 90% at 50% 40%, rgba(165,199,21,.10), transparent 60%),
    linear-gradient(180deg, rgba(11,14,10,.52), rgba(11,14,10,.76));
  mix-blend-mode:multiply;
}
/* The contrast floor for type, strongest where the copy sits. */
.grade-scrim{
  position:absolute;inset:0;
  background:
    radial-gradient(92% 74% at 50% 48%, rgba(11,14,10,.70), rgba(11,14,10,.28) 70%, transparent),
    linear-gradient(180deg, rgba(11,14,10,.58) 0%, transparent 28%, transparent 64%, rgba(11,14,10,.90) 100%);
}
.grade-vig{
  position:absolute;inset:-2px;
  background:radial-gradient(118% 100% at 50% 50%, transparent 44%, rgba(11,14,10,.72) 88%, #0B0E0A 100%);
}

#plate{
  position:fixed;inset:0;z-index:-1;
  width:100%;height:100%;object-fit:cover;
  opacity:.28;transition:opacity .4s linear;pointer-events:none;
}
#gl{position:fixed;inset:0;width:100vw;height:100vh;z-index:0;pointer-events:none}
#stars{position:fixed;inset:0;z-index:40;pointer-events:none}
main{position:relative;z-index:1}

#spotlight{
  position:fixed;inset:0;z-index:-2;pointer-events:none;
  --x:50%; --y:42%;
  background:
    radial-gradient(28rem 28rem at var(--x) var(--y),
      rgba(165,199,21,.16) 0%, rgba(228,118,27,.09) 32%, transparent 68%),
    radial-gradient(13rem 13rem at var(--x) var(--y),
      rgba(242,239,230,.13) 0%, transparent 70%);
  mix-blend-mode:screen;opacity:.85;transition:opacity .5s ease;
}
body.lit #spotlight{opacity:1}

/* ================= the spine ================= */
/* Scoped to `main >` deliberately. As a bare `section` selector this reached
   every <section> on the page — including the Vesopa AI dock, which it handed
   min-height:100svh, padding:14vh 5vw, justify-content:center and
   text-align:center. That is the entire explanation for the dock's centred
   text, the space either side of it, and a panel that ignored its own height:
   the component rules overrode `display` and `flex-direction` and silently
   inherited everything else. Page furniture belongs to page sections;
   components style themselves. */
main > section{
  min-height:100svh;
  display:flex;align-items:center;justify-content:center;
  padding:14vh 5vw;position:relative;text-align:center;
  /* The two soft type-guards below are ::before boxes inset by -18% and -16%
     horizontally, which on a narrow column reaches past the viewport and made
     the document 428px wide on a 390px phone — a horizontal scrollbar on every
     screen of the site. They have to keep those insets: the guard's whole job
     is to fade out well beyond the text it protects.
     `clip`, not `hidden`. `overflow: hidden` would make each section a scroll
     container and break the `position: sticky` stages inside the showcases;
     `clip` establishes no scrollport, so the stages still pin. Verified. */
  overflow-x:clip;
}
.col,.wide{
  width:100%;max-width:var(--measure);margin-inline:auto;
  display:flex;flex-direction:column;align-items:center;
}
.wide{max-width:var(--wide)}

h1,h2,h3{
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-variation-settings:"wdth" 118,"wght" 620;
  line-height:.98;letter-spacing:-.02em;text-wrap:balance;
}
h1{font-size:clamp(3rem,9vw,7.4rem)}
h2{font-size:clamp(2.3rem,5.6vw,4.4rem)}
h3{font-size:1.25rem;font-variation-settings:"wdth" 112,"wght" 600;line-height:1.25}
p{margin-top:1.3rem;max-width:38rem;margin-inline:auto}
p.lead{font-size:clamp(1.15rem,2.4vw,1.55rem);line-height:1.45;max-width:40rem}
a{color:inherit}

.num{
  font-family:"Martian Mono",monospace;font-weight:300;
  font-size:.76rem;letter-spacing:.09em;opacity:.82;
  display:block;margin-bottom:1.9rem;
}

/* ================= the type guard =================
   The particle field runs BETWEEN the footage and the words, so the scrim
   inside #grade cannot defend type — the canvas paints on top of it. This
   guard is inside `main`, above the canvas and below the words, which is the
   only band in the stack where text can be protected.

   It used to exist on the hero and the showcase copy only, which is why the
   field was legible there and turned the story beats, the plans, the steps and
   the forms into noise. Every content column gets it now.

   A soft radial with no hard edge: it lifts the ground behind the words
   without ever reading as a panel drawn behind them. The dark half washes
   toward ink, the daylight half toward paper, so it always thickens the
   section's own ground rather than introducing a third colour. */
.col,.wide{position:relative}
.col::before,.wide::before{
  content:"";position:absolute;inset:-9% -12%;z-index:-1;pointer-events:none;
  background:radial-gradient(58% 56% at 50% 50%,
    rgba(11,14,10,.80) 0%, rgba(11,14,10,.58) 46%, rgba(11,14,10,.20) 72%, transparent 86%);
}
.light .col::before,.light .wide::before{
  background:radial-gradient(58% 56% at 50% 50%,
    rgba(242,239,230,.86) 0%, rgba(242,239,230,.64) 46%, rgba(242,239,230,.24) 72%, transparent 86%);
}
/* The finale is the one place the field IS the content — the V has the floor
   there, and a wash behind the copy would grey it out. */
.finale .col::before{inset:auto -12% -6%;height:42%}

/* ================= reveal =================
   Content lifts in once, on arrival. Transform and opacity only, so it stays
   on the compositor and never triggers layout mid-scroll. */
[data-reveal]{
  opacity:0;transform:translateY(18px);
  transition:opacity .7s cubic-bezier(.16,.7,.3,1),transform .7s cubic-bezier(.16,.7,.3,1);
}
[data-reveal].shown{opacity:1;transform:none}

/* ================= the fixed brand bar ================= */
.mark{
  position:fixed;top:0;left:0;right:0;z-index:3;
  padding:1rem 5vw;
  display:flex;justify-content:space-between;align-items:center;
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-size:.9rem;font-variation-settings:"wdth" 122,"wght" 640;
  letter-spacing:.02em;pointer-events:none;
}
/* difference on the wordmark only. Applying it to the whole bar dragged the
   chips through it too and made their borders vibrate against the video. */
.mark-home{
  mix-blend-mode:difference;color:#fff;
  text-decoration:none;pointer-events:auto;
}
.mark-right{display:flex;gap:.5rem;pointer-events:auto}

.chip-btn{
  display:inline-flex;align-items:center;gap:.45rem;
  font-family:"Martian Mono",monospace;font-size:.62rem;letter-spacing:.06em;
  color:var(--paper);background:rgba(11,14,10,.42);
  border:1px solid rgba(242,239,230,.22);border-radius:2px;
  padding:.45rem .7rem;cursor:pointer;
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  transition:border-color .2s,background .2s,color .2s;
}
.chip-btn:hover{border-color:var(--lime);background:rgba(11,14,10,.62)}
.chip-btn[aria-pressed="true"]{border-color:var(--lime);color:var(--lime)}
.chip-btn i{width:.8rem;height:.8rem;display:block;background:currentColor;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:contain;mask-size:contain}
.ico-sound{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 9v6h4l5 4V5L8 9H4zm12.5 3a4.5 4.5 0 0 0-2.5-4v8a4.5 4.5 0 0 0 2.5-4z'/%3E%3C/svg%3E");
            mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 9v6h4l5 4V5L8 9H4zm12.5 3a4.5 4.5 0 0 0-2.5-4v8a4.5 4.5 0 0 0 2.5-4z'/%3E%3C/svg%3E")}
.ico-fs{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 9V4h5v2H6v3H4zm11-5h5v5h-2V6h-3V4zM4 15h2v3h3v2H4v-5zm14 0h2v5h-5v-2h3v-3z'/%3E%3C/svg%3E");
         mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 9V4h5v2H6v3H4zm11-5h5v5h-2V6h-3V4zM4 15h2v3h3v2H4v-5zm14 0h2v5h-5v-2h3v-3z'/%3E%3C/svg%3E")}
/* On the daylight half the chips need dark ink, not paper. */
body.day .chip-btn{color:var(--ink);background:rgba(242,239,230,.5);border-color:rgba(11,14,10,.2)}
body.day .chip-btn[aria-pressed="true"]{color:#4F6A0B;border-color:#7E9E12}

/* ================= hero ================= */
.hero .col{position:relative}

/* The headline is two lines, always, on every screen.
 *
 * It was written as `We build it,<br>then we run it.` and broke into three
 * lines at both ends of the range, for two different reasons:
 *
 *   - Wide: `.col` is capped at --measure (46rem), but the h1 at its 7.4rem
 *     maximum needs about 876px to set "then we run it." on one line. The
 *     line did not fit the column, so it wrapped underneath the <br>.
 *   - Narrow: the clamp's *floor* of 3rem is not a floor the text can honour.
 *     On a 360px phone that line wants ~360px against ~324px of column.
 *
 * So: the headline gets the wide measure rather than the reading one — a
 * display line is not body copy and has no business being held to a 46rem
 * column — and the size is driven by viewport width all the way down, with
 * `min()` rather than a clamp floor, so it keeps shrinking on the small
 * screens where a floor would have forced the wrap.
 *
 * The two lines are spans rather than a <br> so `white-space: nowrap` can be
 * applied to each. That turns a silent third line into something that would
 * be visible immediately, which is the point: the sizing above is what keeps
 * it on two lines, and the nowrap is the assertion that it worked.
 */
.hero .col{max-width:var(--wide)}
.hero h1{
  font-size:min(10.6vw, 7.4rem);
  text-wrap:nowrap;
}
.hero h1 .ln{display:block;white-space:nowrap}
/* The lead keeps the reading measure the column no longer imposes. */
.hero .lead{max-width:40rem}
.hero .num,.hero .cta-row,.hero-meta{max-width:46rem}
/* The scrim inside #grade cannot protect the copy, because the particle canvas
   paints above it. This guard is inside `main` — above the canvas, below the
   words — which is the only band in the stack where type can be defended. */
.hero .col::before{
  content:"";position:absolute;inset:-14% -18%;z-index:-1;pointer-events:none;
  background:radial-gradient(62% 58% at 50% 50%,
    rgba(11,14,10,.86) 0%, rgba(11,14,10,.66) 42%, rgba(11,14,10,.28) 68%, transparent 82%);
}
.hero-meta{
  display:flex;gap:1.6rem 2.6rem;flex-wrap:wrap;justify-content:center;
  margin-top:3.2rem;
  font-family:"Martian Mono",monospace;font-size:.72rem;letter-spacing:.04em;opacity:.72;
}
.hero-meta b{color:var(--lime);font-weight:500;margin-right:.4rem}

.cue{
  position:absolute;left:50%;bottom:2.2rem;transform:translateX(-50%);
  z-index:4;display:flex;flex-direction:column;align-items:center;gap:.5rem;
  font-family:"Martian Mono",monospace;font-size:.6rem;letter-spacing:.14em;
  text-transform:uppercase;opacity:.5;transition:opacity .4s ease;pointer-events:none;
}
.cue.gone{opacity:0}
.cue i{display:block;width:1px;height:2.2rem;
  background:linear-gradient(180deg,transparent,var(--lime));
  animation:cue 2.4s ease-in-out infinite}
@keyframes cue{
  0%,100%{transform:scaleY(.4);transform-origin:top;opacity:.4}
  50%{transform:scaleY(1);transform-origin:top;opacity:1}
}

/* Click: light leaves the point of contact. One element per strike, removed on
   animationend by hero.js. */
.ring{
  position:absolute;z-index:0;width:2px;height:2px;margin:-1px 0 0 -1px;
  border-radius:50%;pointer-events:none;
  box-shadow:0 0 0 1px rgba(165,199,21,.85), 0 0 22px 3px rgba(165,199,21,.30);
  animation:ring 1.25s cubic-bezier(.16,.7,.3,1) forwards;
}
@keyframes ring{
  from{transform:scale(1);opacity:.95}
  to{transform:scale(320);opacity:0}
}

/* ================= the showcase =================
   A screenshot pinned mid-viewport while the section scrolls past, stepping
   through the app's screens. The section is tall; the stage inside it is one
   viewport and sticky. Height is travel: each step wants roughly a screen of
   scroll to itself, plus one screen for the stage to occupy. */
.showcase{
  display:block;padding:0;
  min-height:calc(100svh + 170svh);
}
.showcase.single{min-height:calc(100svh + 55svh)}
.stage{
  position:sticky;top:0;
  height:100svh;
  display:flex;align-items:center;justify-content:center;
  padding:8vh 5vw 6vh;
}
.stage-grid{
  width:100%;max-width:82rem;margin-inline:auto;
  display:grid;grid-template-columns:minmax(0,.86fr) minmax(0,1.14fr);
  gap:clamp(1.6rem,4vw,4rem);align-items:center;
}
.stage-grid.flip .stage-copy{order:2}
.stage-grid.flip .stage-shots{order:1}
.stage-copy{text-align:left;min-width:0;position:relative}
/* The same guard the hero uses, and for the same reason: the grade inside
   #grade sits under the particle canvas, so it cannot defend type that the
   field is painting over. This one is inside `main` — above the canvas, below
   the words — which is the only band in the stack where it can. Soft-edged, so
   it darkens the ground without ever reading as a panel behind the copy. */
.stage-copy::before{
  content:"";position:absolute;inset:-14% -16%;z-index:-1;pointer-events:none;
  background:radial-gradient(64% 62% at 50% 50%,
    rgba(11,14,10,.86) 0%, rgba(11,14,10,.66) 44%, rgba(11,14,10,.26) 70%, transparent 84%);
}
/* On the daylight half the ground is already bright and a black wash would
   read as a smudge; the light sections carry no showcase today, but the rule
   keeps the two halves honest if one is ever added. */
.light .stage-copy::before{
  background:radial-gradient(64% 62% at 50% 50%,
    rgba(242,239,230,.86) 0%, rgba(242,239,230,.62) 44%, rgba(242,239,230,.22) 70%, transparent 84%);
}
.stage-copy h2{font-size:clamp(2rem,4vw,3.4rem)}
.stage-copy p{margin-inline:0;max-width:32rem;font-size:1.02rem}

/* The steps are stacked in one grid cell so the block does not change height
   as the copy changes — a column that grows and shrinks under a pinned image
   makes the whole stage jitter on every step. */
.step-stack{display:grid}
.step{
  grid-area:1/1;
  opacity:0;transform:translateY(14px);pointer-events:none;
  transition:opacity .55s ease,transform .55s cubic-bezier(.16,.7,.3,1);
}
.step.on{opacity:1;transform:none;pointer-events:auto}
.step.past{transform:translateY(-14px)}

.stage-shots{min-width:0;display:flex;flex-direction:column;gap:1rem}
/* The device frame. A thin lime rule and a deep shadow is enough to read as a
   screen; a drawn bezel would date the page in a year. */
.frame{
  position:relative;width:100%;
  aspect-ratio:16/10;
  border:1px solid var(--rule);border-radius:6px;
  background:#05070a;overflow:hidden;
  box-shadow:0 32px 80px -20px rgba(0,0,0,.75), 0 0 0 1px rgba(165,199,21,.08);
}
.shot-stack{position:absolute;inset:0}
.shot{
  position:absolute;inset:0;
  width:100%;height:100%;
  /* contain, not cover: these are screenshots at four different aspect ratios
     and cropping one loses the edge of the UI, which is the thing being shown. */
  object-fit:contain;object-position:center;
  opacity:0;transform:scale(1.03);
  transition:opacity .6s ease,transform .8s cubic-bezier(.16,.7,.3,1);
}
.shot.on{opacity:1;transform:none}
.shot.past{transform:scale(.985)}

.shot-dots{display:flex;gap:.5rem;justify-content:center;flex-wrap:wrap}
.shot-dot{
  font-family:"Martian Mono",monospace;font-size:.6rem;letter-spacing:.08em;
  text-transform:uppercase;
  color:inherit;background:transparent;cursor:pointer;
  border:1px solid var(--rule);border-radius:2px;
  padding:.35rem .7rem;opacity:.55;
  transition:opacity .25s,border-color .25s,color .25s;
}
.shot-dot.on{opacity:1;border-color:var(--lime);color:var(--lime)}
.shot-dot:hover{opacity:1}

/* ================= store badges ================= */
/* The Microsoft badge is a fixed-size third-party lockup, so the row is built
   around ITS height rather than the other way round: --badge-h is the single
   number both sides agree on, and the link beside it is sized to match exactly
   instead of being a taller pill sitting next to a shorter image. */
.store-row{
  --badge-h:2.75rem;
  display:flex;gap:.75rem;align-items:stretch;flex-wrap:wrap;margin-top:2.2rem;
}
.store-row > *{height:var(--badge-h)}
.store-row .cta{
  margin-top:0;height:var(--badge-h);
  display:inline-flex;align-items:center;
  padding:0 1.25rem;border-radius:4px;
  font-size:.95rem;line-height:1;white-space:nowrap;
}
/* The badge is a third-party custom element and sizes itself from its own
   internal image. Cap it here as well as asking for size="small": the two
   together mean a future change at Microsoft's end cannot make this the
   biggest thing in the section again. */
.badge-slot{display:inline-flex;align-items:center;height:var(--badge-h)}
.badge-slot ms-store-badge{display:block;height:var(--badge-h);max-width:15rem}
/* The component renders an <img> in its shadow root; ::part reaches it where
   the author exposed one, and the height cap catches it where they did not. */
.badge-slot ms-store-badge::part(img){height:var(--badge-h);width:auto;max-width:100%}
.badge-slot ms-store-badge img{height:var(--badge-h);width:auto}
/* Our own link is in the markup and works on its own. Microsoft's component
   replaces it only once it has actually upgraded — a blocked third-party
   script must not leave an empty space where the install button was. */
.badge-slot.has-badge .store-fallback{display:none}
.store-fallback{margin-top:0}

/* The lockup in the page (as opposed to in the loader). Inherits the section's
   ink, so on the daylight half it renders as the primary Logo-01 lockup. */
.lockup-stage{
  width:min(30rem,80vw);
  margin:.4rem auto 2.4rem;
  color:var(--fg);
}

/* ================= the four doors ================= */
.doors{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));
  gap:1px;background:var(--rule);width:100%;margin-top:3rem;text-align:left;
}
.door{
  display:flex;flex-direction:column;gap:.3rem;
  padding:1.7rem 1.5rem 1.5rem;text-decoration:none;
  background:var(--bg);
  transition:background .25s,transform .25s;
}
.light .door{background:var(--paper)}
.door:hover{transform:translateY(-3px)}
.door-k{font-family:"Martian Mono",monospace;font-size:.66rem;letter-spacing:.1em;color:var(--lime);margin-bottom:.5rem}
.light .door-k{color:#5E7A0E}
.door b{font-family:"Archivo",sans-serif;font-variation-settings:"wdth" 114,"wght" 640;font-size:1.3rem}
.door i{font-style:normal;font-size:.95rem;opacity:.75;line-height:1.4}
.door u{
  margin-top:.7rem;text-decoration:none;
  font-family:"Martian Mono",monospace;font-size:.68rem;letter-spacing:.04em;
  color:var(--lime);
}
.light .door u{color:#5E7A0E}
.door:hover u{text-decoration:underline}

/* ================= motion graphics ================= */
.motion-strip{width:100%;max-width:var(--wide);margin-inline:auto;text-align:center}
.motion-holder{
  width:100%;max-width:34rem;margin-inline:auto;aspect-ratio:1;
  opacity:0;transition:opacity .8s ease;
}
.motion-holder.wide{max-width:44rem}
.motion-holder.tall{max-width:20rem;aspect-ratio:1}
.motion-holder.motion-on{opacity:1}
/* Never reserve a square of empty page for something that will not load. */
.motion-holder.motion-off{display:none}
.motion-holder svg{display:block;width:100%;height:100%}
.motion-pair{display:flex;gap:2rem;justify-content:center;flex-wrap:wrap;margin-top:2rem;width:100%}
.motion-cap{
  font-family:"Martian Mono",monospace;font-size:.72rem;line-height:1.6;
  opacity:.66;margin-top:.6rem;letter-spacing:.02em;
}
.strip{min-height:auto;padding:8vh 5vw}

/* ================= facts, CTAs, plans ================= */
.facts{display:flex;flex-wrap:wrap;justify-content:center;gap:2rem 3rem;margin-top:3rem}
.wide-facts{max-width:var(--wide);margin-inline:auto}
.fact{min-width:8rem}
.fact b{
  display:block;font-family:"Martian Mono",monospace;font-weight:500;
  font-size:1.9rem;line-height:1;letter-spacing:-.04em;color:var(--lime);
}
.fact i{display:block;font-style:normal;font-size:.9rem;opacity:.85;margin-top:.5rem;max-width:11rem;margin-inline:auto}

.cta{
  display:inline-block;margin-top:2.8rem;
  font-family:"Archivo",sans-serif;font-variation-settings:"wdth" 112,"wght" 600;
  font-size:1.05rem;padding:.9rem 1.9rem;border-radius:2px;
  background:var(--lime);color:var(--ink);text-decoration:none;
  pointer-events:auto;transition:transform .2s,box-shadow .2s;
}
.cta:hover{transform:translateY(-2px);box-shadow:0 10px 30px -12px rgba(165,199,21,.6)}
.cta.ghost{background:transparent;color:inherit;box-shadow:inset 0 0 0 1px var(--rule)}
.cta.ghost:hover{box-shadow:inset 0 0 0 1px var(--lime)}
.cta.big{font-size:clamp(1.1rem,2.2vw,1.45rem);padding:1.15rem 2.6rem}
.cta-row{display:flex;gap:.7rem;flex-wrap:wrap;justify-content:center}

/* Notes to ourselves — the placeholder-prose warning on the story, the wording
   gate on Pay. They are real and they still stand, but they are addressed to
   whoever edits this page, not to whoever reads it: "the specifics are
   invented" in front of a prospective client is not the disclosure it looks
   like. They stay in the markup and appear under ?probe, so the gate is still
   in the way of anyone reviewing the page rather than quietly deleted. */
.hold{display:none}
body.probing .hold{
  display:block;
  margin-top:2.4rem;padding:1.1rem 1.3rem;border-left:2px solid var(--lime);
  font-family:"Martian Mono",monospace;font-size:.76rem;line-height:1.7;
  opacity:.75;max-width:34rem;text-align:left;white-space:pre-line;
}

#story{padding-top:12vh}
.beats{margin-top:4.5rem;display:grid;gap:0;width:100%}
.beat{
  display:grid;grid-template-columns:3.4rem 1fr;gap:1.6rem;
  padding:2.2rem 0;border-top:1px solid var(--rule);
  max-width:52rem;margin-inline:auto;width:100%;text-align:left;
}
.beat:last-child{border-bottom:1px solid var(--rule)}
.beat em{font-family:"Martian Mono",monospace;font-style:normal;font-size:.76rem;opacity:.55;padding-top:.35rem}
.beat p{margin-top:.5rem;font-size:1.05rem;opacity:.82;margin-inline:0}

.light{--fg:#0B0E0A;--rule:rgba(11,14,10,.18)}
.light .fact b{color:#5E7A0E}
.light .cta{background:var(--ink);color:var(--paper)}
.light .cta:hover{box-shadow:0 10px 30px -12px rgba(11,14,10,.6)}
.light .cta.ghost{background:transparent;color:var(--ink)}
.pay .fact b{color:var(--signal)}
.pay .cta{background:var(--signal);color:#fff}

.plans{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));
  gap:1px;margin-top:3rem;background:var(--rule);
  max-width:52rem;width:100%;text-align:left;
}
.plan{background:var(--paper);padding:1.6rem}
.plan b{display:block;font-family:"Archivo",sans-serif;font-variation-settings:"wdth" 112,"wght" 620;font-size:1.1rem}
.plan u{display:block;text-decoration:none;font-family:"Martian Mono",monospace;font-size:1.4rem;margin:.6rem 0 .35rem}
.plan i{font-style:normal;font-size:.85rem;opacity:.62}

.steps{
  list-style:none;display:grid;gap:0;
  grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));
  margin-top:3.4rem;width:100%;text-align:left;
}
.steps li{padding:1.8rem 1.6rem 2rem;display:flex;flex-direction:column;gap:.15rem;border-top:1px solid var(--rule)}
.step-n{font-family:"Martian Mono",monospace;font-size:.7rem;letter-spacing:.1em;color:var(--lime);margin-bottom:.9rem;display:block}
.light .step-n{color:#5E7A0E}
.steps h3{margin-bottom:.5rem}
.steps p{margin:0;font-size:.98rem;opacity:.78;max-width:none}
@media (min-width:60rem){
  .steps li{border-top:0;border-left:1px solid var(--rule);padding-left:1.6rem}
  .steps li:first-child{border-left:0;padding-left:0}
}

.finale{min-height:150svh;align-items:flex-end;padding-bottom:10vh}
.finale .col{gap:.2rem}
.finale-line{
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-variation-settings:"wdth" 118,"wght" 620;
  font-size:clamp(1.6rem,3.4vw,2.6rem);line-height:1.05;letter-spacing:-.02em;margin-top:.6rem;
}
.finale .cta-row{margin-top:auto}
.finale-foot{font-family:"Martian Mono",monospace;font-size:.72rem;letter-spacing:.05em;opacity:.6;margin-top:1.6rem}
/* The legal row under the finale. Wraps rather than scrolls: on a phone these
   are seven links and a single line of them would run off the side. */
.finale-legal{display:flex;flex-wrap:wrap;gap:.5rem 1.3rem;margin-top:1.6rem}
.finale-legal a{
  font-family:"Martian Mono",monospace;font-size:.68rem;letter-spacing:.06em;
  text-transform:uppercase;opacity:.62;text-decoration:none;
}
.finale-legal a:hover{opacity:1;color:#5E7A0E}

/* The frame counter is a development instrument, not part of the page. It ships
   hidden and site.js reveals it only under ?probe — it was previously sitting
   on top of the Vesopa AI button for every visitor. */
#perf{display:none}
body.probing #perf{
  display:block;
  position:fixed;right:.7rem;bottom:.7rem;z-index:5;
  font-family:"Martian Mono",monospace;font-size:.6rem;line-height:1.5;
  padding:.4rem .55rem;border-radius:3px;
  background:rgba(120,120,120,.22);color:#fff;mix-blend-mode:difference;
  pointer-events:none;white-space:pre;
}
body.is-fullscreen #perf{display:none}
/* In fullscreen, Safari on iPad paints its own exit control over the top-left
   corner — directly on top of the wordmark, which read as "opa Software" with
   the first four letters underneath the button. Nothing can be drawn above
   that control, so the brand moves out from under it instead.

   iOS and iPadOS only. Android and the desktop paint nothing there, so on
   those the shift was clearing an obstacle that does not exist and the
   wordmark simply sat indented from a bar that is flush everywhere else —
   which is exactly how it looked: misaligned, for no reason anyone could see.
   Our own Exit chip is the way out of fullscreen on those platforms, and it
   is at the other end of the bar. */
body.plat-ios.is-fullscreen .mark{padding-left:max(5vw, 4.75rem)}
body.plat-ios.is-fullscreen .mark-home{padding-left:env(safe-area-inset-left,0)}

/* The exit icon, for the one state where it means the opposite of its label.
   On a phone the chip labels are off and the icon is all there is — an
   outward-pointing arrows glyph while already fullscreen reads as "there is
   no way back from here", which is how the Exit control went missing on
   Android. Arrows in, once fullscreen is on. */
body.is-fullscreen .ico-fs{
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 4h2v5H6V7h3V4zm4 0h2v3h3v2h-5V4zM6 15h5v5H9v-3H6v-2zm7 0h5v2h-3v3h-2v-5z'/%3E%3C/svg%3E");
           mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 4h2v5H6V7h3V4zm4 0h2v3h3v2h-5V4zM6 15h5v5H9v-3H6v-2zm7 0h5v2h-3v3h-2v-5z'/%3E%3C/svg%3E")}

/* ================= forms ================= */
.form-card{
  width:100%;max-width:52rem;margin:3rem auto 0;
  background:rgba(242,239,230,.04);
  border:1px solid var(--rule);border-radius:3px;
  padding:clamp(1.4rem,3vw,2.4rem);text-align:left;
}
.light .form-card{background:rgba(11,14,10,.03)}
.form-card h3{margin-bottom:1.4rem;font-size:1.15rem}
.fld{margin-bottom:1.1rem}
/* Direct child only: the feature tickboxes are <label class="opt"> nested in a
   .fld, and a descendant selector here beats .opt on specificity — which
   forces display:block over its flex row and stacks every box above its label. */
.fld > label{
  display:block;font-family:"Martian Mono",monospace;
  font-size:.68rem;letter-spacing:.06em;text-transform:uppercase;
  opacity:.6;margin-bottom:.45rem;
}
.fld input,.fld select,.fld textarea{
  width:100%;font:inherit;font-size:1rem;color:var(--fg);background:transparent;
  border:1px solid var(--rule);border-radius:2px;padding:.75rem .85rem;
}
.fld textarea{resize:vertical;min-height:6rem;font-family:inherit}
.fld input:focus,.fld select:focus,.fld textarea:focus{outline:2px solid var(--lime);outline-offset:-1px}
.light .fld input,.light .fld select,.light .fld textarea{color:var(--ink)}
.fld select option{background:var(--ink);color:var(--paper)}
.light .fld select option{background:var(--paper);color:var(--ink)}
.two{display:grid;gap:1.1rem;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr))}
.opts{display:grid;gap:.5rem;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));margin-bottom:1.2rem}
.opt{
  display:flex;gap:.7rem;align-items:flex-start;
  border:1px solid var(--rule);border-radius:2px;padding:.8rem .9rem;
  font-family:"Archivo",Helvetica,Arial,sans-serif;
  font-size:.95rem;font-variation-settings:"wdth" 110,"wght" 560;
  line-height:1.3;cursor:pointer;text-transform:none;letter-spacing:0;
}
.opt:has(input:checked){border-color:var(--lime);background:rgba(165,199,21,.09)}
.opt input{width:1.05rem;height:1.05rem;margin-top:.1rem;accent-color:var(--lime);flex:none}
.opt small{display:block;opacity:.62;font-size:.82rem;margin-top:.2rem;
  font-family:"Source Serif 4",Georgia,serif;font-variation-settings:normal;font-weight:400}
.estimate{display:flex;align-items:baseline;gap:1rem;flex-wrap:wrap;
  border-top:1px solid var(--rule);margin-top:1.6rem;padding-top:1.6rem}
.estimate b{font-family:"Martian Mono",monospace;font-size:clamp(1.6rem,4vw,2.4rem);
  color:var(--lime);letter-spacing:-.03em;line-height:1}
.light .estimate b{color:#5E7A0E}
.estimate span{font-size:.88rem;opacity:.62;flex:1;min-width:12rem}
.form-note{font-family:"Martian Mono",monospace;font-size:.72rem;opacity:.6;margin-top:1rem}
.form-msg{margin-top:1rem;padding:.8rem 1rem;border-left:2px solid var(--lime);font-size:.92rem;display:none}
.form-msg.on{display:block}
.form-msg.bad{border-left-color:var(--signal)}
/* The bot honeypot. Clipped in place rather than flung to left:-9999px — an
   element parked 9,999px outside the layout is one more thing that can widen
   the document, and it is still a real focusable input either way. Kept out of
   the tab order and off the accessibility tree in the markup. */
.hp{
  position:absolute;width:1px;height:1px;
  margin:-1px;padding:0;border:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;
}

/* ================= the lockup: V becomes VESOPA =================
   Two beats. First the V alone, enlarged and centred in the box, stroking
   itself on. Then it eases back to its own place in the word while the other
   five letters arrive behind it, left to right.

   Everything is `currentColor` except the V's inner stroke, which is the lime
   accent in every official lockup — so the component inherits the ink of
   whatever it is dropped into and needs no light/dark variants. */
.wm{display:block;width:100%;height:auto;overflow:visible}
.wm-l{fill:currentColor}
/* The V arrives as the standalone mark — all lime, the way it reads on its own
   — and only resolves into the lockup as the word lands: outer strokes take
   the section's ink, the inner one keeps the accent. That is the difference
   between favicon.svg and the Logo-01/02 lockups, played as a transition
   rather than picked. */
.wm-v-a,.wm-v-b{fill:var(--lime)}
.wm.settled .wm-v-a{fill:currentColor;transition:fill .55s ease .12s}

/* Before the first beat: nothing. The V is placed and scaled but undrawn, and
   the word is not there at all. */
.wm-v{
  /* User units, so the origin is the V's own centre in the artwork and the
     scale happens about it rather than about the corner of the viewBox. */
  transform-origin:65.695px 169.965px;
  transform:translateX(var(--v-shift)) scale(1.85);
  transition:transform 1.05s cubic-bezier(.55,.06,.2,1);
}
.wm-v polygon{
  fill-opacity:0;
  stroke:var(--lime);stroke-width:1.1;
  /* Comfortably longer than the longest polygon's perimeter (~95 user units);
     the exact figure does not matter as long as it exceeds it. */
  stroke-dasharray:140;stroke-dashoffset:140;
}
.wm-v-b{stroke:var(--lime)}

.wm-word{opacity:0}
.wm-l{
  opacity:0;
  /* Rise and settle. transform-box:fill-box gives each glyph its own box so
     they move about themselves rather than about the viewBox origin. */
  transform-box:fill-box;transform-origin:center;
  transform:translateY(9px) scaleY(.86);
}

/* Beat one — draw the V. */
.wm.drawing .wm-v polygon{
  animation:wm-draw .95s cubic-bezier(.5,0,.2,1) forwards,
            wm-fill .55s ease forwards;
}
.wm.drawing .wm-v polygon:nth-child(1){animation-delay:0s,.62s}
.wm.drawing .wm-v polygon:nth-child(2){animation-delay:.14s,.76s}
.wm.drawing .wm-v polygon:nth-child(3){animation-delay:.28s,.9s}
@keyframes wm-draw{to{stroke-dashoffset:0}}
@keyframes wm-fill{to{fill-opacity:1}}

/* Beat two — the V takes its place and the word arrives. */
.wm.settled .wm-v{transform:none}
/* The stroke is scaffolding for the draw; once the fill is in it is just a
   thickening of every edge, and it has to go before the V rejoins the word or
   the V reads heavier than the five letters beside it. */
.wm.settled .wm-v polygon{stroke-opacity:0;transition:stroke-opacity .4s ease}
.wm.settled .wm-word{opacity:1}
.wm.settled .wm-l{
  opacity:1;transform:none;
  transition:opacity .5s ease,transform .62s cubic-bezier(.16,.7,.3,1);
  /* Left to right, starting as the V is already on its way home so the word
     assembles as one movement rather than two. */
  transition-delay:calc(.16s + var(--i) * .075s);
}

.wm.instant .wm-v{transition:none;transform:none}
.wm.instant .wm-v polygon{animation:none;fill-opacity:1;stroke-opacity:0}
.wm.instant .wm-word,.wm.instant .wm-l{transition:none;opacity:1;transform:none}

/* ================= the loader ================= */
#loader{
  position:fixed;inset:0;z-index:90;
  background:#0B0E0A;color:var(--paper);
  display:flex;align-items:center;justify-content:center;
  transition:opacity .8s ease,visibility .8s;
}
#loader.gone{opacity:0;visibility:hidden;pointer-events:none}
.ld-inner{display:flex;flex-direction:column;align-items:center;gap:1.4rem;padding:2rem;max-width:34rem;text-align:center}
/* The lockup carries the whole opening: the V draws itself, then becomes the
   word. Paper letterforms with the lime accent — brandAssets Logo-02, the
   version made for a dark ground. */
.ld-lockup{width:min(26rem,72vw);color:var(--paper)}

.ld-bar{width:min(15rem,50vw);height:1px;background:rgba(242,239,230,.16);overflow:hidden}
.ld-bar i{display:block;height:100%;background:var(--lime);transform:scaleX(0);transform-origin:left;
  transition:transform .5s cubic-bezier(.16,.7,.3,1)}

.ld-ask{opacity:0;transform:translateY(10px);transition:opacity .6s ease .1s,transform .6s ease .1s}
#loader.asking .ld-ask{opacity:1;transform:none}
#loader.asking .ld-bar{opacity:0;transition:opacity .4s}
.ld-ask-t{font-family:"Archivo",sans-serif;font-variation-settings:"wdth" 114,"wght" 620;
  font-size:clamp(1.3rem,3.4vw,1.9rem);margin-top:.6rem;line-height:1.1}
.ld-ask-s{font-size:.95rem;opacity:.7;margin-top:.7rem;max-width:26rem}
.ld-ask-r{display:flex;gap:.7rem;flex-wrap:wrap;justify-content:center;margin-top:1.6rem}
.ld-go,.ld-skip{
  font-family:"Archivo",sans-serif;font-variation-settings:"wdth" 112,"wght" 600;
  font-size:1rem;padding:.85rem 1.7rem;border-radius:2px;cursor:pointer;border:0;
  background:var(--lime);color:var(--ink);transition:transform .2s}
.ld-skip{background:transparent;color:var(--paper);box-shadow:inset 0 0 0 1px rgba(242,239,230,.24)}
.ld-go:hover,.ld-skip:hover{transform:translateY(-2px)}

/* ================= Vesopa AI ================= */
#ai{position:fixed;right:clamp(.8rem,2.5vw,1.6rem);bottom:clamp(.8rem,2.5vw,1.6rem);z-index:60;
  display:flex;flex-direction:column;align-items:flex-end;gap:.6rem}
.ai-fab{
  display:inline-flex;align-items:center;gap:.5rem;
  font-family:"Archivo",sans-serif;font-variation-settings:"wdth" 112,"wght" 600;
  font-size:.92rem;padding:.75rem 1.15rem;border-radius:999px;border:0;cursor:pointer;
  background:var(--lime);color:var(--ink);
  box-shadow:0 12px 34px -12px rgba(0,0,0,.7);
  transition:transform .2s;
}
.ai-fab:hover{transform:translateY(-2px)}
.ai-fab-dot{width:.5rem;height:.5rem;border-radius:50%;background:var(--ink);
  animation:ai-pulse 2.4s ease-in-out infinite}
@keyframes ai-pulse{0%,100%{opacity:.35;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}
#ai.open .ai-fab{display:none}

/* `hidden` is only a UA `display:none`, so any display declaration here beats
   it and the panel opens itself on load, over the hero. State first. */
.ai-panel[hidden]{display:none}
.ai-panel{
  /* Written by the resize grip and remembered per viewer; these are the
     defaults, not the limits. Wider than it was: at 24rem the bubbles were
     narrower than the words in them and every line wrapped two or three times,
     which is what made the text read as a centred column with air either side. */
  --ai-w:30rem;
  --ai-h:34rem;
  position:relative;
  width:min(var(--ai-w),calc(100vw - 2rem));
  height:min(var(--ai-h),calc(100svh - 6rem));
  /* #ai is itself a flex column, so this panel is a flex item — and a flex
     item's automatic minimum size is its *content*, which quietly overrides
     the height above and let the transcript push the panel to the full height
     of the window. flex:none plus min-height:0 makes the height mean what it
     says. */
  flex:none;min-height:0;
  display:flex;flex-direction:column;
  background:rgba(9,12,9,.94);color:var(--paper);
  border:1px solid rgba(242,239,230,.16);border-radius:8px;
  backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
  box-shadow:0 30px 70px -20px rgba(0,0,0,.8);
  overflow:hidden;
}
/* The resize handle, on the corner furthest from the bottom-right anchor. */
.ai-grip{
  position:absolute;top:0;left:0;width:22px;height:22px;z-index:2;
  cursor:nwse-resize;touch-action:none;
  background:linear-gradient(135deg,var(--lime) 0 2px,transparent 2px 5px,var(--lime) 5px 7px,transparent 7px);
  opacity:.35;border-top-left-radius:8px;transition:opacity .2s;
}
.ai-grip:hover{opacity:.9}
/* While dragging, stop the pointer selecting text or the iframe eating moves. */
#ai.resizing{user-select:none}
#ai.resizing .ai-log{pointer-events:none}

.ai-head{display:flex;align-items:center;justify-content:space-between;
  padding:.85rem 1rem .85rem 1.6rem;border-bottom:1px solid rgba(242,239,230,.12);flex:none}
.ai-title{display:flex;align-items:center;gap:.5rem;
  font-family:"Archivo",sans-serif;font-variation-settings:"wdth" 116,"wght" 640;font-size:.95rem}
.ai-title i{width:.55rem;height:.55rem;border-radius:50%;background:var(--lime);
  box-shadow:0 0 10px var(--lime)}
/* A labelled control rather than a bare glyph: at 1.4rem the × was a 12px hit
   target in the corner of a panel that covers the page on a phone. */
.ai-x{
  display:inline-flex;align-items:center;gap:.4rem;
  background:transparent;color:inherit;cursor:pointer;
  border:1px solid rgba(242,239,230,.2);border-radius:999px;
  font-family:"Martian Mono",monospace;font-size:.62rem;letter-spacing:.06em;
  line-height:1;padding:.4rem .7rem;opacity:.75;
  transition:opacity .2s,border-color .2s,color .2s;
}
.ai-x span[aria-hidden]{font-size:1.05rem;line-height:.8}
.ai-x:hover{opacity:1;border-color:var(--lime);color:var(--lime)}

/* Same trap one level down: without min-height:0 the log will not shrink below
   its transcript, so a long answer pushes the composer off the bottom of the
   panel instead of scrolling inside it. */
.ai-log{flex:1 1 auto;min-height:0;overflow-y:auto;padding:1rem .9rem;
  display:flex;flex-direction:column;gap:.7rem;
  font-size:.94rem;line-height:1.55}
/* The answers are prose and want the width. 88% of a narrow panel, minus the
   padding either side, left barely thirty characters a line. */
.ai-msg{padding:.7rem .9rem;border-radius:10px;max-width:94%;white-space:pre-wrap;word-wrap:break-word;text-align:left}
.ai-bot{max-width:100%}
.ai-bot{background:rgba(242,239,230,.07);border:1px solid rgba(242,239,230,.08);align-self:flex-start;border-bottom-left-radius:3px}
.ai-you{background:var(--lime);color:var(--ink);align-self:flex-end;border-bottom-right-radius:3px}
.ai-msg.bad{border-color:rgba(228,118,27,.5);color:#F0B98A}
.ai-dots{display:inline-flex;gap:.25rem;align-items:center;height:1.2em}
.ai-dots b{width:.32rem;height:.32rem;border-radius:50%;background:currentColor;opacity:.4;
  animation:ai-dot 1.2s ease-in-out infinite}
.ai-dots b:nth-child(2){animation-delay:.15s}
.ai-dots b:nth-child(3){animation-delay:.3s}
@keyframes ai-dot{0%,100%{opacity:.25;transform:translateY(0)}50%{opacity:1;transform:translateY(-3px)}}

/* One row that scrolls sideways, not four stacked chips. Wrapped, these took
   127px out of a 544px panel — a quarter of the dock spent on prompts nobody
   had asked for yet. */
.ai-sugg{
  display:flex;gap:.35rem;padding:.1rem 1rem .6rem;flex:none;
  overflow-x:auto;scrollbar-width:none;
}
.ai-sugg::-webkit-scrollbar{display:none}
.ai-chip{white-space:nowrap;flex:none}
.ai-chip{
  font-family:"Martian Mono",monospace;font-size:.62rem;letter-spacing:.02em;
  background:transparent;color:inherit;cursor:pointer;
  border:1px solid rgba(242,239,230,.2);border-radius:999px;padding:.35rem .65rem;
  transition:border-color .2s,color .2s;
}
.ai-chip:hover{border-color:var(--lime);color:var(--lime)}

.ai-form{display:flex;gap:.5rem;padding:.7rem 1rem;border-top:1px solid rgba(242,239,230,.12);flex:none}
.ai-in{
  flex:1;min-width:0;font:inherit;
  /* 16px minimum, or iOS Safari zooms the whole viewport on focus. */
  font-size:16px;
  color:var(--paper);background:rgba(242,239,230,.06);
  border:1px solid rgba(242,239,230,.14);border-radius:999px;padding:.6rem .9rem;
}
.ai-in:focus{outline:1px solid var(--lime)}
.ai-send{
  flex:none;width:2.4rem;height:2.4rem;border-radius:50%;border:0;cursor:pointer;
  background:var(--lime);color:var(--ink);font-size:1.1rem;line-height:1;
}
.ai-form.busy .ai-send{opacity:.4;pointer-events:none}
.ai-foot{font-family:"Martian Mono",monospace;font-size:.56rem;opacity:.45;
  padding:0 1rem .7rem;margin:0;text-align:center}

/* ================= responsive ================= */
@media (max-width:1000px){
  .stage-grid{grid-template-columns:1fr;gap:1.6rem;max-width:38rem}
  /* On one column the picture leads and the words follow, in both directions —
     a flipped section that puts the copy first on a phone reads as a mistake. */
  .stage-grid.flip .stage-copy,.stage-copy{order:2}
  .stage-grid.flip .stage-shots,.stage-shots{order:1}
  .stage{padding:8vh 5vw 4vh;align-items:center}
  .stage-copy{text-align:center}
  .stage-copy p{margin-inline:auto}
  .store-row{justify-content:center;margin-top:1.4rem}
  .showcase{min-height:calc(100svh + 150svh)}

  /* One column has to fit a screenshot, a heading, a paragraph and an install
     button into one viewport. A ratio-driven frame cannot: at 4/3 on a 390px
     phone it is 260px of the 664 available before the copy has had any, so the
     stage overflowed its own height and pushed the picture up under the fixed
     brand bar. Give the frame a share of the viewport instead of a shape, and
     let object-fit:contain letterbox whatever ratio the screenshot happens to
     be — nothing is cropped and the total always fits. */
  .frame{aspect-ratio:auto;height:34svh;flex:none}
  .stage-grid{align-content:center}
  .stage-copy h2{font-size:clamp(1.6rem,5.6vw,2.2rem)}
  .stage-copy p{font-size:.95rem;margin-top:.9rem}
  .stage-copy .num{margin-bottom:1rem}
  .stage-shots{gap:.7rem}
}
@media (max-width:900px){ body{font-size:18px} }
@media (max-width:640px){
  body{font-size:17px}
  main > section{padding:12vh 6vw}
  .beat{grid-template-columns:2.4rem 1fr;gap:1rem;padding:1.8rem 0}
  #plate{opacity:.2}
  .facts{gap:1.6rem 2rem}
  .fact b{font-size:1.6rem}
  .mark{padding:.7rem 4vw}
  /* Labels off, icons on: two words plus the wordmark do not fit a phone. */
  .chip-btn .lbl{display:none}
  .chip-btn{padding:.45rem .55rem}
  .stage{padding:7vh 5vw 2.5vh}
  .frame{height:27svh}
  .stage-copy h2{font-size:clamp(1.5rem,6.2vw,2rem)}
  .stage-copy p{font-size:.92rem;line-height:1.45}
  .cta{padding:.8rem 1.4rem;font-size:.98rem;margin-top:1.8rem}
  /* Inside the pinned stage every pixel is contested, and the secondary link
     is the one thing here that is said again elsewhere — every one of these
     subdomains gets a full-width door of its own in "The whole stack". The
     install button is what this section exists for; it stays. */
  .store-row .cta.ghost{display:none}
  .store-row{margin-top:1.1rem}
  .store-row .cta{margin-top:0}
  /* The Customer Display carries two paragraphs where the stepped sections
     carry one at a time, so its picture gets the smaller share. It has no dot
     row to give back, which is where the other two found their room. */
  .showcase.single .frame{height:22svh}
  /* On a phone the dock takes the screen. A 30rem floating panel on a 390px
     device is the whole width anyway, and a half-height sheet leaves the
     answer scrolling inside a box inside a page. Full screen, one obvious way
     out, and the remembered desktop size deliberately ignored. */
  .ai-panel{
    position:fixed;inset:0;
    width:100%;height:100%;height:100svh;
    max-width:none;border:0;border-radius:0;
    background:rgba(7,9,7,.98);
  }
  .ai-grip{display:none}
  .ai-head{padding:1rem 1rem 1rem 1.1rem}
  .ai-x{padding:.55rem .9rem;font-size:.68rem}
  .ai-log{padding:1rem;font-size:1rem}
  .ai-msg{max-width:96%}
  .ai-sugg{padding:0 1rem .7rem}
  .ai-form{padding:.8rem 1rem calc(.8rem + env(safe-area-inset-bottom))}
  .motion-pair{gap:1rem}
  .motion-holder.tall{max-width:12rem}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  body{transition:none}
  #plate{transition:none}
  /* The clip itself stays. `display:none` here is what stopped the backdrop
     ever appearing on an iPad in Low Power Mode, which reports this preference
     for a battery setting rather than an accessibility one — see the note in
     stage.js. What reduced motion takes away is the *movement between* clips:
     the cross-fade becomes a cut. */
  .bd-clip{transition:none}
  .bd-clip video{transition:none}
  .cue i{animation:none}
  #spotlight{transition:none}
  [data-reveal]{opacity:1;transform:none;transition:none}
  .ring{display:none}
  .wm-v{transition:none;transform:none}
  .wm-v polygon{animation:none;fill-opacity:1;stroke-opacity:0}
  .wm-word,.wm-l{transition:none;opacity:1;transform:none}
  .ai-fab-dot{animation:none;opacity:.7}
  .shot,.step{transition:opacity .2s linear}
}
