    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }

    body {
        font-family: 'Courier New', Courier, monospace;
        background: #fdfdfd;
        color: #222;
        line-height: 1.6;
        font-size: 14px;
    }

    /* Same inversion as link hover. */
    ::selection {
        background: #000;
        color: #fff;
    }

    /* PP Kyoto Medium: one static cut. Root-absolute, since 404.html is
       served from any depth. */
    @font-face {
        font-family: 'PP Kyoto';
        src: url('/PPKyoto-Medium.otf') format('opentype');
        font-weight: 400 700;
        font-style: normal;
        font-display: swap;
    }

    .container {
        max-width: 800px;
        margin: 0 auto;
        height: 100vh;
        padding: 20px;
    }

    header {
        border-bottom: 2px solid #000;
        padding-bottom: 20px;
        margin-bottom: 40px;
        display: flex;
    }

    .header-nav {
        display: flex;
        flex-grow: 1;
        flex-direction: column;
    }

    .avatar {
        display: flex;
        flex-grow: 1;
        justify-content: end;
    }

    .avatar-deck {
        position: relative;
        width: 150px;
        height: 150px;
    }

    .avatar-card {
        position: absolute;
        inset: 0;
        width: 150px;
        height: 150px;
        object-fit: cover;
        /* Page-colour hairline: separates the cards only once they overlap. */
        outline: 2px solid #fdfdfd;
        transform-origin: 50% 88%;
        transition: transform 260ms cubic-bezier(0.2, 0.7, 0.3, 1);
        /* 0 = chosen, counting outward; the chosen card is always on top. */
        z-index: calc(10 - var(--slot, 0));
    }

    /* Cards are keyboard-operable (role="button"); the default img outline is
       suppressed by the hairline above, so draw an explicit focus ring. */
    .avatar-card:focus-visible {
        outline: 2px solid #000;
        outline-offset: 2px;
    }

    /* Hover only, so it stays off on touch. Slot 0 never moves, so the pointer
       cannot fall off the stack and flicker. */
    @media (hover: hover) and (min-width: 560px) {
        .avatar-deck:hover .avatar-card {
            cursor: pointer;
            transform: translateX(calc(var(--slot, 0) * -52px)) rotate(calc(var(--slot, 0) * -5.5deg));
        }
    }

    /* Tighter spread once the header starts to crowd. */
    @media (hover: hover) and (min-width: 560px) and (max-width: 699px) {
        .avatar-deck:hover .avatar-card {
            transform: translateX(calc(var(--slot, 0) * -28px)) rotate(calc(var(--slot, 0) * -3.5deg));
        }
    }

    /* Headings only; prose stays monospace. */
    h1,
    h2,
    h3,
    h4 {
        font-family: 'PP Kyoto', 'Courier New', Courier, monospace;
        /* One cut ships, so this stops a synthesised bold. */
        font-synthesis: none;
    }

    h1 {
        font-size: 48px;
        margin-bottom: 10px;
    }

    h2 {
        font-size: 24px;
        font-weight: normal;
        margin: 40px 0 20px 0;
        border-bottom: 1px solid #000;
        padding-bottom: 5px;
    }

    /* 404 numeral: page-scale rather than heading-scale. The vw cap keeps
       three glyphs on-screen in a tall, narrow window. */
    .not-found-code {
        font-size: min(20vh, 34vw);
        line-height: 1;
        text-align: center;
        margin: 0 0 20px;
    }

    h2.not-found-code {
        border: 0;
    }

    nav {
        margin-top: 20px;
    }

    nav button {
        background: none;
        border: 1px solid #000;
        padding: 8px 16px;
        margin-right: 10px;
        font-family: 'Courier New', Courier, monospace;
        font-size: 14px;
        cursor: pointer;
        transition: all 0.2s;
    }

    nav button:hover,
    nav button.active {
        background: #000;
        color: #fff;
    }

    /* Brackets echo the [ Kang Ming ] title. Always laid out, so the buttons
       never jump. */
    nav button::before,
    nav button::after {
        opacity: 0;
        transition: opacity 150ms ease;
    }

    nav button::before {
        content: '[';
    }

    nav button::after {
        content: ']';
    }

    nav button:hover::before,
    nav button:hover::after,
    nav button.active::before,
    nav button.active::after {
        opacity: 1;
    }

    nav button:focus-visible,
    a:focus-visible {
        outline: 2px solid #000;
        outline-offset: 2px;
    }

    .section {
        margin-bottom: 40px;
    }

    .section p {
        margin-top: 20px;
    }

    .project,
    .experiences {
        border: 1px solid #000;
        padding: 20px;
        margin-bottom: 20px;
    }

    .experiences.current {
        border: 2px solid red;
    }

    .experiences.current legend {
        background-color: lightcoral;
        color: black;
        padding: 0 4px;
    }

    .experiences ul {
        padding-left: 10px;
        margin: 0;
    }

    .project h3,
    .experiences h3 {
        font-size: 18px;
        margin-bottom: 10px;
    }

    .experiences-header {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: 10px;
        margin-bottom: 10px;
    }

    .experiences-header h3 {
        margin-bottom: 0;
    }

    .experiences-date {
        font-size: 12px;
        color: #555;
        white-space: nowrap;
    }


    .experiences h4 {
        font-size: 14px;
        margin-bottom: 10px;
    }

    .project p {
        margin-bottom: 10px;
    }

    .skills {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
    }

    .skill {
        border: 1px solid #000;
        padding: 5px 10px;
        transition: all 0.2s;
    }

    a {
        color: #000;
        text-decoration: underline;
    }

    a:hover {
        background: #000;
        color: #fff;
    }

    footer {
        border-top: 2px solid #000;
        padding-top: 20px;
        padding-bottom: 5px;
        text-align: center;
    }

    /* Carries the #dither filter. */
    .dither-defs {
        position: absolute;
        width: 0;
        height: 0;
        overflow: hidden;
    }

    /* Fixed height, so a track change swaps contents without reflowing. */
    .last-played {
        display: flex;
        align-items: center;
        gap: 16px;
        height: 128px;
        border: 1px solid #000;
        padding: 15px;
        margin-top: 30px;
    }

    .last-played-art {
        flex: none;
        width: 96px;
        height: 96px;
        border: 1px solid #000;
        overflow: hidden;
    }

    .last-played-art img {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: cover;
        /* Ordered dither: see #dither in index.html. */
        filter: grayscale(1) contrast(1.2) url(#dither);
    }

    /* WebKit drops the whole render for an SVG filter on a non-origin-clean
       image, so iOS painted an empty box (verified on-device: grayscale drew,
       url(#dither) did not). -webkit-touch-callout is WebKit-exclusive, so
       every other engine keeps the dither. */
    @supports (-webkit-touch-callout: none) {
        .last-played-art img {
            filter: grayscale(1) contrast(1.2);
        }
    }

    .last-played-meta {
        /* Lets a long title wrap instead of stretching the row. */
        min-width: 0;
    }

    /* Opt out of `.section p`'s top margin; has to out-specify it (0-1-1). */
    .last-played .last-played-kicker,
    .last-played .last-played-artist {
        margin: 0;
    }

    .last-played-kicker {
        font-size: 12px;
        color: #555;
    }

    .last-played-title {
        /* 2-line clamp so the fixed height holds; fit-content keeps the hover
           invert hugging the text. */
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        width: fit-content;
        overflow: hidden;
        font-size: 16px;
        line-height: 1.3;
        margin: 4px 0 3px;
        overflow-wrap: anywhere;
    }

    .last-played-artist {
        font-size: 13px;
        color: #444;
        /* One line, ellipsised: same reason. */
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    @media (prefers-reduced-motion: reduce) {
        *,
        *::before,
        *::after {
            animation: none !important;
            transition: none !important;
        }
    }

    @media (max-width: 600px) {
        body {
            font-size: 12px;
        }

        h1 {
            font-size: 20px;
        }

        h2 {
            font-size: 16px;
        }

        nav button {
            padding: 6px 12px;
            font-size: 12px;
            margin-bottom: 10px;
        }

        .project {
            padding: 15px;
        }

        .experiences-date {
            font-size: 11px;
        }
    }

    /* Terminal drawer: collapsed to a dashed handle until the gap between the
       last experience and the footer is hovered. Keyboard focus opens it too,
       and touch (no hover) gets it permanently open so it is never unreachable. */
    .terminal-drawer {
        margin-top: 40px;
    }

    .terminal-handle {
        display: flex;
        align-items: center;
        gap: 0.7em;
        color: #767676;
        font-size: 12px;
        user-select: none;
    }

    .terminal-handle::before,
    .terminal-handle::after {
        content: '';
        flex: 1;
        border-top: 1px dashed #c9c9c9;
    }

    .terminal-body {
        max-height: 0;
        overflow: hidden;
        opacity: 0;
        transition: max-height 320ms ease, opacity 220ms ease;
    }

    .terminal-drawer:hover .terminal-body,
    .terminal-drawer:focus-within .terminal-body {
        /* Comfortably taller than the fixed-height terminal plus its note. */
        max-height: 560px;
        opacity: 1;
    }

    @media (hover: none) {
        .terminal-body {
            max-height: none;
            opacity: 1;
        }
    }

    /* Terminal: black and monospaced, in deliberate contrast to the light page.
       Fixed height acts as the screen; text starts at the top and the prompt is
       the last line, so the box reads as a shell rather than a chat composer. */
    .terminal {
        margin-top: 20px;
        border: 1px solid #000;
        background: #0c0c0c;
        color: #f2f2f2;
        font-family: 'Courier New', Courier, monospace;
        font-size: 13px;
        cursor: text;
    }

    .terminal-out {
        height: 300px;
        overflow-y: auto;
        padding: 15px;
        line-height: 1.5;
        overscroll-behavior: contain;
    }

    /* pre-wrap belongs on the lines, not the container: on the container it also
       preserves the newlines in the HTML between the output and the prompt,
       which opened a gap above the prompt. */
    .terminal-line {
        min-height: 1.5em;
        white-space: pre-wrap;
        overflow-wrap: anywhere;
    }

    /* Braille art: keep the grid intact — no wrapping, and line-height near 1 so
       the 4-dot-tall cells stack without gaps. */
    .terminal-line--art {
        white-space: pre;
        overflow-wrap: normal;
        line-height: 1.05;
    }

    /* Boxed output: fixed-width rows, so the ASCII frame stays aligned. The
       content is pre-wrapped in the script; `pre` stops the browser reflowing
       it and breaking the right edge. */
    .terminal-line--box {
        white-space: pre;
        overflow-wrap: normal;
        line-height: 1.3;
    }

    .terminal-line--dim {
        color: #8f8f8f;
    }

    .terminal-line--err {
        color: lightcoral;
    }

    .terminal-line--ok {
        color: #8fd694;
    }

    /* Inline with the output, not a bordered bar: the prompt and caret are the
       only chrome, so it reads as a terminal rather than a form field. */
    .terminal-input {
        display: flex;
        align-items: baseline;
        gap: 0.6em;
    }

    .terminal-prompt {
        flex: none;
        color: #8fd694;
        user-select: none;
    }

    .terminal-input input {
        flex: 1;
        min-width: 0;
        border: 0;
        padding: 0;
        background: none;
        color: inherit;
        font: inherit;
        outline: none;
        caret-color: #8fd694;
    }

    /* The global ::selection inverts to black-on-white, which would vanish on a
       black panel. */
    .terminal ::selection {
        background: #f2f2f2;
        color: #0c0c0c;
    }

    /* Off-screen rather than display:none: bots skip fields they can tell are
       hidden, and this one is only ever meant for them. */
    .terminal-hp {
        position: absolute;
        left: -9999px;
        width: 1px;
        height: 1px;
        opacity: 0;
    }

    @media (max-width: 600px) {
        .terminal {
            font-size: 12px;
        }

        .terminal-out {
            height: 240px;
        }
    }
