/* GENERATED by build.py from src/css/*.css. Edit those files, not this one. */
/* ------------------------------------------------------------
   Design tokens. Change a color or a font here and the whole site follows.
   One committed look: chalk on dark stone, finger-tape orange for emphasis,
   a cool blue reserved for everything about the machine.
   ------------------------------------------------------------ */
@font-face { font-family: 'Instrument Serif'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/instrument-serif-normal-400.woff2') format('woff2'); }
@font-face { font-family: 'Instrument Serif'; font-style: italic; font-weight: 400; font-display: swap; src: url('/fonts/instrument-serif-italic-400.woff2') format('woff2'); }
@font-face { font-family: 'Instrument Sans'; font-style: normal; font-weight: 400 700; font-display: swap; src: url('/fonts/instrument-sans-normal-400-700.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400 600; font-display: swap; src: url('/fonts/jetbrains-mono-normal-400-600.woff2') format('woff2'); }

:root {
    color-scheme: dark;

    --stone: #141311;            /* page */
    --stone-2: #1b1a17;          /* raised panels */
    --stone-3: #26241f;          /* hairlines */
    --stone-4: #3d3a34;          /* stronger lines, inactive controls */
    --chalk: #ece7dc;            /* primary text */
    --chalk-dim: #aaa49a;        /* secondary text */
    --chalk-faint: #726d65;      /* tertiary text, labels */
    --paper: #f2eee6;            /* polaroid frames */
    --ink: #2a2825;              /* text on paper */

    --tape: #ff5a1f;             /* accent: finger tape */
    --tape-soft: rgba(255, 90, 31, 0.2);
    --signal: #7fd18b;           /* online */
    --ice: #8fbfff;              /* the machine */
    --ice-soft: rgba(143, 191, 255, 0.12);

    --steel: #8a857c;
    --steel-dark: #4a4641;
    --plate-red: #d63a2f;
    --plate-blue: #2f63d6;
    --plate-yellow: #e8b923;
    --plate-green: #2e9a5a;
    --plate-white: #e9e4d8;
    --plate-chrome: #b8b5ae;

    --serif: 'Instrument Serif', 'Iowan Old Style', 'Times New Roman', serif;
    --sans: 'Instrument Sans', 'Helvetica Neue', Arial, sans-serif;
    --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

    --measure: 62ch;
    --gutter: clamp(1.25rem, 5vw, 5rem);
    --rail: 5rem;                /* room on the right for the topo nav (desktop) */
    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}

/* Konami code: MAXN_SUPER mode. Warmer stone, hotter accents. */
[data-theme='maxn'] {
    --stone: #1a0b05;
    --stone-2: #24110a;
    --stone-3: #3a1a0d;
    --stone-4: #5c2c15;
    --tape: #ffd166;
    --tape-soft: rgba(255, 209, 102, 0.22);
    --ice: #ff8a5b;
    --ice-soft: rgba(255, 138, 91, 0.14);
}
/* Base: reset, type, shared components (buttons, section headers, polaroids). */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
    margin: 0;
    background: var(--stone);
    color: var(--chalk);
    font-family: var(--sans);
    font-size: clamp(1rem, 0.95rem + 0.3vw, 1.125rem);
    line-height: 1.55;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration-color: var(--chalk-faint); text-underline-offset: 0.2em; }
a:hover { text-decoration-color: var(--tape); }
button { font: inherit; color: inherit; }
p { margin: 0 0 1.1em; max-width: var(--measure); }
::selection { background: var(--tape); color: var(--stone); }
:focus-visible { outline: 2px solid var(--tape); outline-offset: 3px; }

.mono { font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.02em; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip { position: fixed; top: -100%; left: 1rem; z-index: 100; background: var(--tape); color: var(--stone); padding: 0.5rem 1rem; border-radius: 4px; }
.skip:focus { top: 1rem; }

/* Fixed overlays. Grain is a tiled SVG noise, drawn once by the browser. */
.grain { position: fixed; inset: 0; z-index: 55; pointer-events: none; opacity: 0.07; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }
.chalk-layer { position: fixed; inset: 0; z-index: 60; pointer-events: none; width: 100%; height: 100%; }

/* Status dot: green = online, orange = generating, grey = asleep. */
.live-dot { display: inline-block; width: 0.55em; height: 0.55em; border-radius: 50%; background: var(--signal); box-shadow: 0 0 0 0 var(--signal); animation: breathe 2.6s ease-out infinite; vertical-align: middle; }
.live-dot.is-busy { background: var(--tape); box-shadow-color: var(--tape); animation-duration: 0.9s; }
.live-dot.is-off { background: var(--chalk-faint); animation: none; }
@keyframes breathe { 0% { box-shadow: 0 0 0 0 rgba(127, 209, 139, 0.55); } 70% { box-shadow: 0 0 0 0.6em rgba(127, 209, 139, 0); } 100% { box-shadow: 0 0 0 0 rgba(127, 209, 139, 0); } }

.btn { border: 1px solid var(--chalk); background: transparent; color: var(--chalk); padding: 0.6rem 1.15rem; border-radius: 999px; font-weight: 600; cursor: pointer; transition: background 0.2s, color 0.2s, opacity 0.2s; white-space: nowrap; }
.btn:hover { background: var(--chalk); color: var(--stone); }
.btn[disabled] { opacity: 0.45; cursor: wait; }

/* A "pitch" is one section of the climb. */
.pitch { position: relative; max-width: 84rem; margin: 0 auto; padding: clamp(4rem, 10vh, 8rem) var(--gutter); }
.pitch-head { margin-bottom: clamp(2rem, 5vh, 3.5rem); }
.pitch-tag { color: var(--tape); text-transform: uppercase; letter-spacing: 0.18em; margin: 0 0 1rem; display: flex; align-items: center; gap: 0.75rem; }
.pitch-tag::before { content: ''; width: 2rem; border-top: 1px solid var(--tape); }
.pitch-title { font-family: var(--serif); font-weight: 400; font-size: clamp(3rem, 8vw, 6.5rem); line-height: 0.95; letter-spacing: -0.015em; margin: 0; }
.pitch-sub { display: block; font-style: italic; font-size: clamp(1.4rem, 3vw, 2.4rem); color: var(--chalk-dim); margin-top: 0.4em; letter-spacing: 0; }
.lead { font-size: clamp(1.15rem, 1rem + 0.6vw, 1.45rem); line-height: 1.4; }

.polaroid { background: var(--paper); color: var(--ink); padding: 0.7rem 0.7rem 2.4rem; width: min(17rem, 70%); margin: 2.5rem 0 0; box-shadow: 0 24px 48px rgba(0, 0, 0, 0.45); transition: transform 0.5s var(--ease-out); }
.polaroid.lean-left { transform: rotate(-3deg); }
.polaroid.lean-right { transform: rotate(2.5deg); }
.polaroid:hover { transform: rotate(0) scale(1.03); }
.polaroid figcaption { margin-top: 0.7rem; color: var(--ink); }
.polaroid img { background: var(--stone-2); }

/* Scroll reveals: js/reveal.js adds .reveal only when JS runs, so nothing is ever hidden without it. */
.reveal { opacity: 0; transform: translateY(26px); transition: opacity 0.9s var(--ease-out), transform 0.9s var(--ease-out); }
.reveal.is-in { opacity: 1; transform: none; }

.toast { position: fixed; left: 50%; bottom: 5rem; transform: translateX(-50%); z-index: 70; background: var(--chalk); color: var(--stone); padding: 0.6rem 1rem; border-radius: 999px; font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.08em; animation: toast 3s var(--ease-out) forwards; }
@keyframes toast { 0%, 100% { opacity: 0; transform: translate(-50%, 10px); } 10%, 85% { opacity: 1; transform: translate(-50%, 0); } }

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .live-dot, .polaroid, .reveal { animation: none; transition: none; }
}
/* The mark (top-left) and the topo-route navigation (right rail on desktop, bottom bar on phones). */
.mark { position: fixed; top: 1.25rem; left: 1.25rem; z-index: 45; color: var(--chalk); text-decoration: none; padding: 0.3rem; transition: color 0.2s, transform 0.3s var(--ease-out); }
.mark:hover { color: var(--tape); transform: translateY(-2px); }
.mark-cursor { animation: blink 1.1s steps(2, start) infinite; }
@keyframes blink { to { visibility: hidden; } }

.topo { position: fixed; right: 1.4rem; top: 50%; transform: translateY(-50%); z-index: 45; width: 2.5rem; height: min(56vh, 24rem); }
.topo-line { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.topo-track { fill: none; stroke: var(--stone-4); stroke-width: 1.5; stroke-dasharray: 2 5; stroke-linecap: round; }
.topo-progress { fill: none; stroke: var(--chalk); stroke-width: 2; stroke-linecap: round; transition: stroke-dashoffset 0.25s linear; }
.topo-holds { list-style: none; margin: 0; padding: 0; position: relative; height: 100%; display: flex; flex-direction: column; justify-content: space-between; align-items: flex-end; }
.hold { display: flex; flex-direction: row-reverse; align-items: center; gap: 0.7rem; text-decoration: none; color: var(--chalk-dim); padding: 0.2rem 0; }
.hold-dot { width: 0.8rem; height: 0.8rem; border-radius: 50%; border: 1.5px solid var(--chalk-dim); background: var(--stone); flex: none; transition: transform 0.3s var(--ease-out), background 0.3s, border-color 0.3s, box-shadow 0.3s; }
.hold-label { font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; opacity: 0; transform: translateX(6px); transition: opacity 0.25s, transform 0.25s var(--ease-out); }
.hold-label b { color: var(--tape); font-weight: 500; margin-right: 0.5em; }
.hold:hover .hold-label, .hold:focus-visible .hold-label, .hold.is-here .hold-label { opacity: 1; transform: none; }
.hold.is-past .hold-dot { background: var(--chalk); border-color: var(--chalk); }
.hold.is-here .hold-dot { background: var(--tape); border-color: var(--tape); transform: scale(1.35); box-shadow: 0 0 0 5px var(--tape-soft); }
.hold:hover .hold-dot { transform: scale(1.25); }

@media (min-width: 761px) {
    .pitch { padding-right: calc(var(--gutter) + var(--rail)); }
}

@media (max-width: 760px) {
    .mark { top: 0.9rem; left: 0.9rem; }
    .topo { right: 0; left: 0; top: auto; bottom: 0; transform: none; width: auto; height: 3.4rem; padding: 0.2rem 1.6rem 0; background: linear-gradient(to top, var(--stone) 75%, transparent); }
    .topo-holds { flex-direction: row; align-items: center; }
    .hold { flex-direction: column; gap: 0.35rem; padding: 0.4rem 0.3rem; }
    .hold-label { position: absolute; bottom: 2.4rem; transform: translateY(4px); font-size: 0.62rem; }
    .hold-label b { display: none; }
    .hold:hover .hold-label { opacity: 0; }
    .hold.is-here .hold-label { opacity: 1; transform: none; }
    body { padding-bottom: 3.4rem; }
}

@media (prefers-reduced-motion: reduce) {
    .mark-cursor { animation: none; }
    .topo-progress { transition: none; }
}
/* 00 / base: the opener and the ticker beneath it. */
.hero { min-height: 100svh; display: grid; align-content: center; max-width: none; padding: 4rem var(--gutter) 4rem; overflow: hidden; }
.hero-canvas { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; }
.hero-inner { position: relative; z-index: 1; width: 100%; max-width: 84rem; margin: 0 auto; padding: 0 var(--gutter); box-sizing: border-box; }
.hero-kicker { display: flex; align-items: center; gap: 0.7rem; color: var(--chalk-dim); margin: 0 0 2.2rem; }
.hero-name { font-family: var(--serif); font-weight: 400; font-size: clamp(4.6rem, 19vw, 13.5rem); line-height: 0.86; letter-spacing: -0.03em; margin: 0 0 2.2rem; }
.hero-line { display: block; }
.hero-line:nth-child(2) { font-style: italic; padding-left: 0.08em; color: var(--chalk); }
.hero-lede { max-width: 38rem; font-size: clamp(1.15rem, 1rem + 0.8vw, 1.5rem); line-height: 1.38; margin: 0 0 2.5rem; }
.hero-lede em { font-family: var(--serif); font-style: italic; font-size: 1.3em; line-height: 0; color: var(--tape); white-space: nowrap; }
.hero-cue { margin: 0; }
.hero-cue a { text-decoration: none; color: var(--chalk-dim); display: inline-flex; align-items: center; gap: 0.6rem; }
.hero-cue a:hover { color: var(--chalk); }
.cue-arrow { display: inline-block; animation: bob 2.2s ease-in-out infinite; }
@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(5px); } }
.hero-coord { position: absolute; left: calc(var(--gutter) * 2); bottom: 1.4rem; z-index: 1; color: var(--chalk-faint); margin: 0; }

/* Ticker: two copies of the list slide left forever (js/ticker.js makes the copy). */
.ticker { border-top: 1px solid var(--stone-3); border-bottom: 1px solid var(--stone-3); overflow: hidden; padding: 0.75rem 0; background: var(--stone); }
.ticker-track { display: flex; width: max-content; list-style: none; margin: 0; padding: 0; white-space: nowrap; font-family: var(--mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.16em; color: var(--chalk-dim); animation: ticker 70s linear infinite; }
.ticker-track li { padding-right: 2.6rem; }
.ticker-track li::after { content: '\2726'; margin-left: 2.6rem; color: var(--tape); }
.ticker-track [data-tick] { color: var(--chalk); }
@keyframes ticker { to { transform: translateX(-50%); } }
.ticker:hover .ticker-track { animation-play-state: paused; }

@media (max-width: 760px) {
    .hero { padding-top: 3.5rem; }
    .hero-inner { padding: 0; }
    .hero-coord { display: none; }
}
@media (prefers-reduced-motion: reduce) {
    .cue-arrow { animation: none; }
    .ticker-track { animation: none; width: auto; flex-wrap: wrap; row-gap: 0.4rem; white-space: normal; }
}
/* 00.5 / about: one paragraph, a portrait and a fact list, sitting quietly between
   the ticker and the first section. */
.about { padding-top: clamp(3rem, 7vh, 5rem); padding-bottom: 0; }
.about + .experience { padding-top: clamp(2.5rem, 6vh, 4.5rem); }
.about-grid { display: grid; grid-template-columns: minmax(0, 3fr) minmax(12rem, 1fr); gap: 2rem 4rem; align-items: start; }
.about-lede { max-width: 44rem; font-size: clamp(1.1rem, 1rem + 0.5vw, 1.35rem); line-height: 1.5; margin: 0; color: var(--chalk-dim); }
.about-lede strong { font-weight: 600; color: var(--chalk); }
/* The portrait is tilted, so the gap has to clear its dropped corner, not its box. */
.about-side { display: grid; gap: 2rem; justify-items: start; }
.about-shot { width: auto; max-width: 100%; margin: 0; padding: 0.5rem 0.5rem 0.75rem; }
/* Square, which is both what the source is and what a polaroid frame actually looks like. */
.about-shot img { aspect-ratio: 1 / 1; object-fit: cover; }
.about-shot figcaption { font-family: var(--serif); font-style: italic; font-size: 0.98rem; line-height: 1.3; margin-top: 0.6rem; }
.about-facts { list-style: none; margin: 0; padding: 0; width: 100%; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.16em; color: var(--chalk-dim); }
.about-facts li { padding: 0.55rem 0; border-top: 1px solid var(--stone-3); }
.about-facts li:last-child { border-bottom: 1px solid var(--stone-3); }
@media (max-width: 760px) {
    .about-grid { grid-template-columns: minmax(0, 1fr); gap: 1.6rem; }
    .about-side { gap: 1.2rem; }
    .about-shot { max-width: min(13rem, 58%); }
    .about-facts { display: flex; flex-wrap: wrap; gap: 0.4rem 1.4rem; }
    .about-facts li, .about-facts li:last-child { border: 0; padding: 0; }
}
/* 01 / experience: the scannable CV. Two roles, a résumé button, an education line.
   Built to be read in ten seconds: org and dates on one line, then quantified bullets. */
.cv-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1.5rem; margin: 0 0 clamp(2.5rem, 6vh, 3.5rem); max-width: none; }
.cv-resume { display: inline-flex; align-items: center; gap: 0.6rem; border-color: var(--tape); color: var(--tape); }
.cv-resume:hover { background: var(--tape); color: var(--stone); }
.cv-arrow { transition: transform 0.3s var(--ease-out); }
.cv-resume:hover .cv-arrow { transform: translateY(2px); }
.cv-link { color: var(--chalk-dim); text-decoration: none; border-bottom: 1px solid var(--stone-4); padding-bottom: 1px; transition: color 0.2s, border-color 0.2s; }
.cv-link:hover { color: var(--tape); border-color: var(--tape); }

.cv { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(2.25rem, 5vh, 3.25rem); }
.cv-role { position: relative; padding-left: 1.5rem; border-left: 1px solid var(--stone-3); }
.cv-role::before { content: ''; position: absolute; left: -0.3rem; top: 0.55rem; width: 0.55rem; height: 0.55rem; border-radius: 50%; background: var(--tape); }

.cv-head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0.4rem 1.5rem; }
.cv-org { font-family: var(--serif); font-weight: 400; font-size: clamp(1.6rem, 1.3rem + 1vw, 2.2rem); line-height: 1.1; margin: 0; }
.cv-when { display: flex; flex-wrap: wrap; gap: 0.4rem 0.9rem; margin: 0; color: var(--chalk-faint); text-transform: uppercase; letter-spacing: 0.12em; }
.cv-where { color: var(--chalk-faint); }
.cv-where::before { content: '· '; }
.cv-title { color: var(--chalk); font-size: 1.05rem; margin: 0.3rem 0 1rem; max-width: none; }

.cv-points { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.7rem; max-width: 60rem; }
.cv-points li { position: relative; padding-left: 1.1rem; color: var(--chalk-dim); font-size: 0.95rem; line-height: 1.6; }
.cv-points li::before { content: ''; position: absolute; left: 0; top: 0.68em; width: 0.35rem; height: 1px; background: var(--stone-4); }
.cv-points strong { color: var(--chalk); font-weight: 600; }

.cv-edu { margin: clamp(2.25rem, 5vh, 3.25rem) 0 0; padding-top: 1.4rem; border-top: 1px solid var(--stone-3); color: var(--chalk-dim); font-size: 0.95rem; max-width: 60rem; }
.cv-edu strong { color: var(--chalk); font-weight: 600; }

@media (max-width: 620px) {
    .cv-head { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
    .cv-actions { gap: 1rem 1.5rem; }
}
@media (prefers-reduced-motion: reduce) {
    .cv-arrow { transition: none; }
    .cv-resume:hover .cv-arrow { transform: none; }
}
/* 01 / work: three projects in one row, styled like guidebook entries — a photograph,
   then number, year, name, the one-line version, and the detail. The rail down the left
   edge of each card fills on hover, the way the topo line fills on scroll. */
.work-intro { max-width: 47rem; color: var(--chalk-dim); font-size: clamp(1.05rem, 1rem + 0.35vw, 1.22rem); line-height: 1.5; margin: 0 0 clamp(2rem, 5vh, 3rem); }
.work-intro em { color: var(--chalk); }

/* One row of equal-height cards. Below the breakpoint they stack: three columns of this
   much prose gets unreadably narrow before it gets short. */
.route-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.1rem; align-items: stretch; }
@media (min-width: 1100px) {
    .route-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Flex column + margin-top:auto on the footer, so the stack and the links line up along
   the bottom of all three cards however unevenly the prose falls. The opacity transition
   is for js/reveal.js: .reveal's own loses to this rule (same specificity, later file). */
.route { position: relative; display: flex; flex-direction: column; background: var(--stone-2); border: 1px solid var(--stone-3); overflow: hidden; transition: border-color 0.3s, transform 0.45s var(--ease-out), opacity 0.7s var(--ease-out); }
.route::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; z-index: 1; background: var(--stone-4); transform: scaleY(0.1); transform-origin: top; transition: transform 0.55s var(--ease-out), background 0.3s; }
.route:hover, .route:focus-within { border-color: var(--stone-4); transform: translateY(-3px); }
.route:hover::before, .route:focus-within::before { background: var(--tape); transform: scaleY(1); }

/* Held back a little so the photographs sit in the page rather than shouting off it;
   they come up to full strength when you point at the card. */
.route-shot { margin: 0; border-bottom: 1px solid var(--stone-3); }
.route-shot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; filter: saturate(0.9) brightness(0.96); transition: filter 0.45s var(--ease-out); }
.route:hover .route-shot img, .route:focus-within .route-shot img { filter: none; }

.route-text { display: flex; flex-direction: column; flex: 1 1 auto; padding: 1.45rem 1.5rem 1.4rem; }
.route-meta { display: flex; align-items: center; gap: 0.85rem; margin: 0 0 0.85rem; color: var(--chalk-faint); text-transform: uppercase; letter-spacing: 0.14em; }
.route-no { color: var(--tape); }
.route-flag { margin-left: auto; border: 1px solid var(--stone-4); border-radius: 999px; padding: 0.12rem 0.6rem; letter-spacing: 0.1em; }
.route-name { font-family: var(--serif); font-weight: 400; font-size: clamp(1.75rem, 1.4rem + 1vw, 2.35rem); line-height: 1.05; letter-spacing: -0.01em; margin: 0 0 0.55rem; text-wrap: balance; }
.route-what { color: var(--chalk); font-size: 1.04rem; line-height: 1.4; margin: 0 0 0.85rem; max-width: 42ch; text-wrap: balance; }
.route-body { color: var(--chalk-dim); font-size: 0.93rem; line-height: 1.62; margin: 0 0 1.3rem; max-width: none; }
.route-body strong { color: var(--chalk); font-weight: 600; }
.route-body code { font-family: var(--mono); font-size: 0.86em; color: var(--ice); }

.route-stack { list-style: none; display: flex; flex-wrap: wrap; gap: 0.35rem; margin: auto 0 0.9rem; padding: 0; }
.route-stack:last-child { margin-bottom: 0; }  /* a card with no links ends on its chips */
.route-stack li { border: 1px solid var(--stone-3); border-radius: 2px; padding: 0.16rem 0.5rem; color: var(--chalk-faint); font-family: var(--mono); font-size: 0.68rem; letter-spacing: 0.06em; }

.route-links { display: flex; flex-wrap: wrap; gap: 1.1rem; margin: 0; }
.route-links a { color: var(--chalk-dim); text-decoration: none; border-bottom: 1px solid var(--stone-4); padding-bottom: 1px; transition: color 0.2s, border-color 0.2s; }
.route-links a:hover { color: var(--tape); border-color: var(--tape); }

@media (prefers-reduced-motion: reduce) {
    .route, .route::before, .route-shot img { transition: none; }
    .route:hover, .route:focus-within { transform: none; }
    .route::before { transform: scaleY(1); }
}
/* 03 / off the wall: photographs pinned up as polaroids. Reuses the .polaroid
   component from 10-base.css and only changes what a grid of them needs: no fixed
   width, a tighter bottom edge (the caption fills it), and a tilt per position. */
.frames-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(1.6rem, 3vw, 2.8rem); align-items: start; max-width: 72rem; }
.frame { position: relative; width: auto; margin: 0; padding: 0.55rem 0.55rem 0.85rem; transition: transform 0.45s var(--ease-out), opacity 0.7s var(--ease-out); }
.frame img { aspect-ratio: 3 / 4; object-fit: cover; filter: saturate(0.94); }
.frame figcaption { font-family: var(--serif); font-style: italic; font-size: 1.02rem; line-height: 1.3; margin-top: 0.75rem; }

.frame:nth-child(odd) { transform: rotate(-1.7deg); }
.frame:nth-child(even) { transform: rotate(1.5deg); }
.frame:nth-child(3n) { transform: rotate(-0.6deg); }
.frame:hover, .frame:focus-within { transform: rotate(0) scale(1.035); z-index: 2; }

@media (min-width: 620px) {
    .frames-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .frame:nth-child(even) { margin-top: clamp(0.9rem, 2.5vw, 2.4rem); }
}
@media (min-width: 980px) {
    .frames-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
    .frame, .frame:nth-child(odd), .frame:nth-child(even), .frame:nth-child(3n) { transform: none; }
    .frame:hover, .frame:focus-within { transform: none; }
}
/* 01 / machine: chat with the on-device model beside live board telemetry. Blue is the machine's color. */
.machine-intro { max-width: var(--measure); color: var(--chalk-dim); font-size: 1.08rem; margin: 0 0 3rem; }
.machine-grid { display: grid; grid-template-columns: minmax(0, 3fr) minmax(16rem, 2fr); gap: 1.6rem; align-items: stretch; }

.chat { display: flex; flex-direction: column; gap: 1rem; min-height: 0; padding: 1.25rem; border: 1px solid var(--stone-3); border-radius: 14px; background: var(--stone-2); }
.chat-log { flex: 1; min-height: 14rem; max-height: 24rem; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 1rem; scroll-behavior: smooth; padding-right: 0.25rem; }
.msg p { margin: 0; max-width: none; white-space: pre-wrap; overflow-wrap: anywhere; }
.msg-user { align-self: flex-end; max-width: 85%; background: var(--stone-3); padding: 0.6rem 0.95rem; border-radius: 14px 14px 3px 14px; }
.msg-bot { align-self: flex-start; max-width: 94%; padding-left: 1rem; border-left: 2px solid var(--ice); }
.msg-bot.is-streaming p::after { content: '\258D'; color: var(--ice); margin-left: 0.1em; animation: blink 1s steps(2, start) infinite; }
.msg-bot.msg-error { border-left-color: var(--tape); color: var(--chalk-dim); }
.msg-status { color: var(--chalk-faint); font-style: italic; }
.msg-meta { display: block; margin-top: 0.45rem; color: var(--chalk-faint); }

.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.chip { border: 1px solid var(--stone-4); background: transparent; color: var(--chalk-dim); border-radius: 999px; padding: 0.4rem 0.85rem; font-size: 0.85rem; cursor: pointer; transition: border-color 0.2s, color 0.2s; }
.chip:hover { border-color: var(--ice); color: var(--chalk); }
.chat-form { display: flex; gap: 0.5rem; }
.chat-form input, .contact-form input, .contact-form textarea { flex: 1; min-width: 0; background: var(--stone); border: 1px solid var(--stone-4); color: var(--chalk); font: inherit; padding: 0.7rem 1rem; border-radius: 999px; transition: border-color 0.2s; }
.chat-form input { width: 0; } /* flex-grow fills it; without this the input's 20-char intrinsic width widens the column on phones */
.chat-form input:focus { outline: none; border-color: var(--ice); }
.chat-foot { color: var(--chalk-faint); margin: 0; }

.telemetry { padding: 1.25rem; border: 1px solid var(--stone-3); border-radius: 14px; background: linear-gradient(180deg, var(--ice-soft), transparent 45%), var(--stone-2); }
.tele-head { display: flex; align-items: center; gap: 0.6rem; margin: 0 0 0.9rem; color: var(--chalk); }
.tele-state { margin-left: auto; color: var(--chalk-faint); text-align: right; }
.tele-state.is-busy { color: var(--tape); }
.tele-rows { margin: 0; }
.tele-row { display: grid; grid-template-columns: 5.2rem minmax(0, 1fr); gap: 0.75rem; padding: 0.55rem 0; border-top: 1px solid var(--stone-3); font-family: var(--mono); font-size: 0.78rem; }
#t-tps.is-live { color: var(--ice); }
.tele-row dt { color: var(--chalk-faint); text-transform: uppercase; letter-spacing: 0.12em; padding-top: 0.15em; }
.tele-row dd { margin: 0; color: var(--chalk-dim); overflow-wrap: anywhere; }
.tele-big { font-family: var(--serif); font-size: 1.7rem; line-height: 1; color: var(--chalk); margin-right: 0.15rem; }
.spark { display: block; width: 100%; height: 32px; margin-top: 0.45rem; }
.meter { display: block; height: 3px; margin-top: 0.5rem; background: var(--stone-3); border-radius: 2px; overflow: hidden; }
.meter-fill { display: block; height: 100%; width: 0; background: var(--ice); transition: width 0.7s var(--ease-out); }


@media (max-width: 900px) { .machine-grid { grid-template-columns: minmax(0, 1fr); } .chat { min-height: 24rem; } }
@media (prefers-reduced-motion: reduce) {
    .msg-bot.is-streaming p::after { animation: none; }
}
/* 02 / send it: the contact form (POSTs to /api/contact), links, footer. */
.contact-form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.8rem; max-width: 40rem; }
.contact-form input, .contact-form textarea { border-radius: 10px; width: 100%; }
.contact-form textarea { resize: vertical; min-height: 8rem; font-family: var(--sans); }
.contact-form input:focus, .contact-form textarea:focus { outline: none; border-color: var(--tape); }
.btn-big { justify-self: start; padding: 0.9rem 1.6rem; font-size: 1.05rem; }
.contact-form .hp { position: absolute; left: -10000px; width: 1px; height: 1px; opacity: 0; }
.contact-note { margin: 0; color: var(--tape); font-size: 0.8rem; }
.contact-note.is-ok { color: var(--chalk); }
.contact-form.is-hidden, .contact-sent[hidden] { display: none; }
.contact-sent { max-width: 40rem; padding: 1.6rem 1.8rem; border: 1px solid var(--tape); border-radius: 14px; background: var(--stone-2); display: grid; grid-template-columns: auto 1fr; gap: 0.4rem 1.2rem; align-items: center; animation: sent-in 0.6s var(--ease-out) both; }
.sent-check { width: 3rem; height: 3rem; grid-row: 1 / 3; fill: none; stroke: var(--tape); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.sent-check circle { stroke-dasharray: 132; stroke-dashoffset: 132; animation: draw 0.7s var(--ease-out) 0.1s forwards; }
.sent-check path { stroke-dasharray: 30; stroke-dashoffset: 30; animation: draw 0.4s var(--ease-out) 0.6s forwards; }
.sent-title { font-family: var(--serif); font-weight: 400; font-size: 2.2rem; line-height: 1; margin: 0; }
.sent-body { margin: 0; color: var(--chalk-dim); }
.contact-sent .btn { grid-column: 2; justify-self: start; margin-top: 0.8rem; }
@keyframes draw { to { stroke-dashoffset: 0; } }
@keyframes sent-in { from { opacity: 0; transform: translateY(10px); } }
@media (prefers-reduced-motion: reduce) { .contact-sent, .sent-check circle, .sent-check path { animation: none; stroke-dashoffset: 0; } }
.contact-alt { color: var(--chalk-dim); margin: 1.8rem 0 0; }

/* Paper airplane (js/plane.js): fixed SVG overlay above everything but the grain. */
.plane-layer { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 52; pointer-events: none; transition: opacity 0.8s; }
.plane-layer.is-done { opacity: 0; }
.plane { filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.55)); }
.plane-wing { fill: var(--chalk); stroke: var(--stone); stroke-width: 1; stroke-linejoin: round; }
.plane-wing-low { fill: var(--chalk-dim); }
.plane-keel { fill: var(--chalk-faint); stroke: var(--stone); stroke-width: 1; stroke-linejoin: round; }

.site-footer { max-width: 84rem; margin: 0 auto; padding: 2.5rem var(--gutter) 4rem; border-top: 1px solid var(--stone-3); color: var(--chalk-faint); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 1rem; }
.site-footer p { margin: 0; max-width: none; }
.foot-hint { opacity: 0.35; letter-spacing: 0.2em; }

@media (min-width: 761px) { .site-footer { padding-right: calc(var(--gutter) + var(--rail)); } }
@media (max-width: 760px) { .site-footer { padding-bottom: 6rem; } }

/* The chat and the contact form sit close together: halve the seam between them. */
.machine { padding-bottom: clamp(2rem, 4vh, 3.5rem); }
.contact { padding-top: clamp(2rem, 4vh, 3.5rem); }
/* The Send button that becomes a paper plane (js/paperplane.js draws it in the fixed SVG
   overlay). The button itself is a solid chalk pill; on send it hides instantly and the SVG
   sheet takes its exact place. Facets darken as the fold progresses (data-shade 0..3). */
.send-btn { position: relative; background: var(--chalk); color: var(--stone); border-color: var(--chalk); transition: opacity 0.35s, transform 0.35s var(--ease-out); }
.send-btn:hover { background: var(--chalk); color: var(--stone); }
.send-btn.is-away, .send-btn.is-away:disabled { opacity: 0; transition: none; }
.pp { filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.5)); }
.pp-sheet { fill: var(--chalk); }
.pp polygon { fill: var(--chalk); stroke: var(--stone); stroke-width: 0.6; stroke-linejoin: round; }
.pp[data-shade='1'] .pp-rw, .pp[data-shade='2'] .pp-rw { fill: var(--chalk-dim); }
.pp[data-shade='2'] .pp-lb, .pp[data-shade='3'] .pp-lb { fill: var(--chalk-dim); }
.pp[data-shade='2'] .pp-rb { fill: var(--chalk-faint); }
.pp[data-shade='3'] .pp-rb { fill: var(--stone-4); }
@media (prefers-reduced-motion: reduce) { .send-btn { transition: none; } }
/* Admin edit mode (js/edit.js). Every rule is behind .editing on <html>, which is only added
   after /api/admin/me confirms a session — so none of this can affect a visitor's page. */
.editing .editable { outline: 1px dashed var(--stone-4); outline-offset: 0.35rem; border-radius: 2px; cursor: text; transition: outline-color 0.2s, background 0.2s; }
.editing .editable:hover { outline-color: var(--chalk-faint); }
.editing .editable:focus, .editing .editable[contenteditable='true'] { outline: 1px solid var(--tape); background: var(--tape-soft); }
.editing .editable.is-saving { outline-color: var(--chalk-dim); opacity: 0.65; }
.editing .editable.is-saved { outline: 1px solid var(--signal); }
.editing .editable.is-failed { outline: 1px solid var(--plate-red); }

/* The polaroid captions sit on paper, so the dashed chalk outline is invisible there. */
.editing .polaroid .editable { outline-color: var(--steel-dark); }
.editing .polaroid .editable:focus { outline-color: var(--tape); }

.editbar { position: fixed; left: 1rem; bottom: 1rem; z-index: 80; display: flex; align-items: center; gap: 0.75rem; max-width: min(46rem, calc(100vw - 2rem)); padding: 0.6rem 0.75rem 0.6rem 0.9rem; background: var(--stone-2); border: 1px solid var(--stone-4); border-radius: 999px; box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5); color: var(--chalk-dim); }
.editbar-dot { flex: none; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--tape); }
.editbar[data-tone='good'] .editbar-dot { background: var(--signal); }
.editbar[data-tone='bad'] .editbar-dot { background: var(--plate-red); }
.editbar #editbar-msg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.editbar-done { flex: none; border: 1px solid var(--stone-4); background: transparent; color: var(--chalk-dim); font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.08em; padding: 0.2rem 0.7rem; border-radius: 999px; cursor: pointer; }
.editbar-done:hover { border-color: var(--chalk); color: var(--chalk); }

@media (max-width: 760px) {
    .editbar { bottom: 4.2rem; }   /* clear of the phone nav bar */
}
@media (prefers-reduced-motion: reduce) {
    .editing .editable { transition: none; }
}
