/* Cold near-black with a violet cast, bone instead of cream, crimson where the
   orange was — candlelight on stone rather than a warm terminal. Nothing here is
   pure #fff or #000, and every foreground/background pair measures AA or better.
   The syntax colours below and the four binary assets — favicon, touch icon, the
   88x31 button and the OG card — were regenerated to match.

   --logo is separate from --link on purpose, but not because the lambda has to
   stay NixOS blue. It's fetch art, and recolouring a distro logo to match the
   terminal theme is what fetch configs do — red is more faithful to the metaphor
   here than blue. It's deeper than --link so that 19rem of solid colour doesn't
   outshout the actual links, and one value covers both schemes — muted against
   either background, and far enough from --link in each to read as art rather
   than as an enormous link. That's why there's no --logo in the dark block.

   The brand rule still holds where it means something: the GitHub and Discord
   marks are links to those services and keep their own colours, which is also
   why they're solid silhouettes while the generic glyphs are strokes. */
:root {
    --bg: #efe9dd;
    --text: #211d24;
    --muted: #5f5766;
    --border: #cfc6ba;
    --link: #8c1c2b;
    --logo: #a8434e;
    --code-bg: #e5ddcd;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #14111a;
        --text: #d8d1c6;
        --muted: #8f8797;
        --border: #2c2635;
        --link: #e0555f;
        --code-bg: #1c1824;
    }
}

* {
    box-sizing: border-box;
}

body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
    margin: 0;
    /* The footer used to supply the bottom breathing room; now body does.
       Can't live on main — .wrap's padding shorthand would reset it. */
    padding-bottom: 3rem;
    font-family: var(--mono);
    font-size: 1.05rem;
    line-height: 1.65;
    /* Keeps a single word from being stranded on a paragraph's last line.
       Headings override this with balance, which evens the lines instead —
       the right trick for two or three lines, the wrong one for twenty. */
    text-wrap: pretty;
    background: var(--bg);
    color: var(--text);
}

.wrap {
    width: 100%;
    max-width: 44rem;
    margin: 0 auto;
    padding: 0 1.25rem;
}

a {
    color: var(--link);
}

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

/* The last colour the browser was still picking for us — dragging across the page
   used to produce someone else's blue on top of all this. Built from --link and
   --bg rather than its own values, so it flips with the scheme for free and there
   is nothing extra to keep in sync in the dark block above. */
::selection {
    background: var(--link);
    color: var(--bg);
}

/* Page transitions */

/* Cross-fades between pages instead of blanking and repainting. Both the page
   being left and the one being entered have to opt in, which every page does
   for free by sharing this stylesheet. Same-origin only, so the GitHub link is
   unaffected. Browsers without support just navigate normally. */
@view-transition {
    navigation: auto;
}

/* The header opts out of the cross-fade separately — see .site-header below. */

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

    ::view-transition-old(root),
    ::view-transition-new(root),
    ::view-transition-old(site-header),
    ::view-transition-new(site-header) {
        animation: none;
    }
}

/* An inline icon sitting flush against its link text, in a square hairline box
   — the retro register without the chrome. Shared by the header, the home page,
   the feed link and the 404, so they all stay in step. */

.icon-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    white-space: nowrap;
    padding: 0.45rem 0.9rem;
    border: 1px solid var(--border);
    color: var(--text);
    text-decoration: none;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.icon-link:hover {
    border-color: var(--link);
    color: var(--link);
}

/* Sizing is left to each context — a mark in the nav bar and one in a label row
   want different widths — but every icon takes its colour from its parent. */
.icon {
    fill: currentColor;
    flex-shrink: 0;
}

/* Must come after .icon, which it overrides at equal specificity. See icon.html
   for why some marks are drawn as strokes and some as solids. */
.icon-stroke {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.icon-link .icon {
    width: 1.1em;
    height: 1.1em;
}

/* Boxes */

/* A hairline panel with its label notched into the top border. For asides and
   indexes only — body prose needs no frame around it, and a page of boxes reads
   as noise rather than structure. Never nested. */
.box {
    position: relative;
    margin: 2rem 0;
    padding: 1.75rem 1.25rem 1.25rem;
    border: 1px solid var(--border);
}

/* Centred on the top border by translateY rather than a negative margin, so it
   stays centred if the font-size changes. The opaque background is what cuts
   the gap in the border — a tiled background image would show through as a flat
   rectangle here. Absolutely positioned, so keep the label short or it runs
   past the box's right edge. */
.box-title {
    position: absolute;
    top: 0;
    left: 1rem;
    transform: translateY(-50%);
    margin: 0;
    padding: 0 0.5rem;
    background: var(--bg);
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--muted);
}

.box > :last-child {
    margin-bottom: 0;
}

/* Fetch box */

/* .box-title is absolutely positioned, so it stays out of flow and never
   becomes a flex item here. */
.fetch {
    display: flex;
    align-items: center;
    gap: 1.5rem;
}

/* Decorative only, hence aria-hidden on the element. Vector rather than block
   characters — see nixos-logo.html for why. Its height follows from the viewBox,
   so the width below is the only number that needs choosing. */
.fetch-logo {
    flex-shrink: 0;
    width: 19rem;
    fill: var(--logo);
}

.fetch-info {
    flex: 1;
    min-width: 0;
}

/* Underlined by a border rather than a row of dashes, so the rule can't fall
   out of sync with the text above it. inline-block keeps it that text's width. */
.fetch-user {
    display: inline-block;
    margin: 0 0 0.5rem;
    padding-bottom: 0.15rem;
    border-bottom: 1px solid var(--border);
    color: var(--link);
    font-weight: 600;
}

.fetch-info dl {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.1rem 0.75rem;
    margin: 0;
    font-size: 0.9rem;
}

/* A flex row rather than an inline icon, so the glyph centres on the label's box
   instead of sitting on its baseline, where the descender space would drop it
   low. The icon comes first, so every label still starts at the same x. */
.fetch-info dt {
    display: flex;
    align-items: center;
    gap: 0.45em;
    color: var(--link);
    font-weight: 600;
}

.fetch-info dt .icon {
    width: 1em;
    height: 1em;
}

.fetch-info dd {
    margin: 0;
}

/* Below this the logo and the pairs can't share a line without squeezing both,
   so the card stacks rather than dropping the art — it's the centrepiece now,
   and it reads better large and centred than small and beside something. */
@media (max-width: 44rem) {
    .fetch {
        flex-direction: column;
        gap: 1rem;
    }

    .fetch-logo {
        width: min(100%, 20rem);
    }

    .fetch-info {
        width: 100%;
    }
}

/* Header */

.site-header {
    padding: 1.75rem 0;
    margin-bottom: 1.5rem;
    border-bottom: 1px solid var(--border);
    /* Lifts the header out of the full-page snapshot so it holds still while
       only the content below it changes. Unnamed, it would cross-fade against a
       near-identical copy of itself and visibly flicker. */
    view-transition-name: site-header;
}

/* Centred, not baseline-aligned: now that .icon-link is a padded box, engines
   synthesise its baseline and disagree about where — same trap as .page-header. */
.site-header .wrap {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}

.site-title {
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--text);
    text-decoration: none;
}

.site-nav {
    display: flex;
    align-items: center;
    gap: 1rem;
    font-size: 0.95rem;
}

/* Main */

main {
    flex: 1;
}

/* Home */

.home-main {
    display: flex;
    flex-direction: column;
}

/* Larger padding than it looks — with no header on this page, it's all that
   keeps the title off the top of the viewport. */
.home {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-top: 6rem;
}

.home-title {
    font-size: 2.5rem;
    margin: 0 0 1.5rem;
    animation: home-rise 0.8s cubic-bezier(0.65, 0, 0.35, 1) both;
}

/* The one unmistakably terminal touch. Note that most screen readers do announce
   generated content, so this reads as "Iaco underscore" — a single stray
   character, judged an acceptable trade for the effect. */
.home-title::after {
    content: "_";
    color: var(--muted);
    animation: cursor-blink 1.2s step-end infinite;
}

@keyframes cursor-blink {
    50% {
        opacity: 0;
    }
}

/* The column centres its children, so without a width the box would shrink to
   its longest line and its right edge would move as the text is edited. */
.home .box {
    width: 100%;
    max-width: 40rem;
    margin: 2.5rem 0 0;
    text-align: left;
    opacity: 0;
    animation: fade-in 0.7s ease 0.45s forwards;
}

/* Both boxes fade together rather than in sequence: they're a pair, and one more
   step would push the page's entrance past a second and a half. Title styling is
   shared with the blog index, so the two listings can't drift apart. */
.blog-latest-title {
    font-size: 1.2rem;
    margin: 0 0 0.3rem;
}

.blog-latest-all {
    margin: 1rem 0 0;
    font-size: 0.95rem;
}

/* Pushed to the bottom of the viewport, with 2rem as the floor on short screens */
.buttons {
    margin-top: auto;
    padding-top: 2rem;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
}

/* pixelated keeps the 88x31s crisp instead of letting them blur when a HiDPI
   screen scales them up. */
.site-button {
    display: block;
    image-rendering: pixelated;
}

@keyframes home-rise {
    from {
        opacity: 0;
        transform: translateY(1.5rem);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.home .buttons {
    opacity: 0;
    animation: fade-in 0.7s ease 0.6s forwards;
}

@keyframes fade-in {
    to {
        opacity: 1;
    }
}

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

    .home-title,
    .home-title::after,
    .home .box,
    .home .buttons {
        animation: none;
        opacity: 1;
        transform: none;
    }
}

h1,
h2,
h3 {
    line-height: 1.25;
    font-weight: 600;
    text-wrap: balance;
    /* Tightened tracking is a proportional-face trick; monospace is drawn on a
       fixed advance and only looks cramped for it. */
    letter-spacing: normal;
}

.page-title {
    font-size: 2rem;
    margin: 0 0 0.4rem;
}

.post-meta {
    margin: 0;
    color: var(--muted);
    font-size: 0.9rem;
}

/* 404 */

.error-main {
    display: flex;
    flex-direction: column;
}

.error {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 3rem 0;
}

.error-code {
    font-size: 4rem;
    margin: 0 0 0.5rem;
    letter-spacing: normal;
}

.error-message {
    margin: 0 0 2rem;
    color: var(--muted);
}

/* Post list (blog index) */

/* Title on the left, feed link on the right.
   Centred rather than baseline-aligned: .icon-link is an inline-flex box with
   no real baseline, so engines synthesise one and disagree about where. */
.page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1.5rem;
    flex-wrap: wrap;
    margin-bottom: 0.4rem;
}

.page-header .page-title {
    margin-bottom: 0;
}

.post-list {
    list-style: none;
    margin: 2rem 0 0;
    padding: 0;
}

.post-list li + li {
    margin-top: 2rem;
    padding-top: 2rem;
    border-top: 1px solid var(--border);
}

.post-list h2 {
    font-size: 1.35rem;
    margin: 0 0 0.3rem;
}

.post-list h2 a,
.blog-latest-title a {
    color: var(--text);
    text-decoration: none;
}

.post-list h2 a:hover,
.blog-latest-title a:hover {
    color: var(--link);
    text-decoration: underline;
}

.post-excerpt {
    margin: 0.6rem 0 0;
    color: var(--muted);
}

.empty {
    margin-top: 2rem;
    color: var(--muted);
}

/* Post body */

.post-content {
    margin-top: 2.5rem;
    /* Monospace won't break a long URL or filesystem path on its own, and one of
       them in a post would put a horizontal scrollbar across the whole page on a
       phone. pre is unaffected — white-space: pre never wraps — so code keeps
       scrolling inside its own box instead of breaking mid-token. */
    overflow-wrap: break-word;
}

/* A rule under each section heading — early-web structure, and it gives the
   monospace body some hierarchy now that weight is the only other cue. */
.post-content h2 {
    font-size: 1.5rem;
    margin: 2.5rem 0 0.75rem;
    padding-bottom: 0.4rem;
    border-bottom: 1px solid var(--border);
}

.post-content h3 {
    font-size: 1.2rem;
    margin: 2rem 0 0.5rem;
}

.post-content img {
    max-width: 100%;
    height: auto;
}

.post-content blockquote {
    margin: 1.5rem 0;
    padding: 0.1rem 0 0.1rem 1.1rem;
    border-left: 3px solid var(--border);
    color: var(--muted);
}

.post-content hr {
    margin: 2.5rem 0;
    border: 0;
    border-top: 1px solid var(--border);
}

.post-content table {
    width: 100%;
    border-collapse: collapse;
    margin: 1.5rem 0;
    font-size: 0.95rem;
}

.post-content th,
.post-content td {
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    text-align: left;
}

/* Code */

code,
pre {
    font-family: var(--mono);
    font-size: 0.9em;
}

code {
    padding: 0.15em 0.4em;
    background: var(--code-bg);
}

pre {
    padding: 1rem;
    overflow-x: auto;
    background: var(--code-bg);
    border: 1px solid var(--border);
    line-height: 1.5;
}

pre code {
    padding: 0;
    background: none;
}

/* Rouge syntax highlighting (minimal, palette-matched) */

/* Comments */
.highlight .c, .highlight .c1, .highlight .cm, .highlight .cs, .highlight .cp { color: var(--muted); font-style: italic; }
/* Keywords */
.highlight .k, .highlight .kc, .highlight .kd, .highlight .kn, .highlight .kp, .highlight .kr, .highlight .kt { color: #7d1128; }
/* Strings — every subtype, so f-string prefixes and interpolation stay in colour */
.highlight .s, .highlight .s1, .highlight .s2, .highlight .sa, .highlight .sb, .highlight .sc,
.highlight .sd, .highlight .se, .highlight .sh, .highlight .si, .highlight .sr, .highlight .ss,
.highlight .sx, .highlight .dl { color: #44552f; }
/* Names */
.highlight .n, .highlight .nv, .highlight .nn, .highlight .nl, .highlight .ni, .highlight .vi,
.highlight .vg, .highlight .vc { color: var(--text); }
.highlight .nf, .highlight .nc, .highlight .nd, .highlight .ne { color: #7a5320; }
.highlight .nb, .highlight .bp, .highlight .no, .highlight .na { color: #5f3a7a; }
.highlight .nt { color: #2a5361; }
/* Numbers and operators */
.highlight .m, .highlight .mi, .highlight .mf, .highlight .mh, .highlight .mo, .highlight .il { color: #5f3a7a; }
.highlight .o, .highlight .ow, .highlight .p { color: var(--text); }
/* Diffs */
.highlight .gd { color: #7d1128; background: #f2d5d9; }
.highlight .gi { color: #44552f; background: #dde5cc; }
.highlight .gs { font-weight: 600; }
.highlight .ge { font-style: italic; }

@media (prefers-color-scheme: dark) {
    .highlight .k, .highlight .kc, .highlight .kd, .highlight .kn, .highlight .kp, .highlight .kr, .highlight .kt { color: #e05a6d; }
    .highlight .s, .highlight .s1, .highlight .s2, .highlight .sa, .highlight .sb, .highlight .sc,
    .highlight .sd, .highlight .se, .highlight .sh, .highlight .si, .highlight .sr, .highlight .ss,
    .highlight .sx, .highlight .dl { color: #a3b58a; }
    .highlight .nf, .highlight .nc, .highlight .nd, .highlight .ne { color: #d7a55c; }
    .highlight .nb, .highlight .bp, .highlight .no, .highlight .na { color: #b38ad4; }
    .highlight .nt { color: #7fa8b8; }
    .highlight .m, .highlight .mi, .highlight .mf, .highlight .mh, .highlight .mo, .highlight .il { color: #b38ad4; }
    .highlight .gd { color: #ffb3ba; background: #3d1620; }
    .highlight .gi { color: #c8dba8; background: #1e2a16; }
}

.post-nav {
    display: flex;
    gap: 1.5rem;
    margin-top: 3rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--border);
    font-size: 0.95rem;
}

.post-nav a {
    flex: 1;
    max-width: 20rem;
    text-decoration: none;
}

.post-nav a:hover {
    text-decoration: underline;
}

/* Keeps a lone "Newer" link pinned right, where it belongs */
.post-nav .next {
    margin-left: auto;
    text-align: right;
}

.post-nav-label {
    display: block;
    color: var(--muted);
    font-size: 0.85rem;
}

.post-nav-all {
    margin-top: 1.5rem;
    font-size: 0.95rem;
}

