:root {
  --paper: #e9ebee;
  --white: #ffffff;
  --ink: #000000;
  --sub: #50565e;
  --rule: #c2c6cc;
  --works: #146c35;
  --partial: #8a5300;
  --patched: #a8231a;
  --font: "Archivo", ui-sans-serif, system-ui, sans-serif;
  --code: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --gutter: clamp(20px, 4vw, 48px);
}

*, *::before, *::after { box-sizing: border-box; }

html, body { margin: 0; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { text-decoration-thickness: 2px; }
:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------------------------------------
   Page: one family (Archivo), width axis does the work. Condensed and heavy for display,
   normal width for reading. Colour only where it carries meaning (device status).
   ------------------------------------------------------------------------------------------- */
.stage {
  position: relative;
  z-index: 1;
  width: min(1200px, 100% - 2 * var(--gutter));
  margin: 0 auto;
}

.hero { padding-top: clamp(48px, 12vh, 128px); }

.wordmark {
  margin: 0 0 0 -0.03em;
  display: flex;
  align-items: baseline;
  font-size: clamp(60px, 25vw, 330px);
  font-stretch: 62%;
  font-weight: 850;
  line-height: 0.78;
  letter-spacing: -0.02em;
}
.wordmark .ch { display: inline-block; position: relative; isolation: isolate; }
/* Heat copies for the reveal: the letters land hot and cool to black (opacity only) */
.wordmark .ch::before,
.wordmark .ch::after {
  content: attr(data-c);
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
}
.wordmark .ch::before { color: #ff8a00; }
/* No text-shadow: any blur on these copies, combined with the page-wide kick animation, makes
   Firefox re-rasterise them every frame (a ~90 ms hitch at the start of the reveal). */
.wordmark .ch::after { z-index: -1; color: #ff4d00; }
.wordmark .tld { display: inline-block; }

.lede {
  margin: clamp(24px, 4vh, 40px) 0 0;
  max-width: 34ch;
  font-size: clamp(21px, 2.1vw, 26px);
  line-height: 1.35;
}

section > h2 {
  margin: 0;
  padding-bottom: 14px;
  font-size: clamp(30px, 3.6vw, 40px);
  font-stretch: 70%;
  font-weight: 800;
  line-height: 1;
  border-bottom: 2px solid var(--ink);
}

/* projects: name, one line, how well it works */
.projects { margin-top: clamp(80px, 12vh, 128px); }
.project-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 0 clamp(24px, 4vw, 48px);
}
.project-list li { padding: 22px 0 26px; border-bottom: 1px solid var(--rule); }
.project-list h3 {
  margin: 0 0 6px;
  font-size: 26px;
  font-stretch: 75%;
  font-weight: 800;
  line-height: 1.1;
}
.project-list p { margin: 0; max-width: 40ch; }
.project-list .status { margin-top: 10px; font-size: 15px; font-weight: 600; color: var(--works); }
.project-list [data-status="mostly"] .status { color: var(--partial); }
.project-list [data-status="retired"] .status { color: var(--sub); font-weight: 400; }

/* blog: date in its own column, text at a reading measure */
.blog { margin-top: clamp(64px, 11vh, 120px); }
.post {
  display: grid;
  grid-template-columns: 180px minmax(0, 66ch);
  gap: 0 clamp(24px, 4vw, 56px);
  padding: 28px 0 12px;
  border-bottom: 1px solid var(--rule);
}
.post-date { margin: 0; padding-top: 0.4em; color: var(--sub); font-size: 15px; }
.post-body { font-size: 19px; line-height: 1.6; }
.post-body h3 {
  margin: 0 0 14px;
  font-size: clamp(28px, 3.2vw, 38px);
  font-stretch: 72%;
  font-weight: 800;
  line-height: 1.05;
  text-wrap: balance;
}
.post-body p { margin: 0 0 18px; }
.post-hash code {
  font-family: var(--code);
  font-size: 16px;
  word-break: break-all;
}
.post-body pre {
  margin: 0 0 18px;
  padding: 14px 16px;
  overflow-x: auto;
  font-family: var(--code);
  font-size: 15px;
  line-height: 1.55;
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: 4px;
}

.foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px 24px;
  margin-top: clamp(72px, 12vh, 128px);
  padding: 20px 0 28px;
  border-top: 1px solid var(--rule);
  color: var(--sub);
  font-size: 14px;
}
.foot p { margin: 0; }
#replay {
  padding: 0;
  font: inherit;
  color: var(--ink);
  background: none;
  border: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

@media (max-width: 760px) {
  .post { grid-template-columns: minmax(0, 1fr); }
  .post-date { padding: 0 0 8px; }
  .post-body { font-size: 18px; }
}


/* ---------------------------------------------------------------------------------------------
   Blast layers
   ------------------------------------------------------------------------------------------- */
#kaboom {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 100;
  pointer-events: none;
  display: none;
}
html.kb-live #kaboom { display: block; }

#veil {
  position: fixed;
  inset: 0;
  z-index: 99;
  pointer-events: none;
  background: #000;
  display: none;
}
html.kb-armed:not(.kb-lit):not(.kb-done) #veil,
html.kb-fallback #veil { display: block; }

/* No-WebGPU fallback: flash, then a hole burns open in the veil */
@property --hole {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}
html.kb-fallback #veil {
  background: radial-gradient(circle at 50% 50%, #fff6dc 0%, #ffb347 12%, #ff4d00 30%, #1a0802 62%, #000 80%);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, transparent var(--hole), #000 calc(var(--hole) + 6%));
  mask-image: radial-gradient(circle at 50% 50%, transparent var(--hole), #000 calc(var(--hole) + 6%));
  animation: fb-burn 1.5s cubic-bezier(0.5, 0, 0.2, 1) both;
}
@keyframes fb-burn {
  0% { --hole: 0%; filter: brightness(0); }
  8% { filter: brightness(2.5); }
  28% { --hole: 0%; filter: brightness(1); }
  100% { --hole: 120%; filter: brightness(0.6); }
}

/* ---------------------------------------------------------------------------------------------
   Reveal choreography: runs while the WebGPU burn-through is eating the overlay
   ------------------------------------------------------------------------------------------- */

/* the whole page gets kicked by the blast: scale for the punch, translate for the shake */
html.kb-revealed .stage {
  transform-origin: 50% var(--kick-y, 50svh);
  animation:
    kick 1.6s cubic-bezier(0.12, 0.9, 0.2, 1) both,
    shake 0.75s linear both;
}
@keyframes kick {
  from { scale: 1.12; }
  to { scale: 1; }
}
@keyframes shake {
  0% { translate: 0 0; }
  10% { translate: -14px 8px; }
  20% { translate: 12px -10px; }
  30% { translate: -9px -6px; }
  42% { translate: 7px 6px; }
  56% { translate: -4px 3px; }
  70% { translate: 3px -2px; }
  85% { translate: -1px 1px; }
  100% { translate: 0 0; }
}

/* the wordmark lands hot and cools to black (opacity + transform only) */
html.kb-revealed .wordmark .ch {
  animation: slam 0.9s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  animation-delay: calc(var(--d) * 70ms + 60ms);
}
html.kb-revealed .wordmark .ch::before {
  animation: cool-core 1.6s ease-out both;
  animation-delay: calc(var(--d) * 70ms + 60ms);
}
html.kb-revealed .wordmark .ch::after {
  animation: cool-glow 3s ease-out both;
  animation-delay: calc(var(--d) * 70ms + 60ms);
}
@keyframes slam {
  from { opacity: 0; translate: 0 0.2em; scale: 1.35; }
  16% { opacity: 1; translate: 0 0; scale: 1; }
  to { opacity: 1; translate: 0 0; scale: 1; }
}
@keyframes cool-core {
  from { opacity: 1; }
  to { opacity: 0; }
}
@keyframes cool-glow {
  from { opacity: 1; }
  30% { opacity: 0.75; }
  to { opacity: 0; }
}

html.kb-revealed .wordmark .tld { animation: rise 1.1s var(--ease-out) 0.9s both; }

html.kb-revealed .rise {
  animation: rise 1.1s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 90ms + 450ms);
}
@keyframes rise {
  from { opacity: 0; translate: 0 22px; }
  to { opacity: 1; translate: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  html.kb-revealed .stage,
  html.kb-revealed .rise,
  html.kb-revealed .wordmark .ch,
  html.kb-revealed .wordmark .ch::before,
  html.kb-revealed .wordmark .ch::after,
  html.kb-revealed .wordmark .tld {
    animation-duration: 0.01ms;
    animation-delay: 0s;
  }
}

/* Pre-promote everything the reveal animates. Without this, the layers (incl. the glow copies of
   each letter) are created and rasterised in the single reveal frame: a visible hitch. With it
   they are rasterised at load, under the black fuse, while the GPU is otherwise idle. The heat
   copies sit at an invisible 0.001 instead of 0, because fully transparent layers are skipped. */
html.kb-armed .stage,
html.kb-armed .wordmark .ch,
html.kb-armed .wordmark .tld,
html.kb-armed .rise { will-change: opacity, translate, scale; }
html.kb-armed .wordmark .ch::before,
html.kb-armed .wordmark .ch::after { opacity: 0.001; will-change: opacity; }
html.kb-done .wordmark .ch::before,
html.kb-done .wordmark .ch::after { will-change: auto; }
