@font-face {
    font-family: "Departure Mono";
    src: url("/assets/DepartureMono-Regular.otf") format("opentype");
    font-style: normal;
    font-weight: 400;
    font-display: swap;
}

:root {
    color-scheme: light dark;
    --paper: #eeeadf;
    --ink: #151613;
    --secondary: #595959;
    --cool: #336b8a;
    --nominal: #387343;
    --elevated: #92590a;
    --rule: color-mix(in srgb, var(--ink) 28%, transparent);
    --faint-rule: color-mix(in srgb, var(--ink) 13%, transparent);
    --page-gutter: clamp(20px, 5vw, 72px);
    /* Matches the homepage so the header, footer and section rails line up
       across every page. Prose stays readable via --measure, which is what
       actually constrains body text. */
    --content-width: 1200px;
    /* Running text spans the same band as tables, code blocks and the page
       heading, so nothing inside an article steps in from the container edge.
       At this width and the article type size a line runs long, which is why
       .content raises the body size and line height to compensate. Lowering
       this single value narrows prose everywhere if that trade stops paying. */
    --measure: var(--content-width);
    --mono: "Departure Mono", "SFMono-Regular", Consolas, monospace;
    --system: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
}

@media (prefers-color-scheme: dark) {
    :root {
        --paper: #111210;
        --ink: #e9e7dd;
        --secondary: #a8a8a8;
        --cool: #73b7d3;
        --nominal: #86d98f;
        --elevated: #ffc857;
    }
}

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

html {
    scroll-behavior: smooth;
}

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--system);
    font-size: 17px;
    line-height: 1.65;
    text-rendering: optimizeLegibility;
}

a {
    color: inherit;
    text-underline-offset: 4px;
}

a:hover {
    text-decoration-color: var(--cool);
}

:focus-visible {
    outline: 2px solid var(--cool);
    outline-offset: 4px;
}

.skip-link {
    position: fixed;
    z-index: 10;
    top: 8px;
    left: 8px;
    padding: 7px 11px;
    translate: 0 -160%;
    border: 1px solid var(--ink);
    background: var(--paper);
    font-family: var(--mono);
    font-size: 12px;
}

.skip-link:focus {
    translate: 0;
}

.shell {
    width: min(100%, calc(var(--content-width) + var(--page-gutter) * 2));
    margin-inline: auto;
    padding-inline: var(--page-gutter);
}

.site-header,
.site-footer {
    border-color: var(--rule);
}

.site-header {
    border-bottom: 1px solid var(--rule);
}

.nav,
.footer-grid {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
}

.nav {
    min-height: 72px;
}

.brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-family: var(--mono);
    font-size: 17px;
    letter-spacing: .05em;
    text-decoration: none;
    text-transform: uppercase;
}

.brand img {
    width: 32px;
    height: 32px;
}

/* One header treatment for every page. The homepage repeats these values in its
   own inline stylesheet; keep the two in step. */
.nav-links,
.footer-links,
.footer-affiliation {
    color: var(--secondary);
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0.04em;
    text-decoration: none;
    text-transform: uppercase;
    white-space: nowrap;
}

.nav-links {
    display: flex;
    align-items: center;
    gap: clamp(16px, 3vw, 32px);
}

.footer-affiliation:hover {
    color: var(--ink);
    text-decoration: underline;
    text-underline-offset: 3px;
}

.footer-affiliation {
    padding: 14px 0;
    margin: -14px 0;
}

.footer-links {
    display: flex;
    align-items: center;
    gap: clamp(14px, 3vw, 28px);
}

.nav-links a {
    padding: 14px 8px;
    margin: -14px -8px;
    text-decoration-color: transparent;
    text-underline-offset: 4px;
}

.nav-toggle {
    display: none;
    appearance: none;
    padding: 8px 0;
    border: 0;
    background: none;
    color: var(--secondary);
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    cursor: pointer;
}

.nav-toggle:hover {
    color: var(--ink);
}

.nav-links a:hover {
    text-decoration-color: currentColor;
}

.eyebrow,
.scope,
.byline,
.post-kind,
.post-list time,
.metric-kind,
.status-label {
    margin: 0;
    color: var(--secondary);
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: .07em;
    line-height: 1.4;
    text-transform: uppercase;
}

.page-head {
    padding-block: clamp(64px, 10vw, 120px);
    border-bottom: 1px solid var(--rule);
}

.page-head h1 {
    margin: 18px 0 24px;
    font-size: clamp(46px, 7vw, 80px);
    font-weight: 450;
    letter-spacing: -.05em;
    line-height: .98;
}

.lede {
    margin: 0;
    color: var(--secondary);
    font-size: clamp(19px, 2.4vw, 24px);
    line-height: 1.5;
}

.scope {
    margin-top: 30px;
    padding-top: 18px;
    border-top: 1px solid var(--rule);
}

/* Publication date on a post page. It sits between the lede and the scope note
   so the provenance of an article reads in one run: what it is, what it claims,
   when it was written, and on what hardware. Ink rather than secondary, because
   on a site that publishes measurements the date is load-bearing: a reading from
   one macOS build ages, and the reader needs to see how much. */
.byline {
    margin-top: 26px;
    color: var(--ink);
}

.content {
    padding-block: clamp(60px, 9vw, 112px);
}

/* Every band is the same width: .shell governs, and nothing shrinks the
   container. Running text is measured here too, with the same value in the page
   header and the body so the two align on both edges.

   The page-head selector reaches through .shell deliberately. Its heading and
   lede are children of that inner wrapper, not of .page-head, so a direct-child
   selector matches nothing and lets the header run wider than the prose beneath
   it. That mismatch is what this rule exists to prevent. */
.page-head .shell > :where(p, h1, h2),
.content > :where(p, h2, h3, h4, ul, ol, dl, blockquote) {
    max-width: var(--measure);
}

.content > :where(pre, table, .copy-block, .status-card, .callout, .button-row, .post-list) {
    max-width: none;
}

.content > :first-child {
    margin-top: 0;
}

.content h2 {
    margin: 68px 0 18px;
    font-size: clamp(30px, 4vw, 44px);
    font-weight: 500;
    letter-spacing: -.035em;
    line-height: 1.1;
}

.content h3 {
    margin: 42px 0 10px;
    font-family: var(--mono);
    font-size: 15px;
    font-weight: 400;
    letter-spacing: .04em;
    text-transform: uppercase;
}

/* Running text spans the full container, so the type is set larger and looser
   than the 17px site default. Both are readability compensation for the long
   line: bigger glyphs cut the character count, and the extra leading is what
   lets the eye find the start of the next line. Tables, code blocks and the
   mono labels set their own sizes and are unaffected. */
.content {
    font-size: 20px;
    line-height: 1.7;
}

.content p,
.content ul {
    margin-block: 0 26px;
}

/* The Sources list closing each post. Generated by Scripts/sources.py from the
   links already cited in the body, so it cannot list something the article does
   not actually reference. Set smaller and tighter than prose: it is apparatus,
   read by someone checking a claim rather than by someone reading through. */
.sources {
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: 15px;
    line-height: 1.6;
}

.sources li {
    padding-block: 7px;
    border-top: 1px solid var(--faint-rule);
}

.sources li:last-child {
    border-bottom: 1px solid var(--faint-rule);
}

.content .sources li + li {
    margin-top: 0;
}

/* Prose lists only. .post-list is a grid, and its items are spaced by the
   container: an inter-item margin there lands on every li except the first,
   which pushes the second column's border-top 8px below the first column's
   and breaks the rule alignment across the row. */
.content :not(.post-list) > li + li {
    margin-top: 8px;
}

/* Definitions are short and independent, so they read better two-up than as one
   1200px-wide column. Non-metric children keep the full row. */
.metrics-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
    column-gap: clamp(28px, 4vw, 64px);
}

.metrics-grid > :not(.metric) {
    grid-column: 1 / -1;
}

.metric {
    padding-block: 34px;
    border-top: 1px solid var(--rule);
    scroll-margin-top: 24px;
}

.metric:first-of-type {
    margin-top: 46px;
}

/* Grid items do not collapse margins, so that first-of-type offset shifts only
   the first cell and breaks its row. In the grid the space belongs to the
   element above it instead. */
.metrics-grid > .metric:first-of-type {
    margin-top: 0;
}

.metrics-grid > .callout {
    margin-bottom: 46px;
}

.metric h2 {
    margin: 8px 0 12px;
    font-size: clamp(27px, 4vw, 38px);
}

.metric p:last-child {
    margin-bottom: 0;
}

.callout,
.status-card {
    margin-block: 36px;
    padding: 24px;
    border: 1px solid var(--rule);
    border-left: 6px solid var(--cool);
}

.status-card {
    border-left-color: var(--elevated);
}

.callout p:last-child,
.status-card p:last-child {
    margin-bottom: 0;
}

.button-row {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-block: 30px;
}

.button {
    display: inline-flex;
    min-height: 48px;
    align-items: center;
    justify-content: center;
    padding: 11px 18px;
    border: 1px solid var(--ink);
    font-family: var(--mono);
    font-size: 12px;
    letter-spacing: .05em;
    line-height: 1;
    text-decoration: none;
    text-transform: uppercase;
}

.button-primary {
    background: var(--ink);
    color: var(--paper);
}

.copy-block {
    position: relative;
}

/* Keeps the longest command clear of the button. */
.copy-block pre {
    padding-right: 104px;
}

.copy-button {
    position: absolute;
    top: 14px;
    right: 14px;
    z-index: 1;
    padding: 6px 12px;
    border: 1px solid var(--rule);
    background: var(--paper);
    color: var(--secondary);
    cursor: pointer;
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.copy-button:hover {
    border-color: var(--ink);
    color: var(--ink);
}

.copy-button:focus-visible {
    outline: 2px solid var(--cool);
    outline-offset: 3px;
}

.copy-button[data-state="copied"] {
    border-color: var(--nominal);
    color: var(--nominal);
}

/* Hover-reveal only where there is a pointer. On touch there is no hover, so the
   button stays visible rather than becoming unreachable. */
@media (hover: hover) {
    .copy-button {
        opacity: 0;
        transition: opacity 120ms ease;
    }

    .copy-block:hover .copy-button,
    .copy-block:focus-within .copy-button {
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .copy-button {
        transition: none;
    }
}

pre {
    overflow-x: auto;
    margin: 26px 0;
    padding: 22px;
    border: 1px solid var(--rule);
    font-family: var(--mono);
    font-size: 13px;
    line-height: 1.75;
}

/* Code blocks are wrapped and highlighted by Scripts/highlight.py at build
   time, so these rules style static markup. A block that the build did not
   recognise keeps the bare `pre` rule above and still reads correctly. */
.code-block {
    margin: 26px 0;
    border: 1px solid var(--rule);
}

/* The language label reuses the micro-label typography rather than inventing a
   second small-caps style. attr() is enough here: the value is set by the
   build, never by user input. */
.code-block::before {
    content: attr(data-lang);
    display: block;
    padding: 9px 22px;
    border-bottom: 1px solid var(--faint-rule);
    color: var(--secondary);
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: .07em;
    line-height: 1.4;
    text-transform: uppercase;
}

.code-block pre {
    margin: 0;
    border: 0;
}

/* Line numbers are a CSS counter, never document text, so a selection copies
   the code and nothing else. That is the whole reason they are done this way:
   most of these blocks are commands a reader is meant to paste. user-select
   is belt and braces for browsers that would otherwise copy generated
   content. Single-line blocks are not given the class at all. */
.code-block.numbered code {
    counter-reset: line;
}

/* Inline, not block. The markup keeps a real newline between lines so a copied
   selection has line breaks in it, and inside a `pre` that newline already
   creates the visual break. Making these blocks as well would render every
   line twice as far apart. */
.code-block.numbered .line {
    counter-increment: line;
}

.code-block.numbered .line::before {
    content: counter(line);
    position: sticky;
    left: 0;
    display: inline-block;
    width: 2.5em;
    margin-right: 1.25em;
    background: var(--paper);
    color: color-mix(in srgb, var(--ink) 38%, transparent);
    text-align: right;
    user-select: none;
    -webkit-user-select: none;
}

/* Five token colours, all drawn from the existing palette so the articles do
   not introduce a second one. Identifiers and types deliberately stay in the
   body colour; colouring everything would make the code harder to scan, not
   easier. Both schemes inherit these because the palette variables swap. */
.tok-comment {
    color: var(--secondary);
}

.tok-keyword,
.tok-flag {
    color: var(--cool);
}

.tok-string {
    color: var(--nominal);
}

.tok-number,
.tok-meta {
    color: var(--elevated);
}

code {
    font-family: var(--mono);
}

/* Measurement tables run to the full container width and scroll inside their
   own box, so a wide table never makes the page itself scroll sideways.
   Rows stripe in a faint ink wash and tint toward --cool on hover; numerals
   are tabular so measurement columns line up digit for digit. */
.table-scroll {
    overflow-x: auto;
    margin: 30px 0;
    border: 1px solid var(--rule);
    overscroll-behavior-x: contain;
}
.table-scroll table {
    width: 100%;
    border-collapse: collapse;
    font-size: 15px;
    font-variant-numeric: tabular-nums;
}
.table-scroll caption {
    padding: 15px 18px;
    border-bottom: 1px solid var(--rule);
    color: var(--secondary);
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: .07em;
    line-height: 1.4;
    text-align: left;
    text-transform: uppercase;
    caption-side: top;
}
.table-scroll th,
.table-scroll td {
    padding: 13px 18px;
    border-bottom: 1px solid var(--faint-rule);
    text-align: left;
    vertical-align: top;
}
.table-scroll thead th {
    border-bottom: 1px solid var(--rule);
    color: var(--secondary);
    font-family: var(--mono);
    font-size: 11px;
    font-weight: 400;
    letter-spacing: .07em;
    text-transform: uppercase;
    white-space: nowrap;
    vertical-align: bottom;
}
.table-scroll tbody tr:nth-child(odd) td {
    background: color-mix(in srgb, var(--ink) 4%, transparent);
}
.table-scroll tbody tr:hover td {
    background: color-mix(in srgb, var(--cool) 9%, transparent);
}
.table-scroll tbody tr:last-child td {
    border-bottom: 0;
}
.table-scroll code {
    font-size: 13px;
    white-space: nowrap;
}

.post-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
    column-gap: clamp(28px, 4vw, 64px);
    margin: 0;
    padding: 0;
    border-bottom: 1px solid var(--rule);
    list-style: none;
}

.post-list li {
    display: grid;
    grid-template-columns: 130px 1fr;
    gap: 28px;
    padding-block: 28px;
    border-top: 1px solid var(--rule);
}

.post-meta {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.post-list time {
    color: var(--ink);
}

.post-list h2 {
    margin: 0 0 8px;
    font-size: 27px;
}

.post-list p {
    margin: 0;
    color: var(--secondary);
}

.site-footer {
    padding-block: 30px;
    border-top: 1px solid var(--rule);
}

.site-footer p {
    margin: 0;
    color: var(--secondary);
    font-size: 13px;
}

@media (max-width: 680px) {
    .nav {
        min-height: 64px;
    }

    .nav-toggle {
        min-height: 44px;
        padding: 8px 12px;
    }

    .nav-links a:not(:last-child) {
        display: none;
    }

    /* With JavaScript the whole set moves into a full-screen sheet, so the one
       visible link is replaced by a toggle. Without it, the rule above stands
       and the nav behaves as it always has. */
    .nav[data-enhanced] .nav-toggle {
        display: inline-flex;
        position: relative;
        z-index: 50;
    }

    .nav[data-enhanced]:not([data-open]) .nav-links {
        display: none;
    }

    .nav[data-enhanced][data-open] .nav-links {
        position: fixed;
        inset: 0;
        z-index: 40;
        flex-direction: column;
        align-items: flex-start;
        justify-content: center;
        gap: 28px;
        padding: 0 clamp(24px, 8vw, 56px);
        background: var(--paper);
        font-size: 20px;
        letter-spacing: 0.02em;
    }

    /* Undoes the single-link fallback once the sheet owns the links. */
    .nav[data-enhanced][data-open] .nav-links a {
        display: block;
    }

    /* The sheet is the page while it is open, so its links carry full contrast
       rather than the muted tone they have as a header row. */
    .nav[data-enhanced][data-open] .nav-links a {
        color: var(--ink);
    }

    /* Keeps the mark above the sheet, so the header still says where you are. */
    .nav[data-enhanced][data-open] .brand {
        position: relative;
        z-index: 50;
    }

    .post-list li {
        grid-template-columns: 1fr;
        gap: 8px;
    }

    .footer-grid {
        align-items: flex-start;
        flex-direction: column;
    }

    .footer-links {
        flex-wrap: wrap;
    }
}

@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }
}

@media (prefers-contrast: more) {
    :root {
        --rule: var(--ink);
        --faint-rule: color-mix(in srgb, var(--ink) 48%, transparent);
    }
}
