/* ============================================================
   getnessi.ai — intro → landing → ceremony → handoff

   Tokens are the Nessi intro design system (see the design
   handoff's styleguide.md), not the Bubl marketing palette.

   THE TWO-ACCENT RULE. Cyan is Nessi. Coral is the user. The
   headline gradient runs cyan → coral because that is Nessi
   reaching toward the person, and the Get Nessi button picks up
   where that gradient ended so it reads as the completion of the
   headline rather than a second competing sweep. Never introduce
   a third accent.

   Two documented exceptions, both for the same reason — they
   depict real software, so they carry real software's colours:
   the storm's app tiles, and the iMessage thread on the landing.
   ============================================================ */

:root{
  /* Base */
  --ink:#07090C;
  --panel:#0C1116;
  --card:rgba(22,28,36,.94);
  --surface:#171F27;
  --line:#1C242E;
  --line-2:#161D25;
  --line-3:#1E2731;
  --line-4:#232C36;
  --line-mono:#2A3441;

  /* Text */
  --cream:#F2EDE4;
  --body:#DCD7CE;
  --body-2:#B7BFC9;
  --body-3:#E7E2D9;
  --lede:#A8B1BC;
  --muted:#8A93A0;
  --faint:#6E7885;
  --ghost:#4E5865;

  /* Accent */
  --cyan:#34E7DC;
  --cyan-deep:#1FC3CE;
  --cyan-light:#C9FFFB;
  --coral:#FF8F6B;
  --coral-mid:#F2673A;
  --coral-deep:#E0501F;
  --green:#4ADE80;
  --on-cyan:#05201D;
  --on-coral:#2B0C02;

  /* The dock's own surfaces. Deliberately NOT --panel/--line-3: the dock has to
     read as a solid object resting on the page, so it sits a shade darker than
     the panels behind it and its border a shade lighter than the page's rules.
     Reusing the general tokens here is what made it look like glass. */
  --dock:#0B1015;
  --dock-line:#2B3744;
  --field:#0F151B;
  --field-line:#33404E;
  --field-focus:#131A21;

  --grad-head:linear-gradient(100deg,#34E7DC,#FF8F6B);
  --grad-orb:linear-gradient(150deg,#C9FFFB,#34E7DC 52%,#FF8F6B);
  --grad-cta:linear-gradient(120deg,#FF8F6B,#F2673A 62%,#E0501F);
  --grad-go:linear-gradient(140deg,#34E7DC,#1FC3CE);
  --conic:conic-gradient(from 0deg,rgba(52,231,220,0),#34E7DC,rgba(255,143,107,.9),rgba(52,231,220,0));

  --font-sans:'Schibsted Grotesk',system-ui,-apple-system,sans-serif;
  --font-mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,monospace;

  /* Entrances overshoot. Exits don't. Things being consumed accelerate away. */
  --enter:cubic-bezier(.16,1,.3,1);
  --settle:cubic-bezier(.3,1.12,.45,1);
  --spring:cubic-bezier(.2,1.15,.3,1);
  --absorb:cubic-bezier(.6,0,.35,1);
  --pop:cubic-bezier(.2,1.2,.3,1);
  /* More overshoot than --spring. The dock is the one thing that should look
     like it has mass when it arrives. */
  --land:cubic-bezier(.2,1.25,.3,1);

  /* Mobile portrait is the design target; desktop is the same column on a
     wider field. */
  --col:430px;
  /* Raised by JS when a mobile keyboard opens. */
  --kb:0px;
}

*{box-sizing:border-box}
html,body{margin:0;padding:0;background:var(--ink);-webkit-text-size-adjust:100%;overscroll-behavior-y:none}
button,a,input{-webkit-tap-highlight-color:transparent;touch-action:manipulation}
input{font-family:inherit}
a{color:var(--cyan);text-decoration:none}
a:hover{color:var(--coral)}

body{
  font-family:var(--font-sans);
  color:var(--cream);
  -webkit-font-smoothing:antialiased;
}

.stage{display:flex;justify-content:center;min-height:100dvh}

/* The 430px column. Light falls from above, so the page is a radial wash
   rather than a flat fill. Do NOT put transform or filter on .frame — the
   dock is position:fixed and would get trapped inside it. */
.frame{
  position:relative;
  width:100%;max-width:var(--col);min-height:100dvh;
  overflow:hidden;
  background:radial-gradient(120% 70% at 50% -10%,#0D1319 0%,#07090C 62%);
}

.view{
  position:absolute;inset:0;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .45s var(--enter),visibility 0s linear .45s;
}
.view.on{opacity:1;visibility:visible;pointer-events:auto;transition:opacity .45s var(--enter)}

.grad{
  background:var(--grad-head);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
}

:where(a,button,input):focus-visible{
  outline:2px solid var(--cyan);
  outline-offset:3px;
  border-radius:6px;
}

/* ── keyframes ─────────────────────────────────────────── */

@keyframes nsBlink{0%,45%{opacity:.9}55%,100%{opacity:.12}}
@keyframes nsCardIn{
  from{opacity:0;transform:translate(var(--fx),var(--fy)) scale(.86) rotate(0deg)}
  to{opacity:1;transform:translate(0,0) scale(1) rotate(var(--r))}
}
@keyframes nsFall{to{transform:translate(var(--px),var(--py)) rotate(var(--pr)) scale(.93)}}
@keyframes nsConverge{
  0%{opacity:1;transform:translate(0,0) scale(1);filter:blur(0)}
  55%{opacity:.9}
  100%{opacity:0;transform:translate(var(--cx),var(--cy)) scale(.04);filter:blur(2px)}
}
@keyframes nsStormDim{to{opacity:.55;filter:blur(2px) saturate(.7)}}
@keyframes nsDrift{0%,100%{transform:translate(0,0)}50%{transform:translate(-6px,-10px)}}

/* One keyframe handles a story beat's arrival AND its departure. Delay is
   when it starts, duration is how long it lives. Text enters from below and
   leaves upward, so the words move up past the reader's eye. */
@keyframes nsBeat{
  0%{opacity:0;transform:translateY(16px)}
  14%{opacity:1;transform:translateY(0)}
  84%{opacity:1;transform:translateY(0)}
  100%{opacity:0;transform:translateY(-9px)}
}
/* "fast." does not fade in with the rest of its line — it arrives late and
   lands hard. Comes in oversized and out of focus, overshoots THROUGH its
   resting size to 94%, then settles in two decreasing bounces. The blur is
   what sells it as speed rather than as a zoom; the undershoot past 1 is what
   sells it as impact rather than as a pop.

   EVERY KEYFRAME FROM 50% ON MUST RESTATE opacity AND filter, even though the
   values do not change. A property is only interpolated across the keyframes
   that mention it; for the span after the LAST keyframe that names it, it
   reverts to the element's base style. `.crash` has `opacity:0` in its base
   style, so an earlier version that declared opacity only at 0% and 50% had
   the word fade back to nothing between 50% and 100% — and `both` then held it
   invisible forever after. It read as a flash that never landed, and the cause
   looks nothing like the symptom. Same trap applies to `filter`. */
@keyframes nsCrash{
  0%{opacity:0;transform:scale(2.5);filter:blur(7px)}
  50%{opacity:1;transform:scale(.94);filter:blur(0)}
  68%{opacity:1;transform:scale(1.05);filter:blur(0)}
  84%{opacity:1;transform:scale(.985);filter:blur(0)}
  100%{opacity:1;transform:scale(1);filter:blur(0)}
}
@keyframes nsUp{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}
@keyframes nsFade{from{opacity:0}to{opacity:1}}
@keyframes nsOut{to{opacity:0}}
@keyframes nsOrbIn{
  0%{opacity:0;transform:scale(.1)}
  62%{opacity:1;transform:scale(1.16)}
  100%{opacity:1;transform:scale(1)}
}
@keyframes nsBreathe{0%,100%{transform:scale(1)}50%{transform:scale(1.055)}}
@keyframes nsSpin{to{transform:rotate(360deg)}}
@keyframes nsRing{0%{opacity:.5;transform:scale(.8)}100%{opacity:0;transform:scale(1.9)}}
@keyframes nsDot{
  0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(74,222,128,.45)}
  50%{opacity:.75;box-shadow:0 0 0 9px rgba(74,222,128,0)}
}
@keyframes nsLineIn{from{opacity:0;transform:translateY(7px)}to{opacity:1;transform:translateY(0)}}
@keyframes nsGlow{
  0%,100%{box-shadow:0 0 0 1px rgba(255,143,107,.5),0 18px 50px rgba(242,103,58,.24)}
  50%{box-shadow:0 0 0 1px rgba(255,143,107,.75),0 22px 62px rgba(242,103,58,.42)}
}
/* The CTA's displaced position under the headline. --cdx/--cdy are measured at
   runtime by app.js against the button's final rect. */
@keyframes nsCtaTravel{
  0%{transform:translate(var(--cdx,0px),var(--cdy,0px))}
  100%{transform:translate(0,0)}
}
@keyframes nsCollapse{0%{height:44px}100%{height:0}}

/* The dock comes up from below and widens around the button that just landed
   in it, overshooting slightly. scaleX is on the chrome layer only — the pill's
   contents must not stretch, and .pill-chrome is a sibling of nothing, so this
   is safe. It is also BELOW .dock in the tree, never between .dock and .cta,
   so it cannot corrupt the travel measurement. */
@keyframes nsDockLand{
  0%{opacity:0;transform:translateY(26px) scaleX(.86)}
  100%{opacity:1;transform:translateY(0) scaleX(1)}
}
/* Wipes open leftward — see transform-origin on .field for why that direction
   is the whole point. */
@keyframes nsFieldIn{
  0%{opacity:0;transform:scaleX(.2)}
  100%{opacity:1;transform:scaleX(1)}
}
/* A coral ring pushed off the field's border. Runs a COUNTED three times and
   stops; see .field for why this must never be `infinite`. */
@keyframes nsFieldPulse{
  0%{box-shadow:0 0 0 0 rgba(255,143,107,.5)}
  38%{box-shadow:0 0 0 9px rgba(255,143,107,0)}
  100%{box-shadow:0 0 0 0 rgba(255,143,107,0)}
}

/* ══════════════ INTRO ══════════════ */

#vIntro.on{animation:nsOut .85s ease 15.10s both}

.idot{
  position:absolute;left:50%;top:50%;
  width:7px;height:7px;margin:-3px 0 0 -3px;
  border-radius:50%;background:var(--muted);
}
#vIntro.on .idot{animation:nsBlink 1s steps(1,end) 0s 3,nsOut .3s ease .8s both}

/* ── the storm ──────────────────────────────────────────
   Cards are injected by app.js. Each needs THREE nested wrappers because it
   performs three separate transforms that must compose: fly in, fall to the
   pile, get absorbed into the orb. Collapsing them into one element makes the
   later animations fight the earlier ones. */
.storm{position:absolute;inset:0}
#vIntro.on .storm{
  animation:nsStormDim .9s ease 2.60s both,nsDrift 16s ease-in-out 3.60s infinite;
}
.sc{position:absolute;left:var(--l);top:var(--t)}
#vIntro.on .sc{animation:nsCardIn .42s var(--enter) var(--d1) both}
#vIntro.on .sc-f{animation:nsFall .78s var(--settle) var(--d2) both}
#vIntro.on .sc-c{animation:nsConverge .9s var(--absorb) var(--d3) both}

.card{
  width:250px;padding:12px 13px;
  border-radius:16px;
  background:var(--card);
  border:1px solid var(--line);
  box-shadow:0 18px 40px rgba(0,0,0,.5);
  display:flex;gap:11px;align-items:flex-start;
}
.card-t{min-width:0}
.card-l{
  font-family:var(--font-mono);font-size:10px;
  letter-spacing:.1em;text-transform:uppercase;color:var(--faint);
}
/* The pile needs exactly one point of alarm. */
.card-l.od{color:var(--coral)}
.card-m{margin-top:4px;font-size:14px;line-height:1.35;color:var(--body)}

/* ── app tiles ──────────────────────────────────────────
   The one place real-world colour enters the system, and only here: these are
   the problem, not the brand. Original designs in each app's signature colour,
   not the real logos — swap in licensed assets if you get rights, but keep the
   34px / radius-9px / 0 3px 8px envelope. */
.tile{
  flex-shrink:0;width:34px;height:34px;border-radius:9px;
  display:flex;align-items:center;justify-content:center;
}
.tile svg{width:19px;height:19px}
.t-mail{background:linear-gradient(160deg,#4DA3FF,#1471E8);box-shadow:0 3px 8px rgba(20,113,232,.4)}
.t-msg{background:linear-gradient(160deg,#5BE370,#21B93F);box-shadow:0 3px 8px rgba(33,185,63,.4)}
.t-team{
  background:#4A154B;box-shadow:0 3px 8px rgba(74,21,75,.45);
  font-family:var(--font-mono);font-size:18px;font-weight:500;color:#fff;line-height:1;
}
.t-cal{
  background:#FFF;overflow:hidden;box-shadow:0 3px 8px rgba(0,0,0,.35);
  display:block;
}
.t-cal .cb{display:block;height:11px;background:#FF3B30}
.t-cal .cd{
  display:flex;height:23px;align-items:center;justify-content:center;
  font-size:13px;font-weight:600;color:#16150F;font-style:normal;
}
.t-rem{
  background:#FFF;box-shadow:0 3px 8px rgba(0,0,0,.35);
  flex-direction:column;justify-content:center;gap:4px;padding:0 7px;
}
.t-rem i{display:flex;align-items:center;gap:4px;width:100%}
.t-rem i::before{content:"";width:5px;height:5px;border-radius:50%;flex:none}
.t-rem i::after{content:"";flex:1;height:2px;border-radius:2px;background:#D6D0C6}
.t-rem .r1::before{background:#FF3B30}
.t-rem .r2::before{background:#FF9500}
.t-rem .r3::before{background:#007AFF}
.t-rem.od i::before{background:#FF3B30}
.t-notes{
  background:#FFF;overflow:hidden;box-shadow:0 3px 8px rgba(0,0,0,.35);
  display:block;
}
.t-notes .nb{display:block;height:10px;background:#FFD60A}
.t-notes .nr{
  display:block;height:2px;margin:3px 7px 0;border-radius:2px;background:#D6D0C6;
}
.t-notes .nb + .nr{margin-top:6px}
.t-notes .nr.short{width:60%}

/* ── story copy ─────────────────────────────────────────
   Problem and empathy, layered so only one is ever on screen.

   STACKED WITH GRID, NOT position:absolute. Every beat occupies the same grid
   cell, so the block sizes itself to the tallest line and stays centred no
   matter which beat is showing or how many there are. The previous version put
   the first beat in flow and absolutely positioned the second on top of it,
   which only holds while the FIRST beat is the tallest — add a longer line
   later in the sequence and the box is sized to the wrong one. */
.beats{
  position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);
  display:grid;padding:0 34px;text-align:center;
}
.beats>.beat{grid-area:1/1;align-self:center}
.beat{margin:0;text-wrap:pretty}
.b1,.b2{
  font-size:clamp(22px,6.4vw,27px);line-height:1.28;
  font-weight:500;letter-spacing:-.015em;
}
.b2{font-size:clamp(26px,7.6vw,32px);font-weight:600;letter-spacing:-.025em}
/* inline-block so scale() applies — a transform on an inline box is ignored.
   Held at 0 until its own animation starts, so it is absent while the rest of
   the line is read and the ellipsis has somewhere to land. */
.crash{display:inline-block;opacity:0}
#vIntro.on .crash{animation:nsCrash .60s cubic-bezier(.2,.9,.25,1) 5.80s both}
/* THE BEAT DURATIONS ARE LONGER THAN THE HANDOFF SPECIFIES. Deliberate — do
   not "correct" them back.

   nsBeat only holds text at full opacity for 70% of its duration (14% to 84%),
   so the handoff's 2.5s problem beat gave a reader 1.75s for 17 words: about
   10 words/second. The empathy beat was 12/s and the closing answer 16/s.
   Comfortable reading of short centred display type is 4-6 words/second, so
   none of the four lines could actually be read.

   Durations now put every beat in the 5-7.5 w/s band. Delays after each beat
   shifted by the same amount so nothing overlaps — the storm's converge, the
   orb's birth, the overlay fade and the stage timer in app.js all moved with
   them. If you change a duration here you MUST reflow everything downstream;
   the whole chain is listed in site/README.md.

   Cost: the intro is 16.05s rather than 14.9s. Skip still appears at 3s, and
   returning visitors never see it at all — app.js remembers.

   THE FIVE-BEAT CHAIN, in order. Each beat's duration is set from its word
   count at ~6 w/s with a 1.30s floor, so a four-word line still lands as a
   deliberate line instead of a flash:

     [dot 0-1.1s, cards fly in 0.50-2.05s and settle by 2.47s]
     [storm dims 2.60s, pile falls 3.05-3.83s]
     beat  words  delay    dur     ends
     b1     9     2.95s    2.15s   5.10s    <- text starts DURING the dim
     b2     4     5.15s    3.00s   8.15s    <- 0.05s gap, as before
       .crash       5.80s    0.60s   6.40s  <- "fast." lands, then HOLDS
     [cards converge 8.40s, orb born 8.75s]
     a1     8     9.30s    1.90s  11.20s    <- 0.55s of orb alone first
     a2    11    11.20s    2.60s  13.80s    <- answer beats run back to back
     a3     4    13.80s    1.85s  15.65s    <- the lockup
     [overlay fade 15.10s +0.85s -> 15.95s, stage advances 16.05s]

   b2 runs 3.00s rather than the 1.85s its four words would earn, because the
   line is read in two parts: "Life gets messy..." lands with the beat, and
   "fast." crashes in 0.65s later.

   THE POINT OF THAT 3.00s IS THE HOLD AFTER THE CRASH, not the crash itself.
   The crash settles at 6.40s and b2's 84% hold does not end until 7.67s, so
   the finished line sits still and readable for ~1.27s. An earlier version ran
   b2 at 2.40s, which left only 0.72s — and since 0.55s of the word's life was
   spent animating, "fast." read as a flash that never landed. If it ever looks
   like a flash again, the fix is a longer b2, not a slower crash.

   Move b2's delay or duration and you must move the .crash delay with it — it
   is an absolute time, not an offset.

   The invariants from site/README.md all still hold: 0.05s between the two
   problem beats, 0.25s from the last problem beat to the first card
   converging, 0.55s of orb alone before the first answer beat, answer beats
   back to back, the fade starting 0.55s before the last beat ends, and the
   stage advancing 0.10s after the fade completes. Change a duration and you
   must reflow every row below it AND the at() in app.js. */
#vIntro.on .b1{animation:nsBeat 2.15s ease 2.95s both}
#vIntro.on .b2{animation:nsBeat 3.00s ease 5.15s both}

/* ── orb + answer ───────────────────────────────────────
   ONE centred flex column. Their spacing must not depend on viewport height:
   anchoring the orb and the copy independently by percentage printed the copy
   across the orb on anything shorter than ~700px. */
.answer{
  position:absolute;inset:0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:38px;padding:0 34px;pointer-events:none;
}

/* Nessi's only visual form. Six stacked layers. Never flatten it to a plain
   circle, never put it on a light field. */
.orb{position:relative;flex-shrink:0;width:136px;height:136px}
#vIntro.on .orb{animation:nsOrbIn 1.1s var(--pop) 8.75s both}
.orb-breathe{position:absolute;inset:0}
#vIntro.on .orb-breathe{animation:nsBreathe 4.2s ease-in-out 9.90s infinite}
.orb i{position:absolute;border-radius:50%;font-style:normal}
.o-glow{inset:-34px;background:radial-gradient(circle,rgba(52,231,220,.26) 0%,rgba(52,231,220,0) 68%)}
.o-ring{inset:0;background:var(--conic);opacity:.5}
.o-core{inset:5px;background:radial-gradient(circle at 38% 32%,#0F1A20 0%,#07090C 74%);border:1px solid rgba(52,231,220,.28)}
.o-inner{
  inset:26px;filter:blur(7px);
  background:radial-gradient(circle at 40% 36%,rgba(156,253,246,.55) 0%,rgba(52,231,220,.2) 52%,rgba(52,231,220,0) 80%);
}
.o-n{
  inset:0;display:flex;align-items:center;justify-content:center;
  font-size:54px;font-weight:700;letter-spacing:-.05em;
  background:var(--grad-orb);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
}
.o-pulse{inset:0;border:1px solid rgba(52,231,220,.5)}

/* Ambient loops are gated on the screen being up. Nothing on screen is ever
   fully static, but a hidden screen is not on screen — an infinite conic spin
   behind visibility:hidden is pure compositor work for nobody. */
#vIntro.on .o-ring{animation:nsSpin 7s linear infinite}
#vIntro.on .o-pulse{animation:nsRing 3.4s ease-out 10.10s infinite}

/* Same grid stack as .beats, for the same reason — a2 is the longest line and
   it is not the first, so nothing here may be in flow. */
.answers{display:grid;width:100%;max-width:340px;text-align:center}
.answers>.beat{grid-area:1/1;align-self:center}
/* Both sentence beats get the same treatment. a2 used to be the smaller
   supporting line under a longer one; with only three answer beats it carries
   the actual ask, so it is set at a1's size. The span is on the FIRST clause,
   not the last — "just ask Nessi" is the instruction and keeps the cream. */
.a1,.a2{font-size:clamp(21px,6.2vw,26px);line-height:1.3;font-weight:500;letter-spacing:-.015em}
.a2 span{color:var(--body-2)}

/* The closing lockup. Set to the landing's headline, not to the beats around
   it: same weight, same tightening, same gradient on the same word. It is the
   last frame of the intro and the first frame of the landing is the same two
   phrases, so this has to read as that thing arriving rather than as one more
   sentence. */
.a3{
  font-size:clamp(26px,7.8vw,33px);line-height:1.1;
  font-weight:700;letter-spacing:-.035em;
}

#vIntro.on .a1{animation:nsBeat 1.90s ease 9.30s both}
#vIntro.on .a2{animation:nsBeat 2.60s ease 11.20s both}
#vIntro.on .a3{animation:nsBeat 1.85s ease 13.80s both}

/* ── skip ──────────────────────────────────────────────
   Shared by the intro and the ceremony. */
.skip{
  position:absolute;z-index:12;
  right:16px;top:calc(18px + env(safe-area-inset-top));
  padding:10px 16px;border-radius:999px;
  border:1px solid var(--line-4);
  background:rgba(7,9,12,.7);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  color:var(--muted);
  font-family:var(--font-mono);font-size:11px;
  letter-spacing:.08em;text-transform:uppercase;
  cursor:pointer;
}
.skip:hover{color:var(--cream);border-color:var(--cyan)}
#vIntro.on .skip{animation:nsFade .6s ease 3s both}

/* ══════════════ LANDING ══════════════ */

.land{
  position:absolute;inset:0;
  overflow-y:auto;-webkit-overflow-scrolling:touch;
  padding:
    calc(26px + env(safe-area-inset-top)) 24px
    calc(150px + env(safe-area-inset-bottom));
}
.land::-webkit-scrollbar{width:0}
.col{width:100%}

/* The entrance is staged so the headline gets its own moment, the CTA appears
   as display copy beneath it, and the name field is the very last thing to
   arrive — an empty box right after a phrase that just said what you're doing,
   so no instruction copy is needed. Ends at ~4.45s. */
#vLand.on .lockup{animation:nsUp .7s var(--enter) .15s both}
#vLand.on .h1{animation:nsUp .9s var(--enter) .35s both}
#vLand.on .lede{animation:nsUp .8s var(--enter) 1.5s both}
#vLand.on .thread{animation:nsUp .85s var(--enter) 1.85s both}
#vLand.on .closer{animation:nsUp .8s var(--enter) 2.25s both}
/* Slotted in BEFORE the travel starts at 2.95s, not after the dock settles.
   Nothing may move on this page after the field, or the field stops being the
   last thing the eye saw — which is the entire instruction mechanism. It is
   below the fold anyway, so nobody watches it arrive. */
#vLand.on .legal{animation:nsFade .6s ease 2.6s both}

/* ── the arrival ───────────────────────────────────────
   The travel, the pill landing and the field opening overlap into one ~0.7s
   event. They used to be a 0.9s run of separate gentle fades, and the dock
   simply resolved out of nothing rather than arriving.

   The big label now holds almost the whole way down and cuts late, so there is
   never a stretch where both labels are half-visible. Nudge these apart again
   and the softness comes straight back. */
#vLand.on .cta{animation:nsFade .6s ease .95s both,nsCtaTravel .72s cubic-bezier(.5,0,.22,1) 2.95s both}
#vLand.on .dock::before{animation:nsFade .5s ease 3.3s both}
#vLand.on .cta-big{animation:nsOut .2s ease 3.36s both}
#vLand.on .pill-chrome{animation:nsDockLand .52s var(--land) 3.46s both}
#vLand.on .cta-grad{animation:nsFade .18s ease 3.5s both}
#vLand.on .cta-small{animation:nsFade .16s ease 3.52s both}
#vLand.on .field{animation:nsFieldIn .42s var(--pop) 3.62s both,
                 nsFieldPulse 2.1s ease-out 4.2s 3}
/* Stops the ring the instant the visitor engages, and it MUST live here rather
   than in the .field:focus rule below. `.field:focus` is (0,2,0) and this
   timeline rule is (1,2,0), so a focus-side `animation` loses the cascade and
   the pulse just keeps going — the colours still change, which makes it look
   like it worked. Measured, not assumed.

   The nsFieldIn half is character-identical to the line above on purpose:
   animations are matched by name and position, so an identical declaration
   continues the finished animation instead of restarting it. Retune the delay
   here without retuning it above and focusing the field blanks it to scaleX(.2)
   for 3.6 seconds. */
#vLand.on .field:focus{animation:nsFieldIn .42s var(--pop) 3.62s both}
#vLand.on .cta-slot{animation:nsCollapse .55s var(--enter) 3.9s both}

.lockup{display:flex;align-items:center;gap:10px}
.mark{position:relative;width:30px;height:30px;flex:none}
.mark i{position:absolute;border-radius:50%;font-style:normal}
.m-ring{inset:0;background:var(--conic);opacity:.55}
.m-core{inset:2px;background:var(--ink)}
.m-glow{
  inset:6px;
  background:radial-gradient(circle at 40% 36%,rgba(156,253,246,.4),rgba(52,231,220,0) 72%);
}
#vLand.on .m-ring{animation:nsSpin 7s linear infinite}
#vLand.on .m-glow{animation:nsBreathe 4.2s ease-in-out infinite}
.m-n{
  inset:0;display:flex;align-items:center;justify-content:center;
  font-size:15px;font-weight:700;letter-spacing:-.04em;color:var(--cyan);
}
.lk-word{font-size:17px;font-weight:600;letter-spacing:-.01em}
.lk-tag{
  margin-left:auto;
  font-family:var(--font-mono);font-size:11px;
  letter-spacing:.1em;text-transform:uppercase;color:var(--faint);
}

.h1{
  margin:32px 0 0;
  font-size:clamp(42px,13.5vw,56px);
  line-height:.98;font-weight:700;letter-spacing:-.035em;
}

.lede{
  margin:18px 0 0;
  font-size:18px;line-height:1.5;color:var(--lede);
  max-width:21em;text-wrap:pretty;
}

/* ── the proof screenshot ──────────────────────────────────
   Reads as an actual iOS Messages screen in dark mode, not a chat widget:
   conversation header, timestamp divider, read receipt, rich-link footer,
   composer. Everything inside uses the system face so it renders as real SF
   on Apple devices. iMessage blue is not a brand colour — it's here for the
   same reason the storm's app tiles carry their apps' colours.
   ──────────────────────────────────────────────────────── */
.thread{
  --tail-mask:#0A0E13;
  margin:26px 0 0;
  font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text','Segoe UI',Roboto,sans-serif;
  background:#0A0E13;
  border:1px solid #1A222B;
  border-radius:22px;
  overflow:hidden;
  box-shadow:0 18px 44px -22px rgba(0,0,0,.8);
}

/* conversation header */
.ios-bar{
  display:grid;grid-template-columns:24px 1fr 30px;align-items:center;
  padding:9px 13px 8px;
  background:rgba(20,25,31,.92);
  backdrop-filter:saturate(180%) blur(14px);
  -webkit-backdrop-filter:saturate(180%) blur(14px);
  border-bottom:.5px solid rgba(255,255,255,.1);
}
.ios-back{width:11px;height:18px;color:#3E8DF7}
.ios-video{width:25px;height:16px;color:#3E8DF7;justify-self:end}
.ios-who{display:flex;flex-direction:column;align-items:center;gap:3px;min-width:0}
.av{
  width:42px;height:42px;border-radius:50%;
  background:var(--grad-go);color:var(--on-cyan);
  font-size:18px;font-weight:600;
  display:flex;align-items:center;justify-content:center;flex:none;
  letter-spacing:.01em;
}
.tname{
  display:flex;align-items:center;gap:3px;
  font-size:12px;font-weight:500;color:var(--cream);letter-spacing:-.01em;
}
.tname .chev{width:6px;height:10px;color:#6E6E73;margin-top:.5px}

/* message stack */
.msgs{display:flex;flex-direction:column;gap:2px;padding:10px 12px 12px}
.stamp{align-self:center;font-size:11px;color:#8E8E93;margin:2px 0 10px;letter-spacing:-.01em}
.stamp b{font-weight:600;color:#8E8E93}
.receipt{align-self:flex-end;font-size:10.5px;color:#8E8E93;margin:3px 3px 0;letter-spacing:-.01em}
.receipt b{font-weight:600}
.cap{align-self:flex-start;font-size:10.5px;color:#8E8E93;margin:4px 0 0 6px;letter-spacing:-.01em}

.m{
  position:relative;max-width:76%;
  font-size:15px;line-height:1.29;letter-spacing:-.015em;
  padding:8px 13px 9px;border-radius:19px;z-index:1;
}
.m.in{background:#26272B;color:var(--cream);align-self:flex-start}
.m.out{
  background:linear-gradient(180deg,#28A0FF 0%,#0B7CFC 100%);
  color:#fff;align-self:flex-end;
}
.m.gap{margin-top:8px}

/* composer */
.ios-input{
  display:flex;align-items:center;gap:9px;
  padding:9px 12px 11px;
  border-top:.5px solid rgba(255,255,255,.08);
  background:rgba(20,25,31,.92);
}
.plus{
  width:29px;height:29px;border-radius:50%;flex:none;
  background:#2C2C2E;color:#8E8E93;
  display:flex;align-items:center;justify-content:center;
}
.plus svg{width:15px;height:15px}
.ifield{
  flex:1;min-width:0;display:flex;align-items:center;justify-content:space-between;
  height:29px;padding:0 9px 0 12px;
  border:.5px solid #3A3A3C;border-radius:15px;
  font-size:13.5px;color:#8E8E93;letter-spacing:-.01em;
}
.ifield .mic{width:12px;height:17px;color:#8E8E93;flex:none}

/* iMessage tails.
   The ::after mask is painted with the message area's own background. If the
   stack ever moves onto a different surface, --tail-mask must move with it or
   you get bright notches under every tail.
   The outgoing tail matches the BOTTOM stop of the bubble gradient — it sits
   at the bubble's baseline, so anything lighter reads as a seam. */
.m.tail::before{content:"";position:absolute;bottom:-.5px;width:18px;height:18px;z-index:-1}
.m.tail::after{content:"";position:absolute;bottom:-.5px;width:11px;height:18px;background:var(--tail-mask);z-index:-1}
.m.in.tail::before{left:-6px;background:#26272B;border-bottom-right-radius:16px}
.m.in.tail::after{left:-11px;border-bottom-right-radius:11px}
.m.out.tail::before{right:-6px;background:#0B7CFC;border-bottom-left-radius:16px}
.m.out.tail::after{right:-11px;border-bottom-left-radius:11px}

/* Maps rich link preview */
.m.link{padding:0;overflow:hidden;max-width:62%;border-radius:18px}
.m.link .map{display:block;width:100%;height:104px}
.m.link .meta{padding:7px 11px 9px;background:#26272B}
.m.link .mt{font-size:12.5px;font-weight:600;color:var(--cream);line-height:1.25;letter-spacing:-.01em}
.m.link .md{font-size:11px;color:#8E8E93;margin-top:1px;letter-spacing:-.01em}
.m.link.tail::before{border-bottom-right-radius:16px}

.closer{
  margin:30px 0 0;
  font-size:22px;line-height:1.3;font-weight:500;letter-spacing:-.02em;
}
.closer span{color:var(--muted)}

/* The privacy link. Set in --ghost rather than --faint so it reads as the
   footnote it is, but still clears contrast on hover and on focus. It is now
   the only thing between the closer and the dock: the $0 / 0 apps / 0 logins
   row that used to sit here was cut, so this rule is the single divider at the
   bottom of the column rather than the second of two. */
.legal{margin:26px 0 0;padding-top:18px;border-top:1px solid var(--line-2)}
.legal a{
  font-size:11.5px;letter-spacing:.01em;
  color:var(--ghost);text-decoration:none;
  transition:color .16s ease;
}
.legal a:hover{color:var(--muted)}
.legal a:focus-visible{outline:2px solid var(--cyan);outline-offset:3px;border-radius:3px}

/* ── the dock ──────────────────────────────────────────
   NO TRANSFORM may appear between .dock and .cta. The travel offsets are
   measured against the button's final on-screen rect, and any ancestor
   transform corrupts that measurement. That is why the keyboard lift moves
   `bottom` rather than translateY here — a transform on this element would
   put the displaced CTA in the wrong place — and why .dock-in carries only
   position and z-index, never a transform.

   .pill-chrome's nsDockLand scaleX is fine: it is a SIBLING of the button, not
   an ancestor, so it never enters the measured chain. */
.dock{
  position:fixed;left:0;right:0;z-index:20;
  bottom:var(--kb);
  display:flex;justify-content:center;
  /* The 64px top padding is not spacing — it is the runway the scrim needs to
     ramp from nothing to solid. Take it away and the gradient becomes a hard
     line across the conversation card. */
  padding:64px 0 calc(18px + env(safe-area-inset-bottom));
  pointer-events:none;
  transition:bottom .2s var(--enter);
}
/* The scrim: the cheapest move in the whole arrival. The dock does not get
   brighter, everything behind it gets quieter, and contrast does the pointing.
   Safe over the content because .dock is pointer-events:none — it dims the
   conversation card without ever intercepting a tap meant for it.

   ON A PSEUDO-ELEMENT, NOT ON .dock. The handoff puts this background and its
   3.3s fade on the dock's own wrapper, and that wrapper contains the button.
   `both` fills backwards, so an opacity animation there pins the WHOLE dock —
   button included — at opacity 0 until 3.3s, and the CTA's display-copy phase
   under the headline (from .95s) plus most of its travel simply would not be
   visible. Only the scrim may fade; the button has its own schedule. */
.dock::before{
  content:'';
  position:absolute;inset:0;
  background:linear-gradient(to top,
    var(--ink) 44%,
    rgba(7,9,12,.92) 74%,
    rgba(7,9,12,0));
}
/* position/z-index paint the pill above the scrim without a negative z-index,
   which would drop it behind .dock's own stacking context. Neither is a
   transform, so the travel measurement is unaffected — see the note above. */
.dock-in{position:relative;z-index:1;width:100%;max-width:var(--col);padding:0 16px}
.pill{
  position:relative;
  display:flex;gap:8px;align-items:center;
  margin:0;padding:10px;border-radius:999px;
  background:transparent;
  pointer-events:auto;
}
/* The chrome is its own layer so the pill can be invisible while the button is
   displaced yet still lay out, keeping the button right-anchored.

   OPAQUE, on purpose. It was rgba(12,17,22,.92) and you could read the
   conversation card through it, which made the dock look like glass floating
   over the page instead of a bar resting on it. The arrival has to land on
   something solid.

   Still deliberately no backdrop-filter: it smeared a band of the content above
   the pill's rect, and against an opaque fill it would buy nothing anyway. */
.pill-chrome{
  position:absolute;inset:0;border-radius:999px;
  background:var(--dock);
  border:1px solid var(--dock-line);
  box-shadow:0 26px 64px rgba(0,0,0,.85);
}
/* 16px is a floor, not a preference: anything smaller makes iOS zoom the page
   on focus and the visitor loses the dock.

   This used to be borderless and transparent — a placeholder floating in the
   pill with nothing saying "this is a field, type here". It now draws itself.

   transform-origin is the load-bearing line. The field wipes open LEFTWARD,
   away from the button, because the eye is already at the button when the
   travel ends and the wipe pulls it left into the empty box. Grow it from the
   left instead and the motion pushes attention off the edge of the screen. */
.field{
  position:relative;
  flex:1;min-width:0;
  outline:none;
  border:1px solid var(--field-line);border-radius:999px;
  background:var(--field);
  color:var(--cream);font-size:16px;
  min-height:46px;padding:12px 18px;
  transform-origin:right center;
}
.field::placeholder{color:var(--faint)}
/* The 18px inner padding is 14px more than the placeholder can spare at a
   320px viewport — measured, and it clips to "Your first nam". Every live phone
   width from 344px up has room, so this only reaches back for the oldest small
   screens and split-screen windows. The vertical padding and the 16px font are
   deliberately untouched: the first is the field's height, the second is the
   iOS zoom floor. */
@media (max-width:340px){
  .field{padding-left:10px;padding-right:10px}
}
/* Re-declaring `animation` with only nsFieldIn is what drops nsFieldPulse, so
   the ring stops the instant the visitor engages.

   The nsFieldIn half must stay CHARACTER-IDENTICAL to the base rule. Animations
   are matched by name and position in the list: keep it identical and the
   browser treats it as the same already-finished animation and holds the final
   state. Change the delay or the easing here by even a little and it counts as
   a new animation, restarts, and — with a 3.62s delay and `both` filling
   backwards — blanks the field to scaleX(.2) for three and a half seconds the
   moment someone taps it. */
.field:focus{
  border-color:var(--coral);
  background:var(--field-focus);
}

/* One element in two states. The button is transparent; the orange pill is a
   child layer that fades in as it arrives, so the same node can read as display
   copy at the top of the page and as a button in the dock. */
.cta{
  position:relative;
  flex-shrink:0;min-height:46px;padding:13px 20px;
  border:0;border-radius:999px;
  background:transparent;color:var(--on-coral);
  font-family:inherit;font-size:15px;font-weight:600;
  cursor:pointer;white-space:nowrap;
}
.cta-grad{
  position:absolute;inset:0;border-radius:999px;
  background:var(--grad-cta);
  box-shadow:0 10px 26px rgba(242,103,58,.42);
}
/* Absolutely positioned so it contributes no layout width to the button. The
   20px left offset matches the button's left padding and the measurement in
   app.js depends on that number — change one, change both. */
.cta-big{
  position:absolute;left:20px;top:50%;transform:translateY(-50%);
  white-space:nowrap;
  font-size:clamp(30px,9vw,38px);font-weight:700;letter-spacing:-.03em;
  color:var(--coral);
}
.cta-small{position:relative}
.cta:hover .cta-grad{filter:brightness(1.08)}
.cta:active{transform:scale(.975)}

/* Reserves the big label's space under the headline, then collapses. The lede
   keeps its own 20px margin: 44 + 20 clears the label, and the resting gap
   returns to 20px. Zero the lede's margin instead and it rides up into the
   headline's descenders once this collapses. */
.cta-slot{height:44px}

/* ══════════════ CEREMONY ══════════════
   A terminal, not a slideshow. One flat column that fills from the top, then
   pins each new line to the bottom and pushes everything above it up. Lines
   persist and scroll away rather than flashing in place — so "I'm awake." and
   "Hi {name}." get displaced by the learning lines, and that displacement is
   the point.

   app.js owns the geometry: it sizes .stream-view at runtime and drives
   .stream's transform from measured row positions. Do not reintroduce fixed-px
   keyframes here — they had to be rebuilt every time the window height changed,
   and they silently did nothing on tall phones. */
.cer{
  position:absolute;inset:0;
  display:flex;flex-direction:column;justify-content:center;
  padding:
    calc(48px + env(safe-area-inset-top)) 26px
    calc(40px + env(safe-area-inset-bottom));
  font-family:var(--font-mono);
}

/* The mask ramp must be in px. A percentage ramp scales with the panel height,
   and since the column starts at translateY(0) and only ever moves up, the
   first log line then spends its whole life inside the fade at ~20% opacity.
   It was never legible. */
.stream-view{
  flex:0 1 auto;
  height:420px;            /* placeholder; app.js overwrites on mount */
  max-height:100%;
  overflow:hidden;
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 26px);
  mask-image:linear-gradient(to bottom,transparent 0,#000 26px);
}
/* Pairs with the mask so row 0 starts below the ramp. The scroll math reads
   offsetTop, so this padding is absorbed automatically. */
.stream{
  padding-top:34px;
  transition:transform .55s var(--enter);
}

/* Row spacing is padding, NOT gap: the scroll math measures
   offsetTop + offsetHeight, and offsetHeight includes padding but not margin
   or flex gap. The 18px also keeps the stream tall enough to overflow the
   tallest window we render — see app.js. */
.row{padding-bottom:18px;animation:nsLineIn .4s ease both}

/* label ······················· value */
.lg{display:flex;align-items:baseline;gap:8px;font-size:13px;color:var(--lede)}
.lg i{flex:1;border-bottom:1px dotted var(--line-mono)}
.lg .ok{color:var(--cyan);font-weight:400}
.lg .val{color:var(--cream);font-weight:400}
.lg .empty{color:var(--faint);font-weight:400}
.lg.faint{color:var(--faint)}
.lg.faint .val{color:var(--lede)}
.row.owner{padding-top:24px}

/* The switch from mono to Schibsted is the emotional pivot of the scene.
   Mono means the machine is speaking; this is Nessi speaking. Don't flatten it. */
.awake{
  padding:20px 0 18px;
  font-family:var(--font-sans);font-size:26px;font-weight:500;letter-spacing:-.02em;
  background:var(--grad-head);
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
}
.hi{
  font-family:var(--font-sans);font-size:36px;font-weight:700;letter-spacing:-.03em;
  color:var(--cream);
}

.lrn{font-size:12.5px;color:var(--faint)}
.lrn.first{padding-top:22px}
.lrn.wink{color:var(--coral)}

/* ══════════════ HANDOFF ══════════════
   One tap. Everything else on this screen exists to not compete with it. */
.fin{
  position:absolute;inset:0;overflow-y:auto;
  display:flex;flex-direction:column;
  padding:
    calc(90px + env(safe-area-inset-top)) 26px
    calc(44px + env(safe-area-inset-bottom));
}
.fin::-webkit-scrollbar{width:0}

.live{display:flex;align-items:center;gap:9px}
.dot{width:8px;height:8px;border-radius:50%;background:var(--green)}
#vFin.on .dot{animation:nsDot 2s ease-in-out infinite}
.live-lbl{
  font-family:var(--font-mono);font-size:11px;
  letter-spacing:.12em;text-transform:uppercase;color:var(--faint);
}

/* Deliberately de-emphasised — no gradient, muted colour — so the button owns
   the screen. Do not "fix" this by making it brighter. */
.finh{
  margin:20px 0 0;
  font-size:clamp(28px,8.6vw,36px);line-height:1.08;
  font-weight:600;letter-spacing:-.03em;color:var(--muted);
}
.finh span{color:var(--cream)}

/* On a phone this pushes the button down to the thumb, which is the whole
   point. On a desktop window the same flex:1 opens a 250px void between the
   headline and the button and the screen reads as a layout bug. Same media
   query the QR uses, because it is the same "this is a desktop" question:
   collapse the spacer to a fixed gap and centre the column instead. */
.spacer{flex:1;min-height:34px}
@media (hover:hover) and (pointer:fine){
  .fin{justify-content:center}
  .spacer{flex:0 0 auto;height:40px;min-height:0}
}

/* Coral, like Get Nessi — both buttons are the user acting. This ramp is
   DARKER than the dock's because it carries light text and the label sits over
   the gradient's lightest stop: off-white on the dock's #FF8F6B measures ~2.9:1,
   under the 3:1 floor for large text. This ramp measures 3.85:1 there. The dock
   button keeps dark ink on the lighter ramp, which is correct at 15px; the two
   never appear on the same screen. */
.go{
  display:flex;align-items:center;justify-content:center;gap:12px;
  padding:25px 24px;border-radius:24px;
  background:linear-gradient(120deg,#D9501F,#C4441A 62%,#A83510);
  color:#FFF6F1;
  font-size:22px;font-weight:700;letter-spacing:-.015em;
  box-shadow:0 0 0 1px rgba(255,143,107,.42),0 18px 50px rgba(217,80,31,.3);
}
.go span{font-weight:600}
.go:hover{filter:brightness(1.08);color:#FFF6F1}

.fine{
  margin:18px 0 0;
  display:flex;flex-direction:column;align-items:center;gap:5px;
  text-align:center;
  font-size:12.5px;line-height:1.4;color:var(--faint);
}
.fine-row{display:flex;align-items:center;gap:8px}
/* No shadow at this size — a 0 8px 22px glow under a 19px tile reads as a smudge. */
.ptile{
  display:none;flex-shrink:0;width:19px;height:19px;border-radius:5px;
  align-items:center;justify-content:center;
}
.ptile svg{width:12px;height:12px}
.ptile-ios{background:linear-gradient(160deg,#5BE370,#21B93F)}
.ptile-android{background:#FFF}
.fin .ptile-ios{display:flex}
.fin.android .ptile-ios{display:none}
.fin.android .ptile-android{display:flex}
.num{
  font-family:var(--font-mono);font-size:11.5px;letter-spacing:.04em;
  color:var(--ghost);
  user-select:all;-webkit-user-select:all;
}

#vFin.on .live{animation:nsUp .7s var(--enter) .1s both}
#vFin.on .finh{animation:nsUp .8s var(--enter) .22s both}
#vFin.on .go{animation:nsUp .8s var(--enter) .5s both,nsGlow 2.6s ease-in-out 1.4s infinite}
#vFin.on .fine{animation:nsFade .8s ease .66s both}
#vFin.on .qrwrap.on{animation:nsFade .8s ease .8s both}

/* ── desktop QR ────────────────────────────────────────
   sms: is inert on most desktops. Rather than leave a dead button, hand them
   the same pre-filled message on their phone. */
.qrwrap{
  display:none;flex-direction:column;align-items:center;gap:10px;
  margin:26px 0 0;
}
.qrwrap.on{display:flex}
.qr{
  width:132px;height:132px;overflow:hidden;
  background:#fff;border-radius:12px;
  box-shadow:0 10px 30px rgba(0,0,0,.5);
}
.qr svg{display:block;width:100%;height:100%}
.qrcap{font-size:11.5px;color:var(--ghost);margin:0;line-height:1.45;text-align:center}

/* ── reduced motion ─────────────────────────────────────
   Durations and loops go everywhere. Delays are handled per screen, because
   the screens are not the same kind of thing.

   Landing and handoff are plain entrances — staging exists only to give the
   motion somewhere to happen. With the motion gone, keeping the delays leaves
   a reduced-motion visitor watching blank space while the copy, the proof card
   and (worst) the whole dock pop in over three seconds. So: delays to zero,
   the screen is simply there.

   The ceremony keeps its delays. It is a narrative — provision, dim, wake,
   greet — and collapsing it onto one frame doesn't remove motion, it removes
   the story. Nine seconds of lines arriving on cue with no movement is still
   the scene.

   The intro is skipped outright in app.js. Zeroing its delays instead — which
   the design handoff's snippet does globally — fires all twelve storm cards,
   four story beats and the orb on a single frame, which is louder than the
   motion it was meant to spare you. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important;
    animation-iteration-count:1!important;
    transition-duration:.01ms!important;
    scroll-behavior:auto!important;
  }
  /* ::before/::after are spelled out because `#vLand *` does NOT match a
     pseudo-element. Without them the dock's scrim keeps its 3.3s delay while
     everything around it snaps in at zero, and the dock sits bare for three
     seconds — the one place in this view that has a pseudo-element animation. */
  #vLand *,#vLand *::before,#vLand *::after,
  #vFin *,#vFin *::before,#vFin *::after{animation-delay:0s!important}
  #vIntro.on{animation:none!important}

  /* The ceremony's delays are NOT zeroed, so app.js keeps its normal schedule
     here rather than jumping the stream to its resting position. Every row now
     uses nsLineIn, which ends visible and stays — the old montage, which ended
     at opacity 0 and needed a special case to survive this, is gone. The column
     still advances on cue; the .55s transition just collapses, so it steps
     instead of gliding. */
}
