/* acts-world-vision — THE WORLD / THE INSPIRATION / THE VISION
   Ported from `campaign-hybrid` onto style-lab/tokens.css.

   What is local here: grid-column assignments, section rhythm, per-section
   decorative geometry (glow position, collage rotations, plate aspect ratios),
   image grades, and scroll machinery. Nothing else.

   What is NOT local here: no hex, no font names, no absolute type sizes, no
   easing curves. Every colour is a token or a color-mix derived from one; every
   type size is a token or a token multiple; every curve is var(--ease).

   TOKEN GAPS are marked `GAP n:` inline and listed in the handback report.
*/

/* GAP 1 CLOSED — the contract now carries --duration-ui. */

main{position:relative;z-index:1}
section{position:relative}
.act{padding-block:var(--space-section)}
/* campaign-hybrid opened each of these three acts with a longer top rest than
   its generic act padding — kept as a multiple of the section-space token. */
/* .world dropped from this rule 2026-08-01: the "Two Beats" layout carries its
   own opening rest and was measured at 1072px without it. Leaving it here added
   ~1.7x --space-section back on top and quietly undid part of the gap fix. */
.insp{padding-top:calc(var(--space-section) * 1.7)}
.vision{padding-top:calc(var(--space-section) * 1.85)}

/* ---------- atmosphere: one melting grade, tinted per section ---------- */
.atmosphere{position:fixed;inset:0;z-index:-1;pointer-events:none;overflow:hidden}
.grade-base{
  position:absolute;inset:0;background-color:var(--black);
  transition:background-color var(--reveal-duration) linear;
}
/* GAP 2 CLOSED — the contract now carries --cool / --cool-deep / --warm, so the
   four HUE washes are back (Matthew's call 2026-07-31: "I like the atmosphere
   from the campaign hybrid"). Cool / warm / cool / warm, alternating corners,
   exactly campaign-hybrid's arrangement — but sourced from tokens, not hexes.

   The --drift-on gate is GONE (seam-underblack, 2026-07-31). It existed because
   .hero-stage painted an opaque var(--black) over this layer while the page
   below it did not, so the drift cut a horizontal line across the frame exactly
   where the stage's bottom edge passed. The stage is transparent now and the
   hero feathers into this same layer, so the drift is continuous across that
   boundary and simply runs from the first frame.

   BUT the gate is back, for a DIFFERENT reason (measured 2026-08-01). The seam's
   black patch is a transparent section carrying data-tint #060707 — the black IS
   this atmosphere. With the drift running, it washes that patch: measured
   rgb(16–20,17–22,15–22) against a target of rgb(6,7,7), roughly +10 to +14
   levels and up to +4 green-over-red. That matters here more than anywhere else
   on the page, because the black→teal river crossfade IS the seam's whole
   gesture — a patch that starts out teal-washed gives the dissolve less distance
   to travel and it reads as less of a change.
   So --drift-on is no longer an edge-mismatch workaround; it is a colour-integrity
   control. It holds the patch at true black, then ramps the drift in WITH The Film
   so hue and tint arrive together. Defaults to 1 for any page with no hero. */
.grade-drift{
  position:absolute;inset:0;
  opacity:var(--drift-on,1);
  background:
    radial-gradient(120vw 90vh at 85% -10%, color-mix(in srgb,var(--cool) 50%,transparent), transparent 60%),
    radial-gradient(100vw 80vh at -10% 30%, color-mix(in srgb,var(--warm) 48%,transparent), transparent 55%),
    radial-gradient(110vw 90vh at 70% 70%, color-mix(in srgb,var(--cool-deep) 48%,transparent), transparent 60%),
    radial-gradient(120vw 100vh at 10% 105%, color-mix(in srgb,var(--warm) 50%,transparent), transparent 60%);
  animation:grade-drift 36s var(--ease) infinite alternate;
}
@keyframes grade-drift{
  from{background-position:0% 0%,0% 0%,0% 0%,0% 0%}
  to{background-position:6% 8%,-5% -6%,-7% 5%,5% -7%}
}
.grain{
  position:absolute;inset:-100px;opacity:var(--grain-opacity);pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
  animation:grain 7s steps(8) infinite;
}
@keyframes grain{
  0%,100%{transform:translate(0,0)}12%{transform:translate(-30px,20px)}
  25%{transform:translate(20px,-35px)}37%{transform:translate(-25px,-15px)}
  50%{transform:translate(30px,25px)}62%{transform:translate(-15px,30px)}
  75%{transform:translate(25px,-20px)}87%{transform:translate(-30px,-25px)}
}

/* per-act glow: the act's own data-tint, lifted toward --ink. part.js copies
   data-tint onto --tint so the hex stays in exactly ONE place (the attribute).

   The vertical radius is `min(60vh, 62%)`, not a flat 60vh. `inset:0` clips this
   ellipse to the section box, so a section shorter than the viewport had its
   falloff cut mid-gradient and the cut read as a hard rule straight across the
   page at the section's edge — worst on The Record once the static framings hug
   their content (317px on `line`, 467px on `trio`). At 62% of the box the edge
   always sits at ~81% along the ray, past the `transparent 70%` stop, so the
   glow has faded out before the clip can show. Sections taller than ~1.03x the
   viewport still take 60vh and are unchanged. */
.act::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(90vw min(60vh, 62%) at var(--glow-x,50%) 50%,
    color-mix(in srgb, color-mix(in srgb, var(--tint,var(--bg)) 90%, var(--ink)) 28%, transparent),
    transparent 70%);
}
.world{--glow-x:80%}
.insp{--glow-x:18%}
.vision{--glow-x:15%}

/* ---------- shared ---------- */
h2{text-wrap:balance}

/* the token .label supplies font, size, tracking, colour and rhythm.
   The gold rule beside it is this design's decorative geometry, so it lives here. */
.label{display:flex;align-items:center;gap:1rem}
.label::before{content:"";width:2.4rem;height:1px;background:var(--accent)}

/* campaign plate — image treatment + parallax drift */
.plate{
  --drift:0px;--tilt:0deg;
  position:relative;overflow:hidden;margin:0;
  border:1px solid var(--line);background:var(--black);
  box-shadow:0 2rem 6rem color-mix(in srgb,var(--black) 35%,transparent);
  transform:translate3d(0,var(--drift),0) rotate(var(--tilt));
}
.plate img{width:100%;height:100%;object-fit:cover;
  transition:filter var(--reveal-duration) var(--ease)}
/* micro set 2026-08-13, retuned same day: the film plate's hover moved into
   the page's inline block — its transform is owned there by the scroll-lift
   (.film__plate.emerge), which was silently overriding the translateY here
   (same specificity, later in cascade). Now matched to the bio portrait's
   character: -3px + shadow only, no brighten, no caption ride. */
.plate figcaption{transition:transform var(--duration-ui) var(--ease)}
.plate::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,transparent 58%,color-mix(in srgb,var(--black) 62%,transparent));
}
.plate figcaption{
  position:absolute;z-index:2;right:.9rem;bottom:.8rem;left:.9rem;
  font-family:var(--font-display);
  font-size:calc(var(--text-label) * .9);
  letter-spacing:var(--track-caption); /* GAP 3 CLOSED — captions track tighter than eyebrows */
  text-transform:uppercase;
  color:color-mix(in srgb,var(--ink) 70%,transparent);
}

/* ---------- 3 · THE WORLD — TESSELLATE (Kimi v12, adopted 2026-08-14) ----
   Two columns that close flush: left 52.37% (street over desk), right
   43.63% (mirrors over morgue), every gap 4% of the container — both
   columns then total the same height, so the block is a perfect rectangle
   at any viewport. The stack margins are 4%-of-container re-expressed as
   a % of each column (margins resolve against their own containing block).
   THE MATH IS ASPECT-BOUND: solved for exactly 1871/800 + 1200/800 left,
   1066/800 + 1421/800 right — swap a plate and the widths must re-solve.
   Native ratios, no crops (img height:auto overrides the .plate cover
   default); no data-depth — drift would open the flush seams. The two
   paragraphs close beneath as twin columns matched to the image columns. */
.wt__head{margin-bottom:clamp(2.5rem,5.5vh,4rem);max-width:62%}
.wt__head .label{margin-bottom:1.6rem}
.wt__head h2{font-size:calc(var(--text-h2) * 1.4)}
.wt__block{display:flex;gap:4%}
.wt__col--l{width:52.37%}
.wt__col--r{width:43.63%}
.wt__col--l .plate + .plate{margin-top:7.638%}   /* 4% of W, as % of left col  */
.wt__col--r .plate + .plate{margin-top:9.168%}   /* 4% of W, as % of right col */
.wt .plate img{width:100%;height:auto;display:block}
.wt__texts{
  display:flex;gap:4%;margin-top:4%;
  border-top:1px solid var(--line);padding-top:clamp(1.5rem,3.5vh,2.5rem);
}
.wt__texts .prose:first-child{width:52.37%}
.wt__texts .prose:last-child{width:43.63%}
.wt__texts .prose p{max-width:none}

@media (max-width:900px){
  .wt__head{max-width:none}
  .wt__block,.wt__texts{flex-direction:column;gap:2.5rem}
  .wt__col--l,.wt__col--r,.wt__texts .prose:first-child,.wt__texts .prose:last-child{width:100%}
  .wt__col--l .plate + .plate,.wt__col--r .plate + .plate{margin-top:2.5rem}
  .wt__texts{margin-top:2.5rem}
}

/* ---------- 4 · THE INSPIRATION — the archive collage ---------- */
.insp-copy{grid-column:1/6;align-self:center}
.insp-copy h2{font-size:calc(var(--text-h2) * 1.15);margin-bottom:2rem}
.insp-copy h2 em{
  font-family:var(--font-body);font-style:italic;font-weight:500;
  text-transform:none;letter-spacing:0;color:var(--accent);
}
.read-more{
  display:inline-block;margin-top:2.2rem;
  font-family:var(--font-display);font-size:var(--text-label);font-weight:600;
  letter-spacing:var(--track-label);text-transform:uppercase;
  border:1px solid var(--ink-dim);padding:.8rem 1.6rem;
  transition:background var(--duration-ui) var(--ease),
             color var(--duration-ui) var(--ease),
             border-color var(--duration-ui) var(--ease);
}
.read-more:hover{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.insp-note{margin-top:1.8rem;font-size:calc(var(--text-body) * .85);color:var(--ink-faint);font-style:italic}

.collage{grid-column:6/13;position:relative;padding-bottom:4.5rem}
.collage .plate{box-shadow:0 24px 60px color-mix(in srgb,var(--black) 50%,transparent)}
/* collage grayscale grade removed 2026-08-14 — no filters on images (Matthew) */
.collage .arc-beach{width:100%;--tilt:-.6deg}
.collage .arc-tamam{width:56%;margin:-9% 2% 0 auto;--tilt:1.6deg;z-index:2}
.collage .arc-record{width:44%;margin:-14% auto 0 6%;--tilt:-2.2deg;z-index:3}
.collage .arc-tamam,.collage .arc-record{position:relative}

/* ---------- 5 · THE VISION — plate left, statement right ---------- */
/* ---------- 5 · THE VISION — V1 fog statement · candidate A (faithful) ----
   Full-bleed flipped fog, walker held left, the statement laid over the open
   field; the scrim darkens only the text zone. Drift slow enough to read as
   weather. Projector plate retired in this section; the feed ring follows
   on flat tint. */
.vision--fog{padding:0 0 var(--space-section)}
.fogfield{position:relative;min-height:112vh;overflow:hidden;display:flex;align-items:center}
.fogbg{
  position:absolute;inset:0;overflow:hidden;
  /* the fog dissolves into the section instead of hard-cutting at its
     edges (2026-08-13) — the hero-feather pattern. The 34s drift lives on
     the img so the feather never rides it; the wrapper's own entrance
     scale (1.07 -> none, once, under 16px of blur) moves the feather ~7%
     imperceptibly and lands it exact. Only alpha is read; the opaque stop
     uses a token rather than inventing a literal. */
  -webkit-mask-image:linear-gradient(180deg,
    transparent 0, var(--black) 16%, var(--black) 80%, transparent 100%);
  mask-image:linear-gradient(180deg,
    transparent 0, var(--black) 16%, var(--black) 80%, transparent 100%);
}
/* entrance (2026-08-13): the plate emerges like the fog itself — a slow
   blur-clear and scale-settle, much longer than the token reveal. Rides the
   same IO .is-in hook, so reduced motion collapses it to an instant appear
   and no-JS shows it via the standard guard. The drift animation lives on
   the img, this transform on the wrapper — they never fight. */
.fogbg.reveal{
  opacity:0;transform:scale(1.07);filter:blur(16px);
  transition:opacity 1.6s var(--ease),
             filter 2.1s var(--ease),
             transform 2.6s var(--ease-expo);
}
.fogbg.reveal.is-in{opacity:1;transform:none;filter:blur(0)}
.fogbg img{
  width:100%;height:100%;object-fit:cover;display:block;
  /* focal anchor (2026-08-13): the walking man's centre sits at ~20% of the
     plate. Centre-cropping cost him first at medium widths — pinning the
     focal point to him makes the crop eat the open fog on the right ~4x
     faster than the wall strip on his left, holds him left-of-centre at
     every width, and only clips him when nothing else remains. The ≤900px
     rule below (34%, stacked layout) is a different composition and keeps
     its own anchor. */
  object-position:20% center;
  animation:fogdrift 34s ease-in-out infinite alternate;will-change:transform;
}
@keyframes fogdrift{from{transform:scale(1.04) translateX(0)}to{transform:scale(1.12) translateX(1.8%)}}
.fogscrim{
  position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(270deg,rgba(10,12,11,.68) 26%,rgba(10,12,11,.22) 52%,transparent 72%);
}
/* Top/bottom blend into the act tint (acts.js mirrors data-tint onto --tint).
   The plate's top strip is ~4x brighter than the tint, so the fade needs a
   long eased runway — 32vh, multi-stop — or the edge reads as a pasted
   rectangle. The tint itself is a deep olive from the plate's own family
   (#1a1d15, sampled from its bottom strip) so the surrounding page and the
   evidence board below share the fog's palette. */
.fogfield::before,.fogfield::after{
  content:"";position:absolute;left:0;right:0;height:32vh;z-index:2;pointer-events:none;
}
.fogfield::before{top:0;background:linear-gradient(180deg,
  var(--tint),
  color-mix(in srgb, var(--tint) 78%, transparent) 30%,
  color-mix(in srgb, var(--tint) 38%, transparent) 62%,
  transparent)}
.fogfield::after{bottom:0;background:linear-gradient(0deg,
  var(--tint),
  color-mix(in srgb, var(--tint) 78%, transparent) 30%,
  color-mix(in srgb, var(--tint) 38%, transparent) 62%,
  transparent)}
.fog-stage{
  position:relative;z-index:3;width:var(--shell-width);margin:0 auto;
  display:grid;grid-template-columns:minmax(0,5fr) minmax(0,6fr);
  gap:clamp(2rem,5vw,5rem);padding:14vh 0;
}
.fog-ghost{
  grid-column:1;align-self:end;margin:0;
  font-family:var(--font-display);font-size:var(--text-label);
  letter-spacing:.3em;text-transform:uppercase;
  color:color-mix(in srgb,var(--ink) 50%,transparent);
}
.fog-stmt{grid-column:2;justify-self:end}
.fog-stmt h2{font-size:var(--text-h2);margin-bottom:1.8rem}
.fog-stmt h2 em{
  font-family:var(--font-body);font-style:italic;font-weight:500;
  text-transform:none;letter-spacing:0;color:var(--accent);font-size:.94em;
}
/* Why this film // why now — Scott's answer to the reserved question, under
   the pull quote on the same fog field. */
.fog-why{margin-top:2.6rem;border-top:1px solid var(--line);padding-top:1.6rem}
.fog-why .label{margin-bottom:1rem}
.fog-stmt .prose{max-width:34rem}
@media (max-width:900px){
  .fogfield{min-height:0}
  .fogbg img{object-position:34% center}
  .fogscrim{background:linear-gradient(180deg,rgba(10,12,11,.3),rgba(10,12,11,.8) 64%)}
  .fog-stage{grid-template-columns:1fr;padding:15vh 0 11vh}
  .fog-ghost{order:2;margin-top:2.4rem}
  .fog-stmt{grid-column:1}
}
/* max-width, not font-size: Bitter is wider than campaign-hybrid's Piazzolla,
   which pushed this from 2 lines to 3 — and at 3 lines it stops reading as a
   quote and starts reading as a paragraph. Capping the measure restores the
   two-line break without touching the contract's body font.
   Measured, not guessed: at *1.2 the quote is 3 lines in its 537px column;
   *1.1 is the largest step that lands on 2. Still 10% above body size, so it
   keeps its emphasis. (A max-width cap was tried first and was wrong — 30ch
   computed to 410px here, narrower than the column, which pinned the box and
   masked every other adjustment.) */
.pull{
  margin-top:1.6rem;font-style:italic;
  font-size:calc(var(--text-body) * 1.1);line-height:1.5;color:var(--ink);
  border-left:2px solid var(--accent);padding-left:1.4rem;
}
.copy-reserve{margin-top:2rem;padding:1rem 0;border-block:1px solid var(--line)}
.copy-reserve span{
  font-family:var(--font-display);font-size:calc(var(--text-label) * .85);
  letter-spacing:var(--track-label);text-transform:uppercase;color:var(--ink-faint);
}
.copy-reserve p{margin-top:.45rem;color:var(--ink-dim);font-size:calc(var(--text-body) * .85);font-style:italic}

/* ---------- scaffold (NOT deliverable — neighbour stand-ins) ---------- */
.scaffold{padding-block:calc(var(--space-section) * 1.6)}
.scaffold p{
  font-family:var(--font-display);font-size:var(--text-label);
  letter-spacing:var(--track-label);text-transform:uppercase;color:var(--ink-faint);
  border-block:1px solid var(--line);padding:1rem 0;margin:0;
}

/* ---------- responsive ---------- */
@media (max-width:900px){
  .insp-copy,.collage{grid-column:1/13}
  .collage{padding-bottom:0;margin-top:3rem}
  .collage .arc-beach,.collage .arc-tamam,.collage .arc-record{
    width:100%;margin:0 0 1.4rem;--tilt:0deg;
  }
}

/* Reduced motion is handled by tokens.css, which collapses DURATION rather than
   killing animation — do not add `animation:none` here. Only the JS-driven
   parallax needs neutralising, and part.js does that. */
@media (prefers-reduced-motion:reduce){
  .plate{--drift:0px}
}

/* ===== BOARD — film's-feed evidence board (end of #vision, before
   Development). The four @project_bluebird_film stills (script stack ·
   Rubaiyat · pop-art head · text-free poster) pinned and taped like an
   investigator's wall. Ported 2026-08-11 from transition 07 (On the Wall)
   in the client's transitions reference — it replaces the 3D reel ring
   ("over the top"), which git history keeps. Zero JS. ===== */
.feed{margin-top:clamp(4rem,9vh,7rem);text-align:center}
.feed-label{margin:0}
.feed-title{
  margin:1.15rem auto 0;max-width:30ch;
  font-family:var(--font-display);font-size:var(--text-h3);font-weight:500;
  letter-spacing:.05em;text-transform:uppercase;color:var(--ink);
}
.feed-title a{
  color:inherit;text-decoration:none;white-space:nowrap;
  border-bottom:1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  transition:color var(--reveal-duration) var(--ease);
}
.feed-title a:hover,
.feed-title a:focus-visible{color:var(--accent)}
/* THE EVIDENCE BOARD (On the Wall). Four prints pinned and taped at slight
   rotations — never 0; imperfection is the mechanism. Hover straightens a
   print to be read. Static layout, zero JS. */
.board{
  position:relative;height:66vh;min-height:440px;max-width:64rem;
  margin-top:clamp(1.5rem,4vh,3rem);
}
.board figure{
  position:absolute;margin:0;background:var(--black);padding:9px 9px 30px;
  box-shadow:0 18px 50px rgba(0,0,0,.6);border:1px solid var(--line);
  transition:transform .55s var(--ease-expo),box-shadow .55s var(--ease-expo);
}
.board figure .pb{position:absolute;inset:9px 9px 30px;overflow:hidden}
.board figure img{
  width:100%;height:100%;object-fit:cover;display:block;
  -webkit-user-drag:none;user-select:none;
}
.board figcaption{
  position:absolute;left:10px;right:10px;bottom:7px;
  font-family:var(--font-display);font-size:var(--text-label);font-weight:500;
  letter-spacing:var(--track-caption);text-transform:uppercase;color:var(--ink-dim);
}
.board .tape{
  position:absolute;top:-11px;left:50%;transform:translateX(-50%) rotate(-2deg);
  width:78px;height:22px;background:color-mix(in srgb, var(--ink) 16%, transparent);
  border-left:1px dashed color-mix(in srgb, var(--ink) 25%, transparent);
  border-right:1px dashed color-mix(in srgb, var(--ink) 25%, transparent);
}
.board .pinhead{
  position:absolute;top:9px;right:9px;width:9px;height:9px;border-radius:50%;
  background:radial-gradient(circle at 35% 30%,
    color-mix(in srgb, var(--accent) 55%, white),
    var(--accent) 55%,
    color-mix(in srgb, var(--accent) 45%, black));
  box-shadow:0 2px 5px rgba(0,0,0,.7);
}
.board .f1{width:44%;height:62%;left:2%;top:6%;transform:rotate(-3.5deg);z-index:2}
.board .f2{width:38%;height:52%;right:6%;top:2%;transform:rotate(2.6deg);z-index:1}
.board .f3{width:34%;height:44%;left:26%;bottom:2%;transform:rotate(1.8deg);z-index:3}
.board .f4{width:30%;height:40%;right:2%;bottom:8%;transform:rotate(-2.4deg);z-index:2}
.board figure:hover{transform:rotate(0) scale(1.03);z-index:9;box-shadow:0 34px 80px rgba(0,0,0,.75)}
@media (max-width:900px){
  /* The collage folds to a 2x2 grid of prints; the slight rotations stay.
     900px, not 760 — one compact system per page (audit D7). */
  .board{
    height:auto;min-height:0;
    display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
    gap:clamp(1.2rem,4vw,2rem);padding-block:.5rem 1rem;
  }
  .board figure{position:relative;inset:auto;width:auto;height:auto}
  .board .f1,.board .f2,.board .f3,.board .f4{width:auto;height:auto;left:auto;right:auto;top:auto;bottom:auto}
  .board figure .pb{position:relative;inset:auto;margin:0 0 21px}
  .board figure img{height:auto;aspect-ratio:1}
}
@media (prefers-reduced-motion:reduce){
  .board figure{transition:none}
  .board figure:hover{transform:rotate(0)} /* still straightens, no scale */
}
