/* ============================================================

   STUDIO KAIN & KODE — Nabila Melsyana

   Warm editorial ground, one accent, batik as hairline only.



   00 tokens          05 work index

   01 reset & base    06 about + portrait

   02 typography      07 capabilities

   03 chrome (header) 08 trajectory

   04 hero            09 contact + colophon

                      10 console (⌘K)

                      11 drawer (case study)

                      12 motion utilities

                      13 responsive

   ============================================================ */



/* ---------- 00 · TOKENS ---------- */

:root {

    /* ground */

    --ground: #FAF8F4;

    --ground-2: #F2EEE7;

    --surface: #FFFFFF;

    --surface-sunk: #F6F3ED;



    /* ink */

    --ink: #17150F;

    --ink-2: #3D382E;

    --muted: #6B6459;

    --faint: #9A9285;



    /* the one accent */

    --accent: #C1502E;

    --accent-deep: #9C3D20;

    --accent-wash: #F7EAE3;



    /* lines */

    --line: rgba(23, 21, 15, .10);

    --line-2: rgba(23, 21, 15, .16);

    --line-3: rgba(23, 21, 15, .28);



    /* shadow — soft, warm, never grey */

    --shadow-1: 0 1px 2px rgba(23, 21, 15, .04), 0 2px 8px rgba(23, 21, 15, .04);

    --shadow-2: 0 2px 4px rgba(23, 21, 15, .05), 0 12px 32px rgba(23, 21, 15, .08);

    --shadow-3: 0 4px 8px rgba(23, 21, 15, .06), 0 32px 64px rgba(23, 21, 15, .14);



    /* type */

    --display: 'Fraunces', 'Iowan Old Style', Georgia, serif;

    --sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;

    --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;



    /* rhythm */

    --gutter: clamp(1.25rem, 5vw, 3.5rem);

    --measure: 66ch;

    --wide: 1240px;

    --section-y: clamp(5.5rem, 11vw, 9.5rem);

    --radius: 10px;

    --radius-lg: 16px;



    /* motion — Emil's curves */

    --ease-out: cubic-bezier(.23, 1, .32, 1);

    --ease-inout: cubic-bezier(.77, 0, .175, 1);

    --ease-drawer: cubic-bezier(.32, .72, 0, 1);

    --t-press: 120ms;

    --t-hover: 160ms;

    --t-pop: 200ms;

    --t-drawer: 380ms;



    /* the only ornament in the system: kawung, as hairline */

    --kawung: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64' viewBox='0 0 64 64'%3E%3Cg fill='none' stroke='%2317150F' stroke-width='1'%3E%3Cellipse cx='20' cy='20' rx='8' ry='15' transform='rotate(45 20 20)'/%3E%3Cellipse cx='44' cy='20' rx='8' ry='15' transform='rotate(-45 44 20)'/%3E%3Cellipse cx='20' cy='44' rx='8' ry='15' transform='rotate(-45 20 44)'/%3E%3Cellipse cx='44' cy='44' rx='8' ry='15' transform='rotate(45 44 44)'/%3E%3Ccircle cx='32' cy='32' r='2.5'/%3E%3C/g%3E%3C/svg%3E");

}



/* ---------- 01 · RESET & BASE ---------- */

*,

*::before,

*::after {

    box-sizing: border-box;

    margin: 0;

    padding: 0;

}



html {

    -webkit-text-size-adjust: 100%;

    scroll-behavior: smooth;

}



/* [hidden] must beat any display we set later, or hidden panels keep

   swallowing pointer events while being invisible */

[hidden] {

    display: none !important;

}



body {

    background: var(--ground);

    color: var(--ink);

    font-family: var(--sans);

    font-size: 17px;

    line-height: 1.6;

    font-feature-settings: 'kern' 1, 'liga' 1, 'cv11' 1;

    -webkit-font-smoothing: antialiased;

    -moz-osx-font-smoothing: grayscale;

    overflow-x: hidden;

}



body.is-locked {

    overflow: hidden;

}



img {

    max-width: 100%;

    display: block;

}



a {

    color: inherit;

    text-decoration: none;

}



button,

input,

textarea {

    font: inherit;

    color: inherit;

}



::selection {

    background: var(--accent);

    color: var(--ground);

}



:focus-visible {

    outline: 2px solid var(--accent);

    outline-offset: 3px;

    border-radius: 3px;

}



.shell {

    width: 100%;

    max-width: var(--wide);

    margin-inline: auto;

    padding-inline: var(--gutter);

}



.skip {

    position: absolute;

    left: -9999px;

    top: .5rem;

    z-index: 999;

    background: var(--ink);

    color: var(--ground);

    padding: .7rem 1.1rem;

    border-radius: var(--radius);

    font-size: .9rem;

    font-weight: 500;

}



.skip:focus {

    left: 1rem;

}



.sr {

    position: absolute;

    width: 1px;

    height: 1px;

    overflow: hidden;

    clip: rect(0 0 0 0);

    white-space: nowrap;

}



/* ---------- 02 · TYPOGRAPHY ---------- */

h1,

h2,

h3,

h4 {

    font-weight: 400;

    line-height: 1.06;

    letter-spacing: -.02em;

}



.display {

    font-family: var(--display);

    font-optical-sizing: auto;

    font-variation-settings: 'SOFT' 0, 'WONK' 0;

}



.eyebrow {

    font-family: var(--mono);

    font-size: .7rem;

    font-weight: 500;

    letter-spacing: .11em;

    text-transform: uppercase;

    color: var(--faint);

}



.lede {

    font-size: clamp(1.15rem, 2vw, 1.4rem);

    line-height: 1.5;

    color: var(--ink-2);

    letter-spacing: -.011em;

    max-width: 34ch;

}



p {

    max-width: var(--measure);

}



.prose p + p {

    margin-top: 1.1em;

}



.prose {

    color: var(--muted);

    font-size: 1.0435rem;

    line-height: 1.72;

}



/* section headers — a rule, a number, a title. Nothing else. */

.sec-head {

    display: grid;

    grid-template-columns: auto 1fr;

    gap: clamp(1.5rem, 4vw, 3.5rem);

    align-items: end;

    padding-bottom: 1.9rem;

    margin-bottom: clamp(2.5rem, 5vw, 4rem);

    background-image: linear-gradient(var(--line), var(--line));

    background-repeat: no-repeat;

    background-position: 0 100%;

    background-size: 100% 1px;

}



.sec-head .eyebrow {

    padding-bottom: .55rem;

}



.sec-title {

    font-family: var(--display);

    font-size: clamp(2.1rem, 5vw, 3.6rem);

    line-height: 1;

}



/* the wipe is opt-in, added by script — so a failure can never leave

   a heading invisible the way it did before */

.sec-title.is-wipe {

    clip-path: inset(0 100% 0 0);

}



.sec-title em {

    font-style: italic;

    color: var(--accent);

}



.sec-note {

    grid-column: 2;

    margin-top: 1rem;

    color: var(--muted);

    max-width: 52ch;

    font-size: .97rem;

}



.section {

    padding-block: var(--section-y);

    position: relative;

}



/* anchors must clear the fixed header */

section[id],

#hero {

    scroll-margin-top: 5rem;

}



.section + .section {

    border-top: 1px solid var(--line);

}



/* ---------- 03 · CHROME ---------- */

#top {

    position: fixed;

    inset: 0 0 auto 0;

    z-index: 60;

    display: flex;

    align-items: center;

    gap: 1.5rem;

    padding: .85rem var(--gutter);

    background: color-mix(in srgb, var(--ground) 82%, transparent);

    backdrop-filter: saturate(180%) blur(14px);

    -webkit-backdrop-filter: saturate(180%) blur(14px);

    border-bottom: 1px solid transparent;

    transition: border-color var(--t-hover) var(--ease-out),

        background-color var(--t-hover) var(--ease-out);

}



#top.is-stuck {

    border-bottom-color: var(--line);

}



.mark {

    display: flex;

    align-items: baseline;

    gap: .55rem;

    font-family: var(--display);

    font-size: 1.02rem;

    letter-spacing: -.015em;

    white-space: nowrap;

}



.mark i {

    width: 7px;

    height: 7px;

    border-radius: 50%;

    background: var(--accent);

    display: inline-block;

    flex: none;

}



.nav {

    display: flex;

    gap: .35rem;

    margin-inline: auto;

}



.nav a {

    position: relative;

    padding: .4rem .7rem;

    border-radius: 7px;

    font-size: .88rem;

    color: var(--muted);

    transition: color var(--t-hover) ease, background-color var(--t-hover) ease;

}



.nav a:hover {

    color: var(--ink);

    background: var(--ground-2);

}



.nav a[aria-current='true'] {

    color: var(--ink);

}



.nav a[aria-current='true']::after {

    content: '';

    position: absolute;

    left: .7rem;

    right: .7rem;

    bottom: .12rem;

    height: 1.5px;

    background: var(--accent);

    border-radius: 2px;

}



/* the ⌘K trigger — the site's real front door */

.ask {

    display: inline-flex;

    align-items: center;

    gap: .6rem;

    padding: .42rem .5rem .42rem .8rem;

    border: 1px solid var(--line-2);

    border-radius: 99px;

    background: var(--surface);

    color: var(--muted);

    font-size: .84rem;

    cursor: pointer;

    white-space: nowrap;

    transition: border-color var(--t-hover) ease, color var(--t-hover) ease,

        box-shadow var(--t-hover) ease, transform var(--t-press) var(--ease-out);

}



.ask:hover {

    color: var(--ink);

    border-color: var(--line-3);

    box-shadow: var(--shadow-1);

}



.ask:active {

    transform: scale(.98);

}



.ask kbd {

    font-family: var(--mono);

    font-size: .68rem;

    padding: .2rem .42rem;

    border-radius: 5px;

    background: var(--ground-2);

    color: var(--faint);

    border: 1px solid var(--line);

}



#burger {

    display: none;

    width: 38px;

    height: 34px;

    border: 1px solid var(--line-2);

    border-radius: 8px;

    background: var(--surface);

    cursor: pointer;

    position: relative;

}



#burger span,

#burger span::before,

#burger span::after {

    position: absolute;

    left: 50%;

    width: 15px;

    height: 1.5px;

    background: var(--ink);

    border-radius: 2px;

    transition: transform var(--t-pop) var(--ease-out), opacity var(--t-press) ease;

}



#burger span {

    top: 50%;

    transform: translate(-50%, -50%);

}



#burger span::before,

#burger span::after {

    content: '';

    left: 0;

    transform: none;

}



#burger span::before {

    top: -5px;

}



#burger span::after {

    top: 5px;

}



#burger[aria-expanded='true'] span {

    background: transparent;

}



#burger[aria-expanded='true'] span::before {

    transform: translateY(5px) rotate(45deg);

}



#burger[aria-expanded='true'] span::after {

    transform: translateY(-5px) rotate(-45deg);

}



/* ---------- 04 . HERO ---------- */

#hero {

    position: relative;

    min-height: 100svh;

    display: flex;

    align-items: center;

    padding-block: 7.5rem 5rem;

    overflow: hidden;

}



/* one quiet field of texture, bottom-left, behind everything */

#hero::after {

    content: '';

    position: absolute;

    left: -8%;

    bottom: -16%;

    width: min(52vw, 620px);

    aspect-ratio: 1;

    background-image: var(--kawung);

    background-size: 62px 62px;

    opacity: .05;

    pointer-events: none;

    mask-image: radial-gradient(closest-side, #000 22%, transparent 76%);

    -webkit-mask-image: radial-gradient(closest-side, #000 22%, transparent 76%);

}



.hero-grid {

    position: relative;

    z-index: 2;

    display: grid;

    grid-template-columns: minmax(0, 1.25fr) minmax(0, .75fr);

    gap: clamp(2rem, 5vw, 4rem);

    align-items: center;

    width: 100%;

}



.hero-copy {

    max-width: 40rem;

}



/* --- the portrait: the subject of the page --- */

.hero-portrait {

    position: relative;

    z-index: 3;

    justify-self: end;

    width: min(100%, 21rem);

}



.hp-tilt {

    position: relative;

    transform-style: preserve-3d;

    will-change: transform;

}



.hp-card {

    position: relative;

    border-radius: 20px;

    background: var(--surface);

    padding: .7rem .7rem .1rem;

    border: 1px solid rgba(255, 255, 255, .85);

    box-shadow:

        0 2px 6px rgba(23, 21, 15, .07),

        0 26px 56px rgba(23, 21, 15, .20),

        0 70px 100px rgba(23, 21, 15, .10);

}



.hp-frame {

    position: relative;

    border-radius: 14px;

    overflow: hidden;

    aspect-ratio: 1;

    background: var(--ground-2);

}



.hp-frame img {

    width: 100%;

    height: 100%;

    object-fit: cover;

    object-position: center 12%;

    transform: scale(1.02);

    transition: transform 700ms var(--ease-out);

}



.hero-portrait:hover .hp-frame img {

    transform: scale(1);

}



/* a whisper of warmth so the photo belongs to the palette */

.hp-frame::after {

    content: '';

    position: absolute;

    inset: 0;

    background: linear-gradient(200deg, color-mix(in srgb, var(--accent-lit) 8%, transparent), transparent 52%);

    pointer-events: none;

}



.hp-badge {

    position: absolute;

    left: -.7rem;

    bottom: 3.6rem;

    display: inline-flex;

    align-items: center;

    gap: .45rem;

    padding: .42rem .8rem;

    border-radius: 99px;

    background: var(--surface);

    border: 1px solid var(--line-2);

    box-shadow: var(--shadow-2);

    font-family: var(--mono);

    font-size: .68rem;

    letter-spacing: .08em;

    text-transform: uppercase;

    color: var(--ink-2);

    transform: translateZ(40px);

}



.hp-dot {

    width: 6px;

    height: 6px;

    border-radius: 50%;

    background: #3E8E5A;

    box-shadow: 0 0 0 3px rgba(62, 142, 90, .18);

}



.hp-caption {

    padding: .8rem .35rem .75rem;

    text-align: left;

    max-width: none;

}



.hp-caption b {

    display: block;

    font-family: var(--display);

    font-size: 1.08rem;

    font-weight: 400;

    letter-spacing: -.018em;

}



.hp-caption span {

    display: block;

    margin-top: .1rem;

    font-family: var(--mono);

    font-size: .66rem;

    letter-spacing: .07em;

    text-transform: uppercase;

    color: var(--faint);

}



.hero-status {

    display: inline-flex;

    flex-wrap: wrap;

    align-items: center;

    gap: .55rem;

    font-family: var(--mono);

    font-size: .72rem;

    letter-spacing: .07em;

    text-transform: uppercase;

    color: var(--muted);

    margin-bottom: 1.8rem;

}



.hero-status .dot {

    width: 6px;

    height: 6px;

    border-radius: 50%;

    background: #3E8E5A;

    box-shadow: 0 0 0 3px rgba(62, 142, 90, .16);

}



.hero-status .sep {

    color: var(--line-3);

}



#title {

    font-family: var(--display);

    font-size: clamp(2.5rem, 5.6vw, 4.6rem);

    line-height: .98;

    letter-spacing: -.034em;

}



#title em {

    font-style: italic;

    color: var(--accent);

}



.hero-line {

    display: block;

    overflow: hidden;

    padding-bottom: .04em;

}



.hero-line > span {

    display: block;

}



.hero-sub {

    margin-top: 1.8rem;

    color: var(--muted);

    font-size: 1.03rem;

    line-height: 1.68;

    max-width: 50ch;

}



.hero-sub strong {

    color: var(--ink);

    font-weight: 500;

}



.hero-actions {

    display: flex;

    flex-wrap: wrap;

    gap: .65rem;

    margin-top: 2rem;

}



.btn-kbd {

    font-family: var(--mono);

    font-size: .66rem;

    opacity: .55;

    margin-left: .1rem;

}



.hero-facts {

    display: grid;

    grid-template-columns: repeat(4, minmax(0, 1fr));

    gap: 1px;

    margin-top: 2.6rem;

    max-width: 42rem;

    background: var(--line);

    border: 1px solid var(--line);

    border-radius: var(--radius);

    overflow: hidden;

    backdrop-filter: blur(2px);

}



.fact {

    background: color-mix(in srgb, var(--ground) 88%, transparent);

}



.fact {

    background: var(--ground);

    padding: .9rem 1rem;

}



.fact b {

    display: block;

    font-family: var(--display);

    font-size: 1.45rem;

    font-weight: 400;

    letter-spacing: -.02em;

    line-height: 1.1;

}



.fact span {

    display: block;

    margin-top: .18rem;

    font-size: .7rem;

    color: var(--faint);

    line-height: 1.35;

}



/* --- the scene: cloths hanging behind everything in the hero --- */

#scene {

    position: absolute;

    inset: 0;

    width: 100%;

    height: 100%;

    display: block;

    z-index: 0;

    /* the left third stays clean so the type never fights the cloth */

    opacity: .62;

    mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, .2) 24%, #000 52%);

    -webkit-mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, .2) 24%, #000 52%);

}



/* a soft pool of paper light under the copy */

.hero-scrim {

    position: absolute;

    inset: 0;

    z-index: 1;

    pointer-events: none;

    background:

        radial-gradient(72% 90% at 6% 46%, var(--ground) 34%, rgba(250, 248, 244, .82) 58%, transparent 78%),

        linear-gradient(180deg, var(--ground) 0%, transparent 16%, transparent 82%, var(--ground) 100%);

}



/* the loom controls — obviously a control, sitting under the cloth */

.loom {

    position: absolute;

    right: var(--gutter);

    bottom: 2.2rem;

    z-index: 3;

    display: flex;

    align-items: center;

    gap: .7rem;

    padding: .5rem .8rem .5rem .95rem;

    border: 1px solid var(--line-2);

    border-radius: 99px;

    background: color-mix(in srgb, var(--surface) 86%, transparent);

    backdrop-filter: blur(10px);

    -webkit-backdrop-filter: blur(10px);

    box-shadow: var(--shadow-1);

}



.loom-label {

    font-family: var(--mono);

    font-size: .66rem;

    letter-spacing: .09em;

    text-transform: uppercase;

    color: var(--faint);

    white-space: nowrap;

}



.loom-swatches {

    display: flex;

    gap: .3rem;

}



.loom-now {

    font-family: var(--mono);

    font-size: .68rem;

    color: var(--accent);

    min-width: 6.4em;

    white-space: nowrap;

}



.swatch {

    width: 28px;

    height: 28px;

    padding: 0;

    border: 1px solid var(--line-2);

    border-radius: 7px;

    background-color: var(--surface);

    background-repeat: repeat;

    cursor: pointer;

    opacity: .55;

    transition: opacity var(--t-hover) ease,

        border-color var(--t-hover) ease,

        transform var(--t-press) var(--ease-out);

}



.swatch:hover {

    opacity: 1;

    transform: translateY(-2px);

}



.swatch:active {

    transform: scale(.92);

}



.swatch.is-on {

    opacity: 1;

    border-color: var(--accent);

    box-shadow: 0 0 0 2px var(--accent-wash);

}



/* no WebGL: a real length of batik, painted flat */

#hero.is-flat::after {

    background-image: var(--flat-motif, var(--kawung));

    background-size: auto;

    opacity: .5;

    right: 4%;

    bottom: auto;

    top: 24%;

    width: min(34vw, 360px);

    aspect-ratio: 3 / 4;

    border-radius: 4px;

    mask-image: linear-gradient(180deg, #000 82%, transparent);

    -webkit-mask-image: linear-gradient(180deg, #000 82%, transparent);

}



/* buttons - every one answers a press */

.btn {

    --btn-bg: var(--surface);

    --btn-fg: var(--ink);

    --btn-bd: var(--line-2);

    display: inline-flex;

    align-items: center;

    gap: .55rem;

    padding: .72rem 1.15rem;

    border: 1px solid var(--btn-bd);

    border-radius: 99px;

    background: var(--btn-bg);

    color: var(--btn-fg);

    font-size: .92rem;

    font-weight: 500;

    cursor: pointer;

    white-space: nowrap;

    transition: transform var(--t-press) var(--ease-out),

        box-shadow var(--t-hover) var(--ease-out),

        border-color var(--t-hover) ease,

        background-color var(--t-hover) ease;

}



.btn:hover {

    box-shadow: var(--shadow-1);

    border-color: var(--line-3);

}



.btn:active {

    transform: scale(.975);

}



.btn .arw {

    transition: transform var(--t-hover) var(--ease-out);

}



.btn:hover .arw {

    transform: translateX(2px);

}



.btn-solid {

    --btn-bg: var(--ink);

    --btn-fg: var(--ground);

    --btn-bd: var(--ink);

}



.btn-solid:hover {

    --btn-bg: #241F16;

    box-shadow: var(--shadow-2);

}



.btn-accent {

    --btn-bg: var(--accent);

    --btn-fg: #fff;

    --btn-bd: var(--accent);

}



.btn-accent:hover {

    --btn-bg: var(--accent-deep);

}



.btn-quiet {

    --btn-bg: transparent;

    --btn-bd: transparent;

    color: var(--muted);

}



.btn-quiet:hover {

    color: var(--ink);

    background: var(--ground-2);

    box-shadow: none;

}



/* ---------- 06 . ABOUT + PORTRAIT ---------- */

.about-grid {

    display: grid;

    grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);

    gap: clamp(2rem, 6vw, 5rem);

    align-items: start;

}



.portrait {

    position: relative;

}



.portrait-tilt {

    transform-style: preserve-3d;

    will-change: transform;

}



.portrait-frame {

    position: relative;

    border-radius: var(--radius-lg);

    overflow: hidden;

    background: var(--ground-2);

    border: 1px solid var(--line-2);

    aspect-ratio: 4 / 5;

    box-shadow: var(--shadow-2);

}



.portrait-frame img {

    width: 100%;

    height: 100%;

    object-fit: cover;

    object-position: center 18%;

    transform: scale(1.03);

    transition: transform 700ms var(--ease-out);

}



.portrait:hover .portrait-frame img {

    transform: scale(1);

}



.portrait-frame::after {

    content: '';

    position: absolute;

    inset: 0;

    background: linear-gradient(180deg, transparent 52%, rgba(23, 21, 15, .55) 100%);

    pointer-events: none;

}



.portrait-cap {

    position: absolute;

    left: 0;

    right: 0;

    bottom: 0;

    z-index: 2;

    padding: 1rem 1.1rem;

    color: #fff;

    display: flex;

    flex-direction: column;

    gap: .1rem;

}



.portrait-cap span:first-child {

    font-family: var(--display);

    font-size: 1.05rem;

    letter-spacing: -.015em;

}



.portrait-cap span:last-child {

    font-family: var(--mono);

    font-size: .66rem;

    letter-spacing: .08em;

    text-transform: uppercase;

    opacity: .82;

}



.portrait-meta {

    margin-top: 1.1rem;

    display: grid;

    gap: .5rem;

}



.portrait-meta div {

    display: flex;

    justify-content: space-between;

    gap: 1rem;

    padding-bottom: .5rem;

    border-bottom: 1px solid var(--line);

    font-size: .82rem;

}



.portrait-meta dt {

    color: var(--faint);

    font-family: var(--mono);

    font-size: .66rem;

    letter-spacing: .07em;

    text-transform: uppercase;

    padding-top: .2rem;

}



.portrait-meta dd {

    text-align: right;

    color: var(--ink-2);

}



.principles {

    list-style: none;

    display: grid;

    grid-template-columns: repeat(2, minmax(0, 1fr));

    gap: 1px;

    margin-top: clamp(2.5rem, 5vw, 4rem);

    background: var(--line);

    border-block: 1px solid var(--line);

}



.principle {

    background: var(--ground);

    padding: 1.7rem 1.6rem 1.8rem 0;

    transition: background-color var(--t-hover) ease;

}



.principle:nth-child(even) {

    padding-left: 1.6rem;

}



.principle:hover {

    background: var(--ground-2);

}



.principle b {

    display: flex;

    align-items: baseline;

    gap: .7rem;

    font-family: var(--display);

    font-size: 1.18rem;

    font-weight: 400;

    letter-spacing: -.015em;

    margin-bottom: .6rem;

}



.principle b i {

    font-family: var(--mono);

    font-style: normal;

    font-size: .68rem;

    color: var(--accent);

    letter-spacing: .05em;

}



.principle p {

    font-size: .93rem;

    line-height: 1.65;

    color: var(--muted);

}



/* ---------- 07 · CAPABILITIES ---------- */

.caps {

    display: grid;

    gap: 0;

}



.cap {

    display: grid;

    grid-template-columns: minmax(0, 14rem) minmax(0, 1fr);

    gap: clamp(1rem, 4vw, 3rem);

    padding-block: 1.6rem;

    border-bottom: 1px solid var(--line);

    align-items: start;

}



.cap h3 {

    font-family: var(--display);

    font-size: 1.12rem;

    letter-spacing: -.015em;

    padding-top: .18rem;

}



.cap ul {

    list-style: none;

    display: flex;

    flex-wrap: wrap;

    gap: .4rem;

}



.cap li {

    padding: .34rem .72rem;

    border: 1px solid var(--line);

    border-radius: 99px;

    background: var(--surface);

    font-size: .84rem;

    color: var(--ink-2);

    transition: border-color var(--t-hover) ease, color var(--t-hover) ease,

        transform var(--t-hover) var(--ease-out);

}



/* the chips no longer take a click, so they no longer offer one */

.cap li:hover {

    border-color: var(--line);

    color: var(--ink-2);

    transform: none;

}



/* a chip the console can point at */

.cap li.is-lit {

    border-color: var(--accent);

    background: var(--accent-wash);

    color: var(--accent-deep);

}



/* ---------- 08 · TRAJECTORY ---------- */

.traj {

    list-style: none;

    position: relative;

    padding-left: clamp(1.4rem, 4vw, 2.6rem);

}



.traj::before {

    content: '';

    position: absolute;

    left: 3px;

    top: .7rem;

    bottom: 2.6rem;

    width: 1px;

    background: var(--line-2);

}



.traj-item {

    position: relative;

    padding-bottom: 2.6rem;

}



.traj-item::before {

    content: '';

    position: absolute;

    left: calc(-1 * clamp(1.4rem, 4vw, 2.6rem));

    top: .62rem;

    width: 7px;

    height: 7px;

    border-radius: 50%;

    background: var(--ground);

    border: 1.5px solid var(--line-3);

    transition: border-color var(--t-hover) ease, background-color var(--t-hover) ease;

}



.traj-item:first-child::before {

    background: var(--accent);

    border-color: var(--accent);

}



.traj-item:hover::before {

    border-color: var(--accent);

}



.traj-head {

    display: flex;

    flex-wrap: wrap;

    align-items: baseline;

    gap: .3rem .9rem;

    margin-bottom: .5rem;

}



.traj-role {

    font-family: var(--display);

    font-size: 1.22rem;

    font-weight: 400;

    letter-spacing: -.018em;

}



.traj-org {

    color: var(--accent);

    font-size: .95rem;

}



.traj-period {

    font-family: var(--mono);

    font-size: .7rem;

    letter-spacing: .07em;

    text-transform: uppercase;

    color: var(--faint);

    margin-left: auto;

    white-space: nowrap;

}



.traj-note {

    font-size: .74rem;

    color: var(--muted);

    padding: .1rem .5rem;

    border: 1px solid var(--line);

    border-radius: 99px;

}



.traj-item p {

    font-size: .94rem;

    line-height: 1.68;

    color: var(--muted);

    max-width: 62ch;

}



.traj-stack {

    margin-top: .6rem;

    font-family: var(--mono);

    font-size: .72rem;

    color: var(--faint);

    letter-spacing: .01em;

}



/* ---------- 09 · CONTACT + COLOPHON ---------- */

.contact-grid {

    display: grid;

    grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);

    gap: clamp(2rem, 6vw, 5rem);

    align-items: start;

}



.field {

    display: block;

    margin-bottom: 1.1rem;

}



.field > span {

    display: block;

    font-family: var(--mono);

    font-size: .68rem;

    letter-spacing: .08em;

    text-transform: uppercase;

    color: var(--faint);

    margin-bottom: .45rem;

}



.field input,

.field textarea {

    width: 100%;

    padding: .8rem .95rem;

    border: 1px solid var(--line-2);

    border-radius: var(--radius);

    background: var(--surface);

    font-size: .96rem;

    resize: vertical;

    transition: border-color var(--t-hover) ease, box-shadow var(--t-hover) ease;

}



.field input:focus,

.field textarea:focus {

    outline: none;

    border-color: var(--accent);

    box-shadow: 0 0 0 3px var(--accent-wash);

}



.field input::placeholder,

.field textarea::placeholder {

    color: var(--faint);

}



.form-note {

    margin-top: .9rem;

    font-size: .82rem;

    color: var(--faint);

    min-height: 1.2em;

}



.form-note[data-state='ok'] {

    color: #2F7A4C;

}



.form-note[data-state='err'] {

    color: var(--accent-deep);

}



.channels {

    list-style: none;

    border-top: 1px solid var(--line);

}



.channels a {

    display: grid;

    grid-template-columns: 1fr auto;

    gap: .3rem 1rem;

    padding: .95rem .3rem;

    border-bottom: 1px solid var(--line);

    transition: padding-left var(--t-hover) var(--ease-out), background-color var(--t-hover) ease;

}



.channels a:hover {

    padding-left: .8rem;

    background: var(--ground-2);

}



.ch-k {

    font-family: var(--mono);

    font-size: .66rem;

    letter-spacing: .09em;

    text-transform: uppercase;

    color: var(--faint);

    grid-column: 1;

}



.ch-v {

    grid-column: 1;

    font-size: .93rem;

    color: var(--ink-2);

    word-break: break-word;

}



.channels .arw {

    grid-column: 2;

    grid-row: 1 / 3;

    align-self: center;

    color: var(--faint);

    transition: transform var(--t-hover) var(--ease-out), color var(--t-hover) ease;

}



.channels a:hover .arw {

    transform: translateX(3px);

    color: var(--accent);

}



#colophon {

    border-top: 1px solid var(--line);

    padding-block: 2.6rem 3rem;

    color: var(--faint);

    font-size: .82rem;

}



.colophon-row {

    display: flex;

    flex-wrap: wrap;

    gap: .8rem 2rem;

    align-items: center;

}



.colophon-row nav {

    margin-left: auto;

    display: flex;

    gap: 1.2rem;

}



.colophon-row a {

    transition: color var(--t-hover) ease;

}



.colophon-row a:hover {

    color: var(--ink);

}



/* ---------- 10 · CONSOLE (⌘K) ---------- */

#scrim {

    position: fixed;

    inset: 0;

    z-index: 90;

    background: rgba(23, 21, 15, .32);

    backdrop-filter: blur(3px);

    -webkit-backdrop-filter: blur(3px);

    opacity: 0;

    pointer-events: none;

}



#scrim.is-on {

    pointer-events: auto;

}



#console {
    position: fixed;
    z-index: 100;
    left: 0;
    right: 0;
    top: max(9vh, 3.5rem);
    width: min(640px, calc(100vw - 2rem));
    margin-inline: auto;
    background: var(--surface);
    color: var(--ink);
    border: 1px solid var(--line-2);
    border-radius: 14px;
    box-shadow: var(--shadow-3);
    overflow: hidden;
    opacity: 0;
    pointer-events: none;

    /* scales from the top — it comes from the trigger, not from nothing */

    transform-origin: 50% 0%;

}



#console.is-on {

    pointer-events: auto;

}



.console-bar {

    display: flex;

    align-items: center;

    gap: .75rem;

    padding: .95rem 1rem;

    border-bottom: 1px solid var(--line);

}



.console-bar .canting {

    flex: none;

    width: 18px;

    height: 18px;

    color: var(--accent);

}



#console-input {

    flex: 1;

    border: 0;

    outline: 0;

    background: transparent;

    font-size: 1rem;

    letter-spacing: -.005em;

}



#console-input::placeholder {

    color: var(--faint);

}



.console-esc {

    font-family: var(--mono);

    font-size: .66rem;

    color: var(--faint);

    padding: .22rem .45rem;

    border: 1px solid var(--line);

    border-radius: 5px;

    background: var(--ground);

}



#console-body {

    max-height: min(58vh, 460px);

    overflow-y: auto;

    overscroll-behavior: contain;

    padding: .5rem;

}



.console-group {

    padding: .55rem .7rem .3rem;

    font-family: var(--mono);

    font-size: .64rem;

    letter-spacing: .1em;

    text-transform: uppercase;

    color: var(--faint);

}



.console-item {

    display: flex;

    align-items: center;

    gap: .8rem;

    width: 100%;

    padding: .62rem .7rem;

    border: 0;

    border-radius: 8px;

    background: transparent;

    text-align: left;

    cursor: pointer;

    scroll-margin: .5rem;

}



.console-item[aria-selected='true'] {

    background: var(--ground-2);

}



.console-item .ic {

    flex: none;

    width: 26px;

    height: 26px;

    display: grid;

    place-items: center;

    border-radius: 7px;

    background: var(--ground-2);

    border: 1px solid var(--line);

    font-family: var(--mono);

    font-size: .62rem;

    color: var(--muted);

}



.console-item[aria-selected='true'] .ic {

    background: var(--accent-wash);

    border-color: color-mix(in srgb, var(--accent) 35%, transparent);

    color: var(--accent-deep);

}



.console-item .lab {

    flex: 1;

    min-width: 0;

    font-size: .93rem;

}



.console-item .lab i {

    display: block;

    font-style: normal;

    font-size: .78rem;

    color: var(--faint);

    margin-top: .1rem;

    overflow: hidden;

    text-overflow: ellipsis;

    white-space: nowrap;

}



.console-item .go {

    flex: none;

    font-family: var(--mono);

    font-size: .62rem;

    color: var(--faint);

    opacity: 0;

    transition: opacity var(--t-hover) ease;

}



.console-item[aria-selected='true'] .go {

    opacity: 1;

}



/* the answer panel — the console talking back */

.answer {

    padding: .4rem .75rem 1rem;

}



.answer-q {

    font-family: var(--display);

    font-size: 1.1rem;

    letter-spacing: -.018em;

    margin-bottom: .55rem;

}



.answer-a {

    font-size: .92rem;

    line-height: 1.65;

    color: var(--muted);

    max-width: none;

}



.answer-a .cursor {

    display: inline-block;

    width: 2px;

    height: 1em;

    background: var(--accent);

    vertical-align: -.14em;

    margin-left: 2px;

    animation: blink 1s steps(1) infinite;

}



@keyframes blink {

    50% {

        opacity: 0;

    }

}



.answer-src {

    margin-top: .9rem;

    display: flex;

    flex-wrap: wrap;

    gap: .45rem;

}



.src-chip {

    display: inline-flex;

    align-items: center;

    gap: .4rem;

    padding: .34rem .7rem;

    border: 1px solid var(--line-2);

    border-radius: 99px;

    background: var(--ground);

    font-size: .79rem;

    cursor: pointer;

    transition: border-color var(--t-hover) ease, color var(--t-hover) ease,

        transform var(--t-press) var(--ease-out);

}



.src-chip:hover {

    border-color: var(--accent);

    color: var(--accent);

}



.src-chip:active {

    transform: scale(.97);

}



.console-empty {

    padding: 1.6rem .8rem;

    text-align: center;

    color: var(--faint);

    font-size: .88rem;

}



.console-foot {

    display: flex;

    gap: 1.1rem;

    padding: .55rem .9rem;

    border-top: 1px solid var(--line);

    background: var(--ground);

    font-size: .72rem;

    color: var(--faint);

}



.console-foot span {

    display: inline-flex;

    align-items: center;

    gap: .35rem;

}



.console-foot kbd {

    font-family: var(--mono);

    font-size: .64rem;

    padding: .1rem .32rem;

    border: 1px solid var(--line-2);

    border-radius: 4px;

    background: var(--surface);

}



/* ---------- 11 · DRAWER (case study) ---------- */

#drawer {

    position: fixed;

    z-index: 100;

    top: 0;

    right: 0;

    height: 100dvh;

    width: min(720px, 100vw);

    background: var(--ground);

    color: var(--ink);

    border-left: 1px solid var(--line-2);

    box-shadow: var(--shadow-3);

    display: flex;

    flex-direction: column;

    transform: translateX(100%);

    pointer-events: none;

}



#drawer.is-on {

    pointer-events: auto;

}



.drawer-top {

    display: flex;

    align-items: center;

    gap: 1rem;

    padding: 1rem clamp(1.25rem, 3vw, 2.2rem);

    border-bottom: 1px solid var(--line);

    background: color-mix(in srgb, var(--ground) 88%, transparent);

    backdrop-filter: blur(10px);

    -webkit-backdrop-filter: blur(10px);

}



.drawer-top .eyebrow {

    margin-right: auto;

}



.drawer-x {

    width: 34px;

    height: 34px;

    display: grid;

    place-items: center;

    border: 1px solid var(--line-2);

    border-radius: 8px;

    background: var(--surface);

    cursor: pointer;

    transition: transform var(--t-press) var(--ease-out), border-color var(--t-hover) ease;

}



.drawer-x:hover {

    border-color: var(--line-3);

}



.drawer-x:active {

    transform: scale(.94);

}



#drawer-body {

    flex: 1;

    overflow-y: auto;

    overscroll-behavior: contain;

    padding: clamp(1.8rem, 4vw, 3rem) clamp(1.25rem, 3vw, 2.2rem) 4rem;

}



.cs-year {

    font-family: var(--mono);

    font-size: .72rem;

    letter-spacing: .09em;

    color: var(--muted);

}



.cs-name {

    font-family: var(--display);

    font-size: clamp(2rem, 5vw, 3rem);

    letter-spacing: -.028em;

    line-height: 1;

    margin: .5rem 0 .5rem;

}



.cs-kind {

    color: var(--accent);

    font-size: .95rem;

    margin-bottom: 1.6rem;

}



.cs-context {

    font-family: var(--mono);

    font-size: .7rem;

    letter-spacing: .1em;

    text-transform: uppercase;

    color: var(--muted);

    margin: -1.15rem 0 1.35rem;

}



/* the plain-language line: what this is, for a reader who does not
   share the vocabulary. It sits above the narrative summary. */

.cs-plain {

    font-size: 1.02rem;

    line-height: 1.62;

    color: var(--ink);

    max-width: 54ch;

    padding-left: 1rem;

    border-left: 2px solid var(--accent);

    margin-bottom: 1.4rem;

}



.cs-summary {

    font-size: 1.08rem;

    line-height: 1.6;

    color: var(--ink-2);

    letter-spacing: -.008em;

    max-width: 54ch;

}



.cs-figs {

    display: grid;

    grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));

    gap: 1px;

    background: var(--line);

    border: 1px solid var(--line);

    border-radius: var(--radius);

    overflow: hidden;

    margin: 2rem 0;

}



.cs-fig {

    background: var(--ground);

    padding: .9rem 1rem;

}



.cs-fig b {

    display: block;

    font-family: var(--display);

    font-size: 1.35rem;

    font-weight: 400;

    letter-spacing: -.02em;

}



.cs-fig span {

    display: block;

    margin-top: .1rem;

    font-size: .7rem;

    color: var(--faint);

}



.cs-shot {

    border: 1px solid var(--line-2);

    border-radius: var(--radius);

    overflow: hidden;

    background: var(--surface-sunk);

    margin-bottom: .8rem;

}



.cs-shot img {

    width: 100%;

}



.cs-block {

    margin-top: 2.2rem;

}



.cs-block > h4 {

    font-family: var(--mono);

    font-size: .72rem;

    font-weight: 600;

    letter-spacing: .1em;

    text-transform: uppercase;

    color: var(--ink-2);

    padding-bottom: .7rem;

    margin-bottom: 1.1rem;

    border-bottom: 1px solid var(--line);

}



.cs-block p {

    color: var(--muted);

    font-size: .96rem;

    line-height: 1.68;

}



.cs-build {

    list-style: none;

    display: grid;

    gap: 1.3rem;

}



.cs-build li {

    display: grid;

    grid-template-columns: 1.4rem minmax(0, 1fr);

    gap: .9rem;

}



.cs-build .n {

    font-family: var(--mono);

    font-size: .68rem;

    color: var(--accent);

    padding-top: .28rem;

}



.cs-build b {

    display: block;

    font-weight: 500;

    font-size: .98rem;

    margin-bottom: .28rem;

}



.cs-build p {

    font-size: .92rem;

    line-height: 1.65;

}



.cs-stack {

    display: flex;

    flex-wrap: wrap;

    gap: .4rem;

}



.cs-stack span {

    padding: .3rem .7rem;

    border: 1px solid var(--line);

    border-radius: 99px;

    background: var(--surface);

    font-size: .8rem;

    color: var(--ink-2);

}



.cs-flow {

    display: flex;

    flex-wrap: wrap;

    align-items: center;

    gap: .55rem;

    padding: 1.4rem;

    border: 1px solid var(--line);

    border-radius: var(--radius);

    background: var(--surface);

}



.cs-flow b {

    font-family: var(--mono);

    font-size: .74rem;

    font-weight: 500;

    padding: .4rem .7rem;

    border: 1px solid var(--line-2);

    border-radius: 6px;

    background: var(--ground);

}



.cs-flow i {

    color: var(--accent);

    font-style: normal;

}



.cs-actions {

    display: flex;

    flex-wrap: wrap;

    gap: .6rem;

    margin-top: 2.4rem;

    padding-top: 1.8rem;

    border-top: 1px solid var(--line);

}



/* ---------- 12 · MOTION UTILITIES ---------- */

[data-reveal] {

    opacity: 0;

    transform: translateY(14px);

}



.no-motion [data-reveal],

[data-reveal].is-in {

    opacity: 1;

    transform: none;

}



/* ---------- 12a . SCROLL PROGRESS ---------- */

#progress {

    position: fixed;

    inset: 0 0 auto 0;

    height: 2px;

    z-index: 70;

    pointer-events: none;

}



#progress i {

    display: block;

    height: 100%;

    width: 0%;

    background: linear-gradient(90deg, var(--accent), var(--accent-deep));

    transform-origin: 0 50%;

}



/* ---------- 12b . BATIK STRIPS ----------

   Section dividers drawn by batik.js. Thin, low-contrast, and the

   motif changes down the page so the rhythm is felt, not shouted. */

.strip {

    height: 26px;

    background-repeat: repeat;

    opacity: .62;

    mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);

    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);

}



/* the strip replaces the section rule wherever it appears */

.strip + .section {

    border-top: 0;

}



/* ---------- 12c . STAGE SWATCHES ---------- */

.stage-swatches {

    position: absolute;

    left: 0;

    right: 0;

    bottom: -2.6rem;

    display: flex;

    justify-content: center;

    gap: .45rem;

    z-index: 3;

}



.swatch {

    width: 34px;

    height: 34px;

    padding: 0;

    border: 1px solid var(--line-2);

    border-radius: 8px;

    background-color: var(--surface);

    background-repeat: repeat;

    cursor: pointer;

    opacity: .62;

    transition: opacity var(--t-hover) ease,

        border-color var(--t-hover) ease,

        transform var(--t-press) var(--ease-out),

        box-shadow var(--t-hover) ease;

}



.swatch:hover {

    opacity: 1;

    transform: translateY(-2px);

    box-shadow: var(--shadow-1);

}



.swatch:active {

    transform: scale(.94);

}



.swatch.is-on {

    opacity: 1;

    border-color: var(--accent);

    box-shadow: 0 0 0 2px var(--accent-wash);

}



.hero-stage.is-static {

    background-repeat: repeat;

    border: 1px solid var(--line-2);

    opacity: .9;

}



/* ---------- 12d . SKILLS + ORBIT ---------- */

.skills-grid {

    display: grid;

    grid-template-columns: minmax(0, 28rem) minmax(0, 1fr);

    gap: clamp(1.6rem, 3.5vw, 3rem);

    align-items: start;

}



.skills-grid .caps {

    order: -1;

}



.orbit {

    position: sticky;

    top: 6rem;

    aspect-ratio: 5 / 4;

    min-height: 400px;

    max-height: 62vh;

    max-width: 34rem;

    margin-inline: auto;

    cursor: grab;

    touch-action: pan-y;

}



.orbit::before {

    content: '';

    position: absolute;

    inset: 6%;

    z-index: -1;

    border-radius: 50%;

    background: radial-gradient(62% 58% at 50% 46%, color-mix(in srgb, var(--accent-lit) 15%, transparent), transparent 72%);

    pointer-events: none;

}



.orbit.is-dragging {

    cursor: grabbing;

}



#orbit-gl {

    position: absolute;

    inset: 0;

    width: 100%;

    height: 100%;

    display: block;

    touch-action: none;

}



/* the 3D labels are DOM text projected from the scene — without this

   they collapse into a stack of plain words above the caption */

.gl-label {

    position: absolute;

    top: 0;

    left: 0;

    font-family: var(--mono);

    font-size: .62rem;

    letter-spacing: .02em;

    color: var(--muted);

    white-space: nowrap;

    pointer-events: none;

    will-change: transform, opacity;

}



.gl-label.is-primary {

    color: var(--accent-deep);

    font-weight: 500;

    font-size: .68rem;

}



#orbit-labels {

    position: absolute;

    inset: 0;

    pointer-events: none;

}



.stage-hint {

    font-family: var(--mono);

    font-size: .66rem;

    letter-spacing: .04em;

    color: var(--line-3);

}



.orbit-cap {

    position: absolute;

    left: 0;

    bottom: 0;

    display: flex;

    align-items: baseline;

    gap: .7rem;

    max-width: none;

}



.orbit.is-static {

    display: none;

}



/* ---------- 12f . BOLT, THE COMPANION ---------- */

#bolt {

    position: fixed;

    left: 0;

    bottom: clamp(.4rem, 1.6vw, 1rem);

    z-index: 55;

    width: 88px;

    height: 88px;

    padding: 0;

    border: 0;

    background: none;

    cursor: pointer;

    filter: drop-shadow(0 10px 16px rgba(23, 21, 15, .18));

    will-change: transform;

}



#bolt.is-away {

    opacity: 0;

    pointer-events: none;

    transition: opacity var(--t-pop) ease;

}



#bolt:active #bolt-canvas {

    transform: scale(.94);

}



#bolt-canvas {

    transition: transform var(--t-press) var(--ease-out);

}



#bolt-canvas {

    width: 88px;

    height: 88px;

    display: block;

}



.bolt-say {

    position: absolute;

    left: 50%;

    bottom: 74px;

    transform: translateX(-50%) translateY(4px) scale(.96);

    transform-origin: 50% 100%;

    white-space: nowrap;

    padding: .34rem .7rem;

    border-radius: 99px;

    background: var(--ink);

    color: var(--ground);

    font-family: var(--mono);

    font-size: .64rem;

    letter-spacing: .05em;

    text-transform: uppercase;

    opacity: 0;

    transition: opacity var(--t-pop) var(--ease-out), transform var(--t-pop) var(--ease-out);

    pointer-events: none;

    max-width: 42vw;

    overflow: hidden;

    text-overflow: ellipsis;

}



#bolt.is-saying .bolt-say {

    opacity: 1;

    transform: translateX(-50%) translateY(0) scale(1);

}



/* ---------- 13 · RESPONSIVE ---------- */

@media (max-width: 1080px) {





}



@media (max-width: 940px) {

    :root {

        --section-y: clamp(4rem, 9vw, 6rem);

    }

    .mark {
        margin-right: auto;
    }

    .hero-copy {
        text-align: center;
    }
    
    .hero-actions {
        justify-content: center;
    }

    .nav {

        position: fixed;

        inset: 57px 0 auto 0;

        flex-direction: column;

        gap: 0;

        margin: 0;

        padding: .5rem var(--gutter) 1.1rem;

        background: var(--ground);

        border-bottom: 1px solid var(--line);

        box-shadow: var(--shadow-2);

        transform: translateY(-8px);

        opacity: 0;

        pointer-events: none;

        transition: opacity var(--t-pop) var(--ease-out), transform var(--t-pop) var(--ease-out);

    }



    .nav.is-open {

        opacity: 1;

        transform: none;

        pointer-events: auto;

    }



    .nav a {

        padding: .8rem .2rem;

        border-bottom: 1px solid var(--line);

        border-radius: 0;

        font-size: 1rem;

    }



    .nav a:hover {

        background: transparent;

    }



    .nav a[aria-current='true']::after {

        left: .2rem;

        right: auto;

        width: 14px;

        bottom: .55rem;

    }



    #burger {

        display: block;

    }



    .ask .ask-label {

        display: none;

    }



    .ask {

        padding: .42rem .55rem;

    }



    .about-grid,

    .contact-grid,

    .skills-grid {

        grid-template-columns: minmax(0, 1fr);

    }



    .orbit {

        position: relative;

        top: auto;

        max-width: 24rem;

        margin-inline: auto;

        min-height: 260px;

    }



    .chain-stage {

        aspect-ratio: 4 / 3.4;

        min-height: 300px;

    }



    .hero-grid {

        grid-template-columns: minmax(0, 1fr);

        gap: 2.5rem;

    }

    .hero-portrait {
        justify-self: center;
    }

    #hero {
        flex-direction: column;
        justify-content: center;
    }

    .loom {
        position: relative;
        right: auto;
        left: auto;
        bottom: auto;
        transform: none;
        margin-top: 3rem;
        padding: .4rem .6rem .4rem .7rem;
        gap: .5rem;
    }



    .loom-label,

    .loom-now {

        display: none;

    }



    #scene {

        mask-image: linear-gradient(180deg, rgba(0, 0, 0, .3) 0%, #000 40%);

        -webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, .3) 0%, #000 40%);

        opacity: .55;

    }



    .portrait {

        max-width: 22rem;

    }



    .cap {

        grid-template-columns: minmax(0, 1fr);

        gap: .9rem;

    }



    .sec-head {

        grid-template-columns: minmax(0, 1fr);

        gap: .5rem;

    }



    .sec-head .eyebrow {

        padding-bottom: 0;

    }



    .sec-note {

        grid-column: 1;

    }




}



@media (max-width: 700px) {

    body {

        font-size: 16px;

    }



    .principles {

        grid-template-columns: minmax(0, 1fr);

    }



    .principle,

    .principle:nth-child(even) {

        padding: 1.4rem 0;

    }









    .traj-period {

        margin-left: 0;

        width: 100%;

        order: -1;

    }



    .hero-facts {

        grid-template-columns: 1fr 1fr;

    }



    .chain-hud {

        grid-template-columns: repeat(2, minmax(0, 1fr));

    }



    .hud b {

        font-size: 1.1rem;

    }



    .colophon-row nav {

        margin-left: 0;

        width: 100%;

    }



    #console {

        top: 1rem;

        width: calc(100vw - 1.5rem);

    }

}



/* ---------- REDUCED MOTION ---------- */

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



    html {

        scroll-behavior: auto;

    }



    *,

    *::before,

    *::after {

        animation-duration: .01ms !important;

        animation-iteration-count: 1 !important;

        transition-duration: .01ms !important;

    }



    [data-reveal] {

        opacity: 1 !important;

        transform: none !important;

    }



    .orbit {

        display: none;

    }



    .bolt-say {

        transition: none;

    }

}



/* ---------- PRINT ---------- */

@media print {



    #top,

    #console,

    #scrim,

    #drawer,


    .hero-actions,

    .loom,

    #bolt,

    #progress,

    #burger {

        display: none !important;

    }



    body {

        background: #fff;

        font-size: 11pt;

    }



    [data-reveal] {

        opacity: 1 !important;

        transform: none !important;

    }

}



/* ============================================================

   14 · SPATIAL UI + LIQUID GLASS

   The page is built in depth now. Three planes: a lit room in

   WebGL at the back, a haze in the middle, and glass panels

   floating in front of both. Everything in this section either

   makes that depth readable or keeps text legible on top of it.

   Loaded last so it wins over the flat editorial layer above.

   ============================================================ */



:root {

    /* the deep — warm charcoal, not blue-black, so batik still belongs */

    --deep: #100E0C;

    --deep-2: #1A1611;

    --deep-3: #251F18;



    /* type on the deep */

    --on-deep: #F7F2E9;

    --on-deep-2: #C6BCAC;

    --on-deep-3: #8A8073;



    /* the accent has to survive a dark ground */

    --accent-lit: #EE7A4C;

    --accent-glow: rgba(238, 122, 76, .55);

    --indigo: #2E3F6B;

    --indigo-lit: #6E8ACB;



    /* liquid glass */

    --glass: rgba(255, 255, 255, .055);

    --glass-2: rgba(255, 255, 255, .10);

    --glass-line: rgba(255, 255, 255, .14);

    --glass-line-2: rgba(255, 255, 255, .30);

    --glass-blur: 20px;



    /* the specular read: a lit top edge, a dim bottom edge, real cast shadow */

    --glass-lift:

        inset 0 1px 0 rgba(255, 255, 255, .26),

        inset 0 -1px 0 rgba(255, 255, 255, .05),

        0 1px 2px rgba(0, 0, 0, .34),

        0 18px 44px rgba(0, 0, 0, .40);

    --glass-lift-2:

        inset 0 1px 0 rgba(255, 255, 255, .40),

        inset 0 -1px 0 rgba(255, 255, 255, .07),

        0 2px 6px rgba(0, 0, 0, .38),

        0 34px 80px rgba(0, 0, 0, .52);



    /* glass over the light half of the page */

    --glass-day: rgba(255, 255, 255, .58);

    --glass-day-line: rgba(255, 255, 255, .90);

    --glass-day-lift:

        inset 0 1px 0 rgba(255, 255, 255, .9),

        0 1px 2px rgba(23, 21, 15, .05),

        0 18px 44px rgba(23, 21, 15, .12);



    --t-spatial: 620ms;

    --ease-spring: cubic-bezier(.16, 1.06, .28, 1);

}



/* ---------- 14a · the glass primitive ---------- */

.glass {

    position: relative;

    background: linear-gradient(155deg, var(--glass-2), var(--glass) 46%, rgba(255, 255, 255, .03));

    backdrop-filter: blur(var(--glass-blur)) saturate(165%);

    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(165%);

    border: 1px solid var(--glass-line);

    box-shadow: var(--glass-lift);

    isolation: isolate;

}



/* the travelling highlight — this is what makes it read as liquid

   rather than as a grey box with blur behind it */

.glass::before {

    content: '';

    position: absolute;

    inset: 0;

    border-radius: inherit;

    pointer-events: none;

    background: linear-gradient(112deg,

            transparent 28%,

            rgba(255, 255, 255, .13) 44%,

            rgba(255, 255, 255, .03) 52%,

            transparent 66%);

    background-size: 240% 100%;

    background-position: var(--sheen, 120%) 0;

    transition: background-position 900ms var(--ease-out);

    mix-blend-mode: screen;

}



.glass:hover {

    --sheen: -40%;

}



/* ---------- 14b · the deep scope ----------

   Anything inside .on-deep re-points the ink tokens at the light

   set, so the editorial rules above keep working unchanged. */

.on-deep {

    --ink: var(--on-deep);

    --ink-2: var(--on-deep-2);

    --muted: var(--on-deep-2);

    --faint: var(--on-deep-3);

    --accent: var(--accent-lit);

    --surface: rgba(255, 255, 255, .06);

    --surface-sunk: rgba(0, 0, 0, .24);

    --ground: var(--deep);

    --ground-2: rgba(255, 255, 255, .08);

    --line: rgba(255, 255, 255, .10);

    --line-2: rgba(255, 255, 255, .16);

    --line-3: rgba(255, 255, 255, .30);

    color: var(--on-deep);

}



.on-deep .btn {

    --btn-bg: rgba(255, 255, 255, .07);

    --btn-fg: var(--on-deep);

    --btn-bd: rgba(255, 255, 255, .20);

    backdrop-filter: blur(14px) saturate(160%);

    -webkit-backdrop-filter: blur(14px) saturate(160%);

    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22);

}



.on-deep .btn:hover {

    --btn-bg: rgba(255, 255, 255, .13);

    --btn-bd: rgba(255, 255, 255, .34);

    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .34), 0 10px 30px rgba(0, 0, 0, .45);

}



.on-deep .btn-solid {

    --btn-bg: var(--on-deep);

    --btn-fg: #14110C;

    --btn-bd: transparent;

    box-shadow: 0 2px 6px rgba(0, 0, 0, .4), 0 16px 40px rgba(0, 0, 0, .45);

}



.on-deep .btn-solid:hover {

    --btn-bg: #FFFFFF;

    box-shadow: 0 2px 6px rgba(0, 0, 0, .45), 0 22px 52px rgba(0, 0, 0, .55);

}



.on-deep .btn-quiet {

    --btn-bg: transparent;

    --btn-bd: transparent;

    backdrop-filter: none;

    -webkit-backdrop-filter: none;

    box-shadow: none;

    color: var(--on-deep-2);

}



.on-deep .btn-quiet:hover {

    color: var(--on-deep);

    background: rgba(255, 255, 255, .09);

    box-shadow: none;

}



.on-deep .btn-kbd,

.on-deep kbd {

    background: rgba(255, 255, 255, .10);

    border-color: rgba(255, 255, 255, .16);

    color: var(--on-deep-2);

}



/* browser surfaces belong to the design too */

::selection {

    background: color-mix(in srgb, var(--accent-lit) 28%, transparent);

    color: inherit;

}



html {

    scrollbar-color: rgba(23, 21, 15, .28) transparent;

    scrollbar-width: thin;

}



::-webkit-scrollbar {

    width: 11px;

    height: 11px;

}



::-webkit-scrollbar-track {

    background: transparent;

}



::-webkit-scrollbar-thumb {

    background: rgba(23, 21, 15, .22);

    border: 3px solid var(--ground);

    border-radius: 99px;

}



::-webkit-scrollbar-thumb:hover {

    background: rgba(23, 21, 15, .38);

}



:focus-visible {

    outline: 2px solid var(--accent);

    outline-offset: 3px;

    border-radius: 4px;

}



.on-deep :focus-visible {

    outline-color: var(--accent-lit);

}



/* ---------- 14c · THE OPENING ----------

   A held black frame, the name, a line that fills while the fonts and

   the WebGL room load, then the frame splits and the room is behind

   it. Skippable, and it never blocks the page if JavaScript fails. */

#curtain {

    position: fixed;

    inset: 0;

    z-index: 200;

    display: grid;

    place-items: center;

    background: var(--deep);

    color: var(--on-deep);

    overflow: hidden;

}



#curtain[hidden] {

    display: none !important;

}



.curtain-half {

    position: absolute;

    left: 0;

    right: 0;

    height: 50.5%;

    background: var(--deep);

    will-change: transform;

    /* a little slower, so the room is uncovered rather than snapped open,

       and the camera has time to be seen moving behind it */

    transition: transform 1450ms cubic-bezier(.66, 0, .18, 1);

}



.curtain-half.top {

    top: 0;

    box-shadow: 0 1px 0 rgba(255, 255, 255, .07);

}



.curtain-half.bot {

    bottom: 0;

    box-shadow: 0 -1px 0 rgba(255, 255, 255, .07);

}



#curtain.is-open .curtain-half.top {

    transform: translateY(-101%);

}



#curtain.is-open .curtain-half.bot {

    transform: translateY(101%);

}



.curtain-mid {

    position: relative;

    z-index: 2;

    display: grid;

    justify-items: center;

    gap: 1.6rem;

    text-align: center;

    padding: 2rem;

    transition: opacity 380ms var(--ease-out), transform 380ms var(--ease-out),

        filter 380ms var(--ease-out);

}



#curtain.is-open .curtain-mid {

    opacity: 0;

    transform: translateY(-14px) scale(.97);

    filter: blur(6px);

}



.curtain-name {

    font-family: var(--display);

    font-size: clamp(1.9rem, 6vw, 3.4rem);

    font-weight: 300;

    letter-spacing: -.03em;

    line-height: 1;

    display: flex;

    gap: .01em;

    overflow: hidden;

}



.curtain-name span {

    display: inline-block;

    transform: translateY(110%);

    animation: curtain-rise 900ms var(--ease-out) forwards;

    animation-delay: calc(var(--i) * 34ms);

}



@keyframes curtain-rise {

    to {

        transform: translateY(0);

    }

}



.curtain-role {

    font-family: var(--mono);

    font-size: .68rem;

    letter-spacing: .28em;

    text-transform: uppercase;

    color: var(--on-deep-3);

    opacity: 0;

    animation: curtain-fade 700ms var(--ease-out) 620ms forwards;

}



@keyframes curtain-fade {

    to {

        opacity: 1;

    }

}



.curtain-bar {

    width: min(240px, 44vw);

    height: 1.5px;

    background: rgba(255, 255, 255, .12);

    border-radius: 2px;

    overflow: hidden;

}



.curtain-bar i {

    display: block;

    height: 100%;

    width: 100%;

    transform: scaleX(var(--p, 0));

    transform-origin: 0 50%;

    background: linear-gradient(90deg, var(--accent-lit), #F3B48F);

    box-shadow: 0 0 12px var(--accent-glow);

    transition: transform 340ms var(--ease-out);

}



.curtain-skip {

    position: absolute;

    right: clamp(1rem, 4vw, 2.5rem);

    bottom: clamp(1rem, 4vw, 2.5rem);

    z-index: 3;

    padding: .5rem 1rem;

    border: 1px solid rgba(255, 255, 255, .18);

    border-radius: 99px;

    background: rgba(255, 255, 255, .05);

    color: var(--on-deep-2);

    font-family: var(--mono);

    font-size: .68rem;

    letter-spacing: .12em;

    text-transform: uppercase;

    cursor: pointer;

    opacity: 0;

    animation: curtain-fade 500ms var(--ease-out) 1100ms forwards;

    transition: color var(--t-hover) ease, border-color var(--t-hover) ease;

}



.curtain-skip:hover {

    color: var(--on-deep);

    border-color: rgba(255, 255, 255, .4);

}



/* ---------- 14d · THE ROOM (hero) ---------- */

#hero {

    background: var(--deep);

    color: var(--on-deep);

    isolation: isolate;

}



/* the kawung field from the light layer would be invisible here */

#hero::after {

    opacity: .03;

    background-image: var(--kawung);

    filter: invert(1);

    left: -6%;

    bottom: -10%;

}



/* two lamps in the room, and the handoff back to paper at the bottom */

.hero-scrim {

    background:

        radial-gradient(64% 58% at 14% 34%, color-mix(in srgb, var(--accent-lit) 16%, transparent) 0%, transparent 62%),

        radial-gradient(52% 48% at 88% 74%, rgba(110, 138, 203, .13) 0%, transparent 64%),

        radial-gradient(86% 74% at 8% 48%, rgba(16, 14, 12, .95) 0%, rgba(16, 14, 12, .74) 38%, rgba(16, 14, 12, .22) 70%, transparent 86%),

        linear-gradient(180deg, rgba(16, 14, 12, .88) 0%, rgba(16, 14, 12, .26) 20%, rgba(16, 14, 12, .06) 52%, rgba(16, 14, 12, .58) 82%, var(--ground) 100%);

}



/* volumetric light, two slow beams raked across the room */

.hero-beams {

    position: absolute;

    inset: -20% -10%;

    z-index: 1;

    pointer-events: none;

    opacity: .5;

    mix-blend-mode: screen;

}



.hero-beams i {

    position: absolute;

    top: -30%;

    height: 160%;

    width: clamp(120px, 16vw, 260px);

    background: linear-gradient(180deg, transparent, rgba(255, 226, 200, .16) 26%, rgba(255, 226, 200, .05) 62%, transparent);

    filter: blur(26px);

    transform: rotate(14deg);

    animation: beam 17s var(--ease-inout) infinite alternate;

}



.hero-beams i:nth-child(1) {

    left: 22%;

}



.hero-beams i:nth-child(2) {

    left: 58%;

    width: clamp(80px, 10vw, 170px);

    animation-duration: 23s;

    animation-delay: -6s;

    opacity: .7;

}



@keyframes beam {

    from {

        transform: rotate(11deg) translateX(-3vw);

        opacity: .55;

    }



    to {

        transform: rotate(17deg) translateX(4vw);

        opacity: 1;

    }

}



/* the struck flash behind the headline: sized and placed by script from
   the title's own box, so it always sits right where the burst lands */

.hero-burst-flash {

    position: absolute;

    z-index: 0;

    pointer-events: none;

    opacity: 0;

    background: radial-gradient(50% 50% at 50% 50%, color-mix(in srgb, var(--accent-lit) 55%, transparent) 0%, color-mix(in srgb, var(--accent-lit) 18%, transparent) 42%, transparent 72%);

    filter: blur(6px);

    mix-blend-mode: screen;

}



/* every plane drifts a different distance under the pointer */

.hero-grid,

.hero-beams,

#scene {

    will-change: transform;

}



.hero-status {

    color: var(--on-deep-2);

}



.hero-status .sep {

    color: rgba(255, 255, 255, .22);

}



#title {

    color: var(--on-deep);

    text-shadow: 0 2px 30px rgba(0, 0, 0, .5);

}



#title em {

    color: var(--accent-lit);

}



.hero-sub {

    color: var(--on-deep-2);

}



.hero-sub strong {

    color: var(--on-deep);

}



/* the facts become one glass rail floating over the room */

.hero-facts {

    border: 1px solid var(--glass-line);

    border-radius: 16px;

    background: linear-gradient(155deg, var(--glass-2), var(--glass) 52%, rgba(255, 255, 255, .03));

    backdrop-filter: blur(var(--glass-blur)) saturate(165%);

    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(165%);

    box-shadow: var(--glass-lift);

    overflow: hidden;

}



.hero-facts .fact {

    border-color: rgba(255, 255, 255, .10);

}



.hero-facts .fact b {

    color: var(--on-deep);

}



.hero-facts .fact span {

    color: var(--on-deep-3);

}



/* the portrait is a slab of glass with the photo set into it */

.hp-card {

    background: linear-gradient(155deg, rgba(255, 255, 255, .13), rgba(255, 255, 255, .05) 55%, rgba(255, 255, 255, .02));

    backdrop-filter: blur(22px) saturate(170%);

    -webkit-backdrop-filter: blur(22px) saturate(170%);

    border: 1px solid var(--glass-line-2);

    box-shadow: var(--glass-lift-2);

}



.hp-caption b {

    color: var(--on-deep);

}



.hp-caption span {

    color: var(--on-deep-3);

}



.hp-badge {

    background: rgba(20, 17, 13, .72);

    backdrop-filter: blur(14px) saturate(160%);

    -webkit-backdrop-filter: blur(14px) saturate(160%);

    border-color: rgba(255, 255, 255, .22);

    color: var(--on-deep);

    box-shadow: 0 8px 24px rgba(0, 0, 0, .45);

}



/* On an 820px laptop the hero drops this 25px off the floor of the
   window, marooned a hundred pixels below the portrait it belongs with.
   Lift it toward the portrait whenever the viewport is short. */

@media (min-width: 761px) and (max-height: 900px) {

    .loom {

        bottom: auto;

        top: 85%;

    }

}



.loom {

    background: rgba(20, 17, 13, .5);

    backdrop-filter: blur(18px) saturate(170%);

    -webkit-backdrop-filter: blur(18px) saturate(170%);

    border-color: var(--glass-line);

    box-shadow: var(--glass-lift);

}



.loom-label {

    color: var(--on-deep-3);

}



.loom-now {

    color: var(--accent-lit);

}



.swatch {

    border-color: rgba(255, 255, 255, .18);

    background-color: rgba(255, 255, 255, .06);

}



.swatch.is-on {

    border-color: var(--accent-lit);

    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-lit) 28%, transparent), 0 0 18px var(--accent-glow);

}



/* ---------- 14e · the header, over two different worlds ---------- */

#top {

    transition: background-color 420ms var(--ease-out),

        border-color var(--t-hover) var(--ease-out),

        color 420ms var(--ease-out);

}



#top.on-deep {

    background: rgba(16, 14, 12, .42);

    backdrop-filter: saturate(180%) blur(18px);

    -webkit-backdrop-filter: saturate(180%) blur(18px);

    border-bottom-color: rgba(255, 255, 255, .07);

}



#top.on-deep .ask {

    background: rgba(255, 255, 255, .07);

    border-color: rgba(255, 255, 255, .18);

    color: var(--on-deep-2);

    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18);

}



#top.on-deep .ask:hover {

    color: var(--on-deep);

    border-color: rgba(255, 255, 255, .34);

}



#top.on-deep #burger {

    background: rgba(255, 255, 255, .07);

    border-color: rgba(255, 255, 255, .18);

}



#top.on-deep #burger span,

#top.on-deep #burger span::before,

#top.on-deep #burger span::after {

    background: var(--on-deep);

}



/* ---------- 14f · BOLT, IN THREE DIMENSIONS ----------

   The companion is a real WebGL character now: he floats at the foot

   of the window, turns his head to follow your pointer across the

   page, and glides to whichever section you are reading. */

#bolt {

    position: fixed;

    left: 0;

    bottom: 6px;

    z-index: 55;

    width: 168px;

    height: 168px;

    padding: 0;

    border: 0;

    background: none;

    cursor: pointer;

    will-change: transform;

    transition: opacity 360ms var(--ease-out);

    -webkit-tap-highlight-color: transparent;

}



#bolt:focus-visible {

    outline: none;

}



#bolt:focus-visible .bolt-ring {

    opacity: 1;

    border-color: var(--accent);

}



#bolt.is-away {

    opacity: 0;

    pointer-events: none;

}



#bolt-canvas {

    display: block;

    width: 168px;

    height: 168px;

    pointer-events: none;

}



/* the pool of light he stands in, and the focus target */

.bolt-ring {

    position: absolute;

    left: 50%;

    bottom: 20px;

    width: 74px;

    height: 20px;

    margin-left: -37px;

    border: 1.5px solid transparent;

    border-radius: 50%;

    opacity: 0;

    transition: opacity var(--t-hover) ease;

    pointer-events: none;

}



/* the bubble is glass over paper, so it reads against the page */

.bolt-say {

    position: absolute;

    left: 50%;

    bottom: 132px;

    transform: translate(-50%, 6px) scale(.94);

    transform-origin: 50% 100%;

    max-width: 15rem;

    width: max-content;

    padding: .5rem .85rem;

    border-radius: 13px;

    border: 1px solid var(--glass-day-line);

    background: var(--glass-day);

    backdrop-filter: blur(16px) saturate(180%);

    -webkit-backdrop-filter: blur(16px) saturate(180%);

    box-shadow: var(--glass-day-lift);

    color: var(--ink);

    font-size: .8rem;

    line-height: 1.35;

    text-align: center;

    opacity: 0;

    pointer-events: none;

    transition: opacity 200ms var(--ease-out), transform 320ms var(--ease-spring);

}



.bolt-say::after {

    content: '';

    position: absolute;

    left: 50%;

    bottom: -5px;

    width: 10px;

    height: 10px;

    margin-left: -5px;

    transform: rotate(45deg);

    background: var(--glass-day);

    border-right: 1px solid var(--glass-day-line);

    border-bottom: 1px solid var(--glass-day-line);

}



#bolt.is-saying .bolt-say {

    opacity: 1;

    transform: translate(-50%, 0) scale(1);

}



/* over the hero and the arena the page behind him is dark */

#bolt.on-deep .bolt-say {

    border-color: var(--glass-line-2);

    background: rgba(28, 24, 19, .62);

    color: var(--on-deep);

    box-shadow: var(--glass-lift);

}



#bolt.on-deep .bolt-say::after {

    background: rgba(28, 24, 19, .62);

    border-color: var(--glass-line-2);

}



/* ---------- 14g · the stack, lit ----------

   The orbit was grey wireframe on cream and read as a diagram. Set

   into a dark pane it reads as an object with its own light. */

.orbit {

    border-radius: 20px;

    background:

        radial-gradient(58% 52% at 50% 42%, color-mix(in srgb, var(--accent-lit) 13%, transparent) 0%, transparent 68%),

        radial-gradient(70% 60% at 76% 82%, rgba(110, 138, 203, .12) 0%, transparent 70%),

        linear-gradient(165deg, #1C1811 0%, #100E0C 62%);

    border: 1px solid rgba(255, 255, 255, .10);

    box-shadow:

        inset 0 1px 0 rgba(255, 255, 255, .12),

        0 2px 6px rgba(23, 21, 15, .10),

        0 30px 70px rgba(23, 21, 15, .22);

    overflow: hidden;

}



.orbit::before {

    opacity: .05;

    filter: invert(1);

}



.gl-label {

    color: var(--on-deep-3);

    text-shadow: 0 1px 8px rgba(0, 0, 0, .8);

}



.gl-label.is-primary {

    color: var(--on-deep);

    text-shadow: 0 0 14px color-mix(in srgb, var(--accent-lit) 50%, transparent), 0 1px 8px rgba(0, 0, 0, .9);

}



.orbit-cap {

    color: var(--on-deep-3);

    background: rgba(255, 255, 255, .07);

    border: 1px solid rgba(255, 255, 255, .14);

    backdrop-filter: blur(12px);

    -webkit-backdrop-filter: blur(12px);

}



/* ---------- 14i · THE ARENA ----------

   Merge: drop a motif, two of the same fuse into the next one up.

   The rules are visible in one glance, so the section is built as a

   lit stage — dark ground, glass vessel, everything else recedes. */

#play {

    position: relative;

    background: var(--deep);

    isolation: isolate;

    overflow: hidden;

}



#play::before {

    content: '';

    position: absolute;

    inset: 0;

    z-index: 0;

    pointer-events: none;

    background:

        radial-gradient(46% 40% at 50% 26%, color-mix(in srgb, var(--accent-lit) 16%, transparent) 0%, transparent 66%),

        radial-gradient(56% 44% at 84% 88%, rgba(110, 138, 203, .12) 0%, transparent 68%),

        linear-gradient(180deg, var(--ground) 0%, rgba(16, 14, 12, .82) 7%, var(--deep) 18%, var(--deep) 84%, rgba(16, 14, 12, .82) 94%, var(--ground) 100%);

}



#play > .shell {

    position: relative;

    z-index: 1;

}



.arena {

    display: grid;

    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);

    align-items: center;

    gap: clamp(1.25rem, 3vw, 2.5rem);

}



.arena-rail {

    display: grid;

    gap: .75rem;

    align-content: center;

}



.arena-rail.left {

    justify-items: end;

}



/* --- the vessel --- */

.jar {

    position: relative;

    width: clamp(280px, 32vw, 420px);

    aspect-ratio: 3 / 4;

    border-radius: 22px 22px 26px 26px;

    background: linear-gradient(168deg, rgba(255, 255, 255, .11), rgba(255, 255, 255, .035) 46%, rgba(255, 255, 255, .06));

    backdrop-filter: blur(14px) saturate(150%);

    -webkit-backdrop-filter: blur(14px) saturate(150%);

    border: 1px solid rgba(255, 255, 255, .20);

    box-shadow:

        inset 0 1px 0 rgba(255, 255, 255, .40),

        inset 0 -18px 40px rgba(0, 0, 0, .35),

        inset 22px 0 40px -30px rgba(255, 255, 255, .5),

        0 3px 8px rgba(0, 0, 0, .45),

        0 40px 90px rgba(0, 0, 0, .55);

    overflow: hidden;

    cursor: crosshair;

    touch-action: none;

}



.jar:focus-visible {

    outline: 2px solid var(--accent-lit);

    outline-offset: 4px;

}



/* the highlight running down the left wall of the glass */

.jar::after {

    content: '';

    position: absolute;

    left: 8px;

    top: 14px;

    bottom: 26px;

    width: 12px;

    border-radius: 99px;

    background: linear-gradient(180deg, rgba(255, 255, 255, .34), rgba(255, 255, 255, .05) 40%, transparent);

    filter: blur(3px);

    pointer-events: none;

    z-index: 3;

}



/* the play field itself — scoped by id, because the veils inside the

   vessel carry canvases of their own */

#m-canvas {

    position: absolute;

    inset: 0;

    width: 100%;

    height: 100%;

    z-index: 1;

}



/* the line you must not stack past */

.jar-line {

    position: absolute;

    left: 0;

    right: 0;

    top: 17%;

    z-index: 2;

    height: 0;

    border-top: 1px dashed rgba(255, 255, 255, .22);

    pointer-events: none;

    transition: border-color 300ms ease;

}



.jar-line span {

    position: absolute;

    right: 8px;

    top: -1.05rem;

    font-family: var(--mono);

    font-size: .58rem;

    letter-spacing: .16em;

    text-transform: uppercase;

    color: rgba(255, 255, 255, .28);

    transition: color 300ms ease;

}



.jar.is-danger .jar-line {

    border-top-color: color-mix(in srgb, var(--accent-lit) 85%, transparent);

    animation: danger 900ms ease-in-out infinite;

}



.jar.is-danger .jar-line span {

    color: var(--accent-lit);

}



@keyframes danger {



    0%,

    100% {

        opacity: .45;

    }



    50% {

        opacity: 1;

    }

}



/* --- HUD cards --- */

.mcard {

    display: grid;

    gap: .1rem;

    padding: .75rem 1rem;

    min-width: 8.5rem;

    border-radius: 14px;

    border: 1px solid var(--glass-line);

    background: linear-gradient(155deg, var(--glass-2), var(--glass) 52%, rgba(255, 255, 255, .03));

    backdrop-filter: blur(var(--glass-blur)) saturate(165%);

    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(165%);

    box-shadow: var(--glass-lift);

}



.mcard > span {

    font-family: var(--mono);

    font-size: .6rem;

    letter-spacing: .16em;

    text-transform: uppercase;

    color: var(--on-deep-3);

}



.mcard > b {

    font-family: var(--display);

    font-size: 2rem;

    font-weight: 400;

    line-height: 1.05;

    letter-spacing: -.02em;

    color: var(--on-deep);

    font-variant-numeric: tabular-nums;

}



.mcard.is-hit > b {

    animation: score-pop 420ms var(--ease-out);

}



@keyframes score-pop {

    0% {

        transform: scale(1);

    }



    36% {

        transform: scale(1.16);

        color: var(--accent-lit);

    }



    100% {

        transform: scale(1);

    }

}



/* the next piece, shown as itself rather than as a number */

.mnext {

    display: flex;

    align-items: center;

    gap: .8rem;

    padding: .7rem .95rem;

    border-radius: 14px;

    border: 1px solid var(--glass-line);

    background: linear-gradient(155deg, var(--glass-2), var(--glass) 52%, rgba(255, 255, 255, .03));

    backdrop-filter: blur(var(--glass-blur)) saturate(165%);

    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(165%);

    box-shadow: var(--glass-lift);

}



.mnext canvas {

    width: 44px;

    height: 44px;

    flex: none;

}



.mnext div {

    display: grid;

    gap: .05rem;

}



.mnext span {

    font-family: var(--mono);

    font-size: .6rem;

    letter-spacing: .16em;

    text-transform: uppercase;

    color: var(--on-deep-3);

}



.mnext b {

    font-size: .88rem;

    font-weight: 500;

    color: var(--on-deep);

}



/* --- the ladder of motifs --- */

.ladder {

    justify-items: start;

    gap: .3rem;

}



.ladder-h {

    font-family: var(--mono);

    font-size: .6rem;

    letter-spacing: .16em;

    text-transform: uppercase;

    color: var(--on-deep-3);

    margin-bottom: .35rem;

}



.rung {

    display: flex;

    align-items: center;

    gap: .6rem;

    padding: .2rem .5rem .2rem .2rem;

    border-radius: 99px;

    border: 1px solid transparent;

    transition: border-color var(--t-hover) ease, background-color var(--t-hover) ease,

        transform var(--t-hover) var(--ease-out);

}



.rung canvas {

    width: 26px;

    height: 26px;

    flex: none;

    opacity: .38;

    transition: opacity var(--t-hover) ease, transform 420ms var(--ease-spring);

}



.rung b {

    font-size: .78rem;

    font-weight: 400;

    color: var(--on-deep-3);

    transition: color var(--t-hover) ease;

    white-space: nowrap;

}



.rung.is-found canvas {

    opacity: 1;

}



.rung.is-found b {

    color: var(--on-deep-2);

}



.rung.is-new {

    border-color: color-mix(in srgb, var(--accent-lit) 40%, transparent);

    background: color-mix(in srgb, var(--accent-lit) 10%, transparent);

    transform: translateX(3px);

}



.rung.is-new b {

    color: var(--accent-lit);

}



.rung.is-new canvas {

    transform: scale(1.18);

}



/* --- the veils --- */

.jar-veil {

    position: absolute;

    inset: 0;

    z-index: 4;

    display: grid;

    align-content: center;

    justify-items: center;

    gap: .8rem;

    padding: 1.6rem;

    text-align: center;

    background: rgba(14, 12, 10, .78);

    backdrop-filter: blur(14px);

    -webkit-backdrop-filter: blur(14px);

}



.jar-veil[hidden] {

    display: none !important;

}



.jar-veil h3 {

    font-family: var(--display);

    font-size: clamp(1.35rem, 3vw, 1.9rem);

    font-weight: 400;

    letter-spacing: -.02em;

    line-height: 1.15;

    color: var(--on-deep);

}



.jar-veil p {

    font-size: .88rem;

    line-height: 1.55;

    color: var(--on-deep-2);

    max-width: 26ch;

}



.jar-veil .big {

    font-family: var(--display);

    font-size: clamp(3rem, 9vw, 4.4rem);

    font-weight: 300;

    line-height: 1;

    letter-spacing: -.03em;

    color: var(--accent-lit);

    font-variant-numeric: tabular-nums;

}



.jar-veil .sm {

    font-family: var(--mono);

    font-size: .62rem;

    letter-spacing: .18em;

    text-transform: uppercase;

    color: var(--on-deep-3);

}



/* the rule, drawn rather than described */

.rule-strip {

    display: flex;

    align-items: center;

    gap: .5rem;

    padding: .55rem .8rem;

    border-radius: 99px;

    border: 1px solid var(--glass-line);

    background: rgba(255, 255, 255, .05);

}



.rule-strip canvas {

    width: 30px;

    height: 30px;

}



.rule-strip i {

    font-style: normal;

    font-family: var(--mono);

    font-size: .9rem;

    color: var(--on-deep-3);

}



/* --- the sound toggle --- */

.mute {

    display: inline-flex;

    align-items: center;

    gap: .5rem;

    padding: .45rem .85rem;

    border-radius: 99px;

    border: 1px solid var(--glass-line);

    background: rgba(255, 255, 255, .05);

    color: var(--on-deep-3);

    font-family: var(--mono);

    font-size: .62rem;

    letter-spacing: .14em;

    text-transform: uppercase;

    cursor: pointer;

    transition: color var(--t-hover) ease, border-color var(--t-hover) ease;

}



.mute:hover {

    color: var(--on-deep);

    border-color: rgba(255, 255, 255, .3);

}



.mute svg {

    width: 13px;

    height: 13px;

    flex: none;

    stroke: currentColor;

    fill: none;

    stroke-width: 1.6;

    stroke-linecap: round;

    stroke-linejoin: round;

}



.arena-foot {

    margin-top: clamp(1.5rem, 4vw, 2.5rem);

    font-family: var(--mono);

    font-size: .68rem;

    line-height: 1.7;

    color: var(--on-deep-3);

    text-align: center;

}



.arena-foot code {

    color: var(--on-deep-2);

}



@media (max-width: 900px) {

    .arena {

        grid-template-columns: minmax(0, 1fr);

        justify-items: center;

        gap: 1.25rem;

    }



    .arena-rail {

        grid-auto-flow: column;

        grid-template-columns: none;

        align-items: center;

        justify-items: center;

        gap: .6rem;

        order: -1;

    }



    .arena-rail.left {

        justify-items: center;

    }



    .ladder {

        order: 1;

        grid-auto-flow: row;

        grid-template-columns: repeat(auto-fit, minmax(7.5rem, max-content));

        justify-content: center;

    }



    .ladder-h {

        grid-column: 1 / -1;

        text-align: center;

    }



    .jar {

        width: min(92vw, 380px);

    }

}



/* ============================================================

   15 · CORRECTIONS after the first build

   ============================================================ */



/* the skip control has to leave with the frame it belongs to */

#curtain.is-open .curtain-skip {

    opacity: 0;

    transform: translateY(6px);

    transition: opacity 240ms var(--ease-out), transform 240ms var(--ease-out);

}



/* the portrait was lit for paper; in the room it needs a grade */

.hp-frame img {

    filter: saturate(.94) contrast(1.03) brightness(.97);

}



.hp-frame::before {

    content: '';

    position: absolute;

    inset: 0;

    z-index: 2;

    pointer-events: none;

    border-radius: inherit;

    background:

        radial-gradient(78% 66% at 50% 34%, transparent 46%, rgba(16, 14, 12, .34) 100%),

        linear-gradient(180deg, transparent 52%, rgba(16, 14, 12, .30) 100%);

}



/* --- the vessel had to read as glass with something inside it --- */

.jar {

    background:

        linear-gradient(180deg, rgba(255, 255, 255, .045) 0%, rgba(0, 0, 0, .16) 30%, rgba(0, 0, 0, .34) 100%),

        linear-gradient(168deg, rgba(255, 255, 255, .12), rgba(255, 255, 255, .035) 46%, rgba(255, 255, 255, .07));

    border-color: rgba(255, 255, 255, .26);

    box-shadow:

        inset 0 1px 0 rgba(255, 255, 255, .50),

        inset 0 -22px 44px rgba(0, 0, 0, .5),

        inset 26px 0 44px -32px rgba(255, 255, 255, .55),

        inset -26px 0 44px -32px rgba(255, 255, 255, .35),

        0 3px 8px rgba(0, 0, 0, .45),

        0 40px 90px rgba(0, 0, 0, .6);

}



/* the base of the glass, where the light pools */

.jar::before {

    content: '';

    position: absolute;

    left: 0;

    right: 0;

    bottom: 0;

    height: 30px;

    z-index: 2;

    pointer-events: none;

    background: linear-gradient(180deg, transparent, rgba(255, 255, 255, .12) 62%, rgba(255, 255, 255, .22));

    border-radius: 0 0 25px 25px;

}



.jar-line {

    top: 19%;

}



.jar-line span {

    right: auto;

    left: 10px;

}



/* the rails belong beside the vessel, not adrift in the column */

.arena {

    grid-template-columns: auto auto auto;

    justify-content: center;

    align-items: center;

}



.arena-rail.left {

    justify-items: stretch;

}



.mcard,

.mnext {

    min-width: 9.5rem;

}



/* the rule, drawn: it was collapsing to nothing inside the veil */

.rule-strip {

    width: max-content;

    max-width: 100%;

}



.rule-strip canvas {

    flex: none;

    width: 34px;

    height: 34px;

}



.rule-strip i {

    flex: none;

}



/* keep running text clear of the companion */

.arena-foot {

    max-width: 62ch;

    margin-inline: auto;

    padding-inline: 1rem;

}



/* the graph nodes need their own light on a dark pane */

.orbit {

    box-shadow:

        inset 0 1px 0 rgba(255, 255, 255, .14),

        inset 0 -40px 90px rgba(0, 0, 0, .45),

        0 2px 6px rgba(23, 21, 15, .10),

        0 30px 70px rgba(23, 21, 15, .22);

}



/* Bolt needs a margin to stand in; under this width there is none */

@media (max-width: 1180px) {

    #bolt {

        display: none;

    }

}



/* the base of the glass was competing with the pieces */

.jar::before {

    background: linear-gradient(180deg, transparent, rgba(255, 255, 255, .07) 62%, rgba(255, 255, 255, .14));

}



/* ============================================================

   16 · RESPONSIVE + REDUCED MOTION for sections 14–15

   These live last on purpose: the corrections above are written

   without media queries, so the narrow-screen and no-motion

   rules have to come after them to win.

   ============================================================ */



@media (max-width: 900px) {

    .arena {

        grid-template-columns: minmax(0, 1fr);

        justify-content: stretch;

        justify-items: center;

        gap: 1rem;

    }



    /* the readouts become one wrapping row above the vessel */

    .arena-rail.left {

        display: flex;

        flex-wrap: wrap;

        align-items: stretch;

        justify-content: center;

        gap: .5rem;

        order: -1;

        width: 100%;

    }



    .mcard,

    .mnext {

        min-width: 0;

    }



    .mcard {

        padding: .5rem .9rem;

    }



    .mcard > b {

        font-size: 1.5rem;

    }



    .mnext {

        padding: .5rem .8rem;

    }



    .mnext canvas {

        width: 34px;

        height: 34px;

    }



    .ladder {

        order: 1;

        display: flex;

        flex-wrap: wrap;

        justify-content: center;

        gap: .25rem .45rem;

        width: 100%;

    }



    .ladder-h {

        width: 100%;

        text-align: center;

        margin-bottom: .1rem;

    }



    .jar {

        width: min(92vw, 380px);

    }



    .jar-veil h3 {

        font-size: 1.3rem;

    }

}



/* below this the motif names cost more room than they earn:

   the spheres themselves are the ladder */

@media (max-width: 620px) {

    .rung b {

        display: none;

    }



    .rung {

        padding: .15rem;

    }

}



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



    #curtain {

        display: none !important;

    }



    .hero-beams i,

    .curtain-name span,

    .curtain-role,

    .curtain-skip,

    .jar.is-danger .jar-line,

    .mcard.is-hit > b {

        animation: none !important;

    }



    .curtain-name span {

        transform: none;

    }



    .glass::before,

    .bolt-say,

    .rung canvas {

        transition: none;

    }



    /* the room still has to look like a room, only a still one */

    .hero-beams {

        opacity: .35;

    }

}



/* ============================================================

   17 · SECOND PASS

   Spacing, the About spread, the boundary between the two worlds,

   and a pointer of our own.

   ============================================================ */



/* The section head used one `gap` for both axes, so a 56px row gap

   opened between every heading and its note. Columns keep the air;

   rows are spaced by the note's own margin. */

.sec-head {

    gap: 0 clamp(1.5rem, 4vw, 3.5rem);

}



.sec-note {

    margin-top: .85rem;

}



/* ---------- 17a · the About spread ---------- */

.about-grid {

    grid-template-columns: minmax(0, 21rem) minmax(0, 1fr);

    align-items: start;

}



/* A sticky column was tried here and removed: with `align-items:

   start` the grid item is only as tall as its own content, so there

   is no track for it to travel along. Making it work needs an extra

   wrapper, and the card now fills the column anyway. */



.portrait-tilt,

.portrait-frame {

    transform-style: preserve-3d;

}



/* The passport blue fought every other colour on the page. Graded to

   a single warm ink it reads as a printed plate and belongs. */

.portrait-frame {

    isolation: isolate;

    background: #B4491F;

    border: 1px solid var(--glass-day-line);

    border-radius: 18px;

    box-shadow:

        inset 0 1px 0 rgba(255, 255, 255, .7),

        0 2px 6px rgba(23, 21, 15, .07),

        0 26px 60px rgba(23, 21, 15, .18);

    transition: box-shadow var(--t-hover) var(--ease-out);

}



.portrait-frame img {

    filter: grayscale(1) contrast(1.06) brightness(1.03);

    mix-blend-mode: luminosity;

}



.portrait:hover .portrait-frame {

    box-shadow:

        inset 0 1px 0 rgba(255, 255, 255, .8),

        0 3px 8px rgba(23, 21, 15, .09),

        0 38px 80px rgba(23, 21, 15, .24);

}



.portrait-frame::after {

    background: linear-gradient(180deg, transparent 46%, rgba(40, 16, 6, .72) 100%);

    z-index: 1;

}



.portrait-cap {

    z-index: 2;

}



/* the facts below become one panel of glass, not a loose table */

.portrait-meta {

    margin-top: 1rem;

    padding: .35rem 1rem;

    border-radius: 14px;

    border: 1px solid var(--glass-day-line);

    background: var(--glass-day);

    backdrop-filter: blur(16px) saturate(180%);

    -webkit-backdrop-filter: blur(16px) saturate(180%);

    box-shadow: var(--glass-day-lift);

}



.portrait-meta > div:last-child {

    border-bottom: 0;

}



/* ---------- 17b · the four principles, given a surface ---------- */

.principles {

    display: grid;

    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));

    gap: .75rem;

    list-style: none;

    margin-top: clamp(2.5rem, 5vw, 3.5rem);

    border-top: 0;

    perspective: 900px;

}



.principle {

    position: relative;

    padding: 1.35rem 1.4rem 1.5rem;

    border: 1px solid var(--line);

    border-radius: 16px;

    background: linear-gradient(158deg, rgba(255, 255, 255, .82), rgba(255, 255, 255, .42));

    backdrop-filter: blur(14px) saturate(170%);

    -webkit-backdrop-filter: blur(14px) saturate(170%);

    box-shadow: 0 1px 2px rgba(23, 21, 15, .04);

    transform-style: preserve-3d;

    transition: transform 420ms var(--ease-out), box-shadow 420ms var(--ease-out),

        border-color var(--t-hover) ease;

}



.principle:hover {

    border-color: transparent;

    box-shadow:

        inset 0 1px 0 rgba(255, 255, 255, .9),

        0 2px 6px rgba(23, 21, 15, .06),

        0 26px 56px rgba(23, 21, 15, .16);

}



.principle b {

    display: flex;

    align-items: baseline;

    gap: .6rem;

}



.principle i {

    font-family: var(--mono);

    font-style: normal;

    font-size: .66rem;

    letter-spacing: .1em;

    color: var(--accent);

    transform: translateZ(18px);

}



/* ---------- 17c · the seam between the two worlds ----------

   A divider that sits above a dark section reads as a shelf. These

   two straddle the edge instead, half on paper and half in the dark. */

/* ---------- 17d · the pointer ----------

   A ring that trails the true cursor and swells over anything you

   can act on. The system cursor stays available for text. */

.cursor {

    position: fixed;

    left: 0;

    top: 0;

    z-index: 300;

    pointer-events: none;

    mix-blend-mode: difference;

    will-change: transform;

}



.cursor i {

    position: absolute;

    display: block;

    border-radius: 50%;

    transform: translate(-50%, -50%);

}



/* the dot sits exactly on the hotspot */

.cursor .dot {

    width: 5px;

    height: 5px;

    background: #F2EFE7;

}



/* the ring lags, so the pointer has weight */

.cursor .ring {

    width: 30px;

    height: 30px;

    border: 1.4px solid rgba(242, 239, 231, .62);

    transition: width 260ms var(--ease-spring), height 260ms var(--ease-spring),

        border-color 200ms ease, opacity 200ms ease;

}



.cursor.is-hot .ring {

    width: 52px;

    height: 52px;

    border-color: rgba(242, 239, 231, .95);

}



.cursor.is-text .ring {

    width: 2px;

    height: 26px;

    border-radius: 2px;

    border-width: 0;

    background: rgba(242, 239, 231, .8);

}



.cursor.is-down .ring {

    width: 22px;

    height: 22px;

}



.cursor.is-off {

    opacity: 0;

}



/* the label the ring carries into the play area */

.cursor .tag {

    position: absolute;

    left: 0;

    top: 34px;

    transform: translateX(-50%);

    font-family: var(--mono);

    font-size: .58rem;

    letter-spacing: .14em;

    text-transform: uppercase;

    color: rgba(242, 239, 231, .9);

    white-space: nowrap;

    opacity: 0;

    transition: opacity 200ms ease;

}



.cursor.is-tagged .tag {

    opacity: 1;

}



/* with a pointer of our own, the native one only helps in text */

@media (hover: hover) and (pointer: fine) {

    html.has-cursor,

    html.has-cursor a,

    html.has-cursor button,

    html.has-cursor .jar,

    html.has-cursor [role='button'] {

        cursor: none;

    }

}



/* a caret still helps where people type */

html.has-cursor input,

html.has-cursor textarea {

    cursor: text;

}



/* four principles read better as a 2×2 block than a cramped row */

.principles {

    grid-template-columns: repeat(2, minmax(0, 1fr));

}



@media (max-width: 760px) {

    .principles {

        grid-template-columns: minmax(0, 1fr);

    }

}



/* ============================================================

   18 · THIRD PASS

   The cursor-reveal plate, one card language for About, an

   editorial lead, and a pointer that stops shape-shifting.

   ============================================================ */



/* ---------- 18a · the reveal plate ----------

   Two images in exact register. The front one carries a radial mask

   whose transparent centre follows the pointer, so the plate behind

   shows through a soft window. No mask at all until the pointer is

   inside, so the default state is simply the front photograph. */

.reveal {

    position: relative;

    overflow: hidden;

    isolation: isolate;

    /* the pointer writes these three; both plates read them */

    --mx: 50%;

    --my: 50%;

    --mr: 110px;

}



.reveal img {

    display: block;

    width: 100%;

    height: 100%;

    object-fit: cover;

    object-position: center 12%;

}



.reveal .reveal-back,

.reveal .reveal-front {

    position: absolute;

    inset: 0;

}



/* the front plate is opaque until JavaScript hands it a mask */

.reveal .reveal-front {

    z-index: 1;

    -webkit-mask-image: none;

    mask-image: none;

    will-change: mask-image;

}



.reveal.is-revealing .reveal-front {

    -webkit-mask-image: radial-gradient(circle var(--mr) at var(--mx) var(--my),

            transparent 0%, transparent 54%, #000 100%);

    mask-image: radial-gradient(circle var(--mr) at var(--mx) var(--my),

            transparent 0%, transparent 54%, #000 100%);

}



/* a ring of light around the opening, so it reads as a lens

   rather than as a hole */

.reveal .reveal-halo {

    position: absolute;

    inset: 0;

    z-index: 2;

    pointer-events: none;

    opacity: 0;

    transition: opacity 260ms var(--ease-out);

    background: radial-gradient(circle calc(var(--mr, 110px) * .62) at var(--mx, 50%) var(--my, 50%),

            rgba(255, 226, 200, .16) 0%, rgba(255, 226, 200, .05) 60%, transparent 100%);

    mix-blend-mode: screen;

}



.reveal.is-revealing .reveal-halo {

    opacity: 1;

}



/* The hero photograph arrives on a white studio sweep. Keying it out

   is not possible — her blouse is the same white as the sweep — so it

   is graded warm instead, and the frame's own vignette keeps it from

   glaring in a dark room. */

.hp-frame img {

    filter: saturate(.96) contrast(1.03) sepia(.11) brightness(.99);

}



/* the About plates carry a mask, so they must not drift under it */

.portrait-frame.reveal img,

.portrait:hover .portrait-frame.reveal img {

    transform: none;

    transition: none;

    object-position: center 16%;

}



/* the caption and the fade sit above both plates */

.portrait-frame::after {

    z-index: 2;

}



.portrait-cap {

    z-index: 3;

}



/* ---------- 18b · one card language in About ----------

   There were three different panels here and they read as three

   different systems. Now the column is a single card, and the four

   principles repeat its border, radius and shadow exactly. */



/* the passport blue stays; it is her photograph */

.portrait-frame {

    background: var(--ground-2);

    border: 0;

    border-radius: 16px 16px 0 0;

    box-shadow: none;

}



.portrait-frame img {

    filter: none;

    mix-blend-mode: normal;

}



.portrait-frame::after {

    background: linear-gradient(180deg, transparent 52%, rgba(23, 21, 15, .62) 100%);

}



/* photo and facts share one shell */

.portrait {

    border: 1px solid var(--line);

    border-radius: 16px;

    background: var(--surface);

    box-shadow: 0 1px 2px rgba(23, 21, 15, .04), 0 18px 44px rgba(23, 21, 15, .07);

    overflow: hidden;

    transition: box-shadow 420ms var(--ease-out);

}



.portrait:hover {

    box-shadow: 0 2px 4px rgba(23, 21, 15, .05), 0 30px 66px rgba(23, 21, 15, .13);

}



.portrait-meta {

    margin-top: 0;

    padding: .2rem 1.15rem .35rem;

    border: 0;

    border-radius: 0;

    background: none;

    backdrop-filter: none;

    -webkit-backdrop-filter: none;

    box-shadow: none;

}



/* the grey slab behind the four cards was the old hairline grid

   showing through the new gap */

.principles {

    background: none;

    border-block: 0;

    gap: .8rem;

    perspective: 1000px;

}



.principle,

.principle:nth-child(even) {

    padding: 1.4rem 1.45rem 1.5rem;

    border: 1px solid var(--line);

    border-radius: 16px;

    background: var(--surface);

    backdrop-filter: none;

    -webkit-backdrop-filter: none;

    box-shadow: 0 1px 2px rgba(23, 21, 15, .04);

}



.principle:hover {

    background: var(--surface);

    border-color: var(--line-2);

    box-shadow: 0 2px 4px rgba(23, 21, 15, .05), 0 24px 52px rgba(23, 21, 15, .12);

}



/* ---------- 18c · the lead ----------

   A large sans paragraph floating above body copy read as an

   accident. Set in the display face it reads as a decision. */

.lede {

    position: relative;

    font-family: var(--display);

    font-size: clamp(1.35rem, 2.3vw, 1.72rem);

    font-weight: 300;

    line-height: 1.32;

    letter-spacing: -.022em;

    color: var(--ink);

    max-width: 26ch;

    padding-top: 1.5rem;

    margin-bottom: 1.9rem;

}



.lede::before {

    content: '';

    position: absolute;

    top: 0;

    left: 0;

    width: 46px;

    height: 2px;

    border-radius: 2px;

    background: var(--accent);

}



.prose > p:not(.lede) {

    max-width: 62ch;

}



/* ---------- 18d · the pointer, quieter ----------

   It was changing shape four different ways. Two states is enough:

   at rest, and over something you can act on. */

.cursor .ring {

    width: 26px;

    height: 26px;

    border-width: 1.3px;

}



.cursor.is-hot .ring {

    width: 40px;

    height: 40px;

}



.cursor.is-down .ring {

    width: 20px;

    height: 20px;

}



/* the text bar and the floating labels are gone */

.cursor.is-text .ring {

    width: 26px;

    height: 26px;

    border-radius: 50%;

    border-width: 1.3px;

    background: none;

}



.cursor .tag {

    display: none;

}



/* `overflow-x: hidden` on the body turns it into a scroll container,

   which silently breaks every `position: sticky` on the page. `clip`

   trims the same overflow without creating one; the line above stays

   as the fallback for browsers that do not know it. */

body {

    overflow-x: hidden;

    overflow-x: clip;

}



/* the lens reads better a little wider on a card this size */

.reveal {

    --mr: 130px;

}



/* ============================================================

   19 · RESPONSIVE, restated

   Sections 17 and 18 are written without media queries, so the

   narrow-screen rules they overrode have to be declared again

   after them.

   ============================================================ */



@media (max-width: 940px) {

    .about-grid {

        grid-template-columns: minmax(0, 1fr);

    }



    .portrait {
        max-width: 22rem;
        margin-inline: auto;
    }

    .portrait-cap {
        text-align: center;
    }

    .prose p {
        text-align: justify;
    }



    /* one column here, so the gap is a row gap again */

    .sec-head {

        gap: .5rem;

    }



    .lede {

        max-width: 34ch;

    }

}



@media (max-width: 700px) {

    .principles {

        grid-template-columns: minmax(0, 1fr);

    }

}



/* ============================================================

   20 · DIRECTION

   Five things a page needs before it reads as a film rather than

   as a document: a different arrival for every section, work that

   shows itself, one control that dyes the whole room, a stack

   graph that is also a filter, and a reel for people who will

   never scroll.

   ============================================================ */



/* ---------- 20a · the dye ----------

   Every glow on the page is mixed from the accent, so choosing a

   cloth in the hero re-dyes the arena, the dividers and the graph

   at the same time. The rgba line above each mix is the fallback

   for browsers without color-mix. */

:root {

    --glow-accent-16: rgba(238, 122, 76, .16);

    --glow-accent-16: color-mix(in srgb, var(--accent-lit) 16%, transparent);

    --glow-accent-13: rgba(238, 122, 76, .13);

    --glow-accent-13: color-mix(in srgb, var(--accent-lit) 13%, transparent);

    --glow-accent-28: rgba(238, 122, 76, .28);

    --glow-accent-28: color-mix(in srgb, var(--accent-lit) 28%, transparent);

    --glow-accent-10: rgba(238, 122, 76, .10);

    --glow-accent-10: color-mix(in srgb, var(--accent-lit) 10%, transparent);

}



.hero-scrim {

    background:

        radial-gradient(64% 58% at 14% 34%, var(--glow-accent-16) 0%, transparent 62%),

        radial-gradient(52% 48% at 88% 74%, rgba(110, 138, 203, .13) 0%, transparent 64%),

        radial-gradient(86% 74% at 8% 48%, rgba(16, 14, 12, .95) 0%, rgba(16, 14, 12, .74) 38%, rgba(16, 14, 12, .22) 70%, transparent 86%),

        linear-gradient(180deg, rgba(16, 14, 12, .88) 0%, rgba(16, 14, 12, .26) 20%, rgba(16, 14, 12, .06) 52%, rgba(16, 14, 12, .58) 82%, var(--ground) 100%);

}



.orbit {

    background:

        radial-gradient(58% 52% at 50% 42%, var(--glow-accent-13) 0%, transparent 68%),

        radial-gradient(70% 60% at 76% 82%, rgba(110, 138, 203, .12) 0%, transparent 70%),

        linear-gradient(165deg, #1C1811 0%, #100E0C 62%);

}



.swatch.is-on {

    box-shadow: 0 0 0 2px var(--glow-accent-28), 0 0 18px var(--accent-glow);

}



/* ---------- 20b · scenes ----------

   One authored arrival per section instead of the same rise six

   times. Each is opt-in: the class is added once, on arrival. */



/* work: the rule draws, then each row wipes up from its own baseline.

   Every hidden start state below is scoped to `html.js-scenes`, which

   the scene module adds itself, so a script that never runs leaves the

   content visible instead of blank. */

@keyframes row-wipe {

    from {

        opacity: 0;

        clip-path: inset(0 0 100% 0);

        transform: translateY(10px);

    }



    to {

        opacity: 1;

        clip-path: inset(0 0 -2px 0);

        transform: translateY(0);

    }

}



/* about: the card settles out of the page, the lead's rule draws */

.portrait-frame,

.portrait-meta {

    transform-origin: 50% 0;

}



.section[data-scene='about'].is-here .portrait-frame {

    animation: plate-settle 900ms var(--ease-out) 120ms backwards;

}



.section[data-scene='about'].is-here .portrait-meta {

    animation: plate-settle 900ms var(--ease-out) 240ms backwards;

}



@keyframes plate-settle {

    from {

        opacity: 0;

        transform: perspective(900px) rotateX(7deg) translateY(18px) scale(.97);

    }



    to {

        opacity: 1;

        transform: none;

    }

}



html.js-scenes .lede::before {

    transform: scaleX(0);

    transform-origin: 0 50%;

    transition: transform 700ms var(--ease-out) 420ms;

}



.section[data-scene='about'].is-here .lede::before {

    transform: scaleX(1);

}



/* skills: the pane focuses, then the chips arrive in reading order */

html.js-scenes #orbit-gl,

html.js-scenes #orbit-labels {

    opacity: 0;

    filter: blur(9px);

    transform: scale(.94);

    transition: opacity 800ms var(--ease-out), filter 800ms var(--ease-out),

        transform 900ms var(--ease-out);

}



.section[data-scene='skills'].is-here #orbit-gl,

.section[data-scene='skills'].is-here #orbit-labels {

    opacity: 1;

    filter: blur(0);

    transform: none;

}



.section[data-scene='skills'].is-here #orbit-labels {

    transition-delay: 180ms;

}



html.js-scenes .cap li {

    opacity: 0;

    transform: translateY(6px);

}



.section[data-scene='skills'].is-here .cap li {

    animation: chip-in 460ms var(--ease-out) forwards;

    animation-delay: calc(var(--i, 0) * 22ms + 200ms);

}



@keyframes chip-in {

    to {

        opacity: 1;

        transform: none;

    }

}



/* experience: the spine draws as you read down it */

.traj::before {

    background: var(--line-3);

}



html.js-scenes .traj::before {

    transform-origin: 50% 0;

    transform: scaleY(var(--spine, 0));

}



html.js-scenes .traj-item::before {

    transform: scale(0);

    transition: transform 420ms var(--ease-out), border-color var(--t-hover) ease,

        background-color var(--t-hover) ease;

}



.traj-item.is-in::before {

    transform: scale(1);

}



/* play: the house lights come down as the section takes the screen */

#play::before {

    opacity: calc(.35 + var(--lights, 0) * .65);

}



.jar::after {

    transition: transform 1100ms var(--ease-out), opacity 600ms var(--ease-out);

}



.section[data-scene='play'].is-here .jar::after {

    animation: glint 1400ms var(--ease-out) 320ms;

}



@keyframes glint {

    from {

        transform: translateX(-14px) skewX(-8deg);

        opacity: 0;

    }



    28% {

        opacity: 1;

    }



    to {

        transform: translateX(0);

        opacity: 1;

    }

}



/* contact: the fields arrive in the order they are filled */

html.js-scenes .field,

html.js-scenes #contact-form .btn {

    opacity: 0;

    transform: translateY(10px);

}



.section[data-scene='contact'].is-here .field,

.section[data-scene='contact'].is-here #contact-form .btn {

    animation: chip-in 520ms var(--ease-out) forwards;

    animation-delay: calc(var(--i, 0) * 70ms + 120ms);

}



/* ---------- 20d · the graph as a filter ---------- */

#orbit-labels {

    pointer-events: none;

}



.gl-label {

    pointer-events: auto;

    cursor: pointer;

    padding: 2px 6px;

    margin: -2px -6px;

    border-radius: 6px;

    transition: color var(--t-hover) ease, background-color var(--t-hover) ease;

}



.gl-label[data-count='0'] {

    pointer-events: none;

    cursor: default;

}



.gl-label:hover,

.gl-label:focus-visible {

    color: var(--on-deep);

    background: rgba(255, 255, 255, .10);

}



.gl-label.is-picked {

    color: var(--accent-lit);

    background: var(--glow-accent-28);

}



.orbit-hint {

    position: absolute;

    left: 50%;

    bottom: .9rem;

    transform: translateX(-50%);

    z-index: 3;

    font-family: var(--mono);

    font-size: .58rem;

    letter-spacing: .14em;

    text-transform: uppercase;

    color: var(--on-deep-3);

    white-space: nowrap;

    pointer-events: none;

}



/* the bar that says what the list is currently showing */

.work-filter {

    display: flex;

    align-items: center;

    gap: .7rem;

    flex-wrap: wrap;

    margin-bottom: 1.1rem;

    font-size: .85rem;

    color: var(--muted);

}



.work-filter[hidden] {

    display: none !important;

}



.work-filter b {

    display: inline-flex;

    align-items: center;

    gap: .5rem;

    padding: .32rem .5rem .32rem .8rem;

    border: 1px solid var(--accent);

    border-radius: 99px;

    background: var(--accent-wash);

    color: var(--accent-deep);

    font-weight: 500;

}



.work-filter button {

    display: grid;

    place-items: center;

    width: 20px;

    height: 20px;

    border: 0;

    border-radius: 50%;

    background: var(--accent);

    color: var(--surface);

    font-size: .7rem;

    line-height: 1;

    cursor: pointer;

    transition: transform var(--t-press) var(--ease-out);

}



.work-filter button:active {

    transform: scale(.9);

}






/* ---------- 20e · the reel ----------

   A guided pass over the whole page for someone who will not

   scroll: two bars close in, a caption names each stop, and the

   page moves itself. Escape or a click ends it. */

#reel {

    position: fixed;

    inset: 0;

    z-index: 190;

    pointer-events: none;

}



#reel[hidden] {

    display: none !important;

}



.reel-bar {

    position: absolute;

    left: 0;

    right: 0;

    height: 12vh;

    background: var(--deep);

    transform: scaleY(0);

    transition: transform 620ms var(--ease-drawer);

}



.reel-bar.top {

    top: 0;

    transform-origin: 50% 0;

}



.reel-bar.bot {

    bottom: 0;

    transform-origin: 50% 100%;

}



#reel.is-on .reel-bar {

    transform: scaleY(1);

}



.reel-cap {

    position: absolute;

    left: 50%;

    bottom: calc(12vh + 1.4rem);

    transform: translateX(-50%);

    display: grid;

    justify-items: center;

    gap: .5rem;

    text-align: center;

    padding: .7rem 1.2rem;

    border-radius: 14px;

    border: 1px solid var(--glass-line);

    background: rgba(16, 14, 12, .62);

    backdrop-filter: blur(16px) saturate(170%);

    -webkit-backdrop-filter: blur(16px) saturate(170%);

    box-shadow: var(--glass-lift);

    opacity: 0;

    transition: opacity 320ms var(--ease-out);

}



#reel.is-on .reel-cap {

    opacity: 1;

}



.reel-cap b {

    font-family: var(--display);

    font-size: 1.08rem;

    font-weight: 400;

    letter-spacing: -.015em;

    color: var(--on-deep);

}



.reel-cap span {

    font-family: var(--mono);

    font-size: .58rem;

    letter-spacing: .18em;

    text-transform: uppercase;

    color: var(--on-deep-3);

}



.reel-line {

    width: 190px;

    height: 1.5px;

    border-radius: 2px;

    background: rgba(255, 255, 255, .16);

    overflow: hidden;

}



.reel-line i {

    display: block;

    width: 100%;

    height: 100%;

    transform: scaleX(var(--p, 0));

    transform-origin: 0 50%;

    background: var(--accent-lit);

}



.reel-stop {

    position: absolute;

    right: clamp(1rem, 4vw, 2.4rem);

    top: calc(12vh + 1.2rem);

    pointer-events: auto;

    padding: .45rem 1rem;

    border: 1px solid rgba(255, 255, 255, .2);

    border-radius: 99px;

    background: rgba(16, 14, 12, .6);

    backdrop-filter: blur(14px);

    -webkit-backdrop-filter: blur(14px);

    color: var(--on-deep-2);

    font-family: var(--mono);

    font-size: .62rem;

    letter-spacing: .14em;

    text-transform: uppercase;

    cursor: pointer;

    opacity: 0;

    transition: opacity 320ms var(--ease-out), color var(--t-hover) ease;

}



#reel.is-on .reel-stop {

    opacity: 1;

}



.reel-stop:hover {

    color: var(--on-deep);

}



/* while the reel runs, nothing else invites a click */

html.is-reeling {

    cursor: progress;

}



html.is-reeling #bolt,

html.is-reeling #cursor {

    opacity: 0;

}



@media (max-width: 900px) {




    .reel-bar {

        height: 8vh;

    }



    .reel-cap {

        bottom: calc(8vh + 1rem);

        max-width: 86vw;

    }



    .reel-stop {

        top: calc(8vh + .8rem);

    }

}



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




    html.js-scenes .cap li,

    html.js-scenes .field,

    html.js-scenes #contact-form .btn,

    html.js-scenes #orbit-gl,

    html.js-scenes #orbit-labels,


    .cap li,

    .field,

    #contact-form .btn,

    #orbit-gl,

    #orbit-labels {

        opacity: 1;

        transform: none;

        filter: none;

        clip-path: none;

        animation: none !important;

    }



    html.js-scenes .traj::before,

    .traj::before {

        transform: none;

    }



    .traj-item::before {

        transform: scale(1);

    }






    html.js-scenes .lede::before,

    .lede::before {

        transform: none;

    }



    .reel-bar,

    .reel-cap,

    .reel-stop {

        transition: none;

    }

}



/* the reel trigger sits next to the console trigger and adapts to

   whichever world the header is currently crossing */

.reel-cta {

    display: inline-flex;

    align-items: center;

    gap: .45rem;

    padding: .42rem .85rem;

    border: 1px solid var(--line-2);

    border-radius: 99px;

    background: transparent;

    color: var(--muted);

    font-family: var(--mono);

    font-size: .64rem;

    letter-spacing: .14em;

    text-transform: uppercase;

    cursor: pointer;

    white-space: nowrap;

    transition: color var(--t-hover) ease, border-color var(--t-hover) ease,

        transform var(--t-press) var(--ease-out);

}



.reel-cta:hover {

    color: var(--ink);

    border-color: var(--line-3);

}



.reel-cta:active {

    transform: scale(.97);

}



#top.on-deep .reel-cta {

    border-color: rgba(255, 255, 255, .18);

    color: var(--on-deep-3);

}



#top.on-deep .reel-cta:hover {

    color: var(--on-deep);

    border-color: rgba(255, 255, 255, .34);

}



@media (max-width: 1080px) {

    .reel-cta {

        display: none;

    }

}



/* ============================================================

   21 · FIT

   Three things measured rather than guessed: the About column

   overshooting its photograph, the air between roles, and a

   preview plate that flashed at the top-left before it caught up

   with the pointer.

   ============================================================ */



/* ---------- 21a · the About column ----------

   The prose ran 816 px against a 571 px card. Set at a proper

   reading measure (72ch) in a slightly smaller size, the two

   columns finish together. */

.about-grid {

    grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);

}



.lede {

    font-size: clamp(1.22rem, 2vw, 1.5rem);

    line-height: 1.3;

    max-width: 30ch;

    padding-top: 1.2rem;

    margin-bottom: 1.45rem;

}



.prose > p:not(.lede) {

    font-size: .93rem;

    line-height: 1.6;

    max-width: 72ch;

}



.prose p + p {

    margin-top: .95em;

}



/* ---------- 21b · the timeline ----------

   Two and a half rem under every entry read as a gap rather than

   as a rhythm. */

.traj-item {

    padding-bottom: 1.6rem;

}



.traj::before {

    bottom: 1.6rem;

}



.traj-head {

    gap: .25rem .8rem;

    margin-bottom: .35rem;

}



.traj-role {

    font-size: 1.14rem;

}



.traj-item p {

    font-size: .92rem;

    line-height: 1.6;

    max-width: 72ch;

}



.traj-stack {

    margin-top: .45rem;

}



@media (max-width: 940px) {

    .about-grid {

        grid-template-columns: minmax(0, 1fr);

    }



    .lede {

        max-width: 34ch;

    }

}



/* The date was pushed to the far edge of the shell while the prose

   stopped at its reading measure, leaving a void between a role and

   its dates. One measure now governs both, so the dates right-align

   to the text column rather than to the page. */

.traj-item {

    --traj-measure: 43rem;

}



.traj-head,

.traj-item p,

.traj-stack {

    max-width: var(--traj-measure);

}



.traj-head {

    width: 100%;

}



@media (max-width: 940px) {

    .traj-item {

        --traj-measure: none;

    }



    .traj-period {

        margin-left: 0;

    }

}



/* Bolt lives in the left margin, and the margin is about 200 px wide.

   A 15rem bubble reached past it into the running text, so it is

   narrowed to fit and allowed to wrap instead. */

/* Three rules stack on .bolt-say, and the first sets white-space: nowrap
   with overflow hidden and an ellipsis. Everything after it narrowed the
   box without lifting that, so every caption longer than the box was cut
   off. Let it wrap — and hang it off his right shoulder, since he walks
   the right margin now and a centred bubble would run off the screen. */

.bolt-say {

    left: auto;

    right: 6px;

    max-width: 12rem;

    width: max-content;

    font-size: .74rem;

    padding: .45rem .7rem;

    white-space: normal;

    overflow: visible;

    text-overflow: clip;

    text-align: right;

    transform: translate(0, 6px) scale(.94);

    transform-origin: 100% 100%;

}



#bolt.is-saying .bolt-say {

    transform: translate(0, 0) scale(1);

}



/* ============================================================

   21 · ONE CONTINUOUS TAKE

   The page used to be a lit hero followed by eight metres of

   paper. It is now cut as a film: two dark acts, light acts

   between them, and a real dissolve at every boundary instead

   of a section rule. Nothing here invents content — it decides

   where the light is and which project is currently lit.

   ============================================================ */



/* ---------- 21a · the grade ----------

   One fixed vignette over the whole take, so the frame has

   corners. JS drives --grade from the light arc: it is at full

   strength inside a dark act and gone in a light one. */

#grade {

    position: fixed;

    inset: 0;

    z-index: 45;

    pointer-events: none;

    opacity: var(--grade, 0);

    background:

        radial-gradient(128% 96% at 50% 40%,

            transparent 34%,

            rgba(10, 7, 5, .26) 76%,

            rgba(10, 7, 5, .58) 100%);

}



/* the work act is dark, so the paper ground has to go with it */

#work,
#process {
    background: var(--deep);
    isolation: isolate;
}



/* ---------- 21b · the dissolve ----------

   Between two acts the page holds for half a screen and the

   light changes across it. The batik strip she already had

   rides through that band instead of sitting on a hard seam,

   so the motif passes the camera on the way. */

.dissolve {

    position: relative;

    height: clamp(230px, 42vh, 400px);

    display: grid;

    place-items: center;

    overflow: hidden;

}



.dissolve.to-light {

    background-image:

        linear-gradient(180deg, #100E0C 0%, #45362B 55%, #FAF8F4 100%);

    background-image:

        linear-gradient(180deg in oklab,

            #100E0C 0%, #241A14 30%, #7C6553 62%, #E4DDD2 88%, #FAF8F4 100%);

}



.dissolve.to-dark {

    background-image:

        linear-gradient(180deg, #FAF8F4 0%, #45362B 45%, #100E0C 100%);

    background-image:

        linear-gradient(180deg in oklab,

            #FAF8F4 0%, #E4DDD2 12%, #7C6553 38%, #241A14 70%, #100E0C 100%);

}



/* the strip rides the band: JS gives it --drift from scroll */

.dissolve .strip {

    width: 100%;

    opacity: .5;

    transform: translate3d(0, calc(var(--drift, 0) * 34px), 0);

    will-change: transform;

}



/* a dissolve already ends the section above it */

.dissolve + .section {

    border-top: 0;

}



/* ============================================================

   22 · THE LIGHT ARRIVES, AND THREE SECTIONS STOP BEING A FORM

   The dissolve was smearing through mud, the About lede left a

   hole beside it, and Skills / Experience were reading as a CV

   pasted into a template. Same content in all four — different

   decisions about width, weight and what the page shows.

   ============================================================ */



/* ---------- 22a · the dissolve, re-graded ----------

   A straight ramp from near-black to paper spends most of its

   length in the desaturated middle, which is why it read as a

   brown smear. The band now HOLDS the dark for half its height,

   then the light arrives quickly and with real warmth in it,

   coming from the side the next act is on. */

.dissolve.to-light {

    background-color: var(--ground);

    background-image:

        radial-gradient(128% 62% at 50% 104%, rgba(255, 198, 148, .30), transparent 66%),

        linear-gradient(180deg,

            #100E0C 0%,

            #120F0D 44%,

            #1D1611 58%,

            #3D2A1E 70%,

            #8A705A 82%,

            #D5CCBF 92%,

            #FAF8F4 100%);

}



.dissolve.to-dark {

    background-color: var(--deep);

    background-image:

        radial-gradient(128% 62% at 50% -4%, rgba(255, 198, 148, .26), transparent 66%),

        linear-gradient(180deg,

            #FAF8F4 0%,

            #D5CCBF 8%,

            #8A705A 18%,

            #3D2A1E 30%,

            #1D1611 42%,

            #120F0D 56%,

            #100E0C 100%);

}



/* the motif belongs at the crossover, not in the middle of the

   dark half where nothing is happening yet */

.dissolve {

    place-items: start center;

}



.dissolve.to-light .strip {

    margin-top: 60%;

}



.dissolve.to-dark .strip {

    margin-top: 22%;

}



/* ---------- 22b · the About lede ----------

   It was clamped to 26ch inside a 676px column, so it stopped

   short and left a 230px hole with the body copy running past it

   underneath. A lede sets the column; it does not hide in one

   corner of it. */

.about-grid .prose .lede {

    max-width: none;

}



/* ---------- 22c · four claims, not four cards ----------

   Same four principles. The box, the shadow and the equal-size

   grid are gone: the number sits in the margin, the claim holds

   its own column, and a hairline does the separating. */

.tenets {

    list-style: none;

    margin-top: clamp(3rem, 6vw, 5rem);

    border-top: 1px solid var(--line-2);

}



.tenet {

    --lit: 0;

    display: grid;

    grid-template-columns: clamp(2.6rem, 5vw, 4.2rem) minmax(0, 14rem) minmax(0, 1fr);

    gap: clamp(.8rem, 2.2vw, 2.1rem);

    align-items: start;

    padding: clamp(1.1rem, 2.1vw, 1.7rem) 0;

    border-bottom: 1px solid var(--line);

}



.tenet-no {

    font-family: var(--mono);

    font-style: normal;

    font-size: clamp(.95rem, 1.5vw, 1.15rem);

    line-height: 1.25;

    letter-spacing: -.02em;

    color: color-mix(in oklab, var(--faint), var(--accent) calc(var(--lit, 0) * 100%));

}



.tenet-claim {

    font-size: clamp(1.05rem, 1.6vw, 1.28rem);

    font-weight: 400;

    line-height: 1.2;

    letter-spacing: -.024em;

    color: var(--ink);

}



.tenet-body {

    max-width: 54ch;

    font-size: .89rem;

    line-height: 1.58;

    color: var(--muted);

}



@media (max-width: 860px) {

    .tenet {

        grid-template-columns: 2.6rem minmax(0, 1fr);

        row-gap: .55rem;

    }



    .tenet-no {

        grid-area: 1 / 1;

    }



    .tenet-claim {

        grid-area: 1 / 2;

    }



    .tenet-body {

        grid-area: 2 / 2;

    }

}



/* ---------- 22d · the stack graph gets the room ----------

   It was a 380px box in a side column while the inventory beside

   it did the talking. It is her code, and it is the one thing in

   this section that is not a list, so it opens the section. */

.stack-scene {

    margin-block: clamp(1.6rem, 3vw, 2.6rem) clamp(2.6rem, 5vw, 4.2rem);

}



.stack-scene .orbit {

    position: relative;

    top: auto;

    aspect-ratio: auto;

    width: 100%;

    max-width: none;

    height: clamp(300px, 44vh, 460px);

    min-height: 0;

    margin-inline: 0;

}



/* ---------- 22e · the inventory, set rather than chipped ----------

   Forty-seven identical pills is the most template-looking pattern

   a portfolio can ship, and every one of them claimed the same

   weight. Set as running text under a layer label the group reads

   at a glance, and the ones with shipped products behind them

   carry the count and open the work. */

.caps {

    display: block;

    border-top: 1px solid var(--line-2);

}



.cap {

    display: grid;

    grid-template-columns: clamp(7rem, 13vw, 11rem) minmax(0, 1fr);

    gap: clamp(.9rem, 3vw, 2.5rem);

    align-items: baseline;

    padding: clamp(1.3rem, 2.5vw, 2rem) 0;

    border-bottom: 1px solid var(--line);

}



.cap h3 {

    font-family: var(--mono);

    font-size: .66rem;

    font-weight: 400;

    letter-spacing: .12em;

    text-transform: uppercase;

    color: var(--faint);

}



.cap ul {

    list-style: none;

    display: block;

    font-family: var(--display);

    font-size: clamp(1rem, 1.6vw, 1.28rem);

    line-height: 1.62;

    letter-spacing: -.014em;

    color: var(--ink-2);

}



.cap li {

    display: inline;

    padding: 0;

    border: 0;

    background: none;

    border-radius: 0;

    white-space: normal;

}



.cap li + li::before {

    content: "·";

    color: var(--line-3);

}



/* the ones that shipped: the count is real — how many of the eight

   products used it — and clicking narrows the work index */

.cap li[data-tech] {

    cursor: pointer;

    text-decoration: underline;

    text-decoration-color: transparent;

    text-decoration-thickness: 1px;

    text-underline-offset: .2em;

    transition: color var(--t-hover) ease, text-decoration-color var(--t-hover) ease;

}



.cap li[data-tech]::after {

    content: attr(data-count);

    font-family: var(--mono);

    font-size: .5em;

    vertical-align: .68em;

    margin-left: .1em;

    letter-spacing: 0;

    color: var(--accent);

}



.cap li[data-tech]:hover,

.cap li[data-tech]:focus-visible {

    color: var(--accent);

    text-decoration-color: var(--accent);

}



.cap li[data-tech]:focus-visible {

    outline: 2px solid var(--accent);

    outline-offset: 3px;

    border-radius: 2px;

}



.cap li.is-lit {

    color: var(--accent);

    text-decoration-color: var(--accent);

}



@media (max-width: 860px) {

    .cap {

        grid-template-columns: minmax(0, 1fr);

        row-gap: .5rem;

    }

}



/* ---------- 22f · the trajectory, on a real axis ----------

   The list said "eight roles since 2022" and then never showed

   when any of them happened. The spine draws the actual spans, so

   the overlap — three years of it running straight through the

   degree — becomes visible instead of claimed. It rides under the

   header while the roles scroll past it. */

.traj-spine {

    display: none;

}



@media (min-width: 1100px) and (min-height: 680px) {

    .traj-spine {

        display: block;

        position: sticky;

        top: 4.2rem;

        z-index: 6;

        padding: .85rem 0 .6rem;

        margin-bottom: clamp(1.6rem, 3vw, 2.6rem);

        background: color-mix(in srgb, var(--ground) 90%, transparent);

        backdrop-filter: saturate(160%) blur(12px);

        -webkit-backdrop-filter: saturate(160%) blur(12px);

        border-bottom: 1px solid var(--line);

    }

}



.spine-lanes {

    position: relative;

}



.spine-grid {

    position: absolute;

    inset: 0;

    pointer-events: none;

}



.spine-grid i {

    position: absolute;

    top: 0;

    bottom: 0;

    left: calc(var(--x) * 1%);

    width: 1px;

    background: var(--line);

}



.spine-bar {

    position: absolute;

    left: calc(var(--a) * 1%);

    width: calc(var(--w) * 1%);

    top: calc(var(--row) * 11px + 2px);

    height: 5px;

    min-width: 3px;

    border-radius: 3px;

    background: var(--line-3);

    transition: background-color 220ms var(--ease-out);

}



/* the degree runs under all of it, so it is drawn as ground rather

   than as another role */

.spine-bar.is-base {

    background: var(--accent-wash);

    border: 1px solid var(--line-2);

}



.spine-bar.is-lit {

    background: var(--accent);

    height: 7px;

    top: calc(var(--row) * 11px + 1px);

}



.spine-axis {

    position: relative;

    height: 1rem;

    margin-top: .45rem;

}



.spine-year {

    position: absolute;

    left: calc(var(--x) * 1%);

    transform: translateX(-50%);

    font-family: var(--mono);

    font-size: .58rem;

    letter-spacing: .08em;

    color: var(--faint);

    white-space: nowrap;

}



.spine-year.is-first {

    transform: none;

}



.spine-year.is-last {

    transform: translateX(-100%);

}



.spine-cap {

    display: flex;

    align-items: baseline;

    justify-content: space-between;

    gap: 1rem;

    margin-bottom: .55rem;

    font-family: var(--mono);

    font-size: .6rem;

    letter-spacing: .1em;

    text-transform: uppercase;

    color: var(--faint);

}



.spine-cap b {

    font-family: var(--sans);

    font-size: .8rem;

    font-weight: 500;

    letter-spacing: .01em;

    text-transform: none;

    color: var(--ink);

}



/* ---------- 22g · reduced motion ---------- */

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



    .spine-bar {

        transition: none;

    }



    .tenet-no {

        color: var(--faint);

    }

}



/* ============================================================

   23 · SECOND PASS

   ============================================================ */



/* ---------- 23a · the separator needed air ----------

   A trailing space inside `content` collapses in inline flow, so the

   middot was hugging the next name. Margins do not collapse. */

.cap li + li::before {

    content: "·";

    margin: 0 .44em 0 .34em;

}



.cap li[data-tech]::after {

    margin-left: .14em;

}



/* ---------- 23b · the graph caption, off the corner ---------- */

.stack-scene .orbit-cap {

    left: clamp(1rem, 2.4vw, 1.9rem);

    bottom: clamp(.9rem, 2vw, 1.4rem);

}



/* ---------- 23c · the roles get the page width ----------

   The list ran at 43rem in a 1240px shell and left half the row

   empty. The measure is right — 65ch is where it should stay — so

   the row is what changes: the date takes the left margin the way a

   date does on a CV, and the stack takes the right one, which also

   makes the stacks scannable straight down the page. */

@media (min-width: 1100px) {

    .traj-item {

        --traj-measure: none;

        display: grid;

        grid-template-columns:

            clamp(6rem, 8vw, 8.5rem)

            minmax(0, 40rem)

            minmax(0, 13rem);

        gap: .5rem clamp(1.4rem, 3vw, 2.8rem);

        align-items: start;

    }



    .traj-item .traj-period {

        grid-area: 1 / 1;

        margin-left: 0;

        text-align: right;

        padding-top: .35rem;

        white-space: normal;

    }



    .traj-item .traj-head {

        grid-area: 1 / 2;

        max-width: none;

        margin-bottom: .1rem;

    }



    .traj-item > p:not(.traj-stack) {

        grid-column: 2;

        max-width: none;

    }



    .traj-item .traj-stack {

        grid-area: 1 / 3;

        max-width: none;

        margin-top: .4rem;

        line-height: 1.7;

    }



    /* the dot on the rule lines up with the role, not with the date */

    .traj-item::before {

        top: .95rem;

    }

}



/* ---------- 23d · the spine caption ----------

   Two long strings on one line wrapped into four. The role takes the

   room it needs and truncates; the period never wraps. */

.spine-cap {

    flex-wrap: nowrap;

}



.spine-cap b {

    min-width: 0;

    overflow: hidden;

    text-overflow: ellipsis;

    white-space: nowrap;

}



.spine-cap span {

    flex: none;

    white-space: nowrap;

}



/* ============================================================

   24 · THIRD PASS

   ============================================================ */



/* ---------- 24a · the motif was pushed out of its own band ----------

   A percentage `margin-top` resolves against the containing block's

   WIDTH, not its height, so 60% was 864px on a 1440px page and the

   strip left the band entirely. Position it instead. */

.dissolve {

    place-items: center;

}



.dissolve .strip {

    position: absolute;

    left: 0;

    right: 0;

    margin-top: 0;

}



.dissolve.to-light .strip {

    top: 63%;

}



.dissolve.to-dark .strip {

    top: 27%;

}



/* ---------- 24b · the separator must not open a line ----------

   With the middot on ::before, a wrap put it at the head of the next

   line. It belongs to the name in front of it, so it now rides on

   ::after — which means the shipped-count moves into a real element

   rather than competing for the same pseudo. */

.cap li + li::before {

    content: none;

}



.cap li:not(:last-child)::after {

    content: "·";

    margin: 0 .34em 0 .44em;

    color: var(--line-3);

}



.cap li {

    white-space: nowrap;

}



.cap-n {

    font-family: var(--mono);

    font-size: .5em;

    vertical-align: .68em;

    margin-left: .14em;

    letter-spacing: 0;

    color: var(--accent);

}



/* ---------- 24c · the role rows were breathing too hard ----------

   The stack sat in row one beside the heading, so its second line set

   the row height and opened a gap above every description. It belongs

   next to the description it describes. */

@media (min-width: 1100px) {

    .traj-item .traj-stack {

        grid-area: 2 / 3;

        align-self: start;

        margin-top: .25rem;

    }



    .traj-item > p:not(.traj-stack) {

        grid-row: 2;

    }



    .traj-item .traj-head {

        margin-bottom: 0;

    }

}



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

    .dissolve .strip {

        transform: none;

    }

}



/* the count is an element now; this pseudo would otherwise still paint

   an empty box on the last name in each group */

.cap li[data-tech]::after {

    content: none;

}



.cap li:not(:last-child)::after {

    content: "·";

    margin: 0 .34em 0 .44em;

    color: var(--line-3);

}


/* ============================================================
   25 · FOURTH PASS — measured fixes
   ============================================================ */

/* The strip was still leaving the band: `.dissolve.to-light .strip`
   carries three classes, so the 60% margin from the first pass beat
   the reset. Kill it at matching weight. */
.dissolve.to-light .strip,
.dissolve.to-dark .strip {
    position: absolute;
    left: 0;
    right: 0;
    margin: 0;
}

.dissolve.to-light .strip {
    top: 63%;
}

.dissolve.to-dark .strip {
    top: 27%;
}

/* --faint is 2.9:1 on the paper ground. That is fine for a hairline
   and wrong for anything anyone has to read, and this pass promoted
   four of these to things people read: the layer label, the date in
   the margin, and both spine legends. Measured on #FAF8F4:
   --faint 2.9:1, --muted 5.5:1. */
.cap h3,
.traj-period,
.spine-year,
.spine-cap {
    color: var(--muted);
}

/* the count was 4.44:1 at 6.7px — under the floor and under the size
   at which a numeral is legible at all */
.cap-n {
    font-size: .62em;
    color: var(--accent-deep);
}

.on-deep .cap h3,
.on-deep .traj-period {
    color: var(--on-deep-2);
}

/* ============================================================
   26 · THE CLOTH DECIDES THE COLOUR
   Truntum opens the page, so the accent starts pink. Anything
   that was still mixing a hard-coded terracotta is mixed from
   the dye instead, and the dissolve takes the dye with it.
   ============================================================ */

/* alpha steps of the live accent, so a glow follows the cloth */
:root {
    --dye-04: rgba(238, 122, 76, .04);
    --dye-04: color-mix(in srgb, var(--accent-lit) 4%, transparent);
    --dye-05: rgba(238, 122, 76, .05);
    --dye-05: color-mix(in srgb, var(--accent-lit) 5%, transparent);
    --dye-14: rgba(238, 122, 76, .14);
    --dye-14: color-mix(in srgb, var(--accent-lit) 14%, transparent);
    --dye-26: rgba(255, 198, 148, .26);
    --dye-26: color-mix(in srgb, var(--accent-lit) 30%, transparent);
}

/* ---------- 26a · the dissolve, thinner and dyed ----------
   It was 42vh of ramp, which is a lot of page spent on a colour
   change and read as a thick band rather than a cut. Just under
   a quarter of a screen is enough to feel the lights move. The
   mid-tones are now mixed from the accent, so a pink cloth
   dissolves through pink instead of through terracotta. */
.dissolve {
    height: clamp(140px, 24vh, 240px);
}

.dissolve.to-light {
    background-color: var(--ground);
    background-image:
        linear-gradient(180deg, #100E0C 0%, #46372C 62%, #FAF8F4 100%);
    background-image:
        radial-gradient(120% 58% at 50% 106%, var(--dye-26), transparent 68%),
        linear-gradient(180deg,
            var(--deep) 0%,
            var(--deep) 40%,
            color-mix(in oklab, var(--accent-deep) 30%, var(--deep)) 60%,
            color-mix(in oklab, var(--accent) 45%, var(--ground)) 76%,
            color-mix(in oklab, var(--accent-wash) 72%, var(--ground)) 89%,
            var(--ground) 100%);
}

.dissolve.to-dark {
    background-color: var(--deep);
    background-image:
        linear-gradient(180deg, #FAF8F4 0%, #46372C 38%, #100E0C 100%);
    background-image:
        radial-gradient(120% 58% at 50% -6%, var(--dye-26), transparent 68%),
        linear-gradient(180deg,
            var(--ground) 0%,
            color-mix(in oklab, var(--accent-wash) 72%, var(--ground)) 11%,
            color-mix(in oklab, var(--accent) 45%, var(--ground)) 24%,
            color-mix(in oklab, var(--accent-deep) 30%, var(--deep)) 40%,
            var(--deep) 60%,
            var(--deep) 100%);
}

/* The motif was sitting where the band is mid-tone, which is exactly
   where an accent-coloured pattern cannot be seen. It rides the dark
   half now, in the lit variant of the dye — the one thing still
   catching light before the lamps come up. */
.dissolve.to-light .strip,
.dissolve.to-dark .strip {
    position: absolute;
    left: 0;
    right: 0;
    margin: 0;
    opacity: .72;
}

.dissolve.to-light .strip {
    top: 20%;
}

.dissolve.to-dark .strip {
    top: 72%;
}

/* ---------- 26b · the graph carries the section ----------
   At 23rem in the right-hand column the graph was a texture behind
   the reading, not the thing being read: the names took the width and
   the object you can actually turn took what was left. They swap. The
   inventory is a column of names — it does not need a measure, it
   needs to be scannable — so it condenses to the left and each group
   stacks its heading over its list instead of setting them in two
   columns inside a narrow box. */
.skills-grid {
    display: grid;
    grid-template-columns: minmax(0, 28rem) minmax(0, 1fr);
    gap: clamp(1.6rem, 3.5vw, 3rem);
    align-items: start;
}

.skills-grid .caps {
    order: -1;
    border-top: 1px solid var(--line-2);
}

.skills-grid .cap {
    grid-template-columns: minmax(0, 1fr);
    gap: .35rem;
    padding-block: clamp(.85rem, 1.6vw, 1.15rem);
}

.skills-grid .cap ul {
    font-size: clamp(.9rem, 1.15vw, 1rem);
    line-height: 1.62;
}

@media (max-width: 940px) {
    .skills-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ---------- 26d · the graph caption sat on the corner ---------- */
.skills-grid .orbit-cap {
    left: 1.1rem;
    bottom: 1rem;
}

/* ---------- 26e · two captions were sharing one corner ----------
   `.orbit-cap` carries "Core stack / drag to turn" and JS appends
   `.orbit-hint` with "drag to turn - click a name to filter the work"
   across the bottom. They overlapped, and said the same thing twice.
   The label takes the top of the pane, the instruction keeps the
   bottom, and the duplicate half of the label goes. */
.skills-grid .orbit-cap {
    top: 1rem;
    bottom: auto;
    left: 1rem;
}

.skills-grid .orbit-cap .stage-hint {
    display: none;
}

/* ============================================================
   27 · FIFTH PASS
   ============================================================ */

/* The band is 216px now, not 400. The old drift range pushed the
   motif clean out of it at the ends of the travel. */
.dissolve .strip {
    transform: translate3d(0, calc(var(--drift, 0) * 20px), 0);
}

/* height and top are layout properties; a colour change is enough to
   mark the role in focus, and it does not reflow eight bars per frame */
.spine-bar {
    transition: background-color 220ms var(--ease-out);
}

/* a 96px label column beside a 241px list is not a two-column layout */
@media (max-width: 860px) {
    .skills-grid .cap {
        grid-template-columns: minmax(0, 1fr);
        row-gap: .5rem;
    }
}

/* ============================================================
   28 · THE GRADIENT GOES, THE CARDS COME BACK
   ============================================================ */

/* ---------- 28a · hard cuts ----------
   The dissolve bands are gone from the markup. What is left at each
   act boundary is her batik rule on the paper ground and a straight
   cut from the dark act to it — which is a cut, and cuts are fine. */
.strip + .section,
.section + .strip {
    border-top: 0;
}

/* ---------- 28b · four cards ----------
   She wants the four principles in cards, so they are cards: one
   elevation (a soft warm shadow, no competing border), a 2x2 grid
   that does not pretend to be a list, and the number still lighting
   as the reader reaches it. */
.tenets {
    list-style: none;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(.8rem, 1.5vw, 1.1rem);
    margin-top: clamp(2.2rem, 4.5vw, 3.6rem);
    margin-inline: auto;
    max-width: 62rem;
    border-top: 0;
}

.tenet {
    --lit: 0;
    display: block;
    padding: clamp(1.05rem, 1.8vw, 1.45rem);
    border: 0;
    border-radius: 12px;
    background: var(--surface);
    box-shadow: var(--shadow-1);
}

.tenet-no {
    display: block;
    font-family: var(--mono);
    font-style: normal;
    font-size: .72rem;
    letter-spacing: .1em;
    line-height: 1;
    margin-bottom: .6rem;
    color: color-mix(in oklab, var(--muted), var(--accent) calc(var(--lit, 0) * 100%));
}

.tenet-claim {
    font-size: clamp(1rem, 1.35vw, 1.12rem);
    font-weight: 400;
    line-height: 1.22;
    letter-spacing: -.022em;
    margin-bottom: .45rem;
    color: var(--ink);
}

.tenet-body {
    max-width: none;
    font-size: .855rem;
    line-height: 1.58;
    color: var(--muted);
}

@media (max-width: 760px) {
    .tenets {
        grid-template-columns: minmax(0, 1fr);
    }

    .tenet {
        padding: 1.3rem;
    }
}

/* ---------- 28c · the case study shows phones as phones ----------
   Same rule as the preview frame: a portrait set stands side by side
   at a size a handset should be, instead of one 2532px-tall column. */
.cs-shots {
    display: block;
}

.cs-shots.is-tall {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: .6rem;
    align-items: start;
}

.cs-shots.is-tall .cs-shot {
    margin-bottom: 0;
    background: var(--surface-sunk);
}

.cs-shots.is-tall .cs-shot img {
    display: block;
    width: 100%;
    height: auto;
}

@media (max-width: 520px) {
    .cs-shots.is-tall {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: .35rem;
    }
}

/* ============================================================
   29 · THE DIVIDER, WITH THE MOTIF AT ITS CENTRE
   The strip was a full-width band of batik, which read as a
   texture running across the page. A divider is a rule; the
   motif is what sits in the middle of it.
   ============================================================ */
.strip {
    position: relative;
    height: 52px;
    background-repeat: repeat;
    background-position: 50% 50%;
    opacity: 1;
    /* only a hand's width of the motif shows, centred */
    mask-image: linear-gradient(90deg,
            transparent calc(50% - 108px),
            #000 calc(50% - 74px),
            #000 calc(50% + 74px),
            transparent calc(50% + 108px));
    -webkit-mask-image: linear-gradient(90deg,
            transparent calc(50% - 108px),
            #000 calc(50% - 74px),
            #000 calc(50% + 74px),
            transparent calc(50% + 108px));
}

/* the rule runs out to both edges and stops clear of the motif */
.strip::before,
.strip::after {
    content: '';
    position: absolute;
    top: 50%;
    height: 1px;
    background: var(--line-2);
}

.strip::before {
    left: 0;
    right: calc(50% + 122px);
    mask-image: linear-gradient(90deg, transparent, #000 18%);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 18%);
}

.strip::after {
    left: calc(50% + 122px);
    right: 0;
    mask-image: linear-gradient(90deg, #000 82%, transparent);
    -webkit-mask-image: linear-gradient(90deg, #000 82%, transparent);
}

/* on a dark act the rule has to be light, not ink */
.on-deep + .strip::before,
.on-deep + .strip::after {
    background: rgba(255, 255, 255, .12);
}

@media (max-width: 640px) {
    .strip {
        height: 42px;
        mask-image: linear-gradient(90deg,
                transparent calc(50% - 76px),
                #000 calc(50% - 50px),
                #000 calc(50% + 50px),
                transparent calc(50% + 76px));
        -webkit-mask-image: linear-gradient(90deg,
                transparent calc(50% - 76px),
                #000 calc(50% - 50px),
                #000 calc(50% + 50px),
                transparent calc(50% + 76px));
    }

    .strip::before {
        right: calc(50% + 90px);
    }

    .strip::after {
        left: calc(50% + 90px);
    }
}

/* ============================================================
   30 · THE WORKSHOP
   This section does not draw anything of its own. The six benches
   are built into the live compound in § 4, and here the camera
   leaves the corridor and tracks sideways down them — the only
   lateral move on the page, which is what makes it read as a
   different kind of shot rather than more of the same scroll.

   So the markup is a tall empty track for that move to happen in,
   and a caption that sticks to the viewport while it does. The
   scrim is nearly off here: the section IS the picture.
   ============================================================ */
#process {
    --scrim: .10;
}

/* the run the camera needs to travel in — one screen of dwell per
   frame in the hall, so no product goes past unread */
.hall-track {
    position: relative;
    height: 420vh;
    margin-top: clamp(1.5rem, 3vw, 2.6rem);
}

.hall-hud {
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    align-items: center;
    pointer-events: none;
}

/* The card is the boundary. It holds the left of frame and the bench it
   is captioning sits clear of it on the right — without it the whole
   viewport is picture and there is nothing telling you where to look.
   Solid enough to be a wall, not a tint. */
.hall-hud-in {
    max-width: 23rem;
    padding: clamp(1rem, 1.8vw, 1.35rem) clamp(1.05rem, 2vw, 1.5rem);
    border-radius: var(--radius-lg);
    background: linear-gradient(180deg, rgba(9, 8, 7, .46), rgba(9, 8, 7, .62));
    backdrop-filter: blur(16px) saturate(.85);
    -webkit-backdrop-filter: blur(16px) saturate(.85);
    border: 1px solid rgba(255, 255, 255, .09);
    box-shadow: 0 18px 46px rgba(0, 0, 0, .34);
    /* the hud itself is inert so the hall can be looked at through it;
       the card is the one part of it you can reach */
    pointer-events: auto;
}

.hall-count {
    margin: 0 0 .55rem;
    font-family: var(--mono);
    font-size: .76rem;
    letter-spacing: .16em;
    color: var(--on-deep-2);
}

.hall-count b {
    color: var(--accent-lit);
    font-weight: 500;
}

/* the name changes with the frame — it is swapped by script, so it
   gets a transition rather than an animation */
.hall-term {
    margin: 0;
    font-size: clamp(1.5rem, 2.6vw, 2.1rem);
    line-height: 1.06;
    letter-spacing: -.02em;
    color: var(--on-deep);
}

.hall-phase {
    margin: .3rem 0 0;
    font-family: var(--mono);
    font-size: .76rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--accent-lit);
}

.hall-line {
    margin: .6rem 0 0;
    font-size: .93rem;
    line-height: 1.5;
    color: var(--on-deep-2);
    text-wrap: pretty;
}

/* what the product is made of — the same three the index shows */
.hall-stack {
    margin: .7rem 0 0;
    font-family: var(--mono);
    font-size: .76rem;
    letter-spacing: .06em;
    line-height: 1.6;
    color: var(--on-deep-2);
}

.hall-term,
.hall-phase,
.hall-line,
.hall-stack,
.hall-count b {
    transition: opacity 280ms var(--ease-out);
}

.hall-hud-in.is-turning .hall-term,
.hall-hud-in.is-turning .hall-phase,
.hall-hud-in.is-turning .hall-line,
.hall-hud-in.is-turning .hall-stack,
.hall-hud-in.is-turning .hall-count b {
    opacity: 0;
}

/* standing in front of a frame, you can open what is in it */
.hall-open {
    margin-top: 1.1rem;
}

/* one mark per frame, filling as the camera passes each */
.hall-pips {
    list-style: none;
    display: flex;
    gap: .4rem;
    margin: 1.2rem 0 0;
    padding: 0;
}

.hall-pips li {
    height: 2px;
    width: 1.9rem;
    background: rgba(255, 255, 255, .16);
    border-radius: 2px;
    transition: background-color 320ms var(--ease-out);
}

.hall-pips li.is-on {
    background: var(--accent-lit);
}

/* Where the hall cannot be rendered it collapses and the same eight
   hang flat instead — the pictures, on a wall, in one look. It is the
   whole section in that case, so it is never a second copy of one. */
.hall-plain {
    display: none;
}

html:not(.has-world) .hall-track {
    display: none;
}

html:not(.has-world) .hall-plain,
.hall-plain.is-flat {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: clamp(1rem, 2vw, 1.6rem);
    list-style: none;
    margin: clamp(2rem, 4vw, 3rem) auto 0;
    padding: 0 var(--gutter);
    max-width: var(--wide);
}

.hall-plain a {
    display: block;
    text-decoration: none;
    color: inherit;
}

.hall-plain img,
.hall-plain .hall-plate {
    display: block;
    width: 100%;
    aspect-ratio: 3 / 2;
    object-fit: cover;
    border-radius: var(--radius);
    border: 1px solid rgba(255, 255, 255, .10);
    background: #12100D;
    transition: transform 320ms var(--ease-out);
}

/* the two whose screen is not in the repository get a printed plate,
   the same one the hall hangs for them */
.hall-plain .hall-plate {
    display: grid;
    place-content: center;
    text-align: center;
    font-family: var(--display);
    font-style: normal;
    font-size: 1.5rem;
    line-height: 1.15;
    color: var(--on-deep-2);
}

.hall-plain a:hover img,
.hall-plain a:hover .hall-plate {
    transform: translateY(-4px);
}

/* narrowed by a technology picked in the skills section */
.hall-plain li {
    transition: opacity 280ms var(--ease-out);
}

.hall-plain li.is-out {
    opacity: .22;
}

.hall-plain b {
    display: block;
    margin-top: .7rem;
    font-family: var(--display);
    font-size: 1.15rem;
    color: var(--on-deep);
}

.hall-plain span {
    font-family: var(--mono);
    font-size: .72rem;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--on-deep-3);
}

/* HR scans this list for technologies; give them the stack, not just
   the name of the thing. */
.hall-plain i.hp-stack {
    display: block;
    margin-top: .4rem;
    font-family: var(--mono);
    font-style: normal;
    font-size: .68rem;
    line-height: 1.55;
    letter-spacing: .03em;
    text-transform: none;
    color: var(--on-deep-3);
}

@media (max-width: 760px) {
    .hall-track {
        height: 360vh;
    }

    .hall-hud {
        align-items: flex-end;
        padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
    }

    .hall-hud-in {
        max-width: none;
    }
}

/* Reduced motion: there is no camera to track, so the run collapses
   and the plain list carries the section. */
@media (prefers-reduced-motion: reduce) {
    .hall-track {
        display: none;
    }

    .hall-plain {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
        gap: clamp(1rem, 2vw, 1.6rem);
        list-style: none;
        margin: clamp(2rem, 4vw, 3rem) auto 0;
        padding: 0 var(--gutter);
        max-width: var(--wide);
    }
}

/* ============================================================
   31 · THE DIVIDER, PROPERLY
   `mask-image` on the strip was masking its own pseudo-elements
   too, so the rule either side of the motif never showed. The
   strip now only carries the motif; the rule belongs to the
   wrapper around it, where nothing masks it away.
   ============================================================ */
.rule {
    position: relative;
    display: grid;
    place-items: center;
    height: 56px;
}

/* one hairline, edge to edge, with a gap cut for the motif */
.rule::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 1px;
    background: var(--line-2);
    mask-image: linear-gradient(90deg,
            transparent 0,
            #000 9%,
            #000 calc(50% - 96px),
            transparent calc(50% - 82px),
            transparent calc(50% + 82px),
            #000 calc(50% + 96px),
            #000 91%,
            transparent 100%);
    -webkit-mask-image: linear-gradient(90deg,
            transparent 0,
            #000 9%,
            #000 calc(50% - 96px),
            transparent calc(50% - 82px),
            transparent calc(50% + 82px),
            #000 calc(50% + 96px),
            #000 91%,
            transparent 100%);
}

/* the motif is a centred patch, not a band across the page */
.rule .strip {
    position: relative;
    z-index: 1;
    width: 148px;
    height: 46px;
    opacity: .85;
    background-repeat: repeat;
    background-position: 50% 50%;
    mask-image: radial-gradient(closest-side, #000 58%, transparent 100%);
    -webkit-mask-image: radial-gradient(closest-side, #000 58%, transparent 100%);
}

/* on the dark side of a cut the rule has to be light */
.on-deep + .rule::before {
    background: rgba(255, 255, 255, .14);
}

.rule + .section {
    border-top: 0;
}

@media (max-width: 640px) {
    .rule {
        height: 46px;
    }

    .rule .strip {
        width: 108px;
        height: 38px;
    }

    .rule::before {
        mask-image: linear-gradient(90deg,
                transparent 0, #000 8%,
                #000 calc(50% - 74px), transparent calc(50% - 62px),
                transparent calc(50% + 62px), #000 calc(50% + 74px),
                #000 92%, transparent 100%);
        -webkit-mask-image: linear-gradient(90deg,
                transparent 0, #000 8%,
                #000 calc(50% - 74px), transparent calc(50% - 62px),
                transparent calc(50% + 62px), #000 calc(50% + 74px),
                #000 92%, transparent 100%);
    }
}


/* ============================================================
   27 · THE WORLD
   The corridor from § 4 is not hero decoration any more — it is
   the ground the whole page stands on. One fixed canvas under
   everything, one continuous dark room, and a lens on top of it:
   a veil that keeps the far cloth back, a vignette that closes
   the corners, and grain so the light has a texture to sit in.

   Sections no longer paint themselves opaque. They lay a scrim
   over the corridor instead — heavier where the reading is dense,
   almost nothing where the world is the point.
   Loaded last, so it wins.
   ============================================================ */

/* ---------- the ground ----------
   One room, top to bottom. No section owns a background now, so
   nothing cuts the corridor in half. */
html {
    background: var(--deep);
}

body {
    background: transparent;
    color: var(--on-deep);
}

.world {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    overflow: hidden;
}

.world>* {
    position: absolute;
    inset: 0;
}

/* the canvas fills the viewport and is no longer masked to the
   right of the copy — the corridor runs the width of the screen */
#scene {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    opacity: 1;
    mask-image: none;
    -webkit-mask-image: none;
    z-index: 0;
}

/* without WebGL there is no world to show */
.world {
    display: none;
}

html.has-world .world {
    display: block;
}

/* ---------- the lens ---------- */

/* Only the veil survives in CSS. The vignette and the grain moved
   into the composite shader in § 4 — done there they sit under the
   grade instead of on top of it, and two of each read as dirt on the
   screen rather than as a lens. */
.world-veil {
    background:
        radial-gradient(64% 54% at 18% 44%, rgba(16, 14, 12, .62), transparent 74%),
        linear-gradient(180deg, rgba(16, 14, 12, .48) 0%, transparent 26%, transparent 74%, rgba(16, 14, 12, .58) 100%);
}

.world-vignette,
.world-grain {
    display: none;
}

/* film grain — four steps of a small noise tile, so it flickers
   like stock rather than crawling like a gradient */
.world-grain {
    opacity: .14;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.86' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E");
    background-size: 140px 140px;
    animation: world-grain 1.1s steps(4) infinite;
    will-change: transform;
}

@keyframes world-grain {
    0% {
        transform: translate3d(0, 0, 0);
    }

    25% {
        transform: translate3d(-3%, 2%, 0);
    }

    50% {
        transform: translate3d(2%, -3%, 0);
    }

    75% {
        transform: translate3d(-2%, -2%, 0);
    }

    100% {
        transform: translate3d(0, 0, 0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .world-grain {
        animation: none;
    }
}

/* ---------- the page rides above it ----------
   #top is already fixed at z-index 60 and must stay that way — only
   the flow content needs lifting off the canvas. */
main,
#colophon {
    position: relative;
    z-index: 1;
}

/* the room is dark end to end now, so the bar is too — it no longer
   waits for a section to tell it which half of a dissolve it is on */
#top {
    background: rgba(16, 14, 12, .55);
    color: var(--on-deep);
    border-bottom: 1px solid rgba(255, 255, 255, .07);
}

#top.is-stuck {
    background: rgba(13, 11, 10, .82);
}

/* ---------- the scrims ----------
   Every section is a pane of smoked glass laid over the corridor.
   Dense reading gets a heavier pane; the hero gets none at all,
   because there the world *is* the content. */
.section {
    position: relative;
    isolation: isolate;
    background: transparent;
    /* Reading sections stand on solid ground. A live 3-D scene running at
       half strength behind body copy does not read as depth — it reads as
       a distraction competing with the words. So the pane is nearly
       opaque here, and the compound is let back in at the seams, where
       the mask fades it out top and bottom. You see the world between
       thoughts rather than underneath them. */
    --scrim: .90;
}

.section::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    /* a flat wash, never backdrop-filter: blurring a section-sized
       backdrop over a live canvas pins the compositor. The fog in
       the corridor already does the softening. */
    background: rgba(13, 11, 10, var(--scrim));
    /* the pane fades at both edges so sections dissolve into the
       corridor instead of stacking as eight visible slabs */
    mask-image: linear-gradient(180deg, transparent 0%, #000 14%, #000 86%, transparent 100%);
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 14%, #000 86%, transparent 100%);
}

/* the two sections that are mostly picture let more corridor through */
#work,
#play {
    --scrim: .62;
}

/* Three sections still painted themselves solid at ID strength, which
   beats `.section { background: transparent }` no matter what order the
   rules are in — so the compound was never visible behind the work, the
   process or the play, whatever the scrim was set to. They are panes of
   glass like every other section now. */
#hero,
#work,
#process,
#play {
    background: transparent;
    isolation: isolate;
}

/* the borders between sections were doing the dissolve's job.
   The scrim's soft edge does it better. */
.section+.section {
    border-top: 0;
}

#colophon {
    background: rgba(13, 11, 10, .90);
}

/* ---------- no WebGL: the page still needs a floor ---------- */
html:not(.has-world) .section::before {
    background: var(--deep);
    mask-image: none;
    -webkit-mask-image: none;
}

html:not(.has-world) body {
    background: var(--deep);
}

/* ============================================================
   24 · THE PHONE
   The glass header was tuned against a wide page. On a 390px
   screen the same 42% lets a whole paragraph read through it,
   so the header stops being chrome and starts being noise.
   Everything below is the phone reading of decisions already
   made, not a second design.
   ============================================================ */
@media (max-width: 760px) {
    html {
        font-size: 85%;
    }
    
    /* --- the header has to be a surface, not a tint ---
       At 390px a whole paragraph reads through 10% of transparency,
       and the page is dark end to end anyway, so the bar goes solid
       in every state rather than glass in three of them. */
    #top,
    #top.is-stuck,
    #top.on-deep {
        background: #100E0C;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

    /* the rule under it stops the last line of text from appearing
       to belong to the header */
    #top.is-stuck,
    #top.on-deep {
        border-bottom-color: rgba(255, 255, 255, .10);
    }

    /* --- anchors clear the real header, not an assumed one --- */
    section[id],
    #hero {
        scroll-margin-top: 4.6rem;
    }

    /* --- a keyboard shortcut on a device with no keyboard --- */
    kbd,
    .btn-kbd,
    .ask kbd {
        display: none !important;
    }

    /* the search control loses its shortcut, so it should not keep
       the width the shortcut needed */
    .ask {
        padding-inline: .7rem;
    }

    /* --- the opening line, one notch down --- */
    #title {
        font-size: clamp(2.05rem, 9.4vw, 2.6rem);
    }

    .hero-sub {
        font-size: .98rem;
        line-height: 1.62;
    }

    /* four facts in two columns is right; the labels were wrapping
       to three lines inside a box sized for one */
    .hero-facts .fact span {
        font-size: .68rem;
        line-height: 1.35;
    }

    /* --- the hall caption was covering the work it captions --- */
    .hall-hud-in {
        padding: 1rem 1.05rem 1.1rem;
    }

    .hall-term {
        font-size: 1.28rem;
    }

    .hall-line {
        font-size: .86rem;
        line-height: 1.5;
    }

    .hall-stack {
        font-size: .7rem;
    }

    /* --- the case study is the one place a phone reader stops --- */
    .cs-plain {
        font-size: .96rem;
        padding-left: .8rem;
    }

    .cs-context {
        font-size: .64rem;
        letter-spacing: .08em;
    }
}
