/* ==========================================================================
   Shared styles for jennwchoi.com — used by index.html, play.html, etc.
   ========================================================================== */

/* ---------- Design tokens (visual identity — do not change casually) ---------- */
:root {
  --bg: #ffffff;          /* white */
  --ink: #1a1815;         /* near-black text */
  --muted: #8a857d;       /* secondary gray */
  --faint: #b7b2aa;       /* tertiary / labels */
  --line: #e7e2d9;        /* hairlines */
  --text-body: #4a463f;   /* long-form body copy */
  --text-secondary: #6b665e;  /* secondary prose (Figma: Body/Paragraph on paper) */
  --garden: #4a7c59;      /* ASCII green */
  --garden-2: #7ca98a;    /* ASCII green (light) */
  --accent: #4a7c59;      /* text buttons, links that act (same green as the garden) */
  --accent-deep: #2f5a3c; /* accent on hover */

  --font-serif: "Crimson Pro", Georgia, serif;
  --font-sans: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  /* matches jennwchoi.com: system monospace stack for ASCII / numbers / dates */
  --font-mono: "SF Mono", "Fira Code", Monaco, Consolas, monospace;

  --r: 4px;               /* the one UI radius — media (thumbs, covers) stays square */
  --panel: #f0efeb;       /* chat panel ground */
  --surface-2: #f5f4f0;   /* quiet card ground (research cards) */
  --panel-w: 360px;       /* chat panel width */
  --head-h: 70px;         /* fixed top bar: 28px inset + 30px logo + 12px */
  --sidebar-w: clamp(240px, 22vw, 312px);  /* Figma: 312 nav rail; gives way to the content on mid-size screens */
  --gutter: 32px;         /* sidebar → content gap */
  --edge: 20px;           /* page edge padding (inside the capped container) */
  --page-max: 1712px;     /* centered container cap → ~124px margins at 1920 */
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: calc(var(--head-h) - 40px); }   /* + the targets' own 40px scroll-margin = clears the bar */
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.55;
  letter-spacing: -0.02em;      /* IBM Plex Sans is set tight everywhere */
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* when the JENN LLM panel opens, the page content shifts left to make room */
  transition: padding-right 0.32s cubic-bezier(0.22, 0.61, 0.36, 1);
}
@media (min-width: 721px) {
  body.llm-open { padding-right: var(--panel-w); }
  body.llm-open .site-head { padding-right: var(--panel-w); }
  /* detail pages already have an empty right gutter — the panel fills that space,
     so don't push the whole page; just let the right gutter be consumed. */
  body.llm-open:has(.page--detail) { padding-right: 0; }
  body.llm-open:has(.page--detail) .site-head { padding-right: 0; }
}
a { color: inherit; text-decoration: none; }

/* ---------- Landing threshold (index.html) ----------
   A 150vh run of scroll. The stage sticks to the viewport while the monogram
   fills with ink, then the page carries on into the work grid underneath. */
.shade { position: relative; height: 150dvh; background: #f5f3e9; }
.shade__stage { position: sticky; top: 0; height: 100dvh; overflow: clip; }
.shade__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  background: #f5f3e9 url("/images/landing/shade-still.webp") center / cover no-repeat;
}
/* fine analog grain — dissolves the film's compression blocking in the flats */
.shade__stage::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  pointer-events: none; opacity: .5; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.22'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}
.shade__mark {
  position: absolute; z-index: 2; top: 50%; left: 50%;
  width: clamp(7rem, 10vw, 10rem); aspect-ratio: 305 / 296;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 1px 0 rgb(34 35 33 / 0.08));
  transition: transform .3s cubic-bezier(.2, .7, .2, 1);
}
.shade__mark:hover { transform: translate(-50%, -50%) scale(1.04); }
.shade__mark:focus-visible { outline: 2px solid #222321; outline-offset: 12px; border-radius: 8px; }

/* "Scroll" cue: a label over a short line with a bead running down it.
   It steps aside as soon as the visitor starts moving. */
.shade__cue {
  position: absolute; z-index: 2; left: 50%; bottom: 36px;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: rgb(34 35 33 / .85);
  transition: opacity .35s ease;
}
.shade__cue i { position: relative; display: block; width: 1.5px; height: 44px; background: rgb(34 35 33 / .22); overflow: hidden; }
.shade__cue i::after {
  content: ""; position: absolute; left: 0; top: -14px; width: 1.5px; height: 14px;
  background: #222321;
  animation: shade-cue 1.8s cubic-bezier(.6, 0, .4, 1) infinite;
}
@keyframes shade-cue { to { transform: translateY(58px); } }
body.intro-moved .shade__cue { opacity: 0; pointer-events: none; }
/* two stacked copies of the mark: white underneath, ink rising over it */
.shade__mark span {
  position: absolute; inset: 0;
  mask: url("/images/landing/mark.png") center / contain no-repeat;
  -webkit-mask: url("/images/landing/mark.png") center / contain no-repeat;
}
.shade__mark-white { background-color: #fff; }
.shade__mark-ink {
  background: linear-gradient(to top, #222321 0 var(--logo-fill, 0%), transparent var(--logo-fill, 0%) 100%);
}
@media (max-width: 700px) { .shade__mark { width: clamp(6.5rem, 28vw, 8rem); } }

/* the top bar keeps out of the film until the mark is full */
body.at-intro .site-head { opacity: 0; pointer-events: none; }
.site-head { transition: opacity .4s ease; }

@media (prefers-reduced-motion: reduce) {
  :root { --logo-fill: 100%; }
  .shade { height: 100dvh; }
  .shade__cue i::after { animation: none; top: 12px; }
  .shade__video { display: none; }
  .shade__stage { background: #f5f3e9 url("/images/landing/shade-still.webp") center / cover no-repeat; }
}

/* ---------- Page shell: left sidebar + right content ---------- */
.page {
  position: relative;
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  gap: var(--gutter);
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--head-h) var(--edge) 0;
}

/* ---------- Fixed top bar: logo (left) + New York time (right) ----------
   Opaque, so the page scrolls underneath it. Built by panel.js, which moves
   the page's own .logo into it. */
.site-head {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 40;                    /* under the LLM tab (50) / panel (60) */
  background: var(--bg);
  height: var(--head-h);
}
.head-inner {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 28px var(--edge) 0;    /* Figma: 28px above the logo line */
  display: flex;
  align-items: flex-start;
}
.local-time {
  margin: 0 0 0 auto;             /* flush right, even with no logo */
  font-size: 13px;
  line-height: 30px;              /* = logo height */
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ---------- Sidebar / Table of Contents ---------- */
.sidebar {
  position: sticky;
  top: calc(var(--head-h) + 12px);
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.logo {
  display: inline-flex;
  width: 30px;
  height: 30px;
  color: var(--ink);
}
.logo svg, .logo img { width: 100%; height: 100%; object-fit: contain; }

.brand h1 {
  font-family: var(--font-serif);
  font-weight: 300;               /* Light — matches jennwchoi.com display */
  font-size: 34px;
  line-height: 1.05;
  margin: 0 0 8px;
  letter-spacing: -0.01em;
}
.brand .role {
  margin: 0 0 12px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.35;
}
.brand .pov {
  margin: 0;
  color: var(--text-secondary);
  font-weight: 300;
  font-size: 13.5px;
  line-height: 1.55;
}
.rule { border: 0; border-top: 1px solid var(--line); margin: 2px 0 0; width: 100%; }
/* the ant's floor sits quieter than the other hairlines */
.sidebar .rule { border-top-color: #f1eee9; }

/* nav groups */
.nav-group + .nav-group { margin-top: 22px; }
.nav-label {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 10px;
}
.nav-list { list-style: none; margin: 0; padding: 0; }
.nav-list li { margin: 2px 0; }
.nav-list a {
  display: grid;
  grid-template-columns: 26px 1fr auto;
  align-items: baseline;
  gap: 2px;
  font-size: 13px;
  color: var(--muted);
  padding: 1px 0;
  transition: color .15s ease, transform .15s ease;
}
.nav-list a .num { color: inherit; font-variant-numeric: tabular-nums; }
.nav-list a .arr { display: inline-flex; align-items: center; justify-content: flex-end; width: 16px; height: 16px; transition: transform .15s ease; }
.nav-list a .arr img { display: block; }
.nav-list a:hover { color: var(--ink); }
.nav-list a:hover .arr { transform: translate(2px, 0); }
.nav-list a:focus-visible { outline: 2px solid var(--garden); outline-offset: 3px; border-radius: var(--r); }

/* ---------- ASCII garden (moved to bottom of the TOC) ---------- */
.garden {
  margin-top: auto;           /* push garden + footer to the bottom */
  color: var(--garden);
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1.15;
  user-select: none;
}
.sprout {
  position: relative;
  display: inline-block;
  margin-bottom: 18px;
}
.sprout pre { margin: 0; color: var(--garden-2); }
.sprout .bubble {
  position: absolute;
  top: -6px;
  left: 34px;
  background: var(--ink);
  color: var(--bg);
  font-family: var(--font-mono);
  font-size: 9px;
  padding: 2px 7px;
  border-radius: var(--r);
  white-space: nowrap;
}
.sprout .bubble::after {
  content: "";
  position: absolute;
  left: -4px; top: 50%;
  transform: translateY(-50%);
  border: 4px solid transparent;
  border-right-color: var(--ink);
}
.garden pre { margin: 0; }
.garden .cluster { display: flex; gap: 14px; flex-wrap: wrap; }
.garden .cluster pre:nth-child(odd) { color: var(--garden); }
.garden .cluster pre:nth-child(even) { color: var(--garden-2); }

/* ---------- Sidebar footer ---------- */
.side-footer {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--faint);
  line-height: 1.5;
  margin-top: 22px;
}

/* ---------- Main content ---------- */
.content { min-width: 0; }

.work { scroll-margin-top: 40px; }
/* on-page sections (Play, About) — sidebar stays put while these scroll */
.section { margin-top: 72px; scroll-margin-top: 40px; }
.section:first-child { margin-top: 0; }
.section > h2 { font-family: var(--font-serif); font-weight: 400; font-size: 28px; margin: 0 0 14px; }
.section .todo { color: var(--muted); font-size: 15px; max-width: 46em; }

/* Masonry: two explicit columns to match the reference exactly */
.work { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
.col { display: flex; flex-direction: column; gap: 26px; }

/* ---------- Project card (shared component) ---------- */
.card {
  display: block;
  color: var(--ink);
  border-radius: var(--r);
  transition: transform .18s ease;
}
.card:focus-visible { outline: 2px solid var(--garden); outline-offset: 6px; border-radius: var(--r); }

.card .thumb {
  position: relative;
  width: 100%;
  border-radius: 0;
  overflow: hidden;
  background: var(--ph, #eee);
  /* aspect-ratio set per-card inline via --ratio */
  aspect-ratio: var(--ratio, 13 / 10);
}
.card .thumb img,
.card .thumb video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.card:hover .thumb img,
.card:hover .thumb video { transform: scale(1.015); }
.card .thumb img,
.card .thumb video { transition: transform .5s ease; }
.card .thumb .lottie { position: absolute; inset: 0; width: 100%; height: 100%; }
.card .thumb .lottie svg { display: block; }

/* placeholder shimmer label until real media is dropped in */
.thumb .ph-tag {
  position: absolute;
  left: 12px; bottom: 12px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: rgba(0,0,0,.32);
  background: rgba(255,255,255,.5);
  padding: 2px 6px;
  border-radius: var(--r);
  backdrop-filter: blur(2px);
}

.card .meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-top: 14px;
}
.card .meta .title { font-size: 15px; font-weight: 400; color: var(--ink); line-height: 1.4; }
.card .meta .year { font-size: 14px; font-weight: 400; color: var(--text-secondary); flex: none; white-space: nowrap; font-variant-numeric: tabular-nums; }
.card .line {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.45;
}
/* highlight pills — the one or two things that set a project apart.
   They drop into the thumbnail's top-right corner on hover / keyboard focus. */
.card .thumb .pills {
  position: absolute; top: 14px; right: 14px; left: 14px; z-index: 1;
  display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px;
  pointer-events: none;
  opacity: 0; transform: translateY(-6px);
  transition: opacity .22s ease, transform .22s ease;
}
.card .thumb .pills span {
  font-size: 13px; font-weight: 400; line-height: 1; color: var(--ink);
  background: rgba(255, 255, 255, .92);
  /* hairline + soft shadow so the pill still reads on white covers */
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .08), 0 2px 8px rgba(0, 0, 0, .10);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  padding: 7px 12px; border-radius: 999px; white-space: nowrap;
}
.card:hover .thumb .pills,
.card:focus-visible .thumb .pills { opacity: 1; transform: none; }
/* no hover on touch screens, so the pills just stay out */
@media (hover: none) {
  .card .thumb .pills { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .card .thumb .pills { transition: none; transform: none; }
}

/* Play: three columns of thumbnail-only cards; pills sit bottom-right.
   The garden is the feature: it spans the first two columns, and the
   right column runs the full height beside it. */
.work--play { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: auto 1fr; row-gap: 16px; }
/* Play: tiles stack as tightly as the columns sit apart */
.work--play .col { gap: 16px; }
.work--play .play-feature { grid-column: 1 / 3; grid-row: 1; }
.work--play .col[data-col="left"] { grid-column: 1; grid-row: 2; }
.work--play .col[data-col="mid"] { grid-column: 2; grid-row: 2; }
.work--play .col[data-col="right"] { grid-column: 3; grid-row: 1 / span 2; }
/* mid-size screens: two columns. The column wrappers step aside and every
   tile flows into one two-column grid; the feature still runs full width. */
@media (min-width: 901px) and (max-width: 1320px) {
  .work--play { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: none; align-items: start; }
  .work--play .col, .work--play .play-feature { display: contents; }
  .work--play .play-feature > .card { grid-column: 1 / -1; }
}
.work--play .card .thumb .pills { top: auto; bottom: 14px; transform: translateY(6px); }
.work--play .card:hover .thumb .pills,
.work--play .card:focus-visible .thumb .pills { transform: none; }
@media (hover: none) { .work--play .card .thumb .pills { transform: none; } }

/* an embedded sketch at its native size, scaled down from the top-left */
.card--embed .thumb iframe { position: absolute; left: 0; top: 0; transform-origin: 0 0; display: block; }
.card--embed .thumb { cursor: default; }

/* Love letter tile (play.html): a typewriter feeds the letter up on a sheet of paper.
   From loveletter-tile.html, scoped under .tw so .letter etc. don't leak.
   The tile starts at 480px and grows with the paper (JS sets its height). */
.card--letter { cursor: default; }
.work--play { align-items: start; }
.tw { --dark: #161513; }
.tw .tile { position: relative; background: var(--dark); border-radius: 0; overflow: hidden; min-height: 480px; height: auto; width: 100%; container-type: inline-size; transition: height .18s ease-out; }
.tw .gh { position: absolute; right: 20px; top: 18px; font-size: 13px; color: rgba(252,250,247,.5); text-decoration: none; letter-spacing: .04em; z-index: 5; }
.tw .gh:hover { color: rgba(252,250,247,.9); }
.tw .btn {
  position: absolute; left: 50%; bottom: 52px; transform: translateX(-50%); z-index: 5;
  background: #F1EDE5; color: #1C1A17; border: 0; border-radius: 0;
  padding: 7px 14px 8px; font: 400 13px/1 "IBM Plex Mono", monospace; letter-spacing: .02em; cursor: pointer; white-space: nowrap;
  box-shadow: inset -1px -1px 0 #4B4843, inset 1px 1px 0 #FFFFFF, inset -2px -2px 0 #8A8378, inset 2px 2px 0 #FCFAF7, 0 4px 16px rgba(0,0,0,.55);
}
.tw .btn:hover { background: #FCFAF7; }
.tw .btn:active, .tw .btn.down {
  transform: translateX(-50%);
  box-shadow: inset 1px 1px 0 #4B4843, inset -1px -1px 0 #FFFFFF, inset 2px 2px 0 #8A8378, inset -2px -2px 0 #FCFAF7, 0 2px 8px rgba(0,0,0,.55);
  padding: 8px 13px 7px 15px;
}
.tw .btn:focus-visible { outline: 1px dotted #1C1A17; outline-offset: -5px; }
/* the file's bottom:-26% of a 720px tile, restated against the width so the
   platen stays put while the tile grows */
.tw .machine { position: absolute; left: 50%; bottom: -36cqw; width: 84%; transform: translateX(-50%); pointer-events: none; z-index: 3; display: block; }
.tw .machine img { width: 100%; height: auto; display: block; }
/* paper: bottom sits behind the platen; grows upward */
.tw .paper {
  position: absolute; left: 50%; transform: translateX(-50%); width: 66%; background: #FCFAF7; z-index: 2;
  border-radius: 2px 2px 0 0; box-shadow: 0 2px 12px rgba(0,0,0,.35); padding: 20px 20px 84px;
  transition: height .18s ease-out, transform .25s ease-in, opacity .25s ease-in; overflow: hidden;
}
.tw .paper::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: repeating-linear-gradient(0deg, rgba(0,0,0,.015) 0 1px, transparent 1px 3px); mix-blend-mode: multiply; }
.tw .paper.out { transform: translateX(-50%) translateY(-110%); opacity: 0; }
.tw .ascii { margin: 0 0 14px; font: 400 13px/1.15 "IBM Plex Mono", monospace; color: var(--muted); white-space: pre; letter-spacing: normal; }
.tw .letter { margin: 0; font: 400 15px/1.6 "IBM Plex Mono", monospace; color: var(--ink); white-space: pre-wrap; word-wrap: break-word; letter-spacing: normal; }
.tw .sig { margin: 12px 0 0; font: 400 13px/1.6 "IBM Plex Mono", monospace; color: var(--muted); opacity: 0; transition: opacity .2s; letter-spacing: normal; }
.tw .sig.show { opacity: 1; }
.tw .cur { display: inline-block; animation: tw-blink 1s steps(1) infinite; }
@keyframes tw-blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .tw .tile, .tw .paper, .tw .sig { transition: none; } .tw .cur { animation: none; } }

/* a soft paper tint over embedded sketches whose canvas is plain white */
.card--embed .embed-tint { position: absolute; inset: 0; pointer-events: none; background: #f4efe6; mix-blend-mode: multiply; }

/* Play: a title that rises into the top-left corner with the pills */
.card .thumb .hover-title {
  position: absolute; left: 14px; top: 14px; z-index: 1; margin: 0; pointer-events: none;
  font-size: 13px; font-weight: 500; line-height: 1; color: var(--ink);
  background: rgba(255, 255, 255, .92);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .08), 0 2px 8px rgba(0, 0, 0, .10);
  padding: 7px 12px; border-radius: 999px; white-space: nowrap;
  opacity: 0; transform: translateY(-6px);
  transition: opacity .22s ease, transform .22s ease;
}
.card:hover .thumb .hover-title,
.card:focus-visible .thumb .hover-title { opacity: 1; transform: none; }
@media (hover: none) { .card .thumb .hover-title { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .card .thumb .hover-title { transition: none; transform: none; } }

/* optional tag pills (hidden unless a card declares tags) */
.card .tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.card .tags span {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 2px 8px;
}

/* ---------- Page footer (bottom-left, spans full width) ---------- */
.page-footer {
  grid-column: 1 / -1;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--faint);
  margin-top: 130px;
  min-height: 75px;
  padding: 10px 0;
}

/* ---------- JENN LLM panel ---------- */
.llm-backdrop {
  position: fixed; inset: 0; z-index: 55;
  background: rgba(20, 18, 14, 0.28);
  opacity: 0; pointer-events: none;
  transition: opacity 0.25s ease;
}
body.llm-open .llm-backdrop { opacity: 1; pointer-events: auto; }
@media (min-width: 721px) { .llm-backdrop { display: none; } }

.llm-panel {
  position: fixed; top: 0; right: 0; z-index: 60;
  width: var(--panel-w); max-width: 100vw; height: 100dvh;
  background: var(--panel);
  padding: 20px 20px 16px;
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.55s cubic-bezier(0.2, 0.7, 0.2, 1);
  will-change: transform;
}
body.llm-open .llm-panel { transform: none; }

.llm-close {
  position: absolute; top: 18px; left: 22px;
  width: 22px; height: 22px; padding: 0;
  border: 0; background: none; cursor: pointer; color: var(--muted);
  display: grid; place-items: center;
}
.llm-close:hover { color: var(--ink); }

.llm-scroll {
  flex: 1; overflow-y: auto;
  margin-top: 30px;   /* starts below the close icon, so scrolled text never runs under it */
  /* the first bubble starts on the same line as the work column, which begins
     one top-bar down the page (panel padding takes 20 of those px) */
  padding: calc(var(--head-h) - 50px) 0 16px;
  display: flex; flex-direction: column; gap: 22px;
  scrollbar-width: none;
  /* scrolled text fades out before it reaches the close icon above or the ant below */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%);
}
.llm-scroll::-webkit-scrollbar { display: none; }

/* empty state: the ant's strip, one line of welcome, three suggestions */
.llm-hello {
  margin: auto 0; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 0;
  transition: opacity 0.3s ease;
}
.llm-hello p { margin: 0 0 35px; font-size: 13px; line-height: 1.55; color: var(--muted); }   /* Figma: 35px to the chips */
.llm-hello .ant-dock { width: 220px; margin: 0 0 16px; }   /* air between the ant and its line */

.llm-chips { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 10px 16px; border: 0; border-radius: 6px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(20, 18, 14, .06);
  font-family: var(--font-sans); font-size: 13px; font-weight: 300; line-height: 1.2; color: var(--ink);   /* Figma: UI/Chip */
  cursor: pointer; transition: box-shadow 0.2s ease, transform 0.2s ease;
  text-align: center;
}
.chip:hover { box-shadow: 0 2px 6px rgba(20, 18, 14, .12); }
.chip svg { display: none; }   /* Figma: the chips are text only */

/* messages: a black pill for the visitor, plain prose for the answer */
.msg { font-size: 13px; line-height: 1.55; animation: msg-in 0.35s cubic-bezier(0.2, 0.7, 0.2, 1) both; }
@keyframes msg-in { from { opacity: 0; transform: translateY(6px); } }
.msg.user {
  align-self: flex-end; max-width: 86%;
  background: #17150f; color: var(--bg);
  padding: 7px 12px; border-radius: var(--r); font-weight: 400;
  white-space: pre-wrap;
}
.msg.bot { align-self: flex-start; color: var(--text-body); }
.msg.followup { align-self: flex-start; display: flex; flex-wrap: wrap; gap: 10px; margin-top: 0; }
.msg.bot p { margin: 0 0 12px; }
.msg.bot p:last-child { margin: 0; }
.msg.bot ul { margin: 4px 0 8px; padding-left: 18px; }
.msg.bot li { margin: 2px 0; }
.msg.bot strong { font-weight: 500; color: var(--ink); }
.msg.bot a { text-decoration: underline; }
.msg.bot code { font-family: var(--font-mono); font-size: 12px; background: rgba(0, 0, 0, 0.06); padding: 1px 4px; border-radius: var(--r); }
.msg.bot hr { border: 0; border-top: 1px solid var(--line); margin: 8px 0; }
.msg.bot .caret { display: inline-block; width: 6px; height: 12px; margin-left: 1px; background: var(--faint); vertical-align: -1px; animation: caret 1s steps(1) infinite; }
@keyframes caret { 50% { opacity: 0; } }

.typing { align-self: flex-start; display: inline-flex; gap: 4px; padding: 6px 0; }
.typing i { width: 5px; height: 5px; border-radius: 50%; background: var(--faint); animation: dot 1s infinite; }
.typing i:nth-child(2) { animation-delay: 0.15s; }
.typing i:nth-child(3) { animation-delay: 0.3s; }
@keyframes dot { 0%, 100% { opacity: 0.5; } 40% { transform: translateY(-3px); opacity: 1; } }

/* composer */
.llm-compose {
  position: relative; background: #fff; border-radius: 8px;
  box-shadow: 0 1px 3px rgba(20, 18, 14, .07);
  padding: 14px 16px 44px; min-height: 96px;
}
.llm-compose textarea {
  width: 100%; height: 42px; border: 0; outline: 0; resize: none; background: none;
  font-family: var(--font-sans); font-size: 13px; font-weight: 300; line-height: 1.5; color: var(--ink);
}
.llm-compose textarea::placeholder { color: var(--faint); font-weight: 400; }
.llm-send {
  position: absolute; right: 12px; bottom: 12px;
  width: 32px; height: 32px; border-radius: 6px;
  border: 0; background: var(--faint); color: #fff; cursor: pointer;
  display: grid; place-items: center;
  transition: background 0.2s ease;
}
.llm-send.ready { background: #17150f; }
.llm-send:disabled { cursor: default; }

@media (max-width: 720px) { :root { --panel-w: 100vw; } }

/* ---------- Chat ant ----------
   A pixel ant wanders the sidebar's hairline. Click it and its "!" bubble
   types out an invite; click again and it sinks through the rule and
   re-emerges inside the JENN LLM panel. Built by panel.js. */
/* the bubble floats 26px above the line and is 20px tall, so the rule needs
   ~46px of clear air above it — 24px sidebar gap + this margin = 56px */
.ant-rule { position: relative; margin-top: 58px; clip-path: inset(-70px 0 0 0); }

/* ant and bubble ride one carriage, so they can never drift apart */
.ant-walker {
  position: absolute; bottom: 0; left: 0;
  width: 40px; height: 20px;
  transition: left .9s linear;
  will-change: left;
}
.ant {
  position: absolute; bottom: 0; left: 0;
  width: 40px; height: 20px;
  padding: 0; border: 0; background: none;
  color: var(--ink); cursor: pointer;
}
.ant::before { content: ""; position: absolute; inset: -10px -8px; }   /* easier to hit */
.ant svg { display: block; width: 40px; height: 20px; shape-rendering: crispEdges; }
.ant.flip svg { transform: scaleX(-1); }
/* goodbye wave: a front leg raised above the head, rocking */
.ant svg { overflow: visible; }
.ant .arm { opacity: 0; }
.ant.wave .arm { opacity: 1; }
.ant.wave .arm .w1 { animation: ant-step .32s steps(1) infinite; }
.ant.wave .arm .w2 { animation: ant-step .32s steps(1) infinite reverse; }
.ant .legB { opacity: 0; }
.ant.walk .legB { animation: ant-step .14s steps(1) infinite; }
.ant.walk .legA { animation: ant-step .14s steps(1) infinite reverse; }
@keyframes ant-step { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

/* typing: a little keyboard slides under the front feet, which tap on it until
   the answer is finished (panel.js keeps .type on for exactly that long) */
.ant .keys {
  position: absolute; right: 2px; bottom: 0;
  width: 18px; height: 6px;
  background: url("/images/keyboard.svg") center / 18px 6px no-repeat;
  image-rendering: pixelated;
  opacity: 0; transition: opacity .18s ease;
  pointer-events: none;
}
.ant.type .keys { opacity: 1; }
.ant.type { transform: translateY(-4px); }   /* stands on the keys, not through them */

.ant.type .legA, .ant.type .legB { opacity: 1; animation: none; }
.ant.type .legA .back { opacity: 1; }
.ant.type .legB .back { opacity: 0; }
.ant.type .legA .front { animation: ant-step .1s steps(1) infinite; }
.ant.type .legB .front { animation: ant-step .1s steps(1) infinite reverse; }
.ant.sink { animation: ant-sink .55s cubic-bezier(.5, 0, .9, .4) forwards; }
@keyframes ant-sink { to { transform: translateY(24px); } }
.ant.emerge { animation: ant-emerge .45s cubic-bezier(.2, .7, .2, 1) both; }
@keyframes ant-emerge { from { transform: translateY(24px); } to { transform: none; } }

/* centred over the ant, nudged inward by --shift when it would run off the end */
.ant-bubble {
  position: absolute; bottom: 26px; left: 50%;
  transform: translateX(calc(-50% + var(--shift, 0px)));
  height: 20px; width: 18px; padding: 0;
  border: 0; border-radius: var(--r);
  background: #17150f; color: var(--bg);
  /* pixel face to match the pixel ant */
  font-family: "VT323", var(--font-mono); font-size: 17px; font-weight: 400; line-height: 20px;
  white-space: nowrap; text-align: center; cursor: pointer;
  clip-path: inset(0 0 -8px 0);   /* clip the text sideways, but let the tail hang below */
  transition: transform .35s cubic-bezier(.2, .7, .2, 1), width .35s cubic-bezier(.2, .7, .2, 1),
              background .25s ease, opacity .2s ease;
}
/* the tail points at the ant — panel.js keeps --tail on the ant's middle
   even when the bubble has been nudged inward to stay on screen */
.ant-bubble::after {
  content: ""; position: absolute; left: var(--tail, 50%); bottom: -4px;
  width: 9px; height: 6px; background: inherit;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  transform: translateX(-50%);
  transition: left .35s cubic-bezier(.2, .7, .2, 1);
}
.ant-bubble.expanded { width: auto; padding: 0 10px; bottom: 30px; background: var(--muted); }
.ant-bubble.expanded:hover { background: #17150f; }

/* ---- states ----
   .loading  = on its way into the panel, not ready for you yet
   thinking  = Anty is composing, so the ant paces with a little dot bubble */
.ant.loading { animation: ant-breathe 1.1s ease-in-out infinite; }
@keyframes ant-breathe { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

.ant-think {
  position: absolute; bottom: 26px; left: 50%;
  transform: translateX(-50%) scale(.7);
  display: inline-flex; gap: 3px; align-items: center;
  height: 20px; padding: 0 7px; border-radius: var(--r);
  background: #17150f;
  opacity: 0; pointer-events: none;
  transition: opacity .2s ease, transform .2s cubic-bezier(.2, .7, .2, 1);
}
.ant-think::after {
  content: ""; position: absolute; left: 50%; bottom: -4px;
  width: 9px; height: 6px; background: inherit;
  clip-path: polygon(0 0, 100% 0, 50% 100%); transform: translateX(-50%);
}
.ant-think i { width: 4px; height: 4px; border-radius: 50%; background: var(--bg); animation: ant-think-dot 1s infinite; }
.ant-think i:nth-child(2) { animation-delay: .15s; }
.ant-think i:nth-child(3) { animation-delay: .3s; }
@keyframes ant-think-dot { 0%, 100% { opacity: .35; } 40% { opacity: 1; transform: translateY(-2px); } }
body.llm-thinking .ant-think { opacity: 1; transform: translateX(-50%) scale(1); }
/* goodbye: the same bubble says "bye!" while the ant waves */
.ant-think.bye { opacity: 1; transform: translateX(-50%) scale(1); padding: 0 7px; font-family: "VT323", var(--font-mono); font-size: 17px; line-height: 20px; color: var(--bg); }
/* in the panel the ant waits at the far left, so its bubble goes with it */
.ant-perch .ant-think { left: 20px; }

/* landing strip inside the panel — collapses while the ant is outside */
.ant-dock { position: relative; height: 20px; margin: 0 20px; clip-path: inset(-40px 0 0 0); }
.ant-dock:empty { height: 0; }
/* the ant's spot while a conversation is running */
.ant-perch { margin: 30px 20px 10px; }   /* room for the "..." bubble above the ant */
.ant-perch:empty { height: 0; margin: 0; }
.ant-dock .ant { transition: left 1.8s linear; }

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  * { scroll-behavior: auto !important; }
  .card, .card .thumb img, .card .thumb video, .nav-list a .arr { transition: none !important; transform: none !important; }
  .llm-panel, .llm-backdrop, body { transition: none !important; }
  .typing i, .msg.bot .caret { animation: none; }
  .ant-walker, .ant-bubble, .ant-think { transition: none !important; }
  .ant.loading, .ant-think i { animation: none; }
  .ant.walk .legA, .ant.walk .legB { animation: none; }
}

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
  :root { --head-h: 62px; }               /* 22px page inset + 30px logo */
  .page { grid-template-columns: 1fr; gap: 40px; padding: var(--head-h) 22px 0; }
  .sidebar { position: static; }
  .head-inner { padding: 22px 22px 0; }
}
@media (max-width: 600px) {
  .work { grid-template-columns: 1fr; }
  .work--play > * { grid-column: auto !important; grid-row: auto !important; }
  .col { gap: 30px; }
}

/* ==========================================================================
   About page — centred 1000px column (Figma redesign, Sep 2026)
   ========================================================================== */
.page--about { grid-template-columns: minmax(0, 1fr); justify-items: center; }
.about-col { width: 100%; max-width: 1000px; padding: 64px 0 320px; display: flex; flex-direction: column; gap: 64px; }
.about-col > * { margin: 0; }

.about-title {
  font-family: var(--font-serif); font-weight: 400;      /* Crimson Pro Regular */
  font-size: 36px; line-height: 42.5px; letter-spacing: -0.01em;
  color: var(--ink);
}

/* photo carousel — 280px squares that drift sideways on their own */
.photo-rail {
  position: relative;
  overflow: hidden;
}
.photo-track { display: flex; gap: 8px; width: max-content; animation: rail-drift 60s linear infinite; }
.photo-rail:hover .photo-track,
.photo-rail:focus-within .photo-track { animation-play-state: paused; }
@keyframes rail-drift { to { transform: translateX(calc(-50% - 4px)); } }   /* one full copy + one gap */

.photo-rail figure { flex: none; margin: 0; display: flex; flex-direction: column; gap: 7px; }
.photo-rail img {
  display: block; width: 280px; height: 280px;
  object-fit: cover; background: #f0ece5;
}
.photo-rail figcaption {
  font-family: var(--font-mono); font-size: 10.5px; line-height: 1.55;
  letter-spacing: 0.42px; color: var(--faint);
}

/* the story */
.about-prose { display: flex; flex-direction: column; gap: 28px; max-width: 655px; }
.about-prose p { margin: 0; font-size: 16px; font-weight: 300; line-height: 1.65; color: var(--text-body); }

/* the ant keeps a line of its own at the foot of the page */
.about-col .rule { margin-top: 24px; }

@media (prefers-reduced-motion: reduce) {
  .photo-track { animation: none; }
  .photo-rail { overflow-x: auto; }
}
@media (max-width: 900px) {
  .about-col { padding: 40px 0 160px; gap: 48px; }
  .photo-rail img { width: 220px; height: 220px; }
}

/* ==========================================================================
   Cursor — 16×16 "Union" mark in garden green (#4A7C59).
   The native cursor is the baseline (touch, no-JS). On a fine pointer panel.js
   swaps in .cursor-mark so the mark can spin once on click.
   ========================================================================== */
html, body, a, button, input, textarea, [role="button"], .card, .chip {
  cursor: url("/images/cursor.svg") 8 8, auto;
}

@media (pointer: fine) {
  body.has-cursor,
  body.has-cursor a,
  body.has-cursor button,
  body.has-cursor input,
  body.has-cursor textarea,
  body.has-cursor [role="button"],
  body.has-cursor .card,
  body.has-cursor .chip { cursor: none; }

  .cursor-mark {
    position: fixed; top: 0; left: 0; z-index: 9999;
    width: 16px; height: 16px;
    pointer-events: none;
    opacity: 0;
    will-change: transform;
  }
  body.has-cursor .cursor-mark { opacity: 1; }
  .cursor-mark.gone { opacity: 0; }

  .cursor-mark i {
    display: block; width: 100%; height: 100%;
    background: url("/images/cursor.svg") center / 16px 16px no-repeat;
  }
  /* one full turn per click */
  .cursor-mark.spin i { animation: cursor-spin .45s cubic-bezier(.34, 0, .2, 1); }
}

@keyframes cursor-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .cursor-mark.spin i { animation: none; }
}

/* ---------- Tracking ----------
   -0.02em rides on <body>, so every IBM Plex Sans style picks it up by
   inheritance. Crimson Pro and the mono faces are not Plex: the ones that
   set no tracking of their own are put back to normal here. */
.section > h2,
.garden,
.sprout .bubble,
.side-footer,
.page-footer,
.thumb .ph-tag,
.card .tags span,
.msg.bot code { letter-spacing: normal; }

/* a card that is the toy itself — no link, no hover lift */
.card--live { cursor: default; }
.card--live .thumb { cursor: crosshair; }

/* ==========================================================================
   My garden (play.html) — ASCII plants scattered on a patch. Hover one to
   water it; garden.js places them and drives the growing.
   ========================================================================== */
.garden-toy { position: absolute; inset: 0; overflow: hidden; background: #333333; container-type: size; }
.plant {
  --leaf: #22c55e; --ground: #86efac; --bark: #a78b6b; --petal: var(--leaf);
  position: absolute;
  transform: translate(-50%, -100%);
  transform-origin: bottom center;
  padding: 0; border: 0; background: none; cursor: crosshair;
  font-family: var(--font-mono); line-height: 1.2;
  /* the plot is drawn for a 432px-wide portrait tile at 11px; glyphs scale
     with the tile so the composition holds at any width */
  font-size: 11px; font-size: calc(100cqw * 11 / 432);
  white-space: pre; letter-spacing: normal; text-align: center;
}
.plant-art { display: flex; flex-direction: column; align-items: center; }
.plant-art span { display: block; }
.plant .leaf   { color: var(--leaf); }
.plant .ground,
.plant .stem   { color: var(--ground); }
.plant .trunk  { color: var(--bark); }
.plant .petal  { color: var(--petal); }
.plant--scenery { cursor: default; }
.plant:focus-visible { outline: 2px solid var(--garden); outline-offset: 4px; border-radius: 4px; }
.plant.grew { animation: plant-pop .4s cubic-bezier(.2, .7, .2, 1); }
@keyframes plant-pop {
  0%   { transform: translate(-50%, -100%) scale(.94) translateY(2px); }
  100% { transform: translate(-50%, -100%); }
}

/* one drop falls each time you water */
.garden-drop {
  position: absolute; top: -14px; left: 50%;
  width: 2px; height: 6px; margin-left: -1px;
  background: #8fb6d8; border-radius: 1px;
  animation: garden-drop .3s ease-in forwards;
}
@keyframes garden-drop { to { transform: translateY(16px); opacity: 0; } }

.garden-tip {
  position: absolute; left: 50%; bottom: calc(100% + 10px);
  transform: translate(-50%, 4px);
  font-family: var(--font-mono); font-size: 10px; line-height: 1; letter-spacing: normal;
  color: #1E1E1E; background: #fff;
  padding: 5px 8px; border-radius: 5px; white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
}
.garden-tip.on { opacity: 1; transform: translate(-50%, 0); }
.garden-tip::after {
  content: ""; position: absolute; left: 50%; bottom: -5px;
  margin-left: -5px; border: 5px solid transparent; border-top-color: #fff; border-bottom: 0;
}

@media (prefers-reduced-motion: reduce) {
  .plant.grew { animation: none; }
}
