/* ------------------------------------------------------------------
   Gloo · landing
   Mobile-first. Every section is one viewport. Content builds in.
   ------------------------------------------------------------------ */

/* Helvetica Now (licensed). Drop the two .woff2 files into assets/fonts/
   and these load; until then the stack falls back to Helvetica Neue Medium,
   which every Apple device has. */
@font-face{
  font-family:"Helvetica Now Display";
  src:url("../assets/fonts/HelveticaNowDisplay-Medium.woff2") format("woff2");
  font-weight:500; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Helvetica Now Text";
  src:url("../assets/fonts/HelveticaNowText-Medium.woff2") format("woff2");
  font-weight:500; font-style:normal; font-display:swap;
}

:root{
  --white:#fff;
  --card:#fcfcfb;
  --ink:#000;
  --ink-30:rgba(0,0,0,.3);
  --pill:#a8cbef;
  --hair:#e6e6e4;

  --display:"Helvetica Now Display","Helvetica Neue",Helvetica,Arial,sans-serif;
  --text:"Helvetica Now Text","Helvetica Neue",Helvetica,Arial,sans-serif;

  --fs-display:clamp(27px, 7.4vw, 48px);
  --fs-text:clamp(15px, 4.1vw, 24px);

  --gutter:24px;
  --r:28px;
  --shadow:0 2px 6px rgba(0,0,0,.05), 0 16px 40px -14px rgba(0,0,0,.16);
  --shadow-soft:0 2px 6px rgba(0,0,0,.05), 0 14px 30px -10px rgba(0,0,0,.14), 0 34px 64px -26px rgba(0,0,0,.20);

  --safe-top:max(env(safe-area-inset-top, 0px), 46px);
  --safe-bottom:env(safe-area-inset-bottom, 0px);
  --chip-bottom:calc(env(safe-area-inset-bottom, 0px) + 26px);

  /* The header reserve. NOT the logo's aspect (that is 1172/3253 = .3603) —
     this is a deliberate over-reserve so the header band has air under the
     mark. Change it and every section's block padding moves. */
  --logo-w:clamp(150px, 46vw, 220px);
  --head:calc(var(--safe-top) + 10px + var(--logo-w) * .507 + 10px);

  --ease:cubic-bezier(.22,1,.36,1);
}

*,*::before,*::after{box-sizing:border-box}
html{
  background:var(--white);
  scroll-snap-type:y mandatory;
  scroll-behavior:smooth;
  -webkit-text-size-adjust:100%;
  text-size-adjust:100%;
  scrollbar-width:none;        /* no rule down the right edge, on any surface */
  -ms-overflow-style:none;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar{width:0;height:0;display:none}
body{
  margin:0;
  background:var(--white);
  color:var(--ink);
  font-family:var(--text);
  font-weight:500;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img,video{display:block;max-width:100%}
h2,p,figure,ul{margin:0}
ul{padding:0;list-style:none}
a{color:inherit}

/* ---- type ---- */
.display{
  font-family:var(--display);
  font-weight:500;
  font-size:var(--fs-display);
  line-height:1.055;  /* tight. the pill below is sized to still clear the line above */
  letter-spacing:-.02em;
  text-wrap:balance;
  max-width:26ch;
}
.text{
  font-family:var(--text);
  font-weight:500;
  font-size:var(--fs-text);
  line-height:1.06;
  letter-spacing:-.02em;
}
.muted{color:var(--ink-30)}

/* ---- the logo: one element, hero → header, driven by scroll ---- */
#logo-clip{
  position:fixed;
  inset:0 0 auto;
  height:100svh;
  overflow:hidden;      /* the hero logo bleeds past both edges; this keeps it off the scrollbar */
  pointer-events:none;
  z-index:10;
}
#logo{
  position:absolute;
  pointer-events:auto;
  left:50%;
  top:0;
  width:var(--logo-w);
  transform-origin:50% 0;
  transform:translate3d(-50%,0,0);
  will-change:transform;
  text-decoration:none;
}
#logo picture{
  display:block;
  opacity:0;
  filter:blur(14px);
  transform:scale(.97);
  transition:opacity 1.3s var(--ease),filter 1.3s var(--ease),transform 1.3s var(--ease);
}
.ready #logo picture{opacity:1;filter:none;transform:none}
#logo img{width:100%;height:auto}
.logo-float{display:block;animation:float 7s ease-in-out infinite}
@keyframes float{
  0%,100%{transform:translate3d(0,0,0)}
  50%{transform:translate3d(0,-3px,0)}
}

/* ---- sections ---- */
main{display:block}
.s{
  position:relative;
  height:100svh;
  min-height:100svh;
  overflow:hidden;
  scroll-snap-align:start;
  padding:calc(var(--head) * .5) var(--gutter);
  display:grid;
  place-items:center;
  text-align:center;
  background:var(--white);
}

/* build-in primitives. --d = duration, --dl = delay, set inline per element
   so different pieces arrive at different speeds. */
.b{
  opacity:0;
  translate:0 22px;
  scale:.985;
  filter:blur(6px);
  transition:
    opacity var(--d,.9s) var(--ease) var(--dl,0s),
    translate var(--d,.9s) var(--ease) var(--dl,0s),
    scale var(--d,.9s) var(--ease) var(--dl,0s),
    filter var(--d,.9s) var(--ease) var(--dl,0s);
  will-change:opacity,translate,scale,filter;
}
.b.nb{filter:none;transition:opacity var(--d,.9s) var(--ease) var(--dl,0s),translate var(--d,.9s) var(--ease) var(--dl,0s),scale var(--d,.9s) var(--ease) var(--dl,0s)}
.in .b{opacity:1;translate:0 0;scale:1;filter:none}

/* words arrive one after another, and scatter when the screen leaves */
.display .w{
  display:inline-block;
  opacity:0;
  translate:0 .38em;
  filter:blur(5px);
  transition:
    opacity .75s var(--ease) calc(var(--i,0) * 38ms + .08s),
    translate .95s var(--ease) calc(var(--i,0) * 38ms + .08s),
    filter .75s var(--ease) calc(var(--i,0) * 38ms + .08s);
}
.in .display .w{opacity:1;translate:0 0;filter:none}

/* leaving: scroll-driven. Each word lifts off on its own vector at its own
   speed, so the line comes apart rather than fading. */
.display .ch{display:inline-block;white-space:pre;font-style:normal}
/* The hands-free line is a scroll STAGE (#handsfree), not a snap screen. A snap
   jump lasts ~400ms and the whole exit had to fit inside it, which is why it read
   fast and clunky. As a stage it owns two viewports of scroll: the glow opens,
   holds, and only then carries the letters out to every corner and edge. */
.stage.s-line{
  display:grid;place-items:center;text-align:center;
  padding:calc(var(--head) * .5) var(--gutter);
  background:var(--white);
}
.s-line .display{position:relative}
.s-line .display .w{position:relative;z-index:1}
.bloom{
  position:absolute;left:50%;top:50%;
  width:min(74vw, 520px);aspect-ratio:1;
  translate:-50% -50%;
  border-radius:50%;
  background:radial-gradient(circle closest-side,
    rgba(168,203,239,.95) 0%, rgba(168,203,239,.72) 34%, rgba(168,203,239,.28) 58%, rgba(168,203,239,0) 78%);
  filter:blur(26px);
  opacity:0;
  scale:.12;
  z-index:0;
  pointer-events:none;
}
/* the glow arrives first, holds at full strength through the middle of the
   stage, and only fades once the letters are already on their way out */
.s-line.leaving .bloom{
  --g:var(--e, 0);
  transition:none;
  opacity:calc(
    clamp(0, calc(var(--g) / .12), 1) *
    (1 - clamp(0, calc((var(--g) - .62) / .34), 1))
  );
  scale:calc(.10 + var(--g) * 2.05);
  will-change:opacity,scale;
}
/* the letters wait for the glow, then leave for every corner and every edge */
.s-line.leaving .display .ch{
  --t:clamp(0, calc((var(--e, 0) - .20 - var(--cl, 0)) / .66), 1);
  --te:calc(var(--t) * (2 - var(--t)));            /* ease out */
  transition:none;
  translate:calc(var(--cx, 0vw) * var(--te)) calc(var(--cy, 0vh) * var(--te));
  rotate:calc(var(--cr, 0deg) * var(--te));
  opacity:calc(1 - var(--t) * var(--t));
  filter:blur(calc(var(--t) * 7px));
  will-change:translate,opacity;
}
.s-line.leaving .display .w{filter:none}

/* leaving a snapped section: each word drifts off on its own vector */
.out .display .w{
  opacity:0;
  translate:var(--rx, 0em) var(--ry, -.5em);
  rotate:var(--rr, 0deg);
  filter:blur(14px);
  transition:
    opacity var(--rd, .5s) cubic-bezier(.4,0,.7,.2) var(--rdl, 0s),
    translate var(--rd, .5s) cubic-bezier(.35,0,.6,.35) var(--rdl, 0s),
    rotate var(--rd, .5s) cubic-bezier(.35,0,.6,.35) var(--rdl, 0s),
    filter var(--rd, .5s) linear var(--rdl, 0s);
}

/* the highlight pill draws in after the line has landed */
.display mark{
  position:relative;
  isolation:isolate;
  background:none;
  color:inherit;
  padding:0 .16em;
  margin:0 -.16em;
}
.display mark::before{
  content:"";
  position:absolute;
  inset:.135em 0 .075em;    /* hugs the cap height, not the line box */
  background:var(--pill);
  border-radius:.32em;
  z-index:-1;
  transform:scaleX(0);
  transform-origin:left center;
  transition:transform .6s var(--ease) .9s;
}
.in .display mark::before{transform:scaleX(1)}

/* ---- 000 hero ---- */
.s-hero{padding-top:0}
.cue{
  position:absolute;
  left:50%;
  bottom:calc(var(--safe-bottom) + 30px);
  width:4px;height:4px;
  margin-left:-2px;
  border-radius:50%;
  background:var(--ink);
  opacity:0;
  animation:cue 2.6s var(--ease) 1.6s infinite;
}
@keyframes cue{
  0%{opacity:0;transform:translateY(-10px)}
  25%{opacity:.35}
  70%{opacity:0;transform:translateY(12px)}
  100%{opacity:0;transform:translateY(12px)}
}

/* ---- 200 / 600 single lines ---- */
.s-line .display{max-width:22ch}

/* ---- 200–500 · the tether parts ------------------------------------
   One sticky stage, three snap stops. Scroll drives a single --p (0→1):
   the keyboard climbs out of frame, the mouse sinks out of frame, and the
   line is left alone in the gap between them.                           */
.stagewrap{
  position:relative;
  height:300svh;
  background:var(--white);
}
.stage{
  position:sticky;
  top:0;
  height:100svh;
  overflow:hidden;
  --p:0;
}
/* ------------------------------------------------------------------
   The tether reveal — paced, and built in FLOW.

   Two things are load-bearing here and neither is the animation:

   1. Keyboard, line and mouse are three grid ROWS in normal order. Not
      absolutely positioned. With absolute positioning an overlap is always one
      failed declaration away, and this screen printed the headline across the
      keyboard four separate times on a phone. In flow, overlap is not a state
      this markup can reach — whatever the CSS does or doesn't compute.
   2. Nothing is clipped and nothing collapses. The line always occupies its
      full row; the objects are pulled over that space with `transform`, which
      does not touch layout.

   Across the stage's --p:
      0   → .26   nothing. Just the two objects, ~20px apart.
      .26 → .60   they release back into their rows, opening the space.
      .62 → .76   the line fades and unblurs in.
      .60 → .82   the objects are pushed off the top and bottom of the frame.
      .82 → 1     NOTHING. The finished line, alone, for the last fifth of the
                  stage — so a firm swipe cannot carry you past the payoff the
                  instant it lands. The two beats worth stopping on (objects
                  alone, line alone) carry `scroll-snap-stop: always`; the
                  middle one does not, so the reveal still scrubs.
   Nothing transitions; every frame is a function of scroll position, so the
   reader sets the pace and can hold anywhere inside it — including under
   reduced motion, where scroll-driven is exactly the kind of motion that is
   fine, because the reader is the one moving it.
   ------------------------------------------------------------------ */
.s-tether{
  display:grid;place-items:center;text-align:center;
  padding:calc(var(--head) * .5) var(--gutter);
  background:var(--white);
  --q:clamp(0, calc((var(--p, 0) - .26) / .34), 1);
  --qe:calc(var(--q) * (2 - var(--q)));
  --x:clamp(0, calc((var(--p, 0) - .60) / .22), 1);   /* the exit */
  --xe:calc(var(--x) * var(--x));                     /* ease in, it accelerates away */
}
.tether-grid{
  display:grid;
  justify-items:center;
  align-content:center;
  gap:clamp(24px, 7svh, 84px);     /* always open — nothing here ever collapses */
  width:100%;
}
.obj{
  display:block;
  height:auto;                   /* never the HTML height attribute */
  max-width:100%;
}
/* 1400 × 380 */
.obj-keyboard{width:min(84vw, 580px)}
/* 420 × 666 */
.obj-mouse{width:min(17vw, 110px)}

/* NOTHING IS CLIPPED. The line always occupies its full row at full size; the
   objects are pulled over that space with `transform`, which does not touch
   layout. `--pull` is measured by JS as the distance that leaves them 20px
   apart. Two consequences worth keeping:
     · a clip can never shave a descender, which is what made `keyboard` read
       as `kevboard` — tight leading puts the ink outside the line box, so any
       max-height cut to the box cuts the letters;
     · if --pull or --qe fail to compute, the transform is invalid, the objects
       sit in their rows and the screen reads. */
.s-tether.armed .obj-keyboard{
  transform:translate3d(0, calc(var(--pull, 0px) * (1 - var(--qe)) + var(--xe) * -72svh), 0);
}
.s-tether.armed .obj-mouse{
  transform:translate3d(0, calc(var(--pull, 0px) * (var(--qe) - 1) + var(--xe) * 72svh), 0);
}

.stage-line{
  max-width:22ch;
  text-align:center;
  margin:0;
  padding-block:clamp(10px, 2.2svh, 22px);
}
/* the line is visible by default; an armed stage hands it to scroll */
/* The row finishes opening at --p .74; the words start at .76. So the clip is
   never something you see happen — by the time there is any ink, the box it
   sits in has stopped moving. Blur and fade, nothing else. */
.s-tether.armed .stage-line .w{
  --wq:clamp(0, calc((var(--p, 0) - .62 - var(--i, 0) * .007) / .13), 1);
  transition:none;
  opacity:var(--wq);
  translate:none;
  filter:blur(calc((1 - var(--wq)) * 6px));
}

/* A stage is not a `.s`, so the `.in` observer never touches it and its
   headline would stay at the build-in's opacity:0 forever. `.lit` is how a
   stage says "I am on screen" — js/site.js sets it on #handsfree and #modes.
   Delete this rule and two screens go blank. */
.stage.lit .display .w{opacity:1;translate:0 0;filter:none}


/* three invisible snap stops laid over the stage: 1302 / 1303 / 1304 */
.steps{position:absolute;inset:0;pointer-events:none}
.step{height:100svh;scroll-snap-align:start}
.step.stop{scroll-snap-stop:always}

/* ---- 700–900 · one panel, three states -----------------------------
   The sentence never moves. The glyph, the highlighted word and the clip
   cross-fade as --p walks the three snap stops.                        */
.stage.s-modes{
  display:grid;
  grid-template-rows:minmax(0,1fr) auto minmax(0,1fr);
  justify-items:center;
  padding:calc(var(--head) * .5) var(--gutter);
  text-align:center;
}
.glyphs{
  position:relative;
  width:clamp(48px, 13vw, 72px);
  aspect-ratio:1;
  align-self:end;
  margin-bottom:clamp(18px, 3.4svh, 30px);
}
.glyph{
  position:absolute;
  inset:0;
  border-radius:12%;
  background:var(--ink);
  display:grid;
  place-items:center;
  opacity:0;
  scale:.92;
  transition:opacity .45s var(--ease), scale .45s var(--ease);
}
.glyph.on{opacity:1;scale:1}
.glyph svg{width:100%;height:100%;fill:#fff}

.s-modes .display{align-self:center;max-width:24ch}
.s-modes .vcard{align-self:start;margin-top:clamp(18px, 3.4svh, 30px)}

/* No pill. One soft orb travels between the three words, the same blue
   cursor the app cards use — so the reading is consistent: wherever the
   glow rests is where Gloo is listening. The marks carry no paint at all
   now; they are just the targets the glow is aimed at from JS. */
.display mark.hl{
  background:none;
  color:inherit;
  padding:0 .16em;
  margin:0 -.16em;
}
.s-modes .display{position:relative}
/* Behind the type, so the words stay black and legible while the light
   sits under them. Travel is slow and eased — it should read as one lamp
   moving, never as three lamps switching. */
/* the look is declared again after .gl.rest below, so it wins */
.gl.gl-mode{
  z-index:0;
  transition:left .72s var(--ease), top .72s var(--ease),
             width .72s var(--ease), height .72s var(--ease),
             opacity .5s ease;
}
/* The stage clips, as every stage must — the tether throws its objects off
   frame and the carousel runs wide. A blurred box spills past its own edge,
   so on the last word of a line that spill met the clip and the glow came
   out with a hard vertical cut. The orb below paints its falloff INSIDE its
   own box instead, which leaves nothing to clip; this is the second belt. */
.stage.s-modes{overflow:visible}
.s-modes .display .w,.s-modes .display mark.hl{position:relative;z-index:1}
.stage.s-modes:not(.lit) .gl-mode{opacity:0 !important}

/* gaze: the pair scans, saccade-fast with a dwell at each end */
.eyes{animation:gaze 5.2s cubic-bezier(.62,0,.2,1) infinite}
@keyframes gaze{
  0%,14%   {transform:translateX(0)}
  22%,40%  {transform:translateX(6px)}
  48%,66%  {transform:translateX(-6px)}
  74%,100% {transform:translateX(0)}
}

/* gesture: five fingertips. index and thumb pinch, the curled three hold still */
.ft{transform-box:fill-box;transform-origin:center}
.ft-index{animation:pinch-i 2.9s cubic-bezier(.4,0,.16,1) infinite}
.ft-thumb{animation:pinch-t 2.9s cubic-bezier(.4,0,.16,1) infinite}
@keyframes pinch-i{
  0%,26%   {transform:translate(0,0) scale(1)}
  46%,60%  {transform:translate(-5.5px,7.6px) scale(1.12)}
  84%,100% {transform:translate(0,0) scale(1)}
}
@keyframes pinch-t{
  0%,26%   {transform:translate(0,0) scale(1)}
  46%,60%  {transform:translate(5.5px,-7.6px) scale(1.12)}
  84%,100% {transform:translate(0,0) scale(1)}
}

/* voice: three bars on a live level, each on its own period */
.bar{transform-box:fill-box;transform-origin:center;will-change:transform}
.b1{animation:lvl 1.15s ease-in-out infinite}
.b2{animation:lvl 0.82s ease-in-out .14s infinite}
.b3{animation:lvl 1.34s ease-in-out .29s infinite}
@keyframes lvl{
  0%,100%{transform:scaleY(.5)}
  50%    {transform:scaleY(1.18)}
}
.s-mode .display{max-width:24ch}
.vcard{
  width:min(34vw, 150px, calc(23svh * .82));
  aspect-ratio:172/210;
  border-radius:var(--r);
  overflow:hidden;
  box-shadow:var(--shadow);
  background:#e9eef4;
  isolation:isolate;
  transform:translateZ(0); /* keeps the radius clipping the video on Safari */
}
.vcard video,.vcard .clip-alt{width:100%;height:100%;object-fit:cover}
/* the small card holds all three clips and cross-fades between them */
.vcard .clip,.vcard .clip-alt{position:absolute;inset:0;opacity:0;transition:opacity .6s var(--ease)}
.vcard .clip.on,.vcard .clip-alt.on{opacity:1}

/* The Low Power Mode fallback. iOS refuses to run <video> at all on a
   phone in Low Power Mode — autoplay and script play() alike — but it has
   no such objection to an animated image. So when playback does not start,
   the page swaps every card to an animated WebP of the same clip. The
   twins carry no src until that happens, so nobody else pays for them.
   display:none, not opacity, so the hidden half leaves the a11y tree and
   the clip is not announced twice. */
.vcard .clip-alt{display:none}
html.no-video .vcard .clip-alt{display:block}
html.no-video .vcard .clip{display:none}

/* ---- 1000–1200 in your apps · layered, and scroll-jacked -----------
   The stage holds still while --p drives the carousel sideways.        */
.stage.s-apps{
  display:flex;
  flex-direction:column;
  align-items:center;
  text-align:left;
  padding:var(--head) var(--gutter) calc(var(--safe-bottom) + 22px);
  gap:0;
}
/* the caption sits behind and to the left; the video defines the row height */
.apps-head{
  position:relative;
  width:100%;
  max-width:560px;
  z-index:3;
  margin-top:clamp(10px, 3.2svh, 40px);
}
/* one caption per modality, cross-fading with the active card. They are
   stacked, so the block never reflows as the words change length. */
.apps-head .caps{
  position:absolute;
  left:0;
  top:.15em;
  width:40%;
  max-width:11ch;
  z-index:2;
}
.caps .cap{
  margin:0;
  font-size:clamp(11px, 3.1vw, 15px);
  line-height:1.12;
  transition:opacity .45s var(--ease);
}
.caps .cap:not(:first-child){position:absolute;inset:0}
.caps .cap{opacity:0}
.caps .cap.on{opacity:1}
.vcard-sm{
  margin-left:auto;
  margin-right:0;
  width:min(52vw, 264px, calc(40svh * .82));
  aspect-ratio:348/424;
  position:relative;
  z-index:3;
}
/* the cards slide up under the video so the two layers interlock */
.cards{
  position:relative;
  z-index:1;
  display:flex;
  gap:14px;
  --centre-l:calc((100vw - min(88vw, 460px)) / 2);
  width:calc(100% + var(--gutter) * 2);
  margin:min(-9svh, -58px) calc(var(--gutter) * -1) auto;
  padding:56px 0 74px var(--centre-l);
  overflow:visible;
  transform:translate3d(calc(-1 * var(--tx, 0px)), 0, 0);
  will-change:transform;
}
.card{
  flex:0 0 min(88vw, 460px);
  aspect-ratio:537/394;
  scroll-snap-align:start;
  background:var(--card);
  border-radius:var(--r);
  box-shadow:var(--shadow-soft);
  padding:min(5.8vw, 30px) min(3.87vw, 20px) min(5.28vw, 28px);
  display:flex;
  flex-direction:column;
  gap:0;
  position:relative;
  overflow:hidden;
  opacity:.3;
  transition:opacity .55s var(--ease);
}
/* only the module in focus is live */
.card.on{opacity:1}
.tile{width:min(11.7vw, 61px);height:auto;aspect-ratio:1;flex:0 0 auto;align-self:flex-start;object-fit:contain}

/* the UI fragment fills the rest of the card */
.ui{margin-top:auto;width:100%;letter-spacing:-.012em}
.mail{margin-top:min(4.75vw, 25px)}

/* caret, shared by all three */
.caret{
  display:inline-block;
  width:.085em;min-width:1px;
  height:1.02em;
  margin:0 0 -.16em .05em;
  background:currentColor;
  opacity:0;
  vertical-align:baseline;
}
.typing .caret{opacity:.85;animation:blink 1.06s steps(1,end) infinite}
@keyframes blink{0%,49%{opacity:.85}50%,100%{opacity:0}}

/* --- mail --- */
.mail{font-size:clamp(11px, 3.35vw, 15px);line-height:1.26}
.mail p{margin:0 0 .66em}
.mail p:last-child{margin:0}
.mail .sig{opacity:0;transition:opacity .5s var(--ease)}
.mail.done .sig{opacity:1}
/* No fill. The glow IS the highlight on this card — a solid pill competes with
   it and reads as two different indicators for one thing. The mark stays only
   as the box the glow settles onto. */
.mail mark.target{
  background:none;
  color:inherit;
  padding:.09em .3em;margin:0 -.3em;
  -webkit-box-decoration-break:clone;box-decoration-break:clone;
}

/* --- coding agent --- */
.term{display:flex;flex-direction:column;gap:.62em;font-size:clamp(9.5px, 2.85vw, 13px)}
.chips{display:flex;gap:.44em}
.chip{
  display:inline-flex;align-items:center;gap:.42em;
  background:#f2f2f0;border-radius:.55em;padding:.36em .62em;color:#3a3a38;white-space:nowrap;
}
.chip svg{width:1.05em;height:1.05em;flex:none;fill:none;stroke:#6c6c69;stroke-width:1.3;stroke-linecap:round;stroke-linejoin:round}
.chip-sq{padding:.36em .5em}
.field{
  background:#fff;border:1px solid var(--hair);border-radius:.72em;
  padding:.6em .7em;display:flex;justify-content:space-between;align-items:center;gap:.6em;
  color:#111;min-height:2.7em;
}
.ftext{min-width:0;overflow:hidden;white-space:nowrap}
.ret{color:#a8a8a5;flex:none}
.trow{display:flex;justify-content:space-between;color:#9a9a97;font-size:.92em}
.trow .tr{display:inline-flex;align-items:center;gap:.4em}
.ring{display:inline-block;width:.82em;height:.82em;border-radius:50%;border:1.3px solid #cfcfcd}

/* --- browser --- */
/* ------------------------------------------------------------------
   The glow as the input cursor.

   One element per card, moved and sized by js/site.js onto whatever the
   modality is acting on. The three read differently on purpose, because the
   three inputs feel different:

     voice   it rides the caret while the sentence is dictated, then settles
             over the finished sentence. Speech lands as a continuous stream.
     gaze    it SACCADES — a hard 150ms jump, then a long dwell. Eyes do not
             glide between targets, they snap and fixate, and gliding here
             read as a mouse pointer.
     gesture it drifts in on a slow hand-like curve and PINCHES: contracts,
             throws a ring, and the page responds.
   ------------------------------------------------------------------ */
.ui{position:relative}
.gl{
  position:absolute;left:0;top:0;width:0;height:0;
  border-radius:50%;
  background:radial-gradient(closest-side,
    rgba(74,148,238,.62) 0%, rgba(74,148,238,.32) 52%, rgba(74,148,238,0) 80%);
  filter:blur(8px);
  opacity:0;
  pointer-events:none;
  z-index:3;
}
/* settled over a finished, wrapped sentence a radial falloff leaves the second
   line thin — a blurred block covers both evenly */
.gl.rest{
  background:rgba(74,148,238,.34);
  border-radius:.55em;
  filter:blur(13px);
}
/* The sentence orb, matched to the card cursors above: same hot core, same
   falloff. It was a flat box under a 19px blur before, which spread the blue
   so thin it read grey at display size. A gradient keeps the density in the
   middle where the word is, and needs only a little blur to kill the banding
   — so it stays bright AND stops spilling outside its box. */
.gl.rest.gl-mode{
  background:radial-gradient(closest-side,
    rgba(74,148,238,.62) 0%,
    rgba(74,148,238,.40) 46%,
    rgba(74,148,238,.13) 68%,
    rgba(74,148,238,0)   84%);
  border-radius:0;
  filter:blur(7px);
}
.gl-voice{filter:blur(6px);transition:left .14s linear, top .18s var(--ease), width .14s linear, height .18s var(--ease), opacity .5s ease}
.gl-gaze {transition:left .15s cubic-bezier(.7,0,.2,1), top .15s cubic-bezier(.7,0,.2,1), width .15s cubic-bezier(.7,0,.2,1), height .15s cubic-bezier(.7,0,.2,1), opacity .35s ease}
.gl-hand {transition:left .7s cubic-bezier(.3,.9,.25,1), top .7s cubic-bezier(.3,.9,.25,1), width .7s cubic-bezier(.3,.9,.25,1), height .7s cubic-bezier(.3,.9,.25,1), opacity .5s ease}
/* the pinch: the glow tightens and throws one ring */
.gl-hand.pinching{transition-duration:.16s,.16s,.16s,.16s,.16s}
.pinch{
  position:absolute;inset:0;border-radius:inherit;
  border:1.5px solid rgba(74,148,238,.75);
  opacity:0;
}
.gl-hand.pinching .pinch{animation:pinchring .5s cubic-bezier(.2,.8,.3,1)}
@keyframes pinchring{
  0%  {opacity:.9;transform:scale(.72)}
  70% {opacity:.25;transform:scale(1.28)}
  100%{opacity:0;transform:scale(1.42)}
}

/* the search results the gesture pulls up */
.results{
  list-style:none;margin:.62em 0 0;padding:0;
  display:grid;grid-template-columns:repeat(5, minmax(0, 1fr));gap:.4em;
  font-size:.74em;
}
.results li{
  opacity:0;translate:0 .5em;
  transition:opacity .5s var(--ease) calc(var(--n) * 70ms),
             translate .6s var(--ease) calc(var(--n) * 70ms);
}
.web.searched .results li{opacity:1;translate:0 0}
.results img{
  display:block;width:100%;height:auto;aspect-ratio:318/422;object-fit:cover;
  border-radius:.34em;background:#f1f1ef;
}
.results b{display:block;margin-top:.3em;font-weight:500;color:#2b2b2a;letter-spacing:-.02em;
  white-space:nowrap;overflow:hidden}
.results span{display:none;color:#a6a6a3;font-size:.82em;letter-spacing:-.02em;
  white-space:nowrap;overflow:hidden;text-overflow:clip}

/* clicking a result opens it. The tile's own box is handed to .zoom, then one
   class moves it to the full results area — a hero transition, so the thing you
   picked is visibly the thing that opened. */
.zoom{
  position:absolute;margin:0;padding:0;
  border-radius:.4em;overflow:hidden;
  background:#f2f2f0;
  opacity:0;pointer-events:none;z-index:2;
  display:flex;align-items:center;gap:.9em;
  transition:left .52s var(--ease), top .52s var(--ease),
             width .52s var(--ease), height .52s var(--ease), opacity .3s ease;
}
.zoom.open{opacity:1}
/* contain, not cover: a product view shows the whole garment */
.zoom img{width:auto;height:100%;object-fit:contain;flex:none;display:block}
.zoom figcaption{flex:1;min-width:0;padding-right:1em;line-height:1.25;
  font-size:clamp(9px, 2.6vw, 12px)}
.zoom b{display:block;font-weight:500;color:#1d1d1c;letter-spacing:-.02em;font-size:1.2em}
.zoom span{display:block;color:#8d8d8a;margin-top:.15em}

.web{padding-bottom:1.5%;display:flex;flex-direction:column}
.search{
  display:flex;align-items:center;gap:.55em;
  border:1px solid var(--hair);border-radius:999px;background:#fff;
  padding:0 .42em 0 .78em;
  height:clamp(31px, 9.4vw, 44px);
  font-size:clamp(9.5px, 2.9vw, 13.5px);
  color:#1b1b1a;
}
.search .plus{color:#4a4a48;font-size:1.25em;line-height:1;flex:none}
.search .q{flex:1;min-width:0;overflow:hidden;white-space:nowrap;color:#2b2b2a}
.search .si{width:1.35em;height:1.35em;flex:none;fill:none;stroke:#2b2b2a;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round}
.search .si rect{fill:#2b2b2a;stroke:none}
.search .aim{
  flex:none;background:#f1f1ef;border-radius:999px;
  padding:.34em .66em;color:#232322;font-size:.94em;white-space:nowrap;
}

.dots{display:flex;gap:12px;margin:clamp(22px, 4.4svh, 54px) auto 0;align-self:center;position:relative;z-index:4}
.dot{width:8px;height:8px;border-radius:50%;background:#d9d9d7;transition:background .45s var(--ease),transform .45s var(--ease)}
.dot.on{background:var(--ink);transform:scale(1.15)}

/* ---- 1300 download ---- */
.s-download{position:relative}
/* the download screen centres as one group — icon, headline and body copy
   together — rather than hanging the icon and copy off a centred headline,
   which reads high because the icon carries far more mass than the copy */
.dl-body{
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  width:100%;height:100%;
}
.dl-body .appicon{margin-bottom:clamp(20px, 4svh, 40px)}
.dl-body .text{margin-top:clamp(16px, 3svh, 30px)}
.appicon{
  width:clamp(130px, 36vw, 250px);
  height:auto;
  aspect-ratio:1;
  border-radius:22.37%;          /* matches the squircle baked into the asset */
  box-shadow:
    0 2px 4px rgba(0,0,0,.05),
    0 12px 26px -8px rgba(0,0,0,.13),
    0 34px 70px -24px rgba(0,0,0,.18);
}
.s-download .display{max-width:14ch}
.s-download .text{max-width:34ch;text-align:left}
.dl{text-decoration:none;position:relative;display:inline-block}
.dl::after{
  content:"";position:absolute;left:0;right:0;bottom:-.06em;height:2px;background:var(--ink);
  transform:scaleX(0);transform-origin:left;transition:transform .5s var(--ease);
}
.dl:hover::after,.dl:focus-visible::after{transform:scaleX(1)}
.dl:focus-visible{outline:none}
.foot{position:absolute;left:var(--gutter);right:var(--gutter);bottom:var(--chip-bottom);display:flex;justify-content:center;line-height:0}

/* the url chip: the logotype's two counters read as eyes, so the mark is a
   black disc with the same pair. One element, used on the hero and the footer. */
.chip-url{
  display:inline-flex;align-items:center;gap:.42em;
  padding:.22em .95em .22em .24em;
  border:1px solid #cfcfcf;
  border-radius:999px;
  background:var(--white);
  text-decoration:none;
  font-family:var(--text);
  font-size:clamp(13px, 3.6vw, 20px);
  line-height:1;
  letter-spacing:-.012em;
  color:#b4b4b4;
  white-space:nowrap;
  transition:color .35s var(--ease), border-color .35s var(--ease);
}
.chip-url:hover{color:#8e8e8e;border-color:#b6b6b6}
.chip-url .face{width:1.06em;height:1.06em;flex:none}
.chip-url .face svg{display:block;width:100%;height:100%}
.eyes-sm{animation:gaze-sm 6.4s cubic-bezier(.62,0,.2,1) infinite}
@keyframes gaze-sm{
  0%,16%   {transform:translateX(0)}
  24%,42%  {transform:translateX(2.1px)}
  50%,68%  {transform:translateX(-2.1px)}
  76%,100% {transform:translateX(0)}
}
/* the hero chip and the footer chip are the same object at the same size in the
   same place — one offset, used by both, so they never drift apart */
.s-hero .chip-url{
  position:absolute;left:50%;translate:-50% 0;
  bottom:var(--chip-bottom);
  z-index:3;
}

/* ---- desktop pass ---- */
@media (min-width:900px){
  :root{
    --gutter:40px;
    --r:40px;
    --shadow:0 3px 8px rgba(0,0,0,.05), 0 26px 60px -22px rgba(0,0,0,.18);
    --shadow-soft:0 3px 8px rgba(0,0,0,.05), 0 22px 46px -14px rgba(0,0,0,.15), 0 52px 96px -38px rgba(0,0,0,.22);
    --logo-w:min(21vw, 408px);
  }
  .s-line .display{max-width:26ch}
  .obj-keyboard{width:min(40vw, 620px)}
  .obj-mouse{width:min(6.5vw, 104px)}
  .s-tether .display{max-width:26ch}
  .vcard{width:min(10vw, 172px)}
  .s-modes .display{max-width:26ch}
  /* The apps stage is pinned to the Figma frame. Everything derives from three
     numbers, so the alignments can't drift:
       caption left  = card 2 left
       clip right    = card 3 left
     Figma 1920×1080: row starts 2.6%, cards 28%, gaps 3.9%. */
  .stage.s-apps{
    display:block;padding:0;
    position:sticky;top:0;        /* must stay sticky, or the stage scrolls
                                     away and leaves two blank viewports */
    --row-l:2.6vw;
    --cw:28vw;
    --cg:3.9vw;
    --centre-l:calc((100vw - var(--cw)) / 2);   /* left edge of a centred card */
    /* The caption and the clip are the frame's header: they do NOT ride the card
       row, they sit on the CENTRED slot, which is where the live module always is.
         caption left  = the live module's left edge
         clip right    = the next slot's left edge
       Both derived, so they cannot drift. */
    --next-l:calc(var(--centre-l) + var(--cw) + var(--cg));
    --clip-w:17.9vw;
  }
  .apps-head{position:static;max-width:none;margin:0}
  .apps-head .caps{
    position:absolute;left:var(--centre-l);top:19.5%;width:8.2vw;
    max-width:none;text-align:left;
  }
  /* on .cap, not on .caps: .text sets its own font-size and would win */
  .apps-head .caps .cap{font-size:clamp(12px, 1.06vw, 17px);line-height:1.1}
  .vcard-sm{
    position:absolute;left:calc(var(--next-l) - var(--clip-w));top:19.5%;
    width:var(--clip-w);margin:0;aspect-ratio:348/424;z-index:3;
  }
  /* the active module sits in the MIDDLE of the frame and the row slides so the
     next one takes that place. Mail is centred at the first stop, the agent to
     its right, chrome off frame. The caption and clip do not move with it —
     they stay on their Figma coordinates, derived from --row-l. */
  .cards{
    position:absolute;left:0;top:50.2%;
    width:auto;max-width:none;margin:0;
    /* redeclare: the base .cards rule sets --centre-l on the element itself,
       and a value set here beats the one inherited from .stage.s-apps */
    --centre-l:calc((100vw - var(--cw)) / 2);
    padding:0 0 0 var(--centre-l);gap:var(--cg);
    justify-content:flex-start;
  }
  .card{flex:0 0 var(--cw);aspect-ratio:475/375;padding:1.96vw 1.29vw 1.74vw;min-height:0}
  .tile{width:3.72vw}
  .mail{margin-top:1.51vw;font-size:clamp(11px, .97vw, 15px);line-height:1.3}
  .dots{position:absolute;left:0;right:0;bottom:5.2%;margin:0;justify-content:center}
  .mail{font-size:clamp(11px, .92vw, 14px);line-height:1.3}
  .zoom figcaption{font-size:clamp(9px, .82vw, 13px)}
  /* the retailer line only fits once the card is wide enough not to clip it */
  .results span{display:block}
  .foot{justify-content:center}
}

/* The Figma's 8.2vw caption column is a 1440 spec. Below that it is only
   90–110px wide, and at that width most words take a line of their own, so the
   caption grows to 16 lines and reaches the card row. Between 900 and 1439 widen
   it into the space it already has — as far as the clip's left edge.
   This has to be its own block: folding it into the desktop pass above by
   closing that block early spills every rule after it to all widths. */
@media (min-width:900px) and (max-width:1439px){
  .apps-head .caps{
    width:calc(var(--next-l) - var(--clip-w) - var(--centre-l) - 1.4vw);
  }
}

/* ---- motion preferences ----
   Reduced motion here means REDUCED, not removed. Almost everything on this page
   is scroll-driven — the reader is the one moving it, at their own pace — and
   that is a very different thing from motion that plays at you. So the stages,
   the build-ins, the glow and the scatter all still run; what gets scaled back is
   amplitude. Shorter travel, no blur, no spin, a smaller bloom, and letters that
   ease out near the middle instead of crossing the whole viewport.
   (`js/site.js` damps the per-letter vectors to 30% for the same reason.) */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}

  /* build-ins: keep the fade, drop the blur and most of the travel */
  .b{filter:none !important}
  .in .b{filter:none}
  .display .w{translate:0 .16em;filter:none}
  .in .display .w,.stage.lit .display .w{filter:none}

  /* the bloom still opens, to about a third of the size */
  .s-line.leaving .bloom{scale:calc(.10 + var(--g) * .78)}

  /* letters drift out and fade rather than being thrown */
  .s-line.leaving .display .ch{rotate:none;filter:none}

  /* the decorative idle loops are the one thing that does play on its own */
  .logo-float,.cue{animation:none}
}
}
}
