/* ========================
   HOME — every frame of every project, in three views: the ring (A1), the
   piles (A4) and the film strip (A5). home.js places the frames; this sheet
   only says what a frame looks like and how each view is arranged.
   ======================== */

.home {
    background: var(--bg);
    --ui: var(--primary);
    --ui-dim: var(--secondary);
    --ui-faint: rgba(0, 0, 0, 0.12);
}

body.dark .home { --ui-faint: rgba(255, 255, 255, 0.16); }

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* ---- The stage ---- */
.work { position: absolute; inset: 0; }

.work-stage {
    position: absolute;
    inset: 0;
    perspective: 1700px;
    perspective-origin: 50% 47%;
    touch-action: none;
}

.work-cloud {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 0;
    height: 0;
    transform-style: preserve-3d;
}

.work-cloud { transition: transform 0.9s cubic-bezier(0.65, 0, 0.35, 1); }
.work.is-turning .work-cloud, .work.is-settled .work-cloud { transition: none; }

.piece {
    position: absolute;
    left: 0;
    top: 0;
    display: block;
    background: #f1f1ef center / cover no-repeat;
    outline: 1px solid rgba(0, 0, 0, 0.07);
    box-shadow: 0 26px 40px -34px rgba(0, 0, 0, 0.45);
    transform: translate3d(var(--x, 0px), var(--y, 0px), var(--z, 0px))
               rotate(var(--r, 0deg)) scale(calc(var(--s, 1) * var(--lift, 1)));
    opacity: var(--o, 1);
    transition: transform 0.85s cubic-bezier(0.65, 0, 0.35, 1) var(--d, 0ms),
                opacity 0.5s ease var(--d, 0ms),
                outline-color 0.2s ease;
    backface-visibility: hidden;
}

body.dark .piece { background-color: #1a1a1a; outline-color: rgba(255, 255, 255, 0.12); }

/* while the work is riffling past, nothing eases: it cuts */
.work.is-riffling .piece { transition: none !important; }

/* once a move has finished, pointing at something answers quickly */
.work.is-settled .piece {
    transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
                opacity 0.35s ease,
                outline-color 0.2s ease;
}

.piece.is-hover,
.piece:focus-visible {
    --lift: 1.06;
    z-index: 5;
    outline: 1px solid var(--ui);
    opacity: 1 !important;
}

/* ---- PILES: a pile per project, named underneath ---- */
.pile-names {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.4s ease, visibility 0.4s ease;
}

.work[data-mode="piles"] .pile-names { opacity: 1; visibility: visible; }
html.gating .pile-names { opacity: 0; visibility: hidden; }

.pile-name {
    position: absolute;
    z-index: 5;
    display: flex;
    gap: 12px;
    transform: translateX(-50%);
    font-family: var(--font);
    font-size: 11px;
    line-height: 18px;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--ui);
    text-decoration: none;
    white-space: nowrap;
    pointer-events: auto;
    transition: opacity 0.3s ease, left 0.5s ease, top 0.5s ease;
}

.pile-name .dim { color: var(--ui-dim); }
.pile-name.is-open { font-weight: 500; }
.pile-name.is-back { opacity: 0.3; }

/* ---- Readouts (the same layer the project pages use) ---- */
.home .readout {
    position: absolute;
    z-index: 50;
    font-family: var(--font);
    font-size: 10px;
    font-weight: 400;
    line-height: 18px;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--ui);
    white-space: pre;
}

.home-facts {
    top: 52px;
    right: 13px;
    display: grid;
    grid-template-columns: auto auto;
    column-gap: 32px;
}

.home-facts dt { color: var(--ui-dim); }
.home-facts dd { text-align: right; }
.home-facts dd:first-of-type { font-weight: 500; }
.home-facts .live::before {
    content: '';
    display: inline-block;
    width: 6px;
    height: 6px;
    margin-right: 8px;
    border-radius: 50%;
    background: #1DB954;
    vertical-align: 1px;
}

.home-caption {
    position: absolute;
    left: 13px;
    bottom: calc(13px + env(safe-area-inset-bottom, 0px));
    z-index: 50;
    display: flex;
    gap: 16px;
    font-family: var(--font);
    font-size: 12px;
    line-height: 1.144;
    color: var(--ui);
    white-space: nowrap;
}

.home-caption .cap-sub { color: var(--ui-dim); }

/* His name is a link to X, but only while the caption is saying his name */
.home-caption .cap-name { color: inherit; text-decoration: none; pointer-events: none; }
.home-caption.is-link .cap-name { pointer-events: auto; }
.home-caption.is-link .cap-name:hover { text-decoration: underline; text-underline-offset: 3px; }

/* The clock holds the bottom right corner on its own, set tight so the place,
   the date and the time read as one object rather than three readouts. */
.home-clock {
    right: 13px;
    bottom: calc(13px + env(safe-area-inset-bottom, 0px));
    display: flex;
    gap: 9px;
}
/* tabular figures already hold the width, so no reserved box: the gap between
   the date and the time is the same gap as between the place and the date */

/* STACK: the line that opens what is in front, under the deck */
.stack-name {
    left: 50%;
    top: calc(50% + 172px);
    transform: translateX(-50%);
    opacity: 0;
    pointer-events: none;
    color: var(--ui-dim);
    transition: opacity 0.45s ease, color 0.2s ease;
}
.work[data-mode="stack"] .stack-name { opacity: 1; pointer-events: auto; }
.stack-name:hover { color: var(--ui); }

.home-modes {
    bottom: 11px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 22px;
}

/* The row is navigation now: MARK is the index, the other three are pages.
   Links and the current page share one rule so they sit on the same baseline. */
.home-modes button,
.home-modes a,
.home-modes .is-here {
    position: relative;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    letter-spacing: inherit;
    line-height: inherit;
    text-decoration: none;
    color: var(--ui-dim);
    min-width: 4.4em;
    text-align: center;
}
.home-modes button, .home-modes a { cursor: pointer; }

.home-modes button::after,
.home-modes a::after { content: ''; position: absolute; inset: -10px -8px; }
.home-modes button.is-on,
.home-modes .is-here { color: var(--ui); font-weight: 500; }
.home-modes button:hover,
.home-modes a:hover { color: var(--ui); }
.home-modes button:focus-visible,
.home-modes a:focus-visible { outline: 1.5px solid var(--ui); outline-offset: 3px; }

/* ========================
   PHONE
   ======================== */

@media (max-width: 768px) {
    .home-facts { display: none; }
    .home-caption { left: 12px; bottom: 44px; }
    .home-clock { right: 12px; bottom: 44px; gap: 7px; }
    .home-clock .dim { display: none; }
    .home-modes { bottom: 14px; left: 12px; transform: none; gap: 16px; }
    .stack-name { top: auto; bottom: 74px; }
    .pile-name { transform: none; }
}

/* ========================
   THE WAY IN — Stacker (gate.js). The board sits exactly where the front
   square of the cloud sits, and the ghost outline where its back square
   sits, so the finished stack becomes the mark and the work comes out of it.
   ======================== */

/* The site waits behind the game and fades in once the square is built */
html.gating .home .nav,
html.gating .home .readout,
html.gating .home-caption {
    opacity: 0;
    visibility: hidden;
}

.home .nav,
.home .readout,
.home-caption {
    transition: opacity 0.7s ease 0.25s, visibility 0.7s ease 0.25s;
}

/* The game is its own screen: the theme turned inside out, so it doesn't
   read as part of the site until the site arrives. */
.gate {
    --ink: #f2f2f0;
    --paper: #0b0b0b;
    --line: rgba(255, 255, 255, 0.09);
    --ghost: rgba(255, 255, 255, 0.22);
    --cell: 66.6667px;
    position: fixed;
    inset: 0;
    z-index: 40;
    background: var(--paper);
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    transition: opacity 0.8s ease;
}

body.dark .gate {
    --ink: #1c1c1c;
    --paper: #f2f2f0;
    --line: rgba(0, 0, 0, 0.09);
    --ghost: rgba(0, 0, 0, 0.22);
}

.gate-mark {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 0;
    height: 0;
    transition: opacity 0.5s ease;
}

.gate-ring {
    position: absolute;
    left: -110px;
    top: -100px;
    width: 400px;
    height: 400px;
    overflow: visible;
    fill: none;
}

.gate-ring rect { vector-effect: non-scaling-stroke; }
.gate-ring .ghost { stroke: var(--ghost); stroke-dasharray: 4 6; }
.gate-ring .line {
    stroke: var(--ink);
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    transition: stroke-dashoffset 0.6s cubic-bezier(0.65, 0, 0.35, 1);
}

.gate-board {
    position: absolute;
    left: -240px;
    top: -230px;
    width: 400px;
    height: 400px;
    background: var(--paper);
}

.gate-grid {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(to right, var(--line) 1px, transparent 1px),
        linear-gradient(to bottom, var(--line) 1px, transparent 1px);
    background-size: var(--cell) var(--cell);
    box-shadow: inset -1px -1px 0 var(--line);
    transition: opacity 0.4s ease;
}

.gate-block {
    position: absolute;
    left: calc(var(--c) * var(--cell));
    bottom: calc(var(--r) * var(--cell));
    width: var(--cell);
    height: var(--cell);
}

.gate-block::before {
    content: '';
    position: absolute;
    inset: 4px;
    background: var(--ink);
    transition: inset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
}

.gate-block.is-falling {
    animation: gate-fall 0.75s cubic-bezier(0.55, 0, 1, 0.45) forwards;
}

@keyframes gate-fall {
    to {
        transform: translateY(calc((var(--r) + 3) * var(--cell))) rotate(var(--spin, 12deg));
        opacity: 0;
    }
}

.gate-block.is-fill::before {
    animation: gate-pop 0.32s cubic-bezier(0.16, 1, 0.3, 1) var(--delay, 0ms) both;
}

@keyframes gate-pop {
    from { transform: scale(0); }
}

/* The square resolves: gaps close, the grid goes, the outline draws */
.gate.is-solid .gate-block::before { inset: -0.5px; }
.gate.is-solid .gate-grid { opacity: 0; }
.gate.is-solid .gate-ring .ghost { opacity: 0; }
.gate.is-solid .gate-ring .line { stroke-dashoffset: 0; }

/* Done: the whole screen dissolves and the work is underneath */
.gate.is-leaving {
    opacity: 0;
    pointer-events: none;
}

.gate-readout {
    position: absolute;
    font-family: var(--font);
    font-size: 10px;
    font-weight: 400;
    line-height: 18px;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
    white-space: pre;
}

.gate-readout .dim { opacity: 0.5; }

.gate-status { top: 52px; left: 13px; }
.gate-load { top: 52px; right: 13px; text-align: right; }
.gate-hint { bottom: 11px; left: 50%; transform: translateX(-50%); }

@media (max-width: 768px) {
    .gate-status { top: 44px; left: 12px; }
    .gate-load { top: 44px; right: 12px; }
    .gate-hint { bottom: 14px; left: 50%; transform: translateX(-50%); }
}

/* ========================
   WAYS IN — the vault, the drive and the keycard (gates.js). Each one owns
   the whole screen and carries nothing but itself: no nav, no name, no
   caption, only the words the game needs.
   ======================== */

.entry {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: #0c0e10;
    color: #e6e9ea;
    font-family: var(--font);
    user-select: none;
    -webkit-user-select: none;
    touch-action: none;
    transition: opacity 0.8s ease;
}

.entry.is-leaving { opacity: 0; pointer-events: none; }

.entry-top,
.entry-hint {
    position: absolute;
    pointer-events: none;
    left: 0;
    right: 0;
    text-align: center;
    font-size: 11px;
    letter-spacing: 0.18em;
    color: #7d858b;
}

.entry-top { top: 90px; }
.entry-hint { bottom: 80px; color: #9aa3a8; }
.entry.is-open .entry-top { color: #35d07f; }

/* ---- the vault ---- */
.vault {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 620px;
    height: 620px;
    margin: -300px 0 0 -310px;
    transition: transform 1s cubic-bezier(0.6, 0, 0.9, 0.4), opacity 0.9s ease;
}

.entry--vault.is-open .vault { transform: scale(7); opacity: 0; }

.vault-panel {
    position: absolute;
    left: 50%;
    top: -74px;
    width: 300px;
    margin-left: -150px;
    padding: 12px 0;
    text-align: center;
    background: #06080a;
    border-radius: 4px;
    font-size: 20px;
    font-weight: 500;
    letter-spacing: 0.3em;
    color: #63d68a;
}

.vault-door { position: absolute; inset: 0; border-radius: 50%; }

.vault-plate {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: linear-gradient(145deg, #e2e5e7 0%, #b4babe 52%, #6f767b 100%);
    box-shadow: 0 30px 70px -10px rgba(0, 0, 0, 0.6);
}

.vault-ring {
    position: absolute;
    inset: 35px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.45);
    background: #dcdfe1;
}

.vault-ticks { position: absolute; inset: 78px; border-radius: 50%; }

.vault-ticks i {
    position: absolute;
    left: 50%;
    top: 0;
    width: 2px;
    height: 10px;
    margin-left: -1px;
    background: #9aa1a6;
    transform-origin: 50% 232px;
    transform: rotate(var(--a));
}

.vault-ticks i.long { height: 18px; background: #2b3034; width: 3px; }

.vault-near {
    position: absolute;
    inset: 60px;
    border-radius: 50%;
    border: 3px solid #35d07f;
    opacity: 0;
    transition: opacity 0.12s linear;
}

.vault-wheel { position: absolute; inset: 0; }

.vault-wheel i {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 18px;
    height: 320px;
    margin: -160px 0 0 -9px;
    border-radius: 9px;
    background: linear-gradient(#b7bdc1, #8f969b);
}

.vault-wheel i:nth-child(1) { transform: rotate(0deg); }
.vault-wheel i:nth-child(2) { transform: rotate(45deg); }
.vault-wheel i:nth-child(3) { transform: rotate(90deg); }
.vault-wheel i:nth-child(4) { transform: rotate(135deg); }

.vault-wheel b {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 120px;
    height: 120px;
    margin: -60px 0 0 -60px;
    border-radius: 50%;
    background: radial-gradient(circle at 36% 32%, #eef0f1, #8b9297);
    box-shadow: inset 0 0 0 10px rgba(255, 255, 255, 0.25);
}

.vault-pointer {
    position: absolute;
    left: 50%;
    top: 8px;
    width: 4px;
    height: 24px;
    margin-left: -2px;
    border-radius: 2px;
    background: #1a1a1a;
}

.entry.is-near .vault-pointer { background: #35d07f; }
.entry.is-click .vault-pointer { box-shadow: 0 0 22px 6px rgba(53, 208, 127, 0.8); }

.vault-bolts { position: absolute; inset: 0; }

.vault-bolts i {
    position: absolute;
    left: 50%;
    top: 14px;
    width: 18px;
    height: 18px;
    margin-left: -9px;
    border-radius: 50%;
    background: #8e959a;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
    transform-origin: 50% 296px;
    transform: rotate(var(--a));
}

.entry--vault.is-open .vault-bolts i { background: #35d07f; }

.vault-num {
    position: absolute;
    left: 0;
    right: 0;
    bottom: -56px;
    text-align: center;
    font-size: 13px;
    letter-spacing: 0.3em;
    color: #6e767b;
}

/* ---- the drive ---- */
.drive {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 420px;
    height: 150px;
    margin: -180px 0 0 -210px;
    cursor: grab;
    transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}

.drive.is-held { cursor: grabbing; transition: none; }

.drive-conn {
    position: absolute;
    left: 0;
    top: 32px;
    width: 126px;
    height: 86px;
    border-radius: 3px;
    background: linear-gradient(#cfd4d7, #9aa1a6);
}

.drive-conn::after {
    content: '';
    position: absolute;
    inset: 12px 14px;
    background: #6d7479;
    border-radius: 2px;
}

.drive-body {
    position: absolute;
    left: 116px;
    top: 0;
    right: 0;
    bottom: 0;
    border-radius: 6px;
    background: linear-gradient(160deg, #23262a, #121417);
    box-shadow: 0 22px 44px -18px rgba(0, 0, 0, 0.9);
}

.drive-mark { position: absolute; left: 36px; top: 46px; width: 26px; height: 26px; background: #3a3f44; }
.drive-mark::after { content: ''; position: absolute; left: 10px; top: 10px; width: 26px; height: 26px; border: 1.5px solid #3a3f44; }
.drive-led { position: absolute; right: 30px; top: 66px; width: 12px; height: 12px; border-radius: 50%; background: #3a3f44; }
.entry--drive.is-seated .drive-led { background: #35d07f; box-shadow: 0 0 16px 3px rgba(53, 208, 127, 0.7); }

.port {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 240px;
    height: 70px;
    margin: 110px 0 0 -120px;
    border-radius: 5px;
    background: #15181b;
    box-shadow: inset 0 0 0 2px #2a2f34;
    transition: box-shadow 0.2s ease;
}

.port i { position: absolute; inset: 14px 18px; background: #0a0c0e; border-radius: 3px; }
.entry--drive.is-near .port { box-shadow: inset 0 0 0 2px #35d07f; }
.entry--drive.is-seated .drive,
.entry--drive.is-seated .port { opacity: 0; transition: opacity 0.4s ease; }

.pass {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 420px;
    margin: -60px 0 0 -210px;
    text-align: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.5s ease 0.25s;
}

.entry--drive.is-seated .pass { opacity: 1; }
.entry--drive.is-mounting .pass { opacity: 0; transition-delay: 0s; }
.pass-label { font-size: 11px; letter-spacing: 0.2em; color: #6e767b; }
.pass-dots { margin-top: 22px; font-size: 24px; letter-spacing: 0.3em; min-height: 34px; }
.pass-dots b { display: inline-block; width: 10px; height: 24px; background: #e6e9ea; vertical-align: -4px; animation: caret 1s steps(1) infinite; }
@keyframes caret { 50% { opacity: 0; } }

.mount {
    position: absolute;
    pointer-events: none;
    left: 50%;
    top: 130px;
    width: 620px;
    margin-left: -310px;
    height: 600px;
    overflow: hidden;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.entry--drive.is-mounting .mount { opacity: 1; }
.mount-list { transition: transform 0.2s linear; }
.mount-list p { display: flex; justify-content: space-between; font-size: 13px; line-height: 26px; color: #5a6268; }
.mount-list p:nth-last-child(-n+4) { color: #e6e9ea; }
.mount-list p:nth-last-child(-n+4) span:last-child { color: #35d07f; }
.mount-bar { position: absolute; left: 0; right: 0; bottom: 30px; height: 2px; background: #1e2226; }
.mount-bar i { display: block; height: 2px; width: 0; background: #35d07f; transition: width 0.12s linear; }

/* ---- the keycard ---- */
.reader {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 230px;
    height: 360px;
    margin: -180px 0 0 60px;
    border-radius: 10px;
    background: #17191c;
    box-shadow: inset 0 0 0 1px #24282c;
}

.reader-slot {
    position: absolute;
    left: 26px;
    right: 26px;
    top: 34px;
    height: 26px;
    border-radius: 3px;
    background: #08090a;
    box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.9);
}

.reader-led {
    position: absolute;
    left: 50%;
    top: 108px;
    width: 16px;
    height: 16px;
    margin-left: -8px;
    border-radius: 50%;
    background: #ff4d4d;
}

.entry--card.is-open .reader-led { background: #35d07f; box-shadow: 0 0 18px 4px rgba(53, 208, 127, 0.7); }
.entry--card.is-error .reader-led { animation: err 0.18s steps(2) 4; }
@keyframes err { 50% { opacity: 0.2; } }

.reader-state {
    position: absolute;
    left: 0;
    right: 0;
    top: 140px;
    text-align: center;
    font-size: 10px;
    letter-spacing: 0.2em;
    color: #6e767b;
}

.card {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 300px;
    height: 188px;
    margin: -94px 0 0 -420px;
    border-radius: 7px;
    background: #ececeb;
    color: #1a1a1a;
    cursor: grab;
    box-shadow: 0 26px 46px -18px rgba(0, 0, 0, 0.7);
    transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

.card.is-held { cursor: grabbing; transition: none; }
.card-mark { position: absolute; left: 26px; top: 26px; width: 30px; height: 30px; background: #262626; }
.card-mark::after { content: ''; position: absolute; left: 11px; top: 11px; width: 30px; height: 30px; border: 1.5px solid #262626; }
.card-name { position: absolute; left: 26px; bottom: 46px; font-size: 12px; letter-spacing: 0.14em; }
.card-sub { position: absolute; left: 26px; bottom: 26px; font-size: 10px; letter-spacing: 0.14em; color: #8a8a88; }
.card-stripe { position: absolute; left: 0; right: 0; top: 78px; height: 26px; background: #1c1c1b; }

.entry--card.is-open .card { transform: translate(120px, 420px); opacity: 0; }
.entry--card.is-open .reader { transform: scale(4); opacity: 0; transition: transform 1s cubic-bezier(0.6, 0, 0.9, 0.4), opacity 0.9s ease; }

@media (max-width: 768px) {
    .vault { transform: scale(0.62); }
    .drive { transform: scale(0.72); }
    .card, .reader { transform: scale(0.7); }
    .card { margin-left: -280px; }
    .reader { margin-left: 40px; }
    .mount { width: 340px; margin-left: -170px; }
}

/* ========================================================================
   THE DRIVE — ?game=drive. Plug it in, it glows, it copies, the work
   shoots out. No password: it is in, so it opens. ?drive=lens is the
   acrylic block, ?drive=blank the blackened steel.
   ======================================================================== */
.entry--drive {
    --lit: 140, 215, 255;
    --in: 0;                     /* 0 to 1, how far it has gone in */
    background: #050607;
    overflow: hidden;
}

/* the object ---------------------------------------------------------- */
.dv {
    position: absolute;
    left: 50%;
    top: 84px;
    width: 92px;
    z-index: 3;
    cursor: grab;
    touch-action: none;
    transform: translate(calc(-50% + var(--dx, 0px)), var(--dy, 0px));
}
.dv.is-held { cursor: grabbing; }
.dv-body { position: relative; display: block; height: 264px; border-radius: 5px; }
.dv-tongue {
    position: absolute;
    left: 50%; top: 264px;
    width: 54px; height: 74px;
    margin-left: -27px;
    border-radius: 0 0 2px 2px;
    background: linear-gradient(90deg, #83888b 0%, #d4d8da 45%, #7d8285 100%);
}
.dv-tongue::before,
.dv-tongue::after {
    content: '';
    position: absolute;
    left: 8px; right: 8px;
    height: 7px;
    background: rgba(18, 20, 22, 0.55);
}
.dv-tongue::before { top: 15px; }
.dv-tongue::after { top: 36px; }

/* 05 the lens: a solid acrylic block with the mark suspended inside */
.dv-lens .dv-body {
    background: linear-gradient(100deg, rgba(190,212,224,0.5) 0%, rgba(116,142,158,0.32) 34%, rgba(214,233,243,0.48) 66%, rgba(100,122,138,0.4) 100%);
    border: 1px solid rgba(255, 255, 255, 0.42);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.75), inset 0 -1px 0 rgba(0,0,0,0.3), 0 22px 44px -18px rgba(0,0,0,0.85);
}
.dv-lens .dv-mark {
    position: absolute;
    left: 20px; top: 52px;
    width: 30px; height: 30px;
    background: rgba(246, 251, 255, 0.92);
}
.dv-lens .dv-mark::after {
    content: '';
    position: absolute;
    left: 11px; top: 11px;
    width: 30px; height: 30px;
    border: 1.5px solid rgba(246, 251, 255, 0.72);
}
.dv-lens .dv-etch { display: none; }

/* 04 the blank: blackened steel, one etched serial, no mark at all */
.dv-blank .dv-body {
    background: linear-gradient(100deg, #26282a 0%, #4b4e51 22%, #2b2e30 56%, #171a1c 100%);
    border: 1px solid rgba(255, 255, 255, 0.10);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.22), inset 0 -1px 0 rgba(0,0,0,0.65), 0 22px 44px -18px rgba(0,0,0,0.9);
}
.dv-blank .dv-mark { display: none; }
.dv-blank .dv-etch {
    position: absolute;
    left: 0; right: 0; top: 148px;
    text-align: center;
    font-family: var(--font);
    font-size: 8px;
    letter-spacing: 0.24em;
    color: rgba(255, 255, 255, 0.26);
}

.dv-led {
    position: absolute;
    left: 50%; bottom: 24px;
    width: 18px; height: 5px;
    margin-left: -9px;
    border-radius: 3px;
    background: rgba(255, 255, 255, 0.2);
    transition: background 0.3s ease, box-shadow 0.3s ease;
}
.entry--drive.is-home .dv-led,
.entry--drive.is-glow .dv-led,
.entry--drive.is-copy .dv-led {
    background: rgb(var(--lit));
    box-shadow: 0 0 22px 3px rgba(var(--lit), 0.9);
}

/* the slot ------------------------------------------------------------- */
.dv-port {
    position: absolute;
    left: 50%; bottom: 0;
    width: 260px; height: 118px;
    margin-left: -130px;
    z-index: 4;
    cursor: pointer;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-bottom: 0;
    border-radius: 6px 6px 0 0;
    background: #14161a;
    transition: border-color 0.3s ease;
}
.entry--drive.is-near .dv-port { border-color: rgba(var(--lit), 0.5); }
.dv-mouth {
    position: absolute;
    left: 50%; top: 14px;
    width: 124px; height: 26px;
    margin-left: -62px;
    border-radius: 3px;
    background: #040506;
    transition: box-shadow 0.35s ease;
}
.entry--drive.is-near .dv-mouth { box-shadow: 0 0 46px 4px rgba(var(--lit), 0.55); }
.dv-seam {
    position: absolute;
    left: 50%; top: 16px;
    width: 100px; height: 5px;
    margin-left: -50px;
    border-radius: 2px;
    background: rgb(var(--lit));
    box-shadow: 0 0 40px 6px rgba(var(--lit), 0.95);
    opacity: 0;
    transition: opacity 0.3s ease;
}
.entry--drive.is-home .dv-seam { opacity: 1; }

/* the light that leaks up out of the slot as it goes in */
.dv-spill {
    position: absolute;
    left: 50%; bottom: 30px;
    width: 760px; height: 480px;
    margin-left: -380px;
    z-index: 2;
    pointer-events: none;
    background: radial-gradient(ellipse 50% 58% at 50% 100%, rgba(var(--lit),0.5) 0%, rgba(var(--lit),0.15) 42%, transparent 72%);
    opacity: calc(var(--in) * 0.85);
    transition: opacity 0.35s ease;
}
.entry--drive.is-home .dv-spill,
.entry--drive.is-glow .dv-spill { opacity: 1; }
.entry--drive.is-copy .dv-spill { opacity: 0.3; }

/* mounted: half a second where the screen takes the colour of the drive */
.dv-mounted {
    position: absolute;
    left: 0; right: 0; top: 33%;
    z-index: 5;
    text-align: center;
    font-family: var(--font);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.35s ease;
}
.entry--drive.is-glow .dv-mounted { opacity: 1; }
.dv-mounted h2 {
    margin: 14px 0;
    font-size: 46px;
    font-weight: 500;
    letter-spacing: 0.06em;
    color: #fff;
    text-shadow: 0 0 44px rgba(var(--lit), 0.85);
}
.dv-vol,
.dv-sub { font-size: 12px; letter-spacing: 0.16em; color: rgba(165, 208, 228, 0.85); }

/* the copy: twenty-one real frames landing in order */
.dv-grid {
    position: absolute;
    left: 50%; top: 50%;
    width: 1008px; height: 300px;
    margin: -176px 0 0 -504px;
    z-index: 5;
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    grid-auto-rows: 92px;
    gap: 12px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}
.entry--drive.is-copy .dv-grid,
.entry--drive.is-shoot .dv-grid { opacity: 1; }
.dv-grid i {
    border: 1px solid rgba(255, 255, 255, 0.14);
    background: #0b0d10 center / cover no-repeat;
    opacity: 0;
    transform: translateY(14px) scale(0.94);
    transition: opacity 0.3s ease, transform 0.35s cubic-bezier(0.2, 0.9, 0.3, 1), box-shadow 0.3s ease, border-color 0.3s ease;
}
.dv-grid i.is-in { opacity: 1; transform: none; }
.dv-grid i.is-front {
    border-color: rgba(var(--lit), 0.85);
    box-shadow: 0 0 34px 4px rgba(var(--lit), 0.55);
}
.entry--drive.is-shoot .dv-grid i {
    transition: transform 0.9s cubic-bezier(0.42, 0, 0.6, 1) var(--sd, 0ms), opacity 0.9s ease var(--sd, 0ms);
    transform: translate(var(--sx, 0px), -125vh) scale(2.6);
    opacity: 0;
}
.entry--drive.is-seating .entry-hint,
.entry--drive.is-glow .entry-hint,
.entry--drive.is-copy .entry-hint { opacity: 0; }

@media (max-width: 1080px) {
    .dv-grid { width: 92vw; margin-left: -46vw; grid-auto-rows: 8vw; }
}
@media (max-width: 768px) {
    .dv { top: 56px; width: 74px; }
    .dv-body { height: 210px; }
    .dv-tongue { top: 210px; width: 44px; margin-left: -22px; height: 60px; }
    .dv-port { width: 200px; margin-left: -100px; height: 96px; }
    .dv-mouth { width: 96px; margin-left: -48px; }
    .dv-mounted h2 { font-size: 30px; }
    .dv-grid { grid-template-columns: repeat(3, 1fr); grid-auto-rows: 17vw; height: auto; margin-top: -26vw; }
}

/* ========================================================================
   STACKER, LIT — ?look=led (the default) draws the same game as a lit
   board: bone white has landed, blue is still in play, hairline is still
   owed, and every row you land draws another sixth of the mark behind.
   ?look=flat is the original.
   ======================================================================== */
.gate.is-led {
    --lit: 140, 215, 255;
    --paper: #050a0d;
    --ink: #f2f7fa;
    --line: rgba(120, 200, 235, 0.10);
    --ghost: rgba(120, 200, 235, 0.14);
    --live: rgb(var(--lit));
    background: radial-gradient(ellipse 70% 55% at 50% 46%, #071119 0%, #04080b 70%);
}
body.dark .gate.is-led {
    --lit: 140, 215, 255;
    --paper: #050a0d;
    --ink: #f2f7fa;
    --line: rgba(120, 200, 235, 0.10);
    --ghost: rgba(120, 200, 235, 0.14);
}

/* the board is a dark panel, not a white one */
.gate.is-led .gate-board { background: rgba(10, 20, 26, 0.55); }

/* landed blocks: bone white with a soft bloom */
.gate.is-led .gate-block::before {
    inset: 3px;
    background: var(--ink);
    box-shadow: 0 0 22px 1px rgba(var(--lit), 0.45);
}

/* the row still moving: blue, and brighter than anything else */
.gate.is-led .gate-block.is-live::before {
    background: var(--live);
    box-shadow: 0 0 46px 6px rgba(var(--lit), 0.85);
}

/* the outline behind answers each landed row */
.gate.is-led .gate-ring .line {
    stroke: var(--live);
    stroke-dashoffset: calc(1 - var(--ring, 0));
    filter: drop-shadow(0 0 6px rgba(var(--lit), 0.7));
}
.gate.is-led .gate-ring .ghost { stroke-dasharray: 2 7; }

/* the finish: everything comes up to full and the whole board blooms once */
.gate.is-led.is-solid .gate-block::before {
    background: #ffffff;
    box-shadow: 0 0 40px 6px rgba(var(--lit), 0.75);
}
.gate.is-led.is-solid .gate-ring .line {
    stroke: #ffffff;
    stroke-dashoffset: 0;
    filter: drop-shadow(0 0 14px rgba(var(--lit), 0.95));
}
.gate.is-led.is-solid { background: radial-gradient(ellipse 80% 65% at 50% 46%, #0d2230 0%, #04080b 75%); }

/* the readouts pick up the same light */
.gate.is-led .gate-readout { color: rgb(var(--lit)); }
.gate.is-led .gate-readout .dim { color: rgba(150, 200, 225, 0.55); }

/* ========================================================================
   STACKER, DAY — the default. The game hands over to a white site, so the
   game is white: it is the same sheet of paper the whole way through and
   nothing inverts when the work arrives. No colour at all. Solid has
   landed, hollow is still in play, hairline is still owed.
   ======================================================================== */
.gate.is-day {
    --paper: transparent;
    --ink: #111111;
    --line: rgba(0, 0, 0, 0.07);
    --ghost: rgba(0, 0, 0, 0.16);
    background: #f2f2f0;
}
body.dark .gate.is-day {
    --ink: #f2f2f0;
    --line: rgba(255, 255, 255, 0.08);
    --ghost: rgba(255, 255, 255, 0.18);
    background: #0b0b0b;
}
.gate.is-day .gate-board { background: transparent; }
.gate.is-day .gate-block::before { inset: 3px; background: var(--ink); }

/* the row still in play is an outline, so you can see what you are about
   to keep before you keep it */
.gate.is-day .gate-block.is-live::before {
    inset: 4px;
    background: transparent;
    box-shadow: inset 0 0 0 2px var(--ink);
}
.gate.is-day .gate-ring .line {
    stroke: var(--ink);
    stroke-dashoffset: calc(1 - var(--ring, 0));
}
.gate.is-day.is-solid .gate-block::before { inset: -0.5px; background: var(--ink); }
.gate.is-day.is-solid .gate-ring .line { stroke-dashoffset: 0; }

/* the crosshair is already the right colour for a white screen, so the
   gate's inversion has to be undone for this look */
html.gating[data-look="day"] .xhair-cross::before,
html.gating[data-look="day"] .xhair-cross::after { background: #111; }
html.gating[data-look="day"] .xhair-cross { filter: drop-shadow(0 0 1px rgba(255, 255, 255, 0.6)); }
html.gating[data-look="day"] .xhair-label { background: #111; color: #fff; }
html.gating[data-look="day"] body.dark .xhair-cross::before,
html.gating[data-look="day"] body.dark .xhair-cross::after { background: #f2f2f0; }
html.gating[data-look="day"] body.dark .xhair-label { background: #f2f2f0; color: #111; }

/* the light on the lit board, switchable: ?tint=blue (default), white, warm */
.entry--drive.tint-white,
.gate.is-led.tint-white { --lit: 236, 240, 244; }
.entry--drive.tint-warm,
.gate.is-led.tint-warm { --lit: 255, 196, 128; }
.entry--drive.tint-green,
.gate.is-led.tint-green { --lit: 150, 245, 190; }

/* What the copy screen is made of. The work stays hidden until it has
   actually arrived, so ?copy=blocks (the default) fills twenty-one solid
   blocks, names fills filenames, bar fills numbers, frames is the old one
   that showed the projects early. */
.entry--drive.cp-blocks .dv-grid i { background: rgba(var(--lit), 0.16); border-color: rgba(var(--lit), 0.22); }
.entry--drive.cp-blocks .dv-grid i.is-in { background: #eef4f7; border-color: rgba(255, 255, 255, 0.5); }

.entry--drive.cp-names .dv-grid i,
.entry--drive.cp-bar .dv-grid i {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 8px;
    font-family: var(--font);
    font-size: 9px;
    letter-spacing: 0.08em;
    text-align: center;
    color: rgba(var(--lit), 0.55);
    background: transparent;
}
.entry--drive.cp-names .dv-grid i.is-in,
.entry--drive.cp-bar .dv-grid i.is-in { color: #eef4f7; border-color: rgba(var(--lit), 0.45); }
.entry--drive.cp-bar .dv-grid i { font-size: 13px; letter-spacing: 0.12em; }

/* The day board, in ink only. No colour and no blur: a landed block is solid,
   the block still in play is the same block hollowed out, and what is still
   owed is a hairline. Three states, one material, nothing soft anywhere.
   ?tint=blue / green / warm put the light back if it is ever wanted. */
.gate.is-day .gate-block::before {
    inset: 3px;
    background: var(--ink);
    box-shadow: none;
}
.gate.is-day .gate-block.is-live::before {
    inset: 3px;
    background: transparent;
    box-shadow: inset 0 0 0 3px var(--ink);
}
.gate.is-day .gate-ring .line {
    stroke: var(--ink);
    filter: none;
}
.gate.is-day.is-solid .gate-block::before {
    inset: -0.5px;
    background: var(--ink);
    box-shadow: none;
}
.gate.is-day.is-solid .gate-ring .line { stroke: var(--ink); filter: none; }
.gate.is-day.is-solid { background: #f2f2f0; }

/* and the same board with the light put back, for comparison */
.gate.is-day.tint-blue  { --lit: 18, 152, 226; }
.gate.is-day.tint-green { --lit: 29, 185, 84; }
.gate.is-day.tint-warm  { --lit: 226, 120, 30; }
.gate.is-day.tint-blue .gate-block.is-live::before,
.gate.is-day.tint-green .gate-block.is-live::before,
.gate.is-day.tint-warm .gate-block.is-live::before {
    background: rgb(var(--lit));
    box-shadow: 0 0 0 1px rgba(var(--lit), 0.55), 0 0 34px 6px rgba(var(--lit), 0.45);
}
.gate.is-day.tint-blue .gate-ring .line,
.gate.is-day.tint-green .gate-ring .line,
.gate.is-day.tint-warm .gate-ring .line {
    stroke: rgb(var(--lit));
    filter: drop-shadow(0 0 7px rgba(var(--lit), 0.45));
}

/* The mounting page: the volume copying across, one line at a time. */
.dv-list {
    position: absolute;
    left: 50%; top: 50%;
    width: 760px; height: 340px;
    margin: -210px 0 0 -380px;
    z-index: 5;
    overflow: hidden;
    opacity: 0;
    pointer-events: none;
    font-family: var(--font);
    transition: opacity 0.35s ease;
}
.entry--drive.is-list .dv-list { opacity: 1; }
.dv-rows { position: absolute; left: 0; right: 0; top: 0; transition: transform 0.24s cubic-bezier(0.2, 0.9, 0.3, 1); }
.dv-rows p {
    display: grid;
    grid-template-columns: 34px 1fr 90px 28px;
    align-items: center;
    height: 26px;
    margin: 0;
    font-size: 11px;
    letter-spacing: 0.06em;
    color: rgba(var(--lit), 0.75);
    opacity: 0;
    transform: translateY(6px);
    transition: opacity 0.22s ease, transform 0.22s ease;
}
.dv-rows p.is-in { opacity: 1; transform: none; }
.dv-rows p:last-child { color: #eef4f7; }
.dv-rows .dv-f { color: inherit; }
.dv-rows .dv-s { text-align: right; opacity: 0.6; }
.dv-rows .dv-ok { text-align: right; opacity: 0.45; }
.dv-bar {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 2px;
    background: rgba(255, 255, 255, 0.12);
}
.dv-bar i {
    display: block;
    width: 0;
    height: 100%;
    background: rgb(var(--lit));
    box-shadow: 0 0 18px 2px rgba(var(--lit), 0.7);
    transition: width 0.12s linear;
}
@media (max-width: 860px) {
    .dv-list { width: 88vw; margin-left: -44vw; }
    .dv-rows p { grid-template-columns: 28px 1fr 64px; font-size: 10px; }
    .dv-rows .dv-ok { display: none; }
}

/* the sound switch, in with the other readouts */
.gate-snd {
    display: block;
    margin-top: 6px;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    letter-spacing: inherit;
    color: inherit;
    opacity: 0.45;
    cursor: pointer;
    text-align: right;
    width: 100%;
    transition: opacity 0.2s ease;
}
.gate-snd:hover { opacity: 1; }
.gate.is-solid .gate-snd, .gate.is-leaving .gate-snd { opacity: 0; }

/* the mark in the corner is a mark now, not a switch */
.theme-toggle.is-static { pointer-events: none; cursor: default; }

/* The handover: the square you built shrinks to the size the mark sits at on
   the home page and the work comes out of it, rather than one square fading
   out while a different one fades in. */
.gate.is-handing .gate-mark {
    transition: transform 0.78s cubic-bezier(0.62, 0, 0.3, 1);
}
.gate.is-handing .gate-grid { opacity: 0; }
.gate.is-leaving { transition: opacity 0.55s ease; }

/* ========================================================================
   THE LOAD — the mark drawing itself while the work comes down. It is the
   same shape the site rests in, so nothing is replaced when it finishes:
   the square is already there, it just stops being the only thing.
   ======================================================================== */
.load {
    position: fixed;
    inset: 0;
    z-index: 40;
    background: var(--bg);
    transition: opacity 0.6s ease;
}
.load.is-leaving { opacity: 0; pointer-events: none; }

.load-mark {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 0;
    height: 0;
}
.load-ring,
.load-box {
    position: absolute;
    display: block;
    width: 168px;
    height: 168px;
}
/* the outline square, behind and offset, exactly as the mark is drawn */
.load-ring {
    left: -22px;
    top: -22px;
    border: 1.5px solid var(--primary);
    opacity: 0.22;
    transition: opacity 0.45s ease;
}
.load-box {
    left: -146px;
    top: -146px;
    overflow: hidden;
    box-shadow: inset 0 0 0 1.5px rgba(0, 0, 0, 0.10);
}
body.dark .load-box { box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.14); }
.load-box b {
    position: absolute;
    inset: 0;
    display: block;
    background: var(--primary);
    transform: scaleY(0);
    transform-origin: 50% 100%;
}
.load.is-full .load-ring { opacity: 0.9; }

@media (prefers-reduced-motion: reduce) {
    .load-box b { transform: scaleY(1) !important; }
}
@media (max-width: 768px) {
    .load-ring, .load-box { width: 116px; height: 116px; }
    .load-ring { left: -16px; top: -16px; }
    .load-box { left: -100px; top: -100px; }
}

/* On a phone the cards are one column that scrolls, and the switch and the clock
   float over it — so a card slides under them and the controls read as something
   dropped on the artwork rather than as chrome. A fade from the page colour gives
   them a floor. Desktop does not need it: there the rail is horizontal and the
   corners are empty. */
@media (max-width: 860px) {
    .home::after {
        content: "";
        position: absolute; left: 0; right: 0; bottom: 0;
        height: calc(92px + env(safe-area-inset-bottom, 0px));
        z-index: 40; pointer-events: none;
        background: linear-gradient(to top, var(--bg, #fff) 38%, rgba(255, 255, 255, 0) 100%);
    }
    body.dark .home::after {
        background: linear-gradient(to top, #0a0a0a 38%, rgba(10, 10, 10, 0) 100%);
    }
    .mode-switch, .home-clock { z-index: 50; }
}

/* ---------------------------------------------------------------------------
   The section switch. It replaces two things at once: the name that sat in the
   bottom-left corner and did nothing but link to Contact, and the pair of words
   centred along the bottom edge that were the only way into either collection
   and were the smallest type on the page.

   Two icons — a phone and a browser window, which is what the two collections
   actually are. The one you are in carries the pill. Point at the other and the
   pill slides over to meet it before you have committed to anything; take the
   pointer away and it slides back. The move is the affordance: it says these
   two are one control and you are on one side of it. */
.mode-switch {
    position: absolute;
    left: 13px;
    /* the inset clears the home indicator; without it the pill's lower half is under the
       bar on a notched iPhone and only the top of it takes a tap */
    bottom: calc(13px + env(safe-area-inset-bottom, 0px));
    z-index: 50;
    display: flex;
    align-items: center;
    gap: 0;
    padding: 3px;
    border-radius: 999px;
    background: var(--ui-well, rgba(0, 0, 0, 0.05));
    isolation: isolate;
}
body.dark .mode-switch { background: rgba(255, 255, 255, 0.08); }

.mode-switch a {
    position: relative;
    z-index: 2;
    width: 34px;
    height: 26px;
    display: grid;
    place-items: center;
    border-radius: 999px;
    color: var(--ui-dim);
    transition: color 0.26s cubic-bezier(0.32, 0.72, 0, 1);
}
.mode-switch a svg { width: 15px; height: 15px; display: block; }
.mode-switch a[aria-current="page"] { color: var(--bg, #fff); }
body.dark .mode-switch a[aria-current="page"] { color: #101112; }
.mode-switch a:not([aria-current="page"]):hover { color: var(--ui); }

/* one pill, moved rather than redrawn, so it reads as a single object sliding */
.mode-switch .pill {
    position: absolute;
    z-index: 1;
    top: 3px;
    bottom: 3px;
    left: 3px;
    width: 34px;
    border-radius: 999px;
    background: var(--ui);
    transition: transform 0.34s cubic-bezier(0.32, 0.72, 0, 1);
}
.mode-switch[data-at="1"] .pill { transform: translateX(34px); }
.mode-switch[data-at="2"] .pill { transform: translateX(68px); }
/* The tease: the pill goes to meet whichever icon is under the pointer. The pill
   is the first child, so the two links are nth-child 2 and 3 — counting them as
   1 and 2 sent the pill a full slot past the end of the well. */
.mode-switch:has(a:nth-child(2):hover) .pill { transform: none; }
.mode-switch:has(a:nth-child(3):hover) .pill { transform: translateX(34px); }
.mode-switch:has(a:nth-child(4):hover) .pill { transform: translateX(68px); }

/* and the icon the pill has arrived under inverts with it. Every other icon goes
   dim, whichever one was carrying the pill a moment ago. */
.mode-switch:has(a:hover) a { color: var(--ui-dim); }
.mode-switch:has(a:nth-child(2):hover) a:nth-child(2),
.mode-switch:has(a:nth-child(3):hover) a:nth-child(3),
.mode-switch:has(a:nth-child(4):hover) a:nth-child(4) { color: var(--bg, #fff); }
body.dark .mode-switch:has(a:nth-child(2):hover) a:nth-child(2),
body.dark .mode-switch:has(a:nth-child(3):hover) a:nth-child(3),
body.dark .mode-switch:has(a:nth-child(4):hover) a:nth-child(4) { color: #101112; }

@media (prefers-reduced-motion: reduce) {
    .mode-switch .pill, .mode-switch a { transition: none; }
}

