/* ----------------------------------------------------------------------------
   void — design tokens (lifted from the design system)
   ---------------------------------------------------------------------------- */

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 100;
  font-display: swap;
  src: url("/static/fonts/JetBrainsMono-Thin.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("/static/fonts/JetBrainsMono-Light.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/JetBrainsMono-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/JetBrainsMono-Medium.woff2") format("woff2");
}

:root {
  --bg-0: #0a0a0b;
  --bg-1: #101012;
  --bg-2: #16161a;

  --fg: #e6e6e8;
  --fg-2: #8a8a90;
  --fg-3: #4d4d54;
  --fg-4: #2a2a2f;

  --signal: #c8c8cc;
  --signal-warn: #b87a5a;
  --screen: #5faa7a;

  --font: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --w-light: 300;
  --w-reg: 400;
  --w-med: 500;

  --tracking: 0.02em;
  --tracking-wide: 0.18em;

  --t-12: 12px;
  --t-14: 14px;
  --t-16: 16px;
  --t-18: 18px;
  --t-heading: clamp(28px, 4vw, 56px);
  --t-display: clamp(48px, 9vw, 124px);

  --ease: cubic-bezier(0.2, 0.65, 0.2, 1); /* default — smooth out */
  --ease-in-out: cubic-bezier(0.45, 0, 0.35, 1); /* dramatic, for emphasis */
  --ease-soft: cubic-bezier(0.3, 0, 0.3, 1); /* gentle for pulses */
  --t-fast: 160ms var(--ease);
  --t-med: 380ms var(--ease);
  --t-slow: 800ms var(--ease);

  --radius: 2px;
  --radius-pill: 999px;

  /* the "island" max width — header, footer, hero, sections all align to it */
  --island: 1180px;
  --gutter: 32px;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  background: var(--bg-0);
  color: var(--fg);
  font-family: var(--font);
  font-size: var(--t-14);
  font-weight: var(--w-reg);
  letter-spacing: var(--tracking);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Выделение текста — инверсия в стиле primary-кнопки (grayscale), вместо
   дефолтного браузерного синего. Один тон на весь сервис (= app base.css). */
::selection {
  background: var(--fg);
  color: var(--bg-0);
}

/* Клавиатурный фокус (tab-навигация) — как в app: НЕ браузерное кольцо
   (оно читается как «дефолт»), а повтор hover-состояния + outline:none.
   Реализовано не здесь, а прямо в hover-правилах интерактивных элементов
   ниже (`.x:hover, .x:focus-visible`) — один источник истины на состояние.
   Элементы без явного правила (прозаические ссылки) сохраняют дефолтный
   браузерный outline как видимый cue — так же, как в app. */

/* NB: don't set `scroll-behavior: smooth` here — it fights Lenis (each
   Lenis-driven scrollTo() triggers the browser's own smoothing, producing
   stutter and lag instead of the expected glide). Lenis owns scrolling.

   Скроллбар прячем НА ОБОИХ html и body: основной scrolling-host браузера —
   <html> (он же создаёт root scrollbar gutter), на body одного только
   ::-webkit-scrollbar { display:none } недостаточно — нативная полоса
   остаётся справа. Дублируем правила и на html. */
html {
  scroll-behavior: auto;
  scrollbar-width: none;       /* Firefox */
  -ms-overflow-style: none;    /* legacy Edge / IE */
}
html::-webkit-scrollbar {
  display: none;               /* Chrome / Safari / new Edge */
  width: 0;
  height: 0;
}
body {
  overflow-x: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
body::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
}

/* Когда фон рисует шейдер, виньетка считается внутри него — в вещественных
   числах и под общий дизер. Слоем CSS сверху она принесла бы свои собственные
   пять градаций на полэкрана, то есть свои террасы поверх гладкого кадра. */
html[data-bg-gl] body::after {
  display: none;
}

body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  /* Многостоповый спад вместо двух стопов: на одном стопе браузер
     переключается с «ничего» на линейное затемнение, и этот излом наклона
     виден кольцом по периметру. Стопы по квадратичной кривой — без границы. */
  background: radial-gradient(
    120% 80% at 50% 50%,
    transparent 40%,
    rgba(0, 0, 0, 0.03) 55%,
    rgba(0, 0, 0, 0.1) 68%,
    rgba(0, 0, 0, 0.21) 79%,
    rgba(0, 0, 0, 0.36) 90%,
    rgba(0, 0, 0, 0.55) 100%
  );
}

/* Плёнка шума поверх блобов и виньетки — ДИЗЕРИНГ, а не декор (та же плёнка,
   что в приложении, см. public/css/base.css — там же полный разбор). Коротко:
   градиент в 5-6 градаций яркости на полэкрана в 8 битах обязан рисоваться
   террасами, и лечит это только шум — размытие не помогает, после него всё
   снова округляется до 8 бит.

   PNG 64×64 с равномерным шумом: размах на выходе = 255 × opacity = 4 градации.
   pixelated — чтобы на retina тайл не растянулся с интерполяцией и не потерял
   половину амплитуды. z-index 2 — выше виньетки (body::after); контент секций
   тоже z-index 2, но идёт позже в DOM и остаётся над плёнкой. */
/* На шейдерном фоне плёнка остаётся только фактурой: дизеринг уже сделан
   до округления, дублировать его шумом сверху незачем. */
html[data-bg-gl] body::before {
  opacity: 0.008;
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  opacity: 0.016;
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAQS0lEQVR42gFAEL/vAOsKnpWjWMuqjTG+roDfusNkMvIAJdjDAyblASjF5bCJhdRrlDiRRzbM6CsK1E6H3MmChUbCdGuT6ZCWr+CAKWEAmJL4HabFk3lQncSFX/LNmsV5HHcsrPg58pQ1MfLkw4nEelfg6/jrC8VDGLkqRgtwg/D+jIBngM9SpLAvc3FKawDUasXenp2mEOYsCL7wqp9+WREqiCImqflYGfQeTEDbdm5SiLjEa4y/xlETK5fQvq71mNytdiUjZep3WT3IBmZ3AINFxI4t+bAvVlh42C44/uu3gaPX1rB0IlECzmIihp96Nls1+DqwBscyYa71cUr/Nzoj06Ah/WkTCLLkIWzJFCQA99g4FOGAvPnn+6ClRK1s5Wji5WFmfGJy3uUSrdvHOtjggpN/0WVh7cY+eDyM50NthtQpaQH80KOOqbqqI52anQAGYZy+UxTq6ZQWOUT/q28eAsa9z7W15sG2uK5HByB+dT6hNAVlFYsHsNQjKX1Zza9bl+OMxwcfMZRNTnsG6nbdAPgYeAaKxI/m6mryLWYIapZHGoyYU78Qn1K/XtUUMxDpgKz0C9AKAC5o7TAvJ+zz7RBnIljreTKIFEHaK3NWoyAAkMhLd0muU/O4hFFpT7UJunVsvdaoKyCG8MuCJLfzpaDd+2N+LWqS0ESzFhFa62Ty8XS4tsvKppEOcgiBfV+JswDZa8w6fW5Q85+LAVcgo7CRISxq11kQKMJL5PlmYjVcU9/OzEccdsytKwFCD0KaivakbZzcmRWoOfsRk3ciRFPrAOuRtnWZKQuZIKsMIFi3DYsOO0ofC1Y3wqrogVqt1Lz/EFACJn+GWT9SyE3oyz6AK1nkBjinFlHuFcYTQDm/uWsAtXPezCQMUKjBJsPVN/esazPTvGSaWzHwQDpxEqvg//zq5j2MOl6f9w2YXB5NW3Ohc/VhoS+qzKCC7rtSYHFX2QB4Xkp7ixJkIyxDW7+GAz2Cqdmlxqj5g/SzGHwX7Ir1o9/lqbjMt9/FdsyYICHQe0sDWfP7OU9I/pE3r0Cn0kR9ANfQlK9hDhCt1BapVAaWGTZJLq0R4nAHagj2La85P5uTbNAaOyVRTMSdCVbjk7qHkpWuqT0q77nyHYdQaWAZyXwArIaxw9+MZ1jfDNXlImPmpXCGdrUtgDHZoHMGlGr4MsDfXYz1F25l2YlmKV0U3g6tfK8vEIuv28NaD/PC/u3GywCR5ugPRAMcQuy2gzZ5Fh+kV/fboidoIhu7ybrsskZ9xmmz9SjoVuqNPaVllO3lJfJGpu89L4FF43FY2FGJJC7LAB/Z+Z4ZpEqpbKlsg33jMESMmeB3LyjmcFEPhTphlZeLQ8A9iOVctfZAVCIxCOO8MhbBI9bcnVqR7Pzq2xzh1PIApkcvpntsHjSX+e5SockuD9ByNqWjdlH1CR67ufdhR11KNWe9gsxJVnJzg3dRZ78rAUPzjy5DsFdEakXrFZo4OwAdIs/9Nq5BIP8t0BmTA/CTLRSyUEcCSNAlC+rbCUzQTCmXHGdGvwsI5ykZDFL5dFhsOnOCjvgCVzP3jM3Hpt1rAJImZbnfsQVm/snitR2ETkjDY2OeCnCqWdH9jF8WuYCguRwOVAOR6YlTpLdRYraAGKcW1FDtAwE8r4VlGrNVUP4AKV09LKaY3V8qfZewn3NwNZoul8D8pw86lDtZt7kTE2Yk9Sav4Bro3mw1oAuyllld4z8DhOFKdEpIReXZYDoPAQCXm+xXoYeBDYhzaEYPBK0CvGNNpgQKMBwxxVdC/Edk3XoDGRhPZp3T7DPfE3KF0V6lIbKSo+L7Kn7gG2lPM2Q3ANsd5KlnkuXzDMICam6quC0LHTNuL1i7nqpt2m8d/2+eDHl4nJ9zXCr3Hcf20uDudO23aOOC0D5iWsQh/jaHN00ATo4Eu8ZdowYeL87cOh+D4JVytByFYmoPqfEwZHGa2EMRQGgbi4ADaBwMBeghn6h5LYJYE1a7o3/5CpLktlMmPQDrapbX8a/ClmiT9EEwjogpUDbD16gQwTbnLzq6AhsBUnPCtvZksmbEOumk8sD3SGabOT8VPrYSkWclH9VM6Cr/ALCap8U5KNuAPx9e1S0N+96dZKfTH7/YxAXwJtXgcqGrlesgqA+sXg2PYOU0AKfMpYu38zp80C58duTZBv8JImsAYcD3Z66eIQLPTzK3N+AMKKpHV3G7eBpCnCGp7VPQn8cmo5uayWNbo9ytLi14yNDX5x6l7aB12KS83ZyU/2XLWACnGBdESrARbVImmVd7/vUuqn092ExyZbLlbn4iBLxxaULpTnlU9oWr6lI++uWX96vJfbMwEzNQ0U/cP1iuhQJGAPZaSMJ9FxuKNp7kVCSjx6w1QbW+JAtHSQHko/FF/VY0HOTSDDDa7UZhRMf+439Os8Wa3Nf37fBtPR8HFtQgxBsA4d4W7Gfc+mrPmFm0qKKOV/Yla9sUS9IgsSW1g4Ws9MP+wb2RUZ62/glx00wn9PfKBsVCHGkboDXyYDtqI9Zm/QA/xa/qSvEmobtVOH4WiHLmyO07P/jDHKGqZLa5QSaUMVh/OqqH30gQbv/uOEShbCKOoCKjWjKw6iUfgYR7PnONAC5DoMOYUYqrdQAtN8P+nrk1GxBjdkXZiYHkl+Pflf2MDm8Qp1MsSap5CJWXXG0c6Xp/8rjeoM0fQOr5S7VyV/8AzHH48JboscnnvVhuJpT6QzCDOKQMr3jPpi7movM1IubZRRTofzpmi4P5o0GFCrFq5F5TZvdA/sM747r832bD2AAuGSOSPZ7gpEYdI3CUW6Wd0WTW8Y2kV8Byk4md349gzrCOVUHyty1GXqUsqRQ7z4HeKOTbGoaLzF69g+4IaS1JAEalL/qysxuG/C2hvC1oKs/DfpuLoH9wx4djOnAmD7xgqva+3dbVq2tRf+X565jbU49y6dWN2JWMiqeNwMeAtg4AxheaKqgA6/de0xwfDL9FNpeVLNfN6GfxoAzvTY4UjRSYzqKU+5pxDGpBzUsah9XWrQiDiSVAgL/4/mKexW2bOgBAnLS/VwtKO3UPGTN4v4XFjR04mTiF+h0uEL4XZop7V+p13vAwxWdol7c1MNfEqyixjJZpC8mqh0RgLXvbqYtRAA2XCE6mfL5IRzM4zRqz4K+n1DL9zjr0MV4plxVCtJF26yhiyLMjc6pxsHE73KZZqwlCQztM1aBPJHYbA4XerqYAoLMlA8motmX6U0bueEPitCXAoq1wpPTsmnCMWq/K5kb1ndNArvLwApUQeeHzJr6RqS0BByK6RTK3Xq/XQ2hhqwDecxhOz+x4t0CDtOEFrz/kTTAe16yIrO+jGZV4kCZx9E93K9LCvSxinQIq2LOXRtvoi0m0hOVp8rvvWiIzxYqOAAOhkdhU8nwbZFjUOvo/oWNGTPAkLd2KCwebrH/NylCqQYrHxghE25Uz6xLJhPmuE7dfPiv3xJyQyUdv6SBFeIEAYOti5MTQOpgjj4w5uVCtAxZh29+fTQ/6gIUQzEC6Sh58ZURLt8NOoUn+LzrAazhoIpDm/OcuDqTCTujz4WmjJABxp4zbit4THpxhgwC1hA2JFEejHlrYoJBiAd9TUKuIEZe2Qi8/zqT6Db9aqWRcAIsmvP0kyIZlT3UHEgOTOUO0AM8Ny72o7IJCGTw5+Kdyb8FvNdSjtJ2F9IiK8CD7AR65gOtJsP22R/2YI6JceU2CX+fhabK9djgVx7A9RgX3X1kA0wtu/cNdEm/zIUkS3ewBIEYjZKq5aWb7rCOl5ohFlnY6OCjvjAXsg0035sibO2vB5PyyxrNLyF/Mv0kbamm23gDmXm+UA/PFcC1V+wOnTIUfQgKgBTI0118tLacURS2f8gQfQ6ZDhIVBFaPM0PcgN7Ij6eVDxnCtzWaThQq+/hWtAE5CzRIlVlA2WlWOX1UYwdO/ESVltm23LRwNempfsVleCjFjADbZkhVyKdmZRsnfuD+l9FRXQUiTm162NlcXBSIAsShgeWA0bsIfIkM3kjnWGQzD0bcGW6aKvqUwXiy+qUzeIv4i4FVH8fB10m/phww4jPtY6CxqywWkWZ0aoDIjsQByeDez/Q/N66WyAjot8stln7X0sUhAvceiHzPfdUgvj246d/nR7UguzMMc2tW8pJMLrqg1yBA66iVDT1vsbyzMACWxbx/2LsQe+ieJz82HtL3EEWXQ7fzpWmjzup3dTHTg+rQrlkdBjkZsUIuUH2zmnetIqKkHSPU54HAicQjuKk0AKn49iOshOZtzeyQ6/qMJMOmmGWEoCVJKTWvChLMFYTsaXx3tsuoIJ7T2L2cuRTtk5sxL2L0F21FQER9ZDM9zOwAGtZSqOoxxQ+8beqSvuxYDIgU529s1vv6F6Wo9NL6zyqblGhEBSHCV3Ht5mJCo1kK1FOYDORiIcr7R8Qcfp1lyANWE78MKPuLz6FnMQCrU2xshOAhq3/DRh0oyds7bxpkb1GQbrRYia1t2xQBdlV7DYTfTdG4pQtD7uUYLXLhdvgMAWEgGCWJvGW/MpwZPCRPKrjpsoD1VDBjuVNyoo78e53/Xy4V/2CzefCrPNRj9VWX53Zfq82EBwe4KsTkag9xjUAAq2bXX3TTnl+x/qrvSCPnCHNCDf4it2mPCekkCLMKqnpucpQ6MaoUvzDhjAt0B5EddKMWMFz5yKDnFLQkqBPxwABekDkphTPkrw5uHYZy7blHQUKLEds/wu8difhHRxEbiibJcytQsWAb67bCBcTb/AvOTVgrRwbDbnRx24Utx+GgANTNJuqzX1exH+SqAZiHtUmVriwOHcC31OkHE6F4Y3tDWBsgysW58HzGZH2sSgXd8RQUouw5vz2T9R6xRxfRjYABglD3WK/fpP3Mee1A0d2sPmQf9++JKP9xpkD/Crrqg+Z3hLcRlWKxaETHSz9tKH+qiSn90UlzUsdSvaW1MKC5EAK59FhesHqNtEEJzk2LOkR5k6exboN3+qoKtmGY2uiOJbzVRsPIH7VZWtu15oCyEWR2I9f6epXHyqZBdqRxhc8sAWaxIaQbJKSwQiHOiuGxbA09N5lgQnoRcpyD4Q8iFDbzUNIcKtPXLw7puKyAeMAGcdOIX5k4iLP0NwiRnSy5PvQAZI9nhFreHVjzwJSaCCSAJLb9kibw7ZnB9M9gfycQiHMsgAGP0iCU6EiBFTYb0pRQkXJni4WWDhNBj2vAlL1ceALYtu3U2mVcGfCf2k8OhRZ9i54CTHhnd+CE5pCgSaXCn944sNZtJIH0sIvgRUSoxQtfUwvtu1WpV+To+QZVNB8kAxwbXq5GrBP4avB/2EbGBBf3xiejBQIbkq9cFWCVSFSCaO9YGUK1oUBcj+yJdUo7kNsliIBPM9F/JXIEz1uQjGQDEpVNhRydBgJUW0TfhIJhruFxr5OC+UGHrGFaknnzC98ntzFxIcFexOOEwjJvoorv3sAffOpH3o0IyQA8o+KW5AFjI9XP4X03L8ymcKY4xgirhHodzhH6L0FHqgBeVUogEd0BPM03RlOKUoXdIRpCU8h3gpoPgEHprxp0PrWkPQK6jq+n3hfSxzAAAAABJRU5ErkJggg==");
  background-repeat: repeat;
  image-rendering: pixelated;
}

button,
input,
textarea {
  font-family: inherit;
  color: inherit;
  letter-spacing: inherit;
}

h1 {
  font-size: var(--t-heading);
  font-weight: var(--w-reg);
  letter-spacing: var(--tracking);
  line-height: 1.1;
  text-transform: lowercase;
}
h2 {
  font-size: var(--t-18);
  font-weight: var(--w-med);
  letter-spacing: var(--tracking);
  text-transform: lowercase;
}
p {
  font-size: var(--t-14);
  color: var(--fg);
  line-height: 1.6;
  text-wrap: pretty;
}

.eyebrow {
  color: var(--fg-3);
  font-size: var(--t-12);
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.id {
  font-family: var(--font);
  font-weight: var(--w-med);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg);
}

/* primitives ---------------------------------------------------------------- */
.btn {
  height: 48px;
  padding: 0 24px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--fg-4);
  border-radius: var(--radius);
  font-family: var(--font);
  font-size: var(--t-12);
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition:
    border-color var(--t-fast),
    color var(--t-fast),
    background var(--t-fast),
    transform 380ms var(--ease);
}
.btn:hover,
.btn:focus-visible {
  border-color: var(--fg-2);
  transform: translateY(-1px);
  outline: none;
}
.btn:active {
  background: var(--bg-1);
  transform: translateY(0);
}
.btn.primary {
  background: var(--fg);
  color: var(--bg-0);
  border-color: var(--fg);
  position: relative;
}
.btn.primary::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: var(--radius);
  pointer-events: none;
  box-shadow: 0 0 0 rgba(255, 255, 255, 0);
  transition: box-shadow 480ms var(--ease);
}
.btn.primary:hover,
.btn.primary:focus-visible {
  background: #fff;
  border-color: #fff;
  transform: translateY(-2px);
}
.btn.primary:hover::before,
.btn.primary:focus-visible::before {
  box-shadow:
    0 0 16px rgba(255, 255, 255, 0.24),
    0 0 42px rgba(255, 255, 255, 0.14),
    0 0 90px rgba(255, 255, 255, 0.06);
}
.btn.primary:active {
  background: var(--fg);
  transform: translateY(0);
}
.btn.ghost {
  border-color: transparent;
  color: var(--fg-2);
}
.btn.ghost:hover,
.btn.ghost:focus-visible {
  color: var(--fg);
}

/* Disabled state — для CTA, которые показывают будущую функциональность
   ("скачать · скоро"). pointer-events:none гарантирует, что href="#0" не
   сработает даже на тач-устройствах, где :active мог бы дать ложное эхо. */
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* arrow — proper SVG glyph that scales with currentColor */
.btn-arrow {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  transition: transform var(--t-med);
}
.btn:hover .btn-arrow,
.btn:focus-visible .btn-arrow {
  transform: translateX(4px);
}

.dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--signal);
  box-shadow: 0 0 8px rgba(200, 200, 204, 0.45);
  display: inline-block;
}
.dot.warn {
  background: var(--signal-warn);
  box-shadow: 0 0 8px rgba(184, 122, 90, 0.45);
}
.dot.screen {
  background: var(--screen);
  box-shadow: 0 0 8px rgba(95, 170, 122, 0.45);
}

@keyframes void-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}
.dot.pulse {
  animation: void-pulse 1.6s var(--ease) infinite;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 22px;
  padding: 0 10px;
  border: 1px solid var(--fg-4);
  border-radius: var(--radius-pill);
  color: var(--fg-2);
  font-size: var(--t-12);
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  white-space: nowrap;
}

/* ============================================================================
   void — landing
   layout: a fixed-width "island" with floating header & footer over the canvas
   ============================================================================ */

.bg-canvas {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* ----- the shell: every section content lines up to this width --------- */
.shell {
  max-width: var(--island);
  margin: 0 auto;
  padding: 0 var(--gutter);
}
@media (max-width: 720px) {
  .shell {
    padding: 0 20px;
  }
  :root {
    --gutter: 20px;
  }
}
section {
  position: relative;
  z-index: 2;
}

/* ============================================================================
   intro — slower, more cinematic
   ============================================================================ */
.intro {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg-0);
  display: grid;
  place-items: center;
  transition: opacity 1200ms var(--ease);
}
.intro.fade-out {
  opacity: 0;
  pointer-events: none;
}

.intro-canvas {
  position: fixed;
  inset: 0;
  z-index: 101;
  pointer-events: none;
}

.intro-stage {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 102;
}

/* ----------------------------------------------------------------------------
   Intro: a soft glowing orb pinned dead-centre of the viewport. It spins,
   accelerates, throws off swirling particles as it charges up, then explodes
   in a supernova burst before flying up to become the header's logo dot.

   Centering: top:50%; left:50% + translate(-50%, -50%) puts the logo EXACTLY
   at viewport center at any browser zoom / Windows DPI scaling — no flex/grid
   gap math, no off-by-N pixels. JS preserves the centering during liftoff by
   using calc(-50% + Xpx) inside the transform.
   ---------------------------------------------------------------------------- */
.intro-logo {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 200px;
  height: 200px;
  transform-origin: 50% 50%;
  will-change: transform, opacity;
  opacity: 0;
  /* pixel-based translate (NOT translate(-50%, -50%)) so liftOff can smoothly
       interpolate to a pixel-based target transform — calc(-50% + Xpx) edge
       cases occasionally failed to animate in some browsers, leaving the orb
       frozen mid-intro. -100px = half of width (200/2). */
  transform: translate(-100px, -100px) scale(0.92);
  transition:
    transform 700ms var(--ease),
    opacity 600ms var(--ease);
}
.intro-logo.is-awake {
  opacity: 1;
  transform: translate(-100px, -100px) scale(1);
}

/* outer halo — wide soft bloom that intensifies as the spin accelerates.
   driven via CSS custom property so warming/charging classes can crank it
   without re-declaring the gradient. */
.orb-halo {
  position: absolute;
  inset: 14%;
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 50%,
    rgba(255, 255, 255, calc(0.18 * var(--intensity, 1))) 0%,
    rgba(255, 255, 255, calc(0.1 * var(--intensity, 1))) 24%,
    rgba(245, 245, 247, calc(0.04 * var(--intensity, 1))) 50%,
    rgba(0, 0, 0, 0) 72%
  );
  filter: blur(6px);
  pointer-events: none;
  z-index: 0;
  transition: inset 700ms var(--ease);
}

/* concentric ring frames — quiet structural background */
.orb-frame {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  z-index: 1;
  transition: opacity 400ms var(--ease);
}
.orb-frame .orb-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid rgba(230, 230, 232, 0.14);
  transform-origin: 50% 50%;
  opacity: 0.75;
}
.orb-frame .r-2 {
  inset: 14%;
  opacity: 0.55;
}
.orb-frame .r-3 {
  inset: 28%;
  opacity: 0.35;
}

/* SPINNER — the rotating arc that VISUALLY READS the spin. Driven by JS rAF
   so we can smoothly ramp angular velocity via a physics model. A bright dot
   sits at the top edge so the rotation is unmistakable. */
.orb-spinner {
  position: absolute;
  inset: 10%;
  border-radius: 50%;
  border: 1px solid transparent;
  border-top-color: rgba(255, 255, 255, 0.7);
  border-right-color: rgba(255, 255, 255, 0.22);
  transform-origin: 50% 50%;
  will-change: transform;
  z-index: 2;
  transition: opacity 400ms var(--ease);
}
.orb-spinner::before {
  content: "";
  position: absolute;
  top: -3.5px;
  left: 50%;
  width: 5px;
  height: 5px;
  background: #fff;
  border-radius: 50%;
  transform: translateX(-50%);
  box-shadow:
    0 0 6px rgba(255, 255, 255, 0.9),
    0 0 16px rgba(255, 255, 255, 0.45);
}

/* the core: bright, breathing white sphere */
.orb-core {
  position: absolute;
  inset: 40%;
  border-radius: 50%;
  background: radial-gradient(
    circle at 45% 45%,
    #ffffff 0%,
    #ffffff 45%,
    #e0e0e4 80%,
    #b8b8c0 100%
  );
  box-shadow:
    0 0 10px rgba(255, 255, 255, 0.85),
    0 0 28px rgba(255, 255, 255, 0.42),
    0 0 70px rgba(245, 245, 247, 0.22);
  z-index: 3;
  transform-origin: 50% 50%;
  will-change: transform, box-shadow;
  transition:
    box-shadow 800ms var(--ease-in-out),
    transform 800ms var(--ease-in-out);
}

/* WARMING + CHARGING — modulate intensity smoothly as the rotor accelerates.
   Halo blooms, core brightens & swells. CSS-only escalation. */
.intro-logo.is-warming {
  --intensity: 1.6;
}
.intro-logo.is-warming .orb-core {
  box-shadow:
    0 0 16px rgba(255, 255, 255, 0.95),
    0 0 48px rgba(255, 255, 255, 0.6),
    0 0 120px rgba(245, 245, 247, 0.32);
}
.intro-logo.is-charging {
  --intensity: 2.6;
}
.intro-logo.is-charging .orb-core {
  box-shadow:
    0 0 24px rgba(255, 255, 255, 1),
    0 0 80px rgba(255, 255, 255, 0.75),
    0 0 200px rgba(245, 245, 247, 0.45),
    0 0 360px rgba(210, 210, 216, 0.28);
  transform: scale(1.08);
}
.intro-logo.is-bursting .orb-core {
  box-shadow:
    0 0 40px rgba(255, 255, 255, 1),
    0 0 140px rgba(255, 255, 255, 0.95),
    0 0 320px rgba(245, 245, 247, 0.65),
    0 0 600px rgba(210, 210, 216, 0.42);
  transform: scale(1.25);
  transition:
    box-shadow 260ms cubic-bezier(0.2, 0.9, 0.25, 1),
    transform 260ms cubic-bezier(0.2, 0.9, 0.25, 1);
}

/* lift-off: orb scales/translates to header slot; halo+frame+spinner dissolve
   so only the core remains. The JS-set inline transform uses pure pixel-based
   `translate(Xpx, Ypx) scale(s)` (no calc(), no percent mixing) so browsers
   smoothly interpolate the transition every time — earlier calc()-based form
   occasionally failed to animate in some browsers, freezing the intro. */
.intro-logo.is-flying {
  transition:
    transform 1400ms cubic-bezier(0.55, 0.05, 0.2, 1),
    opacity 1400ms cubic-bezier(0.55, 0.05, 0.2, 1);
}
.intro-logo.is-flying .orb-halo {
  opacity: 0;
  transition: opacity 500ms var(--ease);
}
.intro-logo.is-flying .orb-frame {
  opacity: 0;
  transition: opacity 500ms var(--ease);
}
.intro-logo.is-flying .orb-spinner {
  opacity: 0;
  transition: opacity 400ms var(--ease);
}
.intro-logo.is-flying .orb-core {
  box-shadow:
    0 0 4px rgba(255, 255, 255, 0.7),
    0 0 14px rgba(255, 255, 255, 0.35);
  transform: scale(1);
  transition:
    box-shadow 1000ms var(--ease),
    transform 1000ms var(--ease);
}

/* shared @keyframes used by .hero-vis-orbit (and historically by the intro
   orbit before it was migrated to JS rAF). Must live in the global CSS
   scope — was accidentally purged in an earlier intro rewrite. */
@keyframes orbit-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

/* page reveal */
.page {
  position: relative;
  z-index: 2;
  opacity: 0;
  transition: opacity 1200ms var(--ease);
}
.page.is-visible {
  opacity: 1;
}

/* ============================================================================
   header — floating island, logo centered
   ============================================================================ */
.header-wrap {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  padding: 18px var(--gutter) 0;
  pointer-events: none;
  /* keep header above the vignette overlay (z-index 1) */
}
.header-wrap > .header {
  pointer-events: auto;
}

.header {
  max-width: var(--island);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 56px;
  padding: 0 18px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 2px;
}

/* brand lockup: eclipse always visible, "void" text slides out to the RIGHT
   from behind the orb after intro lands, retracts on scroll, re-appears on
   hover. overflow:hidden clips the text so it visually emerges from the orb. */
.brand-lockup {
  display: inline-flex;
  align-items: center;
  color: inherit;
  text-decoration: none;
  /* hover target stays generous so the user can grab it even when collapsed */
  padding: 4px 6px;
  margin: -4px -6px;
}
.brand-logo-slot {
  position: relative;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  /* sits above the wordmark so the text appears to emerge from behind it */
  z-index: 2;
}
.brand-logo-slot .logo-eclipse {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1.5px solid rgba(230, 230, 232, 0.92);
  /* Opaque background + box-shadow combo: shadow's inward blur is hidden by
     the solid disc, while outward blur radiates freely with no clipping
     boundary. Many low-alpha layers at progressively larger radii give a
     smooth physical falloff that has no visible edge — the eye just sees
     light fading into the page. */
  background: var(--bg, #0a0a0b);
  box-shadow:
    0 0 6px   rgba(255, 255, 255, 0.10),
    0 0 14px  rgba(245, 245, 247, 0.07),
    0 0 28px  rgba(230, 230, 236, 0.05),
    0 0 52px  rgba(210, 210, 220, 0.035),
    0 0 90px  rgba(190, 190, 205, 0.022),
    0 0 140px rgba(170, 170, 190, 0.012);
  opacity: 0;
  transition: opacity 600ms var(--ease);
}
.brand-logo-slot.is-ready .logo-eclipse {
  opacity: 1;
  /* very slow, very subtle "breathing" — scale + glow swell drift in sync */
  animation: eclipse-breath 7200ms ease-in-out infinite;
}
@keyframes eclipse-breath {
  0%, 100% {
    transform: scale(1);
    box-shadow:
      0 0 6px   rgba(255, 255, 255, 0.08),
      0 0 14px  rgba(245, 245, 247, 0.055),
      0 0 28px  rgba(230, 230, 236, 0.04),
      0 0 52px  rgba(210, 210, 220, 0.028),
      0 0 90px  rgba(190, 190, 205, 0.018),
      0 0 140px rgba(170, 170, 190, 0.010);
  }
  50% {
    transform: scale(1.018);
    box-shadow:
      0 0 7px   rgba(255, 255, 255, 0.13),
      0 0 16px  rgba(245, 245, 247, 0.09),
      0 0 32px  rgba(230, 230, 236, 0.065),
      0 0 60px  rgba(210, 210, 220, 0.045),
      0 0 105px rgba(190, 190, 205, 0.028),
      0 0 160px rgba(170, 170, 190, 0.016);
  }
}
@media (prefers-reduced-motion: reduce) {
  .brand-logo-slot.is-ready .logo-eclipse { animation: none; }
}

/* "void" wordmark — collapsed by default (zero-width, clipped behind the orb).
   - .is-revealed: initial reveal after intro lands
   - .is-collapsed: retract on scroll (hover or .is-pinned overrides)
   - .is-pinned:   sticky-shown after a click, until page returns to top */
.brand-mark {
  display: inline-block;
  color: var(--fg);
  font-weight: var(--w-med);
  font-size: 15px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  white-space: nowrap;
  text-shadow:
    0 0 1px rgba(255, 255, 255, 0.9),
    0 0 12px rgba(255, 255, 255, 0.25),
    0 0 28px rgba(255, 255, 255, 0.12);
  /* collapsed: width 0, no left margin, slightly tucked left */
  max-width: 0;
  opacity: 0;
  margin-left: 0;
  transform: translateX(-10px);
  overflow: hidden;
  pointer-events: none;
  /* very long, deeply inertial easing — the text feels like it has real
     weight: slow lead-in, even slower trail-out. cubic-bezier(0.16, 1, 0.3, 1)
     is the "easeOutExpo+" variant — premium-feel deceleration. */
  transition:
    max-width 1500ms cubic-bezier(0.16, 1, 0.3, 1),
    opacity 1200ms cubic-bezier(0.16, 1, 0.3, 1) 80ms,
    margin-left 1500ms cubic-bezier(0.16, 1, 0.3, 1),
    transform 1500ms cubic-bezier(0.16, 1, 0.3, 1);
}
.brand-lockup.is-revealed:not(.is-collapsed) .brand-mark,
.brand-lockup.is-revealed.is-pinned .brand-mark {
  max-width: 80px;
  opacity: 1;
  margin-left: 12px;
  transform: translateX(0);
}
/* hover-reveal only for real pointers — on touch, :hover sticks after a tap
   and the wordmark would stay open until the user taps elsewhere */
@media (hover: hover) and (pointer: fine) {
  .brand-lockup.is-revealed.is-collapsed:hover .brand-mark {
    max-width: 80px;
    opacity: 1;
    margin-left: 12px;
    transform: translateX(0);
  }
}

@media (max-width: 720px) {
  .header {
    padding: 0 12px;
  }
}

/* ============================================================================
   section header pattern
   ============================================================================ */
.sect {
  padding: 140px 0;
  position: relative;
}

@media (max-width: 720px) {
  .sect {
    padding: 88px 0;
  }
}

.sect-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 32px;
  padding-bottom: 32px;
  border-bottom: 1px solid var(--fg-4);
  margin-bottom: 32px;
}
.sect-head .num {
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  margin-bottom: 12px;
}
.sect-head h2 {
  font-size: clamp(22px, 2.4vw, 32px);
  font-weight: var(--w-reg);
  color: var(--fg);
  line-height: 1.2;
  text-transform: lowercase;
  max-width: 640px;
}
.sect-head .note {
  color: var(--fg-3);
  font-size: 12px;
  line-height: 1.6;
  max-width: 320px;
  text-align: right;
}

.sect-head.nobottom {
  border-bottom: none;
  margin-bottom: 0;
}

@media (max-width: 720px) {
  .sect-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }
  .sect-head .note {
    text-align: left;
  }
}

/* ============================================================================
   hero
   ============================================================================ */
.hero {
  min-height: 100vh;
  display: grid;
  align-content: center;
  padding: 140px 0 96px;
  position: relative;
}

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 450px;
  gap: 64px;
  align-items: center;
}
@media (max-width: 880px) {
  .hero-grid {
    grid-template-columns: 1fr;
    gap: 0;
  }
  .hero-copy {
    display: contents;
  }
  .hero-eyebrow { order: 1; }
  .hero-display { order: 2; }
  .hero-vis     { order: 3; }
  .hero-tagline { order: 4; }
  .hero-promise { order: 5; }
  .hero-actions { order: 6; }
}
@media (max-width: 720px) {
  .hero {
    min-height: 100svh;
    padding: 100px 0 64px;
    align-content: start;
  }
}

.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  margin-bottom: 32px;
}
.hero-eyebrow .line {
  width: 32px;
  height: 1px;
  background: var(--fg-4);
}

.hero-display {
  font-family: var(--font);
  font-weight: var(--w-reg);
  font-size: var(--t-display);
  line-height: 0.95;
  letter-spacing: -0.01em;
  text-transform: lowercase;
  color: var(--fg);
  text-shadow:
    0 0 1px rgba(255, 255, 255, 0.6),
    0 0 24px rgba(255, 255, 255, 0.18),
    0 0 60px rgba(255, 255, 255, 0.08);
  margin-bottom: 28px;
}
.hero-display .accent {
  color: var(--fg-2);
  font-weight: var(--w-light);
}
.hero-display .h1-line {
  display: block;
  white-space: nowrap;
  /* JetBrains Mono — monospace, so the space glyph is the full em-width.
     At display size that's a chasm between words ("no    trace."). Pull
     it in with negative word-spacing to ~half a normal proportional space. */
  word-spacing: -0.35em;
}

.hero-tagline {
  color: var(--fg-2);
  font-size: clamp(15px, 1.4vw, 17px);
  line-height: 1.55;
  max-width: 520px;
  margin-bottom: 14px;
}
.hero-promise {
  color: var(--fg-3);
  font-size: clamp(12.5px, 1.05vw, 13.5px);
  line-height: 1.6;
  max-width: 520px;
  font-style: italic;
  margin-bottom: 40px;
}

.hero-actions {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}
.hero-actions .btn {
  height: 52px;
  padding: 0 28px;
}
@media (max-width: 480px) {
  .hero-actions {
    flex-direction: column;
    gap: 10px;
  }
  .hero-actions .btn {
    width: 100%;
    justify-content: center;
    height: 48px;
  }
}

/* ----- hero visualizer ----- */
.hero-vis {
  position: relative;
  aspect-ratio: 1 / 1;
  width: 450px;
  margin-left: auto;
  align-self: start;
  margin-top: 24px;
  isolation: isolate;
}
@media (max-width: 880px) {
  .hero-vis {
    width: min(320px, 80vw);
    margin: 0 auto 40px;
  }
  .hero-eyebrow {
    margin-bottom: 6px;
  }
  .hero-display {
    margin-bottom: 32px;
  }
}
@media (max-width: 480px) {
  .hero-vis {
    width: min(260px, 70vw);
    margin: 0 auto 36px;
  }
}


/* soft halo behind the core — the "expensive" glow.
   tuned smaller to match the smaller core; inset positive so the halo
   sits inside the rings rather than spilling beyond the container. */
.hero-vis-halo {
  position: absolute;
  inset: 22%;
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 50%,
    rgba(255, 255, 255, 0.18) 0%,
    rgba(255, 255, 255, 0.09) 22%,
    rgba(245, 245, 247, 0.04) 44%,
    rgba(0, 0, 0, 0) 70%
  );
  filter: blur(6px);
  pointer-events: none;
  z-index: 0;
  animation: hero-halo-breathe 6.5s cubic-bezier(0.4, 0, 0.4, 1) infinite;
  will-change: transform, opacity;
}
@keyframes hero-halo-breathe {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.85;
  }
  50% {
    transform: scale(1.06);
    opacity: 1;
  }
}

.hero-vis-frame {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  z-index: 1;
}
.hero-vis-frame .r {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid var(--fg-4);
  transform-origin: 50% 50%;
  opacity: 0.7;
}
.hero-vis-frame .r-1 {
  animation: hero-pulse 5.6s cubic-bezier(0.3, 0, 0.3, 1) infinite;
}
.hero-vis-frame .r-2 {
  inset: 12%;
  opacity: 0.45;
  animation: hero-pulse 5.6s cubic-bezier(0.3, 0, 0.3, 1) infinite 1s;
}
.hero-vis-frame .r-3 {
  inset: 24%;
  opacity: 0.3;
  animation: hero-pulse 5.6s cubic-bezier(0.3, 0, 0.3, 1) infinite 2s;
}
.hero-vis-frame .r-4 {
  inset: 36%;
  opacity: 0.2;
}

@keyframes hero-pulse {
  0% {
    transform: scale(0.96);
    opacity: 0;
  }
  20% {
    opacity: 0.7;
  }
  100% {
    transform: scale(1.1);
    opacity: 0;
  }
}

/* the pulsing white core — heart of the visualizer.
   ~16% of the container, with a moderate soft glow that breathes. */
.hero-vis-core {
  position: absolute;
  inset: 42%;
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 50%,
    #ffffff 0%,
    #ffffff 38%,
    #e8e8ea 70%,
    #c8c8cc 100%
  );
  box-shadow:
    0 0 10px rgba(255, 255, 255, 0.92),
    0 0 24px rgba(255, 255, 255, 0.5),
    0 0 52px rgba(245, 245, 247, 0.28),
    0 0 100px rgba(210, 210, 216, 0.16);
  animation: hero-core-pulse 4.2s cubic-bezier(0.45, 0, 0.35, 1) infinite;
  z-index: 3;
  will-change: transform, box-shadow;
}
@keyframes hero-core-pulse {
  0%,
  100% {
    transform: scale(1);
    box-shadow:
      0 0 10px rgba(255, 255, 255, 0.92),
      0 0 24px rgba(255, 255, 255, 0.5),
      0 0 52px rgba(245, 245, 247, 0.28),
      0 0 100px rgba(210, 210, 216, 0.16);
  }
  50% {
    transform: scale(1.1);
    box-shadow:
      0 0 16px rgba(255, 255, 255, 1),
      0 0 40px rgba(255, 255, 255, 0.68),
      0 0 80px rgba(245, 245, 247, 0.38),
      0 0 140px rgba(210, 210, 216, 0.22);
  }
}

.hero-vis-orbit {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid var(--fg-4);
  animation: orbit-spin 28s linear infinite;
  z-index: 2;
}
.hero-vis-orbit::before {
  content: "";
  position: absolute;
  top: -3px;
  left: 50%;
  width: 5px;
  height: 5px;
  background: var(--fg-2);
  border-radius: 50%;
  transform: translateX(-50%);
}
.hero-vis-orbit.a {
  inset: 6%;
}
.hero-vis-orbit.b {
  inset: 18%;
  animation-duration: 18s;
  animation-direction: reverse;
}
.hero-vis-orbit.b::before {
  background: var(--signal);
  box-shadow: 0 0 8px rgba(200, 200, 204, 0.55);
}

/* ============================================================================
   what — centred dictionary-entry definition between hero and how
   ----------------------------------------------------------------------------
   Single column, no card frame: this section's job is to *break* the
   text-left / visual-right rhythm of the surrounding blocks with a quiet
   typographic pause. One JS-driven effect lives here: `.what-def` types its
   line out once on first viewport entry (`[data-type-once]` hook in
   landing.js). Different from the screen-vis caret which loops; this one
   types the line and stops.
   ============================================================================ */
/* selector is `section.what` (not bare `.what`) because the privacy section
   already uses <span class="what"> for its deny-list items — a bare class
   would clobber those spans with padding/position. */
section.what {
  padding: 100px 0 120px;
  position: relative;
}
@media (max-width: 720px) {
  section.what {
    padding: 64px 0 80px;
  }
}

.what-stage {
  max-width: 640px;
  margin: 0 auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
}

.what-eyebrow {
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  margin-bottom: 36px;
}

.what-term {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin-bottom: 36px;
}
.what-word {
  display: inline-block;
  font-family: var(--font);
  font-size: clamp(48px, 7vw, 84px);
  font-weight: var(--w-light);
  line-height: 1;
  color: var(--fg);
  text-transform: lowercase;
  letter-spacing: 0.14em;
  /* added padding-left compensates for the right-side optical gap that
     letter-spacing creates after the last glyph — keeps the word visually
     centered under the eyebrow rather than drifting left. */
  padding-left: 0.14em;
  text-shadow:
    0 0 1px rgba(255, 255, 255, 0.5),
    0 0 18px rgba(255, 255, 255, 0.14),
    0 0 48px rgba(255, 255, 255, 0.06);
}
.what-meta {
  color: var(--fg-3);
  font-size: 12px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.what-meta em {
  font-style: normal;
  color: var(--fg-2);
}

.what-def {
  color: var(--fg);
  font-size: clamp(18px, 2vw, 24px);
  line-height: 1.4;
  font-weight: var(--w-reg);
  text-transform: lowercase;
  max-width: 560px;
  /* Floor for the emptied element while the type-once pass runs (landing.js).
     Only guarantees ONE line — how many the finished text actually takes
     depends on viewport width, the clamped font size and the locale's string
     length, so the real reservation is measured in JS before typing starts and
     released when it ends. Without that measurement the page jerked down by a
     line at the exact frame the text wrapped. */
  min-height: 1.4em;
}

.what-rule {
  width: 60px;
  height: 1px;
  background: var(--fg-4);
  margin: 36px auto;
}

.what-sub {
  color: var(--fg-2);
  font-size: 14px;
  line-height: 1.7;
  max-width: 560px;
  text-wrap: pretty;
}

/* coda — pulled-out closing line under the sub-paragraph. Brighter (var(--fg)
   vs --fg-2), one step larger, and with a faint glow that echoes the .what-word
   text-shadow so the line reads as the section's takeaway rather than a
   continuation of the prose above. */
.what-coda {
  margin-top: 20px;
  color: var(--fg);
  font-size: clamp(15px, 1.5vw, 17px);
  line-height: 1.5;
  text-transform: lowercase;
  letter-spacing: 0.005em;
  text-shadow:
    0 0 1px rgba(255, 255, 255, 0.45),
    0 0 16px rgba(255, 255, 255, 0.12),
    0 0 42px rgba(255, 255, 255, 0.05);
}

@media (max-width: 720px) {
  .what-eyebrow      { margin-bottom: 24px; }
  .what-term         { margin-bottom: 28px; }
  .what-rule         { margin: 28px auto; }
  .what-sub          { font-size: 13px; line-height: 1.65; }
  .what-coda         { margin-top: 16px; font-size: 14px; }
}

/* ============================================================================
   how it works
   ============================================================================ */
.howto {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 96px;
  align-items: start;
}
@media (max-width: 980px) {
  .howto {
    grid-template-columns: 1fr;
    gap: 56px;
  }
}

.howto-steps {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.step {
  display: grid;
  grid-template-columns: 52px 1fr;
  gap: 8px;
  padding: 24px 0 24px 24px;
  border-top: 1px solid var(--fg-4);
  cursor: pointer;
  transition: background var(--t-fast);
  position: relative;
  align-items: start;
}
.step:last-child {
  border-bottom: 1px solid var(--fg-4);
}
.step:hover {
  background: rgba(255, 255, 255, 0.012);
}

.step.is-active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--fg);
  box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
}
.step.is-active .step-title {
  color: var(--fg);
}
.step.is-active .step-n {
  color: var(--fg);
}

.step-icon {
  display: flex;
  align-items: flex-start;
  padding-top: 3px;
}
.step-n {
  font-size: 22px;
  font-weight: var(--w-light);
  letter-spacing: -0.02em;
  color: var(--fg-3);
  line-height: 1;
  transition: color var(--t-med);
  font-variant-numeric: tabular-nums;
}
.step:hover .step-n {
  color: var(--fg-2);
}

/* The "no more room" step uses × instead of "04". The character is half the
   width of a 2-digit number AND renders smaller in JetBrains Mono (drawn at
   x-height, not cap-height), so it would look tiny and offset-left without
   this override. Pin it to the same 2ch column the digits occupy, center it,
   bump the font-size + weight so it matches the digits' visual mass. */
.step[data-step="3"] .step-n {
  display: inline-block;
  min-width: 2ch;
  text-align: center;
  font-size: 30px;
  font-weight: var(--w-reg);
  /* shift up a hair so the × baseline aligns with where the "0" cap-height
     would sit — character has no descender so it floats low otherwise. */
  transform: translateX(-5px);
}

.step-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.step-title {
  color: var(--fg-2);
  font-size: 17px;
  font-weight: var(--w-reg);
  letter-spacing: var(--tracking);
  text-transform: lowercase;
  transition: color var(--t-med);
}
.step-text {
  color: var(--fg-3);
  font-size: 12.5px;
  line-height: 1.6;
  max-width: 380px;
}

/* ----- flow diagram ----- */
.flow {
  position: sticky;
  top: 112px;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--fg-4);
  background: var(--bg-1);
  border-radius: 2px;
  overflow: hidden;
  padding: 24px;
  display: flex;
  flex-direction: column;
}
.flow-head {
  display: flex;
  justify-content: space-between;
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  margin-bottom: 16px;
}
.flow-canvas {
  flex: 1;
  position: relative;
}
.flow-canvas svg.flow-edges {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  z-index: 1;
}

/* nodes are positioned by % onto the canvas — anchored at the RING center so
   the (left, top) coordinate lines up exactly with where edges converge. */
.flow-node {
  position: absolute;
  z-index: 2;
  transform: translate(
    -50%,
    -22px
  ); /* ring is 44px tall, sits at the top of the column */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  width: 96px;
}
.flow-node .ring {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--fg-4);
  background: var(--bg-2);
  display: grid;
  place-items: center;
  position: relative;
  transition:
    border-color var(--t-med),
    box-shadow var(--t-med);
  color: var(--fg-3);
}
.flow-node .ring svg {
  width: 18px;
  height: 18px;
}
.flow-node label {
  color: var(--fg-2);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.flow-node .sub {
  color: var(--fg-3);
  font-size: 10px;
  line-height: 1.4;
}

.flow-node.lit .ring {
  border-color: var(--fg);
  color: var(--fg);
  box-shadow:
    0 0 18px rgba(255, 255, 255, 0.22),
    inset 0 0 8px rgba(255, 255, 255, 0.05);
}
.flow-node.lit label {
  color: var(--fg);
}

/* edges */
#flowPath .p2p,
#flowPath .signaling {
  transition:
    stroke var(--t-med),
    opacity var(--t-med);
  fill: none;
}
#flowPath .p2p {
  stroke: var(--fg-3);
  stroke-width: 1;
  opacity: 0.4;
}
#flowPath .signaling {
  stroke: var(--fg-3);
  stroke-width: 1;
  stroke-dasharray: 3 4;
  opacity: 0.4;
}
#flowPath[data-state="share"] .signaling {
  stroke: var(--fg);
  opacity: 1;
}
#flowPath[data-state="share"] .p2p {
  opacity: 0.4;
}
#flowPath[data-state="talk"] .p2p {
  stroke: var(--fg);
  stroke-width: 1.5;
  opacity: 1;
}
#flowPath[data-state="talk"] .signaling {
  opacity: 0.3;
}
#flowPath[data-state="close"] .p2p,
#flowPath[data-state="close"] .signaling {
  opacity: 0.15;
}

#flowSig,
#flowSig2 {
  transition: opacity var(--t-med);
  opacity: 0;
}
#flowPath[data-state="share"] #flowSig {
  opacity: 1;
  animation: sig-move 1.8s var(--ease) infinite;
}
#flowPath[data-state="share"] #flowSig2 {
  opacity: 1;
  animation: sig-move 1.8s var(--ease) infinite 0.9s;
}
@keyframes sig-move {
  0% {
    offset-distance: 0%;
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  85% {
    opacity: 1;
  }
  100% {
    offset-distance: 100%;
    opacity: 0;
  }
}

#flowPing,
#flowPing2 {
  transition: opacity var(--t-med);
  opacity: 0;
}
#flowPath[data-state="talk"] #flowPing {
  opacity: 1;
  animation: ping-move 1.6s var(--ease) infinite;
}
#flowPath[data-state="talk"] #flowPing2 {
  opacity: 1;
  animation: ping-move 1.6s var(--ease) infinite 0.8s reverse;
}
@keyframes ping-move {
  0% {
    offset-distance: 0%;
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  85% {
    opacity: 1;
  }
  100% {
    offset-distance: 100%;
    opacity: 0;
  }
}

.flow-foot {
  display: flex;
  justify-content: space-between;
  margin-top: 16px;
  gap: 18px;
  flex-wrap: wrap;
}
.flow-legend {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--fg-3);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.flow-legend .swatch {
  width: 20px;
  height: 1px;
  background: var(--fg-2);
}
.flow-legend.dashed .swatch {
  background: transparent;
  border-top: 1px dashed var(--fg-3);
}

/* ============================================================================
   why — compact, interactive grid
   ============================================================================ */
.qual-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Разделители рисуем бордерами: top+left у каждой .qual + right+bottom у
     контейнера = одинарные hairline'ы при любом числе колонок (3/2/1). Старый
     gap-reveal (gap:1px + background:var(--fg-4)) убран — он требовал
     НЕпрозрачных ячеек, а они теперь прозрачные (сквозь них виден фон-канвас). */
  border-right: 1px solid var(--fg-4);
  border-bottom: 1px solid var(--fg-4);
  border-radius: 2px;
  overflow: hidden;
}
@media (max-width: 900px) {
  .qual-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 560px) {
  .qual-grid {
    grid-template-columns: 1fr;
  }
}

.qual {
  /* прозрачный без hover (как .plat-card) — сквозь карточку видно фоновый
     канвас; серый var(--bg-1) проявляется только на hover. Анимация
     «проезжающего света» (.qual::before) при этом сохраняется. */
  background: transparent;
  border-top: 1px solid var(--fg-4);
  border-left: 1px solid var(--fg-4);
  padding: 22px 22px 22px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  position: relative;
  transition: background var(--t-med);
  min-height: 168px;
  overflow: hidden;
  cursor: default;
}
.qual:hover {
  background: var(--bg-1);
}

/* a subtle, slow scanline shimmer that reads on hover */
.qual::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    115deg,
    transparent 0%,
    transparent 45%,
    rgba(255, 255, 255, 0.04) 50%,
    transparent 55%,
    transparent 100%
  );
  background-size: 240% 240%;
  background-position: 100% 0%;
  opacity: 0;
  transition:
    opacity 320ms var(--ease),
    background-position 1200ms var(--ease);
}
.qual:hover::before {
  opacity: 1;
  background-position: 0% 100%;
}

.qual-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}
.qual-tag {
  color: var(--fg-3);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.qual-glyph {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  color: var(--fg-3);
  transition:
    color var(--t-med),
    transform var(--t-med);
  position: relative;
}
.qual-glyph svg {
  width: 22px;
  height: 22px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.qual:hover .qual-glyph {
  color: var(--fg);
  transform: scale(1.08);
}

/* the strikethrough line in qual glyphs grows along its own diagonal
   (top-left → bottom-right) by animating stroke-dashoffset. */
.qual-glyph .ban {
  stroke-dasharray: 26;
  stroke-dashoffset: 26;
  transition: stroke-dashoffset 520ms var(--ease);
}
.qual:hover .qual-glyph .ban {
  stroke-dashoffset: 0;
}

.qual-title {
  color: var(--fg);
  font-size: 13.5px;
  font-weight: var(--w-med);
  letter-spacing: var(--tracking);
  text-transform: lowercase;
}
.qual-text {
  color: var(--fg-3);
  font-size: 12px;
  line-height: 1.55;
  margin-top: auto;
}

/* ============================================================================
   inside — feature rows
   ============================================================================ */
.feat-row {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: 64px;
  padding: 64px 0;
  border-top: 1px solid var(--fg-4);
  align-items: center;
}
.feat-row:last-child {
  border-bottom: 1px solid var(--fg-4);
}
.feat-row.flip {
  grid-template-columns: 1.4fr 1fr;
}
.feat-row.flip .feat-copy {
  order: 2;
}
.feat-row.flip .feat-vis {
  order: 1;
}
@media (max-width: 900px) {
  .feat-row,
  .feat-row.flip {
    grid-template-columns: 1fr;
    gap: 32px;
    padding: 48px 0;
  }
  .feat-row.flip .feat-copy {
    order: 1;
  }
  .feat-row.flip .feat-vis {
    order: 2;
  }
}
@media (max-width: 520px) {
  .feat-row,
  .feat-row.flip {
    gap: 20px;
    padding: 36px 0;
  }
  .feat-copy {
    max-width: 100%;
    gap: 12px;
  }
  .feat-title {
    font-size: clamp(18px, 5.5vw, 24px);
  }
  .feat-text {
    font-size: 13px;
  }
}

.feat-copy {
  display: flex;
  flex-direction: column;
  gap: 18px;
  max-width: 440px;
}
.feat-eyebrow {
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.feat-title {
  color: var(--fg);
  font-size: clamp(22px, 2.2vw, 28px);
  line-height: 1.15;
  font-weight: var(--w-reg);
  text-transform: lowercase;
}
.feat-text {
  color: var(--fg-2);
  font-size: 14px;
  line-height: 1.65;
}
.feat-tags {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 6px;
}

.feat-vis {
  position: relative;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--fg-4);
  background: var(--bg-1);
  border-radius: 2px;
  overflow: hidden;
  min-width: 0;
}
@media (max-width: 520px) {
  .feat-vis {
    aspect-ratio: auto;
  }
  .feat-vis .vis-body {
    padding: 14px;
  }
  /* mesh: peers расставлены по %; почти квадрат — peer'ы не слипаются по вертикали */
  .feat-vis:has(.mesh-wrap) {
    aspect-ratio: 5 / 4;
  }
  .flow {
    aspect-ratio: 4 / 5;
  }
  /* chat: контент сам определит высоту, без зияющей пустоты сверху */
  .feat-vis:has(.chat-vis) .chat-vis {
    justify-content: flex-start;
  }
  /* screen: высота по объёму кода, без пустого низа */
}
.feat-vis .vis-head {
  display: flex;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--fg-4);
  color: var(--fg-3);
  font-size: 10.5px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.feat-vis .vis-body {
  padding: 22px;
  height: calc(100% - 39px);
  position: relative;
}

/* ----- mesh viz ----- */
.mesh-wrap {
  position: relative;
  height: 100%;
}
.mesh-wrap svg.mesh-edges {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.mesh-peer {
  position: absolute;
  transform: translate(-50%, -50%);
  text-align: center;
  width: 60px;
}
.mesh-peer .av {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--fg-3);
  margin: 0 auto 6px;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: lowercase;
  font-weight: var(--w-med);
  position: relative;
}
.mesh-peer.live .av {
  border-color: var(--fg);
  color: var(--fg);
}
.mesh-peer.live .av::after {
  content: "";
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--signal);
  bottom: -1px;
  right: -1px;
  box-shadow: 0 0 8px rgba(200, 200, 204, 0.55);
  border: 1.5px solid var(--bg-1);
}
.mesh-peer .nm {
  color: var(--fg-3);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  white-space: nowrap;
}
.mesh-peer.live .nm {
  color: var(--fg-2);
}

/* arc on the avatar when talking */
.mesh-peer .arc {
  position: absolute;
  inset: -5px;
  border-radius: 50%;
  pointer-events: none;
}
.mesh-peer .arc svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

/* ----- chat viz ----- */
.chat-vis {
  display: flex;
  flex-direction: column;
  gap: 10px;
  height: 100%;
  justify-content: flex-end;
}
.chat-msg {
  border: 1px solid var(--fg-4);
  background: var(--bg-2);
  padding: 10px 12px;
  border-radius: 2px;
  max-width: 80%;
  color: var(--fg-2);
  font-size: 12px;
  line-height: 1.5;
}
.chat-msg .who {
  color: var(--fg-3);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  margin-bottom: 4px;
}
.chat-msg.me {
  align-self: flex-end;
  border-color: var(--fg-2);
}
.chat-msg.file {
  display: flex;
  align-items: center;
  gap: 10px;
}
.chat-msg.file .icon {
  width: 28px;
  height: 28px;
  border: 1px solid var(--fg-3);
  border-radius: 2px;
  display: grid;
  place-items: center;
  color: var(--fg-3);
  font-size: 9px;
  flex-shrink: 0;
  text-transform: uppercase;
}
.chat-msg.file .meta {
  color: var(--fg-3);
  font-size: 10px;
  margin-top: 2px;
}

/* ----- screen share viz — mock window with code + cursor ----- */
.screen-vis {
  height: 100%;
  border: 1px solid var(--fg-4);
  background: var(--bg-2);
  border-radius: 2px;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.screen-vis .screen-bar {
  height: 24px;
  border-bottom: 1px solid var(--fg-4);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  flex-shrink: 0;
  color: var(--fg-3);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.screen-vis .traffic {
  display: inline-flex;
  gap: 5px;
}
.screen-vis .traffic span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--fg-4);
}
.screen-vis .screen-body {
  flex: 1;
  position: relative;
  overflow: hidden;
  padding: 14px 16px;
  font-size: 10.5px;
  line-height: 1.55;
  color: var(--fg-3);
  letter-spacing: 0;
}
.screen-vis .code-line {
  display: block;
  white-space: nowrap;
  overflow: hidden;
}
.screen-vis .ln {
  color: var(--fg-4);
  display: inline-block;
  width: 20px;
}
.screen-vis .kw {
  color: var(--fg-2);
}
.screen-vis .fn {
  color: var(--fg);
}
.screen-vis .st {
  color: #b6a47a;
}
.screen-vis .cm {
  color: var(--fg-4);
  font-style: italic;
}

.screen-cursor {
  position: absolute;
  width: 14px;
  height: 18px;
  pointer-events: none;
  left: 0;
  top: 0;
  transform: translate(0, 0);
  transition: transform 700ms cubic-bezier(0.4, 0.1, 0.3, 1);
  z-index: 3;
}
.screen-cursor svg {
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.18));
}
.screen-cursor::after {
  content: "";
  position: absolute;
  width: 22px;
  height: 22px;
  left: -4px;
  top: -2px;
  border: 1px solid rgba(230, 230, 232, 0.8);
  border-radius: 50%;
  opacity: 0;
  transform: scale(0.2);
  pointer-events: none;
}
.screen-cursor.is-clicking::after {
  animation: cursor-click 360ms cubic-bezier(0.2, 0.6, 0.2, 1) forwards;
}
@keyframes cursor-click {
  0% {
    opacity: 1;
    transform: scale(0.2);
  }
  100% {
    opacity: 0;
    transform: scale(1.4);
  }
}

/* a live, typeable line at the bottom of the code body. JS clicks here,
   types a snippet, then erases — in a quiet loop. */
.live-line {
  position: relative;
  min-height: 1.55em;
  color: var(--fg-2);
}
.live-line .typed {
  color: var(--fg-2);
}
.caret {
  display: inline-block;
  width: 1px;
  height: 1.05em;
  background: var(--fg);
  vertical-align: -2px;
  margin-left: 1px;
  animation: caret-blink 1.05s steps(1, end) infinite;
}
@keyframes caret-blink {
  50% {
    opacity: 0;
  }
}

.screen-onair {
  position: absolute;
  top: 32px;
  right: 12px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--screen);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  z-index: 4;
}
.screen-onair::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--screen);
  box-shadow: 0 0 8px rgba(95, 170, 122, 0.55);
  animation: void-pulse 1.6s var(--ease) infinite;
}

.screen-resolution {
  position: absolute;
  bottom: 10px;
  right: 12px;
  color: var(--fg-4);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  z-index: 4;
}

/* ============================================================================
   manifest / privacy
   ============================================================================ */
.manifest {
  padding: 160px 0;
  text-align: center;
  position: relative;
}
.manifest .lead {
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  margin-bottom: 32px;
}
.manifest h2 {
  font-size: clamp(28px, 3.6vw, 48px);
  font-weight: var(--w-light);
  line-height: 1.25;
  text-transform: lowercase;
  color: var(--fg);
  max-width: 880px;
  margin: 0 auto 64px;
  letter-spacing: -0.005em;
  text-wrap: balance;
}
@media (max-width: 720px) {
  .manifest {
    padding: 100px 0;
  }
  .manifest h2 {
    font-size: clamp(22px, 6vw, 36px);
    max-width: 100%;
    line-height: 1.3;
    margin-bottom: 40px;
  }
  .manifest h2 br {
    display: block;
    content: "";
    margin-top: 0.35em;
  }
}
.manifest h2 em {
  color: var(--fg-3);
  font-style: normal;
}

.deny-list {
  max-width: 640px;
  margin: 0 auto;
  border-top: 1px solid var(--fg-4);
  list-style: none;
}
.deny-list li {
  display: grid;
  grid-template-columns: 80px 1fr 80px;
  gap: 24px;
  align-items: center;
  padding: 22px 0;
  border-bottom: 1px solid var(--fg-4);
  text-align: left;
}
.deny-list .key {
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.deny-list .what {
  color: var(--fg);
  font-size: 16px;
  letter-spacing: var(--tracking);
  text-transform: lowercase;
}
.deny-list .stat {
  color: var(--signal-warn);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  text-align: right;
  display: flex;
  align-items: center;
  gap: 6px;
  justify-content: flex-end;
  margin-right: 20px;
}
.deny-list .stat::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--signal-warn);
  box-shadow: 0 0 8px rgba(184, 122, 90, 0.45);
}
@media (max-width: 720px) {
  .deny-list li {
    grid-template-columns: 60px 1fr;
  }
  .deny-list .stat {
    grid-column: 2;
    justify-content: flex-start;
  }
}

/* ============================================================================
   platforms
   ============================================================================ */
.plat {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border: 1px solid var(--fg-4);
  border-radius: 2px;
  /* overflow: visible — иначе absolute-меню сплит-кнопки скачивания режется
     по краю карточки. Скругление 2px при этом визуально не страдает. */
  overflow: visible;
}
@media (max-width: 720px) {
  .plat {
    grid-template-columns: 1fr;
  }
}

.plat-card {
  padding: 48px 36px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  border-right: 1px solid var(--fg-4);
  position: relative;
  transition: background var(--t-med);
}
@media (max-width: 720px) {
  .plat-card {
    border-right: 0;
    border-bottom: 1px solid var(--fg-4);
    padding: 36px 24px;
  }
}
.plat-card:last-child {
  border-right: 0;
}

.plat-foot {
  margin-top: 20px;
  text-align: center;
  color: var(--fg-3);
  font-size: 11px;
  line-height: 1.6;
}
.plat-foot a {
  color: inherit;
  text-decoration: none;
  position: relative;
  display: inline-block;
}
.plat-foot a::after {
  content: "\2197"; /* ↗ */
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translate(-5px, calc(-50% - 0.06em));
  opacity: 0;
  font-size: 0.9em;
  line-height: 1;
  pointer-events: none;
  transition:
    opacity 520ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 680ms cubic-bezier(0.22, 1, 0.36, 1);
}
.plat-foot a:hover,
.plat-foot a:focus-visible {
  outline: none;
}
.plat-foot a:hover::after,
.plat-foot a:focus-visible::after {
  opacity: 1;
  transform: translate(4px, calc(-50% - 0.06em));
}
.plat-card:hover {
  background: var(--bg-1);
}
.plat-card .index {
  color: var(--fg-3);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.plat-card .name {
  color: var(--fg);
  font-size: 32px;
  font-weight: var(--w-reg);
  text-transform: lowercase;
}
.plat-card .desc {
  color: var(--fg-2);
  font-size: 13px;
  line-height: 1.65;
  margin-bottom: 12px;
  max-width: 380px;
}
.plat-card .spec {
  list-style: none;
  margin-bottom: 24px;
}
.plat-card .spec li {
  display: grid;
  grid-template-columns: 1fr auto;
  padding: 10px 0;
  border-top: 1px solid var(--fg-4);
  color: var(--fg-3);
  font-size: 11.5px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  align-items: center;
}
.plat-card .spec li:last-child {
  border-bottom: 1px solid var(--fg-4);
}
.plat-card .spec li b {
  color: var(--fg);
  font-weight: var(--w-med);
}
.plat-card .actions {
  margin-top: auto;
}

/* Ряд «скачать для windows» + заглушка «macos · скоро». Отдельный модификатор,
   а не правило на .actions: тот же класс носят CTA в других секциях, и делать
   их все флексами ради одной карточки незачем. .dl внутри остаётся
   inline-block со своим absolute-меню — обёртка только ставит две кнопки в
   ряд и переносит их на узком экране. */
.actions--dl {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

/* download split-button + dropdown -------------------------------------------
   Главная половина качает desktop-installer; стрелка справа раскрывает меню с
   обоими вариантами (desktop + portable). Кнопки БЕЗ href — загрузка идёт из
   JS (initDownload в landing.js) через временный <a download>, поэтому браузер
   не светит длинный github-URL в статус-баре при наведении. Меню позиционировано
   absolute, чтобы раскрытие не растягивало карточку и не ломало grid-выравнивание
   с соседней web-карточкой. */
.dl {
  position: relative;
  display: inline-block;
}
.dl-split {
  display: inline-flex;
  align-items: stretch;
  transition: transform 380ms var(--ease);
}
.dl-split:hover,
.dl-split:focus-within {
  transform: translateY(-2px);
}
.dl-main {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.dl-toggle {
  padding: 0 14px;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  margin-left: -1px;  /* схлопнуть стык двух бордюров в один */
}
/* половинки светлеют как единое целое; индивидуальный lift гасим, чтобы пилюля
   не разъезжалась «лесенкой» при наведении на одну из половин. Селектор через
   .dl-split — поднимает специфичность до уровня .btn.primary:hover, иначе тот
   перебивал бы transform:none и каждая половинка поднималась бы сама. */
.dl-split .dl-main:hover,
.dl-split .dl-toggle:hover,
.dl-split .dl-main:focus-visible,
.dl-split .dl-toggle:focus-visible,
.dl-split .dl-main:active,
.dl-split .dl-toggle:active {
  transform: none;
}
.dl-split:hover .dl-main,
.dl-split:hover .dl-toggle,
.dl-split:focus-within .dl-main,
.dl-split:focus-within .dl-toggle {
  background: #fff;
  border-color: #fff;
}
.dl-toggle::after {
  content: "";
  position: absolute;
  left: 0;
  top: 9px;
  bottom: 9px;
  width: 1px;
  background: rgba(0, 0, 0, 0.16);  /* hairline-делитель на светлой primary-заливке */
}
.dl-ico,
.dl-chev {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.dl-chev {
  transition: transform var(--t-med);
}
.dl[data-open="true"] .dl-chev {
  transform: rotate(180deg);
}
.dl-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 100%;
  background: var(--bg-1);
  border: 1px solid var(--fg-4);
  border-radius: var(--radius);
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: 5;
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition:
    opacity var(--t-fast),
    transform var(--t-fast);
}
.dl[data-open="true"] .dl-menu {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.dl-opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  width: 100%;
  height: 40px;
  padding: 0 14px;
  background: transparent;
  border: 0;
  border-radius: 2px;
  color: var(--fg-2);
  font-family: var(--font);
  font-size: var(--t-12);
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background var(--t-fast),
    color var(--t-fast);
}
.dl-opt:hover,
.dl-opt:focus-visible {
  background: var(--bg-0);
  color: var(--fg);
  outline: none;
}
.dl-opt .dl-ico {
  color: var(--fg-3);
  transition: color var(--t-fast);
}
.dl-opt:hover .dl-ico,
.dl-opt:focus-visible .dl-ico {
  color: var(--fg);
}

/* ============================================================================
   contributors — credits crawl + self-typing announcer + persistent CTA
   ----------------------------------------------------------------------------
   Source-of-truth list (PREMIUM_NICKNAMES in public/js/config.js) is served
   over /api/contributors and rendered by injectContributors() in landing.js.
   Layout is a hairline-framed strip: live counter eyebrow → infinite marquee
   of gold-shimmer names → footer row with typing announcer + persistent CTA.
   The gold palette mirrors .participant-name.premium in public/css/stage.css —
   intentionally muted wheat/amber, NOT #FFD700.
   ============================================================================ */
.contrib-stage {
  max-width: 1080px;
  margin: 0 auto;
  padding-top: 48px;
}

/* hairline-framed strip — top/bottom borders only, no fill */
.contrib-frame {
  border-top: 1px solid var(--fg-4);
  border-bottom: 1px solid var(--fg-4);
  padding: 28px 0 24px;
}

/* eyebrow inside the frame: "● roll · N in list · helping void" */
.contrib-eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  padding: 0 24px;
  margin-bottom: 20px;
}
.contrib-eyebrow .dot {
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: var(--signal);
  box-shadow: 0 0 8px rgba(200, 200, 204, 0.45);
  display: inline-block;
}

/* marquee — single-axis infinite scroll with masked edges. JS duplicates
   the name list so translateX(-50%) lands on an identical item → seamless. */
.contrib-marquee {
  position: relative;
  /* overflow:hidden is required so the duplicated marquee track doesn't
     spill sideways past the frame; vertical bleed for the hover-glow halo
     is bought by generous Y-padding instead (overflow-x:clip + overflow-y:
     visible per spec collapses to auto when the X axis is non-visible, so
     it doesn't actually rescue the glow). */
  overflow: hidden;
  padding: 32px 0;
  mask-image: linear-gradient(to right, transparent 0%, #000 8%, #000 92%, transparent 100%);
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 8%, #000 92%, transparent 100%);
}
.contrib-track {
  list-style: none;
  margin: 0;
  padding: 0;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  animation: contrib-roll 56s linear infinite;
  will-change: transform;
}
@keyframes contrib-roll {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}
.contrib-track li {
  display: inline-flex;
  align-items: center;
  gap: 36px;
  padding: 0 24px;
}

/* gold name — shimmer = background-position cycle; glow = drop-shadow
   (text-shadow is ignored under background-clip:text, so the warm halo
   MUST come from filter). */
.contrib-name {
  font-family: var(--font);
  font-size: clamp(36px, 4.4vw, 56px);
  font-weight: var(--w-light);
  letter-spacing: 0.02em;
  text-transform: lowercase;
  /* names aren't clickable — keep the default arrow, not the I-beam text
     cursor that <span>s otherwise inherit from the surrounding text flow. */
  cursor: default;
  background: linear-gradient(110deg,
    #b89254 0%, #e8c97a 28%, #f6e4ad 50%, #e8c97a 72%, #b89254 100%);
  background-size: 220% 100%;
  background-position: 0% 50%;
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
          color: transparent;
  filter:
    drop-shadow(0 0 4px  rgba(232, 201, 122, 0.35))
    drop-shadow(0 0 12px rgba(232, 201, 122, 0.18));
  animation: contrib-shimmer 7s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite;
  /* hover transition kept on the base so pointer-in and pointer-out are
     symmetric. Long duration + easeOutExpo-like curve = the halo blooms
     and decays with no perceptible "snap" — same easing the brand-mark
     wordmark uses, so the contributors section borrows that house feel. */
  transition:
    filter 720ms cubic-bezier(0.16, 1, 0.3, 1),
    transform 720ms cubic-bezier(0.16, 1, 0.3, 1);
}
.contrib-track li:hover .contrib-name {
  /* hover ADDS to the base glow (radii > base 4/12), with a soft third
     bloom layer at 26px for the perceptible "lift" without melting. Scale
     is intentionally tiny — the glow is what reads as a reaction, not the
     geometry shift. */
  filter:
    drop-shadow(0 0 5px  rgba(246, 228, 173, 0.5))
    drop-shadow(0 0 14px rgba(232, 201, 122, 0.32))
    drop-shadow(0 0 26px rgba(232, 201, 122, 0.18));
  transform: scale(1.012);
}
@keyframes contrib-shimmer {
  0%, 100% { background-position:   0% 50%; }
  50%      { background-position: 100% 50%; }
}

/* placeholder slot — "+ ваше_имя" / "+ your_name". Deliberately NOT gold
   (only real contributors get the premium treatment). Reads as an empty
   seat at the table: dashed underline, muted color, brightens on hover.
   Hover timing, scale, and glow-ladder mirror the gold-name hover so both
   reactions feel like they come from the same surface — only the palette
   differs (cool neutral white instead of warm gold). */
.contrib-name.placeholder {
  background: none;
  -webkit-text-fill-color: var(--fg-3);
          color: var(--fg-3);
  filter: none;
  animation: none;
  border-bottom: 1px dashed var(--fg-4);
  padding-bottom: 4px;
  font-weight: var(--w-light);
  transition:
    color 720ms cubic-bezier(0.16, 1, 0.3, 1),
    border-color 720ms cubic-bezier(0.16, 1, 0.3, 1),
    filter 720ms cubic-bezier(0.16, 1, 0.3, 1),
    transform 720ms cubic-bezier(0.16, 1, 0.3, 1);
}
.contrib-track li:hover .contrib-name.placeholder {
  -webkit-text-fill-color: var(--fg);
          color: var(--fg);
  border-bottom-color: var(--fg-2);
  /* same 5/14/26 drop-shadow ladder as the gold hover, recoloured to the
     neutral fg palette — the lift reads as "responsive" without claiming
     premium status. */
  filter:
    drop-shadow(0 0 5px  rgba(230, 230, 232, 0.35))
    drop-shadow(0 0 14px rgba(230, 230, 232, 0.20))
    drop-shadow(0 0 26px rgba(230, 230, 232, 0.10));
  transform: scale(1.012);
}

.contrib-sep {
  color: var(--fg-4);
  font-size: clamp(28px, 3vw, 48px);
  font-weight: var(--w-light);
  line-height: 1;
}

/* footer row — typing announcer on left, persistent CTA pill on right */
.contrib-foot {
  /* grid (1fr auto) instead of flex(space-between): with flex+space-between
     the announcer was shrinking to its intrinsic width and the short typed
     line read as drifting mid-row. 1fr forces column 1 to consume ALL the
     leftover space; the announcer's content then renders at the column's
     left edge — no chance of accidental centering. */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 32px;
  margin-top: 18px;
  padding: 18px 24px 0;
  border-top: 1px solid var(--fg-4);
}
.contrib-announcer {
  min-width: 0;
  color: var(--fg-2);
  font-size: 13px;
  letter-spacing: var(--tracking);
  /* nowrap (not `pre`): the markup is indented for readability, and `pre`
     would render those indent characters as visible whitespace — pushing
     the typed line to the right and inflating the row height. nowrap
     normalises whitespace yet still keeps the line on one row for the
     text-overflow:ellipsis to work when names ever grow long. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: left;
  justify-self: start;
}
.contrib-caret {
  display: inline-block;
  color: var(--signal-warn);
  margin-left: 2px;
  animation: contrib-blink 0.9s steps(2) infinite;
}
@keyframes contrib-blink {
  0%, 50%      { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

.contrib-cta {
  flex: 0 0 auto;
  color: var(--fg);
  font-family: var(--font);
  font-size: 12px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
  text-decoration: none;
  border: 1px solid var(--fg-4);
  border-radius: var(--radius);
  padding: 10px 16px;
  white-space: nowrap;
  transition:
    border-color var(--t-fast),
    color var(--t-fast),
    background var(--t-fast);
}
.contrib-cta:hover,
.contrib-cta:focus-visible {
  border-color: var(--fg-2);
  background: var(--bg-1);
  outline: none;
}
.contrib-cta:active { background: var(--bg-2); }

@media (max-width: 720px) {
  .contrib-stage    { padding-top: 32px; }
  .contrib-frame    { padding: 20px 0 16px; }
  .contrib-eyebrow  { padding: 0 16px; margin-bottom: 14px; font-size: 10px; }
  .contrib-track li { gap: 24px; padding: 0 16px; }
  .contrib-name     { font-size: 28px; }
  .contrib-sep      { font-size: 22px; }
  .contrib-foot {
    /* mobile: stack announcer + cta vertically (grid single-column) */
    grid-template-columns: 1fr;
    gap: 16px;
    padding: 16px 16px 0;
  }
  .contrib-announcer { font-size: 11px; }
  .contrib-cta { text-align: center; padding: 12px 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .contrib-name  { animation: none; background-position: 50% 50%; }
  /* marquee-скролл оставляем включённым даже под reduce-motion — по запросу:
     это горизонтальная лента, не вестибулярно-агрессивная анимация. */
  .contrib-caret { animation: none; }
}

/* empty-state: JS sets [hidden] when the names array comes back empty.
   `[hidden]` defaults to display:none but our `section { position:relative;
   z-index:2 }` declared elsewhere wouldn't fight it — explicit rule kept
   for defensive clarity if some future selector tries to override. */
section.contrib[hidden] { display: none; }

/* ============================================================================
   final cta
   ============================================================================ */
.final {
  padding: 200px 0 240px;
  text-align: center;
  position: relative;
  overflow: hidden;
}
.final .ring-stack {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: max(840px, 130vw);
  aspect-ratio: 1 / 1;
  pointer-events: none;
  opacity: 0.5;
}
@media (max-width: 720px) {
  .final {
    padding: 120px 0 140px;
  }
  .final .ring-stack {
    width: max(560px, 160vw);
  }
  .final .a {
    font-size: clamp(40px, 12vw, 72px);
  }
}
.final .ring-stack span {
  position: absolute;
  inset: 0;
  border: 1px solid var(--fg-4);
  border-radius: 50%;
}
.final .ring-stack span:nth-child(2) {
  inset: 10%;
  opacity: 0.7;
}
.final .ring-stack span:nth-child(3) {
  inset: 20%;
  opacity: 0.5;
}
.final .ring-stack span:nth-child(4) {
  inset: 30%;
  opacity: 0.3;
}
.final .ring-stack span:nth-child(5) {
  inset: 40%;
  opacity: 0.2;
}

.final-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
}
.final .q {
  color: var(--fg-3);
  font-size: 13px;
  letter-spacing: var(--tracking);
  text-transform: lowercase;
  font-style: italic;
}
.final .a {
  font-size: clamp(40px, 6vw, 80px);
  line-height: 1;
  color: var(--fg);
  font-weight: var(--w-light);
  text-transform: lowercase;
  letter-spacing: -0.01em;
  text-shadow:
    0 0 1px rgba(255, 255, 255, 0.55),
    0 0 24px rgba(255, 255, 255, 0.18),
    0 0 80px rgba(255, 255, 255, 0.1);
}

.final-actions {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 8px;
}
.final-actions .btn {
  height: 52px;
  padding: 0 28px;
}
@media (max-width: 480px) {
  .final-actions {
    flex-direction: column;
    align-items: center;
    gap: 10px;
    width: 100%;
  }
  .final-actions .btn {
    width: min(280px, 100%);
    justify-content: center;
    height: 48px;
  }
}

/* ============================================================================
   footer — minimal centered credit
   ============================================================================ */
.footer {
  padding: 16px var(--gutter) 18px;
  text-align: center;
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.footer-copy a {
  color: inherit;
  text-decoration: none;
  position: relative;
  display: inline-block;
  transform: translateX(0);
  transition: transform 620ms cubic-bezier(0.22, 1, 0.36, 1);
}

.footer-copy a::after {
  content: "\2197"; /* ↗ */
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translate(-2px, calc(-50% - 0.06em));
  opacity: 0;
  font-size: 0.9em;
  line-height: 1;
  pointer-events: none;
  transition:
    opacity 520ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 680ms cubic-bezier(0.22, 1, 0.36, 1);
}
.footer-copy a:hover,
.footer-copy a:focus-visible {
  transform: translateX(-1.4em);
  outline: none;
}
.footer-copy a:hover::after,
.footer-copy a:focus-visible::after {
  opacity: 1;
  transform: translate(5px, calc(-50% - 0.06em));
}

/* Internal links in the footer (e.g. /privacy) — skip the slide+↗ animation
   that's meant to telegraph "leaving the site". For same-origin pages we want
   the standard subtle hover, not the external-link departure visual. */
.footer-copy a.internal::after {
  content: none;
}
.footer-copy a.internal:hover,
.footer-copy a.internal:focus-visible {
  transform: none;
  color: var(--fg-2);
}

/* footer language link — manual override for the URL-based language pick.
   Intentionally quiet (low-contrast, small caps) so it doesn't compete with
   the copyright line, but still discoverable for anyone whose system locale
   landed them on the wrong variant (e.g. an English-speaker who got the
   Russian page from a direct social-media share). */
.footer-lang {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: lowercase;
  color: var(--fg-3);
  text-decoration: none;
  border-bottom: 1px dotted var(--fg-4);
  padding-bottom: 1px;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.footer-lang:hover,
.footer-lang:focus-visible {
  color: var(--fg);
  border-bottom-color: var(--fg-3);
  outline: none;
}
.footer-copy {
  margin: 0;
  font-size: 10px;
  line-height: 1.8;
  letter-spacing: 0.18em;
  text-transform: lowercase;
  color: var(--fg-4);
  cursor: default;
  transition: color 0.6s ease;
  background: transparent;
  border: none;
  display: inline-block;
}
.footer-copy:hover {
  color: var(--fg-3);
  text-shadow:
    0 0 8px rgba(255, 255, 255, 0.12),
    0 0 24px rgba(255, 255, 255, 0.06),
    0 0 60px rgba(255, 255, 255, 0.03);
  animation: footer-glow 2.4s ease-in-out infinite;
}

@keyframes footer-glow {
  0%, 100% {
    text-shadow:
      0 0 6px rgba(255, 255, 255, 0.10),
      0 0 20px rgba(255, 255, 255, 0.05),
      0 0 50px rgba(255, 255, 255, 0.02);
  }
  50% {
    text-shadow:
      0 0 10px rgba(255, 255, 255, 0.18),
      0 0 32px rgba(255, 255, 255, 0.09),
      0 0 80px rgba(255, 255, 255, 0.04);
  }
}

/* ============================================================================
   reveal — initial hidden state lives on .reveal itself, so blocks don't
   "blink" when the .in class is added with an animation-delay (previously
   the keyframe's `both` fill-mode would snap the element back to opacity:0
   for the delay window, then animate in — visually it looked like the
   element flickered off for ~500ms before appearing).

   transition (not animation) is used so the initial hidden state is
   declarative, no fill-mode quirks; will-change keeps it composited.

   NB: will-change is dropped (→ auto) in landing.js on transitionend — a
   permanent composite layer makes Chrome ignore custom ::selection and revert
   to the default blue. We only need the hint while the reveal is animating.
   ============================================================================ */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 800ms cubic-bezier(0.2, 0.65, 0.2, 1),
    transform 900ms cubic-bezier(0.2, 0.65, 0.2, 1);
  will-change: opacity, transform;
}
.reveal.in {
  opacity: 1;
  transform: none;
}
.reveal[data-delay="1"] {
  transition-delay: 80ms;
}
.reveal[data-delay="2"] {
  transition-delay: 180ms;
}
.reveal[data-delay="3"] {
  transition-delay: 280ms;
}
.reveal[data-delay="4"] {
  transition-delay: 380ms;
}
.reveal[data-delay="5"] {
  transition-delay: 480ms;
}

/* typing cursor — used by [data-type="cursor"] reveal elements.
   width matches a thin glyph so the line height doesn't jump when text
   appears; steps(2) gives the hard on/off blink instead of a smooth fade. */
.type-cursor {
  display: inline-block;
  width: 0.5ch;
  color: var(--fg);
  animation: cursor-blink 1s steps(2, end) infinite;
  transform: translateY(-0.04em);
  text-shadow:
    0 0 1px rgba(255, 255, 255, 0.6),
    0 0 14px rgba(255, 255, 255, 0.22);
}
.type-cursor::before {
  content: "|";
}
@keyframes cursor-blink {
  0%,
  49% {
    opacity: 1;
  }
  50%,
  100% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001s !important;
    transition-duration: 0.001s !important;
  }
}

/* ============================================================================
   antivirus explainer (ВРЕМЕННЫЙ БЛОК)
   Стоит на месте .plat-foot, пока сборки не подписаны сертификатом: smartscreen
   и defender судят неподписанный exe по репутации, и модалка это объясняет.
   После получения подписи — вернуть .plat-foot в разметке и снести эту секцию.
   ============================================================================ */
.av-foot {
  margin-top: 20px;
  text-align: center;
}
.av-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0;
  background: none;
  border: 0;
  font-family: var(--font);
  font-size: 11px;
  line-height: 1.6;
  letter-spacing: var(--tracking);
  text-transform: lowercase;
  color: var(--fg-3);
  cursor: pointer;
  transition: color var(--t-fast);
}
.av-trigger:hover,
.av-trigger:focus-visible {
  color: var(--fg-2);
  outline: none;
}
.av-trigger-dot {
  flex: none;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--signal-warn);
  box-shadow: 0 0 8px rgba(184, 122, 90, 0.45);
}
.av-trigger > span:last-child {
  padding-bottom: 2px;
  border-bottom: 1px solid var(--fg-4);
  transition: border-color var(--t-fast);
}
.av-trigger:hover > span:last-child,
.av-trigger:focus-visible > span:last-child {
  border-color: var(--fg-2);
}

/* modal ---------------------------------------------------------------------- */
/* z-index выше виньетки (9999), иначе затемнение легло бы поверх панели */
.av-modal {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.av-modal[hidden] {
  display: none;
}
body.av-lock {
  overflow: hidden;
}

.av-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 6, 8, 0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  opacity: 0;
  transition: opacity var(--t-med);
}
.av-panel {
  position: relative;
  width: min(680px, 100%);
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 28px 40px 28px;
  background: var(--bg-1);
  border: 1px solid var(--fg-4);
  border-radius: var(--radius);
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity var(--t-med),
    transform var(--t-med);
  scrollbar-width: thin;
  scrollbar-color: var(--fg-4) transparent;
}
.av-panel::-webkit-scrollbar {
  width: 4px;
  background: transparent;
}
.av-panel::-webkit-scrollbar-track {
  background: transparent;
}
.av-panel::-webkit-scrollbar-thumb {
  background: var(--fg-4);
  border-radius: var(--radius);
  transition: background var(--t-fast);
}
.av-panel:hover::-webkit-scrollbar-thumb {
  background: var(--fg-3);
}
.av-modal.is-open .av-backdrop {
  opacity: 1;
}
.av-modal.is-open .av-panel {
  opacity: 1;
  transform: none;
}

.av-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  border-radius: var(--radius);
  color: var(--fg-3);
  cursor: pointer;
  transition: color var(--t-fast);
}
.av-close svg {
  width: 14px;
  height: 14px;
}
.av-close:hover,
.av-close:focus-visible {
  color: var(--signal-warn);
  outline: none;
}

.av-num {
  margin-bottom: 14px;
  color: var(--fg-3);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.av-head h2 {
  color: var(--fg);
  font-size: 26px;
  font-weight: var(--w-reg);
  line-height: 1.25;
  text-transform: lowercase;
}

.av-body {
  margin-top: 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.av-body p {
  color: var(--fg-2);
  font-size: 13px;
  line-height: 1.75;
}
.av-body b,
.av-steps .what b {
  color: var(--fg);
  font-weight: var(--w-reg);
}

.av-trust {
  margin-top: 22px;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.av-trust .btn {
  height: 42px;
  padding: 0 18px;
  font-size: 11px;
}
.av-trust-note {
  margin-top: 12px;
  color: var(--fg-3);
  font-size: 11px;
  line-height: 1.7;
}

.av-steps {
  margin-top: 22px;
  padding-top: 22px;
  border-top: 1px solid var(--fg-4);
}
.av-steps-title {
  margin-bottom: 18px;
  color: var(--fg-3);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: lowercase;
}
.av-steps ol {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.av-steps li {
  display: grid;
  grid-template-columns: 52px 1fr;
  gap: 12px;
  align-items: start;
}
.av-steps .key {
  padding-top: 2px;
  color: var(--fg-3);
  font-size: 11px;
  letter-spacing: 0.08em;
}
.av-steps .what {
  color: var(--fg-2);
  font-size: 13px;
  line-height: 1.7;
}
.av-note {
  margin-top: 20px;
  color: var(--fg-3);
  font-size: 11px;
  line-height: 1.7;
}

@media (max-width: 720px) {
  .av-modal {
    padding: 14px;
  }
  .av-panel {
    padding: 34px 20px 28px;
    max-height: calc(100vh - 28px);
  }
  .av-head h2 {
    font-size: 21px;
  }
  .av-trust .btn {
    width: 100%;
    justify-content: center;
  }
  .av-steps li {
    grid-template-columns: 1fr;
    gap: 4px;
  }
}
