/* ---------- Tokens ---------- */
:root {
  /* Neutral scale: black → lighter black → lightest black */
  --text-primary: #171717;   /* neutral-900: headings, body */
  --text-secondary: #525252; /* neutral-600: supporting text */
  --text-tertiary: #a3a3a3;  /* neutral-400: nav, labels, meta */
  --border: #e5e5e5;         /* neutral-200: separators, row dividers */
  --placeholder: #f7f7f7;
  --surface-subtle: #fafafa; /* neutral-50: widget backgrounds */
  --surface-muted: #f5f5f5;  /* neutral-100: panels, hover */
  --surface-strong: #e5e5e5; /* neutral-200: selected items */
  --inverse: #fff;

  --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;
  --font-caption: "Switzer", var(--font-sans);

  /* Space above and below the line between sections */
  --section-space: 96px;

  /* Space at the top and bottom of every page */
  --page-space: 120px;

  /* Layout grid: 12 cols, stretch, margin 40, gutter 24 */
  --grid-cols: 12;
  --grid-margin: 40px;
  --grid-gutter: 24px;
}

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

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--inverse);
  color: var(--text-primary);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

p {
  margin: 0;
}

/* Media can't be dragged out or long-pressed to save (see site.js) */
img,
video,
canvas {
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
  user-select: none;
}

a {
  color: inherit;
  text-decoration: none;
}

/* ---------- Text styles ---------- */
/* All styles use normal letter-spacing (each font's built-in tracking) */
.headline-1 { font-size: 24px; line-height: 1.4; }
.body-1 { font-size: 16px; line-height: 1.5; }
.body-2 { font-size: 14px; line-height: 1.5; }
.title-1 { font-size: 20px; line-height: 1.4; font-weight: 500; }
.title-2 { font-size: 17px; line-height: 1.4; }
.caption-1 { font-size: 12px; line-height: 1.5; }
.caption-2 { font-family: var(--font-caption); font-size: 11px; line-height: 1.5; }
.mono-1 { font-family: var(--font-mono); font-size: 14px; line-height: 1.3; }
.mono-2 { font-family: var(--font-mono); font-size: 12px; line-height: 1.4; }

.c-secondary { color: var(--text-secondary); }
.c-tertiary { color: var(--text-tertiary); }

/* ---------- Page grid ---------- */
.page {
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  column-gap: var(--grid-gutter);
  padding: var(--page-space) var(--grid-margin); /* same space top and bottom */
  align-items: start;
}

/* Nav: columns 2–3 */
.site-nav {
  grid-column: 2 / span 2;
  position: sticky;
  top: 120px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.site-nav a {
  color: var(--text-tertiary);
  transition: color 150ms ease-out;
}

.site-nav a:hover {
  color: var(--text-primary);
}

/* Main content: columns 4–9 */
.content {
  grid-column: 4 / span 6;
  display: flex;
  flex-direction: column;
  gap: var(--section-space);
  min-width: 0;
}

/* Platform pattern: a full-width line between top-level sections,
   with equal space above and below it */
.content > section + section {
  padding-top: var(--section-space);
  border-top: 1px solid var(--border);
}

/* ---------- Placeholders ---------- */
/* ---------- Hero ---------- */
.hero {
  display: flex;
  flex-direction: column;
  gap: 64px;
}

.hero-intro {
  display: flex;
  flex-direction: column;
  gap: 40px;
}

/* Code line: syntax colours revealed in a soft circle around the cursor */
.code-line {
  position: relative;
  width: fit-content;
  --x: -100px;
  --y: -100px;
}

.code-base {
  display: block;
}

.code-color {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 250ms ease-out;
  -webkit-mask-image: radial-gradient(circle 64px at var(--x) var(--y), #000 30%, transparent 100%);
  mask-image: radial-gradient(circle 64px at var(--x) var(--y), #000 30%, transparent 100%);
}

.code-line:hover .code-color {
  opacity: 1;
}

.tok-class { color: #0f766e; }
.tok-field { color: #7c3aed; }
.tok-method { color: #2563eb; }
.tok-string { color: #c2410c; }
.tok-punct { color: var(--text-secondary); }

.hero-bio {
  display: flex;
  flex-direction: column;
  gap: 24px; /* one empty 16px*1.5 line between paragraphs */
}

.hero-companies {
  color: var(--text-secondary);
}

.hero-companies strong {
  font-weight: inherit;
}

/* ---------- Hero game (alpaca-game.js) ---------- */
.hero-media {
  display: flex;
  flex-direction: column;
  gap: 16px;
  text-align: center;
}

/* Controls legend under the game: [space] start  [space] munch grass */
.game-controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 8px 20px;
  color: var(--text-secondary);
}

.game-control {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* Keycap: thin outline with a heavier bottom edge, like a physical key */
.key {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 7px;
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--inverse);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1;
}

.alpaca-game {
  --alpaca-ink: #3f429b; /* same indigo as the testimonial stitches */
  --alpaca-bg: var(--placeholder);
  position: relative; width: 100%; aspect-ratio: 1208 / 362; overflow: hidden;
  background: var(--alpaca-bg); color: var(--alpaca-ink);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  touch-action: manipulation; user-select: none; -webkit-user-select: none; cursor: pointer;
}
.alpaca-game svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.alpaca-game [hidden] { display: none !important; }
.alpaca-game__count {
  position: absolute; top: 6px; right: 10px; padding: 1px 6px;
  background: var(--alpaca-bg); font-size: 12px; font-weight: 500;
}
.alpaca-game__card {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  box-sizing: border-box; max-width: calc(100% - 16px); padding: 12px 16px 14px;
  background: var(--alpaca-bg); border: 1px solid var(--alpaca-ink);
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  white-space: nowrap; cursor: default;
}
.alpaca-game__card strong { font-size: 14px; line-height: 20px; font-weight: 500; }
.alpaca-game__card span { font-size: 12px; line-height: 18px; }
.alpaca-game__card button {
  margin-top: 20px; min-height: 32px; padding: 0 12px; /* breathing room under the caption */
  font: 500 12px 'IBM Plex Mono', ui-monospace, monospace;
  border: 1px solid var(--alpaca-ink); background: transparent; color: var(--alpaca-ink); cursor: pointer;
}
.alpaca-game__card button:hover { background: var(--alpaca-ink); color: var(--alpaca-bg); }
.alpaca-game__card button:focus-visible { outline: 2px solid var(--alpaca-ink); outline-offset: 2px; }

/* ---------- Sections ---------- */
.section {
  display: flex;
  flex-direction: column;
  gap: 32px;
  scroll-margin-top: 120px;
}

/* Section headings: 20px medium, no divider */
.section-title {
  margin: 0;
  color: var(--text-primary);
}

/* ---------- Writings ---------- */
.writings-list {
  display: flex;
  flex-direction: column;
  gap: 56px; /* same as between the Shots, and blocks in a case study chapter */
  container-type: inline-size; /* cards respond to this width, not the window */
}

.writing {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr);
  column-gap: 32px;
}

/* The whole card (image, title and details) is one link: the title's link
   stretches over it, so there's a single link per card for keyboards and
   screen readers */
.writing-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* Always the desktop shape (404.7 × 280), so the composition scales as one */
.writing-media {
  aspect-ratio: 404.73 / 280;
  align-self: start;
}

/* Product shot in a frame: padded top and left, bleeding off the right and
   bottom edges (proportions from the mock, so it scales with the card) */
.writing-shot {
  position: relative;
  overflow: hidden;
  background: var(--placeholder); /* stitched landscape drawn on top (patterns.js) */
}

/* Stitched landscape canvas (added by stitchBackgrounds in patterns.js) */
[data-brand] {
  position: relative;
}

.stitch-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* ---------- Media loading / failure (media.js) ---------- */
/* Loading: a tiny blurred copy of the media (set inline) stretched to its box */
.media-loading {
  background-position: center;
  background-size: 100% 100%;
  background-repeat: no-repeat;
}

/* The whole frame blurs while its media loads (edges stay crisp), then
   sharpens into focus as one */
@keyframes media-sharpen {
  from { filter: blur(12px); }
}

.frame-loading {
  overflow: hidden;
}

.frame-loading > * {
  filter: blur(12px);
}

.frame-loaded > *,
.media-loaded {
  animation: media-sharpen 500ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .frame-loaded > *,
  .media-loaded { animation: none; }
}

/* Failed: the frame drops its landscape and contents for plain #f7f7f7 with
   a short note, keeping the media's size */
.media-fallback {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 16px;
  background: var(--placeholder);
  color: var(--text-tertiary);
  text-align: center;
}

.media-fallback p {
  margin: 0;
}

.media-fallback-title {
  color: var(--text-secondary);
}

/* Covering the whole frame, for media that sits inside it (drawers, cards) */
.media-fallback--fill {
  position: absolute;
  inset: 0;
  z-index: 2;
}

.is-failed .stitch-bg,
.is-failed .zoom-badge {
  display: none;
}

/* Keep framed media above its stitched background */
[data-brand] > .shot-video {
  position: relative;
}

.writing-shot img {
  position: absolute;
  top: 12%;
  left: 8%;
  width: 92%;
  height: auto;
  border-top-left-radius: 6px;
  /* Hairline in the app's own divider colour (set per card from patterns.js):
     the apps draw 2px dividers, which shrink to ~0.4px at this scale */
  outline: 0.5px solid var(--line, var(--border));
}

.writing-body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 24px;
}

.writing-header {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.meta {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin: 0;
}

/* Label left, value right */
.meta-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}

.meta-row:not(:last-child) {
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.meta-row dt {
  flex-shrink: 0;
  color: var(--text-tertiary);
}

.meta-row dd {
  margin: 0;
  min-width: 0;
  text-align: right;
  color: var(--text-primary);
}

/* When side by side would squeeze the text column (under ~225px, titles start
   wrapping to 3+ lines), stack the card: full-width image, text underneath */
@container (max-width: 620px) {
  .writing {
    grid-template-columns: 1fr;
    row-gap: 24px;
  }

  .writing-body {
    gap: 32px;
  }
}

/* ---------- Gallery ---------- */
.gallery-list {
  display: flex;
  flex-direction: column;
  gap: 56px; /* same as between the Writings cards */
}

.shot {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Media on a grey frame with equal padding all round; the frame's height
   follows the media's own shape */
.shot-media {
  padding: 4.8%; /* ≈32px at desktop, shrinks with the column */
  background: var(--placeholder);
}

.shot-video {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 4px;
}

/* Cropped video: shows only the region (--cx, --cy, --cw, --ch) of a video
   that is --vw px wide, all in the video's own pixels. Sits inside the normal
   padded frame, so its edges line up with the other shots. */
.shot-crop {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: var(--cw) / var(--ch);
}

.shot-crop .shot-video {
  position: absolute;
  width: calc(var(--vw) / var(--cw) * 100%);
  left: calc(var(--cx) / var(--cw) * -100%);
  top: calc(var(--cy) / var(--ch) * -100%);
  border-radius: 0;
}

/* App screen recordings: rounder corners and a hairline in the app's own
   divider colour (set per video via --line), like the Writings screenshots */
.shot-video--app {
  border-radius: 8px;
  outline: 0.5px solid var(--line, var(--border));
}

.shot-caption {
  display: flex;
  align-items: center;
  gap: 16px;
}

.shot-title {
  flex: 1 1 0;
  min-width: 0;
}

.shot-meta {
  white-space: nowrap;
}

/* ---------- About ---------- */
.about-body {
  display: flex;
  flex-direction: column;
  gap: 72px;
  container-type: inline-size; /* testimonials respond to this width, not the window */
}

.about-text {
  display: flex;
  flex-direction: column;
  gap: 24px;
  color: var(--text-secondary);
}

.testimonials {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.testimonial {
  position: relative;
  background: var(--placeholder);
  min-height: 388px; /* grows if the text needs more room */
  display: flex;
  flex-direction: column;
  transition: translate 300ms cubic-bezier(0.32, 0.72, 0, 1);
}

/* Card floats up a little on hover (its animals take flight, see stitch.js) */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .testimonial:hover {
    translate: 0 -12px;
  }

  /* Keep the gap it leaves underneath hoverable, so the card doesn't
     drop and re-lift when the cursor is near its bottom edge */
  .testimonial:hover::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    height: 12px;
  }
}

/* Edge-to-edge image across the top of the card */
.testimonial-image {
  display: block;
  width: 100%;
  height: 121px;
  object-fit: cover;
  object-position: bottom;
}

.testimonial-body {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 16px;
  padding: 20px;
}

.testimonial-text {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.testimonial-quote {
  margin: 0;
  color: var(--text-primary);
}

.testimonial-note {
  color: var(--text-tertiary);
}

.testimonial-author {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* When the cards would get too narrow (under ~180px each), stack them in one column
   and give the stitched animals a taller strip to fly in */
@container (max-width: 580px) {
  .testimonials {
    grid-template-columns: 1fr;
    gap: 24px; /* twice the 12px hover lift, so a raised card never touches the one above */
  }

  .testimonial {
    min-height: 0;
  }

  .testimonial-image {
    height: 160px;
  }

  .testimonial-body {
    gap: 24px;
  }

  /* The note sits close under the quote when stacked */
  .testimonial-text {
    gap: 4px;
  }
}

/* ---------- Contact ---------- */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
}

.contact-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Diagonal arrow after each link, shown on hover/focus */
.contact-item a {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  align-self: flex-start;
  max-width: 100%;
}

.contact-item a::after {
  content: "";
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  background: currentColor;
  -webkit-mask: url("assets/icons/arrow-up-right.svg") center / contain no-repeat;
  mask: url("assets/icons/arrow-up-right.svg") center / contain no-repeat;
  opacity: 0;
  translate: -4px 4px;
  transition: opacity 150ms ease-out, translate 150ms ease-out;
}

.contact-item a:hover::after,
.contact-item a:focus-visible::after {
  opacity: 1;
  translate: 0 0;
}

/* ---------- Grid overlay (press G) ---------- */
.grid-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  pointer-events: none;
  display: none;
  grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  column-gap: var(--grid-gutter);
  padding: 0 var(--grid-margin);
}

.grid-overlay.is-visible {
  display: grid;
}

.grid-overlay span {
  background: rgba(255, 0, 0, 0.1);
}

/* ---------- Responsive ---------- */
@media (max-width: 1344px) {
  /* Content widens to columns 4–11 below 1344px (where 6 columns would drop
     under 620px), so it never gets squeezed with an empty band on the right */
  .content { grid-column: 4 / span 8; }
  .site-nav { grid-column: 1 / span 2; }
}

@media (max-width: 768px) {
  :root {
    --grid-cols: 4;
    --grid-margin: 16px;
    --grid-gutter: 16px;
    --section-space: 64px;
    --page-space: 48px;
  }

  .page {
    row-gap: 48px;
  }

  .site-nav {
    grid-column: 1 / -1;
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 16px;
  }

  .content {
    grid-column: 1 / -1;
  }

  .contact-grid {
    grid-template-columns: 1fr;
  }

  .grid-overlay span:nth-child(n + 5) {
    display: none;
  }
}
