/*
 * The site's stylesheet. One file, loaded on every page — the whole site is
 * smaller than a single photograph, and one cached file beats seven conditional
 * ones. The single exception is `admin.css`, which the editor's page adds on top
 * and which nobody else downloads; it says so in its own first line.
 *
 * Order is the load-bearing part and runs from general to specific: a rule may
 * rely on everything declared above it and must not fight anything below.
 *
 *   1. tokens      colours, faces, the width of the page — the only place a
 *                  literal value is allowed to appear
 *   2. base        element defaults: the styles unclassed markup gets for free
 *   3. layout      the boxes a page is built out of, knowing nothing about it
 *   4. components  everything that appears on more than one page
 *   5. pages       everything that appears on exactly one page
 *
 * Where a new rule goes follows from that list: it starts in the page section,
 * and moves up to components the moment a second page needs it. A component may
 * use a token and a layout class; it may never reach into another component.
 *
 * `.prose` — вёрстка статьи, набранной в Markdown (ADR 0027), — по этому
 * порядку расходится: у разметки без классов нет своего места, поэтому её
 * селекторы дописаны к тем правилам, чьи значения она повторяет (typography,
 * tables, notes), а то, чего в вёрстке до сих пор не было, собрано в отдельном
 * компоненте ниже. Дочерний комбинатор в них не украшение: `.prose p` накрыл бы
 * и абзац внутри цитаты.
 */


/* ==========================================================================
   tokens
   ========================================================================== */

/*
 * Design tokens taken from the mockups in tmp/export2. Every value that repeats
 * across more than one page lives here; a page stylesheet may only compose them.
 * Colours keep the mockup's exact hexes — the palette is the site's identity,
 * not a placeholder.
 */
:root {
    color-scheme: dark;

    /* Surfaces, dark to light. --surface-veil is the barely-there fill the
       mockups use inside bordered frames instead of a solid panel colour. */
    --bg: #000205;
    --bg-panel: #03050a;
    --bg-input: #05080f;
    --bg-grid: #04060c;
    --surface-veil: rgba(255, 255, 255, 0.012);

    /* Rules. Three weights: hairline between sections, frame around blocks,
       and the one visible enough to read as a control's edge. */
    --line-dim: #10151f;
    --line: #1a2231;
    --line-strong: #2b3344;

    /* Text, brightest to faintest. Body copy is --text-2, secondary --text-4. */
    --text: #e6e9f0;
    --text-max: #ffffff;
    --text-1: #d3d9e6;
    --text-2: #b9c2d3;
    --text-3: #98a3b8;
    --text-4: #8c97ad;
    --text-dim: #5d687e;
    --text-faint: #4c5670;
    --text-ghost: #39414f;

    /* Accents carry meaning across the site: blue — physics and rules, green —
       what has been added, amber — time and cost, violet — what the world turns
       up (a find, a generated system, an interface change), danger — a refusal:
       a form error or the engine's own no. */
    --accent: #7fb2ff;
    --accent-soft: #cfe0ff;
    --accent-green: #8ee08a;
    --accent-amber: #ffd166;
    --accent-violet: #a78bfa;
    --accent-danger: #ff9b8a;

    --font-sans: 'Space Grotesk', system-ui, sans-serif;
    --font-mono: 'IBM Plex Mono', ui-monospace, monospace;

    /* Page frame: the mockups hold one width and one horizontal padding
       throughout, so both are tokens rather than per-section values. */
    --page-max: 1440px;
    --page-pad: clamp(20px, 4vw, 40px);
    --radius-pill: 100px;
    --radius-field: 8px;
}

/*
 * Breakpoints are not tokens and cannot be: a media query does not read a custom
 * property. So the widths the file is allowed to name are listed here instead,
 * each with the thing that stops fitting at it:
 *
 *   860px  the header stops fitting on one line and goes to two rows
 *   720px  two columns stop fitting side by side: an article and its rail, and
 *          the sign-in page's two panes (2 x 360px, which is where the number
 *          comes from)
 *   520px  a phone in one hand — the vertical rhythm is taken from the width
 *          rather than from the height
 *
 * A fourth width needs an argument, not a number: everything else here is fluid
 * (clamp, auto-fit, flex-wrap) and holds without being told where to break.
 */

/* ==========================================================================
   base
   ========================================================================== */

/*
 * Reset and element defaults. Everything here is unclassed markup: the styles a
 * page gets for free before a single component is applied.
 */
* {
    box-sizing: border-box;
}

html {
    background: var(--bg);
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-sans);
    font-size: 16px;
    line-height: 1.6;
    overflow-x: hidden;
    /* A word longer than the column it stands in breaks rather than widening
       the page: an address in the cabinet, a term in an article. */
    overflow-wrap: break-word;
    -webkit-font-smoothing: antialiased;
}

a {
    color: var(--accent);
    text-decoration: none;
}

a:hover {
    color: var(--accent-soft);
}

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

h1,
h2,
h3 {
    font-weight: 500;
    letter-spacing: -0.02em;
}

table {
    border-collapse: collapse;
}

summary::-webkit-details-marker {
    display: none;
}

input::placeholder {
    color: #4a5468;
}

/* Components set their own display, which would beat the browser's default rule
   for [hidden]. Everything the scripts hide (a tab panel, a filtered-out line)
   relies on this. */
[hidden] {
    display: none !important;
}

/* The site never animates anything load-bearing, so switching motion off costs
   the reader nothing. */
@media (prefers-reduced-motion: reduce) {
    * {
        animation: none !important;
        transition: none !important;
    }
}

/* ==========================================================================
   layout: the page frame
   ========================================================================== */

/*
 * Page frame: the boxes every page is built out of before any component is
 * placed inside them. Nothing here knows what page it is on.
 */

/* The outermost wrapper. Sections bleed to the viewport edge, so the shell only
   guards against horizontal overflow. */
.page {
    max-width: 100%;
    overflow-x: hidden;
}

/* Vertically centred pages (auth, 404) need the shell to fill the viewport —
   the visible one: on a phone 100vh counts in the browser's own chrome, and the
   last line of a centred page ends up under the toolbar. The vh line stays
   underneath for a browser that does not know svh. */
.page--full {
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
}

.container {
    max-width: var(--page-max);
    margin: 0 auto;
    width: 100%;
}

/* Reading widths. The narrower the content, the fewer characters per line: a
   full-width grid gets --page-max, a column of prose gets --text. */
.container--wide {
    max-width: 1180px;
}

.container--text {
    max-width: 1100px;
}

.container--centered {
    max-width: 1000px;
    text-align: center;
}

/* A band of content with a hairline above it. Sections carry the page's whole
   vertical rhythm; nothing inside them sets its own top padding. */
.section {
    padding: clamp(90px, 14vh, 170px) var(--page-pad);
    border-top: 1px solid var(--line-dim);
}

.section--tight {
    padding: clamp(80px, 12vh, 150px) var(--page-pad);
}

.section--first {
    border-top: 0;
}

/* Text beside a picture, either order. Below ~700px the two stack.

   `min(320px, 100%)` and not a bare 320px, here and in every auto-fit grid on
   the site: a track's minimum is a hard floor, so on a screen narrower than the
   minimum the column keeps its width and the row hangs off the right edge —
   where `.page` clips it, and the last word of every line with it. Wrapped in
   min() the floor gives way to the screen, and the column simply gets narrow. */
.split {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
    gap: clamp(32px, 5vw, 80px);
    align-items: center;
}

.split__col {
    min-width: 0;
}

/* On a narrow screen the picture must not come first, whichever side it is on
   in the wide layout — the text is the thing being read. */
.split__col--media-first {
    order: 1;
}

.split__col--text-last {
    order: 2;
}

/* An article with a rail beside it (about, changelog). The rail drops
   under the article when there is no room for both — and the account page uses
   the same box with no rail at all, for the vertical rhythm around a column. */
.reading {
    display: flex;
    flex-wrap: wrap;
    gap: clamp(28px, 4vw, 64px);
    align-items: flex-start;
    padding-bottom: 96px;
}

/* flex-grow 999 keeps the article greedy: the rail takes its declared width and
   the article takes everything left. */
.reading__main {
    flex: 999 1 340px;
    min-width: 0;
    max-width: 860px;
}

.reading__main--wide {
    max-width: 820px;
}

/* The band under the header that carries the kicker, the h1 and the lead. */
.page-head {
    padding: clamp(56px, 10vh, 120px) var(--page-pad) clamp(32px, 5vh, 56px);
    border-bottom: 1px solid var(--line-dim);
}

.page-head--compact {
    padding: clamp(48px, 8vh, 88px) var(--page-pad) clamp(28px, 4vh, 44px);
}

/* Head with an action pinned to its right edge (account: "Открыть игру"). */
.page-head__row {
    display: flex;
    flex-wrap: wrap;
    gap: 28px;
    align-items: flex-end;
    justify-content: space-between;
}

/* The text block ends where its last line ends: `.lead` carries a 40px bottom
   margin for the pages that continue underneath, and flex-end lines up margin
   edges — with it the button stood 40px below the sentence it belongs to. */
.page-head__row .lead {
    margin-bottom: 0;
}

/* Pages whose content starts right under the header carry the horizontal
   padding on the container instead of on a section. */
.container--padded {
    padding: 0 var(--page-pad);
}

/* An article that follows a page head rather than the site header needs air
   above it. */
.reading--spaced {
    padding-top: clamp(32px, 5vh, 56px);
}

/* On a phone the air above a section is taken from the screen's height, and a
   phone's screen is tall: 14vh of 844px is 118px above the band and 118px below
   it, a quarter of the screen spent on the gap between two sections. Below this
   width the same rhythm is set from the width, where there is less of it. */
@media (max-width: 520px) {
    .section {
        padding: clamp(56px, 8vh, 96px) var(--page-pad);
    }

    .section--tight {
        padding: clamp(48px, 7vh, 84px) var(--page-pad);
    }

    .page-head {
        padding: clamp(40px, 6vh, 72px) var(--page-pad) clamp(24px, 4vh, 40px);
    }

    .page-head--compact {
        padding: clamp(36px, 5vh, 60px) var(--page-pad) clamp(20px, 3vh, 32px);
    }

    .reading {
        padding-bottom: 56px;
    }
}

/* ==========================================================================
   component: typography
   ========================================================================== */

/*
 * The type scale. Sizes live here and nowhere else: a page picks a step, it does
 * not invent one. Every step is fluid — the mockups were drawn at 1440 and have
 * to hold from 360 up.
 */

/* Home hero only. The one place the type is the picture. */
.display {
    margin: 0 0 26px;
    font-size: clamp(48px, 8.4vw, 124px);
    line-height: 0.94;
    letter-spacing: -0.035em;
    font-weight: 500;
    max-width: 16ch;
}

/* Page titles, largest to smallest. */
.title-xl {
    margin: 0 0 22px;
    font-size: clamp(36px, 5.4vw, 72px);
    line-height: 1.02;
    letter-spacing: -0.035em;
    font-weight: 500;
    max-width: 16ch;
}

.title-lg {
    margin: 0 0 22px;
    font-size: clamp(32px, 4.6vw, 58px);
    line-height: 1.04;
    letter-spacing: -0.03em;
    font-weight: 500;
}

/* Section headings inside a page. */
.title-md {
    margin: 0 0 28px;
    font-size: clamp(30px, 3.6vw, 52px);
    line-height: 1.06;
    letter-spacing: -0.03em;
    font-weight: 500;
}

.title-sm {
    margin: 0 0 44px;
    font-size: clamp(28px, 3vw, 42px);
    line-height: 1.08;
    letter-spacing: -0.03em;
    font-weight: 500;
}

/* Headings inside an article or a card. */
.heading,
.prose > h2 {
    margin: 0 0 16px;
    font-size: 26px;
    font-weight: 500;
    letter-spacing: -0.02em;
}

.subheading,
.prose > h3 {
    margin: 0 0 12px;
    font-size: 24px;
    font-weight: 500;
    letter-spacing: -0.02em;
}

.subheading--sm {
    font-size: 21px;
}

/* Body copy. --lead follows a title, --text runs inside an article. */
.lead {
    margin: 0 0 40px;
    font-size: 19px;
    line-height: 1.62;
    color: var(--text-2);
    max-width: 60ch;
}

.lead--sm {
    font-size: 18px;
    line-height: 1.6;
    color: var(--text-4);
    max-width: 54ch;
}

.text,
.prose > p {
    margin: 0 0 18px;
    font-size: 17px;
    line-height: 1.72;
    color: var(--text-2);
}

.text--muted {
    color: var(--text-3);
}

.text--last {
    margin-bottom: 28px;
}

.text--sm {
    font-size: 16px;
    line-height: 1.65;
    color: var(--text-3);
}

/* Mono eyebrow above a title or a card. Colour says which of the site's four
   subjects the block belongs to — see the accent tokens. */
.kicker {
    font-family: var(--font-mono);
    font-size: 11.5px;
    letter-spacing: 0.22em;
    color: var(--accent);
    margin-bottom: 22px;
}

.kicker--green {
    color: var(--accent-green);
}

.kicker--amber {
    color: var(--accent-amber);
}

.kicker--violet {
    color: var(--accent-violet);
}

.kicker--muted {
    color: var(--text-4);
}

.kicker--sm {
    font-size: 11px;
    letter-spacing: 0.2em;
    margin-bottom: 18px;
}

.kicker--lg {
    font-size: 13px;
    letter-spacing: 0.2em;
    margin-bottom: 16px;
}

/* The quieter mono label: rail titles, figure captions, footer columns. */
.label {
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.18em;
    color: var(--text-dim);
    margin-bottom: 16px;
}

.label--xs {
    font-size: 10px;
    letter-spacing: 0.16em;
    margin-bottom: 10px;
}

/* A line of mono text that is not a heading: a caption, a footnote, a status. */
.mono-note {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.1em;
    color: var(--text-dim);
}

/* ==========================================================================
   component: site header
   ========================================================================== */

/*
 * The site header. Two variants: the default sits on top of the page with a
 * hairline under it, --overlay floats over a full-bleed hero and fades into it.
 */
.site-header {
    border-bottom: 1px solid var(--line-dim);
    background: var(--bg);
}

.site-header--overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 30;
    border-bottom: 0;
    background: linear-gradient(#000205cc, transparent);
}

/* The cap has to count the padding in: elsewhere the page grid is a padded
   .section with a --page-max .container inside it, and here both sit on one
   box. Capping at --page-max alone would inset the logo by --page-pad past the
   left grid line every page below it stands on. */
.site-header__inner {
    max-width: calc(var(--page-max) + var(--page-pad) * 2);
    margin: 0 auto;
    padding: 22px var(--page-pad);
    display: flex;
    align-items: center;
    gap: 36px;
    flex-wrap: wrap;
}

.site-header__brand {
    color: var(--text);
    font-size: 16px;
    letter-spacing: 0.22em;
    font-weight: 500;
}

.site-header__brand:hover {
    color: var(--text);
}

.site-header__aside {
    display: flex;
    align-items: center;
    gap: 18px;
}

/* margin-left:auto pushes the nav and everything after it to the right edge. */
.site-nav {
    display: flex;
    gap: 26px;
    margin-left: auto;
    flex-wrap: wrap;
}

.site-nav__link {
    font-size: 14px;
    color: var(--text-3);
}

.site-nav__link:hover {
    color: var(--text);
}

.site-nav__link--current {
    color: var(--text);
}

/* The signed-in badge that replaces the "Войти" button. */
.account-chip {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 14px;
    color: var(--text);
    border: 1px solid var(--line-strong);
    padding: 7px 8px 7px 16px;
    border-radius: var(--radius-pill);
}

.account-chip:hover {
    border-color: var(--accent);
    color: var(--text);
}

.account-chip__mark {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--line);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--accent);
}

/* A header that floats is a header that covers. One row of it over a tall
   window is 81px with the picture running underneath, which is the landing
   page's design; two rows over a 640px phone is a seventh of the screen, held
   there for the whole page — and the text that scrolls under the second row
   shows through a scrim drawn to have faded out by that height. So below the
   width where the header takes two rows it stands above the hero instead of on
   it, and the hero gets the screen back. */
@media (max-width: 860px) {
    .site-header--overlay {
        position: static;
        background: var(--bg);
        border-bottom: 1px solid var(--line-dim);
    }
}

/* Two rows instead of one: the brand and the one action above, the navigation
   and the language under them. Left to wrap on its own the flex row gave three
   rows and a 200px header, and over the hero — where the header floats — the
   third row landed on top of the kicker. Grid rather than order, because the
   two rows are the layout and not a consequence of what happened to fit.

   The language moves down to the navigation because the top row has to hold at
   360px, where the brand, the codes and the button together do not: two of the
   three fit, and the third belongs with the links. */
@media (max-width: 860px) {
    .site-header__inner {
        display: grid;
        grid-template-columns: 1fr auto;
        gap: 12px 16px;
        padding: 15px var(--page-pad);
    }

    /* The aside stops being a box so its two children can take places of their
       own in the grid — they end up on different rows. */
    .site-header__aside {
        display: contents;
    }

    /* Every place is written down: auto-placement reads the markup order and
       would put the navigation beside the brand and the language under it. The
       action is addressed as the last child because it is a button when nobody
       is signed in and the account badge when somebody is. */
    .site-header__aside > :last-child {
        grid-area: 1 / 2;
    }

    .site-header__aside > .lang-switch {
        grid-area: 2 / 2;
        justify-self: end;
        align-self: center;
    }

    /* One line that scrolls, rather than a third row: the links keep their
       order and their size, and the row keeps its height. The fade at the end
       is what says so — without it the last label is sliced mid-word at the
       edge, which reads as a bug and not as "there is more this way". */
    .site-nav {
        grid-area: 2 / 1;
        margin-left: 0;
        gap: 18px;
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
        mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
    }

    .site-nav::-webkit-scrollbar {
        display: none;
    }

    .site-nav__link {
        white-space: nowrap;
    }
}

/* On a phone the header is a band the reader looks past to get to the page, so
   it gives up what air it can and keeps only what the two rows need. */
@media (max-width: 520px) {
    .site-header__inner {
        padding: 12px var(--page-pad);
        gap: 10px 14px;
    }
}

/* ==========================================================================
   component: site footer
   ========================================================================== */

/*
 * The site footer. --slim drops the link columns and keeps only the bottom row,
 * for pages that are an interruption rather than a destination (404).
 */
.site-footer {
    /* Воздух над линией — внешним отступом, а не внутренним: padding живёт под
       рамкой, а пусто нужно над ней. Без этого верхняя граница футера липнет к
       последней строке страницы и читается как её подчёркивание. */
    margin-top: clamp(56px, 8vh, 104px);
    border-top: 1px solid var(--line-dim);
    padding: 56px var(--page-pad) 40px;
}

/* На странице ошибки футер стоит внизу полноэкранной оболочки (.page--full),
   и всё пустое место над ним уже отдано `flex: 1` у .error-main. */
.site-footer--slim {
    margin-top: 0;
    padding: 20px var(--page-pad);
}

.site-footer__inner {
    max-width: var(--page-max);
    margin: 0 auto;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
    gap: 32px;
}

.site-footer__brand {
    font-size: 15px;
    letter-spacing: 0.22em;
    margin-bottom: 14px;
}

.site-footer__tagline {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.6;
    color: var(--text-dim);
    max-width: 32ch;
}

.site-footer__col-title {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.18em;
    color: var(--text-dim);
    margin-bottom: 14px;
}

.site-footer__links {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.site-footer__link {
    font-size: 14px;
    color: var(--text-3);
}

.site-footer__link:hover {
    color: var(--text);
}

/* The legal line. On --slim it is the whole footer, so it carries no rule. */
.site-footer__bottom {
    max-width: var(--page-max);
    margin: 40px auto 0;
    padding-top: 20px;
    border-top: 1px solid var(--line-dim);
    display: flex;
    gap: 22px;
    flex-wrap: wrap;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-faint);
}

.site-footer--slim .site-footer__bottom {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

/* Ссылка в нижней строке: цвет строки, а не ссылки. Подчёркивание появляется
   под курсором — иначе две ссылки среди подписей выглядели бы двумя ошибками
   вёрстки. */
.site-footer__legal-link {
    color: inherit;
    text-decoration: none;
    transition: color 0.15s ease;
}

.site-footer__legal-link:hover {
    color: var(--text-3);
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* ==========================================================================
   component: language switch
   ========================================================================== */

/*
 * RU / EN, and whatever gets added next: the switch is a row of codes, the one
 * being read is plain text and the rest are links to the same page under
 * another language prefix.
 */
.lang-switch {
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--text-4);
    letter-spacing: 0.1em;
}

.lang-switch__current {
    color: var(--text);
}

.lang-switch__link {
    color: var(--text-4);
    text-decoration: none;
}

.lang-switch__link:hover {
    color: var(--text);
}

.lang-switch__sep {
    color: var(--text-ghost);
}

/* Two mono characters are a 24px target for a finger. On the narrow layout the
   codes get the padding that makes them one; the header's row does not grow,
   the brand beside them is taller. */
@media (max-width: 860px) {
    .lang-switch__current,
    .lang-switch__link {
        display: inline-block;
        padding: 9px 3px;
    }
}

/* ==========================================================================
   component: buttons
   ========================================================================== */

/*
 * Buttons and button-shaped links. One shape (a pill), two weights: --primary
 * for the single action a page wants and the default outline for everything
 * else. There is no destructive weight: nothing on the site deletes anything.
 */
.btn {
    display: inline-block;
    font-family: inherit;
    font-size: 15px;
    line-height: 1.2;
    color: var(--text);
    background: transparent;
    border: 1px solid var(--line-strong);
    padding: 14px 28px;
    border-radius: var(--radius-pill);
    cursor: pointer;
    text-align: center;
    white-space: nowrap;
}

.btn:hover {
    border-color: var(--accent);
    color: var(--accent);
}

/* A button that exists but is not written yet. It keeps its shape and loses its
   response: nothing here lights up on hover. */
.btn:disabled,
.btn:disabled:hover {
    color: var(--text-dim);
    border-color: var(--line);
    cursor: default;
}

.btn--primary {
    background: var(--text);
    border-color: var(--text);
    color: var(--bg);
}

.btn--primary:hover {
    background: var(--text-max);
    border-color: var(--text-max);
    color: var(--bg);
}

/* Header-sized. */
.btn--sm {
    font-size: 14px;
    padding: 9px 20px;
}

.btn--md {
    font-size: 14.5px;
    padding: 12px 22px;
}

/* Hero-sized: the two calls to action on the landing page. */
.btn--lg {
    font-size: 15px;
    padding: 15px 32px;
}

.btn--block {
    display: block;
    width: 100%;
}

.btn-row {
    display: flex;
    gap: 14px;
    align-items: center;
    flex-wrap: wrap;
}

.btn-row--centered {
    justify-content: center;
}

/* A link that is a link, sitting in a row of buttons. */
.link-quiet {
    font-size: 14px;
    color: var(--text-4);
}

.link-quiet:hover {
    color: var(--text);
}

/* ==========================================================================
   component: breadcrumbs
   ========================================================================== */

/*
 * Breadcrumbs. Mono and upper-case: they read as a coordinate, not as prose.
 */
.breadcrumbs {
    padding: 20px 0 8px;
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
    font-family: var(--font-mono);
    font-size: 11.5px;
    letter-spacing: 0.12em;
    color: var(--text-dim);
}

.breadcrumbs__link {
    color: var(--text-4);
}

.breadcrumbs__link:hover {
    color: var(--text);
}

.breadcrumbs__current {
    color: var(--text);
}

/* ==========================================================================
   component: the rail beside an article
   ========================================================================== */

/*
 * The rail beside an article: section navigation, a table of contents, a card of
 * numbers. Used by every page that has a sidebar, on either side of the text.
 */
.rail {
    flex: 1 1 220px;
    max-width: 280px;
    min-width: 220px;
    border-top: 1px solid var(--line);
    padding-top: 20px;
}

/* Numbered section list. The current item is marked by a filled edge, so it
   stays legible when the colour alone is not enough. */
.rail-nav {
    list-style: none;
    margin: 0 0 28px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.rail-nav__link {
    display: flex;
    gap: 12px;
    padding: 9px 12px;
    font-size: 14.5px;
    color: var(--text-3);
    background: transparent;
    border-left: 2px solid transparent;
}

.rail-nav__link:hover {
    color: var(--text);
}

/* A list of sections can end in an action rather than a link: «Выйти» in the
   cabinet is a form, since signing out changes state. It takes the link's shape
   and only resets what a <button> brings with it — the font size stays the
   link's, so the item does not stand out of the list. */
.rail-nav__action {
    width: 100%;
    border: 0;
    border-left: 2px solid transparent;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

.rail-nav__link--current {
    color: var(--text);
    background: rgba(127, 178, 255, 0.07);
    border-left-color: var(--accent);
}

.rail-nav__num {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-dim);
}

/* Plain list of links: a table of contents, an archive. */
.rail-list {
    list-style: none;
    margin: 0 0 28px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
    font-size: 14.5px;
}

.rail-list--tight {
    gap: 9px;
    font-size: 13.5px;
}

.rail-list__link {
    color: var(--text-3);
}

.rail-list__link:hover {
    color: var(--text);
}

.rail-list__link--current {
    color: var(--text);
}

/* A framed note or a small table of figures at the bottom of the rail. */
.rail-card {
    border: 1px solid var(--line);
    padding: 16px 18px;
}

.rail-card + .rail-card,
.rail-card + .label {
    margin-top: 24px;
}

.rail-card__text {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.6;
    color: var(--text-4);
}

/* Key–value rows: "Кораблей — 3", "Версия — 0.9.4". */
.rail-stats {
    margin: 0 0 28px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    font-size: 13.5px;
}

.rail-stats__row {
    display: flex;
    justify-content: space-between;
    gap: 12px;
}

.rail-stats__key {
    color: var(--text-4);
}

.rail-stats__value {
    margin: 0;
    font-family: var(--font-mono);
    color: var(--text);
}

.rail-stats__value--green {
    color: var(--accent-green);
}

/* A section divider inside the rail, above a second block. */
.rail__section {
    margin-top: 28px;
    border-top: 1px solid var(--line);
    padding-top: 18px;
}

/* One column. `flex-basis: 100%` is what makes that true and not merely likely:
   left to their own widths the pair would keep sharing a line down to about
   630px, and the article would be reading at 340px next to a rail — and the
   rules below would already have moved the rail to the other side of it.

   Once it is a line of its own the rail takes all of it (the 280px cap is what
   holds it to a sidebar, and there is nothing beside it now), and it goes after
   the article: on the mechanics pages the rail is written before the article
   and would otherwise stand a list of eight sections between the reader and the
   first paragraph. The pages whose rail already follows the text — the cabinet,
   the changelog — do not move: their main keeps the default order. */
@media (max-width: 720px) {
    .reading__main {
        order: 1;
    }

    .rail {
        order: 2;
        flex-basis: 100%;
        min-width: 0;
        max-width: none;
    }
}

/* ==========================================================================
   component: notes, quotes, documents
   ========================================================================== */

/*
 * Framed asides: a rule the reader must not miss, a quote, an archival document.
 * All three are a border and a fill — the site has no cards with shadows.
 */

/* A note about a rule, blue like everything that is physics. Markdown has one
   way to set a passage apart and this is what it looks like: the article's
   «ПРИМЕЧАНИЕ» and its pull-quote both arrive as a blockquote now (ADR 0027). */
.note,
.prose > blockquote {
    border: 1px solid var(--line);
    background: rgba(127, 178, 255, 0.04);
    padding: 22px 24px;
    margin: 0 0 36px;
}

.note__title {
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.18em;
    color: var(--accent);
    margin-bottom: 10px;
}

.note__text,
.prose > blockquote > p {
    margin: 0;
    font-size: 16px;
    line-height: 1.65;
    color: var(--text-1);
}

/* The one sentence a section is really about. */
.pull-quote {
    border-left: 2px solid var(--accent-amber);
    padding: 6px 0 6px 22px;
    margin: 0 0 44px;
}

.pull-quote__text {
    margin: 0;
    font-size: 18px;
    line-height: 1.62;
    color: var(--text-1);
}

/* A document quoted verbatim: a probe log, a telemetry fragment, an address
   query. Monospace and pre-wrap because the original is fixed-width text. */
.doc {
    margin: 36px 0;
    border: 1px solid var(--line);
    background: var(--surface-veil);
    padding: 22px 24px;
}

.doc__title {
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.18em;
    color: var(--text-dim);
    margin-bottom: 14px;
}

.doc__body {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 13px;
    line-height: 1.85;
    color: #c6cbd8;
    white-space: pre-wrap;
}

/* ==========================================================================
   component: tables of figures
   ========================================================================== */

/*
 * Tables of figures. Numbers are monospace and right-aligned so columns line up
 * digit under digit; the label column stays in the sans face.
 */

/* Tables hold their minimum width and scroll sideways inside this wrapper
   rather than squeezing the columns. */
.table-scroll {
    overflow-x: auto;
    margin: 0 0 36px;
    padding-bottom: 4px;
}

.table {
    width: 100%;
    min-width: 560px;
    font-size: 15px;
}

.table__caption {
    caption-side: top;
    text-align: left;
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.16em;
    color: var(--text-dim);
    padding: 0 0 14px;
}

.table__th,
.prose th {
    text-align: left;
    padding: 10px 0;
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.16em;
    color: var(--text-dim);
    font-weight: 400;
    border-bottom: 1px solid var(--line);
}

.table__th--right,
.prose th[align='right'],
.prose td[align='right'] {
    text-align: right;
}

.prose th[align='center'],
.prose td[align='center'] {
    text-align: center;
}

.table__td,
.prose td {
    padding: 12px 0;
    border-bottom: 1px solid var(--line-dim);
    color: var(--text-1);
}

/* A figure: monospace, right-aligned, never wrapped mid-value. */
.table__td--num {
    font-family: var(--font-mono);
    text-align: right;
    white-space: nowrap;
    color: var(--text);
}

.table__td--num-dim {
    color: var(--text-4);
}

.table__td--right {
    text-align: right;
    color: var(--text-4);
}

/* Middle columns need air on both sides; the outer ones align to the edge. */
.table__th:not(:first-child):not(:last-child),
.table__td:not(:first-child):not(:last-child),
.prose th:not(:first-child):not(:last-child),
.prose td:not(:first-child):not(:last-child) {
    padding-left: 16px;
    padding-right: 16px;
}

/* ==========================================================================
   component: picture frames and figures
   ========================================================================== */

/*
 * Picture frames. Every image on the site is still a placeholder, so the frame
 * is the component and the caption inside it says what belongs there — see
 * docs/IMAGES.md for the shot list.
 */
.media-frame {
    border: 1px solid var(--line);
    aspect-ratio: 16 / 9;
    background: var(--surface-veil);
    display: flex;
    align-items: flex-end;
    padding: 16px;
    min-width: 0;
}

.media-frame__note {
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--text-dim);
    letter-spacing: 0.14em;
}

.figure {
    margin: 0 0 36px;
}

.figure--flush {
    margin: 0 0 20px;
}

/* Diagrams are drawn at a fixed viewBox and scroll rather than shrink. */
.figure--scroll {
    overflow-x: auto;
}

.figure__caption {
    margin-top: 14px;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-dim);
    letter-spacing: 0.1em;
}

/* A caption in the reader's voice rather than the archive's: sentence case,
   sans face, sits under a screenshot in a gallery. */
.figure__legend {
    margin-top: 12px;
    font-size: 14.5px;
    line-height: 1.55;
    color: var(--text-4);
}

.figure__legend strong {
    color: var(--text);
    font-weight: 400;
}

/* ==========================================================================
   component: previous / next
   ========================================================================== */

/*
 * Previous / next at the end of an article. Two equal targets: the reader is
 * choosing a direction, not following a call to action.
 */
.pager {
    border-top: 1px solid var(--line);
    padding-top: 22px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
    gap: 16px;
}

.pager__link {
    border: 1px solid var(--line);
    padding: 18px 20px;
    display: block;
}

.pager__link:hover {
    border-color: var(--line-strong);
    color: inherit;
}

.pager__link--next {
    text-align: right;
}

.pager__link--next:hover {
    border-color: var(--accent);
}

.pager__label {
    display: block;
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.16em;
    color: var(--text-dim);
    margin-bottom: 8px;
}

.pager__title {
    display: block;
    font-size: 17px;
    color: var(--text);
}

/* ==========================================================================
   component: form fields
   ========================================================================== */

/*
 * Form fields. Values are typed in monospace: on this site an input holds an
 * address, a name or a login, and all three are read character by character.
 */
.field {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.field__label {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.14em;
    color: var(--text-4);
}

.field__input {
    background: var(--bg-input);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-field);
    color: var(--text);
    padding: 14px 16px;
    font-family: var(--font-mono);
    font-size: 14.5px;
    width: 100%;
}

/* A value that is shown but not editable here (the email is changed by a
   separate flow, not by typing over it). */
.field__input[readonly],
.field__input[disabled] {
    background: var(--bg-panel);
    border-color: var(--line);
    color: var(--text-4);
}

/* Поле, значение которого всё равно уедет в верхнем регистре (имя игрока).
   Печатать можно как угодно — приводит к нему домен; это лишь показывает
   результат по ходу ввода. Значение поля не меняется, поэтому правило
   исключительно оформительское. */
.field__input--upper {
    text-transform: uppercase;
}

.field__error {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--accent-danger);
}

.form {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.form__row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
    gap: 20px;
}

.form__actions {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    align-items: center;
}

.form__footer {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    font-size: 14px;
}

/* A button that has to look like a link (switching to the sign-up form). */
.form__link-button {
    background: none;
    border: 0;
    padding: 0;
    font-family: inherit;
    font-size: 14px;
    color: var(--accent);
    cursor: pointer;
}

/* ==========================================================================
   component: tabs and chips
   ========================================================================== */

/*
 * Pill controls whose state lives in an ARIA attribute — aria-selected for a
 * chosen tab, aria-pressed for a chosen filter. The attribute is both the
 * accessible state and the styling hook, so the JavaScript behind them
 * (assets/controllers/) only ever flips the attribute and never a class.
 */

/* Two mutually exclusive views of one panel: вход / регистрация. */
.tabs {
    display: flex;
    gap: 2px;
    margin-bottom: 34px;
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    padding: 4px;
    width: fit-content;
}

.tabs__btn {
    padding: 9px 22px;
    border-radius: var(--radius-pill);
    border: 0;
    cursor: pointer;
    font-family: inherit;
    font-size: 14px;
    background: transparent;
    color: var(--text-3);
}

.tabs__btn[aria-selected='true'] {
    background: var(--text);
    color: var(--bg);
}

/* A row of filters or a segmented choice. */
.chip-row {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.chip {
    padding: 8px 18px;
    border-radius: var(--radius-pill);
    cursor: pointer;
    font-family: inherit;
    font-size: 13.5px;
    border: 1px solid var(--line-strong);
    background: transparent;
    color: var(--text-3);
}

.chip:hover {
    color: var(--text);
}

.chip[aria-pressed='true'] {
    border-color: var(--text);
    background: var(--text);
    color: var(--bg);
}

/* ==========================================================================
   component: badges and tags
   ========================================================================== */

/*
 * Static markers: a pill that labels a release, a word that labels a line in it.
 * Colour follows the accent tokens — amber is a rule and a cost, green is what
 * was added, blue is the interface, violet is a repair.
 */
.badge {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.16em;
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    border: 1px solid currentColor;
    color: var(--text-4);
}

.badge--green {
    color: var(--accent-green);
    border-color: #8ee08a55;
}

.badge--blue {
    color: var(--accent);
    border-color: #7fb2ff55;
}

.badge--amber {
    color: var(--accent-amber);
    border-color: #ffd16655;
}

.badge--violet {
    color: var(--accent-violet);
    border-color: #a78bfa55;
}

.tag {
    font-family: var(--font-mono);
    font-size: 10.5px;
    letter-spacing: 0.16em;
    white-space: nowrap;
    color: var(--text-4);
}

.tag--amber {
    color: var(--accent-amber);
}

.tag--green {
    color: var(--accent-green);
}

.tag--blue {
    color: var(--accent);
}

.tag--violet {
    color: var(--accent-violet);
}

/* ==========================================================================
   component: figures with a caption
   ========================================================================== */

/*
 * Figures with a caption. Two forms: the landing page states four numbers at
 * display size, the sign-in page repeats three of them small, in a frame.
 */
.stat-grid {
    max-width: var(--page-max);
    margin: 0 auto;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
    gap: 40px 32px;
}

/* The longest value here is "1 500 000 000" and it must not wrap: the size is
   picked so that it fits one grid column, not so that it fills the band. */
.stat__value {
    font-size: clamp(28px, 3vw, 42px);
    line-height: 1.1;
    letter-spacing: -0.03em;
    white-space: nowrap;
    color: var(--text);
    margin-bottom: 10px;
}

.stat__label {
    margin: 0;
    font-size: 15px;
    line-height: 1.6;
    color: var(--text-4);
    max-width: 30ch;
}

/* The framed variant: a 1px gap over a lit background draws the grid lines. */
.stat-tiles {
    position: relative;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
    gap: 1px;
    background: #141b28;
    border: 1px solid #141b28;
}

.stat-tile {
    background: var(--bg-panel);
    padding: 16px 18px;
}

.stat-tile__value {
    font-family: var(--font-mono);
    font-size: 18px;
    color: var(--text);
    margin-bottom: 6px;
}

.stat-tile__label {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--text-dim);
}

/* Two columns and three tiles leave the fourth cell empty, and a grid ruled by
   its own gaps draws that cell as a tile with nothing written in it. In one
   column every row is full — and a label reads better whole than in 150px. */
@media (max-width: 520px) {
    .stat-tiles {
        grid-template-columns: 1fr;
    }
}

/* ==========================================================================
   component: статья, набранная в Markdown
   ========================================================================== */

/*
 * `.prose` — то, что получилось из Markdown страницы (ADR 0027). Разметка
 * приходит без классов, поэтому её вид собран из двух частей: значения, которые
 * у сайта уже были, дописаны селекторами к своим правилам выше (`.text`,
 * `.heading`, `.table__th`, `.note`), а здесь — только то, чего в вёрстке не
 * было вовсе, и ритм, который у классов выражался модификатором.
 *
 * Дочерний комбинатор обязателен там, где правило спорит с вложенностью:
 * `.prose p` попал бы и в абзац цитаты, и в подпись.
 */

.prose > h2 {
    /* Воздух заголовок берёт у абзаца над собой — как `.article__heading`,
       которым это было, пока статья жила в шаблоне. */
    margin: 8px 0 16px;
}

/* У абзаца из Markdown нет модификатора `--last`: последний блок статьи не
   должен уносить с собой отступ до перехода «дальше читать». */
.prose > :last-child {
    margin-bottom: 0;
}

.prose ul,
.prose ol {
    margin: 0 0 18px;
    padding-left: 22px;
    font-size: 17px;
    line-height: 1.72;
    color: var(--text-2);
}

.prose li + li {
    margin-top: 8px;
}

.prose li > ul,
.prose li > ol {
    margin: 8px 0 0;
}

/* Цитата может быть в несколько абзацев — рамка у неё одна, воздух между ними
   свой. */
.prose > blockquote > p + p {
    margin-top: 14px;
}

.prose a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* Картинка живёт по ширине колонки и не тянется сверх своего размера. */
.prose img {
    display: block;
    max-width: 100%;
    height: auto;
    margin: 0 0 36px;
    border: 1px solid var(--line);
}

.prose hr {
    border: 0;
    border-top: 1px solid var(--line);
    margin: 36px 0;
}

.prose code {
    font-family: var(--font-mono);
    font-size: 0.92em;
    color: var(--text);
}

.prose pre {
    margin: 0 0 36px;
    padding: 18px 20px;
    overflow-x: auto;
    border: 1px solid var(--line);
    background: var(--surface-veil);
}

.prose pre code {
    font-size: 13px;
    line-height: 1.85;
    color: #c6cbd8;
}

/* ==========================================================================
   page: главная
   ========================================================================== */

/*
 * The landing page. Everything here is used once, on this page: the hero, the
 * three-card row, the two picture splits, the cycles and the closing screen.
 * Anything that appears twice on the site has moved to components/.
 */

/* The hero is a full-bleed picture with the header floating on top of it, so it
   sets its own padding and carries no rule above. Below 860px the header stops
   floating and stands above the picture, and the padding drops to match. */
.hero {
    position: relative;
    min-height: min(88vh, 900px);
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding: clamp(120px, 18vh, 180px) var(--page-pad) clamp(110px, 18vh, 190px);
    background: var(--bg);
    overflow: hidden;
}

/* Three layers under the text: a hatched grid, the photograph, and a veil dark
   enough to keep the copy readable over any of it. The grid is what shows while
   the picture is on the wire, and if it never arrives. */
.hero__grid {
    position: absolute;
    inset: 0;
    background:
        repeating-linear-gradient(135deg, rgba(127, 178, 255, 0.05) 0 1px, transparent 1px 14px),
        var(--bg-grid);
}

/* The shot is 16:9 and the hero is not, at any window: the picture is cropped
   rather than fitted, and pulled to its bottom edge — the planet's limb is what
   the frame is for, the empty sky above it is not. */
.hero__art {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 100%;
}

/* Two scrims, because the picture is bright exactly where the copy is: the
   vertical one seats the hero into the black page above and below it, the
   horizontal one darkens the column the text stands in and lets the planet come
   through clean on the right. */
.hero__veil {
    position: absolute;
    inset: 0;
    background:
        linear-gradient(
            90deg,
            rgba(0, 2, 5, 0.88) 0%,
            rgba(0, 2, 5, 0.6) 34%,
            rgba(0, 2, 5, 0) 66%
        ),
        linear-gradient(
            180deg,
            rgba(0, 2, 5, 0.55) 0%,
            rgba(0, 2, 5, 0.25) 45%,
            rgba(0, 2, 5, 0.92) 100%
        );
}

/* Type over a photograph carries a shadow and never a plate — the same rule the
   client's own place screens follow. It costs nothing where the scrim already
   holds and saves the line that runs past it onto the lit limb. */
.hero__content {
    position: relative;
    max-width: var(--page-max);
    margin: 0 auto;
    width: 100%;
    text-shadow: 0 1px 24px rgba(0, 2, 5, 0.85);
}

.hero__lead {
    margin: 0 0 34px;
    font-size: clamp(17px, 1.5vw, 21px);
    line-height: 1.55;
    color: var(--text-2);
    max-width: 48ch;
}

/* The premise, set large: the only section on the page with no heading. */
.statement {
    margin: 0;
    font-size: clamp(24px, 3.4vw, 46px);
    line-height: 1.28;
    letter-spacing: -0.02em;
    color: var(--text);
    text-wrap: pretty;
}

.statement__note {
    margin: 34px 0 0;
    font-size: clamp(18px, 1.6vw, 22px);
    line-height: 1.6;
    color: var(--text-4);
    max-width: 62ch;
}

/* "Для кого это сделано" — three cards, one claim each. */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
    gap: 16px;
}

.card {
    background: var(--bg);
    border: 1px solid #141b28;
    padding: 32px 30px 36px;
}

.card__text {
    margin: 0;
    font-size: 16px;
    line-height: 1.65;
    color: var(--text-3);
}

/* The sentence under a block that qualifies it — the honest caveat. */
.section__note {
    margin: 36px 0 0;
    font-size: 17px;
    line-height: 1.65;
    color: var(--text-4);
    max-width: 60ch;
}

/* Lit sections. The glow follows the accent of the subject: blue for the
   figures, amber for the rhythm, blue again for the closing screen. */
.section--figures {
    position: relative;
    padding: clamp(64px, 8vh, 104px) var(--page-pad);
    border-top: 1px solid var(--line-dim);
    background:
        radial-gradient(800px 500px at 20% 40%, rgba(127, 178, 255, 0.09), transparent 70%),
        var(--bg);
}

.section--rhythm {
    background: radial-gradient(700px 420px at 70% 50%, rgba(255, 209, 102, 0.05), transparent 70%);
}

/* The full table of durations, open on arrival: it is the section's argument
   and not its appendix, so nothing folds it away. */
.durations {
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}

.durations__title {
    padding: 20px 0;
    font-family: var(--font-mono);
    font-size: 12.5px;
    letter-spacing: 0.14em;
    color: var(--text-4);
}

.durations__body {
    overflow-x: auto;
    padding-bottom: 24px;
}

/* Explore / expand / learn / encounter. */
.cycle-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
    gap: 40px;
}

.cycle__text {
    margin: 0;
    font-size: 16px;
    line-height: 1.62;
    color: var(--text-3);
}

/* "Чего в игре нет": a term and the reason it is absent, one row each. */
.absent {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}

.absent__item {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
    gap: 16px 32px;
    padding: 26px 0;
    border-top: 1px solid var(--line);
}

.absent__term {
    font-size: clamp(20px, 2vw, 28px);
    line-height: 1.2;
    letter-spacing: -0.02em;
    color: var(--text);
}

.absent__note {
    font-size: 16px;
    line-height: 1.62;
    color: var(--text-4);
}

/* Four screenshots of the client. */
.gallery {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
    gap: 28px;
}

/* The last screen: one sentence, two doors. */
.closing {
    min-height: 82vh;
    display: flex;
    align-items: center;
    padding: clamp(80px, 12vh, 140px) var(--page-pad);
    border-top: 1px solid var(--line-dim);
    background: radial-gradient(900px 600px at 50% 50%, rgba(127, 178, 255, 0.1), transparent 70%);
}

.closing__text {
    margin: 0 0 48px;
    font-size: clamp(24px, 3.2vw, 44px);
    line-height: 1.3;
    letter-spacing: -0.025em;
    color: var(--text);
    text-wrap: pretty;
}

.closing__note {
    margin: 22px 0 0;
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--text-dim);
    letter-spacing: 0.1em;
}

/* Most of the hero's top padding is clearance for the header floating over it.
   Below 860px the header stands above the hero instead (see the header's own
   rule), so what is left here is air over the kicker and not room for a bar. */
@media (max-width: 860px) {
    .hero {
        padding-top: clamp(56px, 9vh, 110px);
    }
}

/* The bands that set their own padding follow the same phone rhythm as .section
   does — otherwise the tightened ones stand between untouched ones. The hero has
   already taken a step down above (the rule before this one) and here gives up
   the air under the buttons as well. */
@media (max-width: 520px) {
    .hero {
        padding-bottom: clamp(72px, 12vh, 120px);
    }

    .section--figures {
        padding: clamp(48px, 7vh, 72px) var(--page-pad);
    }

    .closing {
        padding: clamp(56px, 9vh, 100px) var(--page-pad);
    }
}

/* ==========================================================================
   page: об игре
   ========================================================================== */

/*
 * "Об игре" — a mechanics article with a rail of sections beside it. The page
 * owns almost nothing: the rail, the tables, the notes and the pager are all
 * components, and the text itself is `.prose`. What is left is the article's own
 * spacing — shared with the editor's form (ADR 0027), which is the same article
 * seen from the other side and wears the same kicker and title.
 */

/* The kicker sits under the breadcrumbs, which already have padding of their
   own, so this one starts closer to the top than the shared step. */
.article__kicker {
    margin: 24px 0 20px;
}

.article__title {
    font-size: clamp(34px, 4.6vw, 58px);
}

/* Заголовки статьи переехали в `.prose > h2` вместе с текстом (ADR 0027):
   класс некому ставить — Markdown отдаёт голый `<h2>`. */

/* Обзор механик: список статей раздела. */
.section-index {
    list-style: none;
    margin: 0;
    padding: 0;
}

.section-index__item {
    padding: 22px 0;
    border-top: 1px solid var(--line-dim);
}

.section-index__item:last-child {
    border-bottom: 1px solid var(--line-dim);
}

.section-index__link {
    display: flex;
    align-items: baseline;
    gap: 14px;
    margin-bottom: 10px;
    font-size: 21px;
    letter-spacing: -0.02em;
    color: var(--text);
}

.section-index__link:hover {
    color: var(--accent);
}

/* ==========================================================================
   page: журнал изменений
   ========================================================================== */

/*
 * Журнал изменений. A list of releases, newest first; every line inside one is
 * tagged with what it changed. Filters are client-side (assets/controllers/).
 */
.changelog-head__title {
    max-width: 16ch;
}

.releases {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 44px;
}

.release {
    border-top: 1px solid var(--line);
    padding-top: 22px;
}

.release__meta {
    display: flex;
    gap: 16px;
    align-items: baseline;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

.release__version {
    font-family: var(--font-mono);
    font-size: 20px;
    color: var(--text);
    letter-spacing: 0.04em;
}

.release__date {
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--text-dim);
    letter-spacing: 0.12em;
}

.release__summary {
    margin: 0 0 20px;
    font-size: 16.5px;
    line-height: 1.68;
    color: var(--text-3);
    max-width: 62ch;
}

.release__changes {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* A change is a tag and a sentence. On a narrow screen the tag sits above it. */
.release__change {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
    gap: 6px 20px;
    align-items: baseline;
}

.release__text {
    font-size: 16px;
    line-height: 1.62;
    color: var(--text-2);
}

.changelog-foot {
    margin-top: 48px;
    border-top: 1px solid var(--line);
    padding-top: 22px;
    display: flex;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    align-items: center;
}

.changelog-foot__count {
    font-family: var(--font-mono);
    font-size: 11.5px;
    letter-spacing: 0.14em;
    color: var(--text-dim);
}

/* ==========================================================================
   page: аккаунт
   ========================================================================== */

/*
 * Аккаунт. Everything on this page belongs to the account, not to the world:
 * the game state is behind the sign-in screen and is not edited here.
 */
.account-main {
    flex: 999 1 340px;
    min-width: 0;
    max-width: 820px;
    display: flex;
    flex-direction: column;
    gap: 44px;
}

.account-section__title {
    margin: 0 0 8px;
}

.account-section__note {
    margin: 0 0 24px;
    font-size: 15.5px;
    line-height: 1.6;
    color: var(--text-4);
    max-width: 56ch;
}

/* Профиль: что об аккаунте известно, подписью, а не полем ввода. Имя не
   меняется вовсе (Core\Identity\User), а поле, из которого ничего нельзя
   отправить, обещает обратное. Значения моноширинные — это данные. */
.account-facts {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.account-facts__key {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.14em;
    color: var(--text-4);
    margin-bottom: 8px;
}

.account-facts__value {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 15px;
    color: var(--text);
}

/* Раздел кабинета, которого ещё нет: a page that says it is empty, so the menu
   never leads into a 404. The frame stays — the first real setting replaces the
   line inside it and nothing around it. */
.account-stub {
    margin: 0;
    font-size: 15px;
    line-height: 1.7;
    color: var(--text-4);
}

/* A framed group of fields. */
.panel {
    border: 1px solid var(--line);
    padding: 24px;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

/* ==========================================================================
   page: вход и регистрация
   ========================================================================== */

/*
 * Вход и регистрация. Two panes: the form, and the reason to fill it in. Below
 * ~720px they stack and the form comes first.
 *
 * The page has no .container of its own — it is its own shell — so it takes the
 * site's width here: past --page-max the panes stop growing and the pair is
 * centred, instead of the form drifting into one corner of a wide monitor and
 * the quote into the other. Outside the cap there is nothing to see: the page
 * background is the same --bg the art pane paints its glow over.
 */
.auth {
    min-height: 100vh;
    min-height: 100svh;
    display: grid;
    /* min() so a pane may go under its 360px: on a narrower screen a hard
       minimum is wider than the window, and the form loses its right edge to
       the page's overflow rather than getting narrower. */
    grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
    max-width: var(--page-max);
    margin: 0 auto;
    overflow-x: hidden;
}

.auth__pane {
    padding: clamp(28px, 4vw, 48px);
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100svh;
    border-right: 1px solid var(--line-dim);
}

.auth__brand {
    color: var(--text);
    font-size: 15px;
    letter-spacing: 0.22em;
    font-weight: 500;
}

/* margin:auto 0 centres the form vertically without a second grid. */
.auth__box {
    margin: auto 0;
    padding: 48px 0;
    width: 100%;
    max-width: 420px;
}

.auth__title {
    margin: 0 0 12px;
    font-size: clamp(30px, 3.4vw, 42px);
    line-height: 1.08;
    letter-spacing: -0.03em;
    font-weight: 500;
}

.auth__lead {
    margin: 0 0 32px;
    font-size: 16px;
    line-height: 1.6;
    color: var(--text-4);
    max-width: 44ch;
}

/* A sentence under a field that the field itself cannot say: what the value
   will mean afterwards. The one place it is used is the name, which is chosen
   once and never again. Sits inside .form, hence the negative top margin —
   it belongs to the field above it, not to the gap between fields. */
.auth__hint {
    margin: -12px 0 0;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-4);
    max-width: 44ch;
}

.auth__note {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-faint);
    letter-spacing: 0.1em;
}

/* The right pane: a sky, a line from the game, three figures. */
.auth__art {
    position: relative;
    padding: clamp(28px, 4vw, 48px);
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 40px;
    min-height: 100vh;
    min-height: 100svh;
    background:
        radial-gradient(800px 600px at 60% 30%, rgba(127, 178, 255, 0.12), transparent 65%),
        radial-gradient(500px 400px at 30% 80%, rgba(167, 139, 250, 0.07), transparent 70%),
        var(--bg);
}

/* Stars drawn as gradients rather than an image: five dots cost nothing and
   never 404. */
.auth__stars {
    position: absolute;
    inset: 0;
    background-image:
        radial-gradient(1px 1px at 18% 26%, #ffffff88, transparent),
        radial-gradient(1.3px 1.3px at 52% 16%, #ffffffaa, transparent),
        radial-gradient(1px 1px at 74% 42%, #ffffff77, transparent),
        radial-gradient(1px 1px at 34% 72%, #ffffff66, transparent),
        radial-gradient(1.2px 1.2px at 82% 78%, #ffffff88, transparent);
}

.auth__lang {
    position: relative;
    display: flex;
    justify-content: flex-end;
}

.auth__quote {
    position: relative;
    margin: 0;
    max-width: 44ch;
}

.auth__quote-text {
    margin: 0 0 18px;
    font-size: clamp(20px, 2.2vw, 30px);
    line-height: 1.42;
    letter-spacing: -0.02em;
    color: var(--text);
}

.auth__cite {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.16em;
    color: var(--text-dim);
    font-style: normal;
}

/* Stacked, the two panes are two screens one under the other. The form keeps
   its own: it is what the page is for. The sky loses it and takes what the
   quote needs — a second full screen of it is scrolling past a decoration.
   The hairline between the panes turns with them: on a full-width pane a
   border on the right edge is a line down the side of the screen. */
@media (max-width: 720px) {
    .auth__pane {
        border-right: 0;
        border-bottom: 1px solid var(--line-dim);
    }

    .auth__art {
        min-height: 0;
        gap: 28px;
    }
}

/* ==========================================================================
   page: ошибки
   ========================================================================== */

/*
 * Error pages. An empty address is an ordinary thing in the game and an error
 * on the site; the page says both, and offers the two ways back.
 */
.error-page {
    background: radial-gradient(900px 600px at 50% 30%, rgba(127, 178, 255, 0.08), transparent 68%);
}

.error-main {
    flex: 1;
    display: flex;
    align-items: center;
    padding: clamp(48px, 10vh, 120px) var(--page-pad);
}

.error-grid {
    max-width: var(--page-max);
    margin: 0 auto;
    width: 100%;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
    gap: clamp(32px, 5vw, 72px);
    align-items: center;
}

.error-title {
    max-width: 14ch;
}

.error-text {
    margin: 0 0 32px;
    font-size: 18px;
    line-height: 1.6;
    color: var(--text-4);
    max-width: 46ch;
}

/* The readout keeps the game's own line height — it is a terminal answer. */
.error-page .doc__body {
    line-height: 1.9;
}

/* Inside the error grid the readout is a column of its own, so it drops the
   vertical margin an archival document carries in running text. */
.error-page .doc {
    margin: 0;
}
