/* Shared design system for karlswift.com and oss.karlswift.com.
   One source file, installed into both web roots by deploy.sh, so the two
   sites cannot drift apart. Dark only, on purpose. */

:root {
    --bg: #030406;
    --hairline: #12141a;
    --hairline-strong: #1f232c;
    --text: #dcdee3;
    --muted: #7a7f8a;
    --dim: #4b505b;
    --accent-a: #22d3ee;
    --accent-b: #8b5cf6;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, sans-serif;
}

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

html {
    scroll-behavior: smooth;
}

body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--sans);
    line-height: 1.6;
    /* No -webkit-font-smoothing here. It forces greyscale antialiasing and
       throws away Windows subpixel rendering, which is what made the light
       headline look rough and under-inked on Karl's machine. */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

/* A single wide, very soft glow behind the hero. Cheap depth, no images. */
body::before {
    content: "";
    position: fixed;
    top: -30vh;
    left: 50%;
    width: 120vw;
    height: 90vh;
    transform: translateX(-50%);
    background: radial-gradient(
        ellipse at center,
        rgba(34, 211, 238, 0.045) 0%,
        rgba(139, 92, 246, 0.028) 35%,
        transparent 70%
    );
    pointer-events: none;
    z-index: 0;
}

/* The nav floats 20px down from the top, so without this, body copy scrolls
   up into the gap above it and peeks out over the pill. Sits above the content
   (z-index 1) and below the nav (z-index 10). */
body::after {
    content: "";
    position: fixed;
    inset: 0 0 auto 0;
    height: 96px;
    background: linear-gradient(
        to bottom,
        var(--bg) 0%,
        var(--bg) 42%,
        rgba(3, 4, 6, 0) 100%
    );
    pointer-events: none;
    z-index: 5;
}

.wrap {
    width: 100%;
    max-width: 940px;
    margin: 0 auto;
    padding: 0 24px;
    position: relative;
    z-index: 1;
}

/* ---------- navigation ---------- */

.nav {
    position: sticky;
    top: 20px;
    z-index: 10;
    margin: 20px auto 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    max-width: 940px;
    padding: 10px 10px 10px 22px;
    border: 1px solid var(--hairline);
    border-radius: 999px;
    /* Opaque enough that body copy scrolling underneath does not ghost through
       the pill. 0.72 was too transparent to read over. */
    background: rgba(6, 7, 10, 0.93);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
}

.nav-mark {
    font-family: var(--mono);
    font-size: 0.78rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text);
    text-decoration: none;
    white-space: nowrap;
}

.nav-links {
    display: flex;
    align-items: center;
    gap: 4px;
    list-style: none;
}

.nav-links a {
    display: block;
    font-family: var(--mono);
    font-size: 0.72rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
    text-decoration: none;
    padding: 9px 16px;
    border-radius: 999px;
    border: 1px solid transparent;
    transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}

.nav-links a:hover,
.nav-links a:focus-visible {
    color: var(--text);
    border-color: var(--hairline-strong);
    background: rgba(255, 255, 255, 0.03);
}

.nav-links a[aria-current="page"] {
    color: var(--text);
    border-color: var(--hairline-strong);
    background: rgba(255, 255, 255, 0.05);
}

/* ---------- hero ---------- */

main {
    flex: 1 0 auto;
}

.hero {
    padding: 14vh 0 10vh;
}

.eyebrow {
    font-family: var(--mono);
    font-size: 0.7rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--dim);
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 28px;
}

.eyebrow::after {
    content: "";
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, var(--hairline-strong), transparent);
}

h1 {
    /* Segoe UI Variable Display is Windows' optical size cut for large text.
       It carries far cleaner stems at headline sizes than the plain UI face,
       which is where the roughness showed. Everything else falls back. */
    font-family: "Segoe UI Variable Display", var(--sans);
    font-size: clamp(2.2rem, 5vw, 3.5rem);
    font-weight: 350;
    line-height: 1.08;
    letter-spacing: -0.02em;
    margin-bottom: 28px;
}

/* The reference's signature move: one phrase boxed in a hairline pill. */
.boxed {
    display: inline-block;
    padding: 0.06em 0.42em 0.14em;
    border: 1px solid var(--hairline-strong);
    border-radius: 0.34em;
    background: linear-gradient(
        135deg,
        rgba(34, 211, 238, 0.09),
        rgba(139, 92, 246, 0.09)
    );
}

.lede {
    font-size: 1.1rem;
    color: var(--muted);
    max-width: 46ch;
}

/* ---------- the CPC character ---------- */

/* A CPC 464 as a character: monitor for a head, cassette deck for a heart.
   Entirely inline SVG and CSS. No images, no libraries, nothing to fetch. */

.hero-split {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 48px;
    align-items: center;
}

.cpc {
    width: 285px;
    height: auto;
    overflow: visible;
    flex-shrink: 0;
}

/* The dance. Deliberately small: a shift in weight, not a jig. */
.cpc-all {
    animation: cpc-dance 3.6s ease-in-out infinite;
    transform-origin: 130px 250px;
}

@keyframes cpc-dance {
    0%   { transform: translateY(0) rotate(0deg); }
    25%  { transform: translateY(-5px) rotate(-1.6deg); }
    50%  { transform: translateY(0) rotate(0deg); }
    75%  { transform: translateY(-5px) rotate(1.6deg); }
    100% { transform: translateY(0) rotate(0deg); }
}

/* Head lags the body by a beat, which is what makes it read as alive. */
.cpc-head {
    animation: cpc-nod 3.6s ease-in-out infinite;
    transform-origin: 130px 150px;
}

@keyframes cpc-nod {
    0%   { transform: rotate(0deg); }
    25%  { transform: rotate(2.2deg); }
    50%  { transform: rotate(0deg); }
    75%  { transform: rotate(-2.2deg); }
    100% { transform: rotate(0deg); }
}

/* Long open, quick shut. A steady 50/50 blink looks mechanical. */
.cpc-eye {
    animation: cpc-blink 5.2s infinite;
    /* Without fill-box, `center` resolves against the whole SVG view-box, so
       both eyes squash toward the middle of the picture instead of their own
       centres. Measured, not assumed: it computed to 130px 135px. */
    transform-box: fill-box;
    transform-origin: center;
}

@keyframes cpc-blink {
    0%, 92%, 100% { transform: scaleY(1); }
    95%, 97%      { transform: scaleY(0.12); }
}

.cpc-reel {
    animation: cpc-spin 2.8s linear infinite;
}

@keyframes cpc-spin {
    to { transform: rotate(360deg); }
}

/* Square-wave blink, like a real BASIC cursor, not a fade. */
.cpc-cursor {
    animation: cpc-caret 1.06s steps(1, end) infinite;
}

@keyframes cpc-caret {
    0%, 50%   { opacity: 1; }
    50.01%, 100% { opacity: 0; }
}

.cpc-scan {
    animation: cpc-scan 4.4s linear infinite;
}

/* Recording light, oss.karlswift.com only. Pulses in place, so it survives
   reduced motion along with the blinks. */
.cpc-rec {
    animation: cpc-rec 1.9s ease-in-out infinite;
}

@keyframes cpc-rec {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.25; }
}

@keyframes cpc-scan {
    0%   { transform: translateY(-4px); opacity: 0; }
    12%  { opacity: 0.5; }
    88%  { opacity: 0.5; }
    100% { transform: translateY(52px); opacity: 0; }
}

/* ---------- numbered index rows ---------- */

.section-label {
    font-family: var(--mono);
    font-size: 0.7rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--dim);
    margin-bottom: 18px;
}

.index {
    list-style: none;
    border-top: 1px solid var(--hairline);
}

.index li {
    border-bottom: 1px solid var(--hairline);
}

.index a,
.index .row {
    display: flex;
    align-items: baseline;
    gap: 22px;
    padding: 22px 4px;
    text-decoration: none;
    color: inherit;
    transition: background 0.18s ease, padding-left 0.18s ease;
}

.index a:hover,
.index a:focus-visible {
    background: rgba(255, 255, 255, 0.02);
    padding-left: 12px;
}

.index .num {
    font-family: var(--mono);
    font-size: 0.72rem;
    letter-spacing: 0.1em;
    color: var(--dim);
    flex: 0 0 auto;
    padding-top: 0.25em;
}

.index .body h3 {
    font-size: 1.05rem;
    font-weight: 500;
    margin-bottom: 4px;
}

.index .body p {
    color: var(--muted);
    font-size: 0.94rem;
}

.index a:hover .body h3 {
    background: linear-gradient(135deg, var(--accent-a), var(--accent-b));
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

/* ---------- generic prose block ---------- */

.block {
    padding: 0 0 10vh;
}

.block p + p {
    margin-top: 1.1em;
}

.block p {
    color: var(--muted);
    max-width: 62ch;
}

/* Wins over `.block p { max-width: 62ch }` when the placeholder is a <p>.
   Without this the empty-state box renders at ~half width and reads as broken
   layout rather than as a deliberate gap. */
.placeholder {
    max-width: none;
    border: 1px dashed var(--hairline-strong);
    border-radius: 10px;
    padding: 44px 28px;
    text-align: center;
    color: var(--dim);
    font-family: var(--mono);
    font-size: 0.74rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
}

/* ---------- call to action ---------- */

.cta {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    margin-top: 30px;
    padding: 13px 24px;
    border: 1px solid var(--hairline-strong);
    border-radius: 999px;
    font-family: var(--mono);
    font-size: 0.74rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text);
    text-decoration: none;
    background: linear-gradient(
        135deg,
        rgba(34, 211, 238, 0.1),
        rgba(139, 92, 246, 0.1)
    );
    transition: border-color 0.18s ease, transform 0.18s ease;
}

.cta:hover,
.cta:focus-visible {
    border-color: var(--accent-a);
    transform: translateY(-1px);
}

/* ---------- footer ---------- */

footer {
    flex-shrink: 0;
    border-top: 1px solid var(--hairline);
    margin-top: 40px;
}

footer .wrap {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    padding-top: 22px;
    padding-bottom: 34px;
    font-family: var(--mono);
    font-size: 0.68rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--dim);
}

footer a {
    color: var(--muted);
    text-decoration: none;
}

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

/* ---------- responsive ---------- */

@media (max-width: 860px) {
    .hero-split {
        grid-template-columns: 1fr;
        gap: 40px;
        justify-items: start;
    }

    .cpc {
        width: 200px;
    }
}

@media (max-width: 640px) {
    .nav {
        padding-left: 16px;
        gap: 10px;
    }

    .nav-links a {
        padding: 8px 10px;
        font-size: 0.66rem;
        letter-spacing: 0.08em;
    }

    .hero {
        padding: 9vh 0 7vh;
    }

    .index a,
    .index .row {
        gap: 14px;
    }
}

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

    /* `* { animation: none }` was too blunt. The setting exists to stop large
       positional movement that can trigger vestibular symptoms, not to freeze
       a blinking cursor. So the dance, the nod and the scanline stop; the
       in-place blinks and the reels keep going, slower.
       Result: the character is still visibly alive with the setting on. */
    .cpc-all,
    .cpc-head,
    .cpc-scan {
        animation: none !important;
    }

    .cpc-reel {
        animation-duration: 7s !important;
    }

    * {
        transition: none !important;
    }
}
