/* ==========================================================================
   ScopeBee landing — design tokens
   ========================================================================== */

:root {
  /* surfaces */
  --bg:            #0b0b16;
  --bg-alt:        #101024;
  --surface:       rgba(255, 255, 255, .04);
  --border:        rgba(255, 255, 255, .1);

  /* text
     --text-faint is the floor: at .42 it measured 3.8:1 on --bg, under the 4.5:1
     WCAG AA needs for text this small. .52 lands at 5.1:1 and reads the same. */
  --text:          #f5f5f7;
  --text-muted:    rgba(245, 245, 247, .65);
  --text-faint:    rgba(245, 245, 247, .52);

  /* accents */
  --accent:        #ff8a4c;
  --accent-2:      #f0568f;
  --accent-3:      #7b5cff;
  /* --accent-3 as text on its own .16 tint is 3.8:1; this lighter twin clears AA
     without touching the gradient, which is where --accent-3 earns its value. */
  --accent-3-hi:   #9683ff;

  /* non-accent signals: a shipped milestone, and the worst result in a ranking.
     --worst is held dark on purpose — at full red it outshouted the leader's gradient;
     its text twin is lighter only so the label still clears AA on --bg. */
  --ok:            #4ade80;
  --worst:         #9e3640;
  --worst-text:    #d06a72;

  /* section separator: sits a touch above --border because one of these lands
     on the hero's gradient, where the page's own tint is already light */
  --hairline:      rgba(255, 255, 255, .16);
  --accent-grad:   linear-gradient(115deg, var(--accent), var(--accent-2) 55%, var(--accent-3));

  /* layout */
  --maxw:          1200px;
  /* Hero pushes its columns wider than the content grid. The vw half only
     matters below 2000px, where it buys back the room the shot needs to stay
     at its 780px cap on a 1728px laptop; past that the 1920px cap governs. */
  --maxw-hero:     min(1920px, 96vw);
  --gutter:        clamp(1.25rem, 4vw, 3rem);
  --topbar-h:      72px;
  --section-pad:   clamp(4rem, 9vh, 6.5rem);
  --radius:        18px;
  /* second-level bar of the docs section; anchors there clear both bars */
  --docsbar-h:     52px;

  /* code. A system stack and nothing else: the site runs from file:// and
     ships no font files. */
  --mono:          ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --code-bg:       rgba(255, 255, 255, .045);

  /* motion */
  --ease-out:      cubic-bezier(.2, .7, .2, 1);
  --reveal-dur:    .85s;

  color-scheme: dark;
}

/* ==========================================================================
   Base
   ========================================================================== */

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

html { scroll-behavior: smooth; scroll-padding-top: calc(var(--topbar-h) + 16px); }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 16px/1.6 "Inter", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
ul, ol { margin: 0; padding: 0; list-style: none; }
h1, h2, h3 { margin: 0; line-height: 1.1; letter-spacing: -.02em; }
p { margin: 0; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 200;
  padding: .6rem 1rem;
  border-radius: 10px;
  background: var(--accent);
  color: #16121f;
  font-weight: 600;
  transform: translateY(-200%);
  transition: transform .2s var(--ease-out);
}
.skip-link:focus-visible { transform: translateY(0); }

/* ==========================================================================
   Top bar
   ========================================================================== */

.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--topbar-h);
  transition: background-color .3s var(--ease-out), backdrop-filter .3s var(--ease-out), border-color .3s var(--ease-out);
  border-bottom: 1px solid transparent;
}

.topbar.is-scrolled {
  background: rgba(11, 11, 22, .72);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: var(--border);
}

.topbar__inner {
  max-width: var(--maxw);
  height: 100%;
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: .65rem;
  font-weight: 700;
  letter-spacing: -.01em;
}
.brand__mark { width: 36px; height: 36px; }
.brand__name { font-size: 1.125rem; }

/* flex:1, а не margin-left:auto — растянутый .nav разводит своих детей сам:
   список встаёт к логотипу, .topbar__end к краю. Группа остаётся ВНУТРИ .nav,
   потому что .nav и есть выпадающая панель ниже брейкпоинта: всё вынесенное
   наружу пришлось бы на мобильном либо потерять, либо продублировать. */
.nav {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 2rem;
}

.nav__list {
  display: flex;
  align-items: center;
  gap: 1.75rem;
  font-size: .95rem;
}

.nav__list a {
  position: relative;
  color: var(--text-muted);
  transition: color .2s var(--ease-out);
}
.nav__list a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -6px;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: var(--accent-grad);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .25s var(--ease-out);
}
.nav__list a:hover { color: var(--text); }
.nav__list a:hover::after { transform: scaleX(1); }

/* Всё, что относится к сайту целиком, а не к текущей странице: путь в
   документацию и выбор языка. Якоря на секции лендинга остались слева. */
.topbar__end {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 1rem;
}

/* Ссылка, а не кнопка: в герое уже есть акцентная кнопка, и вторая такая в
   шапке спорила бы с ней. Вес отличает её от якорей, цвет — нет. */
.docs-btn {
  font-size: .875rem;
  font-weight: 700;
  white-space: nowrap;  /* «Документация» не должна разложить шапку на две строки */
  color: var(--text-muted);
  transition: color .2s var(--ease-out);
}
.docs-btn:hover { color: var(--text); }
.docs-btn[aria-current] { color: var(--accent); }

.lang {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .35rem .7rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .06em;
}
.lang__btn {
  position: relative;
  padding: 0 .15rem;
  color: var(--text-faint);
  transition: color .2s var(--ease-out);
}
/* EN and RU are 22x20 of ink. The pill's proportions are the design, so the
   touch target is grown with a pseudo-element instead of padding — it clears
   the 24px minimum without moving a pixel. */
.lang__btn::before {
  content: "";
  position: absolute;
  inset: -.5rem -.35rem;
}
.lang__btn:hover { color: var(--text); }
.lang__btn.is-active { color: var(--accent); }
.lang__sep { color: var(--text-faint); }

.burger {
  display: none;
  margin-left: auto;
  width: 44px;
  height: 44px;
  padding: 11px 10px;
  flex-direction: column;
  justify-content: space-between;
}
.burger span {
  display: block;
  height: 2px;
  border-radius: 2px;
  background: var(--text);
  transition: transform .3s var(--ease-out), opacity .2s var(--ease-out);
}
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(10px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-10px) rotate(-45deg); }

/* ==========================================================================
   Hero
   ========================================================================== */

.hero {
  /* Drives the wordmark; the tagline below is sized as a ratio of it, so the
     two keep their edges flush when either one changes. */
  --hero-display: clamp(2.5rem, 7.4vw, 7.25rem);

  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
  isolation: isolate;
}

.hero__bg,
.hero__blobs,
.hero__grain {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

/* Hues are kept, lightness is not: no phase of gradientShift may glare. */
.hero__bg {
  background: linear-gradient(125deg,
    #08080f 0%,
    #141032 22%,
    #331544 42%,
    #5c2340 58%,
    #7d3823 74%,
    #0f0e20 100%);
  background-size: 300% 300%;
  animation: gradientShift 20s ease-in-out infinite alternate;
}

.hero__blobs {
  background:
    radial-gradient(38% 42% at 18% 28%, rgba(255, 138, 76, .24), transparent 70%),
    radial-gradient(34% 38% at 78% 22%, rgba(123, 92, 255, .22), transparent 70%),
    radial-gradient(46% 44% at 62% 82%, rgba(240, 86, 143, .18), transparent 72%);
  mix-blend-mode: screen;
  filter: blur(28px);
  animation: blobDrift 26s ease-in-out infinite alternate;
}

.hero__grain {
  background:
    linear-gradient(rgba(8, 8, 16, .3), rgba(8, 8, 16, .3)),
    radial-gradient(120% 90% at 50% 50%, transparent 42%, rgba(6, 6, 14, .82) 100%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.35'/%3E%3C/svg%3E");
  opacity: .55;
}

@keyframes gradientShift {
  0%   { background-position:   0% 50%; }
  50%  { background-position: 100%  0%; }
  100% { background-position:  60% 100%; }
}

@keyframes blobDrift {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(-3%, 2%, 0) scale(1.08); }
  100% { transform: translate3d(2%, -3%, 0) scale(1.03); }
}

.hero__inner {
  width: 100%;
  max-width: var(--maxw-hero);
  margin-inline: auto;
  padding: calc(var(--topbar-h) + 3rem) var(--gutter) 6rem;
  display: grid;
  /* The left track is max-content — the brand row and the nowrap tagline are
     cut to the same width on purpose, and auto tracks would centre them.
     The right track takes what is left instead of a second max-content: two
     max-content tracks cannot shrink, so between 900px and 1600px they summed
     past the container and the editor shot was clipped by the page's
     overflow-x. Above ~1620px the leftover already exceeds the shot's 780px
     cap, so wide screens render exactly as before. */
  grid-template-columns: max-content minmax(0, 1fr);
  justify-content: space-between;
  align-items: center;
  gap: clamp(1.5rem, 3vw, 3rem);
}

.hero__left { justify-self: start; }

.hero__brand {
  display: flex;
  align-items: center;
  gap: clamp(.9rem, 1.6vw, 1.5rem);
}

.hero__logo {
  flex: none;
  width: clamp(72px, 10.5vw, 156px);
  height: auto;
  filter: drop-shadow(0 6px 16px rgba(0, 0, 0, .45));
}

.hero__title {
  font-size: var(--hero-display);
  font-weight: 800;
  letter-spacing: -.03em;
}

/* Sized to span the brand row above it in one unbroken line: the ratio between
   this and --hero-display is what keeps both edges flush, so they change together. */
.hero__tagline {
  margin-top: 1.5rem;
  max-width: none;
  white-space: nowrap;
  font-size: clamp(.9rem, 2.08vw, 2.04rem);
  color: rgba(255, 255, 255, .78);
  line-height: 1.4;
}

/* Two columns, three rows: the engine names sit in the header, so every number
   below them is claimed without repeating whose it is. One structure holds from
   360px to a wide desktop — nothing wraps away from its label. */
.hero__stats {
  margin-top: clamp(1.4rem, 3vh, 2.2rem);
  width: 100%;          /* spans the column the wordmark and tagline set */
  border-collapse: collapse;
  text-align: left;
}
.hero__stats th,
.hero__stats td { padding: .3rem 0; }
.hero__stats thead th,
.hero__stats tbody th {
  font-weight: 400;
  font-size: .8125rem;
  color: rgba(255, 255, 255, .55);
}
.hero__stats thead .is-ours { color: var(--accent); font-weight: 600; }
.hero__stats thead th { width: 50%; }
.hero__stats tbody th {
  width: 1%;            /* shrink-to-fit: the two value columns take the rest */
  padding-right: clamp(1rem, 3vw, 2.25rem);
  white-space: nowrap;
}

/* the value columns: ours carries the weight, Unity's stays a reference point */
.hero__stats tbody td {
  width: 50%;
  font-size: .9375rem;
  color: rgba(255, 255, 255, .5);
  white-space: nowrap;
}
.hero__stats tbody td.is-ours {
  font-size: clamp(1.15rem, 2vw, 1.6rem);
  font-weight: 800;
  letter-spacing: -.02em;
  color: var(--text);
}
.hero__stats tbody tr + tr th,
.hero__stats tbody tr + tr td { border-top: 1px solid rgba(255, 255, 255, .07); }

.hero__stats-note { margin-top: 1.1rem; font-size: .8125rem; color: rgba(255, 255, 255, .5); }
.hero__stats-note a { border-bottom: 1px solid rgba(255, 255, 255, .25); }
.hero__stats-note a:hover { color: var(--text); border-bottom-color: var(--text); }

.hero__right {
  justify-self: end;
  /* The cap has to sit on the grid item, not on .hero__shot: a percentage on
     the figure would resolve against a shrink-to-fit parent and cancel itself
     out, and justify-self:end would then push the overflow left, over the
     wordmark. Here 100% is the grid track, which is a definite width. */
  max-width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(1.25rem, 2.5vw, 2.25rem);
}

/* the demo player carries the right-hand column on its own */
.hero__shot {
  margin: 0;
  width: clamp(320px, 46vw, 780px);
  max-width: 100%;  /* never wider than the track the grid leaves for it */
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 14px;
  overflow: hidden;
  background: #1f1f1f;
  box-shadow: 0 24px 60px -24px rgba(0, 0, 0, .75), 0 2px 8px rgba(0, 0, 0, .35);
}
/* display:block kills the inline-baseline gap that would show the figure's
   background as a strip under the frame; aspect-ratio holds the box before
   the poster decodes, so the hero never jumps on load. */
.hero__shot video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  background: #04101a;  /* the video's own letterbox colour, not the frame's grey */
}

.hero__scroll {
  position: absolute;
  left: 50%;
  bottom: 2.25rem;
  translate: -50% 0;
  width: 2px;
  height: 56px;
  border-radius: 2px;
  background: rgba(255, 255, 255, .18);
  overflow: hidden;
}
.hero__scroll-line {
  display: block;
  width: 100%;
  height: 40%;
  border-radius: 2px;
  background: #fff;
  animation: scrollHint 2.4s var(--ease-out) infinite;
}
@keyframes scrollHint {
  0%   { transform: translateY(-100%); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: translateY(250%); opacity: 0; }
}

/* ==========================================================================
   News

   Sits between the hero and the feature sections, so it stays quiet: cards on
   the same --surface tint as the rest of the page, no shadow, and one accent
   reserved for the newest date.
   ========================================================================== */

.news__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  /* hairlines run with the content, not edge to edge: a full-bleed rule reads
     as a page break on a wide monitor, and this block is part of the flow */
  border-block: 1px solid var(--hairline);
  padding-block: var(--section-pad);
}

.news__head { margin-bottom: 2.75rem; }

/* auto-fit rather than a fixed three, so the row reflows on its own and the
   min() keeps a card from demanding more than the viewport on a narrow phone */
.news__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: clamp(1rem, 2vw, 1.5rem);
}
/* the card is a link inside the li, so the li has to pass the row height on —
   without this a shorter entry leaves a ragged bottom edge next to a longer one */
.news__list > li { display: flex; }
.news__list > li > .news-card { flex: 1; }

.news-card {
  display: flex;
  flex-direction: column;
  gap: .7rem;
  padding: clamp(1.25rem, 2.4vw, 1.75rem);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  transition: border-color .2s var(--ease-out), background-color .2s var(--ease-out);
}
/* the whole card is the link, so the hit area matches what people aim at */
a.news-card:hover {
  border-color: rgba(255, 255, 255, .22);
  background: rgba(255, 255, 255, .06);
}

.news-card__when {
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent);
}

.news-card__title {
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.25;
}

.news-card__text {
  color: var(--text-muted);
  font-size: 1rem;
}

/* the way out of the three cards and into the full archive */
.news-all { margin-top: 2rem; }

.news-all__link {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .55rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-weight: 700;
  color: var(--text);
  transition: border-color .2s var(--ease-out), background-color .2s var(--ease-out);
}
.news-all__link:hover { border-color: rgba(255, 255, 255, .22); background: var(--surface); }
.news-all__link svg { width: 14px; height: 14px; }

/* ==========================================================================
   News archive  (news/index.html)

   The landing page shows three cards; this page shows every post, with a rail
   of months beside it. One column is the floor: main.js builds the rail, so
   with scripting off there is nothing to divide the page with and the posts
   simply run down the page.
   ========================================================================== */

.archive__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: calc(var(--topbar-h) + 3.5rem) var(--gutter) 5rem;
}

.archive__head { margin-bottom: 2.75rem; }

/* echoes the date stamp on a card, one step quieter: it is a status line, not
   a heading. Empty until main.js names the current filter. */
.archive__status {
  margin-top: .85rem;
  min-height: 1.2em;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.archive__cols { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); }
/* the second column hangs off .js for the same reason every reveal rule does */
.js .archive__cols {
  grid-template-columns: 17rem minmax(0, 1fr); /* minmax(0,…) or a long title widens the track */
  align-items: start;
}

.archive__nav ul { display: flex; flex-direction: column; gap: .25rem; }
.archive__group { margin-top: 1.5rem; }

.archive__year {
  margin-bottom: .5rem;
  padding-inline: .85rem;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.archive__link {
  display: flex;
  justify-content: space-between;
  gap: .75rem;
  padding: .5rem .85rem;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--text-muted);
  transition: border-color .2s var(--ease-out), background-color .2s var(--ease-out), color .2s var(--ease-out);
}

.archive__link:hover {
  border-color: var(--border);
  background: var(--surface);
  color: var(--text);
}

/* the current filter carries a border as well as a fill: in forced colours the
   fill is replaced wholesale, and a signal made only of background disappears */
.archive__link[aria-current] {
  border-color: var(--accent);
  background: var(--accent);
  color: #1a1005;
  font-weight: 700;
}

.archive__label { display: flex; gap: .4rem; }

.archive__count {
  font-size: .8125rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
}
.archive__link[aria-current] .archive__count { color: inherit; }

/* only printed when the rail spans more than one year — with a single year
   above every link it would just repeat the group heading */
.archive__link-year { color: var(--text-faint); font-weight: 400; }
.archive__link[aria-current] .archive__link-year { color: inherit; }

.archive__list {
  display: flex;
  flex-direction: column;
  gap: clamp(1rem, 2vw, 1.5rem);
}
/* filtering is a plain show/hide — no transition, nothing to wait for. The
   class rather than [hidden] because any display rule would outrank it. */
.archive__list > li.is-filtered { display: none; }

/* ==========================================================================
   News article  (news/*.html)

   One column, one measure. The landing page is a showcase; an article is
   reading material, so nothing here competes with the text.
   ========================================================================== */

.article {
  max-width: 46rem;
  margin-inline: auto;
  padding: calc(var(--topbar-h) + 3.5rem) var(--gutter) 5rem;
}

.article__back {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: 2.5rem;
  padding: .45rem .9rem .45rem .75rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: .875rem;
  font-weight: 600;
  color: var(--text-muted);
  transition: color .2s var(--ease-out), border-color .2s var(--ease-out);
}
.article__back:hover { color: var(--text); border-color: rgba(255, 255, 255, .22); }
.article__back svg { width: 14px; height: 14px; }

.article__when {
  margin-bottom: .9rem;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent);
}

.article__title {
  font-size: clamp(2rem, 4.6vw, 3.1rem);
  font-weight: 800;
  letter-spacing: -.025em;
}

.article__lead {
  margin-top: 1.1rem;
  color: var(--text-muted);
  font-size: clamp(1.05rem, 1.6vw, 1.25rem);
  line-height: 1.5;
}

.article__body { margin-top: 3rem; }
.article__body > * + * { margin-top: 1.25rem; }

.article__body h2 {
  margin-top: 3rem;
  font-size: clamp(1.35rem, 2.2vw, 1.75rem);
  font-weight: 700;
}

.article__body p { color: var(--text-muted); font-size: 1.0625rem; }
/* links inside running text: the CTA is a button, these must not look like one */
.article__body p a:not(.article__cta):not(.cmp__demo) {
  color: var(--text);
  border-bottom: 1px solid rgba(255, 255, 255, .3);
  transition: border-color .2s var(--ease-out);
}
.article__body p a:not(.article__cta):not(.cmp__demo):hover { border-bottom-color: var(--accent); }
.article__body strong { color: var(--text); font-weight: 600; }

.article__body ul {
  display: grid;
  gap: .6rem;
  color: var(--text-muted);
  font-size: 1.0625rem;
}
.article__body li { padding-left: 1.1rem; position: relative; }
.article__body li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .65em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}

/* A link out to the thing the article is about, at both ends of the piece. */
.article__cta-row { margin-top: 1.75rem; text-align: center; }
.article__cta {
  display: inline-block;
  padding: .7rem 1.4rem;
  border-radius: 999px;
  background: var(--accent);
  color: #1a1005;
  font-weight: 700;
  transition: filter .2s var(--ease-out);
}
.article__cta:hover { filter: brightness(1.08); }

/* Paired screenshots. Stacked by default so a wide shot keeps its detail; the
   two orientation shots instead sit side by side and are matched on height,
   because their different shapes are the point being made. */
.article__shots { display: grid; gap: .75rem; }
.article__shots img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* Comparison shots break out of the text column: side by side is how you read
   a difference, and at 46rem two of them would be too small to see one. The
   width is picked so each half is no smaller than it was stacked full-width. */
.article__media--wide {
  position: relative;
  left: 50%;
  translate: -50% 0;
  width: min(1400px, 94vw);
}
.article__shots--row { grid-auto-flow: column; grid-auto-columns: 1fr; }

@media (max-width: 900px) {
  .article__media--wide { position: static; left: auto; translate: none; width: 100%; }
  .article__shots--row { grid-auto-flow: row; }
}

.article__shots--screens {
  grid-auto-flow: column;
  justify-content: start;
  align-items: end;
}
.article__shots--screens img { width: auto; height: clamp(190px, 30vw, 275px); }

@media (max-width: 620px) {
  .article__shots--screens { grid-auto-flow: row; justify-items: center; }
  .article__shots--screens img { width: auto; max-width: 100%; height: auto; }
}

/* Each shot is a button: opening it full size is an action, and a button gets
   keyboard focus and Enter/Space for free. */
.shot {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  border-radius: var(--radius);
}
.shot img { transition: filter .2s var(--ease-out); }
.shot:hover img { filter: brightness(1.07); }

/* ==========================================================================
   Lightbox

   Built once by main.js and reused. Arrows only ever move within the gallery
   the image was opened from, so one pair of shots never leads into the next.
   ========================================================================== */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(4, 6, 14, .92);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity .2s var(--ease-out);
}
.lightbox.is-open { opacity: 1; }
/* `display: grid` above outranks the browser's own `[hidden] { display: none }`,
   so without this the closed lightbox stays in the layout as a transparent
   full-screen sheet and swallows every click on the page. */
.lightbox[hidden] { display: none; }

.lightbox__img {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - 9rem);
  width: auto;
  height: auto;
  border-radius: 10px;
  box-shadow: 0 30px 80px -30px rgba(0, 0, 0, .9);
}

.lightbox__caption {
  position: absolute;
  left: 50%;
  bottom: clamp(.75rem, 2.5vw, 1.5rem);
  translate: -50% 0;
  max-width: min(60ch, 90vw);
  text-align: center;
  font-size: .875rem;
  color: rgba(255, 255, 255, .65);
}

.lightbox button {
  position: absolute;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .18);
  background: rgba(0, 0, 0, .45);
  color: #fff;
  transition: background-color .2s var(--ease-out), border-color .2s var(--ease-out);
}
.lightbox button:hover { background: rgba(255, 255, 255, .16); border-color: rgba(255, 255, 255, .4); }
.lightbox button svg { width: 18px; height: 18px; }

.lightbox__close { top: clamp(.75rem, 2.5vw, 1.5rem); right: clamp(.75rem, 2.5vw, 1.5rem); }
.lightbox__prev  { left: clamp(.5rem, 2vw, 1.5rem); top: 50%; translate: 0 -50%; }
.lightbox__next  { right: clamp(.5rem, 2vw, 1.5rem); top: 50%; translate: 0 -50%; }
/* a single-image gallery has nowhere to step to */
.lightbox.is-solo .lightbox__prev,
.lightbox.is-solo .lightbox__next { display: none; }

/* the page behind must not scroll under an open lightbox */
body.has-lightbox { overflow: hidden; }

/* A small comparison table inside an article. Same reading as the hero table:
   the engine names head the columns, ours carries the weight. */
.article__table {
  width: 100%;
  margin-top: 2rem;
  border-collapse: collapse;
  text-align: left;
}
.article__table th,
.article__table td { padding: .6rem 0; }
.article__table thead th,
.article__table tbody th {
  font-weight: 400;
  font-size: .875rem;
  color: var(--text-faint);
}
.article__table thead .is-ours { color: var(--accent); font-weight: 600; }
.article__table thead th { width: 32%; }
.article__table tbody th { width: 36%; padding-right: 1rem; }
.article__table tbody td { font-size: 1rem; color: rgba(255, 255, 255, .55); white-space: nowrap; }
.article__table tbody td.is-ours { font-size: 1.15rem; font-weight: 700; color: var(--text); }
.article__table tbody tr + tr th,
.article__table tbody tr + tr td { border-top: 1px solid rgba(255, 255, 255, .07); }

.article__note {
  margin-top: 1rem;
  font-size: .8125rem;
  line-height: 1.5;
  color: var(--text-faint);
}

/* Placeholder for a clip that is not shot yet. Swap the whole .video-slot for
   a <video controls preload="metadata" poster="…"> — .article__media already
   carries the frame, so nothing else has to change. */
.article__media { margin: 2.5rem 0 0; }

.video-slot {
  display: grid;
  place-items: center;
  align-content: center;   /* rows sit together, not spread down the box */
  gap: .75rem;
  aspect-ratio: 16 / 9;
  padding: 1.5rem;
  border: 1px dashed rgba(255, 255, 255, .2);
  border-radius: var(--radius);
  background: var(--surface);
  text-align: center;
}
.video-slot__mark {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--border);
  color: var(--text-faint);
}
.video-slot__mark svg { width: 16px; height: 16px; }
.video-slot__label {
  max-width: 34ch;
  color: var(--text-faint);
  font-size: .9375rem;
}

.article__caption {
  margin-top: .85rem;
  color: var(--text-faint);
  font-size: .875rem;
  text-align: center;
}
/* Where the assets in a clip came from: its own smaller line under the caption,
   so the credit never reads as part of what the clip shows. */
.article__credit { display: block; margin-top: .3rem; font-size: .8125rem; }
.article__caption a {
  color: var(--text-muted);
  border-bottom: 1px solid rgba(255, 255, 255, .25);
  transition: border-color .2s var(--ease-out);
}
.article__caption a:hover { border-bottom-color: var(--accent); }

/* no aspect-ratio here on purpose: a clip is whatever shape it was recorded in,
   and the width/height attributes hold the box before it loads. The 16/9 on
   .video-slot is only there because a placeholder has no shape of its own. */
.article__media video {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #04101a;
}

/* ==========================================================================
   Feature sections
   ========================================================================== */

/* clip, not hidden: the pre-reveal translateX must not widen the page,
   but a scroll container here would break the sticky top bar */
.feature {
  padding-block: var(--section-pad);
  overflow-x: clip;
}

/* every other feature gets a slightly lifted band; data-side already alternates */
.feature[data-side="right"] {
  background: linear-gradient(180deg, transparent, var(--bg-alt) 22%, var(--bg-alt) 78%, transparent);
}

.feature__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(2.5rem, 6vw, 5.5rem);
}

/* Image is always first in the DOM (so it stacks on top on mobile);
   on wide screens data-side flips the visual order. */
.feature[data-side="right"] .feature__media { order: 2; }
.feature[data-side="right"] .feature__body  { order: 1; }

.feature__media {
  margin: 0;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: 0 30px 80px -40px rgba(0, 0, 0, .9);
}
.feature__media img { width: 100%; height: auto; }

.feature__eyebrow,
.section-eyebrow {
  margin-bottom: .9rem;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  background: var(--accent-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.feature__title,
.section-title {
  font-size: clamp(2rem, 3.6vw, 3rem);
  font-weight: 800;
}

.feature__text {
  margin-top: 1.1rem;
  max-width: 46ch;
  color: var(--text-muted);
  font-size: 1.0625rem;
}

.checks {
  margin-top: 2rem;
  display: grid;
  gap: .9rem;
}

.checks li {
  position: relative;
  padding-left: 2.25rem;
  font-size: 1.0625rem;
  font-weight: 500;
}

/* staggered only when JS is there to un-stagger them — see Scroll reveal */
.js .checks li {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity .5s var(--ease-out),
    transform .5s var(--ease-out);
  transition-delay: calc(.22s + var(--i, 0) * .09s);
}

.checks li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  translate: 0 -50%;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent-grad);
  box-shadow: 0 4px 14px -4px rgba(255, 138, 76, .8);
}

.checks li::after {
  content: "";
  position: absolute;
  left: 8px;
  top: 50%;
  width: 6px;
  height: 11px;
  border: solid #14101f;
  border-width: 0 2.5px 2.5px 0;
  translate: 0 -62%;
  rotate: 45deg;
}

.js .feature__body.is-visible .checks li {
  opacity: 1;
  transform: translateY(0);
}

/* ==========================================================================
   Comparison

   Engines are rows; each metric is a number plus a bar. The bar width comes
   from an inline `--w` — the share of the heaviest result in that column, so
   build size and heap scale independently of each other.
   ========================================================================== */

.compare__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  /* padding lives on the inner, not the section, so the rule sits on the seam
     between blocks instead of floating inside the section's own top gap */
  border-top: 1px solid var(--hairline);
  padding-block: var(--section-pad);
}

.compare__head { margin-bottom: 2.75rem; }

.compare__lead {
  margin-top: 1.1rem;
  max-width: 66ch;
  color: var(--text-muted);
  font-size: 1.0625rem;
}

.compare__block + .compare__block { margin-top: 2.25rem; }

.compare__caption {
  display: flex;
  align-items: baseline;
  gap: .9rem;
  flex-wrap: wrap;
  margin-bottom: .7rem;
}
.compare__caption h3 { font-size: 1.15rem; font-weight: 700; }

/* Legend maps the two bars in every cell; without it the pair is unreadable. */
.compare__platform {
  padding: .22rem .6rem;
  border-radius: 999px;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent-3-hi);
  background: rgba(123, 92, 255, .16);
}

/* The table itself never widens the page — it scrolls inside this box.
   On a phone roughly half of it is off-screen, including the Demo column,
   and macOS/iOS overlay scrollbars give no hint that it moves. The two
   `local` covers ride the content and uncover a shadow at whichever edge
   still has table behind it; both vanish once you reach that end. Pure CSS,
   no scroll handler. */
.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background:
    linear-gradient(90deg,  var(--bg), rgba(11, 11, 22, 0)) 0 0 / 28px 100% no-repeat local,
    linear-gradient(270deg, var(--bg), rgba(11, 11, 22, 0)) 100% 0 / 28px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%,   rgba(255, 255, 255, .26), rgba(255, 255, 255, 0)) 0 0 / 22px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(255, 255, 255, .26), rgba(255, 255, 255, 0)) 100% 0 / 22px 100% no-repeat scroll,
    var(--surface);
}
/* the region is focusable so the table can be scrolled from the keyboard */
.table-scroll:focus-visible { outline-offset: -2px; }

.cmp {
  width: 100%;
  min-width: 780px;
  border-collapse: collapse;
}

.cmp th,
.cmp td {
  padding: .5rem 1.1rem;
  text-align: left;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.cmp tbody tr:last-child > * { border-bottom: 0; }

.cmp thead th {
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
  background: rgba(255, 255, 255, .03);
}

/* four value columns now share the row with the engine name and the demo link */
.cmp td { width: 24%; }
/* Physics table has one data column instead of two, so it gets a wider one —
   at 31% the bar would sit in a sliver next to an oversized demo column. */
.cmp--physics td { width: 55%; }

/* The playable table carries five value columns instead of three, so the shares
   drop and the table needs more room before the bars turn into stubs. It lives
   inside .article__media--wide, which is wider than the text column it follows. */
.cmp--playable { min-width: 1000px; }
.cmp--playable td { width: 14%; }
/* Flappy Bird has four value columns: between the playable table and the rest. */
.cmp--flappy { min-width: 900px; }
.cmp--flappy td { width: 18%; }
/* Five columns leave every cell narrower, so the value gives the bar back the
   width it needs — the same trade the narrow-screen rules make below. */
.cmp--playable .cmp__value { width: 4.6em; font-size: .9rem; }
.cmp--playable .cmp__bar { width: calc(100% - 5.2em); }
/* Flappy Bird has two-decimal megabytes ("28.55 MB"), which wrap at 4.6em. */
.cmp--flappy .cmp__value { width: 5.3em; font-size: .9rem; white-space: nowrap; }
.cmp--flappy .cmp__bar { width: calc(100% - 5.9em); }

.cmp td:last-child { width: auto; }

.cmp__engine {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -.01em;
}
.cmp__engine small {
  font-size: .75rem;
  font-weight: 500;
  color: var(--text-faint);
}

/* Cocos Creator has no wasm at all, so its row measures something else and has
   to say so under the engine name — the inline <small> holds the version. */
.cmp__engine-note {
  display: block;
  margin-top: .15rem;
  font-size: .7rem;
  line-height: 1.3;
}

/* Value and bar share one line: stacking them doubled every row's height,
   and with two tables on the page that cost a whole screen of scrolling. */
.cmp__value {
  display: inline-block;
  width: 5.2em;
  font-size: .95rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  vertical-align: middle;
}

.cmp__bar {
  display: inline-block;
  position: relative;
  width: calc(100% - 6em);
  height: 8px;
  vertical-align: middle;
  border-radius: 5px;
  background: rgba(255, 255, 255, .09);
}
.cmp__bar::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--w, 0%);
  border-radius: 5px;
  background: rgba(255, 255, 255, .3);
}

/* our row: tinted and accented by name */
.cmp__ours > * { background: rgba(255, 138, 76, .07); }
.cmp__ours .cmp__engine { color: var(--accent); }

/* The leader of a column gets the brand gradient — whoever it is. Marking only
   our own row would make the table read as a claim rather than a measurement. */
.cmp__best .cmp__value { color: var(--accent); }
.cmp__best .cmp__bar::before { background: var(--accent-grad); }

/* Flappy Bird is ranked per metric rather than laid out per engine: a row is one
   metric, its cell lists all seven engines best first. A bar then gets the whole
   cell instead of a fifth of a row, and only the first and the last place are
   coloured — a tie for first still lights one bar, the order sets which. */
.cmp--rank { min-width: 0; }
.cmp--rank td { width: auto; padding-block: .75rem; }
/* .cmp--rank in front: .cmp th would otherwise win on vertical-align and padding */
.cmp--rank .cmp__metric {
  width: 11rem;
  vertical-align: top;
  padding-top: 1rem;
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.cmp__rank {
  display: grid;
  grid-template-columns: 8.5rem 1fr 4.6rem;
  align-items: center;
  gap: .3rem .9rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
/* subgrid, not display: contents — contents strips the list-item role from <li> */
.cmp__rank li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; }
.cmp__name { font-size: .875rem; font-weight: 600; white-space: nowrap; }
.cmp__name--ours { color: var(--accent); }
.cmp__rank .cmp__bar { width: auto; height: 10px; }
.cmp__rank .cmp__value { width: auto; font-size: .875rem; text-align: right; }
.cmp__worst .cmp__name,
.cmp__worst .cmp__value { color: var(--worst-text); }
.cmp__worst .cmp__bar::before { background: var(--worst); }
/* inside an article the body's list rule would add a bullet and an indent to every engine */
.article__body .cmp__rank li { padding-left: 0; }
.article__body .cmp__rank li::before { content: none; }
/* and its paragraph rule would enlarge the demo links' line past the table's own type */
.article__body .cmp__demos { color: var(--text-faint); font-size: .875rem; }

/* the demo links leave the table with the per-engine rows */
.cmp__demos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem 1.1rem;
  margin-top: .9rem;
  color: var(--text-faint);
  font-size: .875rem;
}

.cmp__demo {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  color: var(--accent);
  font-weight: 600;
  font-size: .92rem;
}
/* grows the touch target past 24px without adding to the row height */
.cmp__demo::before {
  content: "";
  position: absolute;
  inset: -.4rem -.3rem;
}
.cmp__demo::after {
  content: "↗";
  display: inline-block;
  transition: translate .2s var(--ease-out);
}
.cmp__demo:hover::after { translate: 2px -2px; }

/* Per-block description. The section lead explains the bars once; a block that
   measures something other than a plain web build needs a line of its own. */
.compare__blurb {
  margin: -.2rem 0 .9rem;
  max-width: 70ch;
  color: var(--text-muted);
  font-size: .9375rem;
  line-height: 1.55;
}

.compare__note {
  margin-top: .9rem;
  color: var(--text-faint);
  font-size: .875rem;
}
/* a second note under the same table reads as its continuation */
.compare__note + .compare__note { margin-top: .5rem; }
/* Flappy Bird's method lives in its article; the block keeps only the way there */
.compare__more {
  color: var(--accent);
  font-weight: 600;
}
.compare__more::after {
  content: " →";
  display: inline-block;
  transition: translate .2s var(--ease-out);
}
.compare__more:hover::after { translate: 3px 0; }

/* ==========================================================================
   Roadmap

   Each milestone is a three-column row: the month lives outside the card and
   the middle column is the rail. The rail is uniform except for the current
   milestone (.milestone--now), whose month and node are tinted with the accent
   and whose node carries a pulsing ring — the pill states progress in words,
   the node states it on the track.
   ========================================================================== */

.roadmap {
  background: linear-gradient(180deg, transparent, var(--bg-alt) 20%, var(--bg-alt) 80%, transparent);
}

.roadmap__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  border-top: 1px solid var(--hairline);
  padding-block: var(--section-pad);
}

.roadmap__head { margin-bottom: 3rem; }

.roadmap__list { display: block; }

.milestone {
  display: grid;
  grid-template-columns: 150px 40px minmax(0, 1fr);
  column-gap: clamp(1rem, 2.5vw, 1.75rem);
  align-items: start;
}

.milestone__when {
  text-align: right;
  padding-top: .1rem;
}
.milestone__mon {
  display: block;
  font-size: clamp(1.5rem, 2.4vw, 1.9rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.02em;
}
.milestone__year {
  display: block;
  margin-top: .3rem;
  font-size: .85rem;
  font-weight: 600;
  letter-spacing: .12em;
  color: var(--text-faint);
}

/* rail: a full-height track with the node pinned to the top */
.milestone__rail {
  position: relative;
  justify-self: center;
  width: 100%;
  align-self: stretch;
}
.milestone__rail::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 2px;
  translate: -50% 0;
  background: rgba(255, 255, 255, .1);
}
/* nothing to draw past the last node */
.milestone:last-child .milestone__rail::before { bottom: auto; height: 28px; }

.milestone__node {
  position: absolute;
  left: 50%;
  top: 2px;
  translate: -50% 0;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--bg);
  border: 3px solid rgba(255, 255, 255, .22);
  color: var(--bg);
}
.milestone__node svg { width: 12px; height: 12px; }

/* the milestone under way: accent month, filled node, one soft pulsing ring */
.milestone--now .milestone__mon { color: var(--accent); }
.milestone--now .milestone__node {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 5px rgba(255, 138, 76, .14);
}
.milestone--now .milestone__node::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  animation: node-pulse 2.6s ease-out infinite;
}
@keyframes node-pulse {
  0%   { transform: scale(1);   opacity: .65; }
  70%  { transform: scale(1.9); opacity: 0; }
  100% { transform: scale(1.9); opacity: 0; }
}

/* a shipped milestone: green node with a tick, no pulse — it is not asking for
   attention the way the one under way is. The tick inherits colour from the
   node, which already paints currentColor as the page background. */
.milestone--done .milestone__mon { color: var(--ok); }
.milestone--done .milestone__node {
  background: var(--ok);
  border-color: var(--ok);
}
.milestone--done .milestone__pill {
  color: var(--ok);
  background: rgba(74, 222, 128, .16);
}

.milestone__body { padding-bottom: 2.75rem; }
.milestone:last-child .milestone__body { padding-bottom: 0; }

.milestone__title {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
  font-size: 1.3rem;
  font-weight: 700;
}

.milestone__pill {
  padding: .2rem .6rem;
  border-radius: 999px;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
  background: rgba(255, 138, 76, .16);
}

.milestone__text {
  margin-top: .35rem;
  max-width: 60ch;
  color: var(--text-muted);
  font-size: .975rem;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: 1rem;
}
.tags li {
  padding: .3rem .75rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-muted);
  font-size: .84rem;
}

/* ==========================================================================
   Bottom bar
   ========================================================================== */

.bottombar {
  border-top: 1px solid var(--border);
  background: var(--bg-alt);
}

/* A short footer: mark and tagline on one side, the one way to get in touch on
   the other. Centred, the same three lines stacked into a tall column. */
.bottombar__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: clamp(2.5rem, 5vw, 3.5rem) var(--gutter);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.75rem 3rem;
}

.bottombar__inner .brand__mark { width: 36px; height: 36px; }
.bottombar__inner .brand__name { font-size: 1.15rem; }

.bottombar__tagline {
  margin-top: .6rem;
  color: var(--text-muted);
  font-size: .95rem;
}

.bottombar__contact {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .3rem;
  text-align: right;
}

.bottombar__collab {
  font-size: .85rem;
  letter-spacing: .04em;
  color: var(--text-faint);
}

.bottombar__mail {
  position: relative;
  color: var(--accent);
  font-weight: 600;
  font-size: 1.05rem;
}
/* underline grows on hover instead of sitting there permanently */
.bottombar__mail::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: var(--accent-grad);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .25s var(--ease-out);
}
.bottombar__mail:hover::after { transform: scaleX(1); }

/* below the fold of a phone the two halves stack and both align left */
@media (max-width: 640px) {
  .bottombar__contact { align-items: flex-start; text-align: left; }
}

.bottombar__base {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: 1.5rem var(--gutter) 2.5rem;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: center;
  color: var(--text-faint);
  font-size: .875rem;
}

/* ==========================================================================
   Documentation  (docs/**)

   Two bars stack here: the fixed .topbar and .docsbar under it. The second one
   is opaque rather than a second blurred layer — two backdrop-filter surfaces
   overlapping seam visibly where the upper one samples the lower.
   ========================================================================== */

.docsbar {
  position: sticky;
  top: var(--topbar-h);
  z-index: 90;                    /* under .topbar's 100: the mobile panel covers it */
  margin-top: var(--topbar-h);    /* the fixed bar occupies no flow */
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.docsbar__inner {
  max-width: var(--maxw);
  height: var(--docsbar-h);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  gap: 1.75rem;
  overflow-x: auto;               /* a third tab must never widen the page */
  scrollbar-width: none;
}
.docsbar__inner::-webkit-scrollbar { display: none; }

.docsbar__tab {
  position: relative;
  padding-block: .35rem;
  font-size: .95rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--text-muted);
  transition: color .2s var(--ease-out);
}
.docsbar__tab::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent-grad);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .25s var(--ease-out);
}
.docsbar__tab:hover { color: var(--text); }
.docsbar__tab:hover::after,
.docsbar__tab[aria-current]::after { transform: scaleX(1); }
.docsbar__tab[aria-current] { color: var(--text); }

/* The docs are written against a version of the engine, so the bar says which
   one. One channel for now; the slot is here for when there are two. */
.docsbar__version {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .3rem .7rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .04em;
  white-space: nowrap;
  color: var(--text-faint);
}
.docsbar__version b { color: var(--text); }

/* Anchors clear BOTH bars here. The class sits on <html> in the markup of every
   docs page — the inline head script adds .js next to it without clobbering. */
html.docs,
html.showcase { scroll-padding-top: calc(var(--topbar-h) + var(--docsbar-h) + 16px); }

/* --------------------------------------------------------------------------
   Page shell
   -------------------------------------------------------------------------- */

.docs__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: 2.75rem var(--gutter) 5rem;   /* the docsbar already paid --topbar-h */
}
/* Three columns need more room than the landing grid: at --maxw the reading
   column would drop under 600px, and a signature does not fold gracefully. */
.docs__inner--wide { max-width: 1360px; }

/* Unlike .archive__cols this is NOT gated on .js: the rail is written by hand
   in the markup, so both columns are correct with scripting off. */
.docs__cols {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.5rem);
  grid-template-columns: 15rem minmax(0, 1fr);  /* minmax(0,…) or a wide <pre> blows the track out */
  align-items: start;
}

.docs__side {
  position: sticky;
  top: calc(var(--topbar-h) + var(--docsbar-h) + 1.5rem);
  max-height: calc(100vh - var(--topbar-h) - var(--docsbar-h) - 3rem);
  max-height: calc(100dvh - var(--topbar-h) - var(--docsbar-h) - 3rem);
  overflow-y: auto;
}

.docs__main { min-width: 0; }   /* same reason as minmax(0,…) on the track */

.docs-nav ul { display: flex; flex-direction: column; gap: .25rem; }

.docs-nav__group {
  margin-bottom: .5rem;
  padding-inline: .85rem;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.docs-nav__link {
  display: block;
  padding: .5rem .85rem;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--text-muted);
  font-size: .95rem;
  transition: border-color .2s var(--ease-out), background-color .2s var(--ease-out), color .2s var(--ease-out);
}
.docs-nav__link:hover {
  border-color: var(--border);
  background: var(--surface);
  color: var(--text);
}
/* border as well as fill, for the same reason as .archive__link[aria-current] */
.docs-nav__link[aria-current] {
  border-color: var(--accent);
  background: var(--accent);
  color: #1a1005;
  font-weight: 700;
}

/* On this page. Only earns its place on the class reference, and only where
   there is room for a third column without squeezing the measure. */
.docs__toc { display: none; }

.docs__toc-title {
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.docs__toc ul {
  margin-top: .75rem;
  display: grid;
  gap: .45rem;
  font-size: .875rem;
}
.docs__toc a {
  display: block;
  padding-left: .7rem;
  border-left: 2px solid var(--border);
  color: var(--text-muted);
  transition: color .2s var(--ease-out), border-color .2s var(--ease-out);
}
.docs__toc a:hover { color: var(--text); border-left-color: var(--accent); }

@media (min-width: 1400px) {
  .docs__cols--api { grid-template-columns: 15rem minmax(0, 1fr) 13rem; }
  .docs__cols--api .docs__toc {
    display: block;
    position: sticky;
    top: calc(var(--topbar-h) + var(--docsbar-h) + 1.5rem);
  }
}

.docs__title {
  font-size: clamp(1.9rem, 4vw, 2.6rem);
  font-weight: 800;
}
.docs__title code { font-family: var(--mono); font-size: .9em; }

.docs__lead {
  margin-top: 1rem;
  color: var(--text-muted);
  font-size: clamp(1.05rem, 1.6vw, 1.2rem);
  line-height: 1.5;
}

/* --------------------------------------------------------------------------
   Code

   Nothing on the site used monospace before this section, so the whole layer
   is new. System stack only: the site runs from file:// and ships no fonts.
   -------------------------------------------------------------------------- */

code, kbd, samp, pre { font-family: var(--mono); }

/* em, not rem: inline code follows whatever it sits inside, so `enabled` in a
   heading and in a paragraph both read as code at that size */
:not(pre) > code {
  padding: .1em .35em;
  border-radius: 6px;
  font-size: .9375em;
  background: var(--code-bg);
  border: 1px solid rgba(255, 255, 255, .09);
  color: var(--text);
}

.code {
  margin: 0;
  padding: 1rem 1.15rem;
  border-radius: var(--radius);
  background: var(--code-bg);
  border: 1px solid var(--border);
  font-size: .875rem;
  line-height: 1.6;
  tab-size: 4;
  overflow-x: auto;
  white-space: pre;
}
/* focusable for the same reason .table-scroll is: an overlay scrollbar gives no
   hint that the block moves, and a keyboard has no other way to reach the rest */
.code:focus-visible { outline-offset: -2px; }

/* Highlighting is hand-placed spans in the markup — there is no highlighter in
   main.js, and there must not be: applyLang replaces a node's whole textContent,
   so a listing carries no data-i18n and its comments stay English in both
   languages. Muted palette: keywords and types only. */
.tok-kw   { color: var(--accent-3-hi); }
.tok-type { color: var(--accent); }
.tok-cmt  { color: var(--text-faint); font-style: italic; }

/* Two listings of the same thing, C# on the left and Beef on the right, so a
   line can be compared with the line beside it. The columns stack below 900px,
   where each would be too narrow for a line of code; C# stays first either way. */
.code-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .75rem;
}
.code-pair__col { min-width: 0; }
.article__body .code-pair__lang {
  margin-bottom: .4rem;
  font-family: var(--mono);
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.code-pair .code { font-size: .8125rem; height: calc(100% - 1.6rem); }
@media (max-width: 900px) {
  .code-pair { grid-template-columns: minmax(0, 1fr); }
  .code-pair .code { height: auto; }
}

/* --------------------------------------------------------------------------
   Callouts and diagrams
   -------------------------------------------------------------------------- */

.callout {
  margin-top: 1.25rem;
  padding: .9rem 1.1rem;
  border-left: 2px solid var(--accent);
  border-radius: 0 12px 12px 0;
  background: var(--surface);
  color: var(--text-muted);
  font-size: .95rem;
}
.callout b { color: var(--text); }
.callout--warn { border-left-color: #f0b35a; }
.callout--ok { border-left-color: var(--ok); }

.diagram {
  margin-top: 1.25rem;
  padding: 1rem 1.15rem;
  border-radius: var(--radius);
  background: var(--code-bg);
  border: 1px solid var(--border);
  font-family: var(--mono);
  font-size: .8125rem;
  line-height: 1.55;
  white-space: pre;
  overflow-x: auto;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Editor map  (docs/index.html)

   A screenshot with its panels outlined. Each zone is placed in percent of the
   image (2000x1103), so the outlines follow it at any width; the list under it
   repeats the colour next to each name. The four colours are the page's own
   signal colours, one per panel, set once on the modifier and read by both.
   The badge sits bottom-right: top-left is where every panel keeps its tabs.
   -------------------------------------------------------------------------- */

.editor-map,
.editor-zones__item { --zone: var(--accent); }
.editor-map { position: relative; margin: 1.25rem 0 0; }
.editor-map .shot { width: 100%; border-radius: 10px; }
.editor-map img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid var(--border);
}

.editor-map__zone {
  position: absolute;
  border: 2px solid var(--zone);
  border-radius: 6px;
  background: color-mix(in srgb, var(--zone) 5%, transparent);
  pointer-events: none;
}
.editor-map__zone b {
  position: absolute;
  right: 6px;
  bottom: 6px;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--zone);
  color: var(--bg);
  font-size: .8rem;
  font-weight: 700;
}

.editor-map__zone--hierarchy { left: .4%;  top: 4.6%;  width: 18.3%; height: 95%;   }
.editor-map__zone--scene     { left: 19.1%; top: 4.6%;  width: 66.5%; height: 72%;   }
.editor-map__zone--project   { left: 19.1%; top: 77.2%; width: 66.5%; height: 22.4%; }
.editor-map__zone--inspector { left: 86.1%; top: 4.6%;  width: 13.6%; height: 95%;   }
/* the Game panel shares the Scene panel's slot, it is the next tab over */
.editor-map__zone--game      { left: 19.1%; top: 4.6%;  width: 66.5%; height: 72%;   }
/* Animator and Animation are tabs of the Scene and Project panels, so they take those slots */
.editor-map__zone--animator  { left: 19.1%; top: 4.6%;  width: 66.5%; height: 72%;   }
.editor-map__zone--animation { left: 19.1%; top: 77.2%; width: 66.5%; height: 22.4%; }
.editor-map__zone--controls  { left: 48.2%; top: 1.8%;  width: 3.85%; height: 2.3%;  }
/* too small to hold its badge, so the badge sits just to the right of it */
.editor-map__zone--controls b { right: auto; bottom: auto; left: calc(100% + 6px); top: 50%; transform: translateY(-50%); }

.editor-map__zone--hierarchy, .editor-zones__item--hierarchy { --zone: var(--accent); }
.editor-map__zone--scene,     .editor-zones__item--scene     { --zone: var(--accent-2); }
.editor-map__zone--project,   .editor-zones__item--project   { --zone: var(--ok); }
.editor-map__zone--inspector, .editor-zones__item--inspector { --zone: var(--accent-3-hi); }
.editor-map__zone--game,     .editor-zones__item--game     { --zone: var(--accent); }
.editor-map__zone--controls, .editor-zones__item--controls { --zone: var(--accent-2); }
.editor-map__zone--animator,  .editor-zones__item--animator  { --zone: var(--accent); }
.editor-map__zone--animation, .editor-zones__item--animation { --zone: var(--accent-2); }
.editor-map__zone--game { background: none; }

.editor-zones {
  display: grid;
  gap: 1rem;
  margin-top: 1.5rem;
  padding: 0;
  list-style: none;
  counter-reset: zone;
}
.article__body .editor-zones__item {
  position: relative;
  margin: 0;
  padding: .9rem 1.1rem .9rem 3.25rem;
  border-left: 2px solid var(--zone);
  border-radius: 0 12px 12px 0;
  background: var(--surface);
  counter-increment: zone;
}
.article__body .editor-zones__item::before {
  content: counter(zone);
  position: absolute;
  top: .95rem;
  left: 1.1rem;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--zone);
  color: var(--bg);
  font-size: .8rem;
  font-weight: 700;
}
.article__body .editor-zones__item h3 { margin: 0; font-size: 1.05rem; }
.article__body .editor-zones__item p  { margin: .35rem 0 0; color: var(--text-muted); }

/* at phone width the badges would cover a third of a narrow panel */
@media (max-width: 640px) {
  .editor-map__zone b { width: 1.1rem; height: 1.1rem; right: 3px; bottom: 3px; font-size: .65rem; }
  .editor-map__zone--controls b { right: auto; bottom: auto; left: calc(100% + 3px); }
}

/* --------------------------------------------------------------------------
   Showcase  (showcase/**)

   Shares the docs' second bar (.docsbar) and its title and lead. The grid fills
   as many 180px columns as the row holds: five on a desktop, two on a phone,
   with no breakpoint to maintain. A tile is the icon, the name, a link to the
   article about the game, then one line per place it can be played.
   -------------------------------------------------------------------------- */

.showcase__inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 2.75rem var(--gutter) 5rem;   /* the docsbar already paid --topbar-h */
}

.game-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 2rem 1.5rem;
  margin: 2.5rem 0 0;
  padding: 0;
  list-style: none;
}
/* two columns still fit a 360px phone; 180px would have left it one */
@media (max-width: 520px) {
  .game-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.75rem 1rem; }
}

.game-tile { min-width: 0; }
.game-tile__icon {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  border-radius: 22%;
  border: 1px solid var(--border);
}
.game-tile__title {
  margin-top: .9rem;
  font-size: 1.05rem;
  font-weight: 700;
}
.game-tile__about {
  display: inline-block;
  margin-top: .2rem;
  font-size: .9rem;
  color: var(--accent);
}
.game-tile__about:hover { text-decoration: underline; text-underline-offset: 3px; }

.game-tile__where {
  display: grid;
  gap: .5rem;
  margin: .85rem 0 0;
  padding: .85rem 0 0;
  border-top: 1px solid var(--border);
  list-style: none;
}
.game-tile__place {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .25rem .5rem;
  font-size: .875rem;
  color: var(--text);
}
.game-tile__place img { flex: none; width: 20px; height: 20px; border-radius: 5px; }
/* the play mark matches the platform icons beside it: an app-icon tile with the
   same 5px corner. Its gradient is the site's own, toned down so it sits level
   with the Yandex Games mark instead of outshouting it. Colours are hard-coded
   in the SVG for the same reason as in assets/ — the stops are not tokens. */
.game-tile__play { flex: none; }
a.game-tile__place:hover span { text-decoration: underline; text-underline-offset: 3px; }
.game-tile__place--pending { color: var(--text-muted); }
.game-tile__place--pending img { opacity: .6; }

/* --------------------------------------------------------------------------
   API reference  (docs/api/**)
   -------------------------------------------------------------------------- */

.api-meta {
  margin-top: 1.75rem;
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: .45rem 1.25rem;
  font-size: .9375rem;
}
.api-meta dt {
  padding-top: .15em;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.api-meta dd { margin: 0; color: var(--text-muted); }

.api-index {
  margin-top: 2.25rem;
  padding: 1.15rem 1.35rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.api-index__group {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.api-index__group + ul { margin-top: .6rem; }
.api-index ul + .api-index__group { margin-top: 1.25rem; }
.api-index li { display: flex; flex-wrap: wrap; gap: .1rem .75rem; font-size: .9375rem; }
.api-index li + li { margin-top: .35rem; }
.api-index a { color: var(--accent); }
.api-index a:hover { text-decoration: underline; }
.api-index__brief { color: var(--text-faint); }

/* On a class page the index carries names and nothing else — two dozen of them
   down a column is a screen of scrolling before the reference starts, so they
   run as a wrapping row instead. The name is the link, with no <code> inside:
   the chip is already the box that inline code would have drawn. */
.api-index--chips ul { display: flex; flex-wrap: wrap; gap: .4rem; }
.api-index--chips li { display: block; }
.api-index--chips li + li { margin-top: 0; }
.api-index--chips a {
  display: inline-block;
  padding: .25rem .65rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: .8125rem;
  color: var(--text-muted);
  transition: border-color .2s var(--ease-out), color .2s var(--ease-out);
}
.api-index--chips a:hover { border-color: var(--accent); color: var(--text); text-decoration: none; }

.api-group {
  margin-top: 2.75rem;
  margin-bottom: .9rem;
  display: flex;
  align-items: baseline;
  gap: .6rem;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.api-group__count { font-weight: 400; letter-spacing: 0; }

/* A member reads top to bottom: name, rule, declaration, rule, everything else.
   The rules run the full width of the card, so the padding lives on the rows
   themselves — on a shared head it would clip the line short of both edges. */
.api-member {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}
.api-member + .api-member { margin-top: 1rem; }

.api-member__name {
  padding: .85rem 1.25rem;
  font-family: var(--mono);
  font-size: 1.0625rem;
  font-weight: 700;
}

.api-member__sig {
  padding: .7rem 1.25rem;
  border-top: 1px solid var(--border);
  font-family: var(--mono);
  font-size: .875rem;
  line-height: 1.7;
  color: var(--text-muted);
  overflow-x: auto;
}
/* overloads are one declaration in several forms, not several sections: no rule
   between them, and no doubled padding either — they read as consecutive lines */
.api-member__sig + .api-member__sig { border-top: 0; padding-top: 0; }
/* the declaration is already quiet; colouring it would fight the name above it */
.api-member__sig .tok-kw,
.api-member__sig .tok-type { color: inherit; }

.api-member__body {
  padding: 1.25rem;
  border-top: 1px solid var(--border);
}

.api-member__desc { color: var(--text-muted); font-size: 1.0625rem; }
.api-member__desc + .api-member__desc { margin-top: .8rem; }

.api-member__sub {
  margin-top: 1.35rem;
  margin-bottom: .55rem;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.api-params {
  display: grid;
  grid-template-columns: max-content max-content minmax(0, 1fr);
  gap: .5rem 1.1rem;
  align-items: baseline;
  font-size: .9375rem;
}
.api-params__name { font-family: var(--mono); }
.api-params__type { font-family: var(--mono); color: var(--accent); }
.api-params__desc { color: var(--text-muted); }

.api-returns { display: flex; align-items: baseline; gap: .75rem; font-size: .9375rem; }
.api-returns__type { font-family: var(--mono); color: var(--accent); }
.api-returns__desc { color: var(--text-muted); }

/* A plain <details>: no JS, no stored state, works from file://. */
.api-example {
  margin-top: 1.4rem;
  border-top: 1px solid rgba(255, 255, 255, .07);
}
.api-example > summary {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding-top: 1rem;
  list-style: none;
  cursor: pointer;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-faint);
  transition: color .2s var(--ease-out);
}
.api-example > summary::-webkit-details-marker { display: none; }
.api-example > summary::before {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(-45deg);
  transition: transform .2s var(--ease-out);
}
.api-example[open] > summary::before { transform: rotate(45deg); }
.api-example > summary:hover { color: var(--text); }
.api-example .code { margin-top: .9rem; }

/* ==========================================================================
   Scroll reveal
   ========================================================================== */

/* Everything here hangs off .js, which index.html sets in <head> before the
   first paint. Only JS can take an element out of the hidden state, so without
   it these rules must not apply at all — otherwise a blocked or broken main.js
   leaves the page as a hero, a footer, and five screens of nothing. */

.js [data-reveal] {
  opacity: 0;
  transition:
    opacity .6s var(--ease-out),
    transform var(--reveal-dur) var(--ease-out);
  transition-delay: calc(var(--i, 0) * .1s);
  will-change: transform, opacity;
}

/* default: rise from below (text columns, section headings) */
.js [data-reveal] { transform: translateY(28px); }

/* media slides in from the side the section declares; text drifts in from the
   opposite side, more subtly.

   :not(.is-visible) is load-bearing, not decoration. A bare
   `.feature[data-side] .feature__media[data-reveal]` outweighs
   `[data-reveal].is-visible`, so the offset survived the reveal and every
   image stayed parked 70px off its column — far enough that on a tablet the
   card hung past the left edge and was clipped. Scoping the offset to the
   hidden state takes the two rules out of each other's way. */
.js .feature[data-side="left"]  .feature__media[data-reveal]:not(.is-visible) { transform: translateX(-70px); }
.js .feature[data-side="right"] .feature__media[data-reveal]:not(.is-visible) { transform: translateX(70px); }

.js .feature[data-side="left"]  .feature__body[data-reveal]:not(.is-visible) { transform: translateX(40px); }
.js .feature[data-side="right"] .feature__body[data-reveal]:not(.is-visible) { transform: translateX(-40px); }

.js [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
   Responsive
   ========================================================================== */

/* The nav collapses earlier than the rest of the layout: seven items in
   Russian ("Дорожная карта", "Сообщество") need ~1055px, so keeping the
   inline menu down to 900px would let it collide with the logo.
   Anything below is layout-only and stays on the 900px breakpoint. */
@media (max-width: 1150px) {
  .burger { display: flex; }

  .nav {
    position: fixed;
    inset: var(--topbar-h) 0 auto 0;
    margin: 0;
    padding: 1.5rem var(--gutter) 2rem;
    flex-direction: column;
    align-items: flex-start;
    gap: 1.5rem;
    /* The panel is ~420px tall and fixed. On a phone in landscape (844x390)
       that put Roadmap, Contact and the language switch below the fold with
       no way to reach them — the panel does not move with the page. */
    max-height: calc(100vh - var(--topbar-h));
    max-height: calc(100dvh - var(--topbar-h));
    overflow-y: auto;
    overscroll-behavior: contain;
    background: rgba(11, 11, 22, .96);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--border);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: opacity .25s var(--ease-out), transform .25s var(--ease-out), visibility .25s;
  }
  .nav.is-open {
    transform: translateY(0);
    opacity: 1;
    visibility: visible;
  }

  /* 21px of ink per row was under the 24px minimum touch target. The padding
     absorbs most of the old gap, so the rhythm between rows barely moves. */
  .nav__list {
    flex-direction: column;
    align-items: flex-start;
    gap: .3rem;
    font-size: 1.1rem;
  }
  .nav__list a { display: block; padding-block: .5rem; }
  .nav__list a::after { bottom: .35rem; }

  /* в панели это не правый угол, а последняя строка */
  .topbar__end {
    margin-left: 0;
    width: 100%;
    gap: 1.25rem;
  }
  .docs-btn { font-size: 1.1rem; font-weight: 600; }
}

@media (max-width: 900px) {
  :root { --topbar-h: 64px; }

  /* the rail turns into a row of chips above the posts */
  .js .archive__cols { grid-template-columns: minmax(0, 1fr); }
  .archive__nav ul { flex-direction: row; flex-wrap: wrap; align-items: center; gap: .5rem; }
  .archive__group { margin-top: 0; display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; }
  .archive__year { margin-bottom: 0; padding-inline: 0; }

  /* the docs rail does the same, and stops sticking: a chip row that followed
     the scroll would eat the top of every screen */
  .docs__cols { grid-template-columns: minmax(0, 1fr); }
  .docs__side { position: static; max-height: none; overflow: visible; }
  .docs-nav ul { flex-direction: row; flex-wrap: wrap; align-items: center; gap: .5rem; }
  .docs-nav__group { margin-bottom: .5rem; padding-inline: 0; }

  /* name, type and description stack; three columns of max-content here would
     leave the description a few characters wide */
  .api-params { grid-template-columns: minmax(0, 1fr); gap: .15rem; }
  .api-params__desc { margin-bottom: .65rem; }

  .hero__inner {
    grid-template-columns: 1fr;
    padding-bottom: 5rem;
    gap: 2.5rem;
  }
  /* Stacked layout: the tagline no longer has a brand row to match, so it
     wraps normally and the display size drops to fit a single column. */
  .hero { --hero-display: clamp(2.75rem, 12vw, 6rem); }
  .hero__right { justify-self: start; align-items: flex-start; }

  .hero__shot { width: 100%; }
  .hero__tagline { white-space: normal; max-width: 30ch; font-size: clamp(1.05rem, 3.4vw, 1.5rem); }

  .feature__inner { grid-template-columns: 1fr; }
  .feature[data-side="right"] .feature__media,
  .feature[data-side="right"] .feature__body { order: initial; }

  /* rail moves to the left edge; the quarter sits above the card */
  .milestone {
    grid-template-columns: 34px minmax(0, 1fr);
    row-gap: .35rem;
  }
  .milestone__rail { grid-column: 1; grid-row: 1 / span 2; }
  .milestone__when { grid-column: 2; grid-row: 1; text-align: left; padding-top: 0; }
  .milestone__mon { display: inline; font-size: 1.4rem; }
  .milestone__year { display: inline; margin-left: .4rem; }
  .milestone__body { grid-column: 2; grid-row: 2; }
}

@media (max-width: 560px) {
  .hero__logo { width: 56px; }

  .js .feature[data-side="left"]  .feature__media[data-reveal]:not(.is-visible),
  .js .feature[data-side="right"] .feature__media[data-reveal]:not(.is-visible) { transform: translateY(28px); }
  .js .feature[data-side="left"]  .feature__body[data-reveal]:not(.is-visible),
  .js .feature[data-side="right"] .feature__body[data-reveal]:not(.is-visible) { transform: translateY(28px); }

  /* Buys back ~140px of table width, which is the difference between the Demo
     column being one flick away and being three. */
  .cmp { min-width: 640px; }
  /* five value columns cannot shrink as far — below this the value and its bar
     stop fitting on one line and every row doubles in height */
  .cmp--playable { min-width: 860px; }
  .cmp--flappy { min-width: 800px; }
  .cmp--rank { min-width: 0; }
  .cmp th,
  .cmp td { padding: .5rem .65rem; }
  .cmp__value { width: 4.6em; font-size: .9rem; }
  .cmp__bar { width: calc(100% - 5.2em); }
  .cmp--flappy .cmp__value { width: 5.3em; }
  .cmp--flappy .cmp__bar { width: calc(100% - 5.9em); }
  /* the metric goes above its ranking, so the bars keep the full width */
  .cmp--rank thead { display: none; }
  .cmp--rank tr,
  .cmp--rank th,
  .cmp--rank td { display: block; width: auto; }
  .cmp--rank .cmp__metric { padding: .8rem .65rem 0; border-bottom: 0; }
  .cmp__rank { grid-template-columns: 6.6rem 1fr 4.2rem; gap: .3rem .6rem; }
  .cmp__name,
  .cmp__rank .cmp__value { font-size: .8125rem; }
}

/* ==========================================================================
   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;
    transition-delay: 0ms !important;
  }

  .hero__bg { background-position: 30% 50%; }

  [data-reveal],
  .checks li {
    opacity: 1 !important;
    transform: none !important;
  }

  .hero__scroll { display: none; }
}

/* ==========================================================================
   Forced colours

   Windows High Contrast replaces every background, which erases any text
   painted with background-clip. All three eyebrow labels vanished; here they
   fall back to plain system colours.
   ========================================================================== */

@media (forced-colors: active) {
  .feature__eyebrow,
  .section-eyebrow {
    background: none;
    color: CanvasText;
    -webkit-text-fill-color: CanvasText;
  }

  /* the tick is drawn as a border over a gradient disc — both get replaced */
  .checks li::before { background: Highlight; }
  .checks li::after  { border-color: Canvas; }

  .milestone--now .milestone__node,
  .milestone--done .milestone__node {
    background: Highlight;
    border-color: Highlight;
  }
  .cmp__bar::before { background: Highlight; }
  .archive__link[aria-current] { background: Highlight; color: HighlightText; border-color: Highlight; }
  .docs-nav__link[aria-current] { background: Highlight; color: HighlightText; border-color: Highlight; }
  /* the tab's only signal is a gradient rule, and a gradient is replaced wholesale */
  .docsbar__tab[aria-current]::after { background: Highlight; }
  .cmp__best .cmp__bar::before { background: Highlight; }
  .cmp__worst .cmp__bar::before { background: Mark; }
  /* the outlines and badges carry the mapping; keep them visible, drop the tint */
  .editor-map__zone { border-color: Highlight; background: none; }
  .editor-map__zone b,
  .article__body .editor-zones__item::before { background: Highlight; color: HighlightText; }
}
