/* ==========================================================================
   icesoft.ro — site stylesheet
   Hand-written, no framework. See docs/design-system.md.
   Order: fonts, tokens, reset, typography, utilities, focus, layout,
          chrome, components, motion.
   ========================================================================== */

/* ---------------------------------------------------------------- fonts -- */

/* Self-hosted, two files per family, latin + latin-ext. Romanian needs
   latin-ext for ă î â ș ț. See docs/decisions/006-self-hosted-display-font.md.
   Space Grotesk is a variable face: one file covers the whole 300–700 range,
   which is why there is no separate 700 file. */

@font-face {
    font-family: "Space Grotesk";
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url("../fonts/space-grotesk-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
        U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Space Grotesk";
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url("../fonts/space-grotesk-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("../fonts/ibm-plex-mono-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
        U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("../fonts/ibm-plex-mono-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

/* --------------------------------------------------------------- tokens -- */

/* Light is the base. Dark is declared twice — once for the system preference,
   once for an explicit data-theme — so a future toggle wins in both directions
   without redefining anything a third time.
   See docs/decisions/007-dark-mode.md. */

:root {
    color-scheme: light dark;

    /* surfaces */
    --ice-bg: #f2f5f9;
    /* the page itself */
    --ice-surface: #ffffff;
    /* panels, cards, tables, the footer */

    /* text */
    --ice-ink: #0f1b2b;
    /* 17.33:1 on surface */
    --ice-ink-soft: #1c2c3f;
    /* 14.18:1 — panel body copy */
    --ice-muted: #455464;
    /* 7.76:1 on surface, 7.10:1 on bg — metadata, captions, .lead */
    --ice-faint: #576675;
    /* 5.89:1 on surface — labels only, never a passage */

    /* lines */
    --ice-border: #d5dde7;
    /* panel and card edges */
    --ice-rule: #e6ebf1;
    /* dividers inside a panel, table rows */
    --ice-field-border: #767e8c;
    /* form controls only — see design-system.md */

    /* brand — the blue of the logo outline (#0000ff), dropped in lightness
       until it carries text: 8.84:1 against white, which is the figure both
       ways round — contrast is a ratio between two colours, not a direction, so
       blue on white and white on blue are the same number. */
    --ice-blue: #14459e;
    --ice-blue-dark: #0e3179;
    --ice-blue-wash: #e7ecf8;
    --ice-on-brand: #ffffff;
    /* text on a solid brand fill */

    /* document states — and, since a download is green too, the one of these
       that also fills a control. See decisions/011-action-colours.md. */
    --ice-green: #0c6f3f;
    /* 6.25:1 on surface, 5.71 on bg, 5.28 on the blue wash a current row takes,
       5.21 on the rule a row takes under the cursor — the four grounds a
       download can land on, all of them above the 5.18 floor.

       It was a forest green at 8.73:1 — the value --ice-green-dark now holds —
       and the owner's call (2026-08-28) is that a download reads as unavailable
       in that colour. This is the lightest green that still clears the floor on
       all four; anything above it buys brightness with a ground the file links
       actually use. */
    --ice-green-dark: #05572c;
    /* a green fill on hover — 8.73:1 on surface, and the value the base held */
    --ice-green-wash: #e3f2e9;
    --ice-amber: #8a5a00;
    --ice-amber-wash: #f8efd9;
    --ice-red: #b3000a;
    /* 7.19:1 on surface, 6.13 on its wash */
    --ice-red-field: #dc2b1b;
    /* the invalid field's edge — 4.77:1 on surface, a functional 3:1 border */
    --ice-red-wash: #fbe9e7;

    --ice-focus: #2e6bff;

    /* The licence CTA. The one colour here that does not follow the theme: an
       orange dark enough to carry white text is a brown, so this is a bright
       fill with dark ink on it, the same in both themes. Same reasoning as the
       fixed panels below — a theme switch must not reach it. */
    --ice-orange: #ff8c26;
    /* 7.96:1 under --ice-on-orange; 7.15:1 against the dark masthead */
    --ice-orange-hover: #ffa54d;
    /* 9.46:1 — a bright fill hovers lighter, not darker */
    --ice-orange-edge: #b45309;
    /* the fill is 2.33:1 on white and draws no box: this is the box.
       5.02:1 on surface light, 3.31:1 dark — a functional 3:1 border */
    --ice-on-orange: #0b1420;
    /* text on the orange fill, in both themes */
    --ice-orange-ink: #a34700;
    /* the same CTA written as a link rather than a fill — 5.55:1 on the page
       ground, 6.07:1 on surface. This one *does* follow the theme: the fill is
       2.33:1 on a white page and cannot carry text there, so light takes a
       darker orange and dark takes the fill colour itself. Hence the `-dark`
       suffix on its hover, where the fill's is `-hover`: this pair inverts,
       that one does not.

       It is deliberately not as dark as it could be. At 7:1 this token read
       brown — that value is what its hover holds now — and the owner's call is
       that a CTA has to look like the CTA. 5.55:1 is above the 5.18:1 that is
       the weakest pair the site accepts anywhere. */
    --ice-orange-ink-dark: #8a3800;
    /* 7.26:1 on the page ground */

    /* Fixed dark panels — the utility strip and the hero. These are panels,
       not a theme: they stay dark in both themes and only deepen in dark. */
    --ice-panel: #0f1b2b;
    --ice-panel-ink: #e7eef6;
    /* 14.82:1 */
    --ice-panel-ink-2: #bfcedd;
    /* 10.80:1 */
    --ice-panel-ink-3: #a7b8c9;
    /*  8.53:1 — small text only */
    --ice-panel-blue: #9dbaff;
    /*  8.99:1 — the brand blue readable on a panel; `--ice-blue` is a dark
        navy in light and would vanish here */
    --ice-panel-white: #ffffff;
    /* 17.33:1 — headings on a panel, and the fill of a solid control on one */
    --ice-panel-white-dim: #dfe9f3;
    /* that control on hover; panel ink on it is 14.10:1 */
    --ice-panel-green: #6ee7a7;
    /*  11.26:1 / 12.75:1 — the download fill on a panel; --ice-green is a dark
        forest green in light and would vanish here, exactly as --ice-blue does */
    --ice-panel-green-dim: #5fdd9c;
    /* that fill on hover; panel ink on it is 10.16:1 */
    --ice-panel-hair: rgba(255, 255, 255, .16);
    --ice-panel-line: rgba(255, 255, 255, .24);
    --ice-scrim: rgba(7, 12, 18, .92);
    /*  the lightbox ground — `--ice-panel`'s dark value at 92%. Declared once
        like the two above: a scrim is a fixed dark surface, not a theme. */
    --ice-panel-field: #3f4956;
    /*  the ground of a form control on a panel — panel ink on it is 7.81:1,
        `--ice-panel-ink-3` as its edge 4.50:1. It is the colour white at 14%
        over `.hero__get` resolves to, written out rather than mixed: a
        `<select>`'s option list is painted by the browser with the control's
        own background, where there is no panel behind it for the alpha to fall
        on, so a translucent value flattens against the popup's ground and the
        near-white ink lands on near-white. A ground a control opens must be
        opaque. */

    /* The terminal is a fixed dark panel too, but a deeper one with a shell's
       palette rather than the site's. Its own three, for the same reason the
       panel has its own: a theme switch must not reach them. */
    --ice-term-bg: #0b1220;
    --ice-term-ink: #7ee787;
    /* 12.19:1 on its ground */
    --ice-term-muted: #8b98b0;
    /*  6.43:1 — the `$` prompt, which is decoration and `user-select: none` */

    /* type */
    --font-display: "Space Grotesk", system-ui, -apple-system, "Segoe UI",
        Roboto, sans-serif;
    --font-body: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
        Arial, "Noto Sans", sans-serif;
    --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas,
        "Liberation Mono", monospace;

    /* Role-named, because the same size means different things in different
       places. Values in rem so the reader's own font size still governs. */
    --text-body: 1.125rem;
    /* 18 — running text                */
    --text-lead: 1.1875rem;
    /* 19 — page lede, panel prose      */
    --text-ui: 1.0625rem;
    /* 17 — buttons, nav, list items    */
    --text-dense: 1rem;
    /* 16 — cards, tables, notices      */
    --text-meta: 0.9375rem;
    /* 15 — metadata, hints, notes      */
    --text-label: 0.875rem;
    /* 14 — eyebrow, keys, utility strip*/
    --text-micro: 0.8125rem;
    /* 13 — tags, sub-labels            */

    /* spacing — tuned to the density of the reference design */
    --space-1: 0.25rem;
    /*  4 */
    --space-2: 0.5rem;
    /*  8 */
    --space-3: 0.75rem;
    /* 12 */
    --space-4: 1rem;
    /* 16 */
    --space-5: 1.375rem;
    /* 22 */
    --space-6: 1.75rem;
    /* 28 */
    --space-7: 2.5rem;
    /* 40 */
    --space-8: 3.5rem;
    /* 56 */
    --space-9: 5rem;
    /* 80 */

    /* shape */
    --radius-sm: 0.5625rem;
    /*  9 — buttons, inputs   */
    --radius: 0.75rem;
    /* 12 — cards, panels     */
    --radius-lg: 1rem;
    /* 16 — hero              */
    --radius-pill: 999px;

    /* A card is identified by its border, not by a shadow. The single shadow
       token exists for the one lift the primary button takes on hover. */
    --shadow-press: 0 2px 12px rgba(15, 27, 43, .22);
}

/* Dark — the system preference. Guarded so an explicit light choice wins. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --ice-bg: #0d141d;
        --ice-surface: #151f2b;
        --ice-ink: #eef3f8;
        --ice-ink-soft: #dbe4ed;
        --ice-muted: #a9b6c4;
        --ice-faint: #94a3b3;
        --ice-border: #2c3948;
        --ice-rule: #232f3c;
        --ice-field-border: #7d8b9b;
        --ice-blue: #9dbaff;
        --ice-blue-dark: #c2d3ff;
        --ice-blue-wash: #1b2740;
        --ice-on-brand: #0b1420;
        --ice-green: #64d49a;
        --ice-green-dark: #8ee7b8;
        --ice-green-wash: #12291f;
        --ice-amber: #e0ab4b;
        --ice-amber-wash: #2b2313;
        --ice-red: #ff8a80;
        --ice-red-field: #ff6a5a;
        --ice-red-wash: #2e1a18;
        --ice-focus: #7ea4ff;
        --ice-orange-ink: #ff8c26;
        --ice-orange-ink-dark: #ffa54d;
        --ice-panel: #070c12;
        --ice-panel-field: #383d42;
        /*  panel ink 9.38:1, `--ice-panel-ink-3` 5.40:1 */
    }
}

/* Dark — the explicit choice. Same values; kept separate so neither block
   has to know about the other. */
:root[data-theme="dark"] {
    --ice-bg: #0d141d;
    --ice-surface: #151f2b;
    --ice-ink: #eef3f8;
    --ice-ink-soft: #dbe4ed;
    --ice-muted: #a9b6c4;
    --ice-faint: #94a3b3;
    --ice-border: #2c3948;
    --ice-rule: #232f3c;
    --ice-field-border: #7d8b9b;
    --ice-blue: #9dbaff;
    --ice-blue-dark: #c2d3ff;
    --ice-blue-wash: #1b2740;
    --ice-on-brand: #0b1420;
    --ice-green: #64d49a;
    --ice-green-dark: #8ee7b8;
    --ice-green-wash: #12291f;
    --ice-amber: #e0ab4b;
    --ice-amber-wash: #2b2313;
    --ice-red: #ff8a80;
    --ice-red-field: #ff6a5a;
    --ice-red-wash: #2e1a18;
    --ice-focus: #7ea4ff;
    --ice-orange-ink: #ff8c26;
    --ice-orange-ink-dark: #ffa54d;
    --ice-panel: #070c12;
    --ice-panel-field: #383d42;
}

/* `color-scheme` follows the explicit choice too. The tokens are only half of
   a theme: a radio, a checkbox, a select's arrow and a scrollbar are painted by
   the browser, and they read `color-scheme` rather than any of the above. With
   only `light dark` on `:root` they follow the system while everything else
   follows the attribute, which is how an unchecked radio ends up a solid dark
   disc on a white card. Nothing sets `data-theme` in production — this is what
   makes forcing a theme for a check produce the page a reader would get. */
:root[data-theme="light"] {
    color-scheme: light;
}

:root[data-theme="dark"] {
    color-scheme: dark;
}

/* ---------------------------------------------------------------- reset -- */

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

* {
    margin: 0;
}

/* Components that set their own `display` would otherwise out-specify the
   user agent's `[hidden] { display: none }` and stay on screen. */
[hidden] {
    display: none !important;
}

html {
    -webkit-text-size-adjust: 100%;
    scrollbar-color: var(--ice-field-border) var(--ice-bg);
}

/* Page scrollbar. Firefox reads `scrollbar-color`; Chromium/WebKit need the
   pseudo-elements. Kept on the root scrollport so component overflow stays
   under its own component rules. */
html::-webkit-scrollbar:vertical {
    width: 1rem;
}

html::-webkit-scrollbar-track:vertical {
    background-color: var(--ice-bg);
}

html::-webkit-scrollbar-thumb:vertical {
    min-height: 2.875rem;
    background-color: var(--ice-field-border);
    border: 3px solid var(--ice-bg);
    border-radius: var(--radius-pill);
}

html::-webkit-scrollbar-thumb:vertical:hover {
    background-color: var(--ice-muted);
}

@media (forced-colors: active) {
    html {
        scrollbar-color: auto;
    }

    html::-webkit-scrollbar-track:vertical {
        background-color: Canvas;
    }

    html::-webkit-scrollbar-thumb:vertical {
        background-color: CanvasText;
        border-color: Canvas;
    }
}

/* A column, so the footer can be pushed to the bottom of a short page. Without
   it `min-height` only made the body tall enough — the footer still ended right
   after <main> and the leftover height painted as bare page ground below it.
   `svh` rather than `vh` so a mobile toolbar cannot add a scrollbar to a page
   that fits. */
body {
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    background-color: var(--ice-bg);
    color: var(--ice-ink);
    font-family: var(--font-body);
    font-size: var(--text-body);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

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

img,
video {
    height: auto;
}

input,
button,
textarea,
select {
    font: inherit;
    color: inherit;
}

/* ----------------------------------------------------------- typography -- */

/* Headings are the display face; running text is the system stack. The split
   is what makes the page look set rather than defaulted. */
h1,
h2,
h3,
h4 {
    font-family: var(--font-display);
    font-weight: 700;
    letter-spacing: -0.015em;
    text-wrap: balance;
}

h1 {
    font-size: 1.9375rem;
    /* 31 */
    line-height: 1.16;
}

h2 {
    font-size: 1.4375rem;
    /* 23 */
    line-height: 1.25;
}

h3 {
    font-size: var(--text-body);
    line-height: 1.35;
}

h4 {
    font-size: var(--text-dense);
    line-height: 1.35;
}

p,
li,
dd,
dt,
td,
th {
    text-wrap: pretty;
}

strong,
b {
    font-weight: 700;
}

a {
    color: var(--ice-blue);
    text-decoration: underline;
    text-decoration-thickness: max(1px, .06em);
    text-underline-offset: 3px;
}

a:hover {
    color: var(--ice-blue-dark);
}

/* Numbers that are read rather than skimmed — versions, dates, sizes, money.
   Tabular figures so columns of them line up. */
.mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* The small line above a heading that says what kind of thing follows. */
.eyebrow {
    font-family: var(--font-mono);
    font-size: var(--text-label);
    font-weight: 500;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--ice-blue);
}

.lead {
    font-size: var(--text-lead);
    line-height: 1.55;
    color: var(--ice-muted);
    max-width: 68ch;
}

.text-soft {
    color: var(--ice-ink-soft);
}

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

code {
    font-family: var(--font-mono);
    font-size: .9em;
    padding: .1em .35em;
    border-radius: var(--radius-sm);
    background-color: var(--ice-bg);
    overflow-wrap: break-word;
}

/* Prose — any long-form passage. The measure cap is the single biggest
   readability win at this text size. */
.prose {
    max-width: 68ch;
}

.prose> :is(p, ul, ol, dl, figure, table, blockquote)+* {
    margin-block-start: var(--space-4);
}

.prose> :is(h2, h3) {
    margin-block-start: var(--space-6);
    margin-block-end: var(--space-3);
}

.prose :is(ul, ol) {
    padding-inline-start: var(--space-5);
}

.prose li+li {
    margin-block-start: var(--space-2);
}

/* ------------------------------------------------------------- utilities -- */

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.skip-link {
    position: absolute;
    top: var(--space-2);
    left: var(--space-2);
    z-index: 100;
    padding: var(--space-3) var(--space-5);
    border-radius: var(--radius-sm);
    background-color: var(--ice-blue);
    color: var(--ice-on-brand);
    font-weight: 600;
    text-decoration: none;
    transform: translateY(-200%);
}

.skip-link:focus {
    color: var(--ice-on-brand);
    transform: translateY(0);
}

.sprite {
    display: none;
}

/* Inline-block, not the reset's `block`: most icons sit inside a sentence or
   beside a word, and a block-level SVG breaks the line there. */
.icon {
    display: inline-block;
    vertical-align: -.15em;
    width: 1.25em;
    height: 1.25em;
    flex: none;
    fill: currentColor;
}

.icon--ok {
    color: var(--ice-green);
}

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

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

/* On the dark panels the brand focus blue is too close to the ground. */
.hero :focus-visible,
.terminal :focus-visible,
.lightbox :focus-visible {
    outline-color: var(--ice-panel-ink);
}

/* The skip link's target. `tabindex="-1"` is there so the skip link can move
   focus into the content — but it also makes <main> click-focusable, so a click
   anywhere on the page ground focuses it silently, and then the very next key
   press flips Chrome's `:focus-visible` heuristic to "this is a keyboard user"
   and paints a 3px ring around the entire page container. Click beside the
   card, press Enter, and the whole page is suddenly in a blue box.

   A container is not a control, and the ring says nothing a reader can act on
   in either case: what the skip link delivers is the scroll and the next Tab
   landing inside the content. Same reasoning as `.page-head h1:focus` and
   `#changelog-content:focus`, which are focused by script for the same purpose
   and are silent for the same reason. */
main:focus,
main:focus-visible {
    outline: none;
}

/* --------------------------------------------------------------- layout -- */

.container {
    max-width: 71.875rem;
    /* 1150 */
    margin-inline: auto;
    padding-inline: var(--space-5);
}

/* Usable on its own, not only beside `.container`: `<main>` already *is* the
   container, so a page that wants a narrower column puts this on a child of it
   and would otherwise get the cap without the centring — and a second helping
   of the container's inline padding if it took `.container` too. */
.container--narrow {
    max-width: 48rem;
    margin-inline: auto;
}

/* The rhythm between whatever the narrow column holds: a `.page-head` and the
   block under it, or a `<noscript>` notice between the two. Mostly invisible,
   because `.page-head` carries the same margin and adjacent margins collapse
   — but not on `contact` with the script off, where it is the only thing
   holding the notice off the card. It lived in pages/contact.css and applied
   on one of the six templates that use this class. */
.container--narrow> *+* {
    margin-block-start: var(--space-6);
}

/* `flex-grow` alone, not the `flex: 1` shorthand: that would set `flex-basis: 0`
   and make the main column start from zero height, which the tall pages do not
   need and which interacts badly with anything measuring inside it. Growing
   from the content height does the one job required. */
main {
    flex-grow: 1;
    /* `.container` centres with `margin-inline: auto`, and on a flex item auto
       cross-axis margins beat `align-items: stretch` — so without an explicit
       width <main> would size to its content instead of filling the container
       and every page would be as wide as whatever happened to be inside it. */
    width: 100%;
    padding-block: var(--space-6) var(--space-9);
}

/* The page rhythm unit. Sections do not alternate backgrounds any more —
   the page has one ground and the content sits on panels. */
.section {
    margin-block-end: var(--space-7);
}

.section:last-child {
    margin-block-end: 0;
}

/* The heading of a section, with its rule and an optional link to the long
   version of the same thing. */
.section-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    margin-block-end: var(--space-4);
    padding-block-end: var(--space-3);
    border-block-end: 1px solid var(--ice-border);
}

.section-head h2 {
    margin: 0;
}

.section-head__more {
    flex: none;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-dense);
    white-space: nowrap;
}

/* Its one instance is the licence CTA above the comparison table, so it takes
   the CTA colour — as ink, not as a fill: a link cannot be a pill without
   becoming a button. The ink is a different token from the fill for the reason
   in the token block, and this is a link inside a sentence-sized line rather
   than a control, so it stays a link and keeps its arrow.
   See decisions/011-action-colours.md. */
.section-head__more a {
    color: var(--ice-orange-ink);
}

.section-head__more a:hover {
    color: var(--ice-orange-ink-dark);
}

/* A long heading and a long link stop fitting on one line well before the
   layout breaks; the link drops under it rather than forcing a sideways
   scroll. */
@media (max-width: 38.75em) {
    .section-head {
        flex-wrap: wrap;
    }

    .section-head__more {
        white-space: normal;
    }
}

.page-head {
    margin-block-end: var(--space-6);
}

.page-head h1 {
    margin-block-end: var(--space-3);
}

/* The heading is focused after an in-page navigation; it must not draw a ring
   for a focus the reader did not ask for. */
.page-head h1:focus {
    outline: none;
}

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
}

/* Auto-fitting grid: columns appear when there is room for them, so there is
   no breakpoint to keep in sync with the number of items. */
.grid {
    display: grid;
    gap: var(--space-4);
    grid-template-columns: repeat(auto-fit, minmax(17.5rem, 1fr));
}

/* -------------------------------------------------------------- topbar -- */

/* The utility strip: who wrote this, and nothing else. Dark, so the masthead
   below it reads as the start of the page proper.

   It used to hold Documentație and Forum as well, and the rules that dressed
   them are gone with them — the owner's call (2026-08-28) is that a reader
   looking for either should find it in the nav with everything else, not in a
   strip above the logo. `min-height` now carries the bar's height on its own;
   it used to come from the links' `padding-block`, which is why the row never
   had padding of its own. Nothing in here is focusable any more, which is why
   the strip is also out of the panel focus rule above. */
.topbar {
    background-color: var(--ice-panel);
    color: var(--ice-panel-ink-2);
    font-size: var(--text-label);
}

.topbar__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px var(--space-5);
    min-height: 46px;
}

/* ------------------------------------------------------------ masthead -- */

.masthead {
    background-color: var(--ice-surface);
    border-block-end: 1px solid var(--ice-border);
}

.masthead__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-5);
    min-height: 74px;
    padding-block: var(--space-3);
}

/* The 42px mark sets its height; the min-height takes it to the target floor.
   Free — `.masthead__row` is 74px and has the room. */
.wordmark {
    flex: none;
    margin-inline-end: auto;
    display: inline-flex;
    align-items: center;
    min-height: 46px;
    gap: var(--space-3);
    color: var(--ice-ink);
    text-decoration: none;
}

.wordmark img {
    width: 42px;
    height: 42px;
    flex: none;
}

.wordmark__text {
    display: block;
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 1.6875rem;
    /* 27 */
    letter-spacing: -0.03em;
    line-height: 1.05;
}

/* ---------------------------------------------------------- navigation -- */

/* Its own row, full width, tabs underlined. Five items — three pages and the
   two links that leave the site — wrap onto a second row on a narrow phone,
   which is why there is no disclosure to open: everything is visible at every
   width. See docs/components/layout.md. */
.site-nav {
    background-color: var(--ice-surface);
    border-block-end: 1px solid var(--ice-border);
}

.site-nav__list {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.site-nav__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 46px;
    padding-inline: var(--space-4);
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-ui);
    color: var(--ice-ink);
    text-decoration: none;
    border-block-end: 3px solid transparent;
    margin-block-end: -1px;
}

.site-nav__link:hover {
    background-color: var(--ice-blue-wash);
    color: var(--ice-blue);
}

/* Current page: an underline bar AND weight, never colour alone. */
.site-nav__link[aria-current="page"] {
    color: var(--ice-blue);
    border-block-end-color: var(--ice-blue);
    font-weight: 700;
}

/* -------------------------------------------------------------- buttons -- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 46px;
    padding-inline: var(--space-5);
    border: 1px solid var(--ice-blue);
    border-radius: var(--radius-sm);
    background-color: var(--ice-surface);
    color: var(--ice-blue);
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--text-ui);
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition: background-color .12s, color .12s, border-color .12s;
}

.btn:hover {
    background-color: var(--ice-blue);
    color: var(--ice-on-brand);
}

.btn--primary {
    background-color: var(--ice-blue);
    color: var(--ice-on-brand);
}

.btn--primary:hover {
    background-color: var(--ice-blue-dark);
    border-color: var(--ice-blue-dark);
    color: var(--ice-on-brand);
    box-shadow: var(--shadow-press);
}

/* The download. Green rather than brand blue, because it is the one action the
   whole site points at and it was wearing the same colour as an ordinary link —
   see decisions/011-action-colours.md. `--ice-on-brand` carries the label in
   both themes: green inverts exactly as the brand does, a dark fill with white
   on it in light, a pale fill with near-black in dark. */
.btn--download {
    background-color: var(--ice-green);
    border-color: var(--ice-green);
    color: var(--ice-on-brand);
}

.btn--download:hover {
    background-color: var(--ice-green-dark);
    border-color: var(--ice-green-dark);
    color: var(--ice-on-brand);
    box-shadow: var(--shadow-press);
}

/* The alternative action: present, but not competing with the primary one.

   `--ice-field-border`, not the `--ice-border` hairline this used to carry.
   `.btn` fills itself with `--ice-surface`, which is the same fill `.panel` and
   `.card` use — so on a panel this button's box is drawn by its border alone,
   and at 1.37:1 that border was not drawing it. It read as a line of text
   beside a real button. Quiet is a matter of weight, not of invisibility: the
   same token every form control uses to say "this is a control". */
.btn--quiet {
    border-color: var(--ice-field-border);
    color: var(--ice-ink);
}

.btn--quiet:hover {
    border-color: var(--ice-blue);
    background-color: var(--ice-blue-wash);
    color: var(--ice-blue);
}

/* Buying a licence — the masthead CTA and nothing else. Its border is not its
   fill: `--ice-orange` is 2.33:1 on the light masthead and draws no box there,
   so `--ice-orange-edge` draws it. Hover goes lighter rather than darker, which
   is what a bright fill with dark ink on it has to do to keep its label above
   7:1. See decisions/011-action-colours.md. */
.btn--buy {
    background-color: var(--ice-orange);
    border-color: var(--ice-orange-edge);
    color: var(--ice-on-orange);
}

.btn--buy:hover {
    background-color: var(--ice-orange-hover);
    border-color: var(--ice-orange-edge);
    color: var(--ice-on-orange);
    box-shadow: var(--shadow-press);
}

.btn--danger {
    background-color: var(--ice-red);
    border-color: var(--ice-red);
    color: var(--ice-on-brand);
}

.btn--danger:hover {
    background-color: var(--ice-red);
    border-color: var(--ice-red);
    color: var(--ice-on-brand);
    box-shadow: var(--shadow-press);
}

.btn--lg {
    min-height: 52px;
    padding-inline: var(--space-6);
    font-size: var(--text-body);
}

.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: .6;
    cursor: not-allowed;
}

/* ----------------------------------------------------------------- pill -- */

/* A short label in mono caps: the picker's "versiune" tag, the "curentă"
   marker on a downloads row. One shape; the skin is the modifier.

   The changelog carries a third of these and cannot use this class: that
   markup comes out of python-markdown from a file on the server, so it takes
   no classes and pages/changelog.css writes the shape out for that one
   selector. It is the exception, not a fourth variant. */
.pill {
    display: inline-block;
    padding: 1px var(--space-2);
    border-radius: var(--radius-pill);
    font-family: var(--font-mono);
    font-size: var(--text-micro);
    font-weight: 500;
    letter-spacing: .05em;
    text-transform: uppercase;
    vertical-align: middle;
}

/* Outlined, for a label that names a field rather than reporting a state. */
.pill--quiet {
    border: 1px solid var(--ice-border);
    color: var(--ice-muted);
}

/* Filled, for the one row in a table that the reader is looking for. */
.pill--brand {
    background-color: var(--ice-blue);
    color: var(--ice-on-brand);
}

/* ---------------------------------------------------------------- cards -- */

/* Flat: a card is a background plus an edge. No drop shadow — at this text
   size a shadow reads as blur, not as depth. */
.card {
    background-color: var(--ice-surface);
    border: 1px solid var(--ice-border);
    border-radius: var(--radius);
    padding: var(--space-5);
}

.card__body> *+* {
    margin-block-start: var(--space-3);
}

.card h3 {
    margin-block-end: var(--space-3);
}

/* The card's own prose, and only that. As `.card p` this reached every
   paragraph nested anywhere inside — a field's error line, the body of a
   notice — and won, because `.card p` outweighs a single class. The error
   message on `cumpara-licenta` was rendering muted grey at 16px instead of red
   at 18px, and a notice inside a card lost the `--ice-ink` its own component
   gives it. A card styles what it holds, not what its guests bring. */
.card>p,
.card__body>p {
    font-size: var(--text-dense);
    color: var(--ice-muted);
}

.card>p+p,
.card__body>p+p {
    margin-block-start: var(--space-3);
}

.card ul {
    list-style: none;
    display: grid;
    gap: var(--space-2);
    padding: 0;
}

.card li {
    display: flex;
    gap: var(--space-2);
    font-size: var(--text-dense);
    line-height: 1.45;
}

.card li::before {
    content: "–";
    color: var(--ice-blue);
    flex: none;
}

/* ---------------------------------------------------------------- panel -- */

/* A card that holds a passage rather than a summary: wider measure, body-size
   text, used for the sections that are mostly writing. */
.panel {
    background-color: var(--ice-surface);
    border: 1px solid var(--ice-border);
    border-radius: var(--radius);
    padding: var(--space-5) var(--space-6);
}

.panel> *+* {
    margin-block-start: var(--space-3);
}

.panel p {
    font-size: var(--text-lead);
    color: var(--ice-ink-soft);
    max-width: 74ch;
}

/* The unsubscribe card's form, which is the panel's only child on all three
   newsletter pages. Looser than the panel's own rhythm: there are four things
   in it and two of them are buttons the reader came to press. This was
   `.stack`, a utility with exactly one caller — see
   ../../AGENTS.md on utilities. `_newsletter.html` is a shared partial, so its
   one rule lives here for the same reason `.picker__*` and `.field__*` do. */
.newsletter-form> *+* {
    margin-block-start: var(--space-4);
}

/* A passage someone signed: the same panel, opened on the left with a brand
   edge that fades into the page. A full border would have made it another
   form field; this reads as a note. */
.story {
    --story-edge: color-mix(in srgb, var(--ice-blue) 40%, var(--ice-border));
    border-inline-start: 5px solid var(--ice-blue);
    border-radius: var(--radius) 0 0 var(--radius);
    padding: var(--space-5) var(--space-6);
    color: var(--ice-ink-soft);
    background:
        linear-gradient(90deg, var(--story-edge) 0 3.5em, transparent 12em) left top / 100% 1px no-repeat,
        linear-gradient(90deg, var(--story-edge) 0 3.5em, transparent 12em) left bottom / 100% 1px no-repeat,
        linear-gradient(90deg, var(--ice-blue-wash) 0 3.5em, transparent 15em);
}

.story p {
    max-width: 76ch;
    font-size: var(--text-ui);
}

.story p+p {
    margin-block-start: var(--space-3);
}

/* ---------------------------------------------------------------- lists -- */

/* A list whose items are already affirmative — the free version's limits, what
   a licence adds. The marker is `#icon-check` in the markup rather than a
   pseudo-element, because it is a green SVG and not a text glyph. It replaced 14
   FontAwesome `<i>` tags on the terms page, which had been rendering as nothing
   since the library was deleted.

   The element selector is there to outweigh `.prose :is(ul, ol)`, which would
   otherwise indent a list that carries its own markers. */
ul.list-check {
    list-style: none;
    padding-inline-start: 0;
}

.list-check li {
    display: flex;
    gap: var(--space-3);
    align-items: flex-start;
}

.list-check li+li {
    margin-block-start: var(--space-3);
}

/* The check sits on the first line's text, not on the box's top edge. */
.list-check .icon {
    flex: none;
    margin-block-start: .25em;
}

/* ---------------------------------------------------------------- forms -- */

.field+.field,
.field-row+.field,
.field+.field-row,
.field-row+.field-row {
    margin-block-start: var(--space-5);
}

.field__label {
    display: block;
    margin-block-end: var(--space-2);
    font-weight: 600;
}

/* The required marker: a red asterisk beside the label, with the word itself
   kept for screen readers and one line above the group saying what the asterisk
   means. Spelling "(obligatoriu)" out beside all seven labels of a form is a
   word repeated seven times to say the same thing, and it wraps every narrow
   column onto a second line. What the design system rules out is a *bare*
   asterisk — one with nothing anywhere to say what it stands for. */
.field__req {
    color: var(--ice-red);
    font-weight: 700;
}

/* The note that makes the asterisk mean something. One per form, before the
   first field — inline, so a form with a group heading can hang it off that
   heading instead of spending a line of its own on it. */
.field__required-note {
    font-size: var(--text-meta);
    font-weight: 400;
    color: var(--ice-muted);
}

p.field__required-note {
    margin-block-end: var(--space-5);
}

/* Form controls keep their own, darker edge. The reference design has no
   fields at all, and a 1.4:1 hairline round an input is invisible — an edge
   the reader must find is functional, not decorative. */
.field__input {
    display: block;
    width: 100%;
    min-height: 46px;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--ice-field-border);
    border-radius: var(--radius-sm);
    background-color: var(--ice-surface);
    color: var(--ice-ink);
    font-size: var(--text-body);
}

textarea.field__input {
    resize: vertical;
    line-height: 1.6;
}

/* A field that is not part of a form column: a search box over a table, say.
   It keeps the label above the control and only gives up the full width — a
   24rem input under a 1150px page reads as a control rather than a section. */
.field--inline {
    max-width: 24rem;
}

/* The edge is a functional border, not text: its floor is 3:1, which leaves
   room for a red brighter than the one a message has to stay readable in. */
.field__input[aria-invalid="true"] {
    border-width: 2px;
    border-color: var(--ice-red-field);
}

/* The message carries itself. It used to be prefixed by `#icon-x`, which is
   what the flex row was for; the words are the non-colour signal, so the icon
   was decoration standing in front of the sentence a reader needs. */
.field__error {
    margin-block-start: var(--space-2);
    color: var(--ice-red);
    font-size: var(--text-body);
    font-weight: 600;
}

.field-row {
    display: grid;
    gap: var(--space-5);
}

/* Inside a row the grid gap does the spacing. Without this the second field is
   still an adjacent sibling of the first, takes the stacking margin on top of
   the gap, and hangs 22px lower than the column beside it. */
.field-row>.field+.field {
    margin-block-start: 0;
}

@media (min-width: 56.25em) {
    .field-row--2 {
        grid-template-columns: repeat(2, 1fr);
    }

    /* One long field and two short ones: a company name beside its CIF, an
       address beside its town and county. The shape is the old form's — a half
       and two quarters — with the last column given a little more than its
       quarter, because that is where the county select goes and a select that
       clips "Caraș-Severin" is worse than a column that is not exactly a
       quarter. */
    .field-row--2-1-1 {
        grid-template-columns: 2fr 1fr 1.3fr;
    }

    /* A narrow column wraps its label onto a second line where a wide one does
       not — "Județ / Sector (obligatoriu)" against "Sediu social (obligatoriu)"
       — and the inputs of one row then sit on two different baselines.

       Subgrid hands every field in the row the same three rows: label, control,
       and `.field__foot` — whatever sits under the control. Three and not one
       row per thing that can appear there: aligning each kind of message across
       the row would push one field's error down to clear a taller message
       beside it, which puts a message 90px away from the field it is about.
       Inside the foot they stack under their own control and only the cell is
       aligned. The foot holds just the error line today; it is built this way
       so that stays true if anything joins it.

       Where subgrid is missing the fields keep their own flow and only the
       label heights differ, which is exactly the layout this improves on. */
    @supports (grid-template-rows: subgrid) {

        .field-row--2,
        .field-row--2-1-1 {
            grid-template-rows: auto auto auto;
            row-gap: 0;
        }

        .field-row>.field {
            display: grid;
            grid-template-rows: subgrid;
            grid-row: span 3;
        }

        .field-row>.field>.field__label {
            grid-row: 1;
        }

        .field-row>.field>.field__input {
            grid-row: 2;
        }

        .field-row>.field>.field__foot {
            grid-row: 3;
        }
    }
}

.choice {
    margin: 0;
    padding: 0;
    border: 0;
}

.choice__legend {
    padding: 0;
    margin-block-end: var(--space-3);
    font-weight: 600;
}

.choice__option {
    display: flex;
    gap: var(--space-3);
    align-items: center;
    min-height: 46px;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--ice-border);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.choice__option+.choice__option {
    margin-block-start: var(--space-3);
}

.choice__option input {
    width: 1.375rem;
    height: 1.375rem;
    accent-color: var(--ice-blue);
}

.choice__option:has(input:checked) {
    border-width: 2px;
    border-color: var(--ice-blue);
    background-color: var(--ice-blue-wash);
    padding: calc(var(--space-3) - 1px) calc(var(--space-4) - 1px);
}

/* -------------------------------------------------------------- notices -- */

.notice {
    display: flex;
    gap: var(--space-3);
    align-items: flex-start;
    padding: var(--space-4) var(--space-5);
    border-inline-start: 4px solid currentColor;
    border-radius: var(--radius);
    font-size: var(--text-dense);
}

.notice__icon {
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
    margin-block-start: .15em;
    fill: currentColor;
}

.notice__body {
    color: var(--ice-ink);
}

.notice__body> *+* {
    margin-block-start: var(--space-3);
}

.notice--info {
    background-color: var(--ice-blue-wash);
    color: var(--ice-blue);
}

.notice--warning {
    background-color: var(--ice-amber-wash);
    color: var(--ice-amber);
}

.notice--error {
    background-color: var(--ice-red-wash);
    color: var(--ice-red);
}

.notice+.notice {
    margin-block-start: var(--space-4);
}

/* -------------------------------------------------------- result states -- */

/* Replaces a panel body once an async action finishes. */
.result {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    align-items: center;
    padding-block: var(--space-5);
    text-align: center;
}

.result__icon {
    width: 3rem;
    height: 3rem;
    fill: currentColor;
}

.result__title {
    font-family: var(--font-display);
    font-size: var(--text-lead);
    font-weight: 700;
}

.result--success {
    color: var(--ice-green);
}

.result--error {
    color: var(--ice-red);
}

.result--warning {
    color: var(--ice-amber);
}

.result> :is(p, div) {
    color: var(--ice-ink);
}

/* A result is a passage, not a card's supporting note: its prose reads at lead
   size wherever it sits. Three page stylesheets set this on their own selector
   before it lived here. The title is excluded because it sets its own. */
.result p:not(.result__title) {
    font-size: var(--text-lead);
}

/* -------------------------------------------------------------- spinner -- */

.spinner {
    display: inline-block;
    width: 1.25rem;
    height: 1.25rem;
    flex: none;
    border: 3px solid currentColor;
    border-block-start-color: transparent;
    border-radius: 50%;
    animation: spin .8s linear infinite;
}

.spinner--lg {
    width: 3rem;
    height: 3rem;
    border-width: 4px;
}

@keyframes spin {
    to {
        transform: rotate(1turn);
    }
}

/* --------------------------------------------------------------- tables -- */

/* The table is the panel: the border and the radius belong to the wrapper, so
   a wide table scrolls inside its own edge instead of past it. */
.table-scroll {
    /* Positioned so that `position: absolute` descendants are contained by it.
       Without this their containing block is the page, and an absolutely
       positioned box is not clipped by an ancestor it does not belong to: the
       `.visually-hidden` text inside the cells of a wide table keeps its static
       position out at x = 1160 and drags the whole page into horizontal scroll
       — while every element that has a visible box still measures correctly, so
       the overflow audit finds nothing to blame. */
    position: relative;
    overflow-x: auto;
    scrollbar-color: auto;
    background-color: var(--ice-surface);
    border: 1px solid var(--ice-border);
    border-radius: var(--radius);
}

/* A sticky header and a horizontal scroll container are mutually exclusive.
   `overflow-x: auto` forces `overflow-y` to compute to `auto` as well, which
   makes the wrapper the scrollport the sticky cell sticks to — and that
   scrollport never scrolls vertically, so the header just leaves with the page.
   `overflow: hidden` is a scroll container too; only `visible` works.

   So a table that wants a sticky header gives the wrapper up at the widths
   where it does not need one. Opt in per table, never globally: this table's
   min-content is 346px and it fits from about 390px, but the seven-column one
   on /downloads does not fit anywhere and must keep scrolling. */
@media (min-width: 37.5em) {
    .table-scroll--sticky-head {
        overflow: visible;
    }

    /* Nothing clips the table to the wrapper's radius any more, so everything
       the table paints out to its own edges squares off the wrapper's corners:
       the header's background at the top, a hovered last row at the bottom.
       `clip-path` puts the clip back without putting the scroll container back
       with it. It is the table that gets clipped, to its own box, rounded one
       step tighter than the wrapper because it sits inside that 1px border.

       Rounding the header cells themselves is what this replaces. Those corners
       belong to the cells, so they leave with them: the moment the header
       sticks, it carries two rounded corners down the middle of the table and
       cuts white notches into a bar that should read as a straight edge. */
    .table-scroll--sticky-head .table {
        clip-path: inset(0 round calc(var(--radius) - 1px));
    }
}

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-dense);
}

.table :is(th, td) {
    padding: var(--space-3) var(--space-4);
    border-block-end: 1px solid var(--ice-rule);
    text-align: start;
    vertical-align: middle;
}

/* The header row stays put while the body scrolls under it. On a 21-row
   comparison you otherwise end up counting columns to work out which one you
   are reading. It needs its own background — a sticky cell is painted over the
   rows passing beneath it — and a border drawn as a shadow, because a collapsed
   table's own borders do not travel with a sticky cell. */
.table thead th {
    position: sticky;
    inset-block-start: 0;
    z-index: 1;
    background-color: var(--ice-bg);
    box-shadow: inset 0 -1px 0 var(--ice-border);
    font-family: var(--font-display);
    font-size: var(--text-meta);
    font-weight: 700;
    color: var(--ice-ink);
    white-space: nowrap;
}

.table tbody tr:last-child :is(th, td) {
    border-block-end: 0;
}

.table tbody th {
    font-weight: 400;
    color: var(--ice-ink);
}

.table td.is-center {
    text-align: center;
}

.table__note {
    margin-block-start: var(--space-3);
    font-size: var(--text-meta);
    color: var(--ice-muted);
    max-width: 78ch;
}

/* ------------------------------------------------------------ file link -- */

/* A download link that is an icon plus a file name, inside a table cell. The
   46px floor is the whole point: the name alone is a one-line target. Both
   tables that carry one hang their own padding on top — see
   pages/downloads.css and pages/icefact-server.css. */
.file-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 46px;
    color: var(--ice-green);
    text-decoration: none;
}

.file-link:hover {
    color: var(--ice-green-dark);
    text-decoration: underline;
}

/* ------------------------------------------------------------- terminal -- */

/* Dark in both themes: it is a picture of a terminal, not a surface.
   A column, not a positioned box: the copy button used to be absolute in the
   top-right corner and overlapped the first command as soon as the block was
   narrow — which it always is, inside the hero panel. In flow it cannot. */
.terminal {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
    padding: var(--space-4);
    border-radius: var(--radius-sm);
    background-color: var(--ice-term-bg);
    color: var(--ice-term-ink);
    font-family: var(--font-mono);
    font-size: var(--text-dense);
    line-height: 1.7;
}

/* Long commands wrap with a hanging indent, like a real terminal, instead of
   scrolling sideways. */
.terminal__cmd {
    margin: 0;
    padding-inline-start: 1.5rem;
    text-indent: -1.5rem;
    font: inherit;
    white-space: pre-wrap;
    overflow-wrap: break-word;
}

/* The commands are one run of text; the gap belongs between them and the
   button, not between every line. */
.terminal__cmd+.terminal__cmd {
    margin-block-start: calc(var(--space-3) * -1);
}

/* The prompt stays out of the selection, so a manual copy takes only the
   command. */
.terminal__cmd::before {
    content: "$ ";
    color: var(--ice-term-muted);
    user-select: none;
}

/* Last in the DOM and last on screen, so tab order matches reading order:
   the commands, then the thing that copies them. */
.terminal__copy {
    align-self: flex-end;
    display: inline-flex;
    gap: var(--space-2);
    align-items: center;
    min-height: 46px;
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--ice-panel-line);
    border-radius: var(--radius-sm);
    background-color: transparent;
    color: var(--ice-panel-ink);
    font-family: var(--font-body);
    font-size: var(--text-meta);
    cursor: pointer;
}

.terminal__copy:hover {
    background-color: rgba(255, 255, 255, .12);
}

.terminal__copy.is-copied {
    color: var(--ice-term-ink);
    border-color: var(--ice-term-ink);
}

/* ------------------------------------------------------ download picker -- */

/* "Choose a platform, then download." Everything here is structure and light-
   surface colour; the hero's translucent dark skin is a handful of overrides in
   pages/index.css. Lives in site.css because two pages carry the control and a
   second copy of it would drift. See docs/components/download-picker.md. */

.picker__title {
    font-size: var(--text-lead);
}

/* Version and date sit above the field: they answer "is this newer than what I
   already have?", which is asked before the platform is chosen. */
.picker__line {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-3);
    margin-block: var(--space-3) var(--space-4);
    font-size: var(--text-meta);
    color: var(--ice-ink-soft);
}

/* `.pill.pill--quiet` carries the shape; this is only what a flex child of
   `.picker__line` needs on top of it. */
.picker__tag {
    flex: none;
}

.picker__date {
    font-size: var(--text-label);
    color: var(--ice-muted);
}

.picker__link {
    width: 100%;
    margin-block-start: var(--space-4);
}

.picker__size {
    margin-block-start: var(--space-3);
    font-size: var(--text-meta);
    color: var(--ice-muted);
}

/* Not `:not(:empty)`: the panels are always in the DOM and only `hidden`
   changes, so an empty-looking container is never actually empty. The changelog
   picker carries panels for Windows and macOS only, so choosing a Linux build
   there hides every one of them — and a rule above nothing reads as a mistake. */
.picker__notes:has(> :not([hidden])) {
    margin-block-start: var(--space-4);
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--ice-rule);
}

.picker__notes> :not([hidden])~ :not([hidden]) {
    margin-block-start: var(--space-4);
}

.picker__note-intro {
    margin-block-end: var(--space-3);
    font-size: var(--text-meta);
    color: var(--ice-muted);
}

/* ----------------------------------------------------------- pagination -- */

.pagination__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.pagination__link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 46px;
    min-height: 46px;
    padding-inline: var(--space-3);
    border: 1px solid var(--ice-border);
    border-radius: var(--radius-sm);
    background-color: var(--ice-surface);
    color: var(--ice-blue);
    font-family: var(--font-display);
    font-weight: 600;
    text-decoration: none;
}

.pagination__link:hover {
    background-color: var(--ice-blue-wash);
}

.pagination__link[aria-current="page"] {
    background-color: var(--ice-blue);
    border-color: var(--ice-blue);
    color: var(--ice-on-brand);
}

.pagination__link[aria-disabled="true"] {
    color: var(--ice-faint);
    border-color: var(--ice-rule);
    pointer-events: none;
}

/* ------------------------------------------------------------- lightbox -- */

/* See docs/components/lightbox.md. The dialog is built by site.js; the only
   thing a template writes is the link and `data-lightbox` on it. */

.zoom {
    display: block;
    cursor: zoom-in;
}

/* The dialog paints the scrim itself and `::backdrop` is left alone. A backdrop
   did not inherit custom properties from its originating element until
   recently, so `var(--ice-scrim)` there resolves to nothing on the browsers
   most likely to be a reader's — a transparent sheet over an unreadable page.
   Stretched to `inset: 0` the dialog covers the viewport, and the backdrop is
   never seen.

   A column, not a centring box: the close button takes the first row and the
   figure takes the rest. Absolutely positioning the button instead puts it on
   top of a picture that is trying to fill the same space, and a tall screenshot
   lands underneath it.

   `width` and `height` are set, and that is not belt and braces. The UA gives
   every dialog `height: fit-content` and caps it with a `max-height` of about
   the viewport; lifting the cap without replacing the sizing leaves nothing
   holding it in at all, and a 1152px screenshot makes the dialog 1232px tall in
   a 1031px window — the picture runs off the bottom of the screen and the close
   button goes with it. `inset: 0` does not save it: an inset only resolves the
   height when the height is `auto`, and `fit-content` is not `auto`. */
.lightbox {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin: 0;
    max-width: none;
    max-height: none;
    padding: var(--space-5);
    border: 0;
    background-color: var(--ice-scrim);
    color: var(--ice-panel-ink);
    cursor: zoom-out;

    /* 120ms. `display` and `overlay` are what actually change here, and both
       are discrete — without `allow-discrete` the dialog is simply gone before
       the opacity has anywhere to go, so the fade never runs in either
       direction. Closed is the resting state; `[open]` below is the end of the
       transition and `@starting-style` is its beginning. */
    opacity: 0;
    transition: opacity .12s, display .12s allow-discrete, overlay .12s allow-discrete;
}

/* Not optional. `display: flex` above would otherwise override the UA's
   `dialog:not([open]) { display: none }` and leave the dialog painted over
   every page all the time. */
.lightbox:not([open]) {
    display: none;
}

.lightbox[open] {
    opacity: 1;
}

/* Where `@starting-style` is not supported the dialog appears at once — which
   is also what the reduced-motion rule at the end of this file produces. The
   degradation and the preference are the same picture, which is what makes
   this safe to use rather than something to feature-detect. */
@starting-style {
    .lightbox[open] {
        opacity: 0;
    }
}

/* The page behind must not scroll. `[open]` is the attribute the browser sets
   itself, so there is nothing for a state class to add. */
html:has(.lightbox[open]) {
    overflow: hidden;
}

/* `min-height: 0` so the picture can be told to shrink: a flex item's floor is
   its content size by default, and a 1152px-tall screenshot would push the
   figure past the bottom of the screen rather than scale down inside it. */
.lightbox__figure {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    margin: 0;
}

/* Square, for the same reason `.hero__shot img` is: the picture is a window
   with square corners of its own, and a rounded frame clips them. `4aaa653`
   settled that on the page and this rule quietly undid it at full size, where a
   1542px-wide screenshot makes the mismatch larger, not smaller. No radius on a
   screenshot, anywhere. */
.lightbox__img {
    min-height: 0;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    cursor: default;
}

.lightbox__caption {
    flex: none;
    max-width: 70ch;
    color: var(--ice-panel-ink-2);
    font-size: var(--text-meta);
    text-align: center;
}

/* Icon plus label, like the terminal's copy button. A bare glyph is the thing
   the external-link icon was removed for. */
.lightbox__close {
    flex: none;
    align-self: flex-end;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 46px;
    padding-inline: var(--space-4);
    border: 1px solid var(--ice-panel-line);
    border-radius: var(--radius-sm);
    background-color: transparent;
    color: var(--ice-panel-ink);
    font-family: var(--font-display);
    font-size: var(--text-ui);
    font-weight: 500;
    cursor: pointer;
}

.lightbox__close:hover {
    background-color: rgba(255, 255, 255, .14);
    color: var(--ice-panel-white);
}

/* --------------------------------------------------------------- footer -- */

/* Light, like the rest of the page. The dark band on this site is the utility
   strip at the top; a second one at the bottom would have framed the content
   in a box it does not need. */
.site-footer {
    background-color: var(--ice-surface);
    border-block-start: 1px solid var(--ice-border);
    padding-block: var(--space-7);
    font-size: var(--text-dense);
}

.site-footer__cols {
    display: grid;
    gap: var(--space-6);
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}

.site-footer h2 {
    margin-block-end: var(--space-3);
    font-family: var(--font-display);
    font-size: var(--text-label);
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ice-faint);
}

/* `line-height` here, not on the anchor: the anchor is an inline-block, so it
   is the `<li>`'s own line box that sets the row's height, and tightening only
   the link leaves each row 25.6px tall with a 17.6px link floating inside it.
   One declaration on the list does both — the li and the link inherit it. */
.site-footer__list {
    display: grid;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
    line-height: 1.1;
}

.site-footer a {
    color: var(--ice-ink);
    text-decoration: none;
}

.site-footer a:hover {
    color: var(--ice-blue);
    text-decoration: underline;
}

/* A line of text, not a control: `line-height: 1.1` above makes each link
   17.6px tall, and with the list's 8px between them the rows land 25.6px apart
   — which is exactly what 16px text at the site's 1.6 line-height measures, so
   the column reads at the same rhythm as the fiscal block beside it. It was
   46px, then 32; the owner's call (2026-08-28) is a footer that stacks like
   ordinary text.

   **This still passes WCAG 2.2 SC 2.5.8**, and it is the 8px that carries it:
   the targets are undersized at 17.6px, so they qualify under the spacing
   exception instead — a 24px circle centred on each does not reach the next
   one, because the centres are 25.6px apart. Tighten the gap and that stops
   being true, so the gap is the number not to touch.

   `inline-flex`, `align-items` and a `gap` were all here to centre text in a
   fixed-height box beside an icon. There is no icon and no box. */
.site-footer__list a {
    display: inline-block;
}

.site-footer__fiscal {
    margin-block-start: var(--space-3);
    font-size: var(--text-meta);
    color: var(--ice-muted);
}

.site-footer__legal {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-5);
    margin-block-start: var(--space-6);
    padding-block-start: var(--space-4);
    border-block-start: 1px solid var(--ice-rule);
    font-size: var(--text-meta);
    color: var(--ice-muted);
}

.site-footer__payments {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
    margin-inline-start: auto;
}

.site-footer__payments img {
    height: 24px;
    width: auto;
}

/* ------------------------------------------------- interactive feedback -- */

/* Movement only where there is actually something to click, so no false
   affordances appear. The table row lights up on hover because at five dense
   columns it is what keeps the eye on the line. */
a,
.site-nav__link,
.site-footer a,
.table tbody tr {
    transition: color .12s, background-color .12s, border-color .12s;
}

.table tbody tr:hover {
    background-color: var(--ice-rule);
}

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

@media (prefers-reduced-motion: reduce) {

    *,
    *::before,
    *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }

    /* the spinner stops; the accompanying text carries the state */
    .spinner {
        border-block-start-color: currentColor;
        opacity: .5;
    }
}
