/* kahransingh.com — v2 home.
   One photograph of the day. Wide screens put the poem down the left and the
   leaning block of five ideas on the right, both inside the first screen.
   Narrow screens stack: a screen of photograph with the poem on it, then the
   ideas on a dark band below, then paper. Fraunces carries the voice (the poem
   and the name of each idea); Space Grotesk carries only apparatus, which now
   includes the quiet line of parts under each name. One hue per idea, and the
   hue is the only grouping — there are no labels on it.
   No build step; this file stands alone and does not extend /style.css. */

/* ---------------------------------------------------------------- tokens -- */

:root {
  --paper: #fafaf8;
  --band:  #15140f;           /* behind, and under the tools when they stack */

  --on-photo:      #f6f3ec;   /* poem, tile names */
  --on-photo-soft: #ded9cf;   /* date, day controls, caption */

  --pad-top:   clamp(1.75rem, 5vh, 3.25rem);
  --pad-x:     clamp(16px, 4vw, 48px);
  --mast-h:    3.8rem;        /* binds over the content, so the tools align to the poem exactly */
  --mast-gap:  clamp(2.5rem, 8vh, 5rem);
  --mast-block: calc(var(--mast-h) + var(--mast-gap));

  /* Five ideas: three across, then two, each row stepping right by --tan of
     its distance from the top — the same shear the sixteen had, over half the
     rows, so the block still leans. The short row is centred under the long
     one as well as sheared, which keeps the block a block rather than a
     staircase with a corner missing.
     --tools-w is the widest row (the top one), and the poem's max-width is
     computed off it, so the tile size decides how much room the poem keeps.
     At its 140px ceiling the poem is still at its full 30rem. */
  --tan:    .17;
  --tile:   clamp(112px, 11vw, 140px);
  --gap:    8px;
  --tile-a: .80;
  --tools-w: calc(3 * var(--tile) + 2 * var(--gap));

  /* the flow view: the space between its rows, and the side lanes a connector
     that skips a row bows out into. A tile is the SAME size in both views —
     the flight only translates, so a box that also changed size would pop. */
  --fgap:  7px;
  --frail: 14px;
  --fsurf: clamp(72px, 7vw, 92px);      /* the third column: what he actually gets */
  --me-ink: 38 46% 74%;                 /* warm, and nothing else on the page uses it */
  --ease:   cubic-bezier(.2, .8, .2, 1);
}

@media (prefers-color-scheme: dark) { :root { --paper: #171613; } }

/* Five pigments, dark enough that near-white names clear 4.5:1 on every one of
   the 26 photographs, and far enough apart to stay distinct as glass. */
.tile--catching    { --h: 192; --s: 40%; --l: 26%; }
.tile--remembering { --h:  96; --s: 32%; --l: 24%; }
.tile--knowing     { --h:  41; --s: 45%; --l: 26%; }
.tile--alive       { --h: 352; --s: 38%; --l: 30%; }
.tile--public      { --h: 230; --s: 38%; --l: 32%; }

/* ------------------------------------------------------------------ base -- */

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--on-photo);
  font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 17px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  padding-bottom: clamp(3.5rem, 12vh, 7rem);   /* the paper the page ends on */
}

:focus-visible { outline: 2px solid var(--on-photo); outline-offset: 3px; }

/* ----------------------------------------------------------------- stage -- */

.stage {
  position: relative;
  background: var(--band);
  /* The photograph ends exactly at the fold when the poem is short, so the fade
     to paper lives in this padding — below the fold, never washing the hero. */
  padding-bottom: 9vh;
}

/* The copy sits on this block — one viewport unless the poem runs on. */
.screen { position: relative; min-height: 100svh; }

/* The picture itself is always exactly one screen, so its crop never changes
   whatever the poem does. It sticks to the top of the window while a long poem
   scrolls over it, and its wrapper, which spans the block, is what stops it at
   the block's end. */
.stage-photo { position: absolute; inset: 0; z-index: 0; }
.stage-photo img {
  display: block;
  position: sticky;
  top: 0;
  width: 100%;
  height: 100svh;
  object-fit: cover;
  object-position: center;
}

/* Measured, not chosen. The brightest local patch across all 26 photographs is
   at relative luminance .956; #f6f3ec type needs the backdrop at or under .157
   to clear 4.5:1, so a black veil of at least .58 wherever bare type lands.
   The ramp holds .68 across the poem's column (7.3:1 worst case) and eases to
   .44 across the tools' column, where the tiles carry their own dark fill
   (6.1:1 for a name, 5.2:1 for the corner mark) and the picture can breathe. */
.stage-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(8,8,7,.30) 0%, rgba(8,8,7,0) 18%, rgba(8,8,7,0) 82%, rgba(8,8,7,.28) 100%),
    linear-gradient(90deg,  rgba(8,8,7,.82) 0%, rgba(8,8,7,.74) 12%, rgba(8,8,7,.68) 52%,
                            rgba(8,8,7,.50) 60%, rgba(8,8,7,.44) 94%, rgba(8,8,7,.40) 100%);
}

/* A soft landing where the stage meets the paper. */
.stage::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 8vh;
  z-index: 4;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(21,20,15,0), var(--paper));
}

.stage-copy {
  position: relative;
  z-index: 2;
  max-width: 1080px;
  margin: 0 auto;
  padding: var(--pad-top) var(--pad-x) 12vh;
}

/* -------------------------------------------------------------- masthead -- */

.masthead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: .5rem 2rem;
  min-height: var(--mast-h);
  margin: 0 0 var(--mast-gap);
}

/* Kahran, 2026-09-22: the italic serif is gone. A quiet lower-case mark. */
.wordmark {
  margin: 0;
  font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, sans-serif;
  font-weight: 400;
  font-size: .78rem;
  line-height: 1.5;
  letter-spacing: .14em;
  color: var(--on-photo-soft);
}
.wordmark a { color: inherit; text-decoration: none; }
.wordmark a:hover { color: var(--on-photo); }

.site-links { display: flex; gap: 1.5rem; font-size: .78rem; line-height: 1.5; }
.site-links a { color: var(--on-photo-soft); text-decoration: none; padding-block: .5rem; margin-top: -.5rem; }
.site-links a:hover { color: var(--on-photo); text-decoration: underline; text-underline-offset: .2em; }

.day { display: flex; flex-direction: column; align-items: flex-end; gap: .1rem; }
/* Kahran, 2026-09-22: the date, the weather line and the day arrows are off the page.
   The elements stay for the pick (weather.js still reads and drives it); [hidden] wins. */
.day[hidden] { display: none; }

.day-date {
  margin: 0;
  font-size: .72rem;
  line-height: 1.5;
  color: var(--on-photo-soft);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

.day-where {
  margin: 0;
  font-size: .72rem;
  line-height: 1.5;
  color: var(--on-photo-soft);
  text-align: right;
}
.day-where:empty { display: none; }

.day-walk { display: flex; gap: 1.1rem; }

.day-step {
  margin: 0; padding: 0; border: 0; background: none;
  font: inherit; font-size: .72rem; line-height: 1.5;
  color: var(--on-photo-soft);
  cursor: pointer;
}
.day-step:hover:not([disabled]) { color: var(--on-photo); }
.day-step[disabled] { opacity: .5; cursor: default; }

/* ------------------------------------------------------------- the poem -- */

/* Held clear of the tools' column, and never wider than the dark part of the
   scrim on a narrow desktop window. */
.opening { margin: 0; max-width: min(30rem, calc(100% - var(--tools-w) - 2.5rem)); }

/* At rest the column shows one line, centred. The poem is out of the flow, so
   the page is exactly one screen whatever the poem's length; revealing it puts
   it back in the flow and a long one is free to run past the fold. */
.poem-hold {
  position: relative;
  min-height: calc(100svh - var(--pad-top) - var(--mast-block) - 12vh);
}

/* The label is the poem's own title, set small, under a tiny caption. It and
   the poem are the same thing at two distances: --p (0 at rest, 1 arrived) is
   written every frame by v2/home.js, so a release half way turns round from
   exactly where it got to. The label grows and dissolves as the poem grows in
   through it — a transformation, not a swap. */
.poem-hold { --p: 0; }

.poem-label {
  position: absolute;
  top: 50%; left: 0;
  display: block;
  margin: 0; padding: 0; border: 0; background: none;
  text-align: left;
  cursor: pointer;
  transform-origin: 0 50%;
  transform: translateY(-50%) scale(calc(1 + var(--p) * .62));
  opacity: calc(1 - var(--p) * 2.1);
  will-change: transform, opacity;
}
.poem-label-cap {
  display: block;
  margin: 0 0 .18rem;
  font-family: 'Space Grotesk', -apple-system, sans-serif;
  font-size: .62rem;
  font-weight: 400;
  letter-spacing: .13em;
  line-height: 1.5;
  color: var(--on-photo-soft);
}
.poem-label-title {
  display: block;
  font-family: 'Fraunces', Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: .9rem;
  line-height: 1.32;
  letter-spacing: -.002em;
  color: var(--on-photo);
}
.poem-label:hover .poem-label-title,
.poem-label:focus-visible .poem-label-title { color: var(--on-photo); }

/* Comes forward from the back, driven by the same --p. */
.poem {
  position: absolute;
  top: 0; left: 0; right: 0;
  visibility: hidden;
  pointer-events: none;
  transform-origin: 0 42%;
  transform: scale(calc(.35 + var(--p) * .65));
  opacity: calc((var(--p) - .28) * 2.1);
  /* no animated filter: a blur recomposites the whole block every frame and
     Safari pays for it dearly. Scale and opacity alone are compositor work. */
  will-change: transform, opacity;
}
/* in the flow the moment it starts travelling, so a long poem lengthens the
   stage exactly as it did before */
.poem-hold.is-live .poem { position: static; visibility: visible; }
.poem-hold.is-on .poem { pointer-events: auto; }

.poem-title {
  margin: 0 0 1.4rem;
  font-family: 'Fraunces', Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: clamp(1.4rem, 2.2vw, 1.75rem);
  line-height: 1.26;
  letter-spacing: -.006em;
}
.poem-title a {
  color: var(--on-photo);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .24em;
  text-decoration-color: rgba(246, 243, 236, .38);
}
.poem-title a:hover { text-decoration-color: var(--on-photo); }

.poem-body {
  font-family: 'Fraunces', Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 300;
  font-size: 1.1rem;
  line-height: 1.78;
  color: var(--on-photo);
}

.stanza { margin: 0 0 1.45rem; }
.stanza:last-child { margin-bottom: 0; }

/* A wrapped line is not a new line: hang the runover so verse stays readable. */
.line { display: block; text-indent: -1.05em; padding-left: 1.05em; }

/* Bottom-left of the photograph, the way it sits on a print. It hangs off
   .screen — the block the picture covers — so on a one-screen page that is the
   foot of the window, and on a taller one it rides down with the picture. */
.plate-caption {
  position: absolute;
  left: 0; right: 0;
  bottom: 1.2rem;
  z-index: 3;
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 var(--pad-x);
  font-size: .7rem;
  line-height: 1.55;
  letter-spacing: .012em;
  color: var(--on-photo-soft);
  pointer-events: none;
}

.fallback, .poem-body .trouble {
  font-family: 'Space Grotesk', sans-serif;
  font-size: .85rem;
  color: var(--on-photo-soft);
}
.fallback a, .poem-body .trouble a { color: var(--on-photo); }

/* The only motion on the page, and it answers a click: turning to another day. */
@keyframes turn { from { opacity: 0; } to { opacity: 1; } }
.turned { animation: turn .32s ease both; }

/* ------------------------------------------------------------- the tools -- */

/* Lifted out of the flow into the right of the first screen. Not sticky: it is
   simply placed there, so a long poem runs past it rather than moving it. */
.tools-layer {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 3;
  pointer-events: none;
  /* the diagram's reflows stay its own business */
  contain: layout;
}
.tools-inner {
  max-width: 1080px;
  margin: 0 auto;
  padding: calc(var(--pad-top) + var(--mast-block)) var(--pad-x) 0;
  display: flex;
  justify-content: flex-end;
}
.wall { pointer-events: auto; width: max-content; }


/* --------------------------------------------------- right now I'm: ----- */

.doing { pointer-events: auto; }

.doing-line {
  margin: 0 0 .75rem;
  font-family: 'Space Grotesk', -apple-system, sans-serif;
  font-size: .72rem;
  font-weight: 400;
  letter-spacing: .12em;
  line-height: 1.5;
  color: var(--on-photo-soft);
}

.doing-list { margin: 0; padding: 0; list-style: none; }
.doing-item { position: relative; margin: 0 0 .6rem; }
.doing-item:last-child { margin-bottom: 0; }

/* the poem label's type and size, on the other side of the picture */
.doing-bullet {
  margin: 0; padding: 0; border: 0; background: none;
  font-family: 'Fraunces', Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: .9rem;
  line-height: 1.34;
  letter-spacing: -.002em;
  color: var(--on-photo);
  text-align: left;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .22em;
  text-decoration-color: transparent;
  transition: text-decoration-color 180ms var(--ease);
}
.doing-bullet:hover,
.doing-bullet:focus-visible { text-decoration-color: rgba(246, 243, 236, .5); }

/* empty until there is something to say; the slot exists so filling it later
   is content, not plumbing */
.doing-panel { margin: .45rem 0 0; font-size: .8rem; line-height: 1.5; color: var(--on-photo-soft); }
.doing-panel[hidden] { display: none; }

/* The diagram is out of sight until the first line is asked for. */
.tools-layer:not(.is-flow) .wall-block { display: none; }

.wall-block { width: max-content; }

.wall-row {
  display: flex;
  gap: var(--gap);
  margin: 0 0 var(--gap);
  padding: 0;
  list-style: none;
  /* the shear, plus half a tile per missing tile so the short row sits centred */
  margin-left: calc(var(--tan) * var(--r) * (var(--tile) + var(--gap))
                    + var(--r) * (var(--tile) + var(--gap)) / 2);
}
.wall-row:last-child { margin-bottom: 0; }

/* The five ideas that nothing has been built for yet still exist without
   JavaScript, in the plainest possible form. */
.wall-plain { margin: 0; padding: 0; list-style: none; max-width: 30rem; }
.wall-plain li { margin: 0 0 .5rem; font-size: .8rem; line-height: 1.5; color: var(--on-photo-soft); }
.wall-plain b { display: block; font-family: 'Fraunces', Georgia, serif; font-weight: 400; color: var(--on-photo); }
.wall-plain a { color: var(--on-photo); }

.tile {
  position: relative;
  flex: none;
  width: var(--tile);
  height: var(--tile);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: .34rem;
  padding: .7rem .72rem;
  border-radius: 12px;
  background: hsl(var(--h) var(--s) var(--l) / var(--tile-a));
  border: 1px solid hsl(var(--h) var(--s) calc(var(--l) + 24%) / .34);
}

/* Frosted where it is supported; a more solid tint where it is not. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tile { -webkit-backdrop-filter: blur(12px) saturate(115%); backdrop-filter: blur(12px) saturate(115%); }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tile { --tile-a: .90; }
}

/* A live backdrop blur over a full-bleed photograph is the most expensive
   thing on this page: every frame of any animation re-blurs the picture behind
   five tiles. So it is a rest-state luxury only — while the poem travels or the
   diagram flies, the tiles fall back to the solid tint they already use where
   backdrop-filter is unsupported, and it comes back ~120ms after everything
   settles. v2/home.js owns the switch; both animations share it. */
.tools-layer.is-busy .tile {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  --tile-a: .90;
}

.tile-name {
  font-family: 'Fraunces', Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: .92rem;
  line-height: 1.22;
  letter-spacing: -.004em;
  color: var(--on-photo);
  hyphens: none;
}

/* The parts under an idea. Apparatus, so Space Grotesk, and a step quieter than
   the name so the idea is what you read first and the sixteen are what you read
   second. The step is size and family, NOT contrast: measured the same way the
   scrim was, this is 4.7:1 on the worst of the five fills over the brightest
   patch of the 26 photographs, against 6.0:1 for a name. Dropping the alpha
   further reads better and fails — it was at .72 and 3.6:1 before this. */
.tile-parts {
  font-family: 'Space Grotesk', -apple-system, sans-serif;
  font-size: .6rem;
  line-height: 1.42;
  letter-spacing: .012em;
  color: hsl(var(--h) 18% 88% / .92);
  hyphens: none;
}
a.tile-name {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .2em;
  text-decoration-color: rgba(246, 243, 236, .45);
}
a.tile-name:hover { text-decoration-color: var(--on-photo); }

/* Three states, kept apart on purpose.
   1. Not launched, at rest: dim and desaturated. Nothing to click.
   2. Not launched, hovered or tapped: comes up part way, enough to read the note,
      never as strong as a launched tile.
   3. Launched (.tile--live): full colour and a firmer edge at rest; on hover it lifts,
      brightens and the edge glows, so it is unmistakably a link. */
.tile:not(.tile--live) { opacity: .5; filter: saturate(.55); }
.tile:not(.tile--live):hover,
.tile:not(.tile--live):focus-visible,
.tile:not(.tile--live).is-noted { opacity: .8; filter: saturate(.95); }

.tile--live {
  --lift: 1;
  --tile-a: .92;
  border-color: hsl(var(--h) var(--s) calc(var(--l) + 34%) / .7);
  box-shadow: 0 1px 0 hsl(var(--h) var(--s) calc(var(--l) + 30%) / .25) inset;
}
.tile--live:hover,
.tile--live:focus-within {
  --lift: 1.035;
  transform: translateY(-3px) scale(var(--lift));
  filter: saturate(1.35) brightness(1.18);
  border-color: hsl(var(--h) var(--s) calc(var(--l) + 44%) / .95);
  box-shadow: 0 10px 24px -8px hsl(var(--h) var(--s) 20% / .7),
              0 0 0 1px hsl(var(--h) var(--s) calc(var(--l) + 40%) / .45);
}
@media (prefers-reduced-motion: no-preference) {
  .tile { transition: opacity .16s ease, transform .2s ease, box-shadow .2s ease, border-color .2s ease; }
}

/* The one-line note. A square tile has no room for it, so it appears as a small
   glass card on hover, tap or keyboard focus — the tile never changes size, so
   the parallelogram never moves. home.js builds it from data-note and places
   it, flipping above/below and left/right to stay inside the tools column and
   the viewport. Card text measures 13.8:1 over the worst of the 26 photographs. */
.tile.is-noted { z-index: 6; }

.note {
  display: none;
  position: absolute;
  z-index: 7;
  width: max-content;
  max-width: 248px;
  padding: .6rem .72rem;
  border-radius: 10px;
  background: rgba(27, 26, 20, .90);
  border: 1px solid rgba(246, 243, 236, .16);
  box-shadow: 0 12px 28px rgba(0, 0, 0, .45);
  font-family: 'Fraunces', Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: .78rem;
  line-height: 1.45;
  letter-spacing: -.002em;
  color: var(--on-photo);
  text-align: left;
  white-space: normal;
  hyphens: none;
  will-change: transform, opacity;
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .note { -webkit-backdrop-filter: blur(16px) saturate(115%); backdrop-filter: blur(16px) saturate(115%); }
}
.tile.is-noted .note { display: block; }

/* v2/flow.js adds a second line to every card naming the surface that idea's
   arrow points at, so the words and the arrow always agree. It only means
   anything while the arrows are on screen, so it only shows there — and it has
   to be a line of its own, or it runs on from the sentence above it. */
.note-reach { display: none; }
.is-flow .note-reach {
  display: block;
  margin-top: .42rem;
  padding-top: .38rem;
  border-top: 1px solid rgba(246, 243, 236, .16);
  font-family: 'Space Grotesk', -apple-system, sans-serif;
  font-size: .66rem;
  line-height: 1.45;
  letter-spacing: .01em;
  color: hsl(var(--me-ink) / .86);
}

/* A note on a launched tile is a child of a box that is scaled while hovered,
   so without this it renders 3.5% over its own max-width with its text on
   fractional pixels. --lift is the one place that number lives; the card undoes
   exactly it, and home.js divides the card's offsets by the same value so it
   still lands where it was measured to. It has to be the separate `scale`
   property rather than a transform: noteIn fills forwards, and a filled
   animation's `transform: none` beats any transform declared here. */
.tile--live:hover > .note,
.tile--live:focus-within > .note {
  scale: calc(1 / var(--lift));
  transform-origin: top left;
}

@media (prefers-reduced-motion: no-preference) {
  .tile.is-noted .note { animation: noteIn .14s ease both; }
}
@keyframes noteIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }

/* --------------------------------------------------------- the flow view --

   The wall says what the five ideas are; this says how they run. The same five
   boxes are flown out of the lean into two columns — what the system knows,
   and what it does with it — with the watcher on a row beneath and the
   connectors drawn behind the glass. The block keeps the wall's exact width,
   so the sentence above it does not move when the layout changes underneath.
   v2/flow.js holds where each idea lands and what feeds what; this holds only
   the geometry they land in. */


/* Nothing of the flow view exists on the wall: it must not take a line of
   height there, and the wires must not find a containing block. */
.flow-head, .flow-wires { display: none; }

/* Wide enough for a fourth column, and no wider: the poem's max-width already
   decides where the right-hand half of the page begins, so the same expression
   gives the room left over. The poem itself never moves. */
.is-flow .wall { width: calc(100% - min(30rem, 100% - var(--tools-w) - 2.5rem) - 1.9rem); }

/* Two columns of ideas and the surfaces. Rows: the head, three rows of the
   spine, then the watcher on its own. The watcher's label sits in the empty
   second column beside it rather than on a row of its own, which is the
   difference between the drawing ending above the fold and below it. */
.is-flow .wall-block {
  position: relative;
  display: grid;
  width: 100%;
  padding: 0 var(--frail);
  grid-template-columns: repeat(2, var(--tile)) var(--fsurf);
  grid-template-rows: auto repeat(4, var(--tile));
  grid-auto-rows: auto;
  justify-content: space-between;
  row-gap: var(--fgap);
}
.is-flow .wall-row { display: contents; }

.is-flow .tile {
  grid-column: var(--fc);
  grid-row: calc(var(--fr) + 1);
  z-index: 1;
}
/* Every tile is a stacking context here, so the one showing a note has to be
   lifted above its neighbours or the card is painted under whichever tile comes
   later in the markup — which is how the watcher row's notes went under the
   weekly record. */
.is-flow .tile.is-noted { z-index: 6; }

/* A node in a drawing must not move. A launched tile lifting and scaling pulls
   its edge away from the connectors that end at it, so in here it brightens and
   its border glows and it stays exactly where its arrows point. */
.is-flow .tile--live:hover,
.is-flow .tile--live:focus-within { --lift: 1; transform: none; }

/* On the wall a dim tile means "nothing to click yet". Here every tile is a
   node in a drawing and has to read as one, so the unlaunched ones come up —
   still plainly quieter than a launched tile, which keeps its full colour, its
   firmer edge and its lift. */
.is-flow .tile:not(.tile--live) { opacity: .76; filter: saturate(.88); }
.is-flow .tile:not(.tile--live):hover,
.is-flow .tile:not(.tile--live):focus-visible,
.is-flow .tile:not(.tile--live).is-noted { opacity: .96; filter: saturate(1); }

.is-flying .tile { pointer-events: none; will-change: transform, opacity; }

/* Apparatus: small, spaced, lower case, and never louder than the thing it
   labels. Five boxes name themselves, so the stage headings and the colour
   legend the sixteen needed are both gone — the only labels left name the two
   things that are NOT ideas: the column of surfaces, and the watching. */
.is-flow .flow-head { display: block; grid-column: var(--fc); grid-row: 1; }
.is-flow .flow-head--watch {
  grid-column: 2;
  grid-row: 5;
  align-self: center;
  margin: 0;
}
.is-flow .flow-wires { display: block; }

.flow-head {
  margin: 0 0 .08rem;
  white-space: normal;
  font-family: 'Space Grotesk', -apple-system, sans-serif;
  font-size: .64rem;
  line-height: 1.35;
  letter-spacing: .085em;
  color: rgba(222, 217, 207, .74);
  opacity: 0;
}
/* This one names a person, not a stage, so it takes the warm ink the surfaces
   are drawn in, and it centres over the pills the way their own labels do. */
.flow-head--surf {
  color: hsl(var(--me-ink) / .8);
  letter-spacing: .05em;
  text-align: center;
}

.is-landed .flow-head { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .flow-head { transition: opacity .42s ease; }
}

/* Under the tiles, so a connector that has to cross a column passes behind the
   glass rather than over it. Each one takes the hue of the tile it arrives at. */
.flow-wires {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: visible;
  pointer-events: none;
}
.flow-wire { fill: none; stroke-linecap: round; }
.flow-wire--main  { stroke: hsl(var(--h) var(--s) calc(var(--l) + 42%) / .72); stroke-width: 1.7; }
.flow-wire--light { stroke: hsl(var(--h) var(--s) calc(var(--l) + 42%) / .3);  stroke-width: 1.1; }
.flow-tip { fill: hsl(var(--h) var(--s) calc(var(--l) + 46%) / .8); }
.flow-dot { fill: hsl(var(--h) var(--s) calc(var(--l) + 56%) / .95); }

/* Self-healing has an edge to every idea it watches. At full strength they
   would compete with the four that carry data, so they are dashed —
   attention, not data — kept at the threshold of visible, and they come up
   when you touch the tile they leave from. */
.flow-wire--watch {
  stroke: hsl(var(--h) var(--s) calc(var(--l) + 42%) / 1);
  stroke-width: .9;
  opacity: .13;
}
.flow-wires.is-watched .flow-wire--watch { opacity: .44; }
@media (prefers-reduced-motion: no-preference) {
  .flow-wire--watch { transition: opacity .3s ease; }
}

/* ------------------------------------------------------- the surfaces ----

   A third column, and a different kind of object from a tile: these are not
   parts of the system, they are the things Kahran actually gets. Warm ink,
   pill-shaped, no glass — so it is obvious at a glance where the machine stops
   and a person starts. Six of them, and the four strands running into the
   06:30 one are the truest thing on the page: almost everything the system has
   to say, it says once a day, in one message.

   The lines that reach them are warm too, and they draw in last, after the
   connectors, so the story assembles left to right in time. */

.is-flow .flow-surfs {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  grid-column: 3;
  grid-row: 2 / 6;
  gap: .5rem;
  z-index: 2;
}
.flow-surfs { display: none; }

.flow-surf {
  border: 1px solid hsl(var(--me-ink) / .34);
  border-radius: 999px;
  padding: .34rem .6rem .38rem;
  background: hsl(34 22% 10% / .58);
  text-align: center;
  opacity: 0;
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .flow-surf { -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); }
}
.is-landed .flow-surf { opacity: 1; }

.flow-surf b {
  display: block;
  font-family: 'Space Grotesk', -apple-system, sans-serif;
  font-weight: 500;
  font-size: .64rem;
  line-height: 1.3;
  letter-spacing: .005em;
  color: hsl(var(--me-ink) / .96);
}
.flow-surf i {
  display: block;
  margin-top: .1rem;
  font-family: 'Space Grotesk', -apple-system, sans-serif;
  font-style: normal;
  font-size: .54rem;
  line-height: 1.3;
  letter-spacing: .1em;
  color: hsl(var(--me-ink) / .5);
}

/* The one that matters most is allowed to say so. */
.flow-surf--busy { border-color: hsl(var(--me-ink) / .55); background: hsl(34 24% 11% / .7); }

.flow-surf.is-dim { opacity: .3; }
.flow-surf.is-lit { border-color: hsl(var(--me-ink) / .85); }

.flow-line {
  fill: none;
  stroke: hsl(var(--me-ink) / 1);
  stroke-width: 1.1;
  stroke-linecap: round;
  opacity: .34;
}
.flow-line-tip { fill: hsl(var(--me-ink) / .85); opacity: .34; }
.flow-line.is-lit, .flow-line-tip.is-lit { opacity: .95; }
.flow-line.is-dim, .flow-line-tip.is-dim { opacity: .1; }
.flow-pulse { fill: hsl(var(--me-ink) / .95); opacity: 0; }

/* While the channels are being read the connectors step back, so the two
   layers are never competing for the same glance. */
.flow-wires.is-surf .flow-wire--main,
.flow-wires.is-surf .flow-wire--light,
.flow-wires.is-surf .flow-tip { opacity: .16; }
.flow-wires.is-surf .flow-dot { opacity: 0 !important; }

@media (prefers-reduced-motion: no-preference) {
  .flow-surf { transition: opacity .42s ease, border-color .28s ease; }
  .flow-line, .flow-line-tip,
  .flow-wire--main, .flow-wire--light, .flow-tip { transition: opacity .28s ease; }
}

/* Room to unfold. At rest the column hangs level with the poem's first line;
   with the diagram open it lifts toward the masthead. This layer is out of the
   flow, so whatever falls below the fold cannot be scrolled to — the lift is
   what keeps the whole diagram reachable on a 800px-tall window. The poem does
   not move. */
@media (min-width: 901px) {
  /* padding is a layout property: transitioning it relaid the whole column
     every frame of the flight. It now snaps, under cover of the tiles flying. */
  .is-flow .tools-inner { padding-top: var(--pad-top); }
  /* and the three lines close up a little, to buy the diagram its last rows */
  .is-flow .doing-line { margin-bottom: .5rem; }
  .is-flow .doing-item { margin-bottom: .38rem; }
}


/* ------------------------------------------------- version switch (v2·v1) --
   Self-contained component. Drop this block and the matching markup into any
   page; it carries its own colours so it needs nothing else from this file. */

.vswitch {
  position: fixed;
  right: 14px;
  bottom: 14px;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: .45rem;
  padding: .34rem .68rem;
  border: 1px solid #e3e0d9;
  border-radius: 999px;
  background: rgba(250, 250, 248, .92);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  font-family: 'Space Grotesk', -apple-system, sans-serif;
  font-size: .7rem;
  line-height: 1;
  letter-spacing: .02em;
}
.vswitch a { color: #787266; text-decoration: none; }
.vswitch a:hover { color: #1a1a1a; }
.vswitch .vswitch-here { color: #1a1a1a; font-weight: 500; }
.vswitch > * + *::before { content: '\00b7'; margin-right: .45rem; color: #c9c4ba; }

@media (prefers-color-scheme: dark) {
  .vswitch { border-color: #2e2b25; background: rgba(23, 22, 19, .92); }
  .vswitch a { color: #8f897c; }
  .vswitch a:hover { color: #ece7db; }
  .vswitch .vswitch-here { color: #ece7db; }
  .vswitch > * + *::before { color: #413d35; }
}

/* ----------------------------------------------------------- responsive -- */

/* Narrow: stack. The photograph stays one screen with the poem on it, and the
   tools drop back into the flow on their own dark band underneath, so the crop
   still shows a composition instead of a vertical slice. */
@media (max-width: 900px) {
  .stage-scrim {
    background:
      radial-gradient(140% 85% at 50% 6%, rgba(8,8,7,0) 0%, rgba(8,8,7,.18) 100%),
      linear-gradient(180deg, rgba(8,8,7,.76) 0%, rgba(8,8,7,.66) 20%, rgba(8,8,7,.66) 80%, rgba(8,8,7,.78) 100%);
  }
  .masthead { align-items: flex-start; }
  .day { align-items: flex-start; }
  .opening { max-width: none; }
  .stage-copy { padding-bottom: clamp(2.5rem, 7vh, 4rem); }

  .tools-layer { position: static; pointer-events: auto; background: var(--band); }
  .tools-inner {
    display: block;
    padding: clamp(2.5rem, 7vh, 4rem) var(--pad-x) clamp(3rem, 9vh, 5rem);
  }
  .wall, .wall-block { width: auto; }
}

@media (max-width: 600px) {
  /* Three across still fits a 320px phone at this size; the lean flattens
     because a strong shear inside a narrow band costs a whole tile of width. */
  :root { --tan: .10; --tile: clamp(86px, 27vw, 116px); --gap: 5px; --tile-a: .82; }
  body { font-size: 16px; }
  .poem-body { font-size: 1.06rem; }
  .tile { border-radius: 11px; padding: .55rem .5rem; gap: .26rem; }
  .note { max-width: min(248px, calc(100vw - 32px)); font-size: .75rem; }
  .tile-name { font-size: .8rem; line-height: 1.2; }
  .tile-parts { font-size: .54rem; line-height: 1.38; }
  .doing-bullet { font-size: .88rem; }
  .vswitch { right: 12px; bottom: 12px; }
}

/* The narrow band. Two columns and the surfaces hold here too — the drawing
   says the same thing on a phone as it does wide, only narrower. */
@media (max-width: 900px) {
  /* Stacked: the poem is above the band, not beside it, so there is no
     left-hand half to leave room for. */
  .is-flow .wall { width: 100%; }
  .is-flow .wall-block { width: min(100%, 30rem); margin-inline: 0 auto; }
  :root { --fsurf: clamp(68px, 17vw, 92px); }
}

@media (max-width: 600px) {
  :root { --fgap: 6px; --frail: 10px; }
  .flow-head { font-size: .6rem; letter-spacing: .07em; }
}


@media (prefers-reduced-motion: reduce) {
  .turned { animation: none; }
  *, *::before, *::after { transition-duration: .001ms !important; }
  .tile--live:hover, .tile--live:focus-within { transform: none; }
  /* the poem still fades, it just does not travel */
  /* the poem still fades in, it just does not travel */
  .poem { transform: none !important; }
  .poem-label { transform: translateY(-50%) !important; }
  /* The layouts simply swap; the connectors are already drawn. */
  .is-flow .flow-head { opacity: 1; }
}
