/* Page sections.

   The page is one continuous dark ground — ADR-0002 — so the rhythm has to
   come from somewhere other than colour. It comes from three things: the tonal
   step between sections, the amount of air around a heading, and the scroll
   choreography. Nothing here is decorated for its own sake. */

@property --lit { syntax: '<number>'; inherits: false; initial-value: 0; }

/* ---------------------------------------------------- 0. Shared furniture */

.sec-head {
  display: grid;
  gap: var(--space-6);
  margin-bottom: clamp(2.5rem, 5vw, 4.5rem);
  align-items: end;
}
@media (min-width: 62rem) {
  .sec-head { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); gap: var(--space-16); }
}
.sec-head__lead { max-width: 42ch; }

.points { display: grid; gap: var(--space-4); }
.point {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3);
  align-items: start;
  color: var(--ink-2);
  font-size: var(--text-sm);
}
.point .icon { color: var(--brand-lift); width: 1.05rem; height: 1.05rem; margin-top: 0.35em; }

/* A decorative field of cells. Texture that carries the motif. */
.unit-field {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(26px, 1fr));
  gap: 5px;
  pointer-events: none;
}
.unit-field__cell { aspect-ratio: 1; border-radius: 2px; background: var(--available); opacity: 0.09; }
.unit-field__cell[data-state="blocked"] { background: var(--blocked); opacity: 0.16; }
.unit-field__cell[data-state="sold"] { background: var(--sold); opacity: 0.16; }

/* ============================================================== 1. HERO */

/* Centred, one object, and the ground steps down to the darkest tone on the
   page. The rounded bottom edge is what makes the next section read as sliding
   underneath rather than butting against it. */
.hero {
  position: relative;
  z-index: 1;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: calc(var(--header-h) + clamp(3rem, 8vw, 6.5rem));
  overflow: hidden;
  background: var(--bg-deep);
  border-radius: 0 0 var(--radius-xl) var(--radius-xl);
  isolation: isolate;
}

.hero__haze { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; }

.hero__inner { position: relative; z-index: 2; text-align: center; }

.hero__title { margin-bottom: var(--space-8); }
/* The display sizes are tuned to the longest line the site actually sets, and
   this keeps a line from wrapping into two at an awkward breakpoint. */
.hero__title .line__in { white-space: nowrap; }
.hero__title .word--accent { color: var(--brand-lift); }

.hero__sub {
  font-size: var(--text-lg);
  color: var(--ink-2);
  max-width: 36ch;
  margin-inline: auto;
  margin-bottom: var(--space-8);
  line-height: var(--leading-snug);
}

/* The word that changes. A mask with a column of words inside it, stepped by
   script. The full list is printed once for screen readers in the markup — a
   rotating word announced four times is noise, not information. */
.rotator {
  display: inline-grid;
  vertical-align: bottom;
  overflow: hidden;
  height: 1.35em;
  text-align: left;
  /* Width is written per word by script and eased, so a short word does not
     leave a hole the size of the longest one. The fallback is max-content,
     which is what a no-JS visitor never sees anyway — the rotator is
     aria-hidden and the flat list beside it carries the meaning. */
  width: var(--rot-w, max-content);
  transition: width 0.5s var(--ease-out);
}
.rotator__set { display: block; will-change: transform; }
.rotator__w {
  display: block;
  /* Its own text width, not the mask's — otherwise every word measures the
     same and the width animation above has nothing to animate to. */
  width: max-content;
  height: 1.35em;
  line-height: 1.35em;
  color: var(--brand-lift);
  font-weight: 600;
  white-space: nowrap;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  justify-content: center;
  margin-bottom: var(--space-5);
}
.hero__note { font-size: var(--text-sm); color: var(--ink-3); }

/* The one object. It bleeds off the bottom edge of the hero, which is what
   stops the section reading as a slide with a picture centred on it. */
.hero__stage {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: var(--container-wide);
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
  padding-inline: var(--gutter);
  display: flex;
  justify-content: center;
}
 /* The alerts are positioned against THIS, not the page, so they stay beside
   the device instead of migrating to the screen edges on a wide monitor. It is
   wider than it needs to be for the device alone: the extra is the clearance
   the alerts sit in, and it grew when the device did. */
.hero__frame { position: relative; width: min(58rem, 100%); display: flex; justify-content: center; }

/* Big, and cropped on purpose. It bleeds off the bottom of the hero, which is
   what stops the section reading as a slide with a picture centred on it — so
   the height is not a constraint and the width can be taken as far as the
   alerts beside it allow.

   The max-width override is not optional. `.device--phone` carries its own
   `max-width: 22rem`, which silently caps the frame at 352px however wide this
   wrapper gets — widen the wrapper alone and the measurements all move while
   the phone on screen does not change at all. */
.hero__device { width: min(30rem, 78vw); will-change: transform; }
.hero__device .device--phone { max-width: none; }

@media (max-width: 56rem) {
  .hero__device { width: min(22rem, 70vw); }
}

/* ========================================================= 2. VOCABULARY */

.band { position: relative; z-index: 0; padding-top: clamp(3.5rem, 8vw, 6rem); background: var(--bg); }
.band__label {
  max-width: var(--container-wide);
  margin-inline: auto;
  padding-inline: var(--gutter);
  margin-bottom: var(--space-8);
  font-size: var(--text-sm);
  color: var(--ink-3);
}

/* ========================================================== 3. STATEMENT */

.statement-sec { background: var(--bg); }
.statement-sec__inner { max-width: 70rem; }

/* Words ink in as they are read. The resting colour is legible on its own —
   this is a reading aid, not a reveal, so nothing is ever truly hidden. */
.statement {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' 98, 'wght' 650;
  font-size: clamp(1.35rem, 3.5vw, 3rem);
  line-height: 1.28;
  color: var(--ink-4);
  text-wrap: pretty;
}
.statement__w {
  display: inline-block;
  color: color-mix(in srgb, var(--ink) calc(var(--lit, 0) * 100%), var(--ink-4));
  transition: color 240ms linear;
}
.statement__w--accent {
  color: color-mix(in srgb, var(--brand-lift) calc(var(--lit, 0) * 100%), var(--ink-4));
}

/* ============================================================== 4. RAIL */

/* Vertical scroll pushes a horizontal track. The section is tall, the viewport
   inside it sticks, and the track slides. No wheel handler and no scroll
   hijack — the page scrolls exactly as far as the scrollbar says it does. */
.rail-sec { position: relative; background: var(--bg-lift); }
.rail__viewport {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(1.5rem, 4vw, 3rem);
  overflow: hidden;
  padding-block: calc(var(--header-h) + var(--space-8)) var(--space-12);
}

.rail__head {
  flex: none;
  width: 100%;
  max-width: var(--container-wide);
  margin-inline: auto;
  padding-inline: var(--gutter);
  container-type: inline-size;
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 62rem) {
  .rail__head { grid-template-columns: minmax(0, 1fr) minmax(0, 24rem) auto; align-items: end; gap: var(--space-10); }
}
.rail__lead { color: var(--ink-2); font-size: var(--text-sm); max-width: 40ch; }
.rail__hint { font-size: 10px; text-transform: uppercase; color: var(--ink-4); }

.rail__track {
  display: flex;
  gap: var(--space-5);
  padding-inline: var(--gutter);
  width: max-content;
  will-change: transform;
}

/* Without JS the rail is an ordinary horizontal scroller, which is a perfectly
   good way to read six cards. */
html:not(.js) .rail-sec { padding-block: var(--section-y); }
html:not(.js) .rail__viewport { position: static; height: auto; overflow: visible; padding: 0; }
html:not(.js) .rail__track { overflow-x: auto; width: auto; }

.rcard {
  position: relative;
  flex: none;
  width: min(23rem, 78vw);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
  transition: border-color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}
.rcard:hover { border-color: var(--brand-line); background: var(--surface-1); }

.rcard {
  /* Tall enough to fill the sticky viewport it sits in, instead of floating in
     the middle of it with a third of the screen dead underneath. */
  min-height: min(32rem, 58svh);
}
.rcard__art {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--space-6);
  background: var(--bg-deep);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
  display: flex;
}
.rcard__body { padding: var(--space-6); display: grid; gap: var(--space-3); }
.rcard__title { display: flex; align-items: center; gap: var(--space-3); font-size: var(--text-lg); font-weight: 600; }
.rcard__title .icon { color: var(--brand-lift); width: 1.2rem; height: 1.2rem; }
.rcard__text { color: var(--ink-2); font-size: var(--text-sm); line-height: var(--leading-snug); }

/* ------------------------------------------------------- the card visuals */

/* Built rather than screenshotted: sharp at any size, weightless, and they
   cannot go stale when the app's own UI moves. */
.art { width: 100%; height: 100%; }

/* The Units screen in miniature: the key, then floors beside the units of the
   floor that is open. The state colours are the site's own three, which is also
   why the key is worth the two lines it costs — it is the legend for every dot
   below it, and for the unit field behind the hero. */
.art--grid { display: grid; }

/* The whole art fills its slot and the floor list takes whatever is left, so
   the list is clipped by the card's own bottom edge rather than stopping short
   of it with dead space underneath. */
.art__un { display: grid; grid-template-rows: auto minmax(0, 1fr); gap: var(--space-4); }
.art__un-key { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.art__un-k { display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--ink-3); }
.art__un-k::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--state);
}
/* Available is the quiet one on purpose: a floor of unsold units should not be
   the loudest thing on the card. */
.art__un [data-state="available"] { --state: var(--available-lift); }
.art__un [data-state="blocked"] { --state: var(--blocked); }
.art__un [data-state="sold"] { --state: var(--sold); }

.art__un-cols {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1fr);
  gap: var(--space-4);
  min-height: 0;
}
.art__un-side { display: grid; grid-template-rows: auto minmax(0, 1fr); gap: var(--space-2); min-height: 0; }

/* The list is taken out of the flow, which is the whole point of the wrapper.
   Left in it, twelve floors set the height of the art slot, the art slot set the
   height of the card, and one card in the rail stood taller than the other five.
   Absolute, it fills the room the unit tiles beside it have already decided on
   and shows however many floors fit.

   Masked rather than cut: a hard edge looks like the last floor, a fade looks
   like the next one. */
.art__un-fwrap { position: relative; min-height: 0; }
.art__un-floors {
  position: absolute;
  inset: 0;
  display: grid;
  gap: 2px;
  align-content: start;
  overflow: hidden;
  mask-image: linear-gradient(to bottom, #000 55%, transparent 97%);
}
.art__un-floor {
  display: grid;
  gap: 4px;
  padding: 5px var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
}
.art__un-floor[data-on="1"] { border-color: var(--brand-line); background: var(--brand-wash); }
.art__un-frow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.art__un-fname { font-size: 11px; color: var(--ink-2); }
.art__un-floor[data-on="1"] .art__un-fname { color: var(--brand-lift); }
.art__un-fmeta { flex: none; font-size: 9px; color: var(--ink-4); }

/* Every segment is a state in the key above it — sold, then blocked, then what
   is still available. It was a progress bar before, which is the one thing a
   floor's composition is not: the grey tail meant "blocked" but matched nothing
   in the key, so it read as track left to fill. */
.art__un-fbar {
  height: 3px;
  border-radius: 2px;
  background:
    linear-gradient(90deg,
      var(--sold) 0 var(--is-sold),
      var(--blocked) var(--is-sold) var(--is-blocked),
      var(--available-lift) var(--is-blocked) 100%);
}

.art__un-list { display: grid; gap: var(--space-2); align-content: start; }
.art__un-cap { font-size: 9px; color: var(--ink-4); }
.art__un-side > .art__un-cap { padding-inline: var(--space-2); }
.art__un-tile {
  position: relative;
  display: grid;
  gap: 2px;
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-1);
}
/* The dot sits where the app puts it, opposite the number. */
.art__un-tile::after {
  content: "";
  position: absolute;
  top: var(--space-3); right: var(--space-3);
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--state);
}
.art__un-no { font-size: var(--text-sm); font-weight: 600; color: var(--ink); }
.art__un-meta { font-size: 9px; color: var(--ink-3); }

/* A booking as the app draws one: the customer and the approval across the
   top, the terms below as label-and-value rows separated by hairlines. It
   replaced a unit number over a progress bar, which was a picture of nothing
   the Bookings screen actually shows — a reader who took a demo would not have
   recognised it. */
.art--booking { display: grid; place-items: center; }
.art__bk {
  width: 100%;
  display: grid;
  gap: var(--space-4);
  padding: var(--space-5);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface-1);
}

/* Three columns, and the subtitle drops to a row of its own spanning all of
   them. Kept in the narrow middle column it wraps after "Vantage", and a unit's
   address broken across two lines stops looking like one thing. */
.art__bk-top {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}
/* A rounded square, not a circle: the app's avatars are squares, and the shape
   is half of what makes a screen recognisable at this size. */
.art__bk-av {
  flex: none;
  display: grid; place-items: center;
  width: 2.1rem; height: 2.1rem;
  border-radius: var(--radius-sm);
  background: var(--brand);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
}
.art__bk-name {
  grid-column: 2;
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}
.art__bk-more {
  padding: 0 5px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  font-size: 10px;
  font-weight: 500;
  color: var(--ink-3);
}
.art__bk-sub {
  grid-column: 1 / -1;
  font-size: var(--text-xs);
  color: var(--ink-3);
}
.art__bk-state {
  grid-column: 3;
  display: flex; align-items: center; gap: 5px;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--sold) 14%, transparent);
  color: var(--sold);
  font-size: 10px;
  font-weight: 500;
}
.art__bk-state::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: currentColor;
}

/* The rule above the first row doubles as the divider under the header, which
   is how the app separates them too. */
.art__bk-fields { display: grid; }
.art__bk-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: var(--space-3);
  border-top: 1px solid var(--line);
  font-size: var(--text-xs);
}
.art__bk-row > :first-child { color: var(--ink-3); }
.art__bk-row > :last-child { color: var(--ink); }

.art--ladder { display: grid; gap: var(--space-3); align-content: center; }
.art__rung {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  font-size: var(--text-xs);
}
.art__rung b { position: absolute; inset: 0 auto 0 0; background: var(--brand-wash); }
.art__rung em { position: relative; font-style: normal; color: var(--ink-2); }
.art__rung i { position: relative; color: var(--ink); font-style: normal; }

.art--letter { display: grid; place-items: center; }
.art__letter {
  position: relative;
  height: 100%;
  aspect-ratio: 3 / 4;
  padding: var(--space-5);
  display: grid;
  gap: var(--space-3);
  align-content: start;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  box-shadow: var(--shadow-lift);
}
.art__line { height: 6px; border-radius: 3px; background: var(--surface-3); }
.art__line:nth-child(1) { width: 60%; background: var(--ink-4); }
.art__line:nth-child(4) { width: 82%; }
.art__line:nth-child(6) { width: 44%; }
.art__stamp {
  position: absolute;
  right: var(--space-4); bottom: var(--space-5);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--overdue);
  border-radius: var(--radius-xs);
  color: var(--overdue);
  font-size: 10px;
  text-transform: uppercase;
  transform: rotate(-9deg);
}

/* Three projects, each a full run of stages with the one it has reached marked
   on it. The ticks are a repeating gradient rather than a few hundred spans:
   the same drawing for none of the DOM. The done layer starts at the same left
   edge as the track, so the two tick phases line up and the marker lands on a
   tick rather than between two. */
.art--timeline { display: grid; gap: var(--space-6); align-content: center; }
.art__cs { display: grid; gap: var(--space-2); }
.art__cs[data-state="ok"] { --verdict: var(--brand-lift); }
/* Amber is the app's colour for a date that has slipped. The token is named for
   a unit state, but it is the same amber and the palette is deliberately small. */
.art__cs[data-state="late"] { --verdict: var(--blocked); }
.art__cs[data-state="ahead"] { --verdict: var(--sold); }

.art__cs-head { display: flex; align-items: baseline; gap: var(--space-2); font-size: var(--text-xs); }
.art__cs-head em { font-style: normal; color: var(--ink); }
.art__cs-head i { margin-left: auto; font-style: normal; color: var(--verdict); }
.art__cs-head b { font-weight: 500; color: var(--ink); }

.art__cs-track {
  position: relative;
  height: 18px;
  background: repeating-linear-gradient(90deg, var(--line-strong) 0 1px, transparent 1px 6px);
}
.art__cs-done {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--at);
  background: repeating-linear-gradient(90deg, var(--brand) 0 1px, transparent 1px 6px);
}
/* The last stretch before the marker carries the verdict's colour, the way the
   app tints the approach to a stage that slipped. It is drawn full width and
   clipped to that stretch rather than positioned at it: a gradient that began
   at an arbitrary offset would fall out of phase with the track and the ticks
   would stop lining up. On an on-time project the tint is the blue already
   there, so nothing shows — which is correct. */
.art__cs-tail {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(90deg, var(--verdict) 0 1px, transparent 1px 6px);
  clip-path: inset(0 calc(100% - var(--at)) 0 max(0px, calc(var(--at) - 5%)));
}

/* Taller than the ticks at both ends, which is what makes it read as a marker
   on the run rather than as one more stage in it. */
.art__cs-mark {
  position: absolute;
  left: var(--at);
  top: -3px;
  bottom: -5px;
  width: 2px;
  background: var(--verdict);
}
.art__cs-stage { margin-left: calc(var(--at) + 6px); font-size: 10px; color: var(--ink-2); }

.art__dash { display: grid; gap: var(--space-4); align-content: start; }
.art__stat { display: grid; gap: 2px; }
.art__stat em { font-style: normal; font-size: var(--text-xl); color: var(--sold); }
.art__stat--neg em { color: var(--overdue); }
.art__stat i { font-style: normal; font-size: var(--text-xs); color: var(--ink-3); }
.art__proj { display: grid; gap: var(--space-2); }
.art__proj em { font-style: normal; font-size: var(--text-xs); color: var(--ink-2); }
.art__proj b { display: block; height: 4px; border-radius: 2px; background: var(--surface-3); overflow: hidden; }
.art__proj b i { display: block; height: 100%; background: var(--brand); }

/* ============================================================ 5. MIGRATE */

/* Six spreadsheets that disagree, and one device they all end up inside.

   Pinned like the rail: the section is three screens tall, the viewport inside
   it sticks, and the scroll carries the sheets in. Nothing here may look like
   an interface except the device at the centre, and that is a real photograph
   of a real frame around a real screenshot. The sheets are the reader's own
   mess — the only thing this site may draw that is not the product. See §4c of
   agents.md. */

@property --tilt { syntax: '<number>'; inherits: false; initial-value: 0; }
@property --wake { syntax: '<number>'; inherits: true; initial-value: 0; }

.migrate-sec { position: relative; height: 300vh; background: var(--bg-deep); }

.migrate__viewport {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  flex-direction: column;
  gap: clamp(1.25rem, 3vw, 2.5rem);
  overflow: hidden;
  /* The header is a floating pill: its own top inset plus its height put its
     bottom edge at 88px, so --header-h alone leaves the heading almost touching
     it. This is the hero's own clearance formula, which is why the two sections
     start their content at the same distance from the bar. */
  padding: calc(var(--header-h) + clamp(2.5rem, 5vw, 4.5rem)) var(--gutter) var(--space-10);
}

/* Wider than the page container, and weighted toward the heading. The pinned
   viewport adds its own gutters, so the same two-column split that fits in
   .sec-head elsewhere leaves this heading a column too narrow and breaks it
   across three lines instead of two. */
.migrate__head {
  flex: none;
  width: 100%;
  max-width: var(--container-wide);
  margin-inline: auto;
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 62rem) {
  .migrate__head { grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.7fr); gap: var(--space-12); align-items: end; }
}
.migrate__lead { margin: 0; max-width: 42ch; color: var(--ink-2); font-size: var(--text-md); }

/* The sheets are positioned against this, so it must have a height of its own
   for their percentage tops to resolve. It is also a size container, which is
   what lets the device be sized against the room it actually has rather than
   against the viewport — see the note on the device below. */
.migrate__stage {
  position: relative;
  flex: 1;
  width: 100%;
  max-width: var(--container-wide);
  margin-inline: auto;
  min-height: 0;
  container-type: size;
}

/* ---------------------------------------------------------------- a sheet */

.sheet {
  position: absolute;
  width: clamp(9.5rem, 15vw, 12.5rem);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: #f2f1ec;
  color: #23232b;
  /* rotate is its OWN property, not a transform function. Load bearing: the
     scrub owns `transform` and rewrites it every frame, so a tilt written into
     the same transform would be wiped. */
  rotate: calc(var(--tilt) * 1deg);
  user-select: none;
  -webkit-user-select: none;
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.6);
  will-change: transform, opacity;
}

.sheet__name {
  margin: 0 0 var(--space-2);
  font-size: 11px;
  font-weight: 600;
  color: #3c3c47;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sheet__grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1px;
  background: #cfcdc5;
  border: 1px solid #cfcdc5;
}
.sheet__cell {
  padding: 3px 5px;
  background: #fbfaf7;
  font-size: 10px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sheet__cell--head { background: #e6e4dd; font-weight: 600; color: #52525f; }

/* The disagreement is the section. It gets the only colour on the paper. */
.sheet__cell--clash { background: #ffe0e2; color: #a3121f; font-weight: 600; }
.sheet__cell--stale { color: #9a9aa4; font-style: italic; }

.sheet__meta { margin: var(--space-2) 0 0; font-size: 10px; color: #8b8b95; }

/* --------------------------------------------------------------- the device */

/* Centred and above the sheets, so they land in front of it and disappear INTO
   the screen rather than behind the frame. */
.migrate__device {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  z-index: 1;
  /* The third term is the height guard, and it is measured in cqh — a share of
     the STAGE's height, not the viewport's. That distinction is the whole rule.
     The heading, the caption and the padding above them take a fixed number of
     pixels off the pinned viewport, so the stage is not a constant fraction of
     vh: it is vh minus a constant. A vh cap generous enough to fill a tall
     screen therefore overflows a short one, and a cap safe on a short screen
     leaves a tall one two-thirds empty. Against the stage itself there is no
     such tension. The laptop is about 1.5x wider than it is tall, so 133cqh is
     88% of the stage's height — most of the room, with the rest left for the
     sheets to rest against its edges. */
  width: min(65rem, 62vw, 133cqh);
  pointer-events: none;
}
.migrate__device .device { max-width: none; }

/* --wake is written by the scrub, 0 to 1, and everything about the device being
   awake is derived from it. One number, so the screen, the colour and the glow
   cannot disagree with each other.

   The dashboard is part of the render now, so the screen cannot be faded up on
   its own. It is put to sleep instead: a wash of the page's own darkness laid
   over the glass, which clears as the sheets land. The machine itself stays
   solid throughout — a laptop that faded in with its screen would read as an
   arriving picture rather than as a machine waking up. */
.migrate__device .device__screen {
  background: var(--bg-deep);
  opacity: calc(0.96 - var(--wake) * 0.96);
}
.migrate__device::after {
  content: "";
  position: absolute;
  /* Follows the laptop's LID, not its whole silhouette — a glow that included
     the base would light the desk from under the keyboard. Same measured box as
     the screen, less the hairline of bezel. */
  inset: 10.12% 13.44% 13.83%;
  border-radius: 2%;
  box-shadow: 0 0 90px 10px var(--brand-glow);
  opacity: var(--wake);
  pointer-events: none;
  z-index: -1;
}

/* --------------------------------------------------------------- the line */

.migrate__caption {
  flex: none;
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  color: var(--ink-2);
  font-size: var(--text-md);
  text-align: center;
  opacity: var(--wake);
}

/* ------------------------------------------------------------- fallbacks */

/* No JS, or reduced motion: no pin, no scrub. The sheets sit where the server
   scattered them and the device is simply on — both halves of the argument
   visible at once, which is the honest still frame of it. */
html:not(.js) .migrate-sec,
.migrate-sec.is-static { height: auto; padding-block: var(--section-y); }
html:not(.js) .migrate__viewport,
.migrate-sec.is-static .migrate__viewport { position: static; height: auto; overflow: visible; padding: 0; }
html:not(.js) .migrate__stage,
.migrate-sec.is-static .migrate__stage { height: clamp(28rem, 44vw, 34rem); flex: none; }
html:not(.js) .migrate__device .device__screen { opacity: 0; }
html:not(.js) .migrate__caption { opacity: 1; }

@media (max-width: 52rem) {
  .sheet { width: clamp(8rem, 34vw, 10rem); padding: var(--space-2); }
  .migrate__device { width: min(24rem, 82vw, 48vh); }
  /* Two fewer sheets on a phone. Six around a device this size is a scrum and
     the argument survives four. */
  .sheet:nth-child(n + 5) { display: none; }
}

/* ============================================================ 6. LEDGER */

.ledger-sec { background: var(--bg); }
.ledger { padding: clamp(1.25rem, 2.8vw, 2rem); display: grid; gap: var(--space-6); }

.ledger__control {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-4);
  align-items: center;
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--line);
}
.ledger__control-label { font-size: var(--text-sm); color: var(--ink-2); }
.ledger__out { font-size: var(--text-lg); color: var(--ink); min-width: 3.5em; text-align: right; }
@media (max-width: 40rem) {
  .ledger__control { grid-template-columns: 1fr auto; }
  .ledger__slider { grid-column: 1 / -1; order: 3; }
}

/* A real range input, restyled. Keyboard, screen reader and touch all work
   because none of it was reimplemented. */
.ledger__slider { position: relative; display: flex; align-items: center; height: 2rem; }
.range { position: relative; z-index: 2; width: 100%; margin: 0; background: transparent; -webkit-appearance: none; appearance: none; }
.range:focus { outline: none; }
.range::-webkit-slider-runnable-track { height: 2rem; background: transparent; }
.range::-moz-range-track { height: 2rem; background: transparent; }
.range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 1.6rem; height: 1.6rem;
  margin-top: 0.2rem;
  border-radius: 50%;
  background: var(--ink);
  border: 4px solid var(--brand);
  box-shadow: 0 4px 18px var(--brand-glow);
  cursor: grab;
  transition: transform var(--dur-fast) var(--ease-out);
}
.range::-moz-range-thumb {
  width: 1.6rem; height: 1.6rem;
  border-radius: 50%;
  background: var(--ink);
  border: 4px solid var(--brand);
  box-shadow: 0 4px 18px var(--brand-glow);
  cursor: grab;
}
.range:active::-webkit-slider-thumb { transform: scale(0.9); cursor: grabbing; }
.range:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--ink); outline-offset: 3px; }

.range__track { position: absolute; left: 0; right: 0; height: 4px; border-radius: 2px; background: var(--surface-3); overflow: hidden; }
.range__fill { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--brand-deep), var(--brand-lift)); }

.ledger__totals { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); }
.ledger__total {
  display: grid; gap: var(--space-1);
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--surface-1);
  font-size: var(--text-xs);
  color: var(--ink-3);
}
.ledger__total strong { font-size: var(--text-lg); color: var(--ink); font-weight: 500; }
.ledger__total--due strong { color: var(--overdue); }

.ledger__rows { display: grid; gap: var(--space-2); }
.ledger__row {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  overflow: hidden;
  transition: border-color var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease-out);
}
.ledger__tick {
  width: 0.55rem; height: 0.55rem;
  border-radius: 50%;
  border: 1px solid var(--line-bright);
  transition: background var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}
.ledger__name { font-size: var(--text-sm); display: grid; gap: 1px; }
.ledger__trigger { font-size: 9px; text-transform: uppercase; color: var(--ink-4); }
.ledger__amount { font-size: var(--text-sm); color: var(--ink-2); }
.ledger__state { font-size: 10px; text-transform: uppercase; color: var(--ink-4); min-width: 5.5em; text-align: right; }

.ledger__row[data-state="due"] { border-color: rgba(255, 95, 109, 0.45); background: rgba(255, 95, 109, 0.07); }
.ledger__row[data-state="due"] .ledger__tick { background: var(--overdue); border-color: var(--overdue); }
.ledger__row[data-state="due"] .ledger__state,
.ledger__row[data-state="due"] .ledger__amount { color: var(--overdue); }

.ledger__row[data-state="received"] { border-color: rgba(70, 224, 140, 0.3); }
.ledger__row[data-state="received"] .ledger__tick { background: var(--sold); border-color: var(--sold); }
.ledger__row[data-state="received"] .ledger__state { color: var(--sold); }
.ledger__row[data-state="received"] .ledger__name { color: var(--ink-3); }

.ledger__row.is-firing { animation: ledger-fire 700ms var(--ease-out); }
@keyframes ledger-fire {
  0%   { transform: translateX(0); }
  18%  { transform: translateX(6px); }
  36%  { transform: translateX(-3px); }
  100% { transform: translateX(0); }
}

.ledger__letter {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4) var(--space-5);
  border: 1px dashed var(--brand-line);
  border-radius: var(--radius-sm);
  background: var(--brand-wash);
  font-size: var(--text-sm);
  color: var(--ink-2);
}
.ledger__letter .icon { color: var(--brand-lift); width: 1.2rem; height: 1.2rem; }
.ledger__letter strong { color: var(--ink); font-weight: 600; }
.ledger__letter-cta {
  font-size: 10px; text-transform: uppercase;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--brand);
  color: #fff;
}

/* ================================================================ 7. FAQ */

.faq-sec { background: var(--bg); }
.faq-sec__inner { display: grid; gap: clamp(2.5rem, 5vw, 4rem); }
@media (min-width: 62rem) {
  .faq-sec__inner { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: var(--space-16); align-items: start; }
  .faq-sec__head { position: sticky; top: calc(var(--header-h) + var(--space-8)); }
}
.faq-sec__head { container-type: inline-size; display: grid; gap: var(--space-6); }

/* Native <details>. It opens, closes and is announced correctly with no script
   at all; the height animation is layered on top of something that works. */
.faq { display: grid; }
.faq__item { border-top: 1px solid var(--line); }
.faq__item:last-child { border-bottom: 1px solid var(--line); }

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
  padding-block: var(--space-6);
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--ink-2);
  cursor: pointer;
  list-style: none;
  transition: color var(--dur-base) var(--ease-out);
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--ink); }
.faq__item[open] .faq__q { color: var(--ink); }

/* A plus that becomes a minus: two bars, one of which rotates away. */
.faq__sign { position: relative; width: 1rem; height: 1rem; flex: none; }
.faq__sign::before,
.faq__sign::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto;
  height: 1.5px;
  background: currentColor;
  transition: transform var(--dur-base) var(--ease-out);
}
.faq__sign::after { transform: rotate(90deg); }
.faq__item[open] .faq__sign::after { transform: rotate(0deg); }

.faq__a { overflow: hidden; }
.faq__a p {
  padding-bottom: var(--space-6);
  max-width: 58ch;
  color: var(--ink-3);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}
.faq__a strong { color: var(--ink); font-weight: 600; }

/* =============================================================== 8. CTA */

.cta-sec { background: var(--bg); padding-bottom: var(--space-24); }

.cta {
  position: relative;
  padding: clamp(3rem, 8vw, 6.5rem) clamp(1.5rem, 5vw, 4rem);
  text-align: center;
  border-color: var(--line-strong);
  background: radial-gradient(120% 90% at 50% 120%, rgba(97, 95, 255, 0.18), transparent 62%), var(--surface);
  container-type: inline-size;
}
.cta .unit-field--cta {
  position: absolute;
  inset: -10% -4% auto -4%;
  mask-image: radial-gradient(70% 90% at 50% 0%, #000, transparent 72%);
}
.cta__body { position: relative; z-index: 2; display: grid; justify-items: center; gap: var(--space-8); }
.cta__title .word--accent { color: var(--brand-lift); }
.cta__lead { color: var(--ink-2); max-width: 44ch; }
.cta__note { font-size: var(--text-sm); color: var(--ink-3); }
.cta__note strong { color: var(--ink); font-weight: 600; }

/* ======================================================= 9. Reveal base */

/* The baseline is VISIBLE. JS opts elements into a hidden start state, so a
   script that dies leaves a readable page rather than an empty one.

   The :not(.is-shown) guard matters more than it looks. A reveal finishes with
   utils.cleanInlineStyles(), which strips the inline transform and hands the
   element straight back to the stylesheet — so a hidden state declared
   unconditionally here would re-hide every heading the instant its animation
   completed. The class goes on when the animation starts; from then on this
   rule is off. */
html.js [data-reveal]:not(.is-shown) { opacity: 0; }

/* A display heading is hidden as ONE OBJECT.

   There is no per-word or per-line start state here, and that is the whole
   design: these headings run to about 120px of cap height, and at that size any
   gesture applied per word is a dozen heavy masses moving at once, which reads
   as noise rather than as typography. The larger the type, the smaller and
   slower the gesture — the whole heading lifts ten pixels, which is a twelfth
   of one letter.

   --wdth is deliberately NOT set here. Each display class asks for its own
   resting width, so leaving the computed value alone lets the script read a
   heading's real target before animating toward it. */
html.js [data-split]:not(.is-shown) { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  html.js [data-reveal]:not(.is-shown) { opacity: 1; }
  html.js [data-split]:not(.is-shown) { opacity: 1; }
}

/* Structural only. Nothing is masked and nothing inside a heading moves
   independently, so there is no overflow to clip and no perspective to
   establish — the .word spans survive because they carry the accent colour. */
.line { display: block; }
.line__in { display: block; }
.word { display: inline-block; }
.word--accent { color: var(--brand-lift); }

/* ============================================================== 9. 404 */

.notfound { min-height: 72svh; display: grid; place-items: center; }
.notfound__inner { text-align: center; }
.notfound__lead { margin: var(--space-8) auto var(--space-10); max-width: 34ch; }
.notfound__actions { display: flex; gap: var(--space-4); justify-content: center; flex-wrap: wrap; }

/* ==================================================== 10. Long-form pages */

/* About and Privacy are reading pages. Their body copy is set once here rather
   than by an inline colour on every paragraph, which is what they were doing. */
.doc { display: grid; gap: var(--space-10); margin-top: clamp(2.5rem, 6vw, 4rem); }
.doc__block { max-width: 64ch; }
.doc__block h2 { font-size: var(--text-xl); margin-bottom: var(--space-4); }
.doc p { color: var(--ink-2); }
.doc p + p,
.doc .points + p,
.doc p + .points { margin-top: var(--space-5); }
.doc a { color: var(--ink); }

.lede { margin-top: var(--space-8); max-width: 62ch; }
