/* Projects: an index list whose rows print a preview of each project out of
   the backdrop's dots, and open it as a page of its own (projects-view.js). */

.tags { font: 11px/1.5 var(--mono); letter-spacing: 0.02em; color: var(--faint); }

/* ---------- project preview (the takeover stage) ---------- */
.stage { position: fixed; left: 0; top: 0; z-index: 25; pointer-events: none; visibility: hidden; }
.stage.is-on { visibility: visible; }
.stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
/* a soft pool of dark under the picture, so it sits on the dots rather than in them */
.stage__pool { position: absolute; inset: calc(-1 * var(--above, 40px)) -48px calc(-64px - var(--more-h, 0px)) -48px; background: radial-gradient(closest-side, rgba(10,10,10,0.55), rgba(10,10,10,0.35) 70%, transparent); opacity: 0; transition: opacity 400ms var(--ease); }
.stage.is-open .stage__pool { opacity: 1; }
.stage__frame { position: absolute; inset: 0; border: 1px solid transparent; transition: border-color 500ms var(--ease) 250ms; }
.stage.is-open .stage__frame { border-color: color-mix(in srgb, var(--accent, #fff) 45%, transparent); }
.stage__corner { position: absolute; width: 10px; height: 10px; border: 1px solid var(--accent, #fff); opacity: 0; transition: opacity 300ms var(--ease) 350ms, transform 500ms var(--ease) 300ms; }
.stage.is-open .stage__corner { opacity: 1; transform: none; }
.stage__corner:nth-child(1) { left: -6px; top: -6px; border-right: 0; border-bottom: 0; transform: translate(6px, 6px); }
.stage__corner:nth-child(2) { right: -6px; top: -6px; border-left: 0; border-bottom: 0; transform: translate(-6px, 6px); }
.stage__corner:nth-child(3) { left: -6px; bottom: -6px; border-right: 0; border-top: 0; transform: translate(6px, -6px); }
.stage__corner:nth-child(4) { right: -6px; bottom: -6px; border-left: 0; border-top: 0; transform: translate(-6px, -6px); }
.stage__meta { position: absolute; left: 0; right: 0; top: calc(100% + 16px); display: flex; justify-content: space-between; gap: 24px; font: 11px/1.5 var(--mono); color: var(--faint); opacity: 0; transform: translateY(6px); transition: opacity 350ms var(--ease), transform 450ms var(--ease); }
.stage.is-open .stage__meta { opacity: 1; transform: none; transition-delay: 380ms; }
.stage__meta b { color: var(--accent, var(--text)); font-weight: 400; }
.stage__text { position: absolute; left: 0; right: 0; bottom: calc(100% + 14px); opacity: 0; transform: translateY(8px); transition: opacity 350ms var(--ease), transform 450ms var(--ease); }
.stage.is-open .stage__text { opacity: 1; transform: none; transition-delay: 300ms; }
.stage__text h3 { font-size: clamp(30px, 3.4vw, 52px); font-weight: 600; letter-spacing: -0.035em; line-height: 1; }
.stage__text p { margin-top: 10px; max-width: 60ch; font-size: 14px; color: var(--muted); }
.stage__count { position: absolute; right: 0; bottom: calc(100% + 14px); font: 11px var(--mono); color: var(--faint); opacity: 0; transition: opacity 300ms var(--ease) 300ms; }
.stage.is-open .stage__count { opacity: 1; }

/* ---------- project list ---------- */
.tko { border-top: 1px solid var(--line); }
.tko__row { position: relative; display: grid; grid-template-columns: 26px 1fr auto; gap: 14px; align-items: baseline; padding: 18px 2px; border-bottom: 1px solid var(--line); transition: padding 380ms var(--ease); }
.tko__row::after { content: ""; position: absolute; left: 0; bottom: -1px; height: 1px; width: 100%; background: linear-gradient(90deg, var(--deep), var(--accent)); transform: scaleX(0); transform-origin: 0 50%; transition: transform 520ms var(--ease); }
/* Each project's pixel mark, drawn on a 13-cell grid at 2px a cell so it
   stays sharp; it dims with the title while another project is engaged */
.tko__ic { align-self: center; display: block; width: 26px; height: 26px; image-rendering: pixelated; transition: opacity 250ms var(--ease); }
.tko.has-active .tko__ic { opacity: 0.35; }
.tko .tko__row.is-on .tko__ic { opacity: 1; }
/* A step up from the experience list's company names, in the same family of sizes */
.tko__t { font-size: 22px; font-weight: 500; letter-spacing: -0.015em; transition: color 250ms var(--ease); }
/* A big arrow on every row, so each reads as something to open; the hovered
   one turns white, slides out and keeps nudging forward. */
.tko__go { align-self: center; font: 500 26px/1 var(--sans); color: #fff; opacity: 0.22; transform: translateX(-6px); transition: opacity 250ms var(--ease), transform 380ms var(--ease); }
@keyframes nudge { 0%, 100% { translate: 0 0; } 50% { translate: 6px 0; } }
/* While any project is engaged (row hovered, safe zone, preview, or open),
   the other titles stay dimmed; they brighten only when none is. */
.tko.has-active .tko__t { color: var(--faint); }
.tko .tko__row.is-on { padding-left: 14px; }
.tko .tko__row.is-on .tko__t { color: var(--text); }
.tko .tko__row.is-on .tko__go { opacity: 1; transform: translateX(4px); animation: nudge 1.1s cubic-bezier(0.45, 0, 0.55, 1) 380ms infinite; }
.tko .tko__row.is-on::after { transform: scaleX(1); }

/* Below the picture, the start of the project's notes, running to the bottom
   of the screen and faded out where they're cut: there's more, click to open. */
.stage__more { position: absolute; left: 0; right: 0; top: calc(100% + 52px); height: var(--more-h, 0px); margin: 0; padding-top: 4px; border: 0; overflow: hidden;
  -webkit-mask: linear-gradient(#000 0, #000 35%, transparent 100%); mask: linear-gradient(#000 0, #000 35%, transparent 100%);
  opacity: 0; transform: translateY(8px); transition: opacity 400ms var(--ease), transform 500ms var(--ease); }
.stage.is-open .stage__more { opacity: 1; transform: none; transition-delay: 420ms; }
.stage__more h2:first-child { margin-top: 0; }
.stage.is-flying .stage__more { opacity: 0 !important; transform: none; transition: opacity 180ms !important; }
/* The preview can be reached and clicked: a hit area over everything from the
   title to the notes. Reaching it opens the project (projects-view.js). */
.stage__hit { position: absolute; left: -12px; right: -12px; top: calc(-1 * var(--above, 40px)); bottom: calc(-52px - var(--more-h, 0px)); pointer-events: none; cursor: pointer; }
.stage.is-open .stage__hit { pointer-events: auto; }
.stage.is-flying .stage__hit { pointer-events: none; }
/* Resting on the preview opens it after 1.8 s (HOLD in projects-view.js): a
   line in the project's colour traces the frame clockwise from the top-left
   corner, and the picture brightens to full, both over that time. Leaving
   early rewinds them quickly. */
.stage__timer { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.stage__timer rect { fill: none; stroke: var(--accent, #fff); stroke-width: 7; stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 250ms var(--ease); }
.stage.is-arming .stage__timer rect { stroke-dashoffset: 0; transition: stroke-dashoffset 1800ms linear; }
.stage.is-arming .stage__body { opacity: 1; transition-duration: 1800ms; transition-timing-function: linear; }
.stage.is-flying .stage__timer { opacity: 0; transition: opacity 180ms; }
.stage__titlebar { display: flex; justify-content: space-between; align-items: center; gap: 20px; }
html { scrollbar-gutter: stable; }
/* Only the content is see-through; the dark pool and the bottom shade stay
   at full strength around it. */
.stage__body { position: absolute; inset: 0; opacity: 0.4; transition: opacity 600ms cubic-bezier(0.4, 0, 0.2, 1); }
.stage.is-flying .stage__body { opacity: 1; }
.fly { position: fixed; left: 0; top: 0; z-index: 26; overflow: hidden; pointer-events: none; background: var(--surface); will-change: transform; }
.fly::after { content: ""; position: absolute; inset: 0; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line)); }
.fly img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* the picture flies alone: its captions and pool drop away as it leaves */
.stage.is-flying .stage__text, .stage.is-flying .stage__meta, .stage.is-flying .stage__count, .stage.is-flying .stage__pool { opacity: 0 !important; transition: opacity 180ms !important; }
.stage.is-flying .stage__corner { opacity: 0 !important; transition: opacity 180ms !important; }

/* ---------- home ↔ project page ---------- */
/* While a project is open, home is pinned where it was and slid off to the
   left (moved by the same tween as the picture), a faint sliver at the edge. */
/* Layer hints only while a project is open (the slides need them); kept on
   all the time they made the Projects section's fade-in stall on arrival. */
body.is-detail #home { will-change: transform; }
body.is-detail #home { position: fixed; left: 0; right: 0; top: var(--home-top, 0px); pointer-events: none; }
/* Titles at the edge grow fainter toward the screen's left, over the strip's
   width; --fa eases the fade in and out with the slide. The arrow and the
   hover bar aren't part of this, so they stay full. */
/* The darkening is fixed to the screen's left edge (--m0/--m1 are set every
   frame in each row's own coordinates), so rows slide into the dark rather
   than carrying it with them. */
body.is-detail .is-edge > :not(.is-cur) {
  -webkit-mask: linear-gradient(90deg, rgba(0,0,0,0.05) var(--m0, -9999px), #000 var(--m1, -9998px));
  mask: linear-gradient(90deg, rgba(0,0,0,0.05) var(--m0, -9999px), #000 var(--m1, -9998px)); }
body.is-detail .is-edge { border-top-color: transparent; transition: border-color 300ms; }
/* The projects section's own top rule would otherwise stay drawn across the
   strip while everything else around the list has faded away. */
#projects { transition: opacity 600ms var(--ease), transform 600ms var(--ease), border-color 300ms var(--ease); }
body.is-detail #projects { border-top-color: transparent; }
/* The rows stay usable at the edge: their (right-aligned) titles switch
   projects, and the open one's arrow goes back. */
/* While a project is open the rows are for looking at; the whole strip, top
   to bottom, is the way back, and hovering it lights the open row's arrow. */
body.is-detail .tko__row { pointer-events: none; }
.back-zone { position: fixed; left: 0; top: 0; bottom: 0; width: var(--strip, 60px); z-index: 30; padding: 0; background: none; border: 0; cursor: pointer; display: none; }
body.is-detail .back-zone { display: block; }
@media (max-width: 1023px) { body.is-detail .back-zone { display: none; } }
.back-zone:focus-visible { outline: 1px solid var(--text); outline-offset: -3px; }
body.is-detail .is-cur .tko__go { transition: text-shadow 200ms var(--ease); }
body.is-detail .is-cur.is-hot .tko__go { text-shadow: 0 0 14px rgba(255,255,255,0.55); }
/* In a project, the open row's own arrow has turned to point back; its hover
   nudge follows, toward the left. */
@keyframes nudge-back { 0%, 100% { translate: 0 0; } 50% { translate: -6px 0; } }
body.is-detail .tko .tko__row.is-cur .tko__go { animation: none; }
body.is-detail .tko .tko__row.is-cur.is-hot .tko__go { animation: nudge-back 1.1s cubic-bezier(0.45, 0, 0.55, 1) infinite; }
.tko__t { display: inline-block; }
body.is-detail .tko__t { will-change: transform; }

.detail { position: relative; padding-top: 72px; padding-bottom: 96px; }
.detail[hidden] { display: none; }
/* While a project is open nothing may widen the page sideways: on a phone,
   the project page waiting off to the right would otherwise make the browser
   widen the layout, and home (pinned to the screen's width) would reflow
   mid-slide. */
html:has(> body.is-detail) { overflow-x: clip; }
body.is-detail { overflow-x: clip; }
.tko__row, .detail button { -webkit-tap-highlight-color: transparent; }
/* Phones: the page slides in whole, already laid out, with no rise-in; going
   to the next project just cross-fades the content. */
.detail.is-slide > * { opacity: 1; transform: none; transition: opacity 200ms var(--ease); }
.detail.is-slide.is-out > * { opacity: 0; transform: none; transition-duration: 160ms; }
.detail > * { opacity: 0; transform: translateY(14px); transition: opacity 500ms var(--ease), transform 650ms var(--ease); }
.detail.is-in > * { opacity: 1; transform: none; }
.detail.is-in > :nth-child(2) { transition-delay: 0ms; }
.detail.is-in > :nth-child(3) { transition-delay: 90ms; }
.detail.is-in > :nth-child(4) { transition-delay: 160ms; }
.detail.is-in > :nth-child(5) { transition-delay: 230ms; }
.detail.is-in > :nth-child(6) { transition-delay: 300ms; }
.detail.is-out > * { opacity: 0; transform: translateY(-8px); transition-duration: 220ms; transition-delay: 0ms; }
.detail__crumb { display: flex; align-items: center; gap: 10px; margin-bottom: 22px; font: 12px var(--mono); color: var(--faint); letter-spacing: 0.04em; }
.detail__crumb button { padding: 0; background: none; border: 0; color: var(--muted); font: inherit; cursor: pointer; transition: color 150ms; }
.detail__crumb button:hover, .detail__crumb button:focus-visible { color: var(--text); }
.detail__crumb b { color: var(--accent); font-weight: 400; }
/* the hero slot: the stage lands here, then hands over to the image */
/* The hero is where the flying picture lands, so it never moves on its own
   (no rise-in), and its border sits over the image rather than around it:
   the picture and the hero match to the pixel at hand-over. */
.detail > .detail__hero, .detail.is-in > .detail__hero, .detail.is-out > .detail__hero { opacity: 1; transform: none; transition: none; }
.detail__hero { position: relative; width: var(--hero-w, 100%); aspect-ratio: var(--ratio, 1.6); overflow: hidden; background: var(--surface); }
.detail__hero::after { content: ""; position: absolute; inset: 0; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line)); pointer-events: none; }
.detail__hero img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: opacity 420ms var(--ease); }
.detail__hero img.is-hidden { opacity: 0; }
.detail__head { margin-top: 30px; }
.detail__head h1 { font-size: clamp(40px, 5vw, 64px); font-weight: 600; letter-spacing: -0.04em; line-height: 1; }
/* The project's pixel mark after its title, about as tall as the capitals and
   sitting on the baseline; rounded down to whole 13px steps (the art's grid)
   so every pixel of it stays crisp. */
.detail__ic { display: inline-block; width: 26px; height: 26px; width: round(down, 0.72em, 13px); height: round(down, 0.72em, 13px); margin-left: 0.28em; vertical-align: baseline; image-rendering: pixelated; }
.detail__award { margin-top: 12px; font: 12px var(--mono); color: var(--accent); letter-spacing: 0.04em; }
.detail__lede { margin-top: 16px; font-size: 18px; color: #c4c4c4; max-width: 56ch; text-wrap: pretty; }
.detail__actions { margin-top: 22px; display: flex; flex-wrap: wrap; align-items: center; gap: 14px 20px; }
.detail__open { padding: 10px 16px; background: var(--accent); color: #0a0a0a; font: 500 13px var(--mono); transition: filter 150ms; }
.detail__open:hover, .detail__open:focus-visible { filter: brightness(1.12); }
.detail__md { margin-top: 44px; border-top: 1px solid var(--line); }
.detail__md h2 { margin-top: 36px; margin-bottom: 12px; font: 400 12px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.detail__md p { font-size: 16px; color: #c4c4c4; text-wrap: pretty; }
.detail__md p + p { margin-top: 12px; }
.detail__md strong { color: var(--text); font-weight: 500; }
.detail__md ul { display: grid; gap: 10px; }
.detail__md li { position: relative; padding-left: 18px; font-size: 15px; color: #c4c4c4; }
.detail__md li::before { content: ""; position: absolute; left: 0; top: 0.72em; width: 6px; height: 6px; background: var(--accent); }
.detail__md ul.is-stack { display: flex; flex-wrap: wrap; gap: 8px; }
.detail__md ul.is-stack li { padding: 5px 10px; border: 1px solid var(--line-strong); font: 12px var(--mono); color: var(--muted); }
.detail__md ul.is-stack li::before { display: none; }
.detail__next { margin-top: 64px; padding-top: 22px; border-top: 1px solid var(--line); display: flex; justify-content: space-between; align-items: baseline; gap: 20px; }
.detail__next span { font: 12px var(--mono); color: var(--faint); letter-spacing: 0.04em; }
.detail__next button { padding: 0; background: none; border: 0; color: var(--text); font: 500 clamp(24px, 3vw, 36px) var(--sans); letter-spacing: -0.03em; cursor: pointer; transition: color 250ms var(--ease); }
.detail__next button i { font-style: normal; display: inline-block; margin-left: 10px; transition: transform 350ms var(--ease); }
.detail__next button:hover, .detail__next button:focus-visible { color: var(--next-accent, var(--accent)); }
.detail__next button:hover i { transform: translateX(6px); }

@media (prefers-reduced-motion: reduce) {
  .tko .tko__row.is-on .tko__go,
  body.is-detail .tko .tko__row.is-cur.is-hot .tko__go { animation: none; }
  .stage__body, .detail > *, .tko__row, .tko__go { transition-duration: 1ms !important; }
}
