/* Mutebot. The page is the editor: a brand-gradient canvas above, the dark
   chrome strip with a working timeline below. Tokens mirror VISUAL.md 3 and 11. */

:root {
  /* Brand surfaces. Fixed in both color schemes, exactly as the app's
     editor canvas is dark-locked in both appearances. */
  --brand-deep: #4b6496;
  --brand-teal: #19e1c8;
  --bg: #0b0e17;
  --surface: #1a2130;
  --surface-raised: #293349;
  /* Fixed dark ink for text set directly on the accent (the playhead chip).
     Not part of the surface ramp, so it does not move with it. */
  --ink: #101116;
  --on-chrome: #f2f3f7;
  --on-chrome-dim: #9ba0ae;
  --white: #ffffff;

  --range-fill: rgba(25, 225, 200, 0.22);
  --range-fill-soft: rgba(25, 225, 200, 0.2);
  --wave: rgba(25, 225, 200, 0.65);
  --wave-muted: rgba(255, 255, 255, 0.22);
  --hairline: rgba(255, 255, 255, 0.12);

  --font-display: ui-rounded, -apple-system, system-ui, sans-serif;
  --font-body: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI",
    Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas,
    monospace;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;

  --measure: 1120px;
  --pad: var(--space-6);

  /* The robot SVG is 450x410 in its own units (150 of that width is empty space
     to the left, where the thought trail runs), so its rendered height is
     width * 41 / 45. --robot-drop is how much of it the strip covers. */
  --robot-w: 520px;
  --robot-drop: 44px;
  --robot-visible: calc(var(--robot-w) * 41 / 45 - var(--robot-drop));
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-body);
  -webkit-text-size-adjust: 100%;
}

img,
svg {
  display: block;
  max-width: 100%;
}

.wrap {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--pad);
}

/* ------------------------------------------------------------------ *
 * index.html: the editor page
 * ------------------------------------------------------------------ */

.editor-page {
  color: var(--white);
  background: var(--bg);
}

.page {
  display: flex;
  flex-direction: column;
  min-height: 100svh;
}

/* --- canvas ------------------------------------------------------- */

.canvas {
  position: relative;
  flex: 1;
  display: flex;
  background-color: var(--brand-deep);
  background-image: linear-gradient(rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.45)),
    linear-gradient(135deg, var(--brand-deep), var(--brand-teal));
}

.canvas-inner {
  position: relative;
  display: flex;
  flex-direction: column;
}

.masthead {
  flex: 0 0 auto;
  padding: 28px 0;
}

.wordmark {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--white);
}

/* 2:3 above/below split, so the block sits slightly high of center. */
.hero-slot {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
}

.hero-slot::before {
  content: "";
  flex: 2 0 var(--space-7);
}

.hero-slot::after {
  content: "";
  flex: 3 0 var(--space-8);
}

.hero {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
}

.hero h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(44px, 7.5vw, 88px);
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.025em;
  color: var(--white);
}

/* The word "sound", caught in a timeline range. Screen readers see plain
   text; every part of the marker is decoration. */
/* The marker sat high on the word: the shortened line box takes its slack evenly
   off the top and bottom, but the glyphs sit high inside it, so the visible gap
   above the letters was the larger one. Taking another 0.08em off the line box
   and giving it back as bottom padding moves the whole marker down that far
   without changing its height. */
.ranged {
  position: relative;
  display: inline-block;
  padding: 0 0.14em 0.08em;
  line-height: 0.78;
  background: var(--range-fill-soft);
  border-top: 2px solid var(--brand-teal);
  border-bottom: 2px solid var(--brand-teal);
}

/* A playhead crosses the word, the same way one crosses the muted range in the
   strip below. It is one clipped element moving on transform and opacity only,
   which the compositor handles without touching layout or paint, so it costs
   nothing to run and nothing to leave running. */
.sweep {
  position: absolute;
  top: -2px;
  right: 0;
  bottom: -2px;
  left: 0;
  overflow: hidden;
  pointer-events: none;
}

.sweep::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  background: linear-gradient(
    90deg,
    var(--brand-teal) 0 2px,
    rgb(25 225 200 / 0.28) 2px 16px,
    transparent 16px
  );
  transform: translateX(-6%);
  opacity: 0;
}

.motion .sweep::before {
  animation: sweep 6400ms linear infinite;
}

/* Still for most of the loop, then one pass. Linear on purpose: the strip's own
   playhead is linear, and the two should read as the same clock. */
@keyframes sweep {
  0%,
  58% {
    transform: translateX(-6%);
    opacity: 0;
  }
  60% {
    transform: translateX(-6%);
    opacity: 1;
  }
  96% {
    transform: translateX(100%);
    opacity: 1;
  }
  100% {
    transform: translateX(100%);
    opacity: 0;
  }
}

.ranged::before,
.ranged::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 0.09em;
  height: 1.28em;
  border-radius: 2px;
  background: var(--brand-teal);
  transform: translate(-50%, -50%);
}

.ranged::before {
  left: 0;
}

.ranged::after {
  left: 100%;
}

/* The marker draws itself once on load: the handles slide out to the word's
   edges and the fill arrives behind them. It is the app's whole gesture, played
   on the headline, in half a second. Static without it, so nothing depends on
   the animation running. */
.motion .ranged {
  animation: range-fill 460ms ease-out 420ms backwards;
}

.motion .ranged::before {
  animation: handle-in-left 460ms cubic-bezier(0.2, 0.7, 0.3, 1) 420ms backwards;
}

.motion .ranged::after {
  animation: handle-in-right 460ms cubic-bezier(0.2, 0.7, 0.3, 1) 420ms backwards;
}

@keyframes range-fill {
  from {
    background-color: transparent;
    border-top-color: transparent;
    border-bottom-color: transparent;
  }
}

@keyframes handle-in-left {
  from {
    left: 50%;
    opacity: 0;
  }
}

@keyframes handle-in-right {
  from {
    left: 50%;
    opacity: 0;
  }
}

/* 560px is a reading measure, so it belongs on the prose, not on the
   headline: the headline breaks where the copy says it breaks. */
.lede {
  margin: var(--space-4) 0 0;
  max-width: 560px;
  font-size: clamp(17px, 1.6vw, 21px);
  line-height: 1.5;
  color: var(--white);
}

.cta {
  margin: 28px 0 0;
}

.badge {
  display: inline-block;
  border-radius: 8px;
}

.badge img {
  height: 54px;
  width: auto;
}

.price {
  margin: var(--space-3) 0 0;
  max-width: 560px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--white);
}

/* --- robot -------------------------------------------------------- */

/* It peeks, it does not stand: the trapezoid body runs on behind the
   chrome strip, which paints over it. */
.robot {
  position: absolute;
  right: var(--pad);
  bottom: calc(-1 * var(--robot-drop));
  z-index: 0;
  width: var(--robot-w);
  height: auto;
  pointer-events: none;
}

/* The trail runs up and to the left, out of the robot's head and toward the
   copy: the headline is what it is thinking. Each bubble carries its own resting
   opacity and its own delay, so the four of them read as one stream rather than
   four things blinking. */
.bubbles circle {
  transform-box: fill-box;
  transform-origin: center;
}

.motion .bubbles circle {
  animation: bubble-drift 6200ms var(--d, 0ms) ease-in-out infinite;
}

/* They never fade out. The trail is compositional now, it points the eye from the
   robot to the headline, so it has to be there whenever someone looks. It drifts
   and breathes instead. */
@keyframes bubble-drift {
  0%,
  100% {
    transform: translate(4px, 5px) scale(0.94);
    opacity: calc(var(--o, 0.6) * 0.62);
  }
  50% {
    transform: translate(-5px, -7px) scale(1.05);
    opacity: var(--o, 0.6);
  }
}

/* The blink swaps one designer-drawn eye for the other rather than dropping a
   lid over it: open is the mascot's ring, shut is the icon's barred disc. The
   swap is a hard cut, `steps(1)`, because two cutouts cross-fading over a
   gradient blends into mush and because this is how a drawn blink works. */
.eyes-shut {
  opacity: 0;
}

.motion .eyes-open {
  animation: eyes-open 8000ms steps(1) infinite;
}

.motion .eyes-shut {
  animation: eyes-shut 8000ms steps(1) infinite;
}

/* The visitor moved a handle: the robot notices, once. */
.motion .robot.noticed .eyes-open {
  animation: eyes-open-notice 900ms steps(1);
}

.motion .robot.noticed .eyes-shut {
  animation: eyes-shut-notice 900ms steps(1);
}

@keyframes eyes-open-notice {
  0%, 13%, 32%, 49%, 100% { opacity: 1; }
  14%, 31%, 50%, 67% { opacity: 0; }
}

@keyframes eyes-shut-notice {
  0%, 13%, 32%, 49%, 100% { opacity: 0; }
  14%, 31%, 50%, 67% { opacity: 1; }
}

/* Shut for about 190ms inside an eight second loop. Slower and rarer than a
   human blink on purpose: this one is a hard cut between two drawn states, and
   a cut that short reads as a glitch rather than as the robot closing its eyes. */
@keyframes eyes-open {
  0%,
  95.1%,
  97.7%,
  100% {
    opacity: 1;
  }
  95.2%,
  97.6% {
    opacity: 0;
  }
}

@keyframes eyes-shut {
  0%,
  95.1%,
  97.7%,
  100% {
    opacity: 0;
  }
  95.2%,
  97.6% {
    opacity: 1;
  }
}

/* --- chrome strip ------------------------------------------------- */

/* The strip reads as the app's editor console: it rises over the canvas on
   rounded top corners, so the brand gradient shows through the two corner
   cuts exactly the way the dark canvas shows through the app's console. */
.strip {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  margin-top: -24px;
  background: var(--surface);
  border-radius: 24px 24px 0 0;
  border-top: 1px solid var(--hairline);
}

.strip-inner {
  padding-block: var(--space-5) 20px;
}

.strip-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
}

.coach {
  margin: 0;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--on-chrome-dim);
}

.readout {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--on-chrome-dim);
}

.timeline {
  position: relative;
  height: 88px;
  margin: 34px 0 var(--space-3);
}

.wave {
  width: 100%;
  height: 100%;
}

.bar {
  fill: var(--wave);
  transition: height 160ms ease, y 160ms ease, fill 160ms ease;
}

.bar.muted {
  fill: var(--wave-muted);
}

.range {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--start);
  right: calc(100% - var(--end));
  background: var(--range-fill);
  border-inline: 2px solid var(--brand-teal);
  box-shadow: inset 0 2px 0 rgba(25, 225, 200, 0.6),
    inset 0 -2px 0 rgba(25, 225, 200, 0.6);
}

.handle {
  position: absolute;
  top: 50%;
  display: block;
  width: 6px;
  height: 104px;
  border-radius: 3px;
  background: var(--brand-teal);
  transform: translate(-50%, -50%);
}

/* Hit area, 44px wide, never smaller than the pill is tall. */
.handle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 104px;
  transform: translate(-50%, -50%);
}

.handle-start {
  left: -1px;
}

.handle-end {
  left: calc(100% + 1px);
}

.js .handle {
  cursor: ew-resize;
  touch-action: none;
  transition: box-shadow 160ms ease, width 160ms ease;
}

.js .handle:hover,
.js .handle:focus-visible,
.js .handle:active {
  width: 8px;
  box-shadow: 0 0 0 1px var(--brand-teal), 0 0 14px 2px rgb(25 225 200 / 0.45);
}

/* The strip is the one thing on this page a visitor can touch, and nothing else
   on a landing page usually is, so it says so once: the handles breathe twice
   after the entrance settles, then stop and stay still. `.reacted` is set the
   first time someone actually moves one, which cancels the invitation early. */
.motion .js .handle {
  animation: handle-invite 1400ms ease-in-out 1500ms 2 backwards;
}

.motion .js .strip.reacted .handle {
  animation: none;
}

@keyframes handle-invite {
  0%,
  100% {
    box-shadow: 0 0 0 0 rgb(25 225 200 / 0);
  }
  45% {
    box-shadow: 0 0 0 1px var(--brand-teal), 0 0 16px 3px rgb(25 225 200 / 0.5);
  }
}

.js .range {
  cursor: grab;
}

.playhead {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  display: none;
  width: 2px;
  background: var(--white);
  pointer-events: none;
}

.motion .playhead {
  display: block;
}

.playhead-chip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  padding: 2px 6px;
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--brand-teal);
  transform: translateX(-50%);
}

.ruler {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--on-chrome-dim);
}

.strip-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-5);
  margin-top: var(--space-5);
}

.strip-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
}

.strip-links a,
.business {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--on-chrome-dim);
}

.strip-links a {
  text-decoration: none;
}

.strip-links a:hover,
.strip-links a:focus-visible {
  color: var(--on-chrome);
  text-decoration: underline;
}

/* --- focus -------------------------------------------------------- */

.canvas a:focus-visible {
  outline: 2px solid var(--white);
  outline-offset: 2px;
}

.strip a:focus-visible,
.handle:focus-visible {
  outline: 2px solid var(--brand-teal);
  outline-offset: 2px;
}

/* Hover on the gradient underlines rather than dims: every piece of text
   on the canvas stays 100 percent white. */
.canvas a:hover {
  text-decoration: underline;
}

.badge {
  transition: transform 180ms ease, filter 180ms ease;
}

.badge:hover {
  text-decoration: none;
  transform: translateY(-2px);
  filter: drop-shadow(0 8px 18px rgb(0 0 0 / 0.35));
}

.badge:active {
  transform: translateY(0);
  filter: none;
}

/* Entrance. It runs once, it is short, and the page is already laid out
   underneath it: nothing here moves layout, only opacity and a few pixels of
   travel, so a slow connection or a stalled stylesheet never leaves a visitor
   staring at an empty screen. */
.motion .hero > *,
.motion .robot,
.motion .strip-inner > * {
  animation: enter 520ms cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

.motion .hero > *:nth-child(1) { animation-delay: 60ms; }
.motion .hero > *:nth-child(2) { animation-delay: 140ms; }
.motion .hero > *:nth-child(3) { animation-delay: 220ms; }
.motion .hero > *:nth-child(4) { animation-delay: 300ms; }
.motion .robot { animation-delay: 260ms; animation-duration: 700ms; }
.motion .strip-inner > *:nth-child(1) { animation-delay: 340ms; }
.motion .strip-inner > *:nth-child(2) { animation-delay: 400ms; }
.motion .strip-inner > *:nth-child(3) { animation-delay: 460ms; }

@keyframes enter {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* --- narrow ------------------------------------------------------- */

@media (max-width: 899px) {
  :root {
    --pad: 20px;
    --robot-w: 290px;
    --robot-drop: 34px;
  }

  /* One column now, so the robot no longer sits beside the text, it sits
     under it. Reserve its visible height below the text block: robot and
     copy are both white, and an overlap would be a collision, not a tuck. */
  .hero-slot::before {
    flex: 2 0 var(--space-5);
  }

  .hero-slot::after {
    flex: 3 0 var(--robot-visible);
  }

  .masthead {
    padding: 20px 0;
  }

  .strip-inner {
    padding-block: 20px var(--space-4);
  }

  .badge img {
    height: 48px;
  }

  .timeline {
    margin-top: 30px;
  }

  .lede,
  .price {
    max-width: 30ch;
  }
}

@media (max-width: 899px) and (min-width: 560px) {
  .lede,
  .price {
    max-width: 46ch;
  }
}

@media (max-height: 699px) {
  :root {
    --robot-w: 264px;
    --robot-drop: 30px;
  }
}

/* --- reduced motion ----------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .motion .playhead {
    display: none;
  }

  .bar {
    transition: none;
  }

  .motion .bubbles circle,
  .motion .eyes-open,
  .motion .eyes-shut,
  .motion .hero > *,
  .motion .robot,
  .motion .strip-inner > *,
  .motion .ranged,
  .motion .ranged::before,
  .motion .ranged::after,
  .motion .sweep::before,
  .motion .js .handle {
    animation: none;
  }

  .js .handle {
    transition: none;
  }

  .badge {
    transition: none;
  }
}

/* ------------------------------------------------------------------ *
 * privacy.html: the document page
 * ------------------------------------------------------------------ */

.doc-page .wrap {
  max-width: 744px;
}

.doc-page {
  --doc-bg: #f5f5f8;
  --doc-text: #14151a;
  --doc-text-dim: #4c505c;
  --doc-link: #0a6e63;
  --doc-rule: rgba(0, 0, 0, 0.12);
  color: var(--doc-text);
  background: var(--doc-bg);
  font-size: 16px;
  line-height: 1.6;
}

@media (prefers-color-scheme: dark) {
  .doc-page {
    --doc-bg: #0b0c10;
    --doc-text: #f2f3f7;
    --doc-text-dim: #9ba0ae;
    --doc-link: #19e1c8;
    --doc-rule: rgba(255, 255, 255, 0.12);
  }
}

/* One brand cap, so the two pages read as one site. */
.brand-cap {
  height: 6px;
  background: linear-gradient(135deg, var(--brand-deep), var(--brand-teal));
}

.doc-page .masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-5);
  border-bottom: 1px solid var(--doc-rule);
}

.doc-page .wordmark {
  color: var(--doc-text);
  text-decoration: none;
}

.doc-nav {
  display: flex;
  gap: var(--space-5);
}

.doc-nav a {
  font-size: 15px;
  color: var(--doc-link);
}

.doc {
  max-width: 68ch;
  padding-block: var(--space-7) var(--space-8);
}

.doc h1 {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.doc h2 {
  margin: var(--space-7) 0 var(--space-2);
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.25;
}

.doc p {
  margin: 0 0 var(--space-4);
}

.doc .updated {
  margin-bottom: var(--space-6);
  font-size: 14px;
  color: var(--doc-text-dim);
}

.doc a,
.doc-page a {
  color: var(--doc-link);
}

.site-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  justify-content: space-between;
  padding-block: var(--space-5) var(--space-7);
  border-top: 1px solid var(--doc-rule);
  font-size: 13px;
  color: var(--doc-text-dim);
}

.doc-page a:focus-visible {
  outline: 2px solid var(--doc-link);
  outline-offset: 2px;
}
