/* ipaside.com — no framework, no tracker, no third-party request.
   Every colour below is lifted from the app's own theme
   (src/iPASide.Flutter/lib/ui/theme/palette.dart), so the site and the product
   cannot drift into looking like two different things. */

/* ── Inter, self-hosted and subset to Latin ─────────────────────────────── */
@font-face {
  font-family: "Inter";
  src: url("/assets/fonts/Inter-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/assets/fonts/Inter-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/assets/fonts/Inter-SemiBold.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/assets/fonts/Inter-Bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

/* ── Tokens ─────────────────────────────────────────────────────────────── */
:root {
  --bg0: #0b0b0d;
  --bg1: #121215;
  --bg2: #1a1a1f;
  --hairline: #26262c;
  --hairline-strong: #34343c;
  --card-border: #2c2c37;
  --card-border-hover: #3a3a46;

  --text: #ededef;
  --text-2: #9c9ca6;
  --text-3: #6b6b76;

  --accent: #5e6ad2;
  --accent-hover: #6d78db;
  --accent-from: #6672e6;
  --accent-to: #8c5cf0;
  --accent-subtle: rgba(94, 106, 210, .15);
  --focus: #7d88e0;

  --success: #4cc38a;
  --success-bg: rgba(76, 195, 138, .15);
  --warning: #f0b429;

  --card-from: #1b1b23;
  --card-to: #141419;
  --card-hover-from: #212129;
  --card-hover-to: #18181e;

  --wrap: 1120px;
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-xl: 22px;

  --shadow: 0 4px 16px -4px rgba(0, 0, 0, .35), 0 12px 32px -12px rgba(0, 0, 0, .45);
  --glow: 0 8px 28px -10px rgba(108, 114, 230, .55);

  --ease: cubic-bezier(.22, 1, .36, 1);
}

/* ── Base ───────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: 84px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg0);
  color: var(--text);
  font: 400 16px/1.65 "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--accent-hover); text-decoration: none; }
a:hover { color: #8b95ec; text-decoration: underline; text-underline-offset: 3px; }

code, kbd, pre {
  font-family: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
}
code {
  font-size: .88em;
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--hairline);
  border-radius: 5px;
  padding: .1em .38em;
  color: #c9c9d4;
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 12px;
  z-index: 100;
  background: var(--bg2);
  border: 1px solid var(--card-border);
  border-radius: var(--r-sm);
  padding: 10px 16px;
  color: var(--text);
}
.skip:focus { left: 16px; }

.wrap { width: min(var(--wrap), calc(100% - 44px)); margin-inline: auto; }
.wrap.narrow { width: min(760px, calc(100% - 44px)); }
.wrap.center { text-align: center; }

/* ── Atmosphere ─────────────────────────────────────────────────────────── */
/* The glows are deliberately bigger than the screen and hang off both edges, so
   they are clipped here rather than by overflow-x on the body. Hiding overflow on
   the body makes it a scroll container, which is the classic way to break the
   position:sticky header above without noticing. */
.sky {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  background:
    radial-gradient(1200px 600px at 72% -8%, rgba(38, 39, 80, .55), transparent 62%),
    linear-gradient(180deg, #0e0e13 0%, #08080a 100%);
}

.glow {
  position: absolute;
  border-radius: 50%;
  opacity: .5;
  will-change: transform;
}
.glow-a {
  width: 620px; height: 620px;
  top: -220px; right: -140px;
  background: radial-gradient(circle, rgba(102, 114, 230, .30), transparent 68%);
  animation: breathe 17s var(--ease) infinite;
}
.glow-b {
  width: 520px; height: 520px;
  top: 780px; left: -200px;
  background: radial-gradient(circle, rgba(140, 92, 240, .22), transparent 68%);
  animation: breathe 23s var(--ease) infinite reverse;
}
@keyframes breathe {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(0, 42px, 0) scale(1.11); }
}

.grid-lines {
  position: absolute;
  inset: 0;
  opacity: .32;
  background-image:
    linear-gradient(rgba(255, 255, 255, .022) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .022) 1px, transparent 1px);
  background-size: 56px 56px;
  mask-image: radial-gradient(1000px 700px at 50% 0%, #000 20%, transparent 78%);
}

/* ── Nav ────────────────────────────────────────────────────────────────── */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(11, 11, 13, .72);
  backdrop-filter: saturate(150%) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color .25s var(--ease), background-color .25s var(--ease);
}
/* At the very top the bar should not look like a bar; the moment content passes
   under it, it earns its edge. */
.nav.stuck {
  border-bottom-color: var(--hairline);
  background: rgba(11, 11, 13, .86);
}
.nav-in {
  display: flex;
  align-items: center;
  gap: 26px;
  height: 62px;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--text);
  font-weight: 600;
  font-size: 16.5px;
  letter-spacing: -.01em;
}
.brand:hover { text-decoration: none; color: var(--text); }
.brand img { border-radius: 6px; }

.links { display: flex; gap: 22px; margin-left: 8px; }
.links a {
  position: relative;
  color: var(--text-2);
  font-size: 14.5px;
  font-weight: 500;
  padding: 3px 0;
  transition: color .16s var(--ease);
}
.links a:hover { color: var(--text); text-decoration: none; }

/* Which section you are actually in. The underline is drawn with a transform so it
   wipes in from the left rather than appearing, and costs no layout. */
.links a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 1.5px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent-from), var(--accent-to));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .26s var(--ease);
}
.links a.on { color: var(--text); }
.links a.on::after { transform: scaleX(1); }

.nav-actions { display: flex; align-items: center; gap: 10px; margin-left: auto; }

/* ── Buttons ────────────────────────────────────────────────────────────── */
.btn {
  --btn-bg: transparent;
  display: inline-flex;
  border-radius: 10px;
  border: 1px solid var(--card-border);
  background: var(--btn-bg);
  color: var(--text);
  font-weight: 600;
  font-size: 14.5px;
  line-height: 1;
  transition: transform .16s var(--ease), border-color .16s var(--ease),
              background-color .16s var(--ease), box-shadow .16s var(--ease);
}
.btn:hover { text-decoration: none; color: var(--text); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn-in { display: inline-flex; align-items: center; gap: 8px; padding: 10px 16px; }

.btn.small .btn-in { padding: 8px 13px; font-size: 13.8px; }
.btn.big .btn-in { padding: 14px 22px; font-size: 15.5px; }

.btn.ghost { background: rgba(255, 255, 255, .03); }
.btn.ghost:hover { border-color: var(--card-border-hover); background: rgba(255, 255, 255, .06); }

.btn.primary {
  border-color: transparent;
  background: linear-gradient(135deg, var(--accent-from), var(--accent-to));
  color: #fff;
  box-shadow: var(--glow);
}
.btn.primary:hover {
  background: linear-gradient(135deg, #7883ef, #9c6dfb);
  box-shadow: 0 10px 34px -10px rgba(120, 131, 239, .7);
}

.gh-stars {
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
  font-weight: 500;
  padding-left: 8px;
  margin-left: 2px;
  border-left: 1px solid var(--hairline);
}

.vchip {
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 3px 7px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .17);
  color: rgba(255, 255, 255, .92);
}
.vchip:empty { display: none; }

/* ── Type ───────────────────────────────────────────────────────────────── */
h1 {
  margin: 14px 0 0;
  font-size: clamp(34px, 5.1vw, 58px);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -.032em;
}
h2 {
  margin: 0 0 14px;
  font-size: clamp(27px, 3.5vw, 40px);
  font-weight: 700;
  line-height: 1.14;
  letter-spacing: -.026em;
}
h3 {
  margin: 0 0 8px;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -.012em;
}

.grad {
  background: linear-gradient(100deg, var(--accent-from), var(--accent-to));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.kicker {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  color: var(--text-2);
  font-size: 13.2px;
  font-weight: 500;
  letter-spacing: .01em;
}
.kick-dash {
  width: 26px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent));
}

.lede {
  margin: 18px 0 0;
  max-width: 56ch;
  color: var(--text-2);
  font-size: 17px;
  line-height: 1.68;
}
.lede code { font-size: .84em; }

.section-sub {
  margin: 0 0 34px;
  max-width: 62ch;
  color: var(--text-2);
  font-size: 16.5px;
}
.center .section-sub,
.center .lede { margin-inline: auto; }
.center .kicker { justify-content: center; }

.fineprint {
  margin: 16px 0 0;
  color: var(--text-3);
  font-size: 13.4px;
}
.small-note {
  margin-top: 26px;
  color: var(--text-3);
  font-size: 14px;
  font-style: italic;
}

/* ── Layout ─────────────────────────────────────────────────────────────── */
.section { padding: clamp(64px, 9vw, 116px) 0; }

/* Headings are held to a readable measure, except in a centred block — where a
   max-width with no auto margin parks the text on the left while everything around
   it is centred, which is exactly the wrong half of centring. The `.center` rule has
   to out-specify the one above it, not merely follow it. */
.section > .wrap > h2 { max-width: 20ch; }
.section > .wrap.center > h2 { max-width: none; }

/* ── Hero ───────────────────────────────────────────────────────────────── */
.hero { padding: clamp(48px, 7vw, 96px) 0 0; }
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, .98fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: center;
}
.cta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }
.center .cta { justify-content: center; }

/* ── Hero demo ──────────────────────────────────────────────────────────── */
.demo {
  border: 1px solid var(--card-border);
  border-radius: var(--r-xl);
  background: linear-gradient(180deg, var(--card-from), var(--card-to));
  box-shadow: var(--shadow);
  overflow: hidden;
}
.demo-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 11px 15px;
  border-bottom: 1px solid var(--hairline);
  background: rgba(255, 255, 255, .022);
}
.demo-dot {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: #2f2f38;
}
.demo-title {
  margin-left: 8px;
  color: var(--text-2);
  font-size: 12.6px;
  font-weight: 600;
}
.demo-live {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--text-3);
  font-size: 11.8px;
  font-weight: 500;
}
.pulse {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--success);
  box-shadow: 0 0 0 0 rgba(76, 195, 138, .6);
  animation: pulse 2.1s ease-out infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(76, 195, 138, .55); }
  70%  { box-shadow: 0 0 0 7px rgba(76, 195, 138, 0); }
  100% { box-shadow: 0 0 0 0 rgba(76, 195, 138, 0); }
}

.demo-body { padding: 18px; }

.dropzone {
  position: relative;
  border: 1.5px dashed var(--hairline-strong);
  border-radius: var(--r-lg);
  min-height: 92px;
  display: grid;
  place-items: center;
  padding: 16px;
  transition: border-color .3s var(--ease), background-color .3s var(--ease);
}
.dropzone.filled {
  border-style: solid;
  border-color: var(--accent);
  background: var(--accent-subtle);
}
.dz-idle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  color: var(--text-3);
  font-size: 13.6px;
  transition: opacity .25s var(--ease), transform .25s var(--ease);
}
.dropzone.filled .dz-idle { opacity: 0; transform: scale(.94); position: absolute; }

.dz-file {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  opacity: 0;
  transform: translateY(9px);
  transition: opacity .32s var(--ease), transform .32s var(--ease);
}
.dropzone.filled .dz-file { opacity: 1; transform: none; }

.dz-icon {
  width: 42px; height: 42px;
  flex: none;
  border-radius: 10px;
  background:
    radial-gradient(circle at 32% 28%, #ff5a8a, transparent 58%),
    radial-gradient(circle at 72% 76%, #7b3ff2, transparent 62%),
    linear-gradient(140deg, #f9a03f, #d6249f 58%, #4f5bd5);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .13);
}
.dz-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.dz-meta strong { font-size: 14.6px; font-weight: 600; }
.dz-meta span {
  color: var(--text-3);
  font-size: 12.4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dz-ok {
  margin-left: auto;
  flex: none;
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--success-bg);
  color: var(--success);
}

.steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}
.step {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 11px;
  border: 1px solid var(--hairline);
  border-radius: 10px;
  background: rgba(255, 255, 255, .02);
  color: var(--text-3);
  font-size: 12.8px;
  font-weight: 500;
  transition: color .25s var(--ease), border-color .25s var(--ease),
              background-color .25s var(--ease);
}
.s-dot {
  width: 8px; height: 8px;
  flex: none;
  border-radius: 50%;
  background: #33333c;
  transition: background-color .25s var(--ease), box-shadow .25s var(--ease);
}
.step.on {
  color: var(--text);
  border-color: rgba(94, 106, 210, .5);
  background: var(--accent-subtle);
}
.step.on .s-dot {
  background: var(--accent-hover);
  box-shadow: 0 0 0 4px rgba(94, 106, 210, .18);
}
.step.ok { color: var(--text-2); border-color: rgba(76, 195, 138, .32); }
.step.ok .s-dot { background: var(--success); box-shadow: none; }

.prog { margin-top: 16px; }
.prog-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}
.prog-step {
  color: var(--text-2);
  font-size: 12.8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.prog-pct {
  color: var(--text);
  font-size: 12.8px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.bar {
  height: 6px;
  border-radius: 999px;
  background: #202027;
  overflow: hidden;
}
.bar > i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--accent-from), var(--accent-to));
  /* Shorter than the interval between updates (230ms during the upload), so each
     step actually arrives before the next one restarts it. A longer transition
     never reaches its target and the bar reads as permanently behind. Linear,
     because easing a progress bar makes steady work look like it is stalling. */
  transition: width .18s linear;
}

.demo-done {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: 14px;
  padding: 10px 12px;
  border: 1px solid rgba(76, 195, 138, .3);
  border-radius: 10px;
  background: var(--success-bg);
  color: #b7e8ce;
  font-size: 12.9px;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.demo-done.show { opacity: 1; transform: none; }
.demo-done svg { flex: none; color: var(--success); }
.demo-done em { color: #8fd9b4; font-style: normal; }

/* ── Tape ───────────────────────────────────────────────────────────────── */
.tape {
  margin-top: clamp(46px, 6vw, 78px);
  padding: 15px 0;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.tape-track {
  display: flex;
  gap: 12px;
  width: max-content;
  animation: slide 46s linear infinite;
}
@keyframes slide { to { transform: translate3d(-50%, 0, 0); } }
.chip {
  white-space: nowrap;
  padding: 7px 14px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: rgba(255, 255, 255, .022);
  color: var(--text-2);
  font-size: 12.9px;
  font-weight: 500;
}

/* ── Screenshots ────────────────────────────────────────────────────────── */
.shot-sec { padding-top: clamp(52px, 7vw, 92px); }
.shot-frame {
  position: relative;
  border: 1px solid var(--card-border);
  border-radius: var(--r-xl);
  overflow: hidden;
  background: var(--bg1);
  box-shadow: 0 24px 70px -28px rgba(0, 0, 0, .8);
}
.shot-edge {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
}
.shot-cap {
  margin: 18px auto 0;
  max-width: 60ch;
  text-align: center;
  color: var(--text-3);
  font-size: 14.2px;
}

.shots-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  margin-top: 46px;
}
.shots-row img {
  border: 1px solid var(--card-border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
}

/* ── Cards ──────────────────────────────────────────────────────────────── */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
  gap: 18px;
}
.card {
  position: relative;
  padding: 24px 22px 22px;
  border: 1px solid var(--card-border);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--card-from), var(--card-to));
  transition: border-color .2s var(--ease), transform .2s var(--ease),
              box-shadow .2s var(--ease);
}
.card:hover {
  border-color: var(--card-border-hover);
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}
.card p { margin: 0; color: var(--text-2); font-size: 14.8px; line-height: 1.68; }
.card h3 { margin-top: 14px; }
.card em { color: #b9b9c4; font-style: italic; }
.card strong { color: var(--text); font-weight: 600; }

.card-ico {
  display: block;
  width: 34px; height: 34px;
  border-radius: 9px;
  border: 1px solid rgba(94, 106, 210, .3);
  background: var(--accent-subtle) no-repeat center / 18px 18px;
}
/* Inline SVG as data URIs: one file fewer to fetch, and they inherit nothing so
   they cannot be recoloured by a stray rule. */
.i-sign    { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b95ec' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3l7 3v5c0 4.4-2.9 8.3-7 9.5C7.9 19.3 5 15.4 5 11V6l7-3z'/%3E%3Cpath d='M9.2 11.8l2 2 3.6-3.8'/%3E%3C/svg%3E"); }
.i-local   { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b95ec' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='12' rx='2'/%3E%3Cpath d='M8 20h8M12 16v4'/%3E%3C/svg%3E"); }
.i-tweak   { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b95ec' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 3l-6.5 6.5'/%3E%3Cpath d='M14.5 3H21v6.5'/%3E%3Cpath d='M10 6H5a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-5'/%3E%3C/svg%3E"); }
.i-clock   { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b95ec' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7v5.2l3.4 2'/%3E%3C/svg%3E"); }
.i-device  { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b95ec' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='2.5' width='9' height='15' rx='2'/%3E%3Crect x='13' y='9' width='7' height='12.5' rx='1.8'/%3E%3C/svg%3E"); }
.i-progress{ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b95ec' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 15.5h4L10 6l4 12 2.6-6.5H21'/%3E%3C/svg%3E"); }

/* ── Timeline ───────────────────────────────────────────────────────────── */
.tl { position: relative; margin-top: 34px; padding-left: 26px; }
.tl::before {
  content: "";
  position: absolute;
  left: 5px; top: 8px; bottom: 8px;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--accent), rgba(140, 92, 240, .18));
}
.tl-item { position: relative; padding-bottom: 20px; }
.tl-item:last-child { padding-bottom: 0; }
.tl-dot {
  position: absolute;
  left: -26px; top: 20px;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--bg2);
  border: 2px solid var(--accent);
}
.tl-dot.on {
  background: var(--accent);
  box-shadow: 0 0 0 5px rgba(94, 106, 210, .16);
}
.tl-card {
  padding: 18px 20px;
  border: 1px solid var(--card-border);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--card-from), var(--card-to));
}
.tl-card.accent { border-color: rgba(94, 106, 210, .38); }
.tl-card p { margin: 0; color: var(--text-2); font-size: 14.8px; }
.tl-card strong { color: var(--text); }

/* ── Tweaks + terminal ──────────────────────────────────────────────────── */
.tweaks-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(30px, 5vw, 58px);
  align-items: center;
}
.ticks { margin: 0; padding: 0; list-style: none; display: grid; gap: 13px; }
.ticks li {
  position: relative;
  padding-left: 28px;
  color: var(--text-2);
  font-size: 14.8px;
  line-height: 1.62;
}
.ticks li::before {
  content: "";
  position: absolute;
  left: 0; top: .44em;
  width: 15px; height: 15px;
  border-radius: 50%;
  background: var(--success-bg) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234cc38a' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5l5 5L20 6.5'/%3E%3C/svg%3E") no-repeat center / 9px 9px;
}

.term {
  border: 1px solid var(--card-border);
  border-radius: var(--r-lg);
  background: #0a0a0e;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.term-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--hairline);
  background: rgba(255, 255, 255, .022);
}
.term-title { margin-left: 8px; color: var(--text-3); font-size: 12.2px; }
.term-body {
  margin: 0;
  padding: 16px;
  /* Sized to hold the WHOLE typed session, not the first line of it. The text is
     printed a character at a time, so a floor shorter than the finished output makes
     the box grow as it types and snap back when the loop restarts — shoving every
     section below it up and down the page forever. Measured, not guessed: the full
     session needs 257px here and considerably more once lines start wrapping, hence
     the taller floors below. Each floor carries ~20px of headroom on top of the
     measurement, because those measurements come from Chrome on Windows and iOS
     Safari's monospace is not the same width — a floor that is merely exact here
     could be a pixel short there, which brings the whole bug straight back. */
  min-height: 276px;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
  font-size: 12.6px;
  line-height: 1.78;
  color: #a9b4c8;
}
.term-caret {
  display: inline-block;
  width: 7px;
  height: 14px;
  vertical-align: -2px;
  background: var(--accent-hover);
  animation: blink 1.1s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* ── Limits ─────────────────────────────────────────────────────────────── */
/* Two by two rather than auto-fit: four cards in a 760px column pack as 3 + 1,
   which reads as a mistake rather than as a list. */
.limits {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin-top: 32px;
}
@media (max-width: 620px) {
  .limits { grid-template-columns: minmax(0, 1fr); }
}
.limit {
  padding: 20px;
  border: 1px solid var(--card-border);
  border-left: 2px solid var(--warning);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--card-from), var(--card-to));
}
.limit-n {
  display: block;
  margin-bottom: 8px;
  color: var(--warning);
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -.02em;
}
.limit p { margin: 0; color: var(--text-2); font-size: 14.4px; line-height: 1.62; }

/* ── Verify ─────────────────────────────────────────────────────────────── */
.hash {
  margin: 6px 0 22px;
  border: 1px solid var(--card-border);
  border-radius: var(--r-md);
  background: #0a0a0e;
  overflow: hidden;
}
.hash-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 14px;
  border-bottom: 1px solid var(--hairline);
  background: rgba(255, 255, 255, .022);
}
.hash-file {
  color: var(--text-2);
  font-size: 12.6px;
  font-weight: 600;
  font-family: ui-monospace, Consolas, monospace;
}
.hash-copy {
  margin-left: auto;
  padding: 5px 11px;
  border: 1px solid var(--card-border);
  border-radius: 7px;
  background: rgba(255, 255, 255, .04);
  color: var(--text-2);
  font: 600 12px/1 "Inter", sans-serif;
  cursor: pointer;
  transition: color .16s var(--ease), border-color .16s var(--ease);
}
.hash-copy:hover { color: var(--text); border-color: var(--card-border-hover); }
.hash-copy.done { color: var(--success); border-color: rgba(76, 195, 138, .4); }
.hash-val {
  display: block;
  padding: 14px;
  border: 0;
  background: none;
  color: #8fd9b4;
  font-size: 12.6px;
  line-height: 1.7;
  word-break: break-all;
}

pre {
  margin: 0 0 22px;
  padding: 15px 16px;
  border: 1px solid var(--card-border);
  border-radius: var(--r-md);
  background: #0a0a0e;
  overflow-x: auto;
}
pre code {
  border: 0;
  padding: 0;
  background: none;
  color: #c9c9d4;
  font-size: 12.9px;
}

.note {
  margin: 0 0 18px;
  padding: 15px 17px;
  border: 1px solid var(--card-border);
  border-left: 2px solid var(--accent);
  border-radius: var(--r-md);
  background: rgba(94, 106, 210, .07);
  color: var(--text-2);
  font-size: 14.6px;
  line-height: 1.66;
}
.note strong { color: var(--text); }
.note em { color: #b9b9c4; font-style: normal; font-weight: 600; }

/* ── Specs ──────────────────────────────────────────────────────────────── */
/* Gridlines drawn per cell rather than by letting a background show through 1px
   gaps: with the gap trick, a row that does not fill leaves the container's colour
   sitting there as a grey rectangle, which reads as a rendering bug. Borders on the
   cells themselves simply stop where the cells do. */
.specs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  margin-top: 30px;
  border-top: 1px solid var(--hairline);
  border-left: 1px solid var(--hairline);
  border-radius: var(--r-md);
  overflow: hidden;
}
.spec {
  padding: 17px 19px;
  background: var(--bg0);
  border-right: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.spec dt {
  margin-bottom: 5px;
  color: var(--text);
  font-size: 12.4px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.spec dd { margin: 0; color: var(--text-2); font-size: 14.2px; line-height: 1.6; }

/* ── FAQ ────────────────────────────────────────────────────────────────── */
details {
  border-bottom: 1px solid var(--hairline);
  padding: 4px 0;
  /* Lets `height: auto` be animated to, which is what makes the open/close glide
     instead of jump. Browsers without it simply toggle instantly. */
  interpolate-size: allow-keywords;
}
details summary {
  cursor: pointer;
  list-style: none;
  padding: 16px 34px 16px 0;
  position: relative;
  color: var(--text);
  font-size: 15.8px;
  font-weight: 600;
  letter-spacing: -.008em;
  transition: color .16s var(--ease);
}
details summary::-webkit-details-marker { display: none; }
details summary:hover { color: var(--accent-hover); }
details summary::after {
  content: "";
  position: absolute;
  right: 4px; top: 50%;
  width: 9px; height: 9px;
  border-right: 2px solid var(--text-3);
  border-bottom: 2px solid var(--text-3);
  transform: translateY(-70%) rotate(45deg);
  transition: transform .28s var(--ease), border-color .16s var(--ease);
}
details summary:hover::after { border-color: var(--accent-hover); }
details[open] summary::after { transform: translateY(-30%) rotate(-135deg); }

/* The answer's own box animates open; the text inside fades up a beat later so the
   panel is already the right size by the time there is anything to read in it. */
details::details-content {
  block-size: 0;
  overflow-y: clip;
  transition: block-size .32s var(--ease), content-visibility .32s allow-discrete;
}
details[open]::details-content { block-size: auto; }

details p {
  margin: 0 0 18px;
  padding-right: 34px;
  color: var(--text-2);
  font-size: 15px;
  line-height: 1.7;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity .26s var(--ease) .07s, transform .26s var(--ease) .07s;
}
details[open] p { opacity: 1; transform: none; }

/* ── Final CTA ──────────────────────────────────────────────────────────── */
.cta-final {
  border-top: 1px solid var(--hairline);
  background:
    radial-gradient(720px 300px at 50% 0%, rgba(94, 106, 210, .12), transparent 68%);
}
.cta-final h2 { margin-top: 16px; }

/* ── Footer ─────────────────────────────────────────────────────────────── */
.footer {
  border-top: 1px solid var(--hairline);
  padding: 34px 0 40px;
  background: rgba(0, 0, 0, .28);
}
.foot-in {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 26px;
}
.foot-brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--text-3);
  font-size: 13.4px;
}
.foot-brand img { border-radius: 5px; opacity: .85; }
.foot-links { display: flex; flex-wrap: wrap; gap: 20px; margin-left: auto; }
.foot-links a { color: var(--text-2); font-size: 13.6px; }
.foot-legal {
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px solid var(--hairline);
}
.foot-legal p {
  margin: 0;
  max-width: 92ch;
  color: var(--text-3);
  font-size: 12.4px;
  line-height: 1.66;
}
.foot-legal code { font-size: .92em; }

/* ── Reveals ────────────────────────────────────────────────────────────── */
.js .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .58s var(--ease), transform .58s var(--ease);
}
.js .reveal.in { opacity: 1; transform: none; }
.js .reveal:nth-child(2) { transition-delay: .05s; }
.js .reveal:nth-child(3) { transition-delay: .1s; }
.js .reveal:nth-child(4) { transition-delay: .15s; }
.js .reveal:nth-child(5) { transition-delay: .2s; }
.js .reveal:nth-child(6) { transition-delay: .25s; }

/* ── Responsive ─────────────────────────────────────────────────────────── */
@media (max-width: 980px) {
  .hero-grid, .tweaks-grid { grid-template-columns: minmax(0, 1fr); }
  .demo { max-width: 560px; }
  .links { display: none; }
}

/* ── Phones ──────────────────────────────────────────────────────────────────
   Below this width the page is one column, and one column of bordered boxes reads
   as clutter no matter how good each box is: six feature cards, four limit cards
   and eight spec cells stack into ~4,000px of near-identical rounded rectangles.
   So the rule here is that a box has to earn its border. The things that are really
   *lists* — the limits, the spec sheet — lose their boxes and become rows divided
   by hairlines, which is both far shorter and much easier to skim with a thumb. */
@media (max-width: 760px) {
  .wrap, .wrap.narrow { width: calc(100% - 32px); }
  .hero { padding-top: 36px; }
  .section { padding: 58px 0; }

  /* Icon beside the heading rather than above it: a 34px block on its own line cost
     about 45px per card, and six of those is a screenful of nothing. */
  .card { display: grid; grid-template-columns: auto 1fr; column-gap: 12px; }
  .card-ico { grid-row: 1; width: 30px; height: 30px; background-size: 16px 16px; }
  .card h3 { grid-column: 2; margin: 0; align-self: center; }
  /* Full width rather than indented under the heading: the hanging indent looked
     tidy but cost ~45px of measure on a 390px screen, which is a wrapped word on
     every line. */
  .card p { grid-column: 1 / -1; margin-top: 11px; }

  /* The limits are four facts, not four cards. */
  .limits {
    display: block;
    margin-top: 24px;
    border-top: 1px solid var(--hairline);
  }
  /* Label above the text, not beside it: the four labels differ wildly in width, so
     setting them inline gave every paragraph a different left edge and squeezed the
     longest one into a column four lines deep. Stacked, they read as one list — and
     match the spec rows further down the page. */
  .limit {
    padding: 14px 2px;
    border: 0;
    border-bottom: 1px solid var(--hairline);
    border-radius: 0;
    background: none;
  }
  .limit-n { margin-bottom: 4px; font-size: 15.5px; }
  .limit p { font-size: 14.2px; }

  /* Same for the spec sheet: a bordered 8-cell grid becomes a plain definition
     list. This section alone was 1,704px on a phone. */
  .specs {
    display: block;
    margin-top: 22px;
    border: 0;
    border-top: 1px solid var(--hairline);
    border-radius: 0;
  }
  .spec {
    padding: 13px 2px;
    border: 0;
    border-bottom: 1px solid var(--hairline);
    background: none;
  }
  .spec dt { margin-bottom: 3px; font-size: 11.4px; }
  .spec dd { font-size: 13.8px; line-height: 1.55; }

  /* Two app screenshots is showing the app; four is padding. The pair under
     "how it works" is the useful one, so this pair goes. */
  #internals .shots-row { display: none; }

  /* A command that scrolls sideways looks broken on a phone. Let it wrap. */
  pre { padding: 13px 14px; }
  pre code { white-space: pre-wrap; word-break: break-all; }

  /* Brand + GitHub + star count + Download does not fit a phone; the mark alone is
     recognisable enough, and the count is the part worth keeping. Dropped here rather
     than at 480 because this is the width where the row runs out of room. */
  .nav-in { gap: 12px; }
  .nav-actions { gap: 8px; flex-shrink: 0; }
  .gh-label { display: none; }
  .gh-stars { border-left: 0; padding-left: 0; }

  /* One button per row: a thumb is a blunter instrument than a cursor, and two
     side by side squeezed "Download for Windows" onto two lines. */
  .cta { gap: 10px; }
  .cta .btn { flex: 1 1 100%; }
  .cta .btn-in { justify-content: center; width: 100%; }

  .shots-row { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .steps { grid-template-columns: minmax(0, 1fr); }
  .foot-in { flex-direction: column; align-items: flex-start; gap: 18px; }
  .foot-links { margin-left: 0; gap: 14px 18px; }
  .foot-legal { margin-top: 16px; padding-top: 16px; }
  .tl { padding-left: 22px; }
  .demo-body { padding: 14px; }
  details summary { font-size: 15px; padding-right: 30px; }

  /* One claim per line beats a claim and a half. */
  .wide-only { display: none; }

  /* Narrower column, so the session wraps and needs a taller floor still. The
     smaller type buys back a wrapped line or two. Measured 280px at the bottom of
     this range (441px wide). */
  .term-body { font-size: 11.8px; padding: 14px; min-height: 300px; }
}

@media (max-width: 480px) {
  .wrap, .wrap.narrow { width: calc(100% - 24px); }
  h1 { font-size: 31px; letter-spacing: -.026em; }
  .lede { font-size: 16px; }
  .section { padding: 48px 0; }
  .section-sub { font-size: 15.5px; margin-bottom: 26px; }
  .card { padding: 20px 18px 18px; }
  .limits { gap: 12px; }
  /* Two words on one line beats one word per line. */
  .limit-n { font-size: 19px; }
  .tape { margin-top: 38px; }
  .chip { font-size: 12.2px; padding: 6px 12px; }
  .hash-val, pre code { font-size: 11.6px; }
  .foot-legal p { font-size: 11.8px; }
}

/* Actual phone widths, where nearly every line of the session wraps: measured 343px
   at 360px wide. Kept separate from the 480px block above so that phones between 441
   and 480 get the shorter 300px floor rather than 64px of empty terminal. */
@media (max-width: 440px) {
  .term-body { min-height: 364px; }
}

/* Touch: nothing is revealed by hover, and every target you are meant to hit is
   big enough for a fingertip. Links inside a sentence are left alone — WCAG exempts
   them, and padding them out would wreck the line spacing of the paragraph. */
@media (pointer: coarse) {
  .card:hover, .btn:hover { transform: none; }
  details summary { padding-top: 18px; padding-bottom: 18px; }
  .foot-links { gap: 4px 18px; }
  .foot-links a { padding: 10px 0; }
  .links a { padding: 10px 0; }
  .brand { padding: 6px 0; }
  .hash-copy { padding: 9px 14px; }
  /* Every button in the bar, by position rather than by a class name — an earlier
     version of this rule named .nav-cta, which does not exist in the markup, so the
     download button stayed at 32px while the rule looked like it was doing its job. */
  .nav-actions .btn .btn-in { padding-top: 11px; padding-bottom: 11px; }
}

/* ── Reduced motion ─────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .js .reveal { opacity: 1; transform: none; }
  .tape-track { animation: none; }
}
