/* Single theme. Page ground #0B0A0A, text Soft Linen #eae4da, global accent (--lime) #C3F73A.
   Floppies: Blue #3E92CC (About), Yellow #FCBA04 (Projects), Red #ea2b1f (Contact).
   The rack itself is Carbon Black #1d1d1b (see js/rack3d.js).
   UI language: menu bar with filled tabs, title-bar windows, HUD-style rounded buttons.
   Sections "load" from floppy disks inserted into a drive rack as the page scrolls. */
:root {
  color-scheme: dark;
  --linen: #eae4da;
  --lime: #c3f73a;
  --disk-blue: #3e92cc;
  --disk-yellow: #fcba04;
  --disk-red: #ea2b1f;
  --ground: #0b0a0a;
  --panel: #1a1918;
  --ink: var(--linen);
  --ink-2: #a8a399;
  --line: #3a3837;
  --line-soft: #1a1918;
  --signal: var(--lime);
  --focus: var(--lime);
  --display: 'Oxanium', 'IBM Plex Mono', ui-monospace, monospace;
  --body: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --gutter: clamp(28px, 5vw, 72px);
  --max: 1360px;
  --bar: 64px;
  --ease: cubic-bezier(.22, 1, .36, 1);
}
*, *::before, *::after { box-sizing: border-box }
html { scroll-behavior: smooth; scroll-padding-top: 72px }
body { margin: 0; background: var(--ground); color: var(--ink); font: 400 16px/1.7 var(--body); -webkit-font-smoothing: antialiased; position: relative; isolation: isolate }
a { color: inherit; text-decoration: none }
button { font: inherit }
::selection { background: var(--signal); color: var(--ground) }
a:focus-visible, button:focus-visible { outline: 2px solid var(--focus); outline-offset: 4px }
.p-row:focus-visible, .links a:focus-visible, .detail-link:focus-visible, .more-row:focus-visible, .extra-close:focus-visible { outline-color: var(--ink); outline-offset: -4px }
.wrap { width: min(100%, var(--max)); margin-inline: auto; padding-inline: var(--gutter) }
.skip { position: absolute; left: 12px; top: -80px; background: var(--ink); color: var(--ground); padding: 10px 14px; z-index: 40 }
.skip:focus { top: 12px }
.sr { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap }

/* paper grain */
body::after { content: ''; position: fixed; inset: 0; z-index: 30; pointer-events: none; opacity: .05;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 1 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>") }

/* dot-matrix background (js/grid.js): static, the bottom layer, below the 3D rack, never takes the pointer */
#grid { position: fixed; inset: 0; z-index: -2; width: 100%; height: 100%; display: block; pointer-events: none }
main, .bar, .foot { position: relative; z-index: 1 }

/* ---- menu bar: logo + filled tabs ---- */
.bar, .foot { width: 100%; max-width: none; margin-inline: 0; padding-inline: max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter))) } /* full-bleed bar, content stays on the page grid */
.bar { position: sticky; top: 0; z-index: 10; height: var(--bar); display: flex; align-items: center; gap: 16px; background: color-mix(in srgb, var(--ground) 94%, transparent); border-bottom: 1px solid var(--line) }
.mark { display: inline-flex; align-items: center; min-height: 44px; font: 600 17px var(--display); letter-spacing: .04em; white-space: nowrap }
.mark span { color: var(--signal) }
.bar nav { display: flex; gap: 6px; margin-left: auto }
/* Tabs mimic the floppy: 1px border, top-right corner cut. At rest they are neutral (--rest); on hover and when
   active they take their disk colour (--disk). The element's own background is the border (clipped to the cut
   shape); ::before is the inner fill, inset 1px, with the cut pulled in so the diagonal edge is also 1px thick. */
.bar nav a { --disk: var(--ink-2); --rest: var(--ink-2); --c: var(--rest); --cut: 10px; --fill: var(--ground);
  position: relative; isolation: isolate; display: flex; align-items: center; padding: 9px 16px; font: 500 12px var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--c); background: var(--c);
  clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%);
  transition: color 160ms, background-color 160ms }
.bar nav a::before { content: ''; position: absolute; inset: 1px; z-index: -1; background: var(--fill);
  clip-path: polygon(0 0, calc(100% - var(--cut) + .59px) 0, 100% calc(var(--cut) - .59px), 100% 100%, 0 100%);
  transition: background 160ms }
.bar nav a:nth-child(1) { --disk: var(--disk-blue) }
.bar nav a:nth-child(2) { --disk: var(--disk-yellow) }
.bar nav a:nth-child(3) { --disk: var(--disk-red) }
.bar nav a:hover, .bar nav a:focus-visible { --c: var(--disk); --fill: color-mix(in srgb, var(--disk) 14%, var(--ground)) }
.bar nav a.is-here { --c: var(--disk); --fill: var(--disk); color: var(--ground) }
/* the clip would cut an outside focus ring, so draw it just inside the border */
.bar nav a:focus-visible { outline: 2px solid var(--focus); outline-offset: -5px }
.bar nav a.is-here:focus-visible { outline-color: var(--ground) }

/* the scroll hint bobs gently once the visitor has idled on the hero (js/main.js adds .is-nudging) */
.msg.is-nudging { animation: nudge 2.4s ease-in-out infinite }
@keyframes nudge { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(8px) } }

/* message bar */
.msg { display: inline-flex; align-items: center; margin: 0; padding: 10px 18px; border: 1px solid var(--line); background: color-mix(in srgb, var(--panel) 80%, transparent); font: 500 12px/1.5 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--ink) }

/* hero: type only */
.hero { min-height: min(78vh, 700px); display: flex; flex-direction: column; justify-content: flex-end }
.hero-grid { padding-block: 120px 40px; display: grid; gap: 22px }
h1 { font: 700 clamp(38px, 7.4vw, 112px)/1.04 var(--display); letter-spacing: 0; margin: 0; text-transform: uppercase }
h1 i { font-style: normal; color: var(--signal) }
/* the terminal cursor: a slow, soft fade rather than a hard blink */
h1 i, .mark span { animation: cursor 1.8s ease-in-out infinite }
@keyframes cursor { 0%, 100% { opacity: 1 } 50% { opacity: .12 } }
.hero-line { font-size: clamp(16px, 1.9vw, 24px); margin: 0 }
.hero-copy { margin: 0; max-width: 42ch; color: var(--ink-2) }
.hero-foot { padding-block: 24px 20px }

/* ---- windows: a filled title bar over a framed panel ---- */
.module { display: grid; grid-template-columns: min(100%, 760px); align-content: start; padding-block: 56px }
.win-title { margin: 0; display: flex; justify-content: space-between; align-items: center; padding: 9px 16px; background: var(--accent, var(--ink)); color: var(--ground); font: 600 13px var(--mono); letter-spacing: .16em; text-transform: uppercase }
.win-title::after { content: attr(data-drive); opacity: .7; letter-spacing: .1em }
.module-body { --bx: 24px; padding: 22px 24px 24px; background: color-mix(in srgb, var(--ground) 95%, transparent); border: 1px solid color-mix(in srgb, var(--accent, var(--line)) 60%, transparent); border-top: 0; outline: 1px solid color-mix(in srgb, var(--accent, var(--line)) 26%, transparent); outline-offset: -6px }
.module-body p { margin: 0 0 22px; max-width: 60ch; font-size: clamp(14px, 1.5vw, 17px); line-height: 1.7 }

/* status rows */
.facts { margin: 0 }
.facts div { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding: 9px 0; border-top: 1px solid var(--line) }
.sub { margin: 22px 0 6px; font: 600 12px var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--accent, var(--ink)) }
.path div { padding: 7px 0 }
.tags { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px }
.tags li { padding: 5px 10px; border: 1px solid color-mix(in srgb, var(--accent, var(--ink)) 55%, transparent); font: 500 11px/1.4 var(--mono); letter-spacing: .06em; text-transform: uppercase; color: var(--ink) }
.facts dt { font: 400 12px var(--mono); color: var(--ink-2); text-transform: uppercase; letter-spacing: .1em }
.facts dd { margin: 0; text-align: right }
.facts dt { white-space: nowrap }

/* Projects: same flush style as Contact. Rows tile the window with no dividers or borders and fill with the disk
   colour on hover, keyboard focus and while their detail window is open. */
.projects { list-style: none; margin: 0; padding: 0 }
.p-row { display: grid; grid-template-columns: 2fr 3fr 64px 44px; align-items: center; width: 100%; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; transition: background 140ms, color 140ms }
.p-row > span { padding: 22px 12px }
.p-row:hover, .p-row:focus-visible, .p-row[aria-expanded="true"] { background: var(--accent); color: var(--ground) }
.p-row:hover .p-desc, .p-row:hover .p-meta, .p-row:focus-visible .p-desc, .p-row:focus-visible .p-meta, .p-row[aria-expanded="true"] .p-desc, .p-row[aria-expanded="true"] .p-meta { color: inherit }
.p-row > .p-name { padding-left: 26px; font: 600 clamp(16px, 1.8vw, 20px)/1.2 var(--display) }
.p-desc { color: var(--ink-2); font-size: 14px }
.p-meta { font: 400 12px var(--mono); color: var(--ink-2); justify-content: flex-end }
.p-go { justify-content: center }

/* About: on phones and short windows the facts and the Path collapse behind a "More" row, which opens them in a
   window over About (like a project's detail). On roomy screens they stay inline and the row never shows. */
.more-row, .about-extra .extra-head { display: none }
@media (max-width: 760px), (max-height: 899px) {
  .more-row { display: flex; justify-content: space-between; align-items: center; width: calc(100% + 2 * var(--bx)); min-height: 56px; margin: 0 calc(-1 * var(--bx)) calc(-1 * var(--bx)); padding: 16px var(--bx); border: 0; background: none; color: inherit; font: 600 15px var(--display); text-transform: uppercase; letter-spacing: .08em; text-align: left; cursor: pointer; transition: background 140ms, color 140ms }
  .more-row:hover, .more-row:focus-visible, .more-row[aria-expanded="true"] { background: var(--accent); color: var(--ground) }
  .about-extra { display: none }
  .about-extra.is-open { display: block; background: var(--ground); border: 1px solid color-mix(in srgb, var(--accent) 60%, transparent); border-top: 0; animation: detail-in 200ms var(--ease) }
  .about-extra.is-open .extra-head { display: flex }
  .about-extra .extra-head::after { content: none }
  .extra-body { padding: 4px 16px 16px }
  .extra-body .sub:first-child { margin-top: 12px }
  .is-live .about-extra.is-open { position: absolute; top: clamp(28px, 6vh, 56px); left: 0; right: 0; z-index: 3 }
  .is-live .has-more > .win-title, .is-live .has-more > .module-body { visibility: hidden }
  .is-live .has-more .about-extra.is-open { visibility: visible }
}

/* Detail window: opens beside the list on wide screens, under it otherwise */
.detail { margin-top: 18px; animation: detail-in 200ms var(--ease) }
.detail[hidden] { display: none }
.detail .win-title::after { content: none }
.detail .win-title { gap: 12px }
.detail-close, .extra-close { background: none; border: 0; margin: -6px -8px -6px 0; padding: 10px 14px; color: inherit; font: 500 22px/1 var(--mono); cursor: pointer }
.detail .module-body { padding: 18px 20px 20px }
.detail-link { display: flex; justify-content: space-between; align-items: center; margin: 20px -20px -20px; padding: 18px 20px; font: 600 15px var(--display); text-transform: uppercase; letter-spacing: .08em; transition: background 140ms, color 140ms }
.detail-link:hover, .detail-link:focus-visible { background: var(--accent); color: var(--ground) }
.detail-link[hidden], .detail .detail-note[hidden] { display: none }
/* "Coming soon" takes the slot of the "Open project" button: same bleed to the window edges, padding and type.
   Prefixed with .detail so it outranks the generic .module-body p rule. */
.detail .detail-note { display: flex; align-items: center; max-width: none; margin: 20px -20px -20px; padding: 18px 20px; font: 600 15px var(--display); text-transform: uppercase; letter-spacing: .08em; color: var(--ink-2) }
@keyframes detail-in { from { opacity: 0; transform: translateX(-10px) } to { opacity: 1; transform: none } }
/* Below 1280px there is no free space beside the list, and the stage clips anything taller than the screen, so the
   detail window opens over the list instead of under it. Closing it (x, Esc or the same row) reveals the list again. */
@media (max-width: 1279px) {
  .is-live .detail { position: absolute; top: clamp(28px, 6vh, 56px); left: 0; right: 0; margin: 0; z-index: 3 }
  .is-live .detail .module-body { background: var(--ground) }
  .is-live .has-detail > .win-title, .is-live .has-detail > .module-body.flush { visibility: hidden }
}
@media (min-width: 1280px) {
  .is-live .detail { position: absolute; top: clamp(28px, 6vh, 56px); left: calc(min(100%, 760px) + 28px); width: 380px; margin: 0 }
}

/* Contact: the buttons tile the whole window with no gaps and no borders; one fills with the disk colour on hover */
.module-body.flush { padding: 0; outline: 0 }
.links { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 0 }
.links a { display: flex; justify-content: space-between; align-items: center; gap: 10px; height: 100%; min-height: 96px; padding: 28px 26px; border: 0; font: 600 clamp(15px, 1.6vw, 18px) var(--display); text-transform: uppercase; letter-spacing: .08em; transition: background 140ms, color 140ms }
.links a:hover, .links a:focus-visible { background: var(--accent); color: var(--ground) }

/* ---- fallback: no JS / reduced motion = plain stacked windows ---- */
.rack { --step: 80svh }
.loader { display: none }

/* ---- live: pinned stage, disks load sections ---- */
.rack.is-live { height: calc(100svh - var(--bar) + 3 * var(--step)) }
.is-live .rack-sticky { position: sticky; top: var(--bar); height: calc(100svh - var(--bar)); overflow: clip }
.is-live .rack-grid { height: 100%; padding-block: 0 }

/* the screen has no frame: windows load straight onto the page, over the rack */
.is-live .screen { position: absolute; z-index: 2; top: 0; bottom: 0; left: max(var(--gutter), calc((100vw - var(--max)) / 2 + var(--gutter))); width: min(780px, calc(100% - 2 * var(--gutter))) }

/* each section takes the primary colour of its floppy */
#about { --accent: var(--disk-blue) }
#projects { --accent: var(--disk-yellow) }
#contact { --accent: var(--disk-red) }
.rack[data-disk="0"] .loader { --accent: var(--disk-blue) }
.rack[data-disk="1"] .loader { --accent: var(--disk-yellow) }
.rack[data-disk="2"] .loader { --accent: var(--disk-red) }

.is-live .module { position: absolute; inset: 0; padding: clamp(28px, 6vh, 56px) 0 24px; overflow: visible;
  visibility: hidden; clip-path: inset(0 -700px 100% 0); transition: clip-path 420ms var(--ease), visibility 0s 420ms }
.is-live .module.is-on { visibility: visible; clip-path: inset(0 -700px 0 0); transition: clip-path 420ms var(--ease), visibility 0s }

/* loader */
.is-live .loader { position: absolute; inset: 0; z-index: 2; display: flex; flex-direction: column; justify-content: center; gap: 16px; padding: 0;
  opacity: 0; pointer-events: none; transition: opacity 160ms }
.is-live.is-loading .loader { opacity: 1 }
.loader-name { font: 600 clamp(14px, 1.8vw, 20px)/1.3 var(--display); letter-spacing: .08em; text-transform: uppercase; color: var(--accent, var(--ink)) }
.meter { display: block; width: min(100%, 420px); height: 20px; padding: 3px; border: 2px solid var(--accent, var(--line)) }
.meter i { display: block; height: 100%; width: var(--p, 0%); background: repeating-linear-gradient(90deg, var(--accent, var(--signal)) 0 8px, transparent 8px 11px) }
.loader-meta { display: flex; justify-content: space-between; width: min(100%, 420px); font: 500 11px var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2); font-variant-numeric: tabular-nums }

/* The rack is a low-poly Three.js scene (js/rack3d.js): one fixed, centred canvas behind the whole page.
   It never takes the pointer, so clicks, text selection and scrolling go straight through to the page. */
#rack3d { position: fixed; inset: 0; z-index: -1; width: 100%; height: 100%; display: block; pointer-events: none; touch-action: none; user-select: none }

/* footer */
.foot { display: flex; justify-content: space-between; align-items: center; height: var(--bar); background: color-mix(in srgb, var(--ground) 94%, transparent); border-top: 1px solid var(--line); font: 400 12px var(--mono); color: var(--ink-2) }
.foot a { display: inline-flex; align-items: center; min-height: 44px; padding-inline: 8px; margin-right: -8px; transition: color 140ms }
.foot a:hover { color: var(--ink) }
.checker { height: 12px; background: conic-gradient(var(--ink) 25%, transparent 0 50%, var(--ink) 0 75%, transparent 0) 0 0 / 12px 12px; position: relative; z-index: 1 }

@media (max-width: 760px) {
  .bar { gap: 10px }
  .mark { font-size: 15px }
  .bar nav { gap: 4px }
  .bar nav a { --cut: 7px; min-height: 44px; padding: 8px 8px; font-size: 11px; letter-spacing: .04em }
  .hero-grid { padding-top: 96px }
  .hero-copy { font-size: 13px; line-height: 1.55 }
  .module { padding-block: 40px }
  .module-body { --bx: 16px; padding: 16px }
  .is-live .about-extra.is-open { top: 22px }
  .p-row { grid-template-columns: 1fr 44px }
  .p-row > .p-name, .p-row > .p-desc { grid-column: 1 }
  .p-row > .p-name { padding: 12px 10px 2px 16px }
  .p-row > .p-desc { grid-row: 2; padding: 0 10px 12px 16px; font-size: 12px; line-height: 1.5 }
  .p-row > .p-meta { display: none }
  .p-row > .p-go { grid-column: 2; grid-row: 1 / span 2; padding: 0 }
  .is-live .detail { top: 22px }
  .detail .module-body { padding: 14px 16px 16px }
  .detail-link, .detail .detail-note { margin: 16px -16px -16px; padding: 16px }
  .detail .facts div:nth-child(2), .detail .facts div:nth-child(3) { display: none } /* phone keeps Year and Type */
  .links a { min-height: 80px; padding: 20px 16px }

  .is-live .screen { width: calc(100% - 2 * var(--gutter)) }
  .is-live .module { padding: 22px 0 }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto }
  * { transition-duration: .01ms !important; animation-duration: .01ms !important }
  h1 i, .mark span, .msg.is-nudging { animation: none !important }
}
