/* ============================================================
   Kishaan Gidda — HOME layout
   Loads after site.css, which owns the tokens, the chrome,
   the column grid, the bracket, and the silk field.
   ============================================================ */

main { position: relative; z-index: 1; }

/* ============================================================
   HERO — the silk field at full strength, one serif line
   ============================================================ */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  align-items: center;
  padding: calc(var(--bar-top) + 24px) var(--gutter) calc(var(--bar) + 20px);
}

.hero-inner {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  align-self: center;
}

.hero-line {
  margin: 22px 0 0;
  font-size: clamp(2.55rem, 7.1vw, 7rem);
  font-weight: 400;
  letter-spacing: -0.038em;
  line-height: 1.0;
  color: var(--display);
}

.hero-sub {
  margin: 34px 0 0;
  max-width: 44ch;
  font-size: 1.02rem;
  line-height: 1.62;
  color: var(--ink);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px;
  margin: 28px 0 0;
  margin-left: -12px;   /* pull the bracket's padding back to the grid line */
}
.hero-actions .turn { margin-left: 12px; }

.hero-now {
  align-self: end;
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  max-width: 1180px;
  margin: 0 auto;
  width: 100%;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* the "currently" marker — status by weight and a dot, never by hue */
.dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--display);
  flex: 0 0 auto;
}
@media (prefers-reduced-motion: no-preference) {
  .dot { animation: pulse 3.4s ease-in-out infinite; }
  @keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.32; }
  }
}

/* ============================================================
   Sections
   ============================================================ */

.sec {
  position: relative;
  width: var(--container);
  margin: 0 auto;
  padding: clamp(96px, 13vh, 168px) 0;
}
.sec--tint {
  width: 100%;
  padding-left: max(var(--gutter), calc((100% - var(--container)) / 2));
  padding-right: max(var(--gutter), calc((100% - var(--container)) / 2));
  background: var(--paper-tint);
  border-block: 1px solid var(--rule);
}

.sec-head { margin-bottom: clamp(44px, 6vw, 78px); }
.sec-title {
  margin: 14px 0 0;
  font-size: clamp(2.4rem, 6vw, 4.6rem);
  line-height: 1;
}
.sec-lede {
  margin: 22px 0 0;
  max-width: 46ch;
  font-size: 1.02rem;
  color: var(--ink-soft);
}

/* ============================================================
   WORK — sticky index, scroll-driven viewer
   ============================================================ */

/* The section is a scroll runway; .work-pin sticks inside it and
   holds still while the runway passes, so scrolling advances the
   viewer instead of scrolling past it. Runway length = the pinned
   height plus one segment of scroll per project.

   Column tracks are exact quarters with NO gap, and the breathing
   room comes from padding-right on the children. A `gap` would
   shift every content edge off the hairline grid by a fraction of
   the gutter — the whole point of the grid is that things land on
   it. Same pattern below. */
.work {
  --pin-top: calc(var(--bar-top) + clamp(20px, 4vh, 40px));
  /* The bottom allowance must clear the FIXED footer bar, not just
     leave a margin. At 32px the pin ended 42px underneath the bar
     and the last line of a tall slide body was hidden behind it —
     on a short viewport the figure shrinks as designed, so the body
     is what reaches the bottom edge. */
  --pin-h: min(740px, calc(100svh - var(--pin-top) - var(--bar) - 16px));
  --seg: 92svh;   /* scroll distance each project gets */
  position: relative;
  height: calc(var(--pin-h) + var(--slides, 3) * var(--seg));
}

.work-pin {
  position: sticky;
  top: var(--pin-top);
  height: var(--pin-h);
  display: grid;
  grid-template-columns: 25% 75%;
  align-items: start;
}

.work-index {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding-right: var(--gutter);
}
.work-count {
  margin-top: auto;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
}
.work-count [data-count-now] { color: var(--display); }
.work-index ol { border-top: 1px solid var(--rule); }
.work-index li { border-bottom: 1px solid var(--rule); }
/* the active row steps in on transform, not padding — padding
   would relayout the whole sticky column on every scroll-spy tick.
   Centred in the band so no name is left resting on the column
   rule; the rule is furniture, not a margin to write against. */
.work-index a {
  display: block;
  padding: 17px 0;
  text-align: center;
  transition: transform 260ms var(--ease);
}
.work-index__name {
  display: block;
  font-family: var(--serif);
  font-variation-settings: "opsz" 24;
  font-size: 1.24rem;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--ink-soft);
  transition: color 300ms var(--ease);
}
.work-index__kind {
  display: block;
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  opacity: 0.62;
  transition: color 300ms var(--ease), opacity 300ms var(--ease);
}
.work-index a:hover .work-index__name { color: var(--display); }
.work-index a.on { transform: translateX(14px); }
.work-index a.on .work-index__name { color: var(--display); }
.work-index a.on .work-index__kind { color: var(--ink-soft); opacity: 1; }
/* the active marker rides the left edge, on the grid line */
.work-index li { position: relative; }
.work-index li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 17px;
  width: 1px;
  height: 26px;
  background: var(--display);
  transform: scaleY(0);
  transform-origin: top center;
  transition: transform 320ms var(--ease);
}
.work-index li:has(a.on)::before { transform: scaleY(1); }

/* ---- plates ------------------------------------------------
   Each project's viewer gets its own ground. This is the one
   place on the site that leaves the single blue family, and it
   earns it: the plate is what tells you the slide changed, and
   the hue carries the subject. They stay low-chroma tints so
   the viewer still reads as paper under glass rather than three
   coloured boxes, and every ink below clears 4.5:1 on its own
   plate. The four values are written out per plate rather than
   derived, so canvas can read them straight back.
   ------------------------------------------------------------ */

.slide {
  --plate: var(--paper-tint);
  --plate-ink: var(--display);
  --plate-soft: var(--ink-soft);
  --plate-rule: rgba(39, 81, 126, 0.28);
  --plate-rule-soft: rgba(39, 81, 126, 0.14);
}

/* asphalt — tarmac, timing screens, a cold pit lane */
.slide[data-plate="track"] {
  --plate: #e9edec;
  --plate-ink: #24343a;
  --plate-soft: #4e6068;
  --plate-rule: rgba(36, 52, 58, 0.30);
  --plate-rule-soft: rgba(36, 52, 58, 0.15);
}

/* clinical calm — the colour of care, and of the product's
   safety-first thesis */
.slide[data-plate="care"] {
  --plate: #e6efe9;
  --plate-ink: #1f4a39;
  --plate-soft: #4a6f5f;
  --plate-rule: rgba(31, 74, 57, 0.28);
  --plate-rule-soft: rgba(31, 74, 57, 0.14);
}

/* the record — picks up the red rule and Carleton crest in the
   results crop rather than fighting it */
.slide[data-plate="ctf"] {
  --plate: #f4ece9;
  --plate-ink: #5e2a24;
  --plate-soft: #82544c;
  --plate-rule: rgba(94, 42, 36, 0.26);
  --plate-rule-soft: rgba(94, 42, 36, 0.13);
}

/* the plate colours the type that describes the figure too */
.slide-body h3 { color: var(--plate-ink); }
.slide-body .label { color: var(--plate-soft); }
.slide-text strong { color: var(--plate-ink); }
.slide-links .br { color: var(--plate-soft); }
.slide-links .br:hover { color: var(--plate-ink); }

/* the viewer: slides stack in place and cross-fade */
.work-stage { position: relative; height: 100%; }

/* The cross-fade is tied to scroll position, not fired as a timed
   transition on a threshold — that flip is what made the change
   feel like a stop. --a and --dy come from home.js every frame, so
   a slide hands over gradually and the pinned content keeps moving
   with the wheel instead of sitting frozen between swaps. The two
   slides drift in opposite directions through the handover so the
   overlap reads as one passing the other rather than as mud. */
.slide {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  opacity: var(--a, 0);
  transform: translate3d(0, var(--dy, 0px), 0);
  visibility: hidden;
  pointer-events: none;
  will-change: opacity, transform;
}
/* lit = contributing to the frame; active = the one being read */
.slide.is-lit { visibility: visible; }
.slide.is-active { pointer-events: auto; }

.slide-figure {
  position: relative;
  margin: 0;
  flex: 1 1 auto;
  min-height: 0;          /* the figure gives its height to the pin */
  background: var(--plate);
  border: 1px solid var(--plate-rule-soft);
  overflow: hidden;
}
.slide-figure canvas { width: 100%; height: 100%; }

/* A real artifact rather than a made visual: a crop of the
   results listing. It is shown at its own size on white and
   captioned for what it is, not blown up to fake a hero shot. */
.slide-figure--record {
  display: grid;
  place-content: center;
  gap: 22px;
  background: var(--plate);
  padding: clamp(32px, 4vw, 64px) 32px;
}
/* The source crop (383×267) carries the top edge of the next
   entry in the results table. Trim it so the plate shows one
   complete record instead of one and a sliver. */
.slide-figure--record figcaption { color: var(--plate-soft); }
.slide-figure--record img {
  width: min(560px, 100%);
  aspect-ratio: 383 / 236;
  object-fit: cover;
  object-position: center top;
  border: 1px solid var(--plate-rule);
}
.slide-figure--record figcaption { text-align: center; }

/* three tracks inside the viewer's 75%, so the body text
   starts exactly on the middle hairline */
.slide-body {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  padding-top: 24px;
}
.slide-body > * { padding-right: var(--gutter); }
.slide-body .label { grid-column: 1 / -1; grid-row: 1; }
.slide-body h3 {
  grid-column: 1;
  grid-row: 2;
  margin-top: 10px;
  font-size: clamp(1.5rem, 2.6vw, 2.15rem);
}
.slide-text {
  grid-column: 2 / -1;
  grid-row: 2;
  margin-top: 10px;
  max-width: 62ch;
  color: var(--ink);
  line-height: 1.62;
}
.slide-links {
  grid-column: 1;
  grid-row: 3;
  margin: 16px 0 0 -12px;
  align-self: start;
}

/* ============================================================
   EXPERIENCE — a ledger, not a stack of cards
   ============================================================ */

.ledger { position: relative; border-top: 1px solid var(--rule); }
.ledger-row {
  display: grid;
  grid-template-columns: 25% 75%;
  padding: clamp(34px, 4vw, 54px) 0;
  border-bottom: 1px solid var(--rule);
}

.ledger-when {
  display: flex;
  flex-direction: column;
  gap: 18px;
  align-items: flex-start;
  padding-right: var(--gutter);
}
.ledger-mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  padding: 9px;
  background: var(--paper);
  border: 1px solid var(--rule);
}
.ledger-mark img { width: 100%; height: 100%; object-fit: contain; }
.ledger-date {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.ledger-what h3 { font-size: clamp(1.5rem, 2.6vw, 2.15rem); }

/* The name arrives at announcement scale and settles to record
   scale by the time the line reaches its job — size comes from
   scale, never from weight (DESIGN.md §4). It is a transform and
   not a font-size, because a live font-size would reflow the
   bullets under it on every frame, change the ledger's height,
   and feed that height straight back into the spine maths.
   Growing from the bottom left keeps the overhang inside the
   row's own top padding, so the name never crosses a hairline. */
.ledger.is-traced .ledger-what h3 {
  /* the box has to fit the name, not the column: scaling a
     full-width block throws its right edge hundreds of px off
     screen, and leaning on the body's overflow-x to hide that is
     not the same as not doing it */
  width: fit-content;
  transform-origin: left bottom;
  transform: scale(var(--name, 1));
}
/* at rest there is no transform at all, so the heading is never
   left sitting on a composited layer being resampled */
.ledger.is-traced .is-settled .ledger-what h3 { transform: none; }
.ledger-role {
  margin-top: 6px;
  font-size: 0.95rem;
  color: var(--ink-soft);
}
.ledger-list {
  margin-top: 20px;
  max-width: 72ch;
  border-top: 1px solid var(--rule);
}
.ledger-list li {
  position: relative;
  padding: 9px 0 9px 22px;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--ink);
  border-bottom: 1px solid var(--rule-soft);
}
.ledger-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 18px;
  width: 8px;
  height: 1px;
  background: var(--ink-faint);
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  margin: 18px 0 0 -10px;
}
.tags .br {
  padding: 5px 10px;
  cursor: default;
  color: var(--ink-soft);
}
/* tags are notation, not affordance — they read, they don't open */
.tags .br:hover { --br-open: 0em; color: var(--display); background: transparent; }
.tags--wide { margin-top: 14px; }

/* ---- the spine ---------------------------------------------
   The work viewer's scroll drives a slideshow; this one drives
   time, because the ledger is a chronology and the scroll axis
   is already the time axis. One hairline runs the ledger on the
   left column rule and is drawn to the reading position; each
   job's mark is a station on it. The mark column sticks beside
   its own bullets — TrendAI alone runs past a screen and a half,
   and you should never lose whose work you are reading — so the
   filled node travels the spine at exactly the drawn end. The
   pen tip and the ink are the same point.

   Everything here is gated behind .is-traced, which only home.js
   adds, and only above 900px. Without JS, on a phone, or under
   reduced motion the ledger is exactly what it was.
   ------------------------------------------------------------ */

.ledger.is-traced::before,
.ledger.is-traced::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 1px;
}
/* the whole span of the record, unlit */
.ledger.is-traced::before { background: var(--rule); }
/* and how much of it you have walked */
.ledger.is-traced::after {
  background: var(--display);
  transform: scaleY(var(--spine, 0));
  transform-origin: top center;
}

.ledger.is-traced .ledger-when {
  position: sticky;
  /* grid items stretch by default, and a full-height element can
     never stick — start is what makes this work at all */
  align-self: start;
  top: calc(var(--bar-top) + 30px);
  /* clear the rail so the node can sit on the column rule while
     the mark keeps its own edge, the way .ledger-list li already
     indents past its tick */
  padding-left: 34px;
}

.ledger.is-traced .ledger-mark::before,
.ledger.is-traced .ledger-mark::after {
  content: "";
  position: absolute;
  top: 50%;
}
/* the station, centred on the spine */
.ledger.is-traced .ledger-mark::before {
  left: -34px;
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: var(--paper-tint);
  border: 1px solid var(--rule);
  transition: background 320ms var(--ease), border-color 320ms var(--ease);
}
/* and its tie back to the mark */
.ledger.is-traced .ledger-mark::after {
  left: -27px;
  width: 19px;
  height: 1px;
  margin-top: -0.5px;
  background: var(--rule);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 420ms var(--ease), background 320ms var(--ease);
}

/* Until the line reaches a job it is still ahead of you, so its
   mark sits unlit. This is the section's entrance — the rows do
   not also fade up, or Experience would arrive the same way every
   other section does. */
.ledger.is-traced .ledger-mark { transition: border-color 320ms var(--ease); }
/* greyscale carries "not yet reached" on its own; opacity is held
   high enough that a mark you are already reading past is never
   close to invisible, since the line takes most of a row to
   arrive at it */
.ledger.is-traced .ledger-mark img {
  opacity: 0.6;
  filter: grayscale(1);
  transition: opacity 520ms var(--ease), filter 520ms var(--ease);
}

/* reached: the line has been drawn past this job. Never reverts —
   DESIGN.md §6, reveals do not replay on scroll-back. */
.ledger.is-traced .is-reached .ledger-mark { border-color: rgba(39, 81, 126, 0.3); }
.ledger.is-traced .is-reached .ledger-mark img { opacity: 1; filter: none; }
.ledger.is-traced .is-reached .ledger-mark::before { border-color: var(--ink-faint); }
.ledger.is-traced .is-reached .ledger-mark::after {
  background: var(--ink-faint);
  transform: scaleX(1);
}

/* reading: which job you are in. This one does revert, the same
   way the work index tracks whichever project is showing — it is
   a position indicator, not an entrance. Reached is a condition
   of it: the readout holds the first job before you have entered
   the ledger, and the node must not fill ahead of the ink. */
.ledger.is-traced .is-reached.is-reading .ledger-mark::before {
  background: var(--display);
  border-color: var(--display);
}
.ledger.is-traced .is-reached.is-reading .ledger-mark::after { background: var(--display); }

/* the year readout, set exactly as .work-count so the counter
   reads as one device across both sections */
.sec-head--split {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
}
.ledger-scale {
  flex: 0 0 auto;
  padding-bottom: 8px;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
}
.ledger-scale [data-scale-now] {
  color: var(--display);
  transition: color 300ms var(--ease);
}

/* ============================================================
   ABOUT
   ============================================================ */

/* ---- the dossier -------------------------------------------
   Same 25 / 75 split and hairline rows as the ledger above,
   because About is the same kind of object: a record you scan.
   The two sections rhyme on purpose — this one is just shorter. */

.dossier {
  margin: 0;
  border-top: 1px solid var(--rule);
}

.dossier-row {
  display: grid;
  grid-template-columns: 25% 75%;
  padding: clamp(20px, 2.4vw, 30px) 0;
  border-bottom: 1px solid var(--rule);
}

/* mono caps sit high in their box; nudge the label down so it
   sits on the same optical line as the sans beside it. Centred
   in its quarter for the same reason the values are — nothing
   in this list starts on a hairline. */
.dossier-row dt {
  padding-top: 3px;
  text-align: center;
}

/* the toolkit's own top rule closes the list — two hairlines with
   only whitespace between them read as an empty fifth row */
.dossier-row:last-child { border-bottom: 0; }

/* the measure still caps at 56ch; auto margins then sit that
   measure in the middle of the three columns it spans, and the
   lines centre inside it — so a one-line value and a two-line
   value share a centre instead of a left edge */
.dossier-row dd {
  margin: 0 auto;
  max-width: 56ch;
  text-align: center;
  color: var(--ink);
  line-height: 1.6;
}

.dossier-meta {
  display: block;
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ---- the toolkit ------------------------------------------- */

/* padded to match the groups below, so the block's labels share
   one left edge rather than stepping 12px */
.toolkit-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding-left: 12px;
}
.toolkit-note { color: var(--ink-faint); }

/* Four groups on the page's own four columns (DESIGN.md §2), so
   the background grid's rules do the dividing and this adds none
   of its own. The 12px inset less the bracket's own 10px padding
   leaves every group's first glyph 12px clear of its rule. */
.toolkit-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  margin-top: 30px;
}
.toolkit-group { padding: 0 var(--gutter) 0 12px; }

.toolkit-tags {
  display: block;
  margin: 14px 0 0 -10px;
}
.toolkit-tags .br { display: flex; }

/* depth is weight and the display blue, never a second hue (§3) */
.tags .br--depth {
  font-weight: 500;
  color: var(--display);
}

.toolkit {
  margin-top: clamp(48px, 6vw, 76px);
  padding-top: 26px;
  border-top: 1px solid var(--rule);
}

/* ============================================================
   CLOSE — the silk rises again, and the work's three
   territories converge on the address
   ============================================================ */

.close {
  width: 100%;
  padding-left: max(var(--gutter), calc((100% - var(--container)) / 2));
  padding-right: max(var(--gutter), calc((100% - var(--container)) / 2));
  padding-bottom: calc(var(--bar) + 48px);
  overflow: hidden;
  border-top: 1px solid var(--rule);
}

.silk-host--close {
  top: auto;
  height: 78%;
  bottom: 0;
  mask-image: linear-gradient(to bottom, transparent 0%, #000 42%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 42%);
  background: linear-gradient(180deg, rgba(234, 243, 252, 0) 0%, #d8e9f9 60%, #eff6fd 100%);
}

.constellation {
  position: relative;
  height: clamp(240px, 25vw, 340px);
  margin-bottom: clamp(40px, 5vw, 70px);
  pointer-events: none;
}
.constellation-lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.constellation-lines path {
  fill: none;
  /* the three topics carry a little more ink than their satellites,
     which is the only hierarchy the fan needs */
  stroke: rgba(39, 81, 126, 0.16);
  stroke-width: 1;
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
}
.constellation-lines path[data-rank="node"] { stroke: rgba(39, 81, 126, 0.3); }
.constellation.in .constellation-lines path {
  transition: stroke-dashoffset 1100ms var(--ease) var(--delay, 0s);
  stroke-dashoffset: 0;
}
/* once the draw-on has finished the dash is only in the way — from
   here the lines are re-pathed every frame */
.constellation.live .constellation-lines path {
  stroke-dasharray: none;
  transition: stroke 340ms var(--ease);
}
.constellation.live .constellation-lines path.on { stroke: rgba(39, 81, 126, 0.52); }
.constellation.live .constellation-lines path.dim { stroke: rgba(39, 81, 126, 0.07); }

.cn-hub,
.cn-node,
.cn-sat {
  position: absolute;
  left: var(--x);
  top: var(--y);
  /* --dx/--dy are driven by the field in home.js; the -50% keeps the
     authored --x/--y reading as the label's centre */
  transform: translate(-50%, -50%) translate3d(var(--dx, 0px), var(--dy, 0px), 0);
  white-space: nowrap;
  opacity: 0;
}
.constellation.in .cn-hub,
.constellation.in .cn-node,
.constellation.in .cn-sat {
  opacity: 1;
  transition: opacity 900ms var(--ease) var(--delay, 0s);
}
/* the delay above belongs to the draw-on only; hover states after it
   have to answer immediately */
.constellation.live .cn-node,
.constellation.live .cn-sat {
  will-change: transform;
  transition: opacity 300ms var(--ease), color 300ms var(--ease);
}
.constellation.live .cn-sat.dim,
.constellation.live .cn-node.dim { opacity: 0.32; }
.constellation.live .cn-sat.on { color: var(--display); }

/* the fixed point — the only label that never moves */
.cn-hub {
  display: block;
  text-align: center;
  font-family: var(--serif);
  font-variation-settings: "opsz" 60;
  font-size: clamp(2.5rem, 5.6vw, 4.7rem);
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--display);
}
.cn-hub em {
  display: block;
  margin-top: 0.3em;
  /* proportional to the word, but never smaller than the mono labels
     elsewhere on the page */
  font-size: max(0.245em, 0.63rem);
  font-style: italic;
  font-variation-settings: "opsz" 14;
  letter-spacing: 0;
  color: var(--ink-soft);
}
.cn-node {
  font-family: var(--serif);
  font-variation-settings: "opsz" 24;
  font-size: clamp(1.05rem, 1.55vw, 1.25rem);
  letter-spacing: -0.015em;
  color: var(--display);
}
/* satellites are the same serif, italic and a size down — the fan is
   one voice at three volumes, not three typefaces */
.cn-sat {
  font-family: var(--serif);
  font-variation-settings: "opsz" 14;
  font-style: italic;
  font-size: clamp(0.74rem, 0.98vw, 0.84rem);
  letter-spacing: 0;
  /* --ink-faint is the register the reference sets these in, but it
     lands near 2:1 on white; --ink-soft keeps them subordinate to the
     topics and still clears AA */
  color: var(--ink-soft);
}

.close-inner { position: relative; }
.close-title {
  margin-top: 14px;
  font-size: clamp(2.1rem, 5vw, 4rem);
  line-height: 1.06;
}
.close-sub {
  margin-top: 24px;
  max-width: 40ch;
  color: var(--ink);
}
.close-mail {
  display: inline-block;
  margin-top: clamp(32px, 4vw, 52px);
  font-family: var(--serif);
  font-variation-settings: "opsz" 48;
  font-size: clamp(1.5rem, 4.4vw, 3.4rem);
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: var(--display);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 6px;
  word-break: break-word;
  transition: border-color 260ms var(--ease);
}
.close-mail:hover { border-bottom-color: var(--display); }

.close-links {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  margin: clamp(28px, 3vw, 40px) 0 0 -12px;
}

/* ============================================================
   Responsive
   ============================================================ */

@media (max-width: 900px) {
  .hero { min-height: 92svh; }
  .hero-sub { max-width: 100%; font-size: 0.98rem; }

  /* Pinning a viewport-tall stage on a phone leaves no room to
     read, so the slideshow degrades to a plain stack. The index
     is navigation for the pinned viewer; stacked, it would just
     be a duplicate table of contents, so it goes and the slides
     carry their own names. */
  .work { height: auto; }
  .work-pin { position: static; height: auto; display: block; }
  .work-index { display: none; }
  .work-stage { height: auto; }

  .slide {
    position: static;
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
    will-change: auto;
    padding-bottom: clamp(72px, 9vw, 132px);
  }
  .slide:last-child { padding-bottom: 0; }
  .slide-figure { flex: none; }

  .slide-body { grid-template-columns: 1fr; }
  .slide-body > * { padding-right: 0; }
  .slide-body .label,
  .slide-body h3,
  .slide-text,
  .slide-links { grid-column: 1; grid-row: auto; }
  .slide-text { margin-top: 14px; }

  /* No left rail to hang the spine from once the row collapses to
     a single column, so home.js drops .is-traced here and the
     ledger goes back to being a plain ledger. */
  .ledger-row { grid-template-columns: 1fr; gap: 18px; }
  .ledger-when {
    flex-direction: row;
    align-items: center;
    gap: 14px;
    padding-right: 0;
  }
  /* the year range keeps its place beside the title while there is
     room for it; below 620px it takes its own line */
  .sec-head--split { gap: 18px; }

  /* the diagrams need height once the frame loses its width,
     or the labels have nowhere to go */
  .slide-figure { aspect-ratio: 4 / 3; }
  .slide-figure--record { aspect-ratio: auto; padding: 24px; }

  /* the dossier stacks label over value, exactly as the ledger
     row does — and the column grid is gone here, so the toolkit
     halves to two and provides its own row rhythm */
  .dossier-row { grid-template-columns: 1fr; gap: 8px; }
  .toolkit-grid { grid-template-columns: 1fr 1fr; row-gap: 30px; }

  /* the column grid is hidden here, so there is no hairline left to
     clear — everything goes flush with the dossier's labels */
  .toolkit-head { padding-left: 0; }
  .toolkit-group { padding: 0 18px 0 0; }

  /* and nothing left to centre against either: with the columns
     gone, centred rows would just read as ragged prose */
  .dossier-row dt { text-align: left; }
  .dossier-row dd { margin-inline: 0; text-align: left; }

  /* the status dot belongs on the first line once this wraps */
  .hero-now { align-items: flex-start; }
  .hero-now .dot { margin-top: 0.45em; }

  .constellation { height: 270px; }
  .cn-sat { font-size: 0.76rem; }
}

@media (max-width: 620px) {
  .hero-line { font-size: clamp(2.2rem, 10.5vw, 3.4rem); }
  .site-nav { gap: 0; }
  /* the nav is already tight here — the bracket opens less */
  .br { --br-pad: 8px; font-size: 0.86rem; }
  .br:hover,
  .br:focus-visible { --br-pad: 11px; --br-open: 0.24em; }
  .hero-actions { gap: 8px; margin-left: -8px; }
  .hero-actions .turn { margin-left: 8px; }
  .constellation { height: 220px; }
  .cn-hub { font-size: 1.9rem; }
  .cn-node { font-size: 0.95rem; }
  .cn-sat { font-size: 0.72rem; }
  /* one satellite per topic is all this width can carry —
     home.js drops the hidden ones out of the field entirely */
  .cn-sat[data-compact="off"] { display: none; }
  /* and the survivors move to their phone coordinates; home.js reads
     the same two custom properties, so type and physics agree */
  .cn-hub,
  .cn-node,
  .cn-sat {
    left: var(--cx, var(--x));
    top: var(--cy, var(--y));
  }

  .sec-head--split { display: block; }
  .ledger-scale { margin-top: 16px; padding-bottom: 0; }

  /* [ Claude Agent SDK ] has no room to sit beside a sibling here */
  .toolkit-grid { grid-template-columns: 1fr; row-gap: 26px; }
  .toolkit-head { display: block; }
  .toolkit-note { margin-top: 8px; }
}

/* ============================================================
   Reduced motion — the spine is still drawn, it simply arrives
   already complete, the way the silk field renders one static
   frame rather than disappearing.
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  .ledger.is-traced .ledger-mark,
  .ledger.is-traced .ledger-mark img,
  .ledger.is-traced .ledger-mark::before,
  .ledger.is-traced .ledger-mark::after,
  .ledger-scale [data-scale-now] { transition: none; }

  /* the fan arrives already drawn rather than scrubbing itself on */
  .constellation-lines path,
  .constellation.in .constellation-lines path {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    transition: none;
  }
  .constellation.in .cn-hub,
  .constellation.in .cn-node,
  .constellation.in .cn-sat { transition: none; }
}
