@font-face {
    font-family: "Monument Extended";
    src: url("/assets/fonts/monumentextended-black-webfont.woff2") format("woff2"),
         url("/assets/fonts/monumentextended-black-webfont.woff") format("woff");
    font-weight: 900;
    font-style: normal;
    font-display: swap;
}

:root {
    --font-sans: "Archivo", system-ui, -apple-system, sans-serif;
    --font-display: "Monument Extended", var(--font-sans);
    --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco,
        Consolas, "Liberation Mono", "Courier New", monospace;

    /* Catppuccin Latte palette (https://catppuccin.com/palette/) */
    --ctp-rosewater: #dc8a78;
    --ctp-flamingo:  #dd7878;
    --ctp-pink:      #ea76cb;
    --ctp-mauve:     #8839ef;
    --ctp-red:       #d20f39;
    --ctp-maroon:    #e64553;
    --ctp-peach:     #fe640b;
    --ctp-yellow:    #df8e1d;
    --ctp-green:     #40a02b;
    --ctp-teal:      #179299;
    --ctp-sky:       #04a5e5;
    --ctp-sapphire:  #209fb5;
    --ctp-blue:      #1e66f5;
    --ctp-lavender:  #7287fd;
    --ctp-text:      #4c4f69;
    --ctp-subtext1:  #5c5f77;
    --ctp-subtext0:  #6c6f85;
    --ctp-overlay2:  #7c7f93;
    --ctp-overlay1:  #8c8fa1;
    --ctp-overlay0:  #9ca0b0;
    --ctp-surface2:  #acb0be;
    --ctp-surface1:  #bcc0cc;
    --ctp-surface0:  #ccd0da;
    --ctp-base:      #eff1f5;
    --ctp-mantle:    #e6e9ef;
    --ctp-crust:     #dce0e8;

    /* Theme accent: change this one line to repaint the headline, article
     * titles, and any other "accent" surface. */
    --accent: var(--ctp-maroon);
}

html {
    font-family: var(--font-sans);
}

body {
    font-family: var(--font-sans);
    margin: 0;
    background: var(--ctp-base);
    color: var(--ctp-text);
    /* Belt + suspenders: never let any child cause a horizontal scrollbar. */
    overflow-x: clip;
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-display);
}

/*
 * Fluid display headline — "SVG Way" from
 * https://css-tricks.com/fitting-text-to-a-container/
 *
 * Markup is an SVG containing a <text> at a fixed user-space font-size.
 * On load (assets/scripts/fit-text.js), after web fonts are ready, JS reads
 * the text's getBBox() and sets viewBox to those exact bounds. With width:100%
 * + height:auto, the SVG (and the text inside it) scales perfectly with its
 * container — never clips, never wraps, never overflows. No glyph distortion.
 *
 * The initial viewBox (1200x200) is a generous fallback so things look sane
 * before the JS runs / if it fails. JS also overrides max-inline-size to cap
 * the visible height at 9rem (144px).
 */
.fluid-display-wrap {
    inline-size: 100%;
}

.fluid-display {
    margin: 0;
    line-height: 0;
    padding-bottom: 2rem;
    /* Headline takes the theme accent. The SVG <text> inside uses
     * fill="currentColor" so this propagates down through inheritance. */
    color: var(--accent);
}

.fluid-display-svg {
    display: block;
    inline-size: 100%;
    block-size: auto;
}

/*
 * ENG MANAGER and the article-link titles share the same fluid-display-wrap.
 * Both sit inside body's padding-inline gutter so their left edges line up.
 * (Previously the headline used a negative margin to bleed to the viewport
 * edges, but that misaligned it from the stacked article titles below on
 * narrow screens — now they all share the same left rail.)
 */

/*
 * HTML fallback for article-link titles when the fluid SVG would render the
 * text below 16px. fit-text.js measures the effective font size at the
 * current container width and toggles .is-too-small on the SVG; CSS uses
 * the adjacent-sibling combinator to swap in the fallback.
 *
 * The fallback uses Archivo 900 at 1rem (16px @ 16px root), white-space:
 * nowrap + text-overflow: ellipsis to cleanly truncate long titles.
 */
.article-fluid-fallback {
    display: none;
    font-family: "Archivo", sans-serif;
    font-weight: 900;
    font-size: 1rem;
    line-height: 1.25;
    color: inherit;
    letter-spacing: 0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.article-fluid-svg.is-too-small {
    display: none;
}

.article-fluid-svg.is-too-small + .article-fluid-fallback {
    display: block;
}

/*
 * Pierre-style monospace resume block.
 *
 * 12px monospace text expressed in rem (0.75rem @ 16px root). Each line is
 * its own div with white-space: pre so leading spaces in "  - [...]" survive.
 * Markdown-syntax (`[text](url)`) is rendered as literal text with the URL
 * portion wrapped in an <a> so it's clickable.
 */
.resume {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--ctp-text);
    margin-block-start: 2rem;
}

.resume-line {
    white-space: pre;
}

.resume-heading {
    display: flex;
    align-items: center;
    gap: 0.5em;
}

/* Blinking block cursor after the heading text. */
.resume-heading::after {
    content: "";
    display: inline-block;
    inline-size: 0.55em;
    block-size: 1em;
    background: currentColor;
    animation: resume-cursor-blink 1s steps(2, start) infinite;
}

@keyframes resume-cursor-blink {
    to { background: transparent; }
}

.resume-sep {
    margin-block: 0.75rem;
}

.resume-link {
    color: var(--ctp-mauve);
    text-decoration: none;
    text-underline-offset: 0.15em;
}

.resume-link:hover,
.resume-link:focus-visible {
    color: var(--ctp-blue);
    text-decoration: underline;
}

.resume-strike {
    text-decoration: line-through;
    color: var(--ctp-subtext0);
}

/*
 * 4×4px green status pip in the top-right corner of "ONLINE".
 *
 * - inline-block span so position: relative establishes a containing block
 *   for the absolutely-positioned pip.
 * - Radial gradient with the highlight off-center (top-left light source)
 *   gives a subtle 3D / spherical look.
 * - Box shadow: a soft drop shadow below for lift, plus a faint green glow
 *   to soften the edge against the white page.
 */
.status-online {
    position: relative;
    display: inline-block;
}

.status-pip {
    position: absolute;
    inset-block-start: 0;
    inset-inline-end: -0.3125rem; /* nudge past the right edge */
    inline-size: 0.25rem; /* 4px */
    block-size: 0.25rem; /* 4px */
    border-radius: 50%;
    background: radial-gradient(
        circle at 30% 30%,
        #a6e3a1 0%,
        var(--ctp-green) 50%,
        #2d7d20 100%
    );
    box-shadow:
        0 0.0625rem 0.125rem rgba(0, 0, 0, 0.25),
        0 0 0.25rem rgba(64, 160, 43, 0.35);
}

/*
 * Full viewport height using `dvh` (dynamic viewport height): on iOS/mobile
 * Safari the value updates as the address bar shows/hides, unlike `vh` which
 * is frozen to the largest possible height and causes the page to be cut off.
 */
html {
    block-size: 100%;
}

body {
    /* Page padding so the headline + resume don't touch the viewport edge. */
    padding-block: 2rem;
    padding-inline: 1rem;
    min-block-size: 100dvh;
    box-sizing: border-box;
}

/*
 * Stacked article-link titles under the main headline. Each is a fluid SVG
 * (same treatment as ENG MANAGER) wrapped in an <a>. Default underline hidden;
 * hover paints a chunky underline beneath the title.
 */
.article-fluid-link {
    display: block;
    color: var(--accent);
    text-decoration: none;
}

.article-fluid-link + .article-fluid-link {
    margin-block-start: 1rem;
}

/* Hover treatment: lift the link with a 1.2x scale and a soft elevation
 * shadow under the text. drop-shadow follows the actual glyph outlines
 * (and the SVG content) rather than the bounding box, so the shadow
 * tracks the text shape. The link can clip past the viewport on hover —
 * that's intentional. */
.article-fluid-link {
    transform: scale(1);
    transform-origin: left center;
    transition:
        transform 220ms cubic-bezier(0.2, 0.7, 0.3, 1.0),
        filter 220ms ease;
    will-change: transform, filter;
}

.article-fluid-link:hover,
.article-fluid-link:focus-visible {
    transform: scale(1.2);
    filter: drop-shadow(0 0.75rem 1.5rem rgba(76, 79, 105, 0.25));
}

/* Chunky maroon focus ring for keyboard nav (ArrowDown/Up between titles).
 *
 *   outline-offset: 1rem  — paints the ring 1rem outside the element,
 *                          leaving a 1rem gap between element and ring
 *                          (the "padding on the inside" of the ring).
 *   outline-width:  1rem  — 1rem-thick stroke on all four sides.
 *   outline-color:  var(--accent)  — same maroon as the title fills.
 *
 * outline doesn't participate in layout, so this never causes a reflow when
 * focus moves between links. */
.article-fluid-link:focus-visible {
    outline: 1rem solid var(--accent);
    outline-offset: 1rem;
}

/*
 * Avatar pinned to the bottom-right of the viewport.
 * The fixed positioning lives on the <button> wrapper now so clicks open
 * the bio popover. The <img> inside is static.
 */
.avatar-button {
    position: fixed;
    inset-block-end: 1rem;
    inset-inline-end: 1rem;
    padding: 0;
    margin: 0;
    border: none;
    background: transparent;
    cursor: pointer;
    z-index: 10;
    line-height: 0;
    /* Named anchor for the bio popover (used by position-anchor below). */
    anchor-name: --avatar;
}

.avatar-button:focus-visible {
    outline: 2px solid #5b21b6;
    outline-offset: 2px;
    border-radius: 50%;
}

.avatar {
    block-size: 3rem;
    inline-size: auto;
    display: block;
}

/*
 * Bio popover, anchored so its BOTTOM-RIGHT corner sits at the avatar's
 * TOP-LEFT corner.
 *
 * Two strategies layered:
 *
 *   1. Manual offset (universal): set top/left to auto and bottom/right to
 *      4rem. Avatar is 3rem square at 1rem inset, so its top-left lives 4rem
 *      from the viewport's right and bottom — setting our right and bottom
 *      to 4rem puts our bottom-right corner there. Each of top/right/bottom/
 *      left is set as a *longhand* (not the `inset` shorthand) because the
 *      shorthand fights the UA `inset: 0` rule less reliably across engines.
 *
 *   2. CSS Anchor Positioning (modern browsers, 2024+): position-anchor +
 *      position-area let the browser do the math from the .avatar-button
 *      anchor directly. The `top left` area places the popover above and to
 *      the left of the anchor, with its near corner kissing the anchor's
 *      far corner — which is exactly the alignment we want. Browsers that
 *      don't support anchor positioning silently ignore these properties
 *      and the manual offset above takes over.
 */
#bio {
    /* Override the UA popover defaults (inset: 0; margin: auto = centered). */
    position: fixed;
    top: auto;
    left: auto;
    right: 4rem;
    bottom: 4rem;
    margin: 0;

    /* Sizing */
    width: max-content;
    max-width: 26rem;
    height: auto;

    /* Modern anchor positioning (Chrome 125+, Safari 17.4+, Firefox 137+). */
    position-anchor: --avatar;
    position-area: top left;

    /* Visual */
    padding: 1.25rem 1.5rem;
    border: 1px solid var(--ctp-surface0);
    border-radius: 0.75rem;
    background: var(--ctp-base);
    color: var(--ctp-text);
    box-shadow: 0 1.25rem 2.5rem -0.5rem rgba(76, 79, 105, 0.18);
}

#bio::backdrop {
    background: transparent;
}

/* Reset the .resume margin-block-start inside the popover; the popover has
 * its own padding now. */
#bio .resume {
    margin-block-start: 0;
}

/* =================================================================
 * Articles (monospace-only for now)
 * ================================================================= */

.articles-page {
    color: var(--ctp-text);
}

.articles-index {
    font-family: var(--font-mono);
    max-inline-size: 50rem;
}

/* ===========================================================================
 * Article body typography
 *
 * Reusable for every /articles/{slug}. The only per-article content is the
 * Markdown — these styles cover every element pulldown-cmark might emit.
 *
 * Editorial defaults pulled from Medium / Obsidian / well-set blogs:
 *   - Comfortable measure: max-inline-size ≈ 38rem (~62–70 ch in Archivo)
 *   - Body 17px / line-height 1.7 for relaxed reading
 *   - Clear visual hierarchy: title (display) → h2 → h3 → body
 *   - Generous block spacing between sections, tight inside sections
 *   - Links carry a thin underline rather than colour-only emphasis
 *   - Blockquote pulled left, italicized, slightly larger
 * =========================================================================== */
.article {
    font-family: var(--font-sans);
    font-size: 1.0625rem;
    line-height: 1.7;
    color: var(--ctp-text);
    max-inline-size: 38rem;
    margin-inline: auto;
    padding-block-end: 4rem;
}

/* ===========================================================================
 * Site nav — sticky, glass-blurred, edge-to-edge.
 *
 * Mobile-first: a single row of brand + 3 links, always visible (no
 * hamburger — "less clicks is more valuable"). Bleeds past the body's
 * padding-inline so the blurred background reaches the viewport edge,
 * with its own internal padding for the row contents.
 *
 * `backdrop-filter` gives the live blur over scrolled content; the
 * semi-transparent base mantle keeps text readable when nothing's
 * behind it yet (top of page).
 * =========================================================================== */
.site-nav {
    position: sticky;
    inset-block-start: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-inline: -1rem;
    margin-block: -2rem 2.25rem;
    padding-block: 0.75rem;
    padding-inline: 1rem;
    background: color-mix(in oklab, var(--ctp-base) 78%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    backdrop-filter: saturate(180%) blur(14px);
    border-block-end: 1px solid
        color-mix(in oklab, var(--ctp-surface0) 70%, transparent);
}

.site-nav-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-display);
    font-weight: 900;
    font-size: 0.8125rem;
    letter-spacing: -0.01em;
    color: var(--ctp-text);
    text-decoration: none;
    text-transform: uppercase;
    line-height: 1;
    padding-block: 0.25rem;
    flex-shrink: 0;
    transition: color 120ms ease;
}

/* Mobile-first: only the favicon orb shows up to small-tablet (30rem).
 * Three link labels + wordmark + brand orb overflow a Pixel-sized
 * viewport (~393px) otherwise. Wordmark un-hides at ≥30rem below. */
.site-nav-wordmark {
    display: none;
}

.site-nav-brand:hover,
.site-nav-brand:focus-visible {
    color: var(--accent);
}

.site-nav-mark {
    inline-size: 1.25rem;
    block-size: 1.25rem;
    border-radius: 50%;
    display: block;
    flex-shrink: 0;
    box-shadow: 0 0 0 1px
        color-mix(in oklab, var(--ctp-surface0) 80%, transparent);
}

.site-nav-links {
    display: flex;
    align-items: center;
    gap: 0.125rem;
}

.site-nav-link {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ctp-subtext1);
    text-decoration: none;
    padding-block: 0.5rem;
    padding-inline: 0.5rem;
    border-radius: 0.375rem;
    line-height: 1;
    transition: color 120ms ease, background 120ms ease;
}

.site-nav-link:hover,
.site-nav-link:focus-visible {
    color: var(--ctp-text);
    background: color-mix(in oklab, var(--ctp-surface0) 55%, transparent);
}

.site-nav-link.is-current {
    color: var(--accent);
}

/* From small-tablet up: bring back the wordmark + a touch more
 * breathing room throughout. */
@media (min-width: 30rem) {
    .site-nav {
        gap: 1rem;
        padding-block: 0.875rem;
        padding-inline: 1.5rem;
    }
    .site-nav-wordmark {
        display: inline;
    }
    .site-nav-link {
        font-size: 0.75rem;
        padding-inline: 0.75rem;
    }
    .site-nav-brand {
        font-size: 0.875rem;
    }
}

.articles-index-title {
    font-family: var(--font-mono);
    font-size: 1.25rem;
    font-weight: 700;
    margin: 0 0 0.5rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.article-title {
    font-family: var(--font-display);
    font-weight: 900;
    font-size: clamp(1.875rem, 1.2rem + 2.4vw, 3rem);
    line-height: 1.1;
    letter-spacing: -0.015em;
    color: var(--accent);
    margin: 1rem 0 0.75rem;
    text-transform: uppercase;
}

.article-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.article-entry {
    padding-block: 0.75rem;
    border-block-end: 1px solid var(--ctp-surface0);
}

.article-entry:last-child {
    border-block-end: none;
}

.article-entry-title {
    color: inherit;
    text-decoration: none;
    font-size: 0.875rem;
    font-weight: 600;
    display: block;
}

.article-entry-title:hover {
    text-decoration: underline;
}

.article-entry-date,
.article-entry-summary {
    color: var(--ctp-subtext0);
    font-size: 0.75rem;
    margin-block-start: 0.25rem;
}

.article-entry-summary {
    line-height: 1.5;
    margin: 0.5rem 0 0;
    color: var(--ctp-text);
}

/* Article meta header — Vercel/changelog-flavored.
 *
 *   [avatar]  Matthew Harwood              [ May 31, 2025 ]
 *             Engineering Manager @ Uber
 *
 * Avatar on the left, author name + role stacked, date pushed to the
 * trailing edge as a subtle mono pill. On narrow viewports the date
 * wraps below and aligns with the author block. */
.article-meta {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-block: 0.75rem 2.5rem;
    padding-block-end: 1.25rem;
    border-block-end: 1px solid var(--ctp-surface0);
}

.article-meta-avatar {
    inline-size: 2.5rem;
    block-size: 2.5rem;
    border-radius: 50%;
    background: var(--ctp-mantle);
    border: 1px solid var(--ctp-surface0);
    flex-shrink: 0;
    display: block;
    object-fit: cover;
}

.article-meta-author {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
    min-inline-size: 0;
}

.article-meta-name {
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 0.9375rem;
    color: var(--ctp-text);
    letter-spacing: -0.005em;
}

.article-meta-role {
    font-family: var(--font-sans);
    font-weight: 400;
    font-size: 0.8125rem;
    color: var(--ctp-subtext0);
    margin-block-start: 0.125rem;
}

.article-meta-date {
    margin-inline-start: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 500;
    color: var(--ctp-subtext1);
    background: var(--ctp-mantle);
    border: 1px solid var(--ctp-surface0);
    border-radius: 999px;
    padding-block: 0.25rem;
    padding-inline: 0.625rem;
    line-height: 1.4;
    white-space: nowrap;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.article-meta-date::before {
    content: "";
    inline-size: 0.3125rem;
    block-size: 0.3125rem;
    border-radius: 50%;
    background: var(--ctp-overlay1);
    flex-shrink: 0;
}

@media (max-width: 30rem) {
    .article-meta-date {
        margin-inline-start: 3.25rem;
    }
}

/* Body content */
.article p {
    margin-block: 1.25rem;
}

.article ul,
.article ol {
    margin-block: 1.25rem;
    padding-inline-start: 1.5rem;
}

.article li + li {
    margin-block-start: 0.4rem;
}

.article ul li::marker {
    color: var(--ctp-overlay0);
}

.article strong {
    font-weight: 700;
    color: var(--ctp-text);
}

.article em {
    font-style: italic;
}

/* Headings */
.article h2,
.article h3,
.article h4 {
    font-family: var(--font-sans);
    color: var(--ctp-text);
    line-height: 1.25;
}

.article h2 {
    font-size: 1.625rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    margin-block: 3rem 0.75rem;
}

.article h3 {
    font-size: 1.25rem;
    font-weight: 700;
    margin-block: 2.25rem 0.5rem;
}

.article h4 {
    font-size: 1rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ctp-subtext1);
    margin-block: 1.75rem 0.5rem;
}

.article a {
    color: var(--ctp-blue);
    text-decoration: underline;
    text-decoration-thickness: 0.075em;
    text-underline-offset: 0.2em;
    transition: color 120ms ease;
}

.article a:hover {
    color: var(--ctp-sapphire);
}

.article blockquote {
    border-inline-start: 4px solid var(--ctp-mauve);
    padding: 0.25rem 0 0.25rem 1.5rem;
    margin: 1.75rem 0;
    font-style: italic;
    font-size: 1.125rem;
    line-height: 1.6;
    color: var(--ctp-subtext1);
    background: none;
}

.article blockquote p {
    margin: 0;
}

.article hr {
    border: none;
    border-block-start: 1px solid var(--ctp-surface0);
    margin-block: 3rem;
}

.article table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.8125rem;
    margin-block: 1rem;
}

.article th,
.article td {
    text-align: left;
    padding: 0.5rem 0.75rem;
    border-block-end: 1px solid var(--ctp-surface0);
}

.article th {
    font-weight: 700;
    background: var(--ctp-mantle);
}

/* Inline backtick code (not inside a <pre>). */
.article :not(pre) > code {
    font-family: var(--font-mono);
    background: var(--ctp-mantle);
    color: var(--ctp-text);
    padding: 0.125rem 0.3rem;
    border-radius: 0.25rem;
    font-size: 0.85em;
}

/* ===========================================================================
 * Code-block "editor" chrome (Zed-flavored wireframe).
 *
 * Each <pre> from pulldown-cmark becomes a faux editor window:
 *   - Rounded outer panel (mantle bg, subtle border)
 *   - Top "title bar" strip with three traffic-light dots, colored from the
 *     Catppuccin palette (red / yellow / green).
 *   - <code> body sits below with padding.
 * Everything stays scrollable via overflow-x on the <pre>.
 * =========================================================================== */
.article pre {
    position: relative;
    background: var(--ctp-mantle);
    border: 1px solid var(--ctp-surface0);
    border-radius: 0.5rem;
    padding: 0;
    margin: 1.75rem 0;
    overflow: hidden;
    max-block-size: 32rem;
}

.article pre::before {
    content: "";
    display: block;
    block-size: 1.75rem;
    background-color: var(--ctp-crust);
    background-image:
        radial-gradient(circle at 0.7rem 0.875rem, var(--ctp-red)    0.22rem, transparent 0.23rem),
        radial-gradient(circle at 1.45rem 0.875rem, var(--ctp-yellow) 0.22rem, transparent 0.23rem),
        radial-gradient(circle at 2.2rem 0.875rem, var(--ctp-green)  0.22rem, transparent 0.23rem);
    border-block-end: 1px solid var(--ctp-surface0);
}

/* Copy-to-clipboard pill, injected into each <pre> by scripts/copy-code.js.
 * Sits in the right side of the editor-chrome title bar. Black pill with a
 * tiny copy glyph and a 10px "Copy" label; turns green briefly after a
 * successful clipboard write. */
.code-copy {
    position: absolute;
    inset-block-start: 0.3125rem;
    inset-inline-end: 0.5rem;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding-block: 0.1875rem;
    padding-inline: 0.5rem 0.5625rem;
    background: #000;
    color: #fff;
    border: none;
    border-radius: 999px;
    font-family: var(--font-sans);
    font-size: 0.625rem; /* 10px @ 16px root */
    line-height: 1;
    letter-spacing: 0.02em;
    cursor: pointer;
    z-index: 2;
    transition: background 120ms ease, transform 120ms ease;
}

.code-copy:hover {
    background: var(--ctp-text);
}

.code-copy:active {
    transform: translateY(0.0625rem);
}

.code-copy.is-copied {
    background: var(--ctp-green);
    color: #fff;
}

.code-copy-icon {
    inline-size: 0.625rem;
    block-size: 0.625rem;
    flex-shrink: 0;
}

.article pre > code {
    display: block;
    padding: 1rem 1.25rem;
    overflow-x: auto;
    background: transparent;
    color: var(--ctp-text);
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    line-height: 1.6;
    /* Cap the inner scroll area so the <pre>'s max-block-size is respected. */
    max-block-size: calc(32rem - 1.5rem);
    box-sizing: border-box;
}

/* ===========================================================================
 * Prism.js token colors — Catppuccin Latte.
 * Targets the .token classes Prism adds inside <code class="language-X">.
 * =========================================================================== */
.article code[class*="language-"],
.article pre[class*="language-"],
.article pre > code {
    color: var(--ctp-text);
}

.article .token.comment,
.article .token.prolog,
.article .token.doctype,
.article .token.cdata {
    color: var(--ctp-subtext0);
    font-style: italic;
}

.article .token.namespace { opacity: 0.7; }

.article .token.string,
.article .token.char,
.article .token.attr-value,
.article .token.regex,
.article .token.variable {
    color: var(--ctp-green);
}

.article .token.punctuation,
.article .token.operator {
    color: var(--ctp-sky);
}

.article .token.entity,
.article .token.url,
.article .token.symbol,
.article .token.number,
.article .token.boolean,
.article .token.constant,
.article .token.property,
.article .token.inserted {
    color: var(--ctp-peach);
}

.article .token.atrule,
.article .token.keyword,
.article .token.attr-name,
.article .token.important {
    color: var(--ctp-mauve);
}

.article .token.function,
.article .token.deleted {
    color: var(--ctp-red);
}

.article .token.tag,
.article .token.selector,
.article .token.class-name {
    color: var(--ctp-yellow);
}

.article .token.builtin {
    color: var(--ctp-teal);
}

.article .token.bold { font-weight: 700; }
.article .token.italic { font-style: italic; }

/* ===========================================================================
 * Comically-large custom cursor on the homepage.
 *
 * The native cursor is hidden; scripts/big-cursor.js mounts a position:fixed
 * SVG arrow that follows the mouse. Sized at 256px — about 8x a typical
 * 32px system cursor.
 * =========================================================================== */
.homepage,
.homepage * {
    cursor: none;
}

.big-cursor {
    position: fixed;
    inset-block-start: 0;
    inset-inline-start: 0;
    inline-size: 256px;
    block-size: 256px;
    pointer-events: none;
    z-index: 9999;
    /* Translate is driven by JS via requestAnimationFrame. Provide a subtle
     * fade for cursor-enter / cursor-leave events. */
    transition: opacity 150ms ease;
    will-change: transform;
    filter: drop-shadow(0 0.25rem 0.75rem rgba(0, 0, 0, 0.18));
}

/* ===========================================================================
 * Auteurs article: shader canvas, QR + discord link, workflow, products grid.
 * =========================================================================== */

.auteurs-shader {
    display: block;
    inline-size: min(100%, 32rem);
    aspect-ratio: 1;
    margin: 1rem auto 2rem;
}

.auteurs-join {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    margin-block: 1.75rem;
}

.auteurs-qr {
    inline-size: min(70%, 14rem);
    block-size: auto;
    display: block;
    border-radius: 0.5rem;
    background: #fff;
    padding: 0.5rem;
    border: 1px solid var(--ctp-surface0);
}

.auteurs-discord-link {
    font-family: var(--font-mono);
    font-size: 0.875rem;
    color: var(--ctp-mauve);
    text-decoration: none;
}

.auteurs-discord-link:hover,
.auteurs-discord-link:focus-visible {
    color: var(--ctp-blue);
    text-decoration: underline;
}

/* ===========================================================================
 * Live Discord widget — Vercel-flavored card.
 *
 * Server-rendered from a cached snapshot of Discord's widget + invite
 * endpoints (see website/src/discord.rs). The card sits above the static
 * QR/link block in the Auteurs article; if the snapshot is cold or the
 * widget endpoint is disabled, the card is omitted and the static block
 * keeps working as the join CTA.
 * =========================================================================== */
.discord-widget {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    margin-block: 1.75rem;
    padding: 1.25rem;
    background: var(--ctp-mantle);
    border: 1px solid var(--ctp-surface0);
    border-radius: 0.75rem;
}

.discord-widget-header {
    display: flex;
    align-items: center;
    gap: 0.875rem;
}

.discord-widget-icon {
    inline-size: 2.5rem;
    block-size: 2.5rem;
    border-radius: 0.625rem;
    background: linear-gradient(135deg, #5865f2 0%, #4752c4 100%);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-display);
    font-weight: 900;
    font-size: 1.125rem;
    flex-shrink: 0;
    box-shadow: 0 0.125rem 0.5rem rgba(88, 101, 242, 0.25);
}

.discord-widget-title {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
    margin-inline-end: auto;
    min-inline-size: 0;
}

.discord-widget-name {
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 0.9375rem;
    color: var(--ctp-text);
}

.discord-widget-members {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    color: var(--ctp-subtext0);
    margin-block-start: 0.125rem;
}

.discord-widget-online {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 500;
    color: var(--ctp-subtext1);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

.discord-widget-pip {
    inline-size: 0.4375rem;
    block-size: 0.4375rem;
    border-radius: 50%;
    background: var(--ctp-green);
    box-shadow: 0 0 0.4rem rgba(64, 160, 43, 0.55);
    animation: discord-pip-pulse 2.4s ease-in-out infinite;
}

@keyframes discord-pip-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.45; }
}

.discord-widget-avatars {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.375rem;
}

.discord-widget-avatar {
    inline-size: 1.75rem;
    block-size: 1.75rem;
    border-radius: 50%;
    border: 2px solid var(--ctp-mantle);
    background: var(--ctp-base);
    display: block;
    margin-inline-start: -0.5rem;
    object-fit: cover;
}

.discord-widget-avatar:first-child {
    margin-inline-start: 0;
}

.discord-widget-avatars-more {
    margin-inline-start: 0.5rem;
    font-family: var(--font-mono);
    font-size: 0.75rem;
    color: var(--ctp-subtext0);
}

.discord-widget-channels {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
}

.discord-widget-channel {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding-block: 0.1875rem;
    padding-inline: 0.5rem;
    background: var(--ctp-base);
    border: 1px solid var(--ctp-surface0);
    border-radius: 0.375rem;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    color: var(--ctp-subtext1);
}

.discord-widget-channel-icon {
    color: var(--ctp-overlay1);
    font-weight: 700;
}

/* Filled accent button — sits flush-left so it doesn't hijack the whole
 * card width. Vercel-style: subtle gradient, soft elevation on hover.
 *
 * Scoped under .article to win specificity against `.article a` (which
 * paints the link blue and underlines it). Without this the join CTA
 * comes out blue-on-blurple with the article-body underline. */
.article .discord-widget-join {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding-block: 0.625rem;
    padding-inline: 1rem;
    background: linear-gradient(180deg, #5865f2 0%, #4752c4 100%);
    color: #fff;
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 0.875rem;
    border-radius: 0.5rem;
    text-decoration: none;
    box-shadow: 0 0.125rem 0.5rem rgba(88, 101, 242, 0.3);
    transition:
        transform 120ms ease,
        box-shadow 120ms ease,
        filter 120ms ease;
}

.article .discord-widget-join:hover,
.article .discord-widget-join:focus-visible {
    color: #fff;
    text-decoration: none;
    transform: translateY(-0.0625rem);
    box-shadow: 0 0.25rem 0.875rem rgba(88, 101, 242, 0.4);
    filter: brightness(1.06);
}

.discord-widget-join-arrow {
    font-weight: 400;
    font-size: 0.9375rem;
    line-height: 1;
}

/* ===========================================================================
 * Article inline visuals — callouts, captioned figures, small SVG diagrams.
 * Reusable across articles; first used by /articles/talking-not-typing.
 * =========================================================================== */

/* Highlighted intro box. Soft gradient tint of the accent + a left rail. */
.article-callout {
    margin-block: 2rem;
    padding: 1.125rem 1.375rem;
    background:
        linear-gradient(
            135deg,
            color-mix(in oklab, var(--accent) 6%, transparent),
            color-mix(in oklab, var(--ctp-mauve) 4%, transparent)
        );
    border-inline-start: 3px solid var(--accent);
    border-radius: 0 0.5rem 0.5rem 0;
    font-size: 0.9375rem;
    line-height: 1.55;
    color: var(--ctp-text);
}

.article-callout strong {
    color: var(--accent);
    font-weight: 700;
}

.article-callout p {
    margin: 0;
}

/* Generic captioned-figure shell used by the SVG illustrations below. */
.article figure {
    margin: 2rem 0;
    padding: 1.25rem;
    background: var(--ctp-mantle);
    border: 1px solid var(--ctp-surface0);
    border-radius: 0.75rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
}

.article figcaption {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    color: var(--ctp-subtext0);
    text-align: center;
    line-height: 1.5;
    max-inline-size: 32ch;
}

/* iPad ⟷ GitHub tether. The dashed line animates via SMIL inside the SVG. */
.tether-svg {
    inline-size: min(100%, 18rem);
    block-size: auto;
    color: var(--accent);
}

/* Crescent moon + ascending Zs. Z opacity loops via SMIL. */
.moonloop-svg {
    inline-size: min(100%, 9rem);
    block-size: auto;
    color: var(--accent);
}

/* Workflow diagram: horizontal flex of stage cards connected by →.
 * Reflows to a vertical stack on narrow viewports; arrows rotate 90deg. */
.workflow {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    justify-content: center;
    gap: 0.75rem;
    margin-block: 2rem;
    padding: 1.25rem;
    background: var(--ctp-mantle);
    border: 1px solid var(--ctp-surface0);
    border-radius: 0.75rem;
}

.workflow-stage {
    flex: 1 1 0;
    min-inline-size: 7.5rem;
    padding: 0.875rem 1rem;
    background: var(--ctp-base);
    border: 1px solid var(--ctp-surface0);
    border-radius: 0.5rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    text-align: center;
    color: var(--ctp-text);
}

.workflow-stage-single {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ctp-subtext0);
}

.workflow-stage-title {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ctp-subtext0);
    margin-block-end: 0.5rem;
}

.workflow-stage-items {
    list-style: none;
    padding: 0;
    margin: 0;
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--ctp-text);
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
}

.workflow-arrow {
    align-self: center;
    color: var(--ctp-overlay1);
    font-size: 1.25rem;
    user-select: none;
    flex: 0 0 auto;
}

@media (max-width: 640px) {
    .workflow { flex-direction: column; }
    .workflow-arrow { transform: rotate(90deg); }
    .workflow-stage { flex: 0 0 auto; }
}

/* Products grid */
.auteurs-products {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: 0.75rem;
    margin-block: 1.5rem 2rem;
}

.auteurs-product {
    padding: 2rem 1rem;
    background: var(--ctp-base);
    border: 1px solid var(--ctp-surface0);
    border-radius: 0.5rem;
    text-align: center;
    font-family: var(--font-display);
    font-weight: 900;
    font-size: 1.125rem;
    text-transform: uppercase;
    letter-spacing: -0.01em;
    color: var(--ctp-text);
}

.auteurs-product-stamp {
    color: var(--ctp-maroon);
    border-color: var(--ctp-maroon);
    border-width: 2px;
}

