:root {
  --bg: #ffffff;
  --text: #17171a;
  --text-secondary: #75757d;
  --surface: #f4f4f5;
  --surface-hover: #ededee;
  --radius-card: 12px;
  --radius-sheet: 20px;
  --ease-sheet: cubic-bezier(0.32, 0.72, 0, 1);

  /* 4-column layout grid: 20px page margins, 20px gutters.
     Column 1 stays intentionally empty; content lives in columns 2-4. */
  --margin: 20px;
  --gutter: 20px;
  --col: calc((100vw - 2 * var(--margin) - 3 * var(--gutter)) / 4);
  --indent: calc(var(--margin) + var(--col) + var(--gutter));

  /* Cards are a fixed size independent of the column grid — they only
     start at the grid indent, they don't scale with it. */
  --card-w: min(400px, calc(100vw - 2 * var(--margin)));

  /* Card-to-card spacing is tighter than the layout gutter, tuned to the
     corner radius so the gap reads as the same shape. */
  --card-gap: var(--radius-card);

  /* Highlighter yellow, warmed toward red so it reads amber rather than
     acid. Light enough that anything on it must be dark text, never white. */
  --accent: #ffea00;

  /* The accent fill is the same in both themes, so text on it must NOT
     follow --text (which inverts) or it vanishes in dark mode. */
  --on-accent: #17171a;

  --backdrop: rgba(23, 23, 26, 0.4);
  --sheet-shadow: rgba(23, 23, 26, 0.18);
}

/* Dark palette. Declared once and referenced by both the explicit toggle and
   the system-preference default below. */
:root[data-theme="dark"] {
  --bg: #0d0d0f;
  --text: #f2f2f4;
  --text-secondary: #8b8b94;
  --surface: #1a1a1f;
  --surface-hover: #26262c;
  --backdrop: rgba(0, 0, 0, 0.6);
  --sheet-shadow: rgba(0, 0, 0, 0.5);
}

/* No explicit choice stored: follow the OS. Guarded so the toggle still wins
   when someone has picked light on a dark-mode machine. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0d0d0f;
    --text: #f2f2f4;
    --text-secondary: #8b8b94;
    --surface: #1a1a1f;
    --surface-hover: #26262c;
    --backdrop: rgba(0, 0, 0, 0.6);
    --sheet-shadow: rgba(0, 0, 0, 0.5);
  }
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

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

body {
  /* "Inter var" is the variable family's name in older inter.css builds —
     without it those builds fall back to static Inter and 450 snaps to a
     heavier static weight. */
  font-family: "InterVariable", "Inter var", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 450;
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  transition: background 240ms ease, color 240ms ease;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
}

/* ---------- Status bar ---------- */

/* Pinned to the top-left corner, outside the content indent. Fixed, so it
   stays put while the page scrolls — the empty first column means it never
   sits over anything on desktop. */
.status {
  position: fixed;
  top: 12px;
  left: 16px;
  /* 0.75px more than the left, so the dot — which centring pushes down to
     16.75px from the top — sits the same distance from both edges. */
  right: 16.75px;
  z-index: 10;
  display: flex;
  justify-content: space-between;
  gap: 12px;
  color: var(--text-secondary);
}

.status-group {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Reads as a black dot in light mode and a white one in dark, because it
   just paints itself in the current ink. */
.theme-toggle {
  width: 10px;
  height: 10px;
  padding: 0;
  border-radius: 50%;
  background: var(--text);
  transition: background 240ms ease, transform 160ms var(--ease-sheet);
}

.theme-toggle:hover,
.theme-toggle:focus-visible {
  transform: scale(1.2);
}

/* After the hover rule so it wins while the pointer is both over and down. */
.theme-toggle:active {
  transform: scale(0.85);
  transition-duration: 80ms;
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 3px;
}

.status time {
  font-variant-numeric: tabular-nums;
}

/* ---------- Page ---------- */

.page {
  padding: 128px 0 96px;
  display: flex;
  flex-direction: column;
  gap: 80px;
}

.group {
  margin-left: var(--indent);
  margin-right: var(--margin);
}

.intro-bio {
  max-width: max(var(--col), 320px);
  color: var(--text-secondary);
}

.intro-bio strong {
  color: var(--text);
  font-weight: 500;
}

.intro-bio + .intro-bio {
  margin-top: 20px;
}

/* ---------- Contact row ---------- */

.contact {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 24px;
  margin-top: 40px;
}

.contact-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  color: var(--text-secondary);
  text-decoration: none;
  transition: color 160ms ease;
}

.contact-item svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}

.contact-item:hover,
.contact-item:focus-visible,
.contact-item.is-copied {
  color: var(--text);
}

.group-label {
  font-size: 13px;
  font-weight: 450;
  color: var(--text);
  margin-bottom: 16px;
}

/* Fun / Personal: same fixed-width cards, as many per row as fit. */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, var(--card-w));
  gap: var(--card-gap);
}

/* Works: fixed-width cards in a horizontal scroll. The row spans the
   full viewport and scrolls free (no snap), with the lead/tail insets as
   flex spacers rather than padding — scrolled cards paint all the way to
   the viewport edges, and rounded corners never hit the composited
   padding-clip path that squares them off mid-hover. */
.scroll-row {
  display: flex;
  gap: var(--card-gap);
  margin-left: calc(-1 * var(--indent));
  margin-right: calc(-1 * var(--margin));
  overflow-x: auto;
  scrollbar-width: none;
  /* Headroom so a hover-scaled card isn't clipped by the scroll container;
     the negative margin keeps the row's layout position unchanged. */
  padding-block: 12px;
  margin-block: -12px;
}

.scroll-row::before,
.scroll-row::after {
  content: "";
  flex: 0 0 auto;
}

/* Spacer + the flex gap add up to the grid inset on each side. */
.scroll-row::before {
  width: calc(var(--indent) - var(--card-gap));
}

.scroll-row::after {
  width: calc(var(--margin) - var(--card-gap));
}

.scroll-row::-webkit-scrollbar {
  display: none;
}

.scroll-row .card {
  flex: 0 0 var(--card-w);
}

/* ---------- Cards ---------- */

/* The card is the surface itself; the caption sits inside it, bottom-left,
   over the artwork. */
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  aspect-ratio: 3 / 2;
  padding: 20px;
  background: var(--surface);
  border-radius: var(--radius-card);
  overflow: hidden;
  transition: background 160ms ease, transform 260ms var(--ease-sheet);
}

.card:hover,
.card:focus-visible {
  background: var(--accent);
  transform: scale(1.02);
  z-index: 1;
}

.card:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

.card .thumb {
  position: absolute;
  inset: 0;
}

.card .thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.card-name,
.card-desc {
  position: relative;
  z-index: 1;
  transition: color 160ms ease;
}

.card-name {
  font-weight: 450;
}

.card-desc {
  margin-top: 2px;
  color: var(--text-secondary);
}

/* With artwork behind it, the caption needs its own ground to sit on. */
.card:has(.thumb img)::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(10, 10, 12, 0.62), rgba(10, 10, 12, 0) 58%);
}

.card:has(.thumb img) .card-name {
  color: #fff;
}

.card:has(.thumb img) .card-desc {
  color: rgba(255, 255, 255, 0.78);
}

/* Hover: the card fills with the accent. The fill is near-white in
   luminance, so the caption goes dark rather than white — listed after the
   artwork rules above so it wins for image cards too. */
.card:hover .card-name,
.card:focus-visible .card-name {
  color: var(--on-accent);
}

.card:hover .card-desc,
.card:focus-visible .card-desc {
  color: color-mix(in srgb, var(--on-accent) 72%, transparent);
}

/* ---------- Sheet (slide-up modal) ---------- */

.sheet-backdrop {
  position: fixed;
  inset: 0;
  background: var(--backdrop);
  opacity: 0;
  visibility: hidden;
  transition: opacity 360ms ease, visibility 0s linear 360ms;
  z-index: 40;
}

.sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Sized to the content column plus its inset, not to the viewport — so
     the 64px shows up equally on the top and both sides. */
  width: min(824px, calc(100vw - 40px));
  margin-inline: auto;
  height: min(92dvh, 100dvh - 48px);
  background: var(--bg);
  border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
  box-shadow: 0 -8px 40px var(--sheet-shadow);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform 480ms var(--ease-sheet), visibility 0s linear 480ms;
  z-index: 50;
  display: flex;
}

body.sheet-open .sheet-backdrop {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

body.sheet-open .sheet {
  transform: translateY(0);
  visibility: visible;
  transition-delay: 0s;
}

body.sheet-open {
  overflow: hidden;
}

.sheet-close {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: none;
  border: 1px solid var(--surface-hover);
  color: var(--text-secondary);
  transition: border-color 160ms ease, color 160ms ease;
  z-index: 2;
}

.sheet-close:hover {
  border-color: var(--text-secondary);
  color: var(--text);
}

/* The button's ring doubles as a read-progress meter for the sheet. */
.sheet-close-ring {
  position: absolute;
  /* -1px reaches back over the 1px border, so the 36-unit viewBox maps 1:1
     onto the button's border box and the ring can trace the border itself.
     Do not add width/height here — that squashes and offsets the viewBox. */
  inset: -1px;
  /* Start the sweep at 12 o'clock instead of 3. */
  transform: rotate(-90deg);
  pointer-events: none;
}

.ring-progress {
  fill: none;
  stroke: var(--text-secondary);
  stroke-width: 1;
  stroke-linecap: round;
  /* Full circumference of r=17.5; offset is driven from app.js. */
  stroke-dasharray: 109.956;
  stroke-dashoffset: 109.956;
}

.sheet-scroll {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: inherit;
  scrollbar-width: none;
}

.sheet-scroll::-webkit-scrollbar {
  display: none;
}

.sheet-content {
  /* One inset on all four sides, so content sits the same distance from
     every edge of the sheet. max-width carries the inset with it
     (border-box), keeping the text column itself at 664px. */
  --sheet-pad: 80px;
  padding: var(--sheet-pad);
}

.sheet-title {
  font-size: 32px;
  line-height: 1.2;
  letter-spacing: -0.02em;
  font-weight: 500;
}

/* Description on top, the credit columns in a row beneath it. */
.sheet-lede {
  margin-top: 24px;
  display: grid;
  gap: 32px;
}

.sheet-intro {
  margin: 0;
  color: var(--text-secondary);
  white-space: pre-line;
}

.sheet-meta {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px 16px;
  align-content: start;
}

.sheet-meta dt {
  color: var(--text-secondary);
  margin-bottom: 4px;
}

.sheet-meta dd {
  margin: 0;
}

/* Several values under one label stack as their own lines. */
.sheet-meta dd span {
  display: block;
}

.sheet-section {
  margin-top: 64px;
}

.sheet-section h3 {
  font-size: 13px;
  font-weight: 450;
}

.sheet-section .sheet-section-desc {
  margin-top: 2px;
  color: var(--text-secondary);
}

.media-grid {
  margin-top: 24px;
  display: grid;
  grid-template-columns: repeat(var(--media-columns, 1), minmax(0, 1fr));
  gap: 16px;
  align-items: start;
}

.media-item {
  margin: 0;
}

.media-caption {
  margin-top: 8px;
  color: var(--text-secondary);
}

/* Artwork with no frame of its own — illustrations, diagrams — needs the
   panel to hold it, or it floats edge to edge. Screenshots stay full-bleed. */
.media.is-padded {
  padding: 40px;
}

/* Tile grids: each tile gets its own bordered cell, square so the artwork
   sits centred regardless of its own proportions. */
.media-grid.is-tiles {
  gap: 16px;
}

.media-grid.is-tiles .media {
  background: none;
  border: 1px solid var(--surface-hover);
  border-radius: var(--radius-card);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  padding: 8%;
}

/* Only the artwork grows — the bordered cell holds its position. The cell's
   8% padding leaves room for the scale to happen inside the border. */
.media-grid.is-tiles .media img {
  transition: transform 200ms var(--ease-sheet);
}

.media-grid.is-tiles .media:hover img {
  transform: scale(1.08);
}

.sheet-section .media {
  width: 100%;
  background: var(--surface);
  border-radius: var(--radius-card);
  overflow: hidden;
}

.sheet-section .media img,
.sheet-section .media video {
  width: 100%;
  height: auto;
  display: block;
}

/* ---------- Responsive ---------- */

@media (max-width: 900px) {
  :root {
    /* Grid drops to 2 columns; the empty lead column goes away. */
    --col: calc((100vw - 2 * var(--margin) - var(--gutter)) / 2);
    --indent: var(--margin);
  }

  .page {
    padding-top: 96px;
  }

  /* No empty lead column at this width, so a pinned label would sit on top
     of the cards as they scroll past. Let it scroll away instead. */
  .status {
    position: static;
    padding: 12px 16px 0;
  }
}

@media (max-width: 560px) {
  :root {
    --col: calc(100vw - 2 * var(--margin));
    --indent: var(--margin);
  }

  .page {
    padding-top: 72px;
    gap: 64px;
  }

  .sheet {
    /* Edge to edge on a phone — a floating panel would just waste width. */
    width: 100%;
    height: calc(100dvh - 24px);
  }

  .sheet-content {
    --sheet-pad: 24px;
  }

  .sheet-title {
    font-size: 26px;
  }

  .sheet-lede {
    gap: 24px;
  }

  .sheet-meta {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Side-by-side artwork has no room to breathe on a phone. */
  .media-grid {
    --media-columns: 1;
  }

  .media.is-padded {
    padding: 20px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sheet,
  .sheet-backdrop,
  .card {
    transition-duration: 0.01ms;
  }

  .card:hover,
  .card:focus-visible {
    transform: none;
  }
}
