/* ===================================================================
   Jason Ho — portfolio
   Warm bone paper and warm black, one vermilion accent; one condensed display face
   (Archivo) for every heading, with the accent words picked out in colour.

   Every page colour is light-dark(light, dark), so the whole site flips from one
   attribute on <html>: data-theme="light" | "dark" | absent = follow the OS.
   The project-card windows (.shot) keep fixed colours on purpose: they stand
   for app screens, which look the same whatever mode the page is in.

   Elements that animate in start hidden only under html.js (set by the inline
   script in <head>), so the page is fully visible without JavaScript.
   =================================================================== */
:root {
  color-scheme: light dark;
  --bg:     light-dark(#f2ede4, #120e0c);
  --bg-2:   light-dark(#e8e1d5, #1b1513);
  --card:   light-dark(#faf7f1, #1e1816);
  --ink:    light-dark(#17120f, #f0e9e1);
  --ink-2:  light-dark(#5f574f, #a39a91);
  --line:   light-dark(#d9d0c2, #332a26);
  --mono: 'Geist Mono', ui-monospace, monospace;
  --sans: 'Geist', system-ui, sans-serif;
  --sig: 'Mr Dafoe', cursive;
  --ease: cubic-bezier(.22, .68, .18, 1);
  --acc-light: #d22d17;  /* the accent on light surfaces, also used inside the .shot windows */
  --acc-dark:  #ff5b3f;  /* the accent on dark surfaces */
  --acc-soft:  #ff8a73;  /* the accent inside the dark .shot windows */
  --acc:       light-dark(var(--acc-light), var(--acc-dark));
  --acc-ink:   light-dark(#fff, #120e0c);  /* text on an accent fill */
  --display: 'Archivo', sans-serif; --d-stretch: 62%; --d-weight: 800; --d-track: -.01em;
  /* side margin: grows so content never runs wider than 1280px;
     backgrounds stay full width because this is padding, not a narrower box */
  --px: max(clamp(20px, 5vw, 80px), calc((100% - 1280px) / 2));
}
:root[data-theme="light"] { color-scheme: only light; }
:root[data-theme="dark"]  { color-scheme: only dark; }

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 72px; scroll-padding-bottom: calc(72px + env(safe-area-inset-bottom)); }  /* clear the nav, and the pinned skills strip */
body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--sans); -webkit-font-smoothing: antialiased; overflow-x: hidden; }
a { color: inherit; }
button { font: inherit; color: inherit; cursor: pointer; background: none; border: 0; }
/* .S marks the second word of a heading (often the accent); it shares the display face so headings read as one voice */
.D, .S { font-family: var(--display); font-stretch: var(--d-stretch); font-weight: var(--d-weight); letter-spacing: var(--d-track); text-transform: uppercase; }
.D { line-height: .86; }
.acc { color: var(--acc); }
.cap { font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2); }

/* ---------- nav ---------- */
.nav { position: fixed; z-index: 40; top: 0; left: 0; right: 0; display: flex; align-items: center; gap: 8px; padding: 12px var(--px);
  background: color-mix(in oklab, var(--bg) 78%, transparent); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); }
/* the hero is its own title page: the bar stays out of it and slides in once the manifesto reaches the top.
   It also comes in while a keyboard user is in it (:focus-visible, so a mouse click doesn't pin it over the hero). */
.js .nav { translate: 0 -100%; transition: translate .5s var(--ease); }
.js .nav.shown { translate: 0 0; }
.js .nav:has(:focus-visible) { translate: 0 0; }  /* its own rule: a browser without :has() drops only this one */
.logo { display: grid; line-height: .9; text-decoration: none; }
.logo .S { font-size: 19px; }
.logo .D { font-size: 17px; }
.nav .mid { margin: 0 auto; display: flex; gap: 22px; }
.nav .mid a { text-decoration: none; font-family: var(--mono); font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2); }
.nav .mid a:hover { color: var(--ink); }
.pill { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; padding: 8px 14px; border-radius: 99px; background: var(--acc); color: var(--acc-ink); text-decoration: none;
  font-family: var(--mono); font-size: 12px; letter-spacing: .08em; text-transform: uppercase; transition: transform .2s var(--ease); }
.pill:hover { transform: translateY(-2px); }
.sq { flex: none; width: 36px; height: 36px; border-radius: 10px; border: 1px solid var(--line); background: var(--card); display: grid; place-items: center; }
.sq.ico { color: var(--ink-2); transition: color .2s, border-color .2s; }
.sq.ico:hover { color: var(--ink); border-color: var(--ink-2); }
.sq.ico svg { width: 17px; height: 17px; }
.nav .pill { margin-left: 4px; }

/* ---------- hero ---------- */
.hero { position: relative; isolation: isolate; min-height: 100svh; display: grid; align-content: center; padding: 110px var(--px) 120px; overflow: hidden; }
.hero__top { display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; margin-bottom: 18px; }
.hero__name { position: relative; margin: 0; }
.hero__name .D { display: block; font-size: clamp(110px, 27vw, 420px); }
.hero__name .S { --base: .2s; display: block; font-size: clamp(90px, 20vw, 320px); line-height: .8; margin: -.08em 0 0 .42em; color: var(--acc); }
.sig { position: absolute; pointer-events: none; overflow: visible; }
.sig text { font-family: var(--sig); fill: var(--acc); stroke: var(--acc); stroke-width: 1.4; stroke-dasharray: 900; }
.js .sig text { fill-opacity: 0; stroke-dashoffset: 900; }
.js .sig.on text { animation: sign 2.2s var(--ease) forwards; }
@keyframes sign { 70% { stroke-dashoffset: 0; fill-opacity: 0; } to { stroke-dashoffset: 0; fill-opacity: 1; } }

/* visually hidden, still read by screen readers */
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* "Jason is ...": the noun follows the name, in the display face like "JASON", rolling over like an odometer */
.hn { position: absolute; left: 31vw; bottom: .1em; margin: 0;
  font-family: var(--display); font-stretch: var(--d-stretch); font-weight: var(--d-weight); text-transform: uppercase; letter-spacing: var(--d-track);
  font-size: clamp(37px, 5.7vw, 96px); line-height: 1; color: var(--ink); white-space: nowrap; overflow: hidden; padding: .06em .12em .16em 0; }
.hn .hc { display: inline-block; transition: translate .55s var(--ease), opacity .4s; transition-delay: calc(var(--i) * 18ms); }
.hn .hc.pre { translate: 0 105%; opacity: 0; } .hn .hc.out { translate: 0 -105%; opacity: 0; }
.hero .sig { width: min(32vw, 440px); top: 9%; right: 18vw; rotate: -10deg; }

/* moving background, landing section only: contour lines on a drifting field, drawn by script.
   The hero's isolation keeps the canvas (z-index -1) behind its content but inside it. */
.topo { position: absolute; inset: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none; }
.scroll { position: absolute; right: var(--px); bottom: 30px; }

/* ---------- reveals: one gentle move, once ---------- */
.js .rv { opacity: 0; translate: 0 28px; transition: opacity .9s var(--ease), translate .9s var(--ease); }
.js .rv.in { opacity: 1; translate: 0 0; }
.line-rv { display: block; overflow: hidden; }
.line-rv > span { display: inline-block; }
.js .line-rv > span { translate: 0 105%; transition: translate 1s var(--ease); }
.js .in .line-rv > span { translate: 0 0; }

/* ---------- section entrances ----------
   Each element with data-a gets .in once, when it scrolls into view (the hero on load).
   stage     the hero: its parts come in one after another
   chars     letters rise out of their word, one after another
   light     words dim until you scroll past them
   curtain   a panel opens out from a rounded inset to full width
   slide     the On / Off words slide in from either side
   scramble  letters flicker through random glyphs before settling */
[data-a="chars"] .w { display: inline-block; overflow: hidden; vertical-align: top; padding: .08em .06em .14em; margin: -.08em -.06em -.14em; }
[data-a="chars"] .ch { display: inline-block; }
.js [data-a="chars"] .ch { translate: 0 115%; transition: translate .95s var(--ease); transition-delay: calc(var(--base, 0s) + var(--i) * .04s); }
.js [data-a="chars"].in .ch { translate: 0 0; }
/* light: words start dim and light up as you scroll through (driven by script) */
[data-a="light"] .wd { opacity: .14; transition: opacity .35s; }
[data-a="light"] .wd.lit { opacity: 1; }
/* scramble: letters cycle through random glyphs before settling (driven by script) */
[data-a="scramble"] .w { white-space: nowrap; }  /* letters are inline-blocks; keep each word on one line */
[data-a="scramble"] .ch { display: inline-block; text-align: center; opacity: 0; }
[data-a="scramble"] .ch.on { opacity: 1; }
[data-a="scramble"] .ch.done { color: inherit; }
[data-a="scramble"] .ch.on:not(.done) { color: var(--acc); }
.js [data-a="curtain"] { clip-path: inset(7% 5% round 36px); transition: clip-path 1.3s var(--ease); }
.js [data-a="curtain"].in { clip-path: inset(0 0 round 0); }

/* hero: name, then signature, then the rolling noun */
.js .hero__top { opacity: 0; transition: opacity .8s .1s; }
.js .hero.in .hero__top { opacity: 1; }
.js .hero .sig.on text { animation-delay: .75s; }
.js .hero .scroll, .js .hero .hn { opacity: 0; translate: 0 18px; transition: opacity .8s var(--ease) 1.2s, translate .9s var(--ease) 1.2s; }
.js .hero.in .scroll, .js .hero.in .hn { opacity: 1; translate: 0 0; }

/* ---------- manifesto band ---------- */
.band { background: var(--bg-2); padding: clamp(80px, 14vw, 180px) var(--px); text-align: center; position: relative; overflow: hidden; }
.manifesto { font-size: clamp(34px, 6.4vw, 104px); margin: 22px auto 0; line-height: 1; }

/* ---------- on / off the clock ---------- */
.clock { padding: clamp(80px, 12vw, 160px) var(--px); }
.clock__tabs { display: flex; justify-content: center; align-items: baseline; gap: clamp(20px, 6vw, 90px); }
.clock__tabs button { font-size: clamp(80px, 15vw, 230px); opacity: .18; transition: opacity .3s, color .3s, transform 1.2s var(--ease); }
.js .clock__tabs { opacity: 0; transition: opacity .7s; }
.js .clock__tabs.in { opacity: 1; }
.js .clock__tabs:not(.in) #t-on { transform: translateX(-14vw); }
.js .clock__tabs:not(.in) #t-off { transform: translateX(14vw); }
.clock__tabs button[aria-selected="true"], .clock__tabs button:focus-visible { opacity: 1; }
.clock__sub { text-align: center; margin: 10px 0 46px; }
.rows { position: relative; }
.rows::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: var(--ink); transform-origin: left; }
.row::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--line); transform-origin: left; }
.js .rows::before { transform: scaleX(0); transition: transform 1.2s var(--ease); }
.js .row::after { transform: scaleX(0); transition: transform 1s var(--ease) calc(.25s + var(--i) * .13s); }
.js .row > * { opacity: 0; translate: -48px 0; transition: opacity .7s var(--ease), translate .8s var(--ease); transition-delay: calc(.15s + var(--i) * .13s); }
.js .rows.in::before, .js .rows.in .row::after { transform: none; }
.js .rows.in .row > * { opacity: 1; translate: 0 0; }
.row { position: relative; display: grid; grid-template-columns: 150px minmax(240px, 1fr) minmax(0, 1.5fr); column-gap: 32px; row-gap: 8px; padding: 26px 0; align-items: baseline; }
.row > .tile { grid-column: 1; grid-row: 1; align-self: start; } .row > .cap { grid-column: 1; grid-row: 2; } .row > h3 { grid-column: 2; grid-row: 1; } .row > .role { grid-column: 2; grid-row: 2; } .row > p, .row > .desc { grid-column: 3; grid-row: 1 / span 2; }
/* tiles: real logos in their own colours for work, colour icons for life */
.tile { width: 52px; height: 52px; border-radius: 14px; border: 1px solid var(--line); background: #fff; display: grid; place-items: center; overflow: hidden; }
.tile img { width: 72%; height: 72%; object-fit: contain; }
.tile--emoji, .tile--pair { background: color-mix(in oklab, var(--acc) 12%, var(--card)); }
.tile--emoji img { width: 58%; height: 58%; }
/* two flags, overlapping on the diagonal */
.tile--pair { position: relative; }
.tile--pair img { position: absolute; width: 50%; height: 50%; }
.tile--pair img:first-child { left: 13%; top: 13%; } .tile--pair img:last-child { right: 13%; bottom: 13%; }
.row h3 { margin: 0; font-size: clamp(30px, 3.6vw, 52px); }
.row .role { font-size: 15px; color: var(--ink-2); line-height: 1.5; }
.row p { margin: 0; color: var(--ink-2); line-height: 1.6; font-size: 15px; }
/* a small visual detail under each Off line, so the column isn't just one sentence */
.desc { display: grid; gap: 14px; align-content: start; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { display: inline-flex; align-items: center; gap: 8px; padding: 7px 12px; border: 1px solid var(--line); border-radius: 99px; background: var(--card); font-size: 14px; }
.chip img { width: 18px; height: 18px; }
.chip--mono { font: 12px var(--mono); letter-spacing: .04em; color: var(--ink-2); padding: 6px 11px; }
.tz { display: grid; gap: 2px; padding: 10px 16px; border: 1px solid var(--line); border-radius: 14px; background: var(--card); min-width: 150px; }
.tz b { font-family: var(--display); font-stretch: var(--d-stretch); font-weight: var(--d-weight); font-size: 34px; line-height: 1; letter-spacing: var(--d-track); font-variant-numeric: tabular-nums; }
.tz span { font: 11px var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2); }
.tz i { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--acc); margin-right: 6px; vertical-align: 1px; animation: tick 2s ease-in-out infinite; }
@keyframes tick { 50% { opacity: .25; } }
.row p b { color: var(--ink); font-weight: 500; }
.panel[hidden] { display: none; }

/* ---------- skills strip ----------
   A static, wrapped list by default (no script, reduced motion). With script the list is doubled and
   slides left by exactly one copy per loop, so it never jumps; hovering stops it.
   While running it is the last thing in .skills-scope and sticks to the bottom of the screen, so it shows
   for as long as the projects are on screen and comes to rest after them. */
.skills { position: relative; border-block: 1px solid var(--line); padding: 18px var(--px); }
.skills.run { position: sticky; bottom: 0; z-index: 30; border-bottom: 0; padding: 14px 0 calc(14px + env(safe-area-inset-bottom));
  background: color-mix(in oklab, var(--bg) 82%, transparent); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); }
.ticker { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 0; font-size: clamp(20px, 2.2vw, 28px); line-height: 1; }
/* each item carries its own trailing space (not gap), so the doubled list is exactly twice one copy */
.ticker li { display: flex; align-items: center; gap: .4em; margin-right: 1.4em; }
/* logos sit on the same white tiles as the experience logos, so their own colours read in both themes */
.ticker .tile { width: 1.3em; height: 1.3em; border-radius: .32em; flex: none; }
.skills:not(.run) .ticker { column-gap: 1.4em; } .skills:not(.run) .ticker li { margin-right: 0; }  /* the still list centres on its words */
.skills.run .skills__track { overflow: hidden; mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.skills.run .ticker { flex-wrap: nowrap; width: max-content; animation: ticker 60s linear infinite; }
.skills.run .ticker:hover { animation-play-state: paused; }
@keyframes ticker { to { translate: -50% 0; } }

/* ---------- projects ---------- */
.work { background: var(--bg-2); padding: clamp(80px, 12vw, 160px) var(--px); }
.work__head { display: flex; justify-content: space-between; align-items: end; gap: 24px; flex-wrap: wrap; margin-bottom: 40px; }
.work__head h2 { margin: 0; font-size: clamp(60px, 10vw, 160px); }
.work__head p { max-width: 36ch; color: var(--ink-2); line-height: 1.6; margin: 0; }
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; align-items: start; }
.p { position: relative; display: flex; flex-direction: column; background: var(--card); border: 1px solid var(--line); border-radius: 16px; overflow: hidden;
  transition: border-color .3s, translate .4s var(--ease); transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); transform-style: preserve-3d; }
.p:hover, .p:focus-within { border-color: var(--acc); }
.p .vis { height: 220px; display: grid; place-items: center; border-bottom: 1px solid var(--line); position: relative; overflow: hidden;
  background: radial-gradient(circle at 50% 120%, color-mix(in oklab, var(--acc) 16%, transparent), transparent 60%); }
/* product shots: an interface window rising from the bottom of the card */
.p .shot, .drawer .shot { background: linear-gradient(160deg, color-mix(in oklab, var(--acc) 14%, var(--card)), var(--card) 65%); display: grid; place-items: end center; padding: 22px 22px 0; }
.p .shot--dark, .drawer .shot--dark { background: linear-gradient(160deg, #2a1210, #4a1d17); }
.shot .win { width: 100%; height: 100%; border-radius: 10px 10px 0 0; border: 1px solid #0001; border-bottom: 0; background: #fff; box-shadow: 0 20px 40px -24px #0006;
  overflow: hidden; display: flex; flex-direction: column; transition: translate .5s var(--ease); }
.p:hover .shot .win { translate: 0 -6px; }
.shot .bar { display: flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border-bottom: 1px solid #eee; background: #fafafa; flex: none; }
.shot .bar i { width: 9px; height: 9px; border-radius: 50%; background: #e2e2e2; }
.shot .bar span { margin-left: 8px; font: 10.5px var(--mono); color: #888; }
.shot .win--dark { background: #111318; border-color: #1f2230; } .shot .win--dark .bar { background: #171a21; border-color: #222634; } .shot .win--dark .bar i { background: #2c3040; }
@keyframes blink { 50% { opacity: 0; } }
.shot .win > img { width: 100%; display: block; object-fit: cover; object-position: top; }
.shot .game-row { width: 100%; height: 100%; display: flex; gap: 14px; align-items: flex-end; justify-content: center; }
.shot .win--game { width: auto; height: auto; flex: none; }
.shot .game { height: 168px; width: 95px; overflow: hidden; }  /* 290:512, the game's portrait shape */
.shot .game img { width: 100%; height: 100%; object-fit: cover; object-position: bottom; }
.shot .side { display: grid; gap: 8px; align-self: center; width: 116px; }
.shot .stat { background: #fff; border: 1px solid #0001; border-radius: 8px; padding: 6px 10px; }
.shot .k { font: 9.5px var(--mono); letter-spacing: .08em; text-transform: uppercase; color: #999; }
.shot .stat b { display: block; font-family: var(--display); font-stretch: var(--d-stretch); font-weight: var(--d-weight); font-size: 24px; line-height: 1; color: var(--acc-light); }
.p .meta { padding: 14px 16px 2px; display: flex; justify-content: space-between; align-items: end; gap: 10px; }
.p h3 { margin: 0; font-size: 30px; }
.p.wide { grid-column: span 2; align-self: stretch; }  /* as tall as the card beside it */
.p.wide .body { flex: 1; display: flex; flex-direction: column; }
.p.wide .foot { margin-top: auto; }  /* the extra height goes above the tags, so they line up with the card beside it */
.body { padding: 6px 16px 16px; display: grid; gap: 12px; }
.sum { margin: 0; font-size: 14.5px; line-height: 1.55; color: var(--ink-2); }
.deep { margin: 0; padding-left: 18px; color: var(--ink-2); line-height: 1.6; font-size: 15px; }
.deep li + li { margin-top: 6px; }
.foot { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; }
.tags { display: flex; gap: 5px; flex-wrap: wrap; }
.tags span { font: 11px var(--mono); letter-spacing: .04em; border: 1px solid var(--line); border-radius: 99px; padding: 2px 8px; color: var(--ink-2); }
.links { display: flex; gap: 6px; }
.links a { font: 11px var(--mono); letter-spacing: .08em; text-transform: uppercase; text-decoration: none; padding: 5px 10px; border-radius: 99px; border: 1px solid var(--ink); }
.links a:hover { background: var(--acc); border-color: var(--acc); color: var(--acc-ink); }
.links .priv { font: 11px var(--mono); color: var(--ink-2); }
.more { display: none; width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--line); background: var(--card); place-items: center; font-size: 18px; flex: none;
  transition: background .3s, color .3s, border-color .3s, rotate .3s var(--ease); }

/* cards show the summary line; everything else lives in the panel */
.p:hover .more { background: var(--acc); border-color: var(--acc); color: var(--acc-ink); rotate: 90deg; }
.p .sum { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.js .p { cursor: pointer; }
.js .p .more { display: grid; }
.js .p .deep, .js .p .links { display: none; }
.p .body { padding-top: 4px; grid-template-columns: minmax(0, 1fr); }  /* minmax(0): the no-wrap chip row must not widen the card */
.p .foot { display: block; min-width: 0; }
.js .p .tags { flex-wrap: nowrap; overflow: hidden; }  /* one line; js/main.js swaps the overflow for "+N" */
.p .tags span { flex: none; }
.tags .tag-more { border-style: dashed; }

.drawer .tags::before { content: "Built with"; width: 100%; font: 11px var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2); margin-bottom: 2px; }
.drawer .foot { display: flex; }

/* ---------- arriving at the projects ----------
   1. the heading rises line by line and "built" gets a hand-drawn underline
   2. cards are dealt onto the table: they rise and straighten, one after another
   3. Flappy AI's best score counts up to 300
   4. on hover a card tilts a touch towards the cursor, with a soft light under it */
.scrib { position: relative; display: inline-block; }
.scrib svg { position: absolute; left: -2%; bottom: -.08em; width: 104%; height: .32em; overflow: visible; }
.scrib path { fill: none; stroke: var(--acc); stroke-width: 5; stroke-linecap: round; stroke-dasharray: 520; }
.js .scrib path { stroke-dashoffset: 520; transition: stroke-dashoffset 1.1s var(--ease) .55s; }
.js .in .scrib path { stroke-dashoffset: 0; }

.js .grid .p { opacity: 0; translate: 0 70px; rotate: var(--r, 0deg); scale: .96;
  transition: opacity .7s var(--ease) var(--d), translate .9s var(--ease) var(--d), rotate .9s var(--ease) var(--d), scale .9s var(--ease) var(--d), border-color .3s; }
.js .grid.in .p { opacity: 1; translate: 0 0; rotate: 0deg; scale: 1; }

/* once the cards have landed, drop the entrance delays so hover feels immediate */
.js .grid.settled .p { transition: border-color .3s, transform .5s var(--ease); }
.js .grid.settled .p.tilting { transition: border-color .3s, transform .12s linear; }
.p .vis::after { content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity .3s;
  background: radial-gradient(260px circle at var(--mx, 50%) var(--my, 50%), color-mix(in oklab, var(--acc) 22%, transparent), transparent 70%); }
.p:hover .vis::after { opacity: 1; }

.drawer { margin: 0 0 0 auto; height: 100dvh; max-height: none; max-width: none; width: min(520px, 100vw); border: 0; border-left: 1px solid var(--line); padding: 0;
  background: var(--bg); color: var(--ink); overflow-y: auto; }
.drawer::backdrop { background: color-mix(in oklab, #000 45%, transparent); backdrop-filter: blur(3px); }
.drawer[open] { animation: slide .45s var(--ease); }
@keyframes slide { from { translate: 40px 0; opacity: 0; } }
.drawer .vis { height: 300px; position: relative; overflow: hidden; border-bottom: 1px solid var(--line); }
.drawer .d-in { padding: 22px 26px 32px; display: grid; gap: 16px; }
.drawer h3 { margin: 4px 0 0; font-size: 56px; }
.drawer .sum { font-family: var(--sans); font-size: 18px; font-weight: 500; line-height: 1.5; letter-spacing: -.01em; color: var(--ink); }
.drawer .deep { display: block; }
.drawer .x { position: sticky; top: 12px; float: right; margin: 12px 12px -48px 0; z-index: 2; width: 36px; height: 36px; border-radius: 50%; background: var(--card); border: 1px solid var(--line); font-size: 18px; }

/* ---------- contact ---------- */
/* the heading on one half, a box that emails me on the other */
.contact { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(32px, 6vw, 96px); align-items: center;
  padding: clamp(100px, 14vw, 180px) var(--px) clamp(80px, 10vw, 140px); }
.contact h2 { margin: 18px 0 0; font-size: clamp(90px, 13vw, 220px); }
.contact h2 .S { display: block; }
.mail { display: grid; gap: 18px; background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: clamp(20px, 3vw, 32px); }
.mail .f { display: grid; gap: 8px; }
.mail input, .mail textarea { width: 100%; font: 16px/1.5 var(--sans); color: var(--ink); background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 14px; outline: none; resize: vertical; transition: border-color .2s; }
.mail input::placeholder, .mail textarea::placeholder { color: var(--ink-2); opacity: .7; }
.mail input:focus-visible, .mail textarea:focus-visible { border-color: var(--acc); outline: 2px solid var(--acc); outline-offset: 1px; }
.send { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.send p { margin: 0; }
.send p a { color: var(--ink); text-transform: none; letter-spacing: .02em; text-underline-offset: 3px; }
.send .pill { padding: 11px 20px; font-size: 13px; }
.send .pill:disabled { opacity: .6; cursor: progress; transform: none; }
footer { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; padding: 24px var(--px); border-top: 1px solid var(--line); }

/* ARM IDE: the IDE itself, in its default Catppuccin Mocha theme */
.p .shot--ide, .drawer .shot--ide { background: linear-gradient(160deg, #cba6f7, #89b4fa); }
.shot .win--ide { background: #1e1e2e; border-color: #313244; }
.shot .tabs { display: flex; height: 26px; background: #181825; flex: none; font: 10px var(--mono); }
.shot .tabs span { padding: 7px 12px; color: #6c7086; } .shot .tabs .on { background: #1e1e2e; color: #cdd6f4; }
.shot .ide { position: relative; flex: 1; display: flex; gap: 10px; padding: 10px 12px; }
.shot .ide pre { margin: 0; font: 11px/1.7 var(--mono); }
.shot .gut { color: #45475a; text-align: right; } .shot .code { color: #cdd6f4; }
.shot .op { color: #cba6f7; } .shot .lbl { color: #89b4fa; } .shot .num { color: #fab387; }
.shot .bad { text-decoration: underline wavy #f38ba8; text-underline-offset: 3px; }
.shot .caret { display: inline-block; width: 6px; height: 12px; background: #cdd6f4; vertical-align: -2px; animation: blink 1s steps(1) infinite; }
.shot .ac { position: absolute; left: 78px; top: 112px; display: grid; background: #313244; border: 1px solid #45475a; border-radius: 6px; padding: 3px; font: 10px var(--mono); color: #bac2de; box-shadow: 0 10px 20px -8px #0008; }
.shot .ac span { padding: 2px 10px 2px 6px; border-radius: 4px; } .shot .ac .on { background: #45475a; color: #cdd6f4; }
/* diagram cards (WACC, PintOS, CraveAI): a drawn explanation instead of another window */
.p .shot--diagram, .drawer .shot--diagram { place-items: center; padding: 14px; }
.drawer .shot { padding-top: 60px; }  /* clear the close button; placed after the rules it overrides */
.shot .pipe { width: 100%; height: 100%; }
.shot .pipe .lab text { font: 8px var(--mono); letter-spacing: .12em; fill: #999; text-anchor: middle; }
.shot .pipe .card { fill: #fff; stroke: #0002; }
.shot .pipe .mono { font: 10.5px var(--mono); fill: #222; }
.shot .pipe .arr { fill: none; stroke: var(--acc-light); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.shot .pipe .edge path { stroke: #b9bfd4; stroke-width: 1.5; fill: none; }
.shot .pipe .node circle { fill: #fff; stroke: var(--acc-light); stroke-width: 1.5; }
.shot .pipe .nt text { font: 500 11px var(--mono); fill: var(--acc-light); text-anchor: middle; } .shot .pipe .nt .sm { font-size: 7.5px; }
.shot .pipe .bin { fill: var(--acc-light); }
.shot .pipe .bt text { font: 10.5px var(--mono); fill: #fff; letter-spacing: .04em; }
.shot .sched { width: 100%; background: #fff; border: 1px solid #0001; border-radius: 12px; padding: 12px 14px 10px; box-shadow: 0 20px 40px -26px #0006; display: grid; gap: 7px; transition: translate .5s var(--ease); }
.p:hover .shot .sched { translate: 0 -4px; }
.shot .sched__top { display: flex; justify-content: space-between; align-items: baseline; font-size: 11.5px; }
.shot .sched__top span { font: 9px var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--acc-light); }
.shot .sched__row { display: grid; grid-template-columns: 44px 1fr; align-items: center; gap: 8px; }
.shot .who { font: 500 11px var(--mono); color: #333; } .shot .who i { font-style: normal; font-weight: 400; color: #999; font-size: 9px; }
.shot .track { position: relative; height: 22px; background: #f3f3f6; border-radius: 6px; }
.shot .blk { position: absolute; top: 0; bottom: 0; border-radius: 6px; font: 8.5px var(--mono); display: flex; align-items: center; padding: 0 6px; white-space: nowrap; overflow: hidden; }
.shot .run { background: var(--acc-light); color: #fff; } .shot .run.m { background: var(--acc-soft); } .shot .run.l { background: #c9cdd8; color: #333; }
.shot .wait { background: repeating-linear-gradient(-45deg, #e3e6f2 0 5px, #f3f3f6 5px 10px); color: #777; }
.shot .don { background: #ffb020; color: #2a1a00; }
.shot .sched__axis { display: flex; justify-content: space-between; margin-left: 52px; font: 8.5px var(--mono); color: #aaa; border-top: 1px solid #eee; padding-top: 4px; }
/* Iris: the real app on a backdrop in its own purple */
.p .shot--iris, .drawer .shot--iris { background: linear-gradient(160deg, #8b7cf6, #4c3fb8); }
/* zoom into the centre of the screen (heading + the two recording options) so it reads at card size */
.shot--iris .iris-view { flex: 1; min-height: 0; overflow: hidden; }
.shot--iris .iris-view img { display: block; width: 200%; max-width: none; margin: -8% 0 0 -50%; }
/* Iris: the red recording indicator */
.shot .bar .rec { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; padding: 2px 7px; border-radius: 99px; background: #ffe8e6; color: #d92b1c; font-weight: 500; letter-spacing: .06em; }
.shot .bar .rec i { width: 7px; height: 7px; border-radius: 50%; background: #e5322b; animation: rec 1.4s ease-in-out infinite; }
@keyframes rec { 50% { opacity: .25; } }
/* CraveAI: a row of recommendations, the top pick highlighted */
.shot .recs { width: 100%; background: #fff; border: 1px solid #0001; border-radius: 12px; padding: 11px 12px 12px; box-shadow: 0 20px 40px -26px #0006; display: grid; gap: 9px; transition: translate .5s var(--ease); }
.p:hover .shot .recs { translate: 0 -4px; }
.shot .recs__top { display: flex; justify-content: space-between; align-items: baseline; font-size: 11.5px; }
.shot .recs__top span { font: 9px var(--mono); letter-spacing: .06em; color: #999; }
.shot .recs__row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.shot .pick { position: relative; display: grid; justify-items: start; gap: 2px; padding: 5px 5px 7px; border: 1px solid #ececf1; border-radius: 9px; background: #fafafc; min-width: 0; }
.shot .pick img { width: 100%; height: 46px; object-fit: cover; border-radius: 6px; margin-bottom: 4px; transition: scale .4s var(--ease); }
.p:hover .shot .pick.top img { scale: 1.04; }
.shot .pick b { font-size: 10px; line-height: 1.2; font-weight: 600; color: #111; }
.shot .pick span { font: 8px var(--mono); color: #999; }
.shot .pick i { font: 500 8.5px var(--mono); font-style: normal; color: var(--acc-light); margin-top: 3px; width: 100%; display: grid; gap: 3px; }
.shot .pick i::after { content: ""; height: 3px; border-radius: 2px; background: linear-gradient(90deg, var(--acc-light) var(--m), #e6e8f2 var(--m)); }
.shot .pick.top { border-color: var(--acc-light); background: color-mix(in oklab, var(--acc-light) 5%, #fff); box-shadow: 0 0 0 2px color-mix(in oklab, var(--acc-light) 13%, transparent); }
.shot .pick em { position: absolute; z-index: 1; top: -7px; right: 6px; font: 500 7.5px var(--mono); font-style: normal; letter-spacing: .06em; text-transform: uppercase; background: var(--acc-light); color: #fff; padding: 2px 5px; border-radius: 99px; }
/* GraphDB: the graph and its write-ahead log, side by side; transaction t18 adds C→G in both, on a loop */
.shot .gdb { flex: 1; min-height: 0; display: grid; grid-template-columns: 1.1fr 1fr; }
.shot .gdb__graph { width: 100%; height: 100%; padding: 6px; }
.shot .gdb .e path { stroke: #3a4054; stroke-width: 1.6; fill: none; }
.shot .gdb .e-new { stroke: var(--acc-soft); stroke-width: 2.4; fill: none; stroke-dasharray: 1; stroke-dashoffset: 1; animation: gdb-edge 6s var(--ease) infinite; }
.shot .gdb .n circle { fill: #1b1f2b; stroke: #5b6380; stroke-width: 1.4; }
.shot .gdb .n-hot circle { fill: none; stroke: var(--acc-soft); stroke-width: 2; opacity: 0; animation: gdb-hot 6s var(--ease) infinite; }
.shot .gdb .t text { font: 500 9.5px var(--mono); fill: #c9cdd8; text-anchor: middle; }
.shot .gdb__wal { border-left: 1px solid #222634; padding: 10px 12px; font: 9.5px/1 var(--mono); color: #9aa1b5; display: grid; align-content: start; gap: 7px; }
.shot .gdb__wal > div { display: grid; grid-template-columns: 30px 24px 1fr 56px; gap: 6px; white-space: nowrap; }
.shot .wal-h { color: #5b6380; letter-spacing: .08em; padding-bottom: 6px; border-bottom: 1px solid #222634; }
.shot .wal-r .ok { color: #6ee7a8; }
.shot .wal-r.new { color: color-mix(in oklab, var(--acc-soft) 45%, #fff); opacity: 0; }
.shot .wal-r.n1 { animation: gdb-row1 6s var(--ease) infinite; } .shot .wal-r.n2 { animation: gdb-row2 6s var(--ease) infinite; }
@keyframes gdb-edge { 0%, 8% { stroke-dashoffset: 1; } 30%, 90% { stroke-dashoffset: 0; opacity: 1; } 100% { stroke-dashoffset: 0; opacity: 0; } }
@keyframes gdb-hot { 0%, 5% { opacity: 0; } 12%, 88% { opacity: 1; } 100% { opacity: 0; } }
@keyframes gdb-row1 { 0%, 22% { opacity: 0; translate: 0 4px; } 30%, 90% { opacity: 1; translate: 0; } 100% { opacity: 0; } }
@keyframes gdb-row2 { 0%, 38% { opacity: 0; translate: 0 4px; } 46%, 90% { opacity: 1; translate: 0; } 100% { opacity: 0; } }
@media (max-width: 960px) {
  .contact { grid-template-columns: 1fr; }
  .grid { grid-template-columns: repeat(2, 1fr); }
  .row { grid-template-columns: 1fr; }
  .row > .tile, .row > .cap, .row > h3, .row > .role, .row > p, .row > .desc { grid-column: 1; grid-row: auto; }
}
@media (max-width: 620px) {
  #theme { margin-left: auto; }
  .shot .gdb { grid-template-columns: 1fr 1fr; }
  .shot .gdb__wal { padding: 8px; font-size: 8.5px; }
  .shot .gdb__wal > div { grid-template-columns: 26px 1fr 44px; gap: 4px; }
  .shot .gdb__wal > div > span:nth-child(2) { display: none; }  /* TXN: the first column to go on a small screen */
  .nav .mid { display: none; }
  .nav .pill { display: none; }
  .grid { grid-template-columns: 1fr; }
  .p.wide { grid-column: auto; }
  .drawer { width: 100vw; border-left: 0; }
  .hero__name .D { font-size: 35vw; }  /* "JASON" runs about 2.4em wide: 35vw keeps it on one line down to 320px */
  .hero__name .S { font-size: 30vw; }
  .hn { position: static; display: block; font-size: 8.7vw; margin-top: 8px; }
  .hero .sig { width: 50vw; top: 318px; right: 3vw; rotate: -8deg; }
  .scroll { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tz i, .shot .caret, .shot .bar .rec i { animation: none; }
  .shot .gdb .e-new { animation: none; stroke-dashoffset: 0; } .shot .gdb .n-hot circle, .shot .wal-r.new { animation: none; opacity: 1; }
  .js .rv, .js .line-rv > span, .js .grid .p, .js [data-a] .ch, .js .hero__top, .js .hero .hn, .js .hero .scroll,
  .js .clock__tabs, .js .row > *
    { opacity: 1; translate: none; transform: none; rotate: none; scale: none; transition: none; }
  .js .sig text { stroke-dashoffset: 0; fill-opacity: 1; animation: none; }
  .js .scrib path { stroke-dashoffset: 0; transition: none; }
  .js [data-a="curtain"] { clip-path: none; transition: none; }
  [data-a="light"] .wd, [data-a="scramble"] .ch { opacity: 1; transition: none; }
  .js .rows::before, .js .row::after { transform: none; transition: none; }
  .js .sig.on text, .js .hero .sig.on text { animation: none; }
  .js .clock__tabs:not(.in) #t-on, .js .clock__tabs:not(.in) #t-off { transform: none; }
  .js .clock__tabs button { transition: none; }
  .drawer[open] { animation: none; }
  .js .nav { transition: none; }
  html { scroll-behavior: auto; }
}

/* logos whose files carry their own padding fill more of the tile */
.tile img.fill { width: 100%; height: 100%; }
.tile img.fill-qrt { width: 94%; height: 94%; }
