/* /echo layer on top of /echo-assets/echo-landing.css.
   1. top menu  2. privacy: lock, headline, bento of six live cards
   3. hero entrance.
   Contains vanilla CSS/JS ports of Magic UI components
   (github.com/magicuidesign/magicui), behaviour matched to source:
   Bento Grid, Blur Fade, Text Animate (blurInUp), Animated Beam, Hyper Text,
   Ripple, Dotted Map.

   MIT License

   Copyright (c) Magic UI

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in all
   copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE. */

/* ── 1. Top menu ── */
.nav-links {
  display: flex;
  align-items: center;
  gap: 28px;
}
.nav-links a {
  font: 500 14px/1 var(--body);
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  padding: 8px 0;
  transition: color 160ms ease;
}
.nav-links a:hover { color: var(--ink); }
.nav-links .nav-dl {
  color: var(--gold);
  font-weight: 600;
  transition: color 160ms ease, opacity 260ms var(--ease), visibility 0s linear 0s;
}
.nav-links .nav-dl:hover { color: #ffd970; }
/* Hidden (but holding its space, so the other links never jump) until the
   hero button leaves the screen. visibility also drops it from Tab order. */
.pjs .nav-links .nav-dl:not(.is-on) {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: color 160ms ease, opacity 200ms ease, visibility 0s linear 200ms;
}
/* The Privacy jump lands with the lock 40px under the bar. */
#privacy { scroll-margin-top: calc(var(--nav-h) + 40px - clamp(96px, 13vw, 180px)); }

@media (max-width: 560px) {
  .nav { padding: 0 16px; }
  .nav-links { gap: 20px; }
  .nav-links a { font-size: 14px; padding: 13px 0; }
}
/* Phones can't install Echo: the Download word never appears up top. */
@media (pointer: coarse) and (max-width: 820px) {
  .nav-links .nav-dl { display: none; }
}

/* ── Gap above "Hidden from screen share." ──
   It was ~650-750px of empty dark: the last step ("Hang up...") is a 78vh box
   (50vh on phones) with its text in the middle/top, the steps column then adds
   20vh (30vh on phones) of bottom padding, and the share section opens with
   160px (80px on phones) of top padding. The last step still reaches the
   reading line and the demo stays pinned while it plays; only the slack goes. */
.steps { padding-bottom: 6vh; }
.step[data-step="recap"] { min-height: 56vh; }
.share { padding-top: clamp(56px, 7vw, 96px); }
@media (max-width: 980px) {
  .steps { padding-bottom: 10vh; }
  .step[data-step="recap"] { min-height: 0; }
  .share { padding-top: 56px; }
}

/* ── 2. Privacy ── */
.privacy {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(96px, 13vw, 180px) clamp(16px, 4vw, 48px) 0;
  text-align: center;
}
.privacy-head { display: flex; flex-direction: column; align-items: center; }
.privacy h2 {
  margin-top: 36px;
  font-size: clamp(34px, 4.6vw, 60px);
  line-height: 1.04;
  letter-spacing: -0.03em;
}
.privacy-lede {
  margin-top: 16px;
  font-size: 19px;
  color: var(--muted);
  max-width: 34ch;
}

/* ── The lock ──
   Plays once when it is well in view (echo-privacy.js adds .is-playing), 1.45s:
     0-560ms   the shackle, lifted and flipped out to the LEFT about its long
               right leg (which never leaves the body), swings across to the
               right into line over the body
     560-760   it drops and seats, accelerating so it lands hard
     760       click: the lock dips and rebounds, the body turns gold, the
               keyhole appears, a glow blooms, one sheen crosses the body.
   All 2D, no perspective or preserve-3d: iOS Safari depth-sorted the 3D
   shackle layer in front of the body, so leg ends showed inside it. The swing
   is a scaleX flip (-1 -> 1) about the long leg (x = 35), and the body is a
   later, opaque layer, so any part of a leg inside it is always hidden. */
.lock {
  position: relative;
  width: 120px;
  height: 150px;
}
.lock::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 62%;
  width: 300px;
  height: 300px;
  transform: translate(-50%, -50%) scale(0.5);
  background: radial-gradient(closest-side, rgba(255, 200, 50, 0.26), transparent);
  opacity: 0;
  pointer-events: none;
}
.lock-inner { position: relative; width: 120px; height: 150px; transform-origin: 50% 100%; }
.lock svg { position: absolute; inset: 0; display: block; overflow: visible; }
.lock-lift, .lock-swing { position: absolute; inset: 0; }
/* Paint order is explicit: shackle underneath, body (opaque) on top. */
.lock-lift { z-index: 1; }
.lock .lock-body-svg { z-index: 2; }
.lock-swing { transform-origin: 35px 0; }
.lock-shackle { stroke: var(--gold); }
.lock-body { fill: #16181e; stroke: var(--gold); stroke-width: 3; }
.lock-body-fill { fill: url(#lock-fill); opacity: 0; }
.lock-hole { fill: #1a1405; opacity: 0; }
.lock-sheen { fill: url(#lock-sheen-g); opacity: 0; }

/* Open: the resting state before it plays. */
.pjs .lock-lift { transform: translateY(-26px); }
.pjs .lock-swing { transform: scaleX(-1); }

.lock.is-playing .lock-swing { animation: lk-swing 560ms cubic-bezier(0.45, 0, 0.2, 1) both; }
.lock.is-playing .lock-lift { animation: lk-drop 200ms cubic-bezier(0.55, 0, 0.9, 0.45) 560ms both; }
.lock.is-playing .lock-inner { animation: lk-click 420ms cubic-bezier(0.2, 0.7, 0.3, 1) 760ms both; }
.lock.is-playing .lock-body-fill { animation: lk-fade 260ms ease-out 760ms both; }
.lock.is-playing .lock-hole { animation: lk-fade 240ms ease-out 860ms both; }
.lock.is-playing::before { animation: lk-glow 900ms cubic-bezier(0.22, 1, 0.36, 1) 760ms both; }
.lock.is-playing .lock-sheen { animation: lk-sheen 700ms cubic-bezier(0.4, 0, 0.2, 1) 900ms both; }
@keyframes lk-swing { from { transform: scaleX(-1); } to { transform: scaleX(1); } }
@keyframes lk-drop { from { transform: translateY(-26px); } to { transform: translateY(0); } }
@keyframes lk-click {
  0% { transform: none; }
  28% { transform: translateY(4px) scale(1.035, 0.95); }
  62% { transform: translateY(-2px) scale(0.99, 1.015); }
  100% { transform: none; }
}
@keyframes lk-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes lk-glow { from { opacity: 0; transform: translate(-50%, -50%) scale(0.5); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } }
@keyframes lk-sheen {
  0% { opacity: 1; transform: skewX(-18deg) translateX(0); }
  99% { opacity: 1; }
  100% { opacity: 0; transform: skewX(-18deg) translateX(200px); }
}

/* Closed for good (also the reduced-motion end state). */
.pjs .lock.is-locked .lock-lift, .pjs .lock.is-locked .lock-swing { animation: none; transform: none; }
.lock.is-locked .lock-body-fill, .lock.is-locked .lock-hole { opacity: 1; animation: none; }
.lock.is-locked::before { opacity: 1; transform: translate(-50%, -50%) scale(1); animation: none; }

/* Headline: Magic UI Text Animate "blurInUp", by word (y 20px, blur 10px,
   0.3s / opacity 0.4s, 50ms stagger). Lede follows. Starts with the click. */
.pjs .privacy-h .w, .pjs .step--hero h1 .w {
  display: inline-block;
  opacity: 0;
  transform: translateY(20px);
  filter: blur(10px);
}
.pjs .privacy-head.is-in .privacy-h .w {
  opacity: 1;
  transform: none;
  filter: none;
  transition: opacity 400ms ease-out, transform 300ms ease-out, filter 300ms ease-out;
  transition-delay: calc(var(--i) * 50ms);
}
.pjs .privacy-lede { opacity: 0; transform: translateY(-6px); filter: blur(6px); }
.pjs .privacy-head.is-in .privacy-lede { opacity: 1; transform: none; filter: none; transition: opacity 400ms ease-out 340ms, transform 400ms ease-out 340ms, filter 400ms ease-out 340ms; }

/* ── Bento (Magic UI Bento Grid: 3 columns, gap 16px, rounded-xl cards,
   dark style = 1px rgba(255,255,255,.1) border + 0 -20px 80px -20px #ffffff1f
   inset). Cards size to their text here instead of fixed 22rem rows, because
   the copy is the point. The hover lift/CTA is dropped: no per-card link. ── */
.bento {
  list-style: none;
  margin: clamp(56px, 7vw, 88px) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  text-align: left;
}
.bento-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.022);
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 -20px 80px -20px rgba(255, 255, 255, 0.07) inset;
}
.bento-card.span-2 { grid-column: span 2; }
.bento-copy { padding: 4px 22px 24px; }
.bento h3 {
  margin: 0;
  font: 600 19px/1.25 var(--display);
  letter-spacing: -0.01em;
  color: var(--ink);
  text-wrap: balance;
}
.bento p {
  margin-top: 8px;
  font-size: 15px;
  line-height: 1.55;
  color: var(--muted);
  max-width: 52ch;
}

/* Blur Fade (Magic UI): opacity 0, y -6px, blur 6px -> settle, 0.4s easeOut,
   delay 0.04s + stagger. Once, on entry. */
.pjs .bento-card { opacity: 0; transform: translateY(-6px); filter: blur(6px); }
.pjs .bento-card.is-in {
  opacity: 1;
  transform: none;
  filter: none;
  transition: opacity 400ms ease-out, transform 400ms ease-out, filter 400ms ease-out;
  transition-delay: var(--d, 40ms);
}

/* The stage every card's animation lives in. */
.viz {
  position: relative;
  height: 200px;
  margin: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 78%, transparent);
          mask-image: linear-gradient(180deg, #000 78%, transparent);
}
.beams { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }

/* Shared bits */
.node { position: relative; z-index: 2; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 12px;
  border-radius: 10px;
  background: #15171c;
  border: 1px solid rgba(255, 255, 255, 0.12);
  font: 500 13px/1 var(--body);
  color: var(--ink);
  white-space: nowrap;
}
.hub {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: 16px;
  background: #15171c;
  border: 1px solid rgba(255, 200, 50, 0.45);
  box-shadow: 0 0 30px -6px rgba(255, 200, 50, 0.35);
}
.mini-bars { display: inline-flex; align-items: center; gap: 2px; height: 14px; }
.mini-bars i { display: block; width: 3px; border-radius: 2px; background: var(--gold); }
.mini-bars i:nth-child(1), .mini-bars i:nth-child(5) { height: 6px; }
.mini-bars i:nth-child(2), .mini-bars i:nth-child(4) { height: 10px; }
.mini-bars i:nth-child(3) { height: 14px; }
.hub .mini-bars { gap: 3px; height: 20px; }
.hub .mini-bars i { width: 4px; }
.hub .mini-bars i:nth-child(1), .hub .mini-bars i:nth-child(5) { height: 8px; }
.hub .mini-bars i:nth-child(2), .hub .mini-bars i:nth-child(4) { height: 14px; }
.hub .mini-bars i:nth-child(3) { height: 20px; }
.mini-wave { display: inline-flex; align-items: center; gap: 2px; height: 14px; }
.mini-wave i { display: block; width: 2px; height: 12px; border-radius: 2px; background: rgba(255, 255, 255, 0.7); transform-origin: center; }
.is-live .mini-wave i { animation: mw 900ms ease-in-out infinite; }
.mini-wave i:nth-child(2) { animation-delay: -200ms !important; }
.mini-wave i:nth-child(3) { animation-delay: -450ms !important; }
.mini-wave i:nth-child(4) { animation-delay: -650ms !important; }
.mini-wave i:nth-child(5) { animation-delay: -100ms !important; }
@keyframes mw { 0%, 100% { transform: scaleY(0.35); } 50% { transform: scaleY(1); } }
.is-live .mini-bars i { animation: mb 1200ms ease-in-out infinite; transform-origin: center; }
.mini-bars i:nth-child(2) { animation-delay: -300ms !important; }
.mini-bars i:nth-child(3) { animation-delay: -600ms !important; }
.mini-bars i:nth-child(4) { animation-delay: -900ms !important; }
@keyframes mb { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.55); } }
.mini-screen { width: 16px; height: 11px; border-radius: 2px; border: 1.5px solid rgba(255, 255, 255, 0.7); }
.mini-stack { display: inline-flex; flex-direction: column; gap: 2px; }
.mini-stack i { display: block; width: 14px; height: 3px; border-radius: 1px; background: rgba(255, 255, 255, 0.7); }

/* 1 · No bot: the meeting has three people; Echo sits outside it, on your
   computer. Tiles arrive one at a time and the count never passes 3. */
.viz-call { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 16px 18px 0; }
.vc-echo {
  display: inline-flex; align-items: center; gap: 8px;
  height: 30px; padding: 0 12px; border-radius: 999px;
  background: #101114; border: 1px solid rgba(255, 200, 50, 0.4);
  font: 600 12.5px/1 'DM Mono', monospace; letter-spacing: 0.08em; color: var(--ink);
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.8), 0 0 24px -8px rgba(255, 200, 50, 0.35);
}
.vc-echo em { font: 400 11.5px/1 var(--body); letter-spacing: 0; font-style: normal; color: var(--faint); margin-left: 4px; }
.vc-window { width: 100%; max-width: 300px; border-radius: 12px; background: #121419; border: 1px solid rgba(255, 255, 255, 0.08); padding: 8px; }
.vc-head { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--faint); padding: 0 2px 7px; }
.vc-head b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.vc-live { width: 6px; height: 6px; border-radius: 50%; background: #e5534b; }
.vc-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.vc-tile {
  position: relative; overflow: hidden; height: 68px; border-radius: 8px;
  background: #1c1f26; display: grid; place-items: center;
}
.vc-tile b { position: relative; z-index: 1; display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%; background: #2b2f38; font: 600 11px/1 var(--body); color: rgba(255, 255, 255, 0.8); }
.vc-tile.is-speaking { box-shadow: inset 0 0 0 1.5px rgba(255, 200, 50, 0.6); }
/* Ripple (Magic UI): concentric rings, each 'ripple' 2s ease, delay i*0.2s,
   scale 1 <-> 0.9, opacity stepping down, masked to fade downwards. */
.ripple { position: absolute; inset: 0; pointer-events: none; -webkit-mask-image: linear-gradient(to bottom, #fff, transparent); mask-image: linear-gradient(to bottom, #fff, transparent); }
.ripple i {
  position: absolute; left: 50%; top: 50%;
  width: calc(36px + var(--i) * 22px); height: calc(36px + var(--i) * 22px);
  border-radius: 50%;
  border: 1px solid rgba(255, 200, 50, 0.7);
  background: rgba(255, 200, 50, 0.08);
  opacity: calc(0.5 - var(--i) * 0.1);
  transform: translate(-50%, -50%) scale(1);
}
.is-live .ripple i { animation: ripple 2s ease calc(var(--i) * 0.2s) infinite; }
@keyframes ripple { 0%, 100% { transform: translate(-50%, -50%) scale(1); } 50% { transform: translate(-50%, -50%) scale(0.9); } }
.pjs .viz-call .vc-tile, .pjs .viz-call .vc-echo { opacity: 0; transform: translateY(-6px); filter: blur(6px); }
.pjs .viz-call.is-go .vc-tile, .pjs .viz-call.is-go .vc-echo { opacity: 1; transform: none; filter: none; transition: opacity 400ms ease-out, transform 400ms ease-out, filter 400ms ease-out; }
.viz-call.is-go .vc-tile:nth-child(1) { transition-delay: 100ms; }
.viz-call.is-go .vc-tile:nth-child(2) { transition-delay: 380ms; }
.viz-call.is-go .vc-tile:nth-child(3) { transition-delay: 660ms; }
.viz-call.is-go .vc-echo { transition-delay: 1000ms; }

/* 2 · Not saved: audio and screen flow into Echo (Animated Beam); Echo
   pulses as it uses them; only fragments come out its right side, drift and
   fade; "Never stored" forms where they were. */
.viz-flow { display: grid; grid-template-columns: auto auto 1fr; justify-content: start; align-items: center; gap: clamp(28px, 6vw, 72px); padding: 0 clamp(18px, 4vw, 44px); }
.viz-flow .nf-col { display: flex; flex-direction: column; gap: 22px; }
/* Echo "uses" what came in: one pulse, then the pieces break up and fade. */
.viz-flow .hub.is-using { animation: nf-use 520ms cubic-bezier(0.2, 0.7, 0.3, 1); }
@keyframes nf-use {
  0% { transform: none; box-shadow: 0 0 30px -6px rgba(255, 200, 50, 0.35); }
  40% { transform: scale(1.08); box-shadow: 0 0 44px 2px rgba(255, 200, 50, 0.6); }
  100% { transform: none; box-shadow: 0 0 30px -6px rgba(255, 200, 50, 0.35); }
}
/* "Never stored" forms where the dust was: blur-in at the cloud's centre
   (echo-privacy.js places it), and clears just before the next pulse. */
.nf-label {
  position: absolute; left: 0; top: 0; z-index: 2;
  font: 500 13px/1 var(--body); color: var(--muted); white-space: nowrap;
  transform: translate(-50%, -50%) scale(0.96); opacity: 0; filter: blur(6px);
  transition: opacity 260ms ease-in, filter 260ms ease-in, transform 260ms ease-in;
}
.nf-label.is-on {
  transform: translate(-50%, -50%); opacity: 1; filter: none;
  transition: opacity 600ms ease-out, filter 600ms ease-out, transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
}
.nf-dust { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.nf-dust i { position: absolute; left: 0; top: 0; width: 3.5px; height: 3.5px; border-radius: 1px; background: rgba(255, 255, 255, 0.85); opacity: 0; will-change: transform, opacity; }

/* 3 · Not sold or trained on: one live beam to your answers; the beams to
   training and sale stop dead at a gate. */
.viz-fan { display: flex; align-items: center; justify-content: space-between; padding: 0 clamp(18px, 4vw, 44px); }
.fan-col { display: flex; flex-direction: column; gap: 16px; align-items: flex-end; }
.chip.is-on { border-color: rgba(255, 200, 50, 0.5); }
.chip.is-off { color: var(--faint); border-style: dashed; background: transparent; text-decoration: line-through; text-decoration-color: rgba(255, 255, 255, 0.35); }
.gate { stroke: var(--gold); stroke-width: 2.5; stroke-linecap: round; }
.gate-hit { fill: rgba(255, 200, 50, 0.9); opacity: 0; }

/* 4 · Encrypted: Animated Beam from the app to our servers; the question
   rides it as Hyper Text, scrambled for the whole trip. */
.viz-enc { display: flex; align-items: center; justify-content: space-between; padding: 0 18px; }
.viz-enc .chip:first-child { font: 600 12.5px/1 'DM Mono', monospace; letter-spacing: 0.08em; }
.enc-chip {
  position: absolute; left: 0; top: 0; z-index: 3;
  padding: 5px 8px; border-radius: 7px;
  background: #101114; border: 1px solid rgba(255, 200, 50, 0.45);
  font: 500 11.5px/1 'DM Mono', monospace; color: var(--gold); white-space: pre;
  opacity: 0; will-change: transform, opacity;
}

/* 5 · Canada: Dotted Map (Magic UI), Canada in gold, the US dim. The pin
   drops on Canada; a dashed line runs south to where processing happens. */
.viz-map { display: grid; place-items: stretch; padding: 10px 14px 14px; }
.viz-map { padding-bottom: 14px; }
.viz-map .map { width: 100%; height: 100%; min-height: 0; }
.map .d { transition: opacity 500ms ease-out; }
.pjs .viz-map:not(.is-go) .map .d { opacity: 0 !important; }
.map-pin-drop { transform-box: fill-box; transform-origin: 50% 100%; }
.pjs .viz-map:not(.is-go) .map-pin-drop { opacity: 0; transform: translateY(-14px); }
.viz-map.is-go .map-pin-drop { transition: opacity 200ms ease-out 700ms, transform 520ms cubic-bezier(0.34, 1.56, 0.64, 1) 700ms; }
.map-route { fill: none; stroke: rgba(255, 255, 255, 0.45); stroke-width: 0.9; stroke-dasharray: 2.2 2.6; }
.pjs .viz-map:not(.is-go) .map-route { opacity: 0; }
.viz-map.is-go .map-route { transition: opacity 600ms ease-out 1300ms; }
/* "Stored": SVG in map units (echo-privacy.js), up and right of the pin. */
.map-stored rect { fill: rgba(16, 17, 20, 0.85); stroke: rgba(255, 200, 50, 0.45); stroke-width: 0.53; }
.map-stored text { fill: var(--gold); font-family: var(--body); }
.pjs .viz-map:not(.is-go) .map-stored { opacity: 0; }
.viz-map.is-go .map-stored { transition: opacity 400ms ease-out 1000ms; }

/* 6 · 12 months: the tear-off calendar (styles further down). */
.viz-months { display: flex; align-items: center; justify-content: center; gap: clamp(18px, 4vw, 40px); padding: 0 22px; }

.privacy-more { margin-top: 48px; font-size: 15px; }
.pjs .privacy-more { opacity: 0; transform: translateY(-6px); filter: blur(6px); }
.pjs .privacy-more.is-in { opacity: 1; transform: none; filter: none; transition: opacity 400ms ease-out, transform 400ms ease-out, filter 400ms ease-out; }
.privacy-more a { color: var(--gold); text-decoration: none; font-weight: 600; }
.privacy-more a::after { content: ' \2192'; }
.privacy-more a:hover { text-decoration: underline; text-underline-offset: 4px; }

/* The gap after the policy link: the final section's own top padding plus
   its 92vh minimum height left ~500px of dark. */
.privacy + .final {
  min-height: 0;
  padding-top: clamp(96px, 11vw, 150px);
}

@media (max-width: 980px) {
  .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .bento-card.span-2 { grid-column: span 2; }
}
@media (max-width: 640px) {
  .privacy-lede { font-size: 17px; }
  .lock { transform: scale(0.84); margin: -12px 0; }
  .bento { grid-template-columns: 1fr; gap: 12px; }
  .bento-card.span-2 { grid-column: auto; }
  .viz { height: 180px; }
  .bento-copy { padding: 2px 18px 20px; }
  .bento h3 { font-size: 18px; }
  .privacy-more { margin-top: 40px; }
}

/* ── 3. Hero: arrives on load (Text Animate blurInUp by word, then the line
   and the button blur-fade in). Once. ── */
.pjs .step--hero h1 .w {
  animation: tw-in 400ms ease-out both;
  animation-delay: calc(100ms + var(--i) * 50ms);
}
.pjs .step--hero .lede { animation: bf-in 500ms ease-out 420ms both; }
.pjs .step--hero .get { animation: bf-in 500ms ease-out 560ms both; }
@keyframes tw-in { to { opacity: 1; transform: none; filter: none; } }
/* Once the entrance has played, echo-privacy.js puts the headline back to plain text
   and adds .hero-done: no animation, filter or transform is left on the hero
   text, so it rasterises crisp (a finished fill-mode animation kept
   filter: blur(0px) and an identity transform, which keeps the text on its
   own compositing layer and softens it). */
.hero-done .lede, .hero-done .get, .hero-done h1 { animation: none !important; filter: none !important; transform: none !important; opacity: 1 !important; }
@keyframes bf-in { from { opacity: 0; transform: translateY(-6px); filter: blur(6px); } to { opacity: 1; transform: none; filter: none; } }

@media (prefers-reduced-motion: reduce) {
  .pjs .lock-lift, .pjs .lock-swing { transform: none; }
  .lock-body-fill, .lock-hole { opacity: 1 !important; }
  .lock *, .lock::before, .lock-inner { animation: none !important; }
  .lock::before { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  .pjs .privacy-h .w, .pjs .privacy-lede, .pjs .privacy-more, .pjs .bento-card,
  .pjs .viz-call .vc-tile, .pjs .viz-call .vc-echo,
  .pjs .step--hero h1 .w, .pjs .step--hero .lede, .pjs .step--hero .get {
    opacity: 1 !important; transform: none !important; filter: none !important;
    transition: none !important; animation: none !important;
  }
  .pjs .viz-map .map-pin-drop, .pjs .viz-map .map-route, .pjs .viz-map .map-stored { opacity: 1 !important; transform: none !important; transition: none !important; }
  .viz * { animation: none !important; }
  .nf-label { opacity: 1 !important; transition: none !important; }
  .nf-dust { display: none !important; }
  .nav-links .nav-dl { transition: none !important; }
}

/* ══ Even bento (round 6) ══
   Six equal cards: 3 x 2 on desktop, 2 x 3 at tablet, 1 column on phones.
   Every card has the same 190px stage, centred; each visual is laid out
   for a ~340-370px card, which is also the phone card width. */
.privacy { max-width: 1200px; }
.bento { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: 1fr; }
.bento-card.span-2 { grid-column: auto; }
.bento p { max-width: none; }
.viz { height: 190px; }
.bento-copy { padding: 6px 22px 24px; }

/* The third column is the fragment / label zone: as wide as the fragments'
   centre-to-label reach (the label is centred 72px past Echo, 116px drift),
   so the whole group (chips, Echo, label) centres in the stage.
   echo-privacy.js sets --nf-zone from the label's measured width. */
.viz-flow { grid-template-columns: auto auto minmax(0, var(--nf-zone, 100px)); justify-content: center; gap: 26px; padding: 0 18px; }
.viz-fan { padding: 0 18px; }
.viz-enc { align-items: flex-end; padding: 0 18px 34px; }
.viz-map { padding: 8px 14px 12px; }

.viz-months { flex-direction: column; justify-content: center; gap: 18px; padding: 0 18px; }

@media (max-width: 980px) {
  .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .bento-card.span-2 { grid-column: auto; }
}
@media (max-width: 640px) {
  .bento { grid-template-columns: 1fr; grid-auto-rows: auto; }
  .viz { height: 190px; }
}

/* Encrypted: "Our servers" pulses gold as the scrambled text is absorbed. */
.enc-chip { transform-origin: 50% 100%; }
.viz-enc .chip.is-hit { animation: enc-hit 520ms cubic-bezier(0.2, 0.7, 0.3, 1); }
@keyframes enc-hit {
  0% { border-color: rgba(255, 255, 255, 0.12); box-shadow: none; transform: none; }
  35% { border-color: rgba(255, 200, 50, 0.8); box-shadow: 0 0 22px -2px rgba(255, 200, 50, 0.55); transform: scale(1.04); }
  100% { border-color: rgba(255, 255, 255, 0.12); box-shadow: none; transform: none; }
}

/* Reyaan: no glowing orb behind the lock. */
.lock::before, .lock.is-playing::before, .lock.is-locked::before { display: none !important; }

/* The map fills its stage to the bottom edge: no fade-out mask on it. */
.viz.viz-map { -webkit-mask-image: none; mask-image: none; }

/* ── Canada round trip ── */
.map-ring, .map-dot { transform-box: fill-box; transform-origin: center; }
.map-dot { filter: drop-shadow(0 0 1.2px rgba(255, 200, 50, 0.9)); }
.map-stored.is-hit rect { animation: map-hit 900ms ease-out; }
.map-stored.is-hit text { animation: map-hit-t 900ms ease-out; }
@keyframes map-hit {
  0% { fill: rgba(16, 17, 20, 0.85); filter: none; }
  25% { fill: rgba(255, 200, 50, 0.95); filter: drop-shadow(0 0 4px rgba(255, 200, 50, 0.6)); }
  100% { fill: rgba(16, 17, 20, 0.85); filter: none; }
}
@keyframes map-hit-t {
  0% { fill: var(--gold); }
  25% { fill: #1a1405; }
  100% { fill: var(--gold); }
}

/* ── 12 months: tear-off desk calendar (pages fall; .viz clips them) ── */
.viz-cal { gap: 12px; }
.cal { position: relative; width: 148px; }
.cal-rings { position: absolute; left: 0; right: 0; top: -7px; z-index: 5; display: flex; justify-content: space-between; padding: 0 30px; pointer-events: none; }
.cal-rings i { width: 8px; height: 16px; border-radius: 4px; background: #2d313a; border: 1.5px solid rgba(255, 255, 255, 0.35); }
.cal-stack { position: relative; z-index: 1; height: 112px; }
.cal-page {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border-radius: 10px;
  background: linear-gradient(180deg, #232731 0 22px, #1a1d24 22px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  transform-origin: 50% 25%;
  box-shadow: 0 6px 14px -6px rgba(0, 0, 0, 0.7);
}
.cal-under { z-index: 1; }
.cal-top { z-index: 2; }
.cal-m { margin-top: 12px; font: 600 38px/1 var(--display); letter-spacing: -0.02em; color: var(--ink); }
.cal-y { font: 500 12px/1 'DM Mono', monospace; color: var(--faint); letter-spacing: 0.04em; }
.cal-tag { position: absolute; top: 3px; right: 8px; display: none; font: 600 10.5px/1 var(--body); color: #1a1405; background: var(--gold); padding: 3px 6px; border-radius: 5px; }
.cal-page.is-call .cal-tag { display: block; }
.cal-gone { display: none; font: 600 12px/1 'DM Mono', monospace; letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); border: 1.5px solid rgba(255, 255, 255, 0.28); padding: 6px 9px; border-radius: 6px; transform: rotate(-7deg); margin-top: 12px; }
.cal-page.is-gone .cal-m, .cal-page.is-gone .cal-y, .cal-page.is-gone .cal-tag { display: none; }
.cal-page.is-gone .cal-gone { display: block; }
.cal-count { position: relative; z-index: 30; margin: 0; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; min-height: 1em; }

/* "Processed" is now SVG text in map units (echo-privacy.js); it fades in with the route. */
.pjs .viz-map:not(.is-go) .map-proc { opacity: 0; }
.viz-map.is-go .map-proc { transition: opacity 400ms ease-out 1600ms; }
@media (prefers-reduced-motion: reduce) { .pjs .viz-map .map-proc { opacity: 1 !important; transition: none !important; } }

/* "How it works" moves focus to the first step (for keyboard and screen
   readers); it is a reading target, not a control, so no focus ring. */
#how-first:focus { outline: none; }

/* 320px phones: tighten the "Not saved" row so the label has room. */
@media (max-width: 359px) {
  .viz-flow { gap: 18px; padding: 0 14px; }
}

/* ── One page width (desktop) ──
   The story section is the reference: 1480px max, clamp(20px, 4vw, 48px)
   gutters, a 320-440px text column and the visual filling the rest. The
   share section (was 1280px), the privacy grid (was 1200px) and the footer
   (was 1280px) now use the same box, so their left and right edges line up
   with the story's text and stage. Phones (<= 980px) are untouched. */
@media (min-width: 981px) {
  .share, .privacy, .foot {
    max-width: 1480px;
    padding-left: clamp(20px, 4vw, 48px);
    padding-right: clamp(20px, 4vw, 48px);
  }
  .share {
    grid-template-columns: minmax(320px, 440px) minmax(0, 1fr);
    gap: clamp(24px, 4vw, 64px);
  }
}

/* Wide desktops: the bento spans the full page width, so cards are wider than
   the 356px their visuals were laid out for. echo-privacy.js sets --k (card
   width / 356) on each card; the stage is laid out at 356px-equivalent width
   and drawn scaled by --k, and reserves the extra height below it, so every
   visual grows with its card and nothing is clipped. Phones keep --k = 1. */
@media (min-width: 981px) {
  .bento-card .viz {
    width: calc(100% / var(--k, 1));
    transform: scale(var(--k, 1));
    transform-origin: 0 0;
    margin-bottom: calc(190px * (var(--k, 1) - 1));
  }
  /* One line under the headline on desktop (fits ~60 characters). */
  .privacy-lede { max-width: none; }
}

/* Card copy: centred titles and centred body text (all widths). Balanced
   wrapping evens out the lines (overriding echo-landing.css's site-wide
   p { text-wrap: pretty }). No automatic hyphenation: words are never split. */
.bento h3 { text-align: center; }
.bento p {
  text-align: center;
  text-wrap: balance;
  -webkit-hyphens: manual;
          hyphens: manual;
}
