/* ==========================================================================
   devnest.sk — the shared stylesheet
   --------------------------------------------------------------------------
   Everything structural lives here: the tokens, the header, the sections, the
   way a screenshot is framed. Nothing in this file names a product.

   The per-app skins are in themes.css. They only re-declare colour tokens, so
   the header, the spacing and the components stay identical on every page —
   which is the point: the frame is DevNest, the colour is the app.

   Appearance: the page follows the system setting until the visitor picks a
   side, and the choice is stored. A media query cannot appear in a selector
   list, so every dark rule needs a second copy for the system setting. Only
   the first copy is written by hand. tools/chrome.py writes the second one
   into the chrome:dark-system region right after it.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Fonts
   -------------------------------------------------------------------------- */

/* The fonts are served from this site, not from Google Fonts, so a visit
   reaches no server but ours. The files are the ones Google Fonts served in
   September 2026, with the same split: "latin" covers English, "latin-ext"
   covers letters such as č and ň on the Slovak pages. A browser downloads a
   latin-ext file only when a page uses one of its characters. The unicode
   ranges below are copied from Google's own stylesheet. Both fonts are under
   the SIL Open Font License; the licence texts are in assets/fonts/.

   Instrument Sans is a variable font: one file carries every weight from
   400 to 700. IBM Plex Mono has one file per weight. */

@font-face {
    font-family: "Instrument Sans";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("/assets/fonts/instrument-sans-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: "Instrument Sans";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("/assets/fonts/instrument-sans-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: 400;
    font-display: swap;
    src: url("/assets/fonts/ibm-plex-mono-400-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: 400;
    font-display: swap;
    src: url("/assets/fonts/ibm-plex-mono-400-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("/assets/fonts/ibm-plex-mono-500-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("/assets/fonts/ibm-plex-mono-500-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;
}

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
    color-scheme: light;

    /* Surfaces, light: the greys macOS itself uses for windows and sheets */
    --bg:          #f4f5f7;
    --bg-2:        #eceef2;
    --surface:     #ffffff;
    --surface-2:   #f7f8fa;
    --line:        rgba(9, 16, 26, .10);
    --line-strong: rgba(9, 16, 26, .18);
    --scrim:       rgba(244, 245, 247, .78);

    /* Ink */
    --ink:   #0e1319;
    --ink-2: #39424f;
    --muted: #6a7482;

    /* Accent — overridden per app in themes.css */
    --accent:      #0a6fd0;
    --accent-2:    #38a3f5;
    --accent-ink:  #ffffff;
    --accent-soft: rgba(10, 111, 208, .10);
    --accent-line: rgba(10, 111, 208, .35);
    --good:        #3f9b62;
    --warn:        #d98324;
    --bad:         #d0342c;

    /* The mat an older, shadowless capture sits on. It stays light in both
       page appearances, because the windows on it were photographed light: a
       bright window on a dark page reads as a product photo, a bright window
       on a mid-grey one reads as a mistake. A capture that brings its own
       shadow needs none of this — see `shot--free` below. */
    --mat:      #e7e9ee;
    --mat-line: rgba(9, 16, 26, .14);

    /* Type */
    --font-sans: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
    --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

    /* Metrics */
    --maxw:      1220px;
    --maxw-wide: 1400px;
    --gutter: clamp(1.15rem, 4vw, 2.5rem);
    --nav-h: 58px;
    --r-lg: 18px;
    --r-md: 13px;
    --r-sm: 9px;

    --shadow-sm: 0 1px 2px rgba(9, 16, 26, .06), 0 6px 16px -10px rgba(9, 16, 26, .22);
    --shadow-md: 0 2px 6px rgba(9, 16, 26, .06), 0 22px 44px -26px rgba(9, 16, 26, .35);
    --shadow-lg: 0 4px 10px rgba(9, 16, 26, .07), 0 50px 90px -40px rgba(9, 16, 26, .45);

    --ease: cubic-bezier(.2, .7, .3, 1);
}

/* Dark — chosen explicitly */
:root[data-appearance="dark"] {
    color-scheme: dark;

    --bg:          #21242a;
    --bg-2:        #262a32;
    --surface:     #2c303a;
    --surface-2:   #333844;
    --line:        rgba(255, 255, 255, .09);
    --line-strong: rgba(255, 255, 255, .17);
    --scrim:       rgba(33, 36, 42, .74);

    --ink:   #eef1f6;
    --ink-2: #c2cad6;
    --muted: #8a94a3;

    --accent:      #46a6ff;
    --accent-2:    #7cc3ff;
    --accent-ink:  #04121f;
    --accent-soft: rgba(70, 166, 255, .15);
    --accent-line: rgba(70, 166, 255, .4);
    --good:        #6fcf90;
    --warn:        #e8a34d;
    --bad:         #ff6b62;

    --mat:      #dfe3ea;
    --mat-line: rgba(255, 255, 255, .1);

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5), 0 8px 20px -12px rgba(0, 0, 0, .7);
    --shadow-md: 0 2px 6px rgba(0, 0, 0, .5), 0 26px 50px -28px rgba(0, 0, 0, .85);
    --shadow-lg: 0 4px 12px rgba(0, 0, 0, .55), 0 60px 100px -44px rgba(0, 0, 0, .95);
}

/* Dark — the system setting, when the visitor has not chosen. The region is
   written by tools/chrome.py from the rule above: change that one. */
/* chrome:dark-system */
@media (prefers-color-scheme: dark) {
    :root:not([data-appearance="light"]) {
        color-scheme: dark;

        --bg:          #21242a;
        --bg-2:        #262a32;
        --surface:     #2c303a;
        --surface-2:   #333844;
        --line:        rgba(255, 255, 255, .09);
        --line-strong: rgba(255, 255, 255, .17);
        --scrim:       rgba(33, 36, 42, .74);

        --ink:   #eef1f6;
        --ink-2: #c2cad6;
        --muted: #8a94a3;

        --accent:      #46a6ff;
        --accent-2:    #7cc3ff;
        --accent-ink:  #04121f;
        --accent-soft: rgba(70, 166, 255, .15);
        --accent-line: rgba(70, 166, 255, .4);
        --good:        #6fcf90;
        --warn:        #e8a34d;
        --bad:         #ff6b62;

        --mat:      #dfe3ea;
        --mat-line: rgba(255, 255, 255, .1);

        --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5), 0 8px 20px -12px rgba(0, 0, 0, .7);
        --shadow-md: 0 2px 6px rgba(0, 0, 0, .5), 0 26px 50px -28px rgba(0, 0, 0, .85);
        --shadow-lg: 0 4px 12px rgba(0, 0, 0, .55), 0 60px 100px -44px rgba(0, 0, 0, .95);
    }
}
/* /chrome:dark-system */

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

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

html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    scroll-padding-top: calc(var(--nav-h) + 32px);
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: 1.0625rem;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

h1, h2, h3, h4, h5 {
    margin: 0;
    font-weight: 600;
    letter-spacing: -.028em;
    line-height: 1.12;
    text-wrap: balance;
}

p { margin: 0; }
a { color: inherit; }
img, svg, video { max-width: 100%; display: block; }
code { font-family: var(--font-mono); font-size: .92em; }

::selection { background: var(--accent); color: var(--accent-ink); }

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

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 200;
    background: var(--accent);
    color: var(--accent-ink);
    padding: .6rem 1rem;
}
.skip-link:focus { left: 0; }

.wrap {
    width: 100%;
    max-width: var(--maxw);
    margin-inline: auto;
    padding-inline: var(--gutter);
}
.wrap--wide { max-width: var(--maxw-wide); }

/* --------------------------------------------------------------------------
   3. Type helpers
   -------------------------------------------------------------------------- */

.eyebrow {
    display: flex;
    align-items: center;
    gap: .6rem;
    margin-bottom: 1rem;
    font-family: var(--font-mono);
    font-size: .71rem;
    font-weight: 500;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--muted);
}
.eyebrow::before {
    content: "";
    flex: none;
    width: 20px;
    height: 1px;
    background: var(--accent);
    opacity: .8;
}

.h-display {
    font-size: clamp(2.45rem, 5.6vw, 4.2rem);
    letter-spacing: -.042em;
    line-height: 1.02;
}
.h-section { font-size: clamp(1.85rem, 3.3vw, 2.7rem); letter-spacing: -.033em; }
.h-sub     { font-size: clamp(1.35rem, 2.1vw, 1.85rem); letter-spacing: -.03em; }

.lead {
    max-width: 58ch;
    /* .lead almost always follows an .h-section heading. The vw share here is
       .h-section's own share (3.3vw) times the ratio of the two max sizes
       (1.2rem / 2.7rem), so both fonts stop growing at the same viewport
       width, about 1309px (2.7rem*16 / 0.033). At 1.3vw they did not agree:
       the lead stopped growing at 1477px, well above where the heading even
       starts changing, so the lead moved while the heading still sat still. */
    font-size: clamp(1.03rem, 1.4667vw, 1.2rem);
    line-height: 1.55;
    color: var(--ink-2);
}

.muted { color: var(--muted); }
.mono  { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   4. Header
   -------------------------------------------------------------------------- */

/* The header and the mobile sheet that opens under it are the same frosted
   glass. The sheet sits inside the header. So the header's glass is drawn on
   a ::before layer behind it, not on .nav itself. A backdrop-filter on .nav
   would stop the sheet's own blur from reaching the page. Then only the
   semi-transparent colour is left, and the page text shows through sharp. */
.nav::before,
.sheet {
    background: var(--scrim);
    backdrop-filter: saturate(1.6) blur(16px);
    -webkit-backdrop-filter: saturate(1.6) blur(16px);
}

.nav {
    position: fixed;
    inset: 0 0 auto 0;
    z-index: 60;
    height: var(--nav-h);
    border-bottom: 1px solid transparent;
    transition: border-color .3s var(--ease);
}
.nav::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
}
.nav.is-stuck { border-bottom-color: var(--line); }

.nav__inner {
    height: var(--nav-h);
    display: flex;
    align-items: center;
    gap: 1.1rem;
    max-width: var(--maxw-wide);
}

.brand {
    display: flex;
    align-items: center;
    gap: .55rem;
    flex: none;
    text-decoration: none;
    font-weight: 600;
    font-size: 1.02rem;
    letter-spacing: -.022em;
}
.brand__logo { display: block; }
.brand__logo img { display: block; height: 46px; width: auto; }
.brand__sep     { color: var(--muted); font-weight: 400; }
.brand__product { color: var(--muted); font-weight: 500; }

.nav__links {
    display: flex;
    align-items: center;
    gap: .35rem;
    margin-left: auto;
    font-size: .93rem;
}
.nav__links > a,
.menu__btn {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .45rem .7rem;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--muted);
    font: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: color .18s var(--ease), background .18s var(--ease);
}
.nav__links > a:hover,
.menu__btn:hover,
.menu.is-open .menu__btn { color: var(--ink); background: var(--accent-soft); }
.nav__links .is-current { color: var(--ink); }

.chev {
    width: 5px; height: 5px;
    border-right: 1.6px solid currentColor;
    border-bottom: 1.6px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    transition: transform .25s var(--ease);
}
.menu.is-open .chev { transform: rotate(225deg) translate(-1px, -1px); }

/* Apps menu ------------------------------------------------------------- */

.menu { position: relative; }

.menu__panel {
    position: absolute;
    top: calc(100% + 8px);
    left: 50%;
    width: min(360px, calc(100vw - 2rem));
    padding: .4rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-md);
    transform: translate(-50%, -6px) scale(.98);
    transform-origin: top center;
    opacity: 0;
    visibility: hidden;      /* keeps the links out of the reading order too */
    pointer-events: none;
    transition: opacity .18s var(--ease), transform .18s var(--ease), visibility .18s;
}
.menu.is-open .menu__panel {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, 0) scale(1);
    pointer-events: auto;
}
.menu__panel[hidden] { display: block; }   /* animated, not toggled off */
html:not(.js) .menu__panel[hidden] { display: none; }

.menu__row {
    display: flex;
    align-items: center;
    gap: .7rem;
    padding: .55rem .6rem;
    border-radius: var(--r-sm);
    text-decoration: none;
    transition: background .16s var(--ease);
}
.menu__row:hover { background: var(--surface-2); }
.menu__row.is-current { background: var(--accent-soft); }
.menu__icon { width: 30px; height: 30px; border-radius: 7px; flex: none; }

/* An app icon that has a dark version of its own is a <picture> of two
   files (app_icon in tools/chrome.py). The picture takes the size and the
   corner, and the image inside fills it. */
.appicon img { width: 100%; height: 100%; border-radius: inherit; }
.menu__text { display: grid; gap: .1rem; min-width: 0; }
.menu__name {
    display: flex;
    align-items: center;
    gap: .4rem;
    font-weight: 600;
    font-size: .93rem;
    color: var(--ink);
}
.menu__sub { font-size: .8rem; line-height: 1.35; color: var(--muted); }

.dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    flex: none;
}
.dot--shipping { background: var(--good); }
.dot--beta     { background: var(--warn); }
.dot--building { background: var(--muted); }

/* Buttons in the header -------------------------------------------------- */

.iconbtn {
    display: grid;
    place-items: center;
    flex: none;
    width: 33px; height: 33px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface-2);
    color: var(--ink-2);
    cursor: pointer;
    transition: color .18s var(--ease), border-color .18s var(--ease);
}
.iconbtn:hover { color: var(--ink); border-color: var(--line-strong); }
.iconbtn svg { width: 17px; height: 17px; }

/* Which of the three icons shows follows the explicit choice only — the
   system icon covers both light and dark system defaults, so this needs no
   prefers-color-scheme query. */
.appearance .icon-sun,
.appearance .icon-moon,
.appearance .icon-system { display: none; }

:root[data-appearance="light"] .appearance .icon-sun    { display: block; }
:root[data-appearance="dark"]  .appearance .icon-moon   { display: block; }
:root:not([data-appearance]) .appearance .icon-system   { display: block; }

/* A product page's own store badge, repeated in the header once the page's
   own has scrolled away. site.js adds .has-cta to the header, and the badge
   fades in and out. Only the opacity changes: Apple asks that the badge
   itself is not animated, so it never moves.
   The hidden badge keeps its place, so nothing else in the header moves
   when it comes and goes. Both auto margins centre it in the free space
   between the logo and the links. On the desktop layout it is centred
   on the header itself instead (see the media queries below).
   40px is Apple's smallest allowed badge height on screen. It leaves 9px
   above and below in the 58px header. At 40px the badge corner reads as
   6.8px (see .store-badge). */
.nav__cta {
    display: flex;
    align-items: center;
    flex: none;
    margin-inline: auto;
    opacity: 0;
    visibility: hidden;     /* keeps the hidden link out of the tab order */
    transition: opacity .25s var(--ease), visibility .25s;
}
.nav.has-cta .nav__cta { opacity: 1; visibility: visible; }
.nav__cta + .nav__links { margin-left: 0; }
.nav__cta .store-badge { border-radius: 7px; }
.nav__cta .store-badge img { height: 40px; }

.burger { display: none; position: relative; }
.burger span {
    position: absolute;
    width: 15px; height: 1.6px;
    background: currentColor;
    border-radius: 2px;
    transition: transform .22s var(--ease), opacity .22s var(--ease);
}
.burger span:first-child { transform: translateY(-3.5px); }
.burger span:last-child  { transform: translateY(3.5px); }
.burger[aria-expanded="true"] span:first-child { transform: rotate(45deg); }
.burger[aria-expanded="true"] span:last-child  { transform: rotate(-45deg); }

/* Mobile sheet ----------------------------------------------------------- */

.sheet {
    position: fixed;
    inset: var(--nav-h) 0 auto 0;
    max-height: calc(100dvh - var(--nav-h));
    overflow-y: auto;
    border-bottom: 1px solid var(--line);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .2s var(--ease), transform .2s var(--ease), visibility .2s;
}
.sheet[hidden] { display: block; }
html:not(.js) .sheet[hidden] { display: none; }
.sheet.is-open { opacity: 1; visibility: visible; transform: none; pointer-events: auto; }
.sheet__inner { padding-block: 1rem 1.6rem; display: grid; gap: .15rem; }
.sheet__head {
    margin: 1rem 0 .3rem;
    font-family: var(--font-mono);
    font-size: .7rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--muted);
}
.sheet__link {
    padding: .6rem .6rem;
    border-radius: var(--r-sm);
    text-decoration: none;
    font-weight: 500;
}
.sheet__link:hover { background: var(--surface-2); }

@media (max-width: 860px) {
    .nav__links { display: none; }
    .appearance { margin-left: auto; }
    .nav__cta ~ .appearance { margin-left: 0; }
    .burger { display: grid; }
}

/* On the desktop layout the page's badge is centred on the header, in
   line with the centred hero below it. It leaves the row, so the links go
   back to their own auto margin. */
@media (min-width: 861px) {
    .nav__inner { position: relative; }
    .nav__cta {
        position: absolute;
        top: 50%;
        left: 50%;
        margin: 0;
        transform: translate(-50%, -50%);
    }
    .nav__cta + .nav__links { margin-left: auto; }
}

/* Below 560px nothing is repeated. Measured on the narrow layout, the
   header needs 495px for the badge to fit beside the logo, the appearance
   switch and the burger. */
@media (max-width: 559px) {
    .nav__cta { display: none; }
    .nav__cta ~ .appearance { margin-left: auto; }
}

/* --------------------------------------------------------------------------
   5. Buttons
   -------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    padding: .78rem 1.35rem;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    font: inherit;
    font-weight: 500;
    font-size: .97rem;
    text-decoration: none;
    cursor: pointer;
    transition: transform .16s var(--ease), background .18s var(--ease),
                border-color .18s var(--ease), box-shadow .18s var(--ease);
}
/* The icons come from assets/img/icons.svg. Each place that shows one sets
   its size and its line weight, as here. */
.btn svg { width: 16px; height: 16px; flex: none; stroke-width: 2; }
.btn:active { transform: translateY(1px); }

.btn--primary {
    background: var(--accent);
    color: var(--accent-ink);
    box-shadow: 0 1px 0 rgba(255, 255, 255, .18) inset, var(--shadow-sm);
}
.btn--primary:hover { background: color-mix(in srgb, var(--accent) 88%, var(--ink)); }
.btn--primary:hover svg { transform: translateX(2px); transition: transform .18s var(--ease); }

.btn--ghost {
    border-color: var(--line-strong);
    color: var(--ink);
    background: transparent;
}
.btn--ghost:hover { background: var(--surface); border-color: var(--accent-line); }

/* Apple's official App Store badge, used as a link in place of a button.
   The artwork is Apple's file, unchanged. Apple's guidelines say not to
   modify or animate it, so it has no hover effect. They also ask for at
   least 40px of height on screen. 52px is the measured height of a .btn,
   so the badge lines up with a button next to it.
   The badge's corner can not change, so a button beside it takes the
   badge's corner instead. The corner curve passes 1.997 units in from both
   edges at 45° (measured from the SVG path). A circle through that point has
   a radius of 1.997 / (1 - cos 45°) = 6.8 units. At 52px the badge is 1.3×
   its 40-unit artwork, so the corner reads as about 8.9px, which is --r-sm. */
.store-badge {
    display: inline-flex;
    flex: none;
    border-radius: var(--r-sm);
}
.store-badge img { display: block; width: auto; height: 52px; }
.cta-row:has(.store-badge) .btn { border-radius: var(--r-sm); }

/* A link that reads as the next step, without being a button */
.arrowlink {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    color: var(--accent);
    font-weight: 500;
    text-decoration: none;
}
.arrowlink svg { width: 15px; height: 15px; stroke-width: 2.2; transition: transform .22s var(--ease); }
.arrowlink:hover { text-decoration: underline; text-underline-offset: 4px; }
.arrowlink:hover svg { transform: translateX(3px); }

.btn__note {
    font-family: var(--font-mono);
    font-size: .73rem;
    letter-spacing: .04em;
    color: var(--muted);
}

.cta-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .8rem 1rem;
    margin-top: 2rem;
}

/* --------------------------------------------------------------------------
   6. Sections
   -------------------------------------------------------------------------- */

.section { position: relative; padding-block: clamp(4rem, 8vw, 7.5rem); }
.section--alt { background: var(--bg-2); border-block: 1px solid var(--line); }
.section--tight { padding-block: clamp(3rem, 5vw, 4.5rem); }

.section__head { max-width: 46rem; margin-bottom: clamp(2.2rem, 4.5vw, 3.5rem); }
.section__head .lead { margin-top: 1rem; }
.section__head--center { margin-inline: auto; text-align: center; }
.section__head--center .eyebrow { justify-content: center; }
.section__head--center .lead { margin-inline: auto; }

.split {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(2rem, 4vw, 3.5rem);
    align-items: center;
}
@media (min-width: 900px) {
    .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
    .split--wide-left  { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
    .split--wide-right { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
    .split--flip .split__media { order: -1; }
    .split--top { align-items: start; }
}
/* On a phone the two columns stack in markup order. So the text always comes
   first in the markup, also in a flipped split, and the flip happens here.
   With the picture first, two splits in a row put two pictures next to each
   other, and neither says which text it belongs to. */
.split__text h3 { margin-bottom: .9rem; }
.split__text p { color: var(--ink-2); }
.split__text p + p { margin-top: .9rem; }

/* A paragraph after a list, and the closing link to read more, stand a
   little further off than the paragraphs between them. */
.split__text .facts + p,
.split__text .split__more { margin-top: 1.3rem; }

/* Several splits in one section, one under the other. On a phone the gap to
   the next split must be clearly wider than the 2rem gap between a text and
   its picture, or the picture reads as the start of the next split. 5rem is
   chosen for that; from 1067px wide it grows to the 6rem used on a desktop. */
.split + .split { margin-top: clamp(5rem, 9vw, 6rem); }

/* --------------------------------------------------------------------------
   7. Hero
   -------------------------------------------------------------------------- */

.hero {
    position: relative;
    overflow: hidden;
    padding-top: calc(var(--nav-h) + clamp(3rem, 7vw, 5.5rem));
    padding-bottom: clamp(2.5rem, 5vw, 4.5rem);
    text-align: center;
}
.hero::before {
    content: "";
    position: absolute;
    top: -40%;
    left: 50%;
    width: min(1250px, 140vw);
    aspect-ratio: 1;
    transform: translateX(-50%);
    background: radial-gradient(circle, var(--accent-soft), transparent 62%);
    pointer-events: none;
}
.hero__inner { position: relative; }

.hero .eyebrow { justify-content: center; }
.hero h1 { max-width: 19ch; margin: 0 auto 1.35rem; }
.hero__lead {
    max-width: 58ch;
    margin-inline: auto;
    /* This clamp's vw share is picked so its font stops growing at the same
       viewport width where the h1 above does, about 1200px (h1 is
       clamp(2.45rem, 5.6vw, 4.2rem), which reaches 4.2rem at 4.2*16/0.056).
       At 1.4vw the two used to fall out of step: the lead stopped shrinking
       at 1211px, and the h1 did not start until 1200px, so between those two
       widths the lead sat at its floor while the h1 had not yet moved. */
    font-size: clamp(1.06rem, 1.7vw, 1.28rem);
    line-height: 1.55;
    color: var(--ink-2);
}
.hero__cta { justify-content: center; }
.hero__facts {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .6rem 1.5rem;
    margin-top: 1.8rem;
    font-family: var(--font-mono);
    font-size: .75rem;
    color: var(--muted);
}
.hero__facts span { display: flex; align-items: center; gap: .45rem; }
.hero__facts span::before {
    content: "";
    width: 5px; height: 5px;
    border-radius: 50%;
    background: var(--accent);
}
.hero__stage { margin-top: clamp(2.2rem, 4.5vw, 3.6rem); }

/* --------------------------------------------------------------------------
   8. Screenshots

   Every screenshot is shown at half its pixel width, because the captures are
   Retina: enlarging past that turns the app's own type soft, which is the one
   thing a screenshot must not do. That is what `--w` on the wrapper is for.

   `shot--free` is the one to reach for. The picture carries its own macOS
   shadow on a transparent ground, so the page draws nothing around it — no
   border, no mat, no second shadow. A window photographed that way is a
   product photo; the same window in a box is a clip-art panel.

   `shot--window` and `shot--detail` are the older pair, for captures taken
   without a shadow, which still need something to sit on.
   -------------------------------------------------------------------------- */

.shot { position: relative; }
.shot img {
    width: 100%;
    height: auto;
    border-radius: inherit;
}
.shot--window img,
.shot--detail img,
.shot--free img {
    max-width: var(--w, 100%);
    margin-inline: auto;
}
.shot--window img {
    border-radius: 12px;
    border: 1px solid var(--mat-line);
    box-shadow: var(--shadow-lg);
}
.shot--detail img {
    border-radius: var(--r-sm);
    border: 1px solid var(--mat-line);
    box-shadow: var(--shadow-md);
}
/* A figure photographed in both macOS appearances is a <picture>: the dark
   file sits in a <source> behind a prefers-color-scheme query, so the browser
   fetches one of the two and never both. The appearance button in the header
   can disagree with the system, and site.js rewrites that query when it does.

   The picture element itself has to be told to behave like the image it
   wraps, or the wrapper stays inline and the centring below has nothing to
   act on. */
.shot picture { display: block; }

/* A cut-out is small on purpose, and a small image alone in a column reads as
   a mistake. The mat gives it something to sit on — and stays light, because
   the windows in it were photographed light. */
.mat {
    display: grid;
    place-items: center;
    padding: clamp(1.3rem, 3.5vw, 2.6rem);
    background:
        radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 68%),
        var(--mat);
    border: 1px solid var(--mat-line);
    border-radius: var(--r-lg);
    overflow: hidden;
}
.mat--flush { padding: clamp(.7rem, 2vw, 1.3rem); }

/* The grid centres its item, but an item that shrinks to fit sits wherever its
   intrinsic width leaves it. Making the wrapper full width hands the centring
   back to the image's own auto margins, which respect --w. */
.mat > .shot { width: 100%; }

.shot__caption {
    margin-top: .85rem;
    font-family: var(--font-mono);
    font-size: .73rem;
    line-height: 1.5;
    letter-spacing: .02em;
    color: var(--muted);
}
.mat + .shot__caption,
.shot__caption--center {
    max-width: 64ch;
    margin-inline: auto;
    text-align: center;
}

/* --------------------------------------------------------------------------
   9. Cards, features, facts
   -------------------------------------------------------------------------- */

.cards {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
}
/* Wider cards, so eight of them come out four and four rather than five and
   three. The number of columns is still the browser's to decide. */
.cards--wide { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.card {
    padding: 1.45rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    transition: border-color .2s var(--ease), transform .3s var(--ease);
}
.card:hover { border-color: var(--accent-line); transform: translateY(-2px); }
.card h4 { font-size: 1rem; margin-bottom: .45rem; }
.card p  { font-size: .93rem; line-height: 1.55; color: var(--muted); }
/* A card that is itself a link — a contents page, a next step */
a.card { display: block; text-decoration: none; }
a.card:hover { box-shadow: var(--shadow-sm); }
a.card:hover h4 { color: var(--accent); }
a.card h4 { transition: color .2s var(--ease); }

.features { display: grid; gap: clamp(1.5rem, 3vw, 2.1rem); }
@media (min-width: 700px) {
    .features--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .features--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.feature__mark {
    display: grid;
    place-items: center;
    width: 32px; height: 32px;
    margin-bottom: .8rem;
    border-radius: var(--r-sm);
    background: var(--accent-soft);
    color: var(--accent);
}
.feature__mark svg { width: 17px; height: 17px; stroke-width: 1.8; }
.feature h4 { font-size: 1.04rem; margin-bottom: .4rem; }
.feature p  { font-size: .95rem; line-height: 1.55; color: var(--muted); }
/* A feature that opens with a picture: the heading keeps clear of it */
.feature > .shot + h4 { margin-top: 1.2rem; }

/* Features with pictures, stacked in one column on a phone. Picture, text,
   picture, text gives no clue which text a picture belongs to. So on a phone
   the picture moves under its own text, and the next feature starts clearly
   further off. The markup keeps the picture first, for the columns above
   699px. */
@media (max-width: 699px) {
    .features:has(> .feature > .shot) { row-gap: 3.5rem; }
    .feature:has(> .shot) { display: flex; flex-direction: column; }
    .feature > .shot { order: 1; margin-top: .6rem; }
    .feature > .shot + h4 { margin-top: 0; }
}

.facts {
    display: grid;
    gap: .8rem;
    margin: 1.5rem 0 0;
    padding: 0;
    list-style: none;
}
.facts li {
    position: relative;
    padding-left: 1.25rem;
    font-size: .97rem;
    color: var(--ink-2);
}
.facts li::before {
    content: "";
    position: absolute;
    left: 0; top: .6em;
    width: 7px; height: 7px;
    border-radius: 2px;
    background: var(--accent);
    opacity: .85;
}
.facts b { color: var(--ink); font-weight: 600; }

.tag {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .2rem .5rem;
    border-radius: 6px;
    font-family: var(--font-mono);
    font-size: .66rem;
    font-weight: 500;
    letter-spacing: .1em;
    text-transform: uppercase;
    background: var(--accent-soft);
    color: var(--accent);
    border: 1px solid var(--accent-line);
}
/* --------------------------------------------------------------------------
   10. Terminal
   -------------------------------------------------------------------------- */

.terminal {
    overflow: hidden;
    background: #0b0e13;
    border: 1px solid rgba(255, 255, 255, .1);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-md);
    font-family: var(--font-mono);
    font-size: .84rem;
    line-height: 1.7;
}
.terminal__bar {
    display: flex;
    align-items: center;
    gap: .42rem;
    padding: .65rem .85rem;
    background: rgba(255, 255, 255, .045);
    border-bottom: 1px solid rgba(255, 255, 255, .07);
}
.terminal__bar i { width: 10px; height: 10px; border-radius: 50%; background: #3b414c; }
.terminal__bar span { margin-left: .5rem; font-size: .73rem; color: #7c8797; letter-spacing: .04em; }
.terminal__body { margin: 0; padding: 1.05rem 1.15rem; overflow-x: auto; white-space: pre; color: #cfd6e2; }
.terminal .p { color: #6fd08c; }
.terminal .c { color: #ffffff; }
.terminal .o { color: #8b95a4; }
.terminal .k { color: #7cc0ff; }

/* --------------------------------------------------------------------------
   11. Pricing
   -------------------------------------------------------------------------- */

.plans {
    display: grid;
    gap: 1.1rem;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
    align-items: start;
}
.plan {
    padding: clamp(1.4rem, 3vw, 2rem);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}
.plan--pro {
    border-color: var(--accent-line);
    background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 8%, var(--surface)), var(--surface));
}
.plan__name { display: flex; align-items: center; gap: .6rem; font-size: 1.12rem; font-weight: 600; }
.plan__price {
    margin: .8rem 0 .2rem;
    font-size: 2rem;
    font-weight: 600;
    letter-spacing: -.03em;
    font-variant-numeric: tabular-nums;
}
.plan__price small { margin-left: .35em; font-size: .84rem; font-weight: 400; letter-spacing: 0; color: var(--muted); }
.plan__note { font-size: .89rem; color: var(--muted); }
.plan ul {
    display: grid;
    gap: .65rem;
    margin: 1.4rem 0 0;
    padding: 0;
    list-style: none;
    font-size: .94rem;
    color: var(--ink-2);
}
.plan li { position: relative; padding-left: 1.5rem; }
.plan li svg {
    position: absolute;
    left: 0; top: .35em;
    width: 15px; height: 15px;
    stroke-width: 2.4;
    color: var(--good);
}

/* --------------------------------------------------------------------------
   12. Long-form pages
   -------------------------------------------------------------------------- */

.page { padding-top: calc(var(--nav-h) + clamp(2.5rem, 6vw, 4.5rem)); padding-bottom: 5rem; }
.prose { max-width: 42rem; }
.prose h1 { font-size: clamp(2rem, 4vw, 2.9rem); margin-bottom: 1rem; }
.prose h2 { font-size: 1.3rem; margin: 2.5rem 0 .7rem; }
.prose h3 { font-size: 1.03rem; margin: 1.7rem 0 .45rem; }
.prose p, .prose li { color: var(--ink-2); line-height: 1.7; }
.prose p + p { margin-top: .95rem; }
.prose ul, .prose ol { display: grid; gap: .55rem; margin: 1rem 0 0; padding-left: 1.2rem; }
.prose ul + p, .prose ol + p { margin-top: 1rem; }
.prose a { color: var(--accent); text-underline-offset: 3px; }
/* …but a button inside prose is still a button, and keeps its own colours. */
.prose a.btn--primary { color: var(--accent-ink); }
.prose a.btn--ghost   { color: var(--ink); }
/* Here .lead follows the plain .prose h1 instead of .h-section, and that
   heading has its own, narrower clamp. So this repeats the same match as
   above, against .prose h1's numbers instead: 4vw * (1.2rem / 2.9rem), which
   stops both fonts growing together at about 1160px (2.9rem*16 / 0.04). */
.prose > .lead { margin-bottom: 1.6rem; font-size: clamp(1.03rem, 1.6552vw, 1.2rem); }
.prose strong { color: var(--ink); }

/* --------------------------------------------------------------------------
   12b. The manual

   Three columns: the book's pages down the left, one page in the middle, and
   the headings of that page down the right. The page has one scrollbar, the
   window's; both side columns are stuck to the top of it and only scroll on
   their own when they are taller than the window. Below 1200px the right one
   goes; below 1000px the left one folds behind a button and the page reads as
   one column.

   The left column is generated, like the header — see tools/chrome.py. The
   right one is built from the page's own <h2>s by site.js, so it can never
   fall out of step with what is written.
   -------------------------------------------------------------------------- */

.docs {
    display: grid;
    gap: clamp(1.8rem, 3vw, 3rem);
    align-items: start;
    padding-top: calc(var(--nav-h) + clamp(1.4rem, 3vw, 2.4rem));
    padding-bottom: clamp(3rem, 6vw, 5rem);
}
@media (min-width: 1000px) { .docs { grid-template-columns: 232px minmax(0, 1fr); } }
@media (min-width: 1200px) { .docs { grid-template-columns: 232px minmax(0, 1fr) 190px; } }

.docside {
    position: relative;
    font-size: .91rem;
}
/* A list longer than the window scrolls inside the column. Its scrolling is
   deliberately not contained: at either end of the list the gesture has to
   carry on into the page, or the reader is stuck in the column. The browser
   only hands a gesture on after a pause, so site.js passes on what the column
   could not use itself. */
@media (min-width: 1000px) {
    .docside {
        position: sticky;
        top: calc(var(--nav-h) + 1.4rem);
        max-height: calc(100dvh - var(--nav-h) - 2.8rem);
        overflow-y: auto;
        scrollbar-width: thin;
    }
}

/* Below 1000px the list folds behind this button. It is a button and a class
   rather than <details>, because a closed <details> hides its content in a way
   a media query cannot reliably undo — and on a wide screen the list has to be
   open, always. */
.docside__btn {
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 100%;
    padding: .68rem .9rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface);
    color: inherit;
    font: inherit;
    font-weight: 500;
    text-align: left;
    cursor: pointer;
}
.docside__btn .chev { margin-left: auto; }
.docside__btn[aria-expanded="true"] .chev { transform: rotate(225deg) translate(-1px, -1px); }
@media (min-width: 1000px) { .docside__btn { display: none; } }

@media (max-width: 999px) {
    .docside__list {
        display: none;
        margin-top: .5rem;
        padding: .7rem;
        border: 1px solid var(--line);
        border-radius: var(--r-md);
        background: var(--surface);
    }
    .docside.is-open .docside__list,
    html:not(.js) .docside__list { display: block; }
}

.docside__group {
    margin: 1.6rem 0 .45rem;
    font-family: var(--font-mono);
    font-size: .655rem;
    font-weight: 500;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--muted);
}
.docside__group:first-child { margin-top: 0; }

/* The rail down the left of the list is one border on the container, so the
   current page can sit its own coloured segment exactly on top of it. */
.docside__items {
    display: grid;
    gap: 1px;
    border-left: 1px solid var(--line);
}
.docside__items a {
    position: relative;
    padding: .34rem .8rem;
    color: var(--muted);
    text-decoration: none;
    transition: color .16s var(--ease);
}
.docside__items a::before {
    content: "";
    position: absolute;
    left: -1px; top: 0; bottom: 0;
    width: 1px;
    background: var(--accent);
    opacity: 0;
    transition: opacity .16s var(--ease);
}
.docside__items a:hover { color: var(--ink); }
.docside__items a[aria-current="page"] { color: var(--accent); font-weight: 500; }
.docside__items a[aria-current="page"]::before { opacity: 1; }

/* --------------------------------------------------------------------------
   The page itself
   -------------------------------------------------------------------------- */

.doc { max-width: 44rem; min-width: 0; }
.doc__kind {
    display: block;
    margin-bottom: .5rem;
    font-family: var(--font-mono);
    font-size: .68rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--accent);
}
.doc h1 { font-size: clamp(1.95rem, 3.6vw, 2.55rem); margin-bottom: .8rem; }
.doc__lead {
    font-size: clamp(1.03rem, 1.25vw, 1.16rem);
    line-height: 1.6;
    color: var(--ink-2);
}
.doc h2 { font-size: 1.22rem; scroll-margin-top: calc(var(--nav-h) + 24px); }
.doc h3 { font-size: 1rem; }
.doc p, .doc li { color: var(--ink-2); line-height: 1.72; }
.doc ul, .doc ol { display: grid; gap: .6rem; padding-left: 1.2rem; }

/* One rule for the vertical rhythm, rather than a margin per element and a
   correction for every pair that meets. Every block in the flow gets the same
   air above it; a heading brings more of its own, and the block right after a
   heading starts flush against the space that heading already left. */
.doc > *          { margin-top: 1.05rem; }
.doc > :first-child { margin-top: 0; }
.doc > h2         { margin-top: 2.9rem; margin-bottom: .85rem; }
.doc > h3         { margin-top: 2rem; margin-bottom: .5rem; }
.doc > h2 + *,
.doc > h3 + *     { margin-top: 0; }
.doc li::marker { color: var(--accent); }
.doc a { color: var(--accent); text-underline-offset: 3px; }
.doc strong, .doc b { color: var(--ink); font-weight: 600; }
.doc code {
    padding: .1em .35em;
    border-radius: 5px;
    background: var(--surface-2);
    color: var(--ink-2);
}

/* A key, and the name of something in the app's own interface */
.kbd {
    display: inline-block;
    min-width: 1.7em;
    padding: .08em .45em;
    border: 1px solid var(--line-strong);
    border-bottom-width: 2px;
    border-radius: 6px;
    background: var(--surface);
    font-family: var(--font-mono);
    font-size: .82em;
    line-height: 1.5;
    text-align: center;
    color: var(--ink);
    white-space: nowrap;
}
.ui { font-weight: 600; color: var(--ink); }

/* A figure. The picture brings its own shadow, so there is nothing to draw
   around it; the caption is the only thing this adds. */
.figure { margin: 1.9rem 0 0; }
.figure figcaption {
    margin-top: .8rem;
    font-family: var(--font-mono);
    font-size: .715rem;
    line-height: 1.55;
    color: var(--muted);
}
.figure--wide { margin-inline: calc(clamp(0rem, 2vw, 2rem) * -1); }

/* Something the page wants read, without shouting. The bar is the accent for
   a remark and the warning colour where a thing cannot be undone. */
.note {
    margin-top: 1.5rem;
    padding: .95rem 1.15rem;
    border-left: 3px solid var(--accent-line);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    background: var(--surface-2);
    font-size: .955rem;
}
.note > :first-child { margin-top: 0; }
.note--warn { border-left-color: var(--warn); }
.note__kind {
    display: block;
    margin-bottom: .3rem;
    font-family: var(--font-mono);
    font-size: .66rem;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: var(--muted);
}

/* A table of keys, of messages, of anything with a short left column */
.doctable { width: 100%; margin-top: 1.2rem; border-collapse: collapse; font-size: .95rem; }
.doctable td {
    padding: .62rem .8rem .62rem 0;
    border-bottom: 1px solid var(--line);
    color: var(--ink-2);
    vertical-align: baseline;
}
.doctable tr:last-child td { border-bottom: 0; }
.doctable td:first-child { width: 1%; white-space: nowrap; padding-right: 1.5rem; }
.doctable--wrap td:first-child { white-space: normal; min-width: 11rem; }

/* Previous and next, at the foot of every page */
.docfeet {
    display: grid;
    gap: .8rem;
    margin-top: clamp(2.6rem, 5vw, 4rem);
    padding-top: 1.5rem;
    border-top: 1px solid var(--line);
}
@media (min-width: 640px) { .docfeet { grid-template-columns: 1fr 1fr; } }
.docfoot {
    display: grid;
    gap: .15rem;
    padding: .8rem 1rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    text-decoration: none;
    transition: border-color .2s var(--ease), transform .25s var(--ease);
}
.docfoot:hover { border-color: var(--accent-line); transform: translateY(-2px); }
.docfoot--next { text-align: right; grid-column: -2; }
.docfoot span {
    font-family: var(--font-mono);
    font-size: .655rem;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: var(--muted);
}
.docfoot b { font-weight: 500; color: var(--ink); }

/* --------------------------------------------------------------------------
   On this page — built by site.js from the article's own headings
   -------------------------------------------------------------------------- */

.docmap { display: none; }
@media (min-width: 1200px) {
    .docmap {
        display: block;
        position: sticky;
        top: calc(var(--nav-h) + 1.4rem);
        max-height: calc(100dvh - var(--nav-h) - 2.8rem);
        overflow-y: auto;
        font-size: .84rem;
    }
}
.docmap__head {
    margin-bottom: .6rem;
    font-family: var(--font-mono);
    font-size: .655rem;
    font-weight: 500;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--muted);
}
.docmap ul { display: grid; gap: 1px; margin: 0; padding: 0; list-style: none; }
.docmap a {
    display: block;
    padding: .28rem .7rem;
    border-left: 1px solid var(--line);
    color: var(--muted);
    text-decoration: none;
    transition: color .16s var(--ease), border-color .16s var(--ease);
}
.docmap a:hover { color: var(--ink); }
.docmap a.is-here { color: var(--accent); border-left-color: var(--accent); }

.datacard {
    margin-top: 1.6rem;
    padding: 1.3rem 1.5rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
}
.datacard dl {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: .55rem 1.4rem;
    margin: 0;
    font-size: .94rem;
}
.datacard dt {
    padding-top: .15rem;
    font-family: var(--font-mono);
    font-size: .77rem;
    letter-spacing: .04em;
    color: var(--muted);
}
.datacard dd { margin: 0; color: var(--ink-2); }

/* --------------------------------------------------------------------------
   13. Footer
   -------------------------------------------------------------------------- */

.footer {
    padding-block: 3.2rem 2.2rem;
    background: var(--bg-2);
    border-top: 1px solid var(--line);
    font-size: .91rem;
}
.footer__grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 2.2rem;
}
/* The 7rem is only the space between the columns. When they wrap, the rows
   use the same 2.2rem as the rest of the footer grid. */
.footer__links {
    display: flex;
    flex-wrap: wrap;
    gap: 2.2rem 7rem;
    margin-right: 7rem;
}
/* On a phone the two link columns sit side by side under the brand, instead
   of stacking into one long column. */
@media (max-width: 559px) {
    .footer__links {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 2.2rem 1.5rem;
        width: 100%;
        margin-right: 0;
    }
}
.footer__brand .brand { margin-bottom: .4rem; }
.footer__line { max-width: 24ch; font-size: .87rem; color: var(--muted); }
.footer h5 {
    margin: 0 0 .8rem;
    font-family: var(--font-mono);
    font-size: .72rem;
    font-weight: 500;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--muted);
}
.footer ul { display: grid; gap: .5rem; margin: 0; padding: 0; list-style: none; }
.footer a { color: var(--ink-2); text-decoration: none; }
.footer a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }

/* The rule on the left ties the manual back to the app it belongs to. Scoped
   under .footer so its specificity beats the ".footer ul" reset above. */
.footer .footer__submenu { margin-top: .5rem; padding-left: .8rem; border-left: 1px solid var(--line); }
.footer__bottom {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: .8rem;
    margin-top: 2.6rem;
    padding-top: 1.3rem;
    border-top: 1px solid var(--line);
    font-size: .83rem;
    color: var(--muted);
}

/* --------------------------------------------------------------------------
   14. Motion
   -------------------------------------------------------------------------- */

.reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity .7s var(--ease), transform .7s var(--ease);
    transition-delay: var(--delay, 0ms);
}
.reveal.is-in { opacity: 1; transform: none; }
html:not(.js) .reveal { opacity: 1; transform: none; }
html:not(.js) .wipe   { clip-path: none; }

/* A screenshot arrives differently from a paragraph: it comes up from
   further away and settles, the way a window does when it opens. */
.reveal--rise {
    transform: translateY(34px) scale(.985);
    transition-duration: 1s;
}

/* A list whose children arrive one after another. site.js numbers them in
   --i so the markup does not have to. */
.stagger > * {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity .6s var(--ease), transform .6s var(--ease);
    transition-delay: calc(var(--i, 0) * 70ms);
}
.stagger.is-in > * { opacity: 1; transform: none; }
html:not(.js) .stagger > * { opacity: 1; transform: none; }

/* An underline that draws itself under a heading once it is read */
.underline { position: relative; display: inline; background-image: linear-gradient(var(--accent), var(--accent)); background-repeat: no-repeat; background-position: 0 100%; background-size: 0 2px; transition: background-size 1.1s var(--ease) .2s; }
.reveal.is-in .underline, html:not(.js) .underline { background-size: 100% 2px; }

/* A figure that drifts a little against the scroll. site.js writes the offset
   into --drift, clamped to the element's own data-range, so the picture never
   separates from the text it belongs to. */
.settle {
    transform: translate3d(0, calc(var(--drift, 0) * 1px), 0);
    will-change: transform;
}

/* A number that counts up to itself the first time it is seen */
.count { font-variant-numeric: tabular-nums; }

/* A block that drifts a little against the scroll. The amount is set from JS
   in --shift so the CSS does not have to know how far the page has moved. */
.parallax { transform: translate3d(0, var(--shift, 0px), 0); will-change: transform; }

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .reveal { opacity: 1; transform: none; transition: none; }
    .stagger > * { opacity: 1; transform: none; transition: none; }
    .underline { background-size: 100% 2px; transition: none; }
    .parallax, .settle { transform: none !important; }
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
}
