/* ============================================================
   Naresh Sain — portfolio
   Monochrome by choice: emphasis comes from weight, scale, rule
   and motion, never from hue. One column, no navigation — the
   site is short enough that nav would only be decoration.
   ============================================================ */

/* ---------- Tokens: light is the base layer ---------- */
:root {
  color-scheme: light;

  /* Warm bone paper, warm-black ink. */
  --paper: #f2f1ed;
  --surface: #fbfaf7;
  --surface-2: #e7e5df;
  --ink: #17181a;
  --ink-2: #5f6167;
  --ink-3: #9a9da2;
  --rule: rgba(23, 24, 26, 0.13);
  --rule-2: rgba(23, 24, 26, 0.32);
  --plate: #0a0b0c;
  --shadow: 0 26px 60px -30px rgba(23, 24, 26, 0.5);

  --font-display: "Familjen Grotesk", "Helvetica Neue", Arial, sans-serif;
  --font-body: "Newsreader", Georgia, "Times New Roman", serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --measure: 1080px;
  --gutter: clamp(22px, 6vw, 64px);
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ---------- Tokens: dark, for system preference and the toggle ---------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --paper: #101112;
    --surface: #191b1d;
    --surface-2: #23262a;
    --ink: #f0efec;
    --ink-2: #93969b;
    --ink-3: #5d6165;
    --rule: rgba(240, 239, 236, 0.14);
    --rule-2: rgba(240, 239, 236, 0.34);
    --plate: #08090a;
    --shadow: 0 26px 60px -30px rgba(0, 0, 0, 0.9);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --paper: #101112;
  --surface: #191b1d;
  --surface-2: #23262a;
  --ink: #f0efec;
  --ink-2: #93969b;
  --ink-3: #5d6165;
  --rule: rgba(240, 239, 236, 0.14);
  --rule-2: rgba(240, 239, 236, 0.34);
  --plate: #08090a;
  --shadow: 0 26px 60px -30px rgba(0, 0, 0, 0.9);
}

/* ============================================================
   Base
   ============================================================ */
*,
*::before,
*::after { margin: 0; padding: 0; box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background 0.4s var(--ease), color 0.4s var(--ease);
}

a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
ol, ul { list-style: none; }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 3px;
}

::selection { background: var(--ink); color: var(--paper); }

.band-note,
.side-label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.page {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* ============================================================
   Masthead
   ============================================================ */
.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 36px 0;
}

.who { display: flex; align-items: center; gap: 13px; }

.who-avatar {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  overflow: hidden;
  background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--rule);
  transition: box-shadow 0.3s var(--ease), transform 0.45s var(--ease-out);
}

.who:hover .who-avatar {
  transform: scale(1.06);
  box-shadow: 0 0 0 1px var(--rule-2);
}

.who-avatar img { width: 100%; height: 100%; object-fit: cover; }

.who-text { display: flex; flex-direction: column; min-width: 0; }

.who-name {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.25;
}

.who-role {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--ink-2);
  margin-top: 2px;
}
.who-role a { border-bottom: 1px solid var(--rule-2); }
.who-role a:hover { color: var(--ink); border-color: currentColor; }

.tools { display: flex; gap: 6px; }

.tool {
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: 9px;
  color: var(--ink-2);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease),
    background 0.2s var(--ease), transform 0.2s var(--ease);
}
.tool svg { width: 15px; height: 15px; }
.tool:hover { color: var(--ink); border-color: var(--rule); background: var(--surface); }
.tool:active { transform: scale(0.93); }

.tool .icon-sun { display: none; }
:root[data-theme="dark"] .tool .icon-sun { display: block; }
:root[data-theme="dark"] .tool .icon-moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .tool .icon-sun { display: block; }
  :root:not([data-theme="light"]) .tool .icon-moon { display: none; }
}

/* ============================================================
   Opening
   ============================================================ */
.opening { padding: clamp(56px, 12vh, 128px) 0 clamp(72px, 14vh, 148px); }

.statement {
  font-family: var(--font-display);
  font-size: clamp(36px, 6vw, 82px);
  font-weight: 500;
  line-height: 1.02;
  letter-spacing: -0.035em;
  text-wrap: balance;
  max-width: 16em;
}

/* Words rise in on load; his three verbs shift into the serif italic. */
.statement .w {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.38em);
  animation: word-in 0.85s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 40ms + 80ms);
}

/* The inner span carries the pointer lean, so it never fights the load
   animation sitting on .w. */
.statement .wi {
  display: inline-block;
  transition: transform 0.5s var(--ease-out);
  will-change: transform;
}

.statement .ch {
  display: inline-block;
  white-space: pre;
}

.verb { cursor: default; }

/* play — a hop that travels across the letters */
.verb[data-anim="bounce"].anim .ch {
  animation: ch-bounce 0.62s var(--ease-out) both;
  animation-delay: calc(var(--c) * 34ms);
}
@keyframes ch-bounce {
  0% { transform: none; }
  38% { transform: translateY(-0.26em) rotate(-3deg); }
  66% { transform: translateY(0.04em) rotate(1deg); }
  100% { transform: none; }
}

/* build — lifted and set back down, firmly, one at a time */
.verb[data-anim="snap"].anim .ch {
  animation: ch-snap 0.5s cubic-bezier(0.3, 0, 0.2, 1) both;
  animation-delay: calc(var(--c) * 52ms);
}
@keyframes ch-snap {
  0% { transform: none; }
  42% { transform: translateY(-0.34em); }
  70% { transform: translateY(0.05em) scaleY(0.94); }
  100% { transform: none; }
}

/* hang out — an unhurried sway, letters trailing each other */
.verb[data-anim="sway"].anim .ch {
  animation: ch-sway 1.15s cubic-bezier(0.45, 0, 0.55, 1) both;
  animation-delay: calc(var(--c) * 46ms);
}
@keyframes ch-sway {
  0% { transform: none; }
  30% { transform: rotate(-4deg) translateY(-0.05em); }
  65% { transform: rotate(4deg) translateY(0.03em); }
  100% { transform: none; }
}

.statement .lit {
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 300;
  letter-spacing: -0.012em;
}

@keyframes word-in { to { opacity: 1; transform: none; } }

.statement-sub {
  margin-top: 34px;
  max-width: 48ch;
  color: var(--ink-2);
  font-size: 18px;
  opacity: 0;
  animation: word-in 0.85s var(--ease-out) 0.7s forwards;
}

.statement-sub .place { color: var(--ink); }

.statement-sub .clock {
  font-family: var(--font-mono);
  font-size: 15px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   Bands
   ============================================================ */
.band { padding-bottom: clamp(72px, 11vh, 120px); }

.band-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding-bottom: 20px;
}

.band-title {
  font-family: var(--font-display);
  font-size: clamp(20px, 2.2vw, 26px);
  font-weight: 600;
  letter-spacing: -0.02em;
}

/* Scroll-reveal (JS adds this; without JS everything stays visible) */
.reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out);
  transition-delay: var(--d, 0ms);
}
.reveal.shown { opacity: 1; transform: none; }

/* ============================================================
   Work index
   Hover is a single drawn line and a small shift — no fills,
   no padding jump, nothing that reflows under the cursor.
   ============================================================ */
.index-list { border-top: 1px solid var(--rule); }

.index-row {
  position: relative;
  border-bottom: 1px solid var(--rule);
}

/* The line that draws itself across on hover. */
.index-row::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.65s var(--ease-out);
}
.index-row:hover::after,
.index-row.open::after { transform: scaleX(1); }

.index-btn {
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr) auto 22px;
  align-items: baseline;
  gap: 24px;
  width: 100%;
  padding: 34px 0;
  text-align: left;
}

.index-num {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-3);
  letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums;
  transition: color 0.35s var(--ease);
}
.index-row:hover .index-num { color: var(--ink); }

.index-title {
  font-family: var(--font-display);
  font-size: clamp(28px, 3.4vw, 44px);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.04;
  transition: transform 0.55s var(--ease-out);
}
.index-row:hover .index-title { transform: translateX(10px); }

.index-meta {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-3);
  letter-spacing: 0.02em;
  text-align: right;
  transition: color 0.35s var(--ease);
}
.index-row:hover .index-meta { color: var(--ink-2); }

.index-plus {
  position: relative;
  width: 15px;
  height: 15px;
  justify-self: end;
  align-self: center;
  color: var(--ink-3);
  transition: color 0.35s var(--ease), transform 0.55s var(--ease-out);
}
.index-plus::before,
.index-plus::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1px;
  background: currentColor;
  transition: transform 0.5s var(--ease-out), opacity 0.3s var(--ease);
}
.index-plus::after { transform: rotate(90deg); }

.index-row:hover .index-plus { color: var(--ink); }
.index-row.open .index-plus { color: var(--ink); transform: rotate(180deg); }
.index-row.open .index-plus::after { transform: rotate(0deg); opacity: 0; }

/* Expanding drawer */
.index-drawer {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.62s var(--ease-out);
}
.index-row.open .index-drawer { grid-template-rows: 1fr; }
.index-drawer > div {
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s linear 0.62s;
}
.index-row.open .index-drawer > div {
  visibility: visible;
  transition-delay: 0s;
}

.drawer-inner {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 52px);
  padding: 6px 0 44px;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.5s var(--ease) 0.12s, transform 0.5s var(--ease-out) 0.12s;
}
.index-row.open .drawer-inner { opacity: 1; transform: none; }

.drawer-plate {
  border-radius: 8px;
  overflow: hidden;
  background: var(--plate);
  box-shadow: 0 0 0 1px var(--rule);
}
.drawer-plate img { width: 100%; }

.drawer-facts { display: flex; flex-direction: column; }

.fact {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: 12px;
}
.fact dt {
  color: var(--ink-3);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 10px;
}
.fact dd { color: var(--ink); text-align: right; }

.drawer-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 22px;
}

.action {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 15px;
  border: 1px solid var(--rule-2);
  border-radius: 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink);
  transition: background 0.25s var(--ease), color 0.25s var(--ease),
    border-color 0.25s var(--ease), transform 0.2s var(--ease);
}
.action svg { width: 14px; height: 14px; }
.action:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.action:active { transform: scale(0.97); }
.action span { transition: transform 0.3s var(--ease-out); }
.action:hover span { transform: translateX(3px); }

.action-note {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-3);
}

@media (max-width: 760px) {
  .index-btn {
    grid-template-columns: 32px minmax(0, 1fr) 18px;
    gap: 14px;
    padding: 26px 0;
  }
  .index-meta { grid-column: 2; text-align: left; margin-top: 6px; }
  .index-row:hover .index-title { transform: none; }
  .drawer-inner { grid-template-columns: 1fr; }
}

/* ============================================================
   Explorations
   ============================================================ */
.plates {
  columns: 3;
  column-gap: clamp(16px, 2.4vw, 30px);
  padding-bottom: 24px;
}

.plate {
  break-inside: avoid;
  margin-bottom: clamp(16px, 2.4vw, 30px);
}

.plate figure { margin: 0; }

.plate-btn {
  display: block;
  width: 100%;
  text-align: left;
  cursor: zoom-in;
}

.plate-img {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
  background: var(--plate);
  box-shadow: 0 0 0 1px var(--rule);
  transition: box-shadow 0.45s var(--ease), transform 0.45s var(--ease-out);
}

.plate-img img { width: 100%; transition: transform 0.7s var(--ease-out); }

.plate:hover .plate-img {
  transform: translateY(-5px);
  box-shadow: 0 0 0 1px var(--rule-2), var(--shadow);
}
.plate:hover .plate-img img { transform: scale(1.035); }

.plate figcaption {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin-top: 13px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-2);
}
.plate figcaption .cap-idx { color: var(--ink-3); font-size: 10px; letter-spacing: 0.08em; }

@media (max-width: 900px) { .plates { columns: 2; } }
@media (max-width: 560px) { .plates { columns: 1; } }

/* ============================================================
   About
   ============================================================ */
.about-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 80px);
  border-top: 1px solid var(--rule);
  padding-top: 40px;
}

.about-prose { display: flex; flex-direction: column; gap: 20px; max-width: 60ch; }
.about-prose p { font-size: 19px; line-height: 1.62; }

/* ---------- The duality ----------
   Two poles, the line between them, and a marker that never settles.
   The composition is the sentence: always somewhere between the two. */
.duality {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  grid-template-areas:
    "a mid b"
    "cap cap cap";
  align-items: center;
  gap: 0 clamp(14px, 2.6vw, 30px);
  margin: 10px 0 14px;
  padding: 34px 0 26px;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.about-prose > .duality:first-child {
  border-top: none;
  padding-top: 6px;
}

.pole { display: flex; flex-direction: column; gap: 3px; }
.pole-a { grid-area: a; text-align: right; }
.pole-b { grid-area: b; text-align: left; }

.pole-name {
  font-family: var(--font-display);
  font-size: clamp(21px, 2.7vw, 30px);
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.1;
}

.pole-by {
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 300;
  font-size: clamp(15px, 1.5vw, 17px);
  color: var(--ink-2);
  line-height: 1.2;
}

.spectrum {
  grid-area: mid;
  width: clamp(96px, 13vw, 148px);
  height: auto;
  color: var(--ink);
  overflow: visible;
}

.spectrum .drift {
  animation: drift 7s cubic-bezier(0.45, 0, 0.55, 1) infinite alternate;
  /* A ground-coloured stroke under the fill keeps the marker clean
     wherever it sits on the rule. */
  stroke: var(--paper);
  stroke-width: 3;
  paint-order: stroke;
}

@keyframes drift {
  from { transform: translateX(-28px); }
  to { transform: translateX(28px); }
}

/* While the pointer is over the composition it drives the marker directly,
   and the pole you lean toward gains weight. */
.duality.live .drift {
  animation: none;
  transform: translateX(calc((var(--t, 0.5) - 0.5) * 58px));
  transition: transform 0.28s var(--ease-out);
}
.duality.live .spectrum { color: var(--ink); }

.pole { transition: opacity 0.35s var(--ease); }
.duality.live .pole-a { opacity: calc(0.5 + (1 - var(--t, 0.5)) * 1); }
.duality.live .pole-b { opacity: calc(0.5 + var(--t, 0.5) * 1); }

.duality { cursor: crosshair; }

.duality figcaption {
  grid-area: cap;
  margin-top: 26px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* Narrow screens keep the same horizontal composition — the rule just gets
   shorter and the type steps down. Turning it vertical broke the reading. */
@media (max-width: 620px) {
  .duality {
    gap: 0 clamp(8px, 3vw, 18px);
    padding: 28px 0 22px;
  }
  .spectrum { width: clamp(46px, 15vw, 96px); }
  .pole-name { font-size: clamp(17px, 5.4vw, 26px); }
  .pole-by { font-size: clamp(12px, 3.5vw, 15px); }
  .duality figcaption {
    margin-top: 20px;
    font-size: 9.5px;
    letter-spacing: 0.1em;
  }
}

.side-label { margin-bottom: 16px; }

.connect-list { border-top: 1px solid var(--rule); }
.connect-list li { position: relative; border-bottom: 1px solid var(--rule); }

/* The copy button sits out of flow, so every row's value keeps the same
   right edge whether or not it has one. */
.c-copy {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 7px;
  padding: 4px 10px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s var(--ease), color 0.2s var(--ease),
    border-color 0.2s var(--ease);
}
.connect-list li.has-copy:hover .c-copy,
.c-copy:focus-visible,
.c-copy.done {
  opacity: 1;
  pointer-events: auto;
}
.c-copy:hover { color: var(--ink); border-color: var(--rule-2); }
.c-copy.done { color: var(--ink); border-color: var(--rule-2); }

/* The value steps aside to make room, only while the row is hovered. */
.connect-list li.has-copy .c-value { transition: transform 0.3s var(--ease-out); }
.connect-list li.has-copy:hover .c-value { transform: translateX(-72px); }

.connect-list a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--ink-2);
  transition: color 0.25s var(--ease);
}
.connect-list a:hover { color: var(--ink); }
/* Hovering a row lights the whole row, name included. */
.connect-list a:hover .c-label { color: var(--ink-2); }

.connect-list .c-label {
  color: var(--ink-3);
  transition: color 0.25s var(--ease);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.connect-list .c-value { text-align: right; word-break: break-all; }

@media (max-width: 860px) {
  .about-wrap { grid-template-columns: 1fr; }
}

/* ============================================================
   Footer
   ============================================================ */
.foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 24px 0 72px;
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.foot-md {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: uppercase;
  border-bottom: 1px solid var(--rule-2);
  padding-bottom: 2px;
}
.foot-md:hover { color: var(--ink); border-color: currentColor; }

/* ============================================================
   Hover peek — a closed row shows its plate under the cursor
   ============================================================ */
.peek {
  position: fixed;
  top: 0;
  left: 0;
  width: 216px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--plate);
  box-shadow: 0 0 0 1px var(--rule-2), var(--shadow);
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.95);
  transition: opacity 0.22s var(--ease), transform 0.34s var(--ease-out);
  z-index: 40;
}
.peek.on { opacity: 1; transform: translate(-50%, -50%) scale(1); }
@media (hover: none), (max-width: 900px) { .peek { display: none; } }

/* ============================================================
   Markdown sheet
   ============================================================ */
.sheet {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: end center;
  background: color-mix(in srgb, var(--paper) 70%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  animation: sheet-fade 0.3s var(--ease);
}
.sheet[hidden] { display: none; }

@keyframes sheet-fade { from { opacity: 0; } to { opacity: 1; } }

.sheet-panel {
  width: min(760px, 100%);
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-bottom: none;
  border-radius: 12px 12px 0 0;
  box-shadow: var(--shadow);
  animation: sheet-rise 0.45s var(--ease-out);
}

@keyframes sheet-rise { from { transform: translateY(24px); } to { transform: none; } }

.sheet-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--rule);
}

.sheet-title {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.sheet-actions { display: flex; gap: 6px; }

.sheet-actions button,
.sheet-actions a {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-2);
  padding: 6px 12px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  transition: color 0.2s var(--ease), background 0.2s var(--ease),
    border-color 0.2s var(--ease);
}
.sheet-actions button:hover,
.sheet-actions a:hover {
  color: var(--ink);
  background: var(--surface-2);
  border-color: var(--rule-2);
}

.sheet-body {
  overflow: auto;
  padding: 22px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.75;
  white-space: pre-wrap;
  word-wrap: break-word;
  color: var(--ink-2);
}

@media (max-width: 520px) {
  body { font-size: 16px; }
  .about-prose p { font-size: 17px; }
  .masthead { padding: 26px 0; }
}

/* ============================================================
   Motion preferences
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
  }
  .statement .w,
  .statement-sub { opacity: 1; transform: none; }
  .statement .wi, .statement .ch { transform: none !important; animation: none !important; }
  .spectrum .drift { animation: none; }
  .tile { opacity: 1; animation: none; }
  .canvas-hint { animation: none; }
  .reveal { opacity: 1; transform: none; }
}

/* ============================================================
   Canvas — a project's frames on a plane you can pan and zoom
   ============================================================ */
body.canvas-open { overflow: hidden; }

.canvas {
  position: fixed;
  inset: 0;
  z-index: 95;
  background: var(--paper);
  animation: canvas-in 0.4s var(--ease-out);
}
.canvas[hidden] { display: none; }

@keyframes canvas-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.canvas-plane {
  position: absolute;
  inset: 0;
  overflow: hidden;
  cursor: grab;
  touch-action: none;
  /* The dot grid is painted here and offset by the pan, so the plane
     reads as one continuous surface rather than a scrolling box. */
  background-image: radial-gradient(circle, var(--rule) 1px, transparent 1px);
  background-size: 32px 32px;
}
.canvas-plane.grabbing { cursor: grabbing; }

.canvas-surface {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
}

.tile {
  position: absolute;
  margin: 0;
  border-radius: 10px;
  background: var(--plate);
  box-shadow: 0 0 0 1px var(--rule), var(--shadow);
  opacity: 0;
  animation: tile-in 0.6s var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 55ms + 90ms);
  transition: box-shadow 0.35s var(--ease);
}

.tile:hover { box-shadow: 0 0 0 1px var(--rule-2), var(--shadow); }

.tile img {
  width: 100%;
  border-radius: 10px;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}

.tile figcaption {
  position: absolute;
  top: -22px;
  left: 2px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  color: var(--ink-3);
}

@keyframes tile-in {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

/* Canvas chrome */
.canvas-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 16px clamp(16px, 3vw, 28px);
  border-bottom: 1px solid var(--rule);
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.canvas-id { display: flex; align-items: baseline; gap: 12px; min-width: 0; }

.canvas-title {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.canvas-count {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}

.canvas-actions { display: flex; align-items: center; gap: 10px; }

.zoomer {
  display: flex;
  align-items: center;
  border: 1px solid var(--rule);
  border-radius: 8px;
  overflow: hidden;
}

.zoomer button {
  height: 32px;
  min-width: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-2);
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.zoomer button:hover { background: var(--surface-2); color: var(--ink); }

.zoomer .zoom-level {
  min-width: 62px;
  font-size: 10.5px;
  letter-spacing: 0.08em;
  border-left: 1px solid var(--rule);
  border-right: 1px solid var(--rule);
  font-variant-numeric: tabular-nums;
}

.canvas-close {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 34px;
  padding: 0 8px 0 14px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease),
    background 0.2s var(--ease);
}
.canvas-close:hover { color: var(--ink); border-color: var(--rule-2); background: var(--surface); }

.kbd {
  display: inline-flex;
  align-items: center;
  padding: 2px 6px;
  border-radius: 5px;
  background: var(--surface-2);
  font-size: 9.5px;
  color: var(--ink-3);
}

.canvas-hint {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  padding: 8px 14px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  white-space: nowrap;
  animation: hint-fade 6s var(--ease) forwards;
  pointer-events: none;
}

@keyframes hint-fade {
  0%, 62% { opacity: 1; transform: translateX(-50%); }
  100% { opacity: 0; transform: translateX(-50%) translateY(6px); }
}

@media (max-width: 620px) {
  .canvas-count { display: none; }
  .canvas-hint { font-size: 9.5px; white-space: normal; text-align: center; max-width: 82vw; }
  .canvas-close span.kbd { display: none; }
  .canvas-close { padding: 0 14px; }
}

/* ============================================================
   Loading screen
   Same ground as the site, so a dark visitor never meets a light card.
   That also means the greeting is painted in the ink colour, and a large
   field of ink would read as a flash of the opposite tone — so the word
   fades out as it rushes forward, and the ground never changes at all.
   ============================================================ */
.loader { display: none; }

html.js .loader {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  background: var(--paper);
  color: var(--ink);
  overflow: hidden;
}

html[data-loading] body { overflow: hidden; }

.loader-stage {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
}

.loader-hello {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 1.16em;
  font-family: var(--font-display), "Noto Sans", "Noto Sans Devanagari",
    "Noto Sans JP", "Noto Sans KR", sans-serif;
  font-size: clamp(52px, 13vw, 168px);
  font-weight: 500;
  letter-spacing: -0.045em;
  line-height: 1.16;
  color: var(--ink);
}

/* One element, reused. A greeting leaves completely before the next
   arrives, so two are never on screen together. */
.hw {
  display: inline-block;
  white-space: nowrap;
  will-change: transform, opacity, filter;
  backface-visibility: hidden;
}

.hw.in { animation: hw-in 0.2s cubic-bezier(0.22, 1, 0.36, 1) both; }
.hw.out { animation: hw-out 0.12s cubic-bezier(0.55, 0, 1, 0.45) both; }

@keyframes hw-in {
  from { opacity: 0; transform: translateY(0.2em); filter: blur(4px); }
  to { opacity: 1; transform: none; filter: blur(0); }
}

@keyframes hw-out {
  from { opacity: 1; transform: none; filter: blur(0); }
  to { opacity: 0; transform: translateY(-0.2em); filter: blur(4px); }
}

.loader-count {
  position: absolute;
  right: clamp(18px, 4.5vw, 54px);
  bottom: clamp(14px, 4vh, 40px);
  font-family: var(--font-mono);
  font-size: clamp(40px, 7.5vw, 104px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* ---------- exit ----------
   The count clears, then the greeting rushes toward the viewer and
   dissolves. It fades well before it is large enough to cover the frame,
   so at no point is the screen filled with the opposite tone — the
   ground stays the page's own colour from first frame to last. */
.loader.out .loader-count {
  animation: count-away 0.2s cubic-bezier(0.55, 0, 1, 0.45) both;
}

@keyframes count-away {
  to { opacity: 0; transform: translateY(0.24em); filter: blur(6px); }
}

/* One continuous camera move: the greeting travels a short way and hands
   its momentum to the page, which is already growing behind it. The
   overlap is the point — nothing in the sequence ever comes to a stop. */
.loader.out .hw {
  animation: swallow 0.62s cubic-bezier(0.45, 0, 0.25, 1) 0.04s forwards;
  transform-origin: center center;
}

@keyframes swallow {
  0% { transform: scale(1); opacity: 1; }
  28% { opacity: 0.76; }
  50% { opacity: 0.3; }
  72% { opacity: 0; }
  100% { transform: scale(7); opacity: 0; }
}

/* The page carries the outward motion and settles back out of it. */
html[data-loading] .page {
  opacity: 0;
  will-change: transform, opacity;
}

/* The page inherits the zoom's momentum and decelerates out of it, so
   the movement carries through instead of stopping at the handover. */
html.js .page.arrive {
  animation: page-arrive 0.78s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes page-arrive {
  from { opacity: 0; transform: scale(1.16); }
  to { opacity: 1; transform: none; }
}

/* Release the hint once the entrance is over. */
html.js .page.arrive { animation-fill-mode: both; }
.page.settled { will-change: auto; }

/* Hold the hero until the loader has gone, so its entrance is not spent
   behind the loading screen. */
html[data-loading] .statement .w,
html[data-loading] .statement-sub { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  .hw.in, .hw.out { animation: none; }
  .loader.out { opacity: 0; }
  .loader.out .hw,
  .loader.out .loader-count { animation: none; }
  html.js .page.arrive { animation: none; }
  html[data-loading] .page { opacity: 1; }
}

/* ------------------------------------------------------------------
   Wall — a block of frames repeating forever in both directions.
   The surface holds a lattice of identical blocks; panning moves it
   within one block only, so there is no edge to reach.
   ------------------------------------------------------------------ */
.canvas.is-wall .canvas-plane { cursor: grab; }

.wall-block {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
}

.wall-tile {
  position: absolute;
  margin: 0;
  overflow: hidden;
  border-radius: 10px;
  background: var(--plate);
  box-shadow: 0 0 0 1px var(--rule), var(--shadow);
  transition: box-shadow 0.3s var(--ease), transform 0.3s var(--ease-out);
}

.wall-tile:hover {
  box-shadow: 0 0 0 1px var(--rule-2), var(--shadow);
  transform: translateY(-3px);
}

.wall-tile img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
}

/* Lightbox — one frame, whole, over the wall it came from. */
.lightbox {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6vh 5vw;
  cursor: zoom-out;
  background: color-mix(in srgb, var(--paper) 94%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  animation: canvas-in 0.28s var(--ease-out);
}
.lightbox[hidden] { display: none; }

.lightbox img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 12px;
  box-shadow: 0 0 0 1px var(--rule), var(--shadow);
  cursor: e-resize;
  user-select: none;
  -webkit-user-drag: none;
}

.lightbox-count {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  color: var(--ink-3);
}

.lightbox-close {
  position: absolute;
  top: 18px;
  right: 20px;
  height: 32px;
  padding: 0 14px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.lightbox-close:hover { color: var(--ink); border-color: var(--rule-2); }

@media (prefers-reduced-motion: reduce) {
  .wall-tile { transition: none; }
  .wall-tile:hover { transform: none; }
  .lightbox { animation: none; }
}

/* The cover is a button now — it opens the wall. */
.plate-open {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  border-radius: inherit;
}
.plate-open img { display: block; }
.plate-open:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* ============================================================
   Pluckable rules

   A section line you can grab and pull. The line is an SVG laid
   over the border it replaces — the border is made transparent,
   not removed, so turning this off restores the page exactly.
   One light source for the page, one spring for the line.
   ============================================================ */
.pluck {
  position: absolute;
  left: 0;
  right: 0;
  height: 26px;                 /* the reach — bigger than the line */
  z-index: 4;
  cursor: grab;
  /* The band owns its gesture, so a finger that lands on the line
     plucks it. Everywhere else on the page scrolls as normal. */
  touch-action: none;
}
@media (hover: none) { .pluck { height: 34px; } }   /* a finger is not a pixel */
.pluck.is-held { cursor: grabbing; }
.pluck svg { display: block; width: 100%; height: 100%; overflow: visible; }

.pluck .wire {
  fill: none;
  stroke: var(--rule);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  transition: stroke 0.3s var(--ease);
}
.pluck:hover .wire, .pluck.is-held .wire { stroke: var(--rule-2); }

/* While held: where the line rests, how far it has been pulled. */
.pluck .ghost {
  fill: none;
  stroke: var(--rule);
  stroke-width: 1;
  stroke-dasharray: 1 5;
  vector-effect: non-scaling-stroke;
  opacity: 0;
  transition: opacity 0.25s var(--ease);
}
.pluck .drop {
  stroke: var(--rule-2);
  stroke-width: 1;
  stroke-dasharray: 2 4;
  vector-effect: non-scaling-stroke;
  opacity: 0;
}
.pluck .tick {
  stroke: var(--rule-2);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  opacity: 0;
}
.pluck .readout {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  fill: var(--ink-2);
  opacity: 0;
}
.pluck.is-held .ghost,
.pluck.is-held .drop,
.pluck.is-held .tick,
.pluck.is-held .readout { opacity: 1; }

.has-pluck { position: relative; }
.has-pluck.pluck-top { border-top-color: transparent !important; }
.has-pluck.pluck-bottom { border-bottom-color: transparent !important; }

@media (prefers-reduced-motion: reduce) {
  /* No appetite for motion: leave the borders drawn, build nothing. */
  .has-pluck.pluck-top { border-top-color: var(--rule) !important; }
  .has-pluck.pluck-bottom { border-bottom-color: var(--rule) !important; }
  .pluck { display: none; }
}
