/* home.css: Phase 3 presentation layer left after the shared-system swap.
   What's homepage-specific only: 2 the horizon planes' shapes; 3 proof
   tile-lit; 4 desk card + sticky journey stack. Tokens, reset, nav,
   footer, and the reveal-line/count/plane/stack primitives all moved to
   site.css, loaded before this file. */

/* ---------------------------------------------------------- 2. hero planes
   A layered night scene (DESIGN-hero-depth.md). Three photographic cut-outs,
   alpha WebP: .plane-far (a distant ridge, pylons on its crest, lifted
   toward haze), .plane-mid, and .plane-near, which sits ABOVE the copy
   (z-index 3) so the tracker panel's foot is buried in it. The sky is the
   page navy plus .horizon, the one glow, now BEHIND the far ridge so its
   rim shows only above the crest. Parallax mechanics are generic (site.js
   reads data-rate); each plane's width, offset and stacking live here.
   Offsets are in px from the hero's bottom edge; the near plane carries a
   navy body below its image so no clip line shows when it leads the copy
   (it moves up, data-rate -.12). */
/* The value ladder runs the way a night moor does: the sky is brightest at
   the horizon (gradient plus the glow rim just above the far crest), the
   far ridge is a dark slate silhouette against it, the mid darker, the near
   darkest with its texture lifted, and the panel is lifted off the ground
   plane so the rocks that eat its foot read as rocks in front of a thing.
   Rest geometry at 1440 (hero bottom = 0): panel foot 280 up; far crest
   ~560 (just under the sub line); mid ~440; near crest median ~330, so the
   foot is eaten ~50px (tiles and padding, never the words). Crest spread
   230px, about a quarter of the frame, Fora's proportion. */
.hero {
  padding-bottom: 280px;
  background: linear-gradient(180deg, #0a0e1a 0%, #0b1020 14%, #141c38 30%, #26365f 42%, #26365f 100%);
}
.horizon { z-index: 0; bottom: 500px; }
.plane-far, .plane-mid, .plane-near { height: auto; }
.plane-far img, .plane-mid img, .plane-near img { display: block; width: 100%; height: auto; }
/* The plates scale with the viewport but the copy does not (it caps at
   1280px), so each plane's offset counters its own growth: the crests hold
   at fixed heights above the hero's bottom at every width from 861px up
   (far ~560, mid ~440, near ~318), and the panel's foot is eaten by the
   same amount at 1024, 1440 and 2560. Without this the near ridge swallowed
   the panel from 1600px and the button at 2560. */
.plane-far  { z-index: 0; bottom: calc(560px - 24.4vw); }
/* The mid plane carries a navy body under its image and a fade at its foot:
   at 900 to 1024 the near crest dips below the mid's bottom edge in the
   far-left corner and the plate's straight cut showed against the sky. */
.plane-mid  { z-index: 1; bottom: calc(320px - 20.96vw); left: -5%; right: auto; width: 110%; padding-bottom: 120px;
              background: linear-gradient(#0a0e1a, #0a0e1a) 0 100% / 100% 180px no-repeat; }
.plane-mid::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 120px; height: 70px;
  background: linear-gradient(to bottom, transparent, #0a0e1a); pointer-events: none;
}
/* The near plane is fixed to the copy (data-rate 0). A navy body fills the
   band under its image (padding only, never behind its transparent sky, or
   it would blank the copy it stacks above), and its own floor fade hides
   the plate's cut edge: the shared .planes::after sits at z-index 1 and
   cannot reach a plane at 3. */
/* left -3.6% puts the panel (x 440 to 1000) over the flattest 560px of the
   near crest (measured: 17px of variation there, against 90px elsewhere),
   so the rocks eat the foot evenly and never climb to the words. */
/* The navy body fills the 240px padding band and the plate's last 60px
   (a transparent sliver in the plate's bottom-left corner let the sky
   gradient through as a hard line at 900 to 1024); never higher, or it
   would blank the copy the plane stacks above. */
.plane-near { z-index: 3; bottom: calc(78px - 20.28vw); left: -3.6%; right: auto; width: 120%; padding-bottom: 240px;
              background: linear-gradient(#0a0e1a, #0a0e1a) 0 100% / 100% 300px no-repeat; }
.plane-near::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 240px; height: 140px;
  background: linear-gradient(to bottom, transparent, #0a0e1a); pointer-events: none;
}
/* Small text the glow rim sweeps behind mid-scroll runs at full text
   colour: the dim token bottoms at 2.95:1 on the rim, this holds 6.9:1. */
.hero .support { color: var(--text); }
/* ------------------------------------------------------ 2c. the live sky
   Phase 3b. A muted loop of the dusk sky (Higgsfield clip, cropped to its
   top band, 1440x482, cross-faded into itself) sits behind every plane and
   the glow. It is raised 200px so its cloud band, the only part that
   moves, sits behind and above the H1 (large white type, safe) and it
   fades out above the sub line at every width (fixed-height box, see
   below), so the sub reads on the CSS sky alone: with the clip behind the
   sub, the sub failed AA at 900 and 1280 across the loop.
   The mask fades the clip's visible top edge (under the nav) and its foot
   (into the gradient). Loaded by home.js on desktop with motion allowed
   only; phones and reduced motion keep the CSS sky. */
.hero-sky {
  position: absolute; z-index: 0; top: -200px; left: 0; width: 100%; height: 482px;
  object-fit: cover; pointer-events: none;
  /* fixed height: the visible band is y 104 to ~334 at EVERY width, so its
     foot never reaches the sub. object-fit: cover crops the clip's sides
     until the box is wider than the clip's own 1440px, then crops its top
     and bottom. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 200px, #000 256px, #000 356px, transparent 430px);
          mask-image: linear-gradient(to bottom, transparent 200px, #000 256px, #000 356px, transparent 430px);
  animation: hero-sky-in 1.8s ease-out .15s both;
}
/* The clip settles at 88%. (A sideways pan was tried and removed: it added
   6% of the motion and opened a bare strip down the right edge.) */
@keyframes hero-sky-in { from { opacity: 0; } to { opacity: .88; } }
@media (max-width: 860px), (prefers-reduced-motion: reduce) { .hero-sky { display: none; } }

/* The scene assembles on load: planes fade in, copy blocks rise 64px on a
   spring. Keyframes only, so a no-JS visitor sees the finished page. */
@keyframes hero-plane-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes hero-block-in { from { opacity: 0; transform: translateY(64px); } to { opacity: 1; transform: none; } }
.plane-far, .plane-mid, .plane-near { animation: hero-plane-in 1.2s ease-out both; }
.plane-mid { animation-delay: .12s; }
.plane-near { animation-delay: .24s; }
.hero-inner > * { animation: hero-block-in .9s cubic-bezier(.2, .8, .2, 1) both; }
.hero-inner > :nth-child(2) { animation-delay: 90ms; }
.hero-inner > :nth-child(3) { animation-delay: 180ms; }
@media (prefers-reduced-motion: reduce) {
  .plane-far, .plane-mid, .plane-near, .hero-inner > * { animation: none; }
}
/* The tracker panel: a solid surface (no blur, no glass), the live line
   first so the near ridge buries the tiles and the padding, never the
   words. Replaces the hero's old .proof-line. */
.hero-panel {
  display: inline-flex; flex-direction: column; align-items: flex-start; gap: 12px;
  margin-top: 26px; padding: 16px 20px 24px;
  background: #2c3866; border: 1px solid rgba(236, 240, 250, .16); border-bottom-color: transparent; border-radius: 16px;
  /* the foot is always in the rocks; a visible bottom border showed through a thin gap in the grass at 2560 */
  text-decoration: none; text-align: left; max-width: 560px;
  font: 500 14px/1.5 'JetBrains Mono', monospace; color: rgba(236, 240, 250, .72); letter-spacing: 0.01em;
}
.hero-panel:hover .hero-panel__open, .hero-panel:focus-visible .hero-panel__open { text-decoration: underline; }
.hero-panel__open { color: var(--cyan); white-space: nowrap; }
.hero-panel__tiles { display: flex; flex-wrap: wrap; gap: 4px; max-width: 300px; }
.hero-panel__tiles i { display: block; width: 10px; height: 10px; border-radius: 3px; position: relative; overflow: hidden; }
.hero-panel__tiles i.down { border-width: 1.5px; }
@media (max-width: 860px) {
  /* Phones (390 reference, hero bottom = 0): panel foot 260 up, its words
     from ~314 up; far crest ~470 (behind the CTA), mid ~400, near crest
     median ~280 so the foot is eaten ~20px and the tallest rocks stop
     under the last line of words. Plates at 130/145/170% so each ridge
     keeps its height without turning into a magnified blur. */
  .hero { padding-bottom: 177px; }
  .horizon { bottom: 277px; }
  .plane-far  { width: 130%; left: -15%; right: auto; bottom: 263px; }
  .plane-mid  { width: 145%; left: -22%; bottom: 209px; padding-bottom: 0; background: none; }
  .plane-mid::after { content: none; }
  .plane-near { width: 170%; left: -7.4%; bottom: 0; padding-bottom: 90px;
                background: linear-gradient(#0a0e1a, #0a0e1a) 0 100% / 100% 90px no-repeat; }
  .plane-near::after { bottom: 90px; height: 100px; }
  .hero-panel { padding: 14px 16px 18px; font-size: 13px; }
}

/* --------------------------------------------------------- 3. proof tiles
   Tiles start dim; JS lights them left-to-right (broken tile last) over the
   section's first 40% of scroll-visibility progress. Reduced motion lights
   everything immediately (see home.js). */
.tiles i { opacity: .16; transition: opacity .45s ease; }
.tiles i.is-lit { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .tiles i { transition: none; opacity: 1; } }

/* --------------------------------------------------- 4. desk card + stack
   Three layouts: <=600px static flow; 601-1099px one column (compact desk
   card above a staircase); >=1100px two columns (5/12 desk card, 7/12
   staircase). The staircase's top offsets and z-index order are the same
   at both >=601px widths -- only the desk card's size/position and whether
   it shares the tiles' column changes. */
.stack-zone { position: relative; margin-top: 32px; }
.desk-card {
  background: var(--surface-elev); border-radius: 16px;
  padding: 28px 30px;
}
.desk-card__head {
  font: 500 12px/1.5 'JetBrains Mono', monospace;
  letter-spacing: 0.01em;
  color: var(--text-dim);
}
.desk-card__body {
  margin-top: 8px;
  font-size: 14px; line-height: 1.5; color: var(--text);
}
.desk-card__status {
  display: flex; align-items: center; gap: 9px;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border);
  font: 600 13px/1.3 Inter, sans-serif; color: var(--text);
}
.desk-card__dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--text-dim);
  transition: background-color .3s ease;
}
.desk-card[data-state="mapped"] .desk-card__dot { background: var(--cyan); }
.desk-card[data-state="handled"] .desk-card__dot { background: var(--lime-read); }
.desk-card__tick {
  display: none; color: var(--lime-read); font: 700 13px/1 'JetBrains Mono', monospace;
}
.desk-card[data-state="handled"] .desk-card__tick { display: inline; }
/* "Handled" gets a size step so the payoff reads as the loudest moment in
   its own act. Full-card mode only (>=1100px) -- the compact card (601 to
   1099px, <=600px) has no room to spare without risking overlap. */
@media (min-width: 1100px) {
  .desk-card[data-state="handled"] .desk-card__status {
    font-size: 15px;
    border-top-color: var(--lime-read);
  }
  .desk-card[data-state="handled"] .desk-card__tick { font-size: 15px; }
}

/* A visible status history so the >=1100px left column isn't mostly bare
   card. Not a fourth data point -- repeats the same three locked strings,
   as three dots (not text: text dimmed with opacity failed contrast), each
   an explicit solid colour per state. Desktop only: the compact card has
   no room. The row's own aria-label (home.js) carries the meaning for
   assistive tech; the dots are role=presentation. */
.desk-card__history { display: none; }
@media (min-width: 1100px) {
  .desk-card__history {
    display: flex; align-items: center; gap: 8px;
    margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border);
    list-style: none;
  }
  .hist-step {
    width: 8px; height: 8px; border-radius: 50%; flex: none;
    background: var(--text-faint); /* future: #7c859b */
    transition: background-color .3s ease;
  }
  .hist-step.is-done { background: var(--lime-read); } /* done: #b8e62e */
  .hist-step.is-current { background: var(--text); } /* current: #ecf0fa */
  @media (prefers-reduced-motion: reduce) { .hist-step { transition: none; } }
}

/* Duplicates the .longer paragraph's locked words/href into the desk
   card's own column at >=1100px, where .longer itself is hidden (below) --
   only one instance ever renders. Type size matches .longer. */
.desk-card__longer {
  display: none;
  margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border);
  font-size: 15px; line-height: 1.5; color: var(--text-dim); text-wrap: pretty;
}
.desk-card__longer a { white-space: nowrap; }

/* 601px and up: the four tiles are a sticky staircase. Shared rules only
   (min-height, overflow, hold, form-open release) -- per-breakpoint TOP
   offsets live in the two blocks below, since the offset formula differs
   at 601-1099px (card-relative) vs >=1100px (fixed).
   .tfoot's margin-top:0 (below) lets the footer sit right after the copy
   instead of being pushed down by its own margin-top:auto, so any
   min-height slack lands as bottom padding under the footer rather than a
   gap between the copy and the footer rule. The Day 1 tile's real content
   already exceeds the min-height floor at every width in range, so the
   floor only ever engages on the three shorter tiles.
   Below 1100px, .journey is display:contents (see the >=1100px block for
   its complement), so its four tile children lay out as if they were
   direct children of .stack-zone -- the same flex column, and hence the
   same sticky containing block, as .desk-card-sticky. That shared
   containing block is what lets the card and the tiles release together: a
   sticky element's stuck range is bounded by its containing block's far
   edge, so as long as both share one there is no separate edge for either
   to outlive the other against -- only their own (offset + height) budget
   within it, which .desk-card-sticky's min-height (below) is sized to
   spend no slower than the tiles'. */
@media (max-width: 1099px) {
  .journey { display: contents; }
  /* .stack-zone becomes the real flex column here (>=1100px overrides this
     to display:grid, below, and wins on cascade order) -- it is what makes
     .desk-card-sticky and the four (now-contents-parented) tiles genuine
     flex siblings sharing one box for sticky-containing-block purposes. */
  .stack-zone { display: flex; flex-direction: column; }
}
@media (min-width: 601px) {
  .stack-zone { gap: 24px; }
  .journey > .tile {
    position: sticky;
    min-height: 280px;
    overflow: hidden;
  }
  .journey .tile .tfoot { margin-top: 0; }
  /* A sticky flex item's stuck range is bounded by the flex CONTENT box
     (item boxes + gaps), not the container's trailing padding -- this real
     flex item after tile 4 gives the stack room to stay stuck. >=1100px
     overrides this to 200px, below. */
  .journey-hold { flex: none; height: 300px; }
  .journey.form-open > .tile { position: static; min-height: 0; overflow: visible; }
}
/* The held beat below 600px, so "Handled" and the card get a real pinned
   moment on screen together rather than sharing it only in passing. */
@media (max-width: 600px) {
  .stack-zone { gap: 16px; }
  .journey-hold { display: block; flex: none; height: 200px; }
}

/* >=1100px: the desk card lives in its own grid column (see below), so the
   tiles' sticky offsets are the plain staircase from the top of the stack
   -- there is nothing to clear. The 68px step clears a covered tile's pill
   underline (bottom:-6px off the pill's own border box, 1px tall) with a
   small margin at every width. */
@media (min-width: 1100px) {
  .journey > .tile:nth-child(1) { top: calc(96px + 68px * 0); z-index: 1; }
  .journey > .tile:nth-child(2) { top: calc(96px + 68px * 1); z-index: 2; }
  .journey > .tile:nth-child(3) { top: calc(96px + 68px * 2); z-index: 3; }
  .journey > .tile:nth-child(4) { top: calc(96px + 68px * 3); z-index: 4; }
}

/* 601-1099px: the desk card sits directly above the staircase, sticky at
   84px. Its height varies with viewport width (the mono header wraps at
   some widths), so the tiles' sticky tops are computed from the card's
   REAL measured height (--card-h, home.js) rather than a guessed constant
   -- guarantees the required 24px clearance at every width. Tile 1's own
   offset (n=0, so + 68px*0 makes no difference to it) is what the
   .desk-card-sticky min-height reserve above is sized against.
   Tile 1 carries a negative top margin so the stack renders up into the
   invisible tail of .desk-card-sticky's min-height reserve (below) instead
   of leaving it as a blank gap on the approach. The reserve's own height
   is unchanged (its sticky BUDGET, i.e. how long it stays stuck, still
   matches tile 1's) -- only where tile 1 PAINTS within that reserved flow
   space moves. Value: reserve-height minus card-height minus 16px, i.e.
   (gap + tile-min-height + 32) - 16 = 24 + 280 + 16 = 320px; this cancels
   algebraically to a constant independent of the card's own (variable)
   height, so it needs no --card-h reference. The 16px left un-pulled keeps
   a real 24px between the visible card and tile 1's copy once tile 1
   locks -- not zero, so the two never visually touch. */
@media (min-width: 601px) and (max-width: 1099px) {
  .journey > .tile:nth-child(1) { top: calc(84px + var(--card-h, 90px) + 24px + 68px * 0); z-index: 1; margin-top: -320px; }
  .journey > .tile:nth-child(2) { top: calc(84px + var(--card-h, 90px) + 24px + 68px * 1); z-index: 2; }
  .journey > .tile:nth-child(3) { top: calc(84px + var(--card-h, 90px) + 24px + 68px * 2); z-index: 3; }
  .journey > .tile:nth-child(4) { top: calc(84px + var(--card-h, 90px) + 24px + 68px * 3); z-index: 4; }
}

/* The desk card exists at every width. 601-1099px and <=600px share the
   same compact treatment: header line + status line only, no body. */
@media (max-width: 1099px) {
  /* A hairline (matching .tile/.window elsewhere) so the card reads as a
     floating panel above whatever is behind it, not a rendering glitch. */
  .desk-card { padding: 5px 14px; border: 1px solid var(--border); }
  .desk-card__body { display: none; }
  .desk-card__head { font-size: 10px; line-height: 1.25; }
  .desk-card__status {
    margin-top: 3px; padding-top: 0; border-top: none;
    font-size: 12px; line-height: 1.2;
  }
  .desk-card__tick { font-size: 12px; }
}

/* .desk-card-sticky (wrapping #desk-card, index.html) is the element that
   is actually position:sticky below 1100px -- #desk-card itself just
   carries its background/padding/border-radius. The wrapper is
   pointer-events:none so its invisible reserve (below) never intercepts a
   click or an elementFromPoint read; #desk-card re-enables pointer-events
   for its own real footprint. .form-open releases the wrapper to static so
   it cannot sit over the Day 1 tile once its form has grown the stack into
   normal flow (index.html toggles the class alongside .journey's own). */
@media (max-width: 1099px) {
  .desk-card-sticky { position: sticky; z-index: 50; pointer-events: none; }
  #desk-card { pointer-events: auto; }
  .desk-card-sticky.form-open { position: static; }
}

/* 601-1099px: one column, so the desk card sits directly above the
   staircase as its own full-width row. Sticky at 84px.
   min-height fixes a sticky-release teleport: a sticky element's own stuck
   range only extends as far as (its own box height + its offset) lets it
   inside the shared containing block (.stack-zone, above), so
   #desk-card's real, short content gave it a far LONGER remaining range
   than a tile ever had -- once a tile ran out of range first, a
   static-toggle used to fire to compensate, which is what jumped. This
   min-height instead reserves enough INVISIBLE height (below the visible
   card, never rendered, transparent, pointer-events:none) that
   .desk-card-sticky's own budget is spent no slower than tile 1's -- the
   tile with the smallest offset and hence the longest remaining range of
   the four (a taller Day 1 tile only shortens its OWN range further, so
   tile 1 is always the binding case). +32px is a small margin on top of
   the exact figure. The one consequence: .stack-zone's natural
   (pre-scroll) height grows by the same amount, so the act needs a little
   more scroll to first reach tile 1's lock point. This figure must track
   the tile min-height above -- keep the two in sync if either changes. */
@media (min-width: 601px) and (max-width: 1099px) {
  .desk-card-sticky { top: 84px; min-height: calc(var(--card-h, 90px) + 24px + 280px + 32px); }
}

/* >=1100px: two columns. The desk card gets its own column (5/12), so it
   can never cover -- or be covered by -- a tile: full content (body + the
   history dots + the "want the whole firm" line), and a lower sticky
   offset since there's no ceiling to respect here. .desk-card-sticky is
   display:contents here (the complement of .journey's below 1100px) so
   #desk-card itself is the direct grid child and keeps its own sticky
   behaviour. */
@media (min-width: 1100px) {
  .stack-zone { display: grid; grid-template-columns: 5fr 7fr; gap: 32px; align-items: start; }
  .desk-card-sticky { display: contents; }
  .desk-card { position: sticky; top: 120px; z-index: 50; }
  .journey { margin-top: 0; }
  .desk-card__longer { display: block; }
  .longer { display: none; }
  /* 601-1099px keeps 300px (above) -- the desk card's own column there is
     the compact strip, not the full card, so there's no equivalent bare
     navy to close here. */
  .journey-hold { height: 200px; }
}

/* <=600px: the tiles are sticky too, pinned strictly BELOW the card
   (measured height + 16px + a staircase), so the two can never share
   screen pixels. The .stack-zone gap (above) plus the card's own reserved
   flow height give it its band before any of this engages.
   .desk-card-sticky carries the sticky top and the min-height reserve
   (same mechanism as 601-1099px, using this breakpoint's own 16px gap and
   300px tile min-height); #desk-card and the tiles are unchanged. */
@media (max-width: 600px) {
  .stack-zone { margin-top: 24px; }
  .desk-card-sticky { top: 72px; min-height: calc(var(--card-h, 60px) + 16px + 300px + 32px); }
  .journey > .tile {
    position: sticky;
    min-height: 300px;
    overflow: hidden;
  }
  /* Same negative-margin mechanism as the 601-1099px block above, this
     breakpoint's own numbers: (16 + 300 + 32) - 16 = 332px.
     Lock clearance here is 17px, short of the 24px clearance at wider
     widths -- the negative margin cannot move this number (once sticky, an
     element's position is its own `top` offset only, margin-independent);
     it is set entirely by .stack-zone's own 16px gap at this breakpoint.
     Widening it means raising this breakpoint's base gap to 24px to match
     >=601px, which shifts all four tiles' offsets. */
  .journey > .tile:nth-child(1) { top: calc(72px + var(--card-h, 60px) + 16px + 68px * 0); z-index: 1; margin-top: -332px; }
  .journey > .tile:nth-child(2) { top: calc(72px + var(--card-h, 60px) + 16px + 68px * 1); z-index: 2; }
  .journey > .tile:nth-child(3) { top: calc(72px + var(--card-h, 60px) + 16px + 68px * 2); z-index: 3; }
  .journey > .tile:nth-child(4) { top: calc(72px + var(--card-h, 60px) + 16px + 68px * 3); z-index: 4; }
  .journey.form-open > .tile { position: static; min-height: 0; overflow: visible; }
}

/* ------------------------------------------------- 5. after day 10 (the coda)
   One short paragraph under the Day 10 tile, above the section's outline
   button: what happens after the first job, and where Headroom sits (the
   consequence, not the lead). Ian's ruling, 5 Sep 2026. */
.journey-act .after {
  max-width: 640px; margin: 0 auto 24px; text-align: center;
  color: var(--text-dim); font-size: 15.5px; line-height: 1.6; text-wrap: pretty;
}
.journey-act .after a { white-space: nowrap; }
@media (max-width: 600px) { .journey-act .after { font-size: 14.5px; margin-bottom: 20px; } }
