/* ---------------------------------------------------------------- paper
   The Digital Inventory hero: his own paperwork, in depth, sorted in front
   of him.

   The section is built so that with no script it is still the finished,
   readable sort the page has always been. The canvas is an enhancement that
   lies on top of a real table, not a replacement for one. Ten versions of
   this page were rejected for being information design done more neatly; the
   table underneath is deliberately the plainest possible version of it, and
   the paper is what the reader actually sees when WebGL is there.
   ---------------------------------------------------------------------- */

.paper {
  position: relative;
  background: var(--ink);
  color: #fcfcfc;
  /* The header is sticky, so it takes its own height out of the flow and this
     section began below it: a band of the page's own ground across the top of
     a black hero, and the reader opening the page already looking at a strip
     of scroll they did not make. Same fix as .stage, and the same reason it is
     --chrome-h rather than --header-h: the rent strip is in the flow too, on
     the pages and the visits where it exists at all. */
  margin-top: calc(var(--chrome-h, var(--header-h)) * -1);
}

/* Height comes from the number of chapters, so adding one lengthens the
   scroll instead of squashing the pacing. Set by the script. */
.paper__track { position: relative; }
.paper__stage { position: sticky; top: 0; height: 100svh; overflow: hidden; }
.paper__canvas { display: block; width: 100%; height: 100%; }

/* The readout, bottom left, in the home page's own treatment.

   Anchored to the bottom, so its own height decides where its top edge lands,
   and its height changes every time the chapter changes: measured, the first
   swap moved this block 76px the instant the canvas drew. A floor under it
   and the content pushed to the bottom of that floor means a shorter chapter
   leaves the block where it was instead of dragging it. */
.paper__copy {
  position: absolute; left: max(1.25rem, 5vw); bottom: clamp(2rem, 9vh, 5.5rem);
  max-width: 28rem; z-index: 2; pointer-events: none;
  /* 24.5rem, because the served headline is the tallest thing this block ever
     holds and the floor has to clear it, not the chapters. Measured at seven
     widths from 961 to 2560: the served copy tops out at 392px and every
     chapter sits at 336 to 339, so a floor set to the chapters did nothing and
     the block still shrank by 53px the moment the canvas drew. */
  min-height: 24.5rem; display: flex; flex-direction: column; justify-content: flex-end;
}
.paper__kicker {
  font-family: PlexMono, ui-monospace, monospace; font-size: var(--step-label);
  letter-spacing: .12em; text-transform: uppercase; color: #6b6f7d; margin-bottom: 1rem;
}
.paper__head {
  font-family: Asgard, Impact, sans-serif; font-weight: 500;
  /* ⚠️ ИСКЛЮЧЕНИЕ ИЗ ПРАВИЛА «h1 = --hero», записано. Герой этого экрана -
     бумага на холсте, медиа 1.00 экрана; ось этой страницы медиа, а не
     кегль. На --step-5 десять слов заголовка в колонке 28rem шли ВОСЕМЬЮ
     строками (замерено 05.09 на 1900x800). Та же логика, что у /about/. */
  font-size: var(--step-4); line-height: .98; letter-spacing: -.01em;
  color: #fcfcfc; margin: 0 0 1rem;
}
.paper__line {
  font-family: PlexMono, ui-monospace, monospace; font-size: var(--step-0);
  line-height: 1.6; color: #8c8f9a; max-width: 26rem;
}
.paper__run {
  font-family: PlexMono, ui-monospace, monospace; font-weight: 600;
  font-size: var(--step-0); margin-top: 1rem; min-height: 1.2em;
}
.paper__kicker, .paper__head, .paper__line, .paper__run {
  transition: opacity .3s ease, transform .3s ease;
}
.paper__copy.is-swapping :where(.paper__kicker, .paper__head, .paper__line, .paper__run) {
  opacity: 0; transform: translateY(.5rem);
}
.paper__hint {
  margin-top: 1.5rem; font-family: PlexMono, ui-monospace, monospace;
  font-size: var(--step-0); color: #8c8f9a; transition: opacity .4s ease;
}

/* The picker. His two facts, which is the whole of what the page asks for.
   Markup only until a script is listening: an inert control is worse than no
   control, and a reader whose script failed used to be offered one. */
.paper__ask { display: none; }
.paper[data-live="true"] .paper__ask {
  display: block;
  position: absolute; right: max(1.25rem, 5vw); z-index: 3;
  /* Measured, not guessed at: --chrome-cover is how much of the top of the
     screen is actually covered right now, which is the header plus the rent
     strip when the reader has one. A typed 6.5rem put this under the strip. */
  top: calc(var(--chrome-cover, var(--header-h)) + var(--sp-m));
  width: min(20rem, 34vw); pointer-events: auto;
}
.paper__askkey {
  font-family: PlexMono, ui-monospace, monospace; font-size: var(--step-label);
  letter-spacing: .12em; text-transform: uppercase; color: #6b6f7d;
  display: block; margin-bottom: .6rem;
}
/* The phone's copy of the trade list. Built by script, shown only where the
   chips do not fit. The chevron is inline so the control needs no network. */
.paper__sel { display: none; }
.paper__trades { display: flex; flex-wrap: wrap; gap: .3rem; margin-bottom: 1.4rem; }
.paper__crew { display: flex; flex-wrap: wrap; gap: .3rem; }
.paper__pick { cursor: pointer; }
.paper__pick input { position: absolute; opacity: 0; pointer-events: none; }
.paper__pick span {
  display: block; padding: .3rem .55rem; border-radius: 3px;
  font-family: PlexMono, ui-monospace, monospace; font-size: var(--step--1);
  color: rgba(252, 252, 252, .55); border: 1px solid rgba(252, 252, 252, .14);
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}
.paper__pick input:focus-visible + span { outline: 2px solid var(--blue-lo); outline-offset: 2px; }
.paper__pick:hover span { color: #fcfcfc; border-color: rgba(252, 252, 252, .35); }
.paper__pick input:checked + span {
  color: var(--ink); background: #fcfcfc; border-color: #fcfcfc;
}

/* The fallback, and the accessible copy of the same reading. It goes when the
   script commits to a canvas, not when the canvas finally paints: waiting for
   the paint left the whole table sitting under an empty hero on a slow line.
   `flat` is the withdrawal of that promise, and brings the table back. */
.paper__plain { padding-block: clamp(3rem, 8vh, 6rem); }
.paper[data-committed="true"]:not([data-flat="true"]) .paper__plain {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.paper__tbl { width: 100%; border-collapse: collapse; font-family: PlexMono, ui-monospace, monospace; font-size: var(--step--1); }
.paper__tbl th, .paper__tbl td { text-align: left; padding: .55rem 0; border-bottom: 1px solid rgba(252, 252, 252, .1); }
.paper__tbl th { color: #6b6f7d; font-weight: 400; text-transform: uppercase; letter-spacing: .08em; font-size: var(--step-label); }
.paper__tbl td { color: rgba(252, 252, 252, .82); }
.paper__r { text-align: right; }

/* A wide screen with no WebGL. Nothing will ever be drawn, so the stage stops
   reserving a viewport for a canvas that stays blank, and the reading below is
   the page. The picker still works: it drives the table. */
.paper[data-flat="true"] .paper__stage {
  position: relative; height: auto; min-height: 0;
  padding-block: clamp(7rem, 16vh, 10rem) clamp(2rem, 6vh, 3.5rem);
}
.paper[data-flat="true"] .paper__canvas,
.paper[data-flat="true"] .paper__hint { display: none; }
.paper[data-flat="true"] .paper__copy { position: static; margin-left: max(1.25rem, 5vw); }

/* ------------------------------------------------------------------ sheets
   The audit on a phone. Not a fallback: the same bills and the same figures as
   the stack, rendered as documents in the page instead of documents in a
   scene, which is the one thing a phone can do better than WebGL.

   Hidden on a wide screen, where the canvas is doing this properly. */
.paper__sheets { display: none; }

.sheet {
  background: var(--white); color: var(--panel-dark);
  padding: 1.15rem 1.15rem 1.05rem; border-radius: 2px;
  /* A sheet a degree off reads as something put down by hand. Any more and it
     reads as a sticker. */
  transform: rotate(var(--lean, 0deg));
  box-shadow: 0 14px 30px -20px rgb(0 0 0 / .85);
}
.sheet__top {
  display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  font-family: PlexMono, ui-monospace, monospace; font-size: var(--step-label);
  letter-spacing: .12em; text-transform: uppercase; color: var(--grey);
  padding-bottom: .7rem; border-bottom: 1px solid var(--line);
}
.sheet__verdict { color: var(--v); text-align: right; }
.sheet__what {
  font-family: Asgard, Impact, sans-serif; font-weight: 500;
  font-size: var(--step-2); line-height: 1.02; letter-spacing: -.01em;
  margin: .95rem 0 .5rem;
}
.sheet--sum .sheet__what { line-height: .98; }
.sheet__note {
  font-family: London, sans-serif; font-size: var(--step--1); line-height: 1.5;
  color: var(--text-muted); margin: 0 0 1.15rem;
}
.sheet__foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; }
.sheet__amt {
  font-family: PlexMono, ui-monospace, monospace; font-weight: 600;
  font-size: var(--step-2); line-height: 1; font-variant-numeric: tabular-nums;
}
.sheet__amt span { font-size: .6em; font-weight: 400; color: var(--grey); margin-left: .12em; }
/* A rubber stamp, not the seal the canvas prints: at this size the seal's arc
   text is unreadable and the ring closes up into a blob. */
.sheet__stamp {
  font-family: PlexMono, ui-monospace, monospace; font-weight: 600;
  font-size: var(--step-label); letter-spacing: .16em; color: var(--v);
  border: 1.5px solid var(--v); border-radius: 2px;
  padding: .32rem .5rem .28rem; transform: rotate(-4deg); white-space: nowrap;
}
.sheet__cut {
  font-family: London, sans-serif; font-size: var(--step--1); color: var(--text-muted);
  text-align: right; max-width: 9rem; line-height: 1.35;
}
.sheet--sum { background: var(--off); }

@media (max-width: 60rem) {
  /* The stack cannot survive a phone: one sheet at readable size fills the
     screen and there is nowhere left to put the picker. So the stage stops
     being a camera and becomes a column, and the sheets print into the page.
     It used to hide the whole stage here, which took the only h1 with it. */
  .paper__track { height: auto !important; }
  /* The first screen is spent on the headline, the two questions and the
     running figure, and then the top of the first bill has to show. A reader
     who cannot see any paper has no reason to believe there is any: measured,
     the sheets started one pixel below the fold, so every gap above them is
     set to leave about a hundred pixels of the first one in view. */
  .paper__stage {
    position: static; height: auto; overflow: visible;
    display: flex; flex-direction: column;
    padding: calc(var(--chrome-cover, var(--header-h)) + clamp(.75rem, 3.5vh, 1.75rem))
             max(1.25rem, 5vw) clamp(2.5rem, 8vh, 4rem);
  }
  .paper__canvas { display: none; }

  .paper__copy {
    position: static; inset: auto; max-width: 34rem; order: 1;
    margin-bottom: clamp(1.25rem, 3.5vh, 1.75rem);
    /* No floor here: nothing swaps this block on a phone, and reserving three
       lines of desktop headline would just be a hole above the picker. */
    min-height: 0; display: block;
  }
  .paper__head { font-size: var(--step-3); margin-bottom: .85rem; }
  /* The break is set for a wide measure. On a phone it lands on top of the
     natural wrap and leaves "one." alone on a fourth line. */
  .paper__head br { display: none; }
  .paper__line { font-family: London, sans-serif; font-size: var(--step-0); line-height: 1.45; max-width: none; }
  .paper__kicker { margin-bottom: .7rem; }
  .paper__run { margin-top: .85rem; }
  .paper__hint { display: none; }

  /* Written at the same weight as the rule that reveals it. As `.paper__ask`
     alone this lost to `.paper[data-live] .paper__ask` and the picker stayed
     absolutely positioned, a 128px column lying across the copy and every
     sheet under it. A media query adds no specificity. */
  .paper[data-live="true"] .paper__ask {
    position: static; width: auto; order: 2;
    margin-bottom: clamp(1.25rem, 3.5vh, 1.75rem);
  }
  .paper__askkey { margin-bottom: .5rem; }
  .paper__trades { display: none; }
  .paper__sel {
    display: block; width: 100%; min-height: 48px; margin-bottom: 1.4rem;
    padding: .7rem 2.4rem .7rem .85rem;
    font-family: PlexMono, ui-monospace, monospace; font-size: var(--step-0);
    color: #fcfcfc; background: rgb(252 252 252 / .06);
    border: 1px solid rgb(252 252 252 / .2); border-radius: 3px;
    appearance: none;
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238c8f9a' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right .8rem center; background-size: 1.1rem;
  }
  .paper__sel:focus-visible { outline: 2px solid var(--blue-lo); outline-offset: 2px; }
  /* The option list is drawn by the platform, which does not inherit the dark
     field: without this the text comes out white on white on some Androids. */
  .paper__sel option { color: #0b0d14; background: #fcfcfc; }

  /* Forty four pixels, because these are now fingers rather than a cursor. */
  .paper__pick span {
    padding: .72rem .85rem; font-size: var(--step--1); min-height: 44px;
    display: flex; align-items: center; justify-content: center;
  }
  .paper__crew { gap: .4rem; }
  .paper__crew .paper__pick span { min-width: 3rem; }

  .paper__run { font-size: var(--step-0); }
  .paper__sheets { display: grid; gap: 1.15rem; order: 3; }

  /* The table has done its job once the sheets are on screen: it is the same
     reading twice, so it goes back to being the copy for a screen reader. */
  .paper[data-live="true"] .paper__plain {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
}

/* ----------------------------------------------------------------- tsheet
   Page 11, as the document it is.

   It was a progress bar with three labelled dots and three rows under it, and
   that is the exact failure this page's canon names: information design done
   more neatly, rejected twelve times before the paper won. The bills half
   works because it is his paperwork rather than a chart of it, so the hours
   half gets the paper every owner has already signed. One cell is one hour,
   because eighty nine cells is a week somebody lived and three numbers in a
   bar is a summary of one.
   ---------------------------------------------------------------------- */
.hours__page { color: var(--text-muted); margin-bottom: var(--sp-s); }

/* Two hues, not three. #6d86ff is the dark-surface blue and measures about
   3:1 on this white field, under the 4.5 a label needs, and main.css says in
   as many words that there is one blue for light surfaces and one for dark and
   no third. So rule and model share the light blue: both are work a build
   takes over, and they are told apart by tint in the grid and by a solid rule
   against a dashed one on the stamp. A tint is a graphic and owes 3:1 against
   what it touches, which it clears against both the solid blue and the ink. */
[data-v="rule"] { --v-fill: var(--blue); --v-text: var(--blue); }
[data-v="model"] {
  --v-fill: #8093fb;
  --v-fill: color-mix(in srgb, var(--blue) 45%, #fff);
  --v-text: var(--blue);
}
[data-v="human"] { --v-fill: var(--panel-dark); --v-text: var(--text); }

/* One object, and it has to fit a screen. As a section head above the sheet
   the title was three lines of display type and three hundred pixels, and the
   block came to about twelve hundred: taller than the screen it is meant to be
   one of. A form carries its own title, so it carries this one. */
.tsheet {
  /* Every value a token. It was radius 2px, a hand written border and a hand
     written shadow: three numbers picked by eye that matched nothing else on
     the site, while the home page's cards sit at clamp(14px, 1.4vw, 20px) on
     --lift-2. Same reason the heading had drifted to half size. */
  background: var(--card-bg);
  border: 1px solid var(--card-line);
  border-radius: clamp(14px, 1.4vw, 20px);
  box-shadow: var(--lift-2);
  padding: var(--sp-l);
  color: var(--panel-dark);
}
.tsheet__head {
  display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  font-size: var(--step-label); letter-spacing: .12em; text-transform: uppercase;
  color: var(--grey);
  padding-bottom: .7rem; border-bottom: 1px solid var(--line);
}
.tsheet__total { display: flex; align-items: baseline; gap: .7rem; flex-wrap: wrap; margin: var(--sp-m) 0; }
.tsheet__total b {
  font-size: var(--step-3); font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.tsheet__total span { color: var(--text-muted); font-size: var(--step--1); }

/* The week, an hour to a cell. */
.tsheet__grid {
  display: grid; gap: 3px;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: .95rem;
  margin-bottom: var(--sp-m);
}
@media (min-width: 40rem) { .tsheet__grid { grid-template-columns: repeat(30, 1fr); } }
.tsheet__grid i { display: block; border-radius: 1px; background: var(--v-fill); }

.tsheet__key { list-style: none; padding: 0; margin: 0; display: grid; }
.tsheet__key li {
  display: grid; grid-template-columns: 10rem 4.5rem 1fr; gap: .3rem var(--sp-m);
  align-items: baseline; padding: .5rem 0; border-top: 1px solid var(--line);
}
/* The mark the bills carry, in the margin where a stamp belongs. */
.tsheet__stamp {
  justify-self: start; color: var(--v-text);
  border: 1.5px solid var(--v-text); border-radius: 2px;
  padding: .2rem .4rem .16rem; font-weight: 600;
  font-size: var(--step-label); letter-spacing: .1em; text-transform: uppercase;
  transform: rotate(-1.2deg);
}
.tsheet__key li[data-v="model"] .tsheet__stamp { border-style: dashed; }
.tsheet__fig { font-weight: 600; font-variant-numeric: tabular-nums; }
.tsheet__eg { color: var(--text-muted); font-size: var(--step--1); }
.tsheet__foot {
  margin: var(--sp-m) 0 0; padding-top: var(--sp-s); border-top: 1px solid var(--line);
  color: var(--text-muted); font-size: var(--step--1); max-width: 48rem;
}
.tsheet__foot::first-line { color: var(--text); font-weight: 500; }

/* The other side of the same form. */
.tsheet__turn { margin-top: var(--sp-m); padding-top: var(--sp-s); border-top: 2px solid var(--panel-dark); }
.tsheet__label { color: var(--grey); text-transform: uppercase; letter-spacing: .12em; font-size: var(--step-label); }
.quests {
  list-style: none; padding: 0; margin: .7rem 0 0;
  display: grid; gap: .5rem var(--sp-l);
}
@media (min-width: 46rem) { .quests { grid-template-columns: 1fr 1fr; } }
.quest { display: grid; grid-template-columns: 2rem 1fr; gap: .5rem; align-items: baseline; }
.quest__n { color: var(--grey); }
.quest__q { font-size: var(--step--1); line-height: 1.35; margin: 0; }
.quest__q br { display: none; }

/* -------------------------------------------------------------------- run
   The method, with the numeral doing the work.

   Three attempts here were rejected: four cards, five day panels, and then
   prose with nothing to look at. A box with a label and a title is a layout;
   a figure set at a scale nothing else on the page uses is an image. The
   number is light enough to be a ground rather than a shout, and the text
   sits on it.
   ---------------------------------------------------------------------- */
.run { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--sp-xl); }
@media (min-width: 62rem) { .run { grid-template-columns: repeat(3, 1fr); gap: var(--sp-l); } }

.run__step { position: relative; padding-top: clamp(2.6rem, 5vw, 4.6rem); }
/* Sits behind its own text, not above it, so the column reads as one thing. */
.run__n {
  position: absolute; top: 0; left: -.06em; z-index: -1;
  font-family: Asgard, Impact, sans-serif; font-weight: 500;
  font-size: clamp(5rem, 11vw, 11rem); line-height: .78;
  letter-spacing: -.04em; color: var(--line);
  pointer-events: none; user-select: none;
}
.run__t { font-size: var(--step-2); line-height: 1.06; margin: 0; }
.run__d {
  margin: var(--sp-s) 0 0; color: var(--text-muted);
  font-size: var(--step-0); line-height: 1.55; max-width: 34ch;
}

/* The reveal, in the site's own idiom: an observer puts .is-in on the block
   and the CSS does the rest, so there is no per page timeline to keep alive.

   The numeral is printed rather than faded: it rises out of a mask, which is
   what a figure at this scale wants, and the two lines come up after it. The
   columns are staggered so the eye is walked left to right instead of being
   shown three things at once. */
.run__n {
  /* Insets are negative on three sides on purpose. line-height is .78, so the
     glyph is taller than its own box, and a mask that ends at inset(0) clips
     the feet off every figure. The open state has to be wider than the box. */
  clip-path: inset(100% -20% -34% -20%);
  transform: translateY(.14em);
  transition: clip-path .85s cubic-bezier(.2, .8, .2, 1),
              transform .85s cubic-bezier(.2, .8, .2, 1);
}
.run__t, .run__d {
  opacity: 0; transform: translateY(.7rem);
  transition: opacity .55s ease, transform .55s cubic-bezier(.2, .8, .2, 1);
}
[data-reveal].is-in .run__n { clip-path: inset(-34% -20% -34% -20%); transform: none; }
[data-reveal].is-in :where(.run__t, .run__d) { opacity: 1; transform: none; }

.run__step:nth-child(2) :where(.run__n, .run__t, .run__d) { transition-delay: .13s; }
.run__step:nth-child(3) :where(.run__n, .run__t, .run__d) { transition-delay: .26s; }
.run__step .run__t { transition-delay: .2s; }
.run__step .run__d { transition-delay: .28s; }
.run__step:nth-child(2) .run__t { transition-delay: .33s; }
.run__step:nth-child(2) .run__d { transition-delay: .41s; }
.run__step:nth-child(3) .run__t { transition-delay: .46s; }
.run__step:nth-child(3) .run__d { transition-delay: .54s; }

@media (prefers-reduced-motion: reduce) {
  .run__n { clip-path: none; }
  .run__n, .run__t, .run__d { transition: none; opacity: 1; transform: none; }
}

/* The form on a phone: the three keyed rows stack, and the hour cells get a
   coarser grid so a cell stays a countable square rather than a sliver. */
@media (max-width: 60rem) {
  .tsheet__key li { grid-template-columns: 1fr auto; }
  .tsheet__stamp { grid-row: 1; }
  .tsheet__fig { grid-row: 1; justify-self: end; }
  .tsheet__eg { grid-column: 1 / -1; }
}

/* A tablet is not a phone. Measured at 768: one sheet stretched to 677px with
   a fifty character note sitting in it, and a select the same width. Two up,
   and the controls stop growing with the screen. */
@media (min-width: 40rem) and (max-width: 60rem) {
  .paper__sheets { grid-template-columns: 1fr 1fr; }
  .paper[data-live="true"] .paper__ask { max-width: 28rem; }
  .sheet--sum { grid-column: 1 / -1; }
  .sheet--sum .sheet__what { font-size: var(--step-2); }
}

/* ------------------------------------------------------------------ hands
   Content Line: one row per job, and whose desk it lands on.

   The device is the emptying column, so the only thing the styling has to do
   is make "yours" and "ours" tell apart at a glance and let the reader watch
   the balance change when he picks a tier. Ours is quiet, yours is marked:
   the point of the page is what is still his.
   ---------------------------------------------------------------------- */
.hands__pick { display: none; }
.hands[data-live="true"] .hands__pick { display: block; margin-bottom: var(--sp-m); }
.hands__opts { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .5rem; }
/* Reuses the picker chips from the inventory hero: same control, same page
   furniture, no second vocabulary for the same job. */
.hands .paper__pick span { color: var(--text-muted); border-color: var(--line); }
.hands .paper__pick:hover span { color: var(--text); border-color: var(--text-muted); }
.hands .paper__pick input:checked + span {
  color: var(--white); background: var(--panel-dark); border-color: var(--panel-dark);
}

.hands__count {
  font-family: Asgard, Impact, sans-serif; font-weight: 500;
  font-size: var(--step-2); line-height: 1.1; margin: 0 0 var(--sp-m);
}
.hands__count b { color: var(--red); font-variant-numeric: tabular-nums; }
.hands[data-empty="true"] .hands__count b { color: var(--blue); }

.hands__list {
  list-style: none; padding: 0; margin: 0;
  display: grid; gap: 1px; background: var(--line);
  border-radius: clamp(14px, 1.4vw, 20px); overflow: hidden;
  box-shadow: var(--lift-2);
}
.hand {
  display: grid; grid-template-columns: 6.5rem 1fr; gap: var(--sp-m);
  align-items: baseline; padding: .7rem var(--sp-m);
  background: var(--card-bg);
  transition: background .2s ease, color .2s ease;
}
.hand__who { color: var(--text-muted); }
.hand__job { color: var(--text-muted); }
/* Still his. Red is what this site uses for the thing that costs him, and an
   hour of his own week is exactly that. */
.hand[data-who="you"] { background: linear-gradient(var(--panel-bg), var(--panel-bg)), var(--card-bg); }
.hand[data-who="you"] .hand__who { color: var(--red); }
.hand[data-who="you"] .hand__job { color: var(--text); font-weight: 500; }

.hands__vol { margin: var(--sp-m) 0 0; color: var(--text-muted); font-size: var(--step--1); }
.hands__vol .label { display: block; margin-bottom: .3rem; }

/* --------------------------------------------------------------------- kit */
.kit { display: grid; gap: var(--sp-l); }
@media (min-width: 52rem) { .kit { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 76rem) { .kit { grid-template-columns: repeat(4, 1fr); } }
.kit__group { border-top: 2px solid var(--panel-dark); padding-top: var(--sp-s); }
.kit__g { font-size: var(--step-1); margin: 0 0 var(--sp-s); }
.kit__list { list-style: none; padding: 0; margin: 0; display: grid; gap: .5rem; }
.kit__list li {
  color: var(--text-muted); font-size: var(--step--1); line-height: 1.5;
  padding-left: 1rem; position: relative;
}
.kit__list li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: .35rem; height: 1px; background: var(--text-muted);
}
.kit__out { margin: var(--sp-l) 0 0; max-width: 44rem; }
.kit__guards {
  list-style: none; padding: 0; margin: var(--sp-m) 0 0;
  display: grid; gap: .5rem; max-width: 52rem;
  color: var(--text-muted); font-size: var(--step--1); line-height: 1.55;
}
.kit__guards li { padding-left: 1.1rem; position: relative; }
.kit__guards li::before {
  content: "!"; position: absolute; left: 0; top: 0;
  font-family: PlexMono, ui-monospace, monospace; font-weight: 600; color: var(--red);
}

/* A placeholder that cannot be mistaken for a price. */
.todo {
  border: 1px dashed var(--red); border-radius: var(--radius);
  padding: var(--sp-m); max-width: 52rem; color: var(--text-muted);
}
.todo b { color: var(--red); }

@media (max-width: 60rem) {
  .hand { grid-template-columns: 1fr; gap: .2rem; }
}

/* --- The play ------------------------------------------------------------
 *
 * One pinned screen with two states. The question holds it alone, then hands
 * over to the circuit: twelve threads bent into one small object and fanned
 * out the other side, each leaving with the money it actually made.
 *
 * The handover is a dissolve inside the pin rather than a screen scrolled
 * past, which is how the rest of the site moves between an opening and what it
 * opens onto.
 *
 * One clock, --tp, counting states rather than gaps. State 0 is the question,
 * states 1..n are the channels, so no row gets half a turn and the question is
 * not a special case in the arithmetic.
 */

/* Pulled up under the whole top bar. Without it the sticky pin starts 68px
   down, its 100svh box ends 68px below the fold, and the last caption is cut
   off the bottom of every screen. The pull-up also keeps the rent strip out,
   which is the other half of what [data-dark-top] is for. */
.play-sect {
  height: calc(var(--n, 13) * 54svh);
  margin-top: calc(var(--chrome-h, var(--header-h)) * -1);
}

.play-pin > .wrap:last-of-type { grid-row: 2; }

/* The room the picture stands in. Without it the screen is flat black and
   the threads look like they were drawn on paper. */
/* ⚠️ На телефоне свет СНЯТ, и правило стоит ЗДЕСЬ, а не в `pages.css`:
   `/money-trail/` не несёт `pages: true`, и та таблица к ней не
   подключается вовсе. Замерено: правило в `pages.css` не действовало,
   документ оставался 468 при экране 390.

   Сам дефект: `inset: -20%` считается от ближайшего ПОЗИЦИОНИРОВАННОГО
   предка, а на узком экране пин становится обычным, и обрезка на нём света
   не режет. Рельсы там нет, светить нечему. */
@media (width < 48rem) {
  /* Свет не пропадает, он переезжает на фон пина: фоновый слой не может выйти
     за коробку элемента, чей бы предок ни оказался системой координат.
     ⚠️ Сама подмена стоит НЕ ЗДЕСЬ, а в блоке `width < 48rem` внизу файла:
     ниже по файлу у `.play-pin` идёт шорткат `background: var(--ink)`, и он
     обнулил бы `background-image`, поставленный выше. */
  .play-pin::before { display: none; }
}
.play-pin::before {
  content: "";
  position: absolute;
  inset: -20%;
  pointer-events: none;
  background:
    radial-gradient(38rem 38rem at 50% 46%, rgb(74 108 255 / 0.13), transparent 68%),
    radial-gradient(40rem 40rem at 6% 62%, rgb(252 252 252 / 0.035), transparent 72%);
}
.play-pin > * { position: relative; }

.play-pin {
  position: sticky;
  top: 0;
  height: 100svh;
  display: grid;
  /* Three rows, and every child told which one it is in. Spanning the opener
     across the whole grid and letting the rest auto-place produced five rows,
     two of them zero, and centring that lot pushed the last caption fifteen
     pixels off the bottom of the screen. */
  grid-template-rows: minmax(0, 1fr) auto;
  align-content: stretch;
  overflow: hidden;
  background: var(--ink);
  color: var(--on-dark);
  --heading-color: var(--white);
  --lede-color: var(--on-dark-muted);
  --tp: min(calc(var(--p, 0) * var(--n, 13)), calc(var(--n, 13) - 0.02));
  padding-top: calc(var(--chrome-cover, var(--header-h)) + var(--sp-m));
  padding-bottom: var(--sp-m);
}

/* --- State zero: the question -------------------------------------------- */

.opener {
  grid-row: 1;
  grid-column: 1 / -1;
  align-self: center;
  justify-self: center;
  text-align: center;
  display: grid;
  gap: var(--sp-m);
  /* Wide enough for the question to fall into two or three lines at this
     size. At 46rem the balancer had no room and split it into five. The wrap
     sized padding went with it: on a centred box it pushed the whole thing
     off centre. */
  max-width: 64rem;
  padding-inline: var(--sp-m);
  /* Gone by the time the first channel is on. Scaled and lifted a little as it
     goes, so it reads as handing the screen over rather than blinking out. */
  /* Out before the circuit is in, rather than both at a third: a crossfade
     that overlaps reads as two screens printed on top of each other. */
  opacity: clamp(0, calc(1.1 - var(--tp) * 2.4), 1);
  transform: translateY(calc(clamp(0, calc(var(--tp) * 2.4), 1) * -2.5rem));
  transition: opacity 0.35s ease, transform 0.35s ease;
  pointer-events: none;
}
/* Balanced rather than left to break where it lands: at the owner's width the
   question was falling into five ragged lines. */
.opener h1 { line-height: 1.02; text-wrap: balance; }
.opener .lede { max-width: 38rem; margin-inline: auto; }

/* --- The circuit --------------------------------------------------------- */

.thru {
  grid-row: 1;
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-m);
  align-content: center;
  min-height: 0;
  /* Arrives as the question leaves. */
  /* In after the question, out before the verdict. Held faintly alive under
     the question so the screen has depth before the first handover. */
  opacity: max(
    /* The ghost belongs to the question only. Left as a flat floor it also sat
       under the verdict, where there is nothing for it to hint at. */
    calc(0.06 * clamp(0, calc(1.3 - var(--tp)), 1)),
    min(
      clamp(0, calc(var(--tp) * 2.4 - 1.05), 1),
      clamp(0, calc((var(--chn, 12) + 1 - var(--tp)) * 2.4), 1)
    )
  );
  transition: opacity 0.4s ease;
}

.thru__in,
.thru__out {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-rows: repeat(var(--n, 12), minmax(0, 1fr));
  min-height: 0;
}

/* State 0 is the question, so channel i is lit at tp = i + 1. */
.src,
.dec,
.read,
.out {
  --local: calc(var(--tp) - var(--i) - 1);
  --on: min(clamp(0, calc(var(--local) * 200 + 1), 1), clamp(0, calc((1 - var(--local)) * 200), 1));
}

.src,
.dec {
  /* A soft ramp rather than a hard step, so the row brightens as the reader
     arrives instead of snapping and then easing. */
  /* Spread across most of the segment. At 4 the ramp was a quarter of a
     channel, about a hundred pixels, and a fast frame covers sixty of them:
     the row jumped 0.42 of its brightness in one frame. */
  --near: min(clamp(0, calc(var(--local) * 1.6 + 1), 1), clamp(0, calc((1 - var(--local)) * 1.6 + 1), 1));
  opacity: calc(0.26 + var(--near) * 0.74);
  display: grid;
  align-content: center;
  gap: 0.05rem;
  min-height: 0;
}

.src { padding-right: 1rem; text-align: right; }
.src__name { color: var(--white); line-height: 1.15; font-size: var(--step-0); }
.src__spend { color: var(--on-dark-muted); font-size: var(--step-label); letter-spacing: 0.1em; text-transform: uppercase; }
.src__spend i { font-style: normal; opacity: 0.65; }
.src__spend b { color: var(--red); font-weight: 500; }

.thru__field { position: relative; min-height: 0; }
.thru__web { display: block; width: 100%; height: 100%; overflow: visible; }

/* In: hairline and identical while it waits, because undifferentiated spend
   is all the owner has. The live one lights red: on this site red is money
   leaving, so the reader watches red go into the object and green come out of
   it, and on the one channel that eats the budget red comes out as well. */
.raw {
  fill: none;
  stroke: currentColor;
  vector-effect: non-scaling-stroke;
  --local: calc(var(--tp) - var(--i) - 1);
  --on: min(clamp(0, calc(var(--local) * 200 + 1), 1), clamp(0, calc((1 - var(--local)) * 200), 1));
  /* Driven by the same clock as everything else rather than by a class the
     script would have to set: CSS cannot switch an attribute, and a second
     source of truth for "which channel is live" is how the two halves of a
     screen drift apart. */
  --near: min(clamp(0, calc(var(--local) * 1.6 + 1), 1), clamp(0, calc((1 - var(--local)) * 1.6 + 1), 1));
  color: color-mix(in srgb, var(--red) calc(var(--near) * 100%), rgb(252 252 252 / 0.14));
  stroke-width: 0.9px;
  /* No transition on anything the scroll drives. A transition restarts on
     every frame the target moves, which at speed reads as tearing. The value
     tracks --p directly instead, so it is exactly as smooth as the scroll. */
}


/* Out: thin, bent, and weighted by the money the channel actually made. */
.out {
  fill: none;
  stroke: var(--green);
  color: var(--green);
  stroke-width: calc(var(--w) * 0.62px);
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  /* Fixed radius. Driven by a custom property it was recomputed on every
     frame on all twelve paths, which is what made the scroll feel torn. */
  filter: drop-shadow(0 0 3px currentColor);
  opacity: clamp(0, calc(var(--local) * 1.4 + 1), 0.92);
}
.out[data-eats="yes"] { stroke: var(--red); color: var(--red); }

/* Small, because twelve threads bend into it and that is what makes it read as
   the thing they pass through. The first version was a slab the height of the
   screen, which is a wall, not an instrument. */
.gate {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  justify-items: center;
  gap: 0.15rem;
  padding: 0.7rem 1.1rem;
  border-radius: clamp(14px, 1.4vw, 20px);
  background: linear-gradient(180deg, rgb(74 108 255 / 0.2), rgb(74 108 255 / 0.06));
  border: 1px solid rgb(74 108 255 / 0.45);
  box-shadow: 0 0 3rem rgb(74 108 255 / 0.28), inset 0 1px 0 rgb(252 252 252 / 0.16);
  white-space: nowrap;
}
.gate__name { font-size: var(--step-0); color: var(--white); }
.gate__sub {
  font-size: var(--step-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--blue-lo);
}

/* --- What it decides on -------------------------------------------------- */

.dec { padding-left: 1rem; }
.dec__sum {
  font-family: PlexMono, ui-monospace, monospace;
  font-size: var(--step-1);
  line-height: 1;
  color: var(--green);
  font-variant-numeric: tabular-nums;
}
.dec[data-eats="yes"] .dec__sum { color: var(--red); }
.dec__per { color: var(--on-dark-muted); font-size: var(--step-label); letter-spacing: 0.1em; text-transform: uppercase; }

/* Room for the longest of the twelve lines, measured rather than guessed:
   the tallest wraps to 60px and the reserved 3.2rem cut the last one by 15. */
/* The closing state. Placed in the same cell as the circuit so the two
   dissolve into each other rather than one pushing the other down the page. */
.closer {
  grid-row: 1;
  grid-column: 1 / -1;
  align-self: center;
  justify-self: center;
  display: grid;
  gap: var(--sp-m);
  max-width: 64rem;
  padding-inline: var(--sp-m);
  text-align: center;
  opacity: clamp(0, calc((var(--tp) - var(--chn, 12) - 1) * 2.4 - 0.05), 1);
  transform: translateY(calc(clamp(0, calc(1 - (var(--tp) - var(--chn, 12) - 1) * 2.4), 1) * 1.6rem));
  transition: opacity 0.35s ease, transform 0.35s ease;
  pointer-events: none;
}
/* One figure, at the size of the point. Everything that was a paragraph is
   now either a word or gone. */
.closer__key { color: var(--red); letter-spacing: 0.2em; text-transform: uppercase; font-size: var(--step-label); }
.closer__name { font-size: var(--step-2); line-height: 1; color: var(--white); }
/* Итог пролёта идёт `--step-6`, ступенью выше героя: на эту цифру
   приземляется весь экран, и на `--step-5` она читалась вровень с
   заголовками, мимо которых читатель уже проехал. */
.closer__fig {
  font-family: PlexMono, ui-monospace, monospace;
  font-weight: 600;
  font-size: var(--step-6);
  letter-spacing: -0.03em;
  line-height: 0.9;
  color: var(--red);
  font-variant-numeric: tabular-nums;
}
.closer__say { font-size: var(--step-1); color: var(--on-dark); }
.closer__note { font-size: var(--step--1); color: var(--on-dark-muted); opacity: 0.7; }
html:not(.js) .closer { opacity: 1; transform: none; }

.thru__read {
  position: relative;
  min-height: 4.4rem;
  margin-top: var(--sp-s);
  /* Goes out with the circuit it belongs to. */
  opacity: clamp(0, calc((var(--chn, 12) + 1 - var(--tp)) * 2.4), 1);
  transition: opacity 0.35s ease;
}
.read {
  position: absolute;
  inset: 0;
  color: var(--on-dark-muted);
  max-width: 78ch;
  opacity: var(--on);
  transition: opacity 0.28s ease;
}
.read b { color: var(--white); font-weight: 500; }

html:not(.js) .opener { opacity: 1; transform: none; }
html:not(.js) .thru { opacity: 1; }
html:not(.js) .read { position: relative; opacity: 1; }
html:not(.js) .thru__read { display: grid; gap: var(--sp-s); min-height: 0; }
html:not(.js) .src, html:not(.js) .dec, html:not(.js) .out { opacity: 1; }

@media (min-width: 62rem) {
  .thru { grid-template-columns: minmax(0, 17rem) minmax(0, 1fr) minmax(0, 11rem); gap: 0; }
}

/* ⚠️ ЗДЕСЬ СТОЯЛО РАСПИНИВАНИЕ, И ЭТО СНИМАЛО ВЕСЬ СМЫСЛ ЭКРАНА.
   Секция становилась `height: auto`, пин обычным, а `.src, .dec, .out` и
   `.read` получали `opacity: 1`: двенадцать каналов, двенадцать итогов и
   двенадцать подписей загорались РАЗОМ и ложились друг на друга кучей.
   Замерено на 390x844 кадром.

   PO: «все первые экраны иммерсивный опыт при скролле, а не просто скролл».

   Секция несёт `data-rail-phone`, пин остаётся липким, и каналы снова
   зажигаются по одному. Что уходит на телефоне - только паутина `.thru__field`
   между колонками: она рисуется в 100 единиц ширины на три колонки, из
   которых боковые просят 17rem и 11rem, а их на 390 точках нет. Списки
   остаются оба, слева расход, справа что вернулось. */
@media (max-width: 60rem) {
  .play-pin {
    padding-top: calc(var(--chrome-cover, var(--header-h)) + min(var(--sp-s), 1.6svh));
    padding-bottom: min(var(--sp-s), 1.6svh);
  }
  .opener { padding-inline: 0; }
  .opener h1 { font-size: min(var(--step-4), 7.4vw); line-height: 1.04; }
  .opener .lede { font-size: min(var(--step-0), 3.6vw); }
  /* ⚠️ ДВЕ КОЛОНКИ ЗДЕСЬ НАМЕРЕННО, И ЭТО ИСКЛЮЧЕНИЕ ИЗ ПРАВИЛА.
     Общее правило PO: что на десктопе слева направо, на телефоне сверху вниз.
     Я применил его и тут - распустил обе колонки в один поток, каждый канал
     двумя строками подряд. PO вернул прежний кадр со словами «вот так было
     супер». Причина видна на самих кадрах: здесь левое и правое это не шаги
     рассказа, а ОДНА СТРОКА СЧЁТА - сколько отдал и сколько вернулось. Разбей
     её на две строки, и сравнение, ради которого страница написана,
     перестаёт читаться одним взглядом.
     Паутина между колонками уходит: она рисуется на три колонки, из которых
     боковые просят 17rem и 11rem, а их на 390 точках нет. */
  .thru { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: min(var(--sp-m), 2.4svh); }
  .thru__field { display: none; }
  .src { text-align: left; padding-right: 0; }
  .src__name { font-size: min(var(--step--1), 3.4vw); line-height: 1.1; }
  .src__spend { font-size: 0.52rem; }
  .dec__sum { font-size: min(var(--step-0), 4vw); }
  .dec__per { font-size: 0.52rem; }
  .thru__read { font-size: min(var(--step-0), 3.6vw); }
}

@media (prefers-reduced-motion: reduce) {
  .src, .dec, .out, .read, .opener, .thru { transition: none; }
}

.trail__note { margin-top: var(--sp-m); max-width: 46rem; color: var(--on-dark-muted); }
.sect--rule .trail__note { color: var(--text-muted); }



/* --- /money-trail/, below the fold ---------------------------------------
 *
 * The first pass at this was four outlined cards of prose and a wall of text.
 * The owner's note on it was the right one: the home page has no blocks like
 * that, and people do not read a page, they read what it means. So each block
 * here leads with the object and the sentence is the caption.
 */

/* The identifier that does the joining, at the size of the point. A reader who
   sees ?c=spring-ac and (619) 555-0142 has understood the mechanism before
   reading a word about it. */

.tokens {
  list-style: none;
  margin: var(--sp-l) 0 0;
  padding: 0;
  display: grid;
  gap: 1px;
  background: var(--line);
  border-block: 1px solid var(--line);
}

.token {
  display: grid;
  gap: 0.2rem;
  padding: var(--sp-m) 0;
  background: var(--white);
  align-content: center;
}

.token__id {
  font-size: var(--step-2);
  line-height: 1;
  color: var(--blue);
  letter-spacing: -0.01em;
}
.token__name { color: var(--text); }
.token__says { color: var(--text-muted); }
.token__where {
  color: var(--text-muted);
  font-size: var(--step-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* The report, in the interface object's own language.
 *
 * screen.css belongs to the home page and this page shows its own artefact, so
 * this is not that component. It is deliberately the same language, and that
 * language is specific: a white pane on the dark ground, a near-black rail
 * down the side, one accent, two or three figures set large enough to be the
 * focus, and the row that matters filled as a block of colour rather than
 * marked with a dot.
 *
 * What stood here before was a dark card holding a name, a bar and a number.
 * The page had also claimed there was no dashboard to open, which was simply
 * untrue, and then listed in four lines what the dashboard holds. The object
 * holds them, so it shows them.
 */
.dash {
  --d-paper: #fff;
  --d-ink: #08080b;
  --d-mid: #43434a;
  --d-mute: #8b8b93;
  --d-line: rgb(0 0 0 / 0.09);
  --d-dark: var(--panel-dark);
  --d-accent: #0130f9;

  margin-top: var(--sp-l);
  border-radius: clamp(14px, 1.4vw, 20px);
  background: var(--d-paper);
  color: var(--d-ink);
  overflow: hidden;

  /* The same lift as the panel on the home page, including the hairline of
     light along the top edge: these are the only two white panels on the site
     that ever sit on a dark ground, and they should sit in the same room. */
  box-shadow: var(--edge-light), var(--lift-3);
}

/* --- Chrome --------------------------------------------------------------- */

.dash__bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: clamp(0.85rem, 1.4vw, 1.15rem) clamp(1.1rem, 2vw, 1.7rem);
  border-bottom: 1px solid var(--d-line);
}
.dash__mark { font-family: Asgard, Impact, sans-serif; font-size: 0.84rem; }
.dash__where { font-size: 0.82rem; color: var(--d-mute); flex: 1; }
.dash__stamp {
  font-family: PlexMono, ui-monospace, monospace;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--d-accent);
}

/* --- Rail ----------------------------------------------------------------- */

.dash__body { display: grid; grid-template-columns: minmax(10.5rem, 14rem) 1fr; }

.dash__rail {
  display: grid;
  align-content: start;
  gap: 0.08rem;
  padding: clamp(0.8rem, 1.4vw, 1.1rem) 0.6rem;
  background: var(--d-dark);
}

/* Two lines a tab rather than one. Nine modules on the home page can be nine
   nouns; four views of one month cannot, because Channels and Customers are
   the same twelve rows and only the question is different. */
/* The colour is the tab's own. Inheriting it from the panel put near-black
   type on the near-black rail, and the eleven names read as eleven smudges. */
.dash__tab {
  appearance: none;
  border: 0;
  border-radius: 8px;
  background: none;
  font: inherit;
  font-size: 0.86rem;
  color: rgb(255 255 255 / 0.55);
  text-align: left;
  padding: 0.38rem 0.7rem;
  cursor: pointer;
  transition: color 0.16s ease, background 0.16s ease;
}

.dash__tab:hover { background: rgb(255 255 255 / 0.08); color: #fff; }

.dash__tab[aria-selected="true"] {
  background: var(--d-accent);
  color: #fff;
  font-weight: 500;
}

/* --- Panes ---------------------------------------------------------------- */

.dash__panes {
  padding: clamp(0.9rem, 1.6vw, 1.5rem);
  display: grid;
  align-content: start;
}
.dash__pane { display: grid; gap: clamp(0.8rem, 1.5vw, 1.2rem); align-content: start; }
.dash__pane[hidden] { display: none; }
.dash__pane:not([hidden]) { animation: dpane 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) both; }
@keyframes dpane {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

.dash__head { display: grid; gap: 0.7rem; }
.dash__stats { display: flex; flex-wrap: wrap; gap: clamp(1.4rem, 4vw, 3.4rem); }

.dstat { display: grid; gap: 0.1rem; }
.dstat b {
  font-family: Asgard, Impact, sans-serif;
  font-weight: 500;
  font-size: clamp(1.7rem, 0.9rem + 2vw, 2.7rem);
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: var(--d-ink);
}
.dstat span {
  font-family: PlexMono, ui-monospace, monospace;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--d-mute);
}

.dash__note { font-size: 0.88rem; color: var(--d-mid); max-width: 52ch; }

/* A figure alone answers "how much". Beside last month it answers "and is that
   good", which is the question the owner actually opens the screen with. */
.dlt {
  font-style: normal;
  font-family: PlexMono, ui-monospace, monospace;
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  color: var(--d-mute);
  margin-top: 0.15rem;
}
.dlt--good { color: var(--d-accent); }

.dash__table td.down { color: var(--red); font-variant-numeric: tabular-nums; }

/* --- The table ------------------------------------------------------------ */

.dash__table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 0.88rem; }

.dash__table th {
  text-align: left;
  font-family: PlexMono, ui-monospace, monospace;
  font-weight: 400;
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--d-mute);
  padding: 0 0.7rem 0.65rem;
}
.dash__table th.num,
.dash__table td.num { text-align: right; }

.dash__table td {
  padding: clamp(0.42rem, 0.75vw, 0.62rem) 0.7rem;
  border-top: 1px solid var(--d-line);
  color: var(--d-mid);
  white-space: nowrap;
}
.dash__table td.lead { color: var(--d-ink); font-family: PlexMono, ui-monospace, monospace; }
.dash__table td.body { white-space: normal; color: var(--d-ink); }
.dash__table td.num { font-variant-numeric: tabular-nums; }
.dash__table td.who { color: var(--d-ink); }

/* The last column on every tab is the one the report exists to produce, so it
   is the only column that is ever coloured. */
.dash__table td.sum { color: var(--d-accent); font-variant-numeric: tabular-nums; }

.dash__table tbody tr { transition: background 0.16s ease; }
.dash__table tbody tr:hover td { background: rgb(0 0 0 / 0.03); }

/* The one row the whole page exists to surface, filled as a block. The home
   page fills the job happening right now in blue for the same reason: a dot
   or a red figure is a speck, and a speck is not a finding. */
.dash__table tr.is-bad td {
  background: var(--red);
  color: rgb(255 255 255 / 0.82);
  border-top-color: transparent;
}
.dash__table tr.is-bad td.lead,
.dash__table tr.is-bad td.body,
.dash__table tr.is-bad td.sum,
.dash__table tr.is-bad td.who { color: #fff; }
/* The coverage rule inside a filled row: blue on red is a bar nobody can
   read, and this is the row most likely to be looked at closely. */
.dash__table tr.is-bad td.cell-cov i { background: rgb(255 255 255 / 0.3); }
.dash__table tr.is-bad td.cell-cov i::after { background: #fff; }

.dash__table tr.is-bad td:first-child { border-radius: 8px 0 0 8px; }
.dash__table tr.is-bad td:last-child { border-radius: 0 8px 8px 0; }
.dash__table tbody tr.is-bad:hover td { background: var(--red); }

.dash__table tfoot td {
  border-top: 1px solid var(--d-ink);
  color: var(--d-ink);
}

.dash__foot {
  padding: clamp(0.85rem, 1.5vw, 1.2rem) clamp(1.1rem, 2vw, 1.7rem);
  border-top: 1px solid var(--d-line);
  font-size: 0.78rem;
  color: var(--d-mute);
}

/* --- The rail, grouped ---------------------------------------------------- */

/* Eleven screens in four groups. The home page's nine sit in one list because
   nine modules of one product are one kind of thing; these are not, and a flat
   list of eleven would have hidden that Data quality is not a report. */
.dash__group {
  font-family: PlexMono, ui-monospace, monospace;
  font-size: 0.58rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgb(255 255 255 / 0.32);
  padding: 0.75rem 0.7rem 0.22rem;
}
.dash__group:first-child { padding-top: 0.2rem; }

/* --- The switches over every report --------------------------------------- */

/* Five controls that belong to all eleven screens at once. Two of them change
   what a figure means rather than which figures are shown, so those two are
   lit and carry the sentence that says what they did. */
.dash__work { display: grid; grid-template-rows: auto minmax(0, 1fr); min-width: 0; }

.dash__switch {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  padding: clamp(0.7rem, 1.2vw, 0.95rem) clamp(0.9rem, 1.6vw, 1.5rem);
  border-bottom: 1px solid var(--d-line);
  background: rgb(0 0 0 / 0.015);
}

.sw {
  display: grid;
  gap: 0.05rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--d-line);
  border-radius: 8px;
  font-size: 0.8rem;
  color: var(--d-ink);
  background: var(--d-paper);
}
.sw i {
  font-style: normal;
  font-family: PlexMono, ui-monospace, monospace;
  font-size: 0.55rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--d-mute);
}
.sw em { font-style: normal; font-size: 0.7rem; color: var(--d-mute); }

.sw--on { border-color: rgb(1 48 249 / 0.35); background: rgb(1 48 249 / 0.05); }
.sw--on em { color: var(--d-accent); }

/* --- Overview ------------------------------------------------------------- */

/* Six second-rank figures. Big enough to read across the room, small enough
   that the three above them stay the focus. */
.figs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
  gap: 0.9rem 1.4rem;
  border-top: 1px solid var(--d-line);
  padding-top: 0.9rem;
}
.figs li { display: grid; gap: 0.05rem; }
.figs b {
  font-family: PlexMono, ui-monospace, monospace;
  font-weight: 400;
  font-size: 1.05rem;
  color: var(--d-ink);
  font-variant-numeric: tabular-nums;
}
.figs span { font-size: 0.75rem; color: var(--d-mute); }

/* The rule the product is built on, drawn rather than asserted: the bar is the
   share of the month that is actually joined, and the pale remainder is the
   part no figure on the screen is allowed to pretend it has. */
.cover {
  display: grid;
  gap: 0.7rem;
  padding: clamp(0.9rem, 1.6vw, 1.2rem);
  border: 1px solid rgb(1 48 249 / 0.2);
  border-radius: 12px;
  background: rgb(1 48 249 / 0.04);
}
.cover__bar { height: 10px; border-radius: 999px; background: rgb(1 48 249 / 0.13); overflow: hidden; }
.cover__bar i { display: block; height: 100%; width: calc(var(--k) * 1%); background: var(--d-accent); }
.cover__say { font-size: 0.85rem; color: var(--d-mid); max-width: 62ch; }
.cover__say b { color: var(--d-ink); font-weight: 500; }

/* --- Cells that carry their own coverage ---------------------------------- */

.dash__table td.cell-cov { position: relative; color: var(--d-mid); }
.dash__table td.cell-cov i {
  position: absolute;
  left: 0.7rem;
  right: 0.7rem;
  bottom: 0.35rem;
  height: 2px;
  border-radius: 999px;
  background: rgb(0 0 0 / 0.09);
}
.dash__table td.cell-cov i::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(var(--k) * 1%);
  border-radius: 999px;
  background: var(--d-accent);
}

.dash__table td.none { color: rgb(0 0 0 / 0.28); }

/* A subtotal inside the body: the paid half, so the two free rows under it
   cannot be read as part of the money that was spent. */
.dash__table tr.is-rule td {
  border-top: 1px solid var(--d-ink);
  border-bottom: 1px solid var(--d-line);
  color: var(--d-ink);
  background: rgb(0 0 0 / 0.02);
}

/* Campaign, then ad, then keyword. One indent, because two would need a third
   and the screen is for switching one thing off. */
.dash__table tr.lvl-1 td.lead { padding-left: 1.9rem; color: var(--d-mid); font-size: 0.83rem; }
.dash__table tr.lvl-1 td { color: var(--d-mute); }

/* --- Bars ----------------------------------------------------------------- */

/* A funnel tab whose funnel is a column of numbers is the weaker half of the
   picture the section above already draws, so each step gets its length. The
   money is the point of the row and sits at the end of it. */
.dbars { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.dbars--tight { gap: 0.35rem; }

.dbar {
  display: grid;
  grid-template-columns: minmax(7rem, 12rem) minmax(0, 1fr) 3rem minmax(6rem, 11rem) 5rem;
  align-items: center;
  gap: 0.8rem;
}
.dbar__n { color: var(--d-ink); font-size: 0.88rem; }
.dbar__t { display: block; height: 12px; border-radius: 3px; background: rgb(0 0 0 / 0.05); overflow: hidden; }
.dbar__t i {
  display: block;
  height: 100%;
  width: calc(var(--k) * 1%);
  border-radius: 3px;
  background: var(--d-accent);
}
.dbar__v {
  font-family: PlexMono, ui-monospace, monospace;
  font-weight: 400;
  font-size: 1rem;
  color: var(--d-ink);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.dbar__d { font-size: 0.78rem; color: var(--d-mute); }
.dbar__m {
  font-family: PlexMono, ui-monospace, monospace;
  font-size: 0.85rem;
  color: var(--d-accent);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The step that costs the most, which is the only reason to open this tab. */
.dbar--worst .dbar__t i { background: var(--red); }
.dbar--worst .dbar__n { color: var(--red); font-weight: 500; }
.dbar--worst .dbar__m { color: var(--red); }

/* --- Paths ---------------------------------------------------------------- */

/* A chain reads left to right or it is not a chain. */
.dash__table td.chain { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem; white-space: normal; }
.chain em {
  font-style: normal;
  font-family: London, system-ui, sans-serif;
  font-size: 0.78rem;
  padding: 0.15rem 0.45rem;
  border-radius: 6px;
  background: rgb(0 0 0 / 0.05);
  color: var(--d-ink);
}
.chain em + em::before {
  content: "";
  display: inline-block;
  width: 0.5rem;
  height: 1px;
  margin-right: 0.45rem;
  vertical-align: middle;
  background: rgb(0 0 0 / 0.25);
}

/* --- Cohorts -------------------------------------------------------------- */

/* The one place a heat map earns its keep: the eye reads the diagonal filling
   in, which is the whole argument that a channel is judged too early. */
.dash__table--grid td.heat {
  background: rgb(1 48 249 / calc(var(--k) * 0.006));
  color: var(--d-ink);
  border-radius: 4px;
}
.dash__table--grid th,
.dash__table--grid td { padding-inline: 0.5rem; }

/* --- Data quality --------------------------------------------------------- */

/* The half that can be fixed, marked. The rest is somebody exercising a
   choice, and colouring that as a fault would be the lie this screen exists
   to prevent. */
.dash__table td.can-fix { color: var(--d-accent); }

/* --- Timeline ------------------------------------------------------------- */

.tline { list-style: none; margin: 0; padding: 0 0 0 1.1rem; display: grid; gap: 0.55rem; border-left: 1px solid var(--d-line); }

.tl {
  display: grid;
  grid-template-columns: 4.2rem minmax(0, 1fr) minmax(0, 1fr);
  gap: 0.8rem;
  align-items: baseline;
  position: relative;
}
.tl::before {
  content: "";
  position: absolute;
  left: -1.42rem;
  top: 0.42rem;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.2);
}
.tl__when { font-family: PlexMono, ui-monospace, monospace; font-size: 0.72rem; color: var(--d-mute); }
.tl__what { color: var(--d-ink); font-size: 0.88rem; }
.tl__how { color: var(--d-mute); font-size: 0.8rem; }

/* Money is the only kind of event on this feed that is an outcome. */
.tl--money::before { background: var(--d-accent); }
.tl--money .tl__what { color: var(--d-accent); }
.tl--job::before { background: rgb(0 0 0 / 0.45); }

/* --- Connections and settings --------------------------------------------- */

.conns, .setts { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--d-line); border-block: 1px solid var(--d-line); }

.conn {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 8rem minmax(0, 1.4fr);
  gap: 0.8rem;
  align-items: baseline;
  padding: 0.6rem 0.1rem;
  background: var(--d-paper);
}
.conn b { font-weight: 500; color: var(--d-ink); font-size: 0.88rem; }
.conn__st {
  font-family: PlexMono, ui-monospace, monospace;
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--d-accent);
}
.conn__note { font-size: 0.8rem; color: var(--d-mute); }

.conn--off b { color: var(--d-mute); }
.conn--off .conn__st { color: var(--red); }

.sett {
  display: grid;
  gap: 0.35rem;
  padding: 0.8rem 0.1rem;
  background: var(--d-paper);
}
.sett b { font-weight: 500; color: var(--d-ink); font-size: 0.88rem; }
.sett > span { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.sett em {
  font-style: normal;
  font-size: 0.78rem;
  padding: 0.15rem 0.5rem;
  border-radius: 6px;
  border: 1px solid var(--d-line);
  color: var(--d-mid);
}
.sett em.on { border-color: transparent; background: var(--d-accent); color: #fff; }
.sett__note { display: block; font-size: 0.8rem; color: var(--d-mute); max-width: 66ch; }

/* --- Narrow --------------------------------------------------------------- */

@media (max-width: 61.99rem) {
  .dash__body { grid-template-columns: 1fr; }
  .dash__rail { display: flex; overflow-x: auto; gap: 0.25rem; padding: 0.6rem; align-items: center; }
  .dash__tab { white-space: nowrap; }
  .dash__table { font-size: 0.8rem; }
  .dash__table:not(.dash__table--grid) th:nth-child(3),
  .dash__table:not(.dash__table--grid) td:nth-child(3),
  .dash__table:not(.dash__table--grid) th:nth-child(5),
  .dash__table:not(.dash__table--grid) td:nth-child(5),
  .dash__table:not(.dash__table--grid) th:nth-child(7),
  .dash__table:not(.dash__table--grid) td:nth-child(7) { display: none; }
  .dash__group { padding: 0.4rem 0.5rem; align-self: center; }
  .dbar {
    grid-template-columns: minmax(0, 1fr) 2.6rem 4.5rem;
    grid-template-areas: "n v m" "t t t" "d d d";
    gap: 0.25rem 0.6rem;
  }
  .dbar__n { grid-area: n; }
  .dbar__v { grid-area: v; }
  .dbar__m { grid-area: m; }
  .dbar__t { grid-area: t; }
  .dbar__d { grid-area: d; }
  .tl { grid-template-columns: 4rem minmax(0, 1fr); }
  .tl__how { grid-column: 2; }
  .conn { grid-template-columns: minmax(0, 1fr) 6rem; }
  .conn__note { grid-column: 1 / -1; }
}

@media (prefers-reduced-motion: reduce) {
  .dash__pane:not([hidden]) { animation: none; }
}

/* --- The funnel, as a ribbon through a column grid ------------------------
 *
 * Built to the reference: horizontal, symmetric about the middle, narrowing
 * left to right, standing in a grid of columns with a rule between the steps.
 * The share is large and inside the band, the count and the fall small at the
 * foot of the column. Layers of the same silhouette behind the bright one,
 * which is the whole source of the depth and the reason three flat versions of
 * this section were rejected.
 *
 * The panel is dark inside a light section, which gives it the frame the
 * reference has and keeps the page's light, dark, light, dark rhythm.
 */

.rib {
  /* Every colour here is a palette token. The version before this ended its
     band in a violet nobody chose, which is the one mistake the design system
     names by name. */
  --rb-ground: var(--panel-dark);
  --rb-band: var(--blue-lo);
  --rb-rule: rgb(252 252 252 / 0.09);
  --rb-lab: var(--on-dark-muted);
  --rb-fig: var(--white);
  --rb-n: var(--on-dark);
  --rb-mid: rgb(252 252 252 / 0.14);

  position: relative;
  margin-top: var(--sp-l);
  border-radius: clamp(14px, 1.4vw, 20px);
  background: var(--rb-ground);
  overflow: hidden;
  box-shadow: var(--lift-3);
  isolation: isolate;
}

/* The same object inside the report, on the white pane. It borrows nothing but
   its own tokens: no shadow, no radius, no ground of its own. */
.rib--pane {
  --rb-ground: transparent;
  --rb-band: var(--blue);
  --rb-rule: var(--d-line);
  --rb-lab: var(--d-mute);
  /* The figure sits on the band, and the band is blue on both grounds, so the
     figure is white on both. Inking it to match the pane put near-black type
     on brand blue. */
  --rb-fig: #fff;
  --rb-n: var(--d-ink);
  --rb-mid: rgb(0 0 0 / 0.1);

  margin-top: 0;
  border-radius: 0;
  box-shadow: none;
}

/* The ribbon fills the columns exactly, so the band and the rules cannot drift
   apart at any width. It is stretched by preserveAspectRatio: height is a
   share of the frame, and every share is the count. */
/* The art and the columns are the same box, so the band's centre line and the
   row the figures sit in cannot drift. Sized apart they were forty pixels out
   and the share floated above its own ribbon.

   An svg is also a replaced element: at height auto it takes the viewBox's
   proportion, which here is a square, and a fourteen hundred pixel square then
   ran out of the panel and down the page. Hence the explicit height. */
.rib__stage { position: relative; }

.rib__art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;

  /* The band draws itself once. Two movements at the same time, read off the
     reference frame by frame rather than guessed: it advances from the left
     and it swells from the centre line, so it arrives as a thread and thickens
     into a ribbon. Sixty frames, growing over about sixteen of them, then a
     hold. The hold is the whole thing here: it plays once and stops, because a
     loop on a page is a flicker somebody has to sit through twice.

     Clipped on the svg rather than on the three paths: a clip-path on an svg
     geometry element did not interpolate and the reveal stayed frozen at the
     first column. */
  clip-path: inset(0 100% 0 0);
  transform: scaleY(0.1);
  transform-origin: 50% 50%;
  transition:
    clip-path 1.5s cubic-bezier(0.32, 0.86, 0.3, 1),
    transform 1.25s cubic-bezier(0.22, 0.9, 0.3, 1) 0.12s;
}
.rib.is-in .rib__art { clip-path: inset(0 0 0 0); transform: scaleY(1); }

.rib__band { fill: var(--rb-band); opacity: var(--o); }

/* The break, painted across the narrowing it is about rather than washed over
   a whole column. It breathes, slowly: this is the one thing on the page the
   owner is meant to keep looking at, and a flash would be a thing to sit
   through twice. */
.rib__break { fill: var(--red); animation: ribbeat 3.2s ease-in-out infinite; }
@keyframes ribbeat {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

/* The line the reference runs through its figures. It sits where the band is
   symmetric about, so it also shows that the band is. */
.rib__mid {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: var(--rb-mid);
  z-index: 1;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 1.1s cubic-bezier(0.32, 0.86, 0.3, 1);
}
.rib.is-in .rib__mid { transform: scaleX(1); }

.rib__cols {
  position: relative;
  z-index: 2;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  min-height: 24rem;
}

.rib__c {
  display: grid;
  grid-template-rows: 3.6rem minmax(0, 1fr) 3.6rem;
  align-items: center;
  justify-items: center;
  padding: 1.1rem 0.5rem;
  border-left: 1px solid var(--rb-rule);
  text-align: center;
}
.rib__c:first-child { border-left: 0; }

.rib__lab {
  font-family: PlexMono, ui-monospace, monospace;
  font-size: var(--step-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rb-lab);
  align-self: start;
}

/* The share, inside the band. Set in the display face at the size the
   reference has it: this is the number, and everything else on the panel is a
   footnote to it. */
.rib__pc {
  font-family: Asgard, Impact, sans-serif;
  font-weight: 500;
  font-size: clamp(1.6rem, 0.6rem + 2.2vw, 3rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--rb-fig);
}

/* Where the band has gone thin, the figure would sit on a hairline and read as
   floating. A shadow of the panel's own colour puts it back on the shape. */
.rib__pc.is-thin { text-shadow: 0 0 1.4rem var(--rb-ground), 0 0 0.5rem var(--rb-ground); }

.rib__foot {
  display: grid;
  gap: 0.1rem;
  align-self: end;
  font-family: PlexMono, ui-monospace, monospace;
  font-size: var(--step-label);
  letter-spacing: 0.08em;
}
.rib__n { color: var(--rb-n); font-variant-numeric: tabular-nums; }
.rib__d { font-style: normal; color: var(--red); }
.rib__who { color: var(--rb-lab); text-transform: uppercase; letter-spacing: 0.14em; }

/* The report answers in money, so the report's copy of this picture carries
   money at the foot of the column where the section carries a percentage. */
.rib__cost { color: var(--blue); font-variant-numeric: tabular-nums; }
.rib__c.is-break .rib__cost { color: var(--red); }

/* Inside the pane the object is shorter: it stands under a head and above a
   table, not alone in a section of its own. */
.rib--pane .rib__cols { min-height: 15rem; }
.rib--pane .rib__band:first-of-type { display: none; }
.rib--pane .rib__c { padding: 0.8rem 0.4rem; grid-template-rows: 2.6rem minmax(0, 1fr) 2.6rem; }
.rib--pane .rib__pc { font-size: clamp(1.2rem, 0.7rem + 1.1vw, 1.9rem); }
.rib--pane .rib__art { transition: none; clip-path: none; transform: none; }
.rib--pane .rib__mid { transition: none; transform: none; }

/* The one column the section exists for. The first fall is the biggest and it
   belongs to the advertising, so colouring by size would point at the wrong
   step; this points at the worst one he owns. */
/* No wash on the column. The band says where the break is; the column only
   has to agree with it in three words. */
.rib__c.is-break .rib__lab,
.rib__c.is-break .rib__who { color: var(--red); }
.rib__c.is-break .rib__d { font-weight: 500; }

.rib__say {
  position: relative;
  z-index: 1;
  margin: 0;
  padding: var(--sp-m);
  border-top: 1px solid rgb(252 252 252 / 0.1);
  color: var(--on-dark-muted);
  font-size: var(--step--1);
}
.rib__say b { color: var(--white); font-weight: 500; }

@media (max-width: 61.99rem) {
  /* Six columns of a phone are six slivers, so the columns stack. A ribbon
     narrowing left to right behind stacked rows is a wash, not a funnel, so on
     this width each row carries its own length instead. The encoding does not
     change: the bar is the count against the first step. */
  .rib__art,
  .rib__mid { display: none; }

  .rib__c::after {
    content: "";
    grid-column: 1 / -1;
    height: 6px;
    width: calc(var(--k) * 1%);
    min-width: 3px;
    border-radius: 999px;
    background: var(--rb-band);
  }
  .rib__c.is-break::after { background: var(--red); }
  .rib__cols { grid-auto-flow: row; min-height: 0; }
  .rib__c {
    grid-template-rows: auto auto;
    grid-template-columns: minmax(0, 1fr) auto;
    justify-items: start;
    text-align: left;
    gap: 0.2rem 1rem;
    padding: 0.7rem var(--sp-m);
    border-left: 0;
    border-top: 1px solid var(--rb-rule);
  }
  .rib__c:first-child { border-top: 0; }
  .rib__lab { grid-column: 1; align-self: center; }
  .rib__pc { grid-column: 2; grid-row: 1 / span 2; font-size: var(--step-2); }
  .rib__foot { grid-column: 1; display: flex; gap: 0.6rem; align-self: center; }
  .rib__c.is-break { border-left: 2px solid var(--red); }
}

@media (prefers-reduced-motion: reduce) {
  .rib__art { transition: none; clip-path: none; transform: none; }
  .rib__mid { transition: none; transform: none; }
  .rib__break { animation: none; opacity: 0.9; }
}

/* ===== Первые экраны на телефоне: часть, живущая ЗДЕСЬ =====================

   `.paper`, `.opener` и `.stage` объявлены в этом файле и в `main.css`, и
   правила для них стоят рядом с компонентами: имя класса, написанное ещё и
   в `pages.css`, гейт `collide` считает коллизией. Замерено: пять коллизий
   стали восемью, после переноса вернулись к пяти.

   Смысл тот же, что у соседних правил в `pages.css`: кегль заголовка
   первого экрана течёт по ширине окна, потолок прежний. */
@media (width < 48rem) {
  /* `.stage` объявлен в `main.css`, его правило стоит там. */
  .paper h1, .opener h1 { font-size: min(var(--step-4), 7.6vw); line-height: 1.06; }
  .opener { grid-template-columns: minmax(0, 1fr); }
  /* Синий свет вместо снятого `::before`. Стоит ниже шортката
     `background: var(--ink)`, иначе тот его обнуляет. */
  .play-pin { background-image: var(--night-glow); }
}


/* ===== /content/: первый экран - предмет внутри имени =========================

   Приём: The Watch, REFERENCES.md #6 (28.9x, 6 цветов, медиа 1.00). Имя
   набрано во всю ширину экрана, предмет стоит перед ним и закрывает средние
   знаки. Предмет здесь - настоящая съёмка клиента (`reel.mp4`, 464x848,
   девять секунд), вертикальный кадр в своём масштабе, чтобы не мылить: на
   главной он живёт в макете телефона, здесь стоит сам. Первая версия экрана
   была заголовком, лидом и списком: контраст 2.9x, медиа 0 (аудит 05.09).
   Ратифицированный приём страницы (строка на работу и кто её делает) не
   тронут, он идёт вторым экраном. */

.cl-hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  align-content: center;
  overflow: clip;
  background-color: var(--ink); /* не шортхенд: фон-картинку кладёт `data-glow` */
  color: var(--on-dark);
  margin-top: calc(var(--chrome-h, var(--header-h)) * -1);
  padding-top: calc(var(--chrome-cover, var(--header-h)) + var(--sp-m));
  padding-bottom: var(--sp-m);
}
/* Имя во всю ширину. Кегль от ширины окна: строка «A camera on site.» обязана
   упираться в края, а не лежать в рамке. 11.4vw подобрано по замеру ширины
   строки на 1900 (Asgard, 17 знаков). */
.cl-hero__name {
  position: relative;
  margin: 0;
  text-align: center;
  white-space: nowrap;
  font-family: Asgard, Impact, sans-serif;
  font-weight: 500;
  font-size: 11.4vw;
  line-height: 0.88;
  letter-spacing: -0.03em;
  color: #fff;
}
.cl-hero__name b { font-weight: 500; color: var(--blue-lo); }
/* Предмет: вертикальная съёмка в рост экрана, перед средними знаками. */
.cl-hero__reel {
  position: absolute;
  left: 50%;
  top: calc(var(--chrome-cover, var(--header-h)) + 2svh);
  height: calc(100svh - var(--chrome-cover, var(--header-h)) - 4svh);
  aspect-ratio: 464 / 848;
  transform: translateX(-50%);
  z-index: 1;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: #000;
  box-shadow: 0 40px 80px rgb(0 0 0 / 0.6);
}
.cl-hero__reel video { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Спецификация мелко, слева внизу, как у источника: что это за съёмка и
   чья. Справа внизу - лид страницы. */
.cl-hero__spec, .cl-hero__lede {
  position: absolute; bottom: var(--sp-l); z-index: 2; margin: 0;
  max-width: 30ch; font-size: var(--step--1); line-height: 1.5; color: var(--on-dark-muted);
}
.cl-hero__spec { left: max(1.25rem, 5vw); font-family: PlexMono, ui-monospace, monospace; }
.cl-hero__spec b { color: #fff; font-weight: 400; }
.cl-hero__lede { right: max(1.25rem, 5vw); text-align: right; font-size: var(--step-0); font-weight: 300; color: var(--on-dark); }

@media (width < 60rem) {
  .cl-hero { align-content: start; }
  .cl-hero__name { font-size: 15vw; white-space: normal; text-align: left; }
  /* На телефоне съёмка стоит под именем и режется низом экрана. */
  .cl-hero__reel { position: relative; left: auto; top: auto; transform: none; height: auto; width: min(60vw, 20rem); margin: var(--sp-m) auto 0; }
  .cl-hero__spec, .cl-hero__lede { position: static; max-width: none; text-align: left; margin-top: var(--sp-m); }
}
@media (prefers-reduced-motion: reduce) { .cl-hero__reel video { display: none; } .cl-hero__reel { background: url("/assets/img/case/reel-poster.jpg") center / cover; } }
