/* ==========================================================
   /free-build (VLP-1233) : the Free Build page layer only.

   WHAT THIS FILE OWNS
   Everything that is this page and no other: the hero and the console's lift
   and lean, the terms line under the fold control, the manifesto stage and its
   five stacked sentences, the three product crops this page authors out of the
   /product/ kit, the four principles laid out as a grid, the closer, this
   page's act seams by id, and the no-JS, static and reduced-motion
   restatements for exactly those moves.

   WHAT IT DEPENDS ON
   /folio.css, loaded first: tokens, reset, base type, the section formula,
   buttons, the nav, frames and the window device, the grids, the FAQ, the
   footer, the modal, the sticky CTA. This file adds to that layer and never
   restates it.

   LOAD ORDER
   critical fallback <style> -> /folio.css -> /free-build-v2.css ->
   /scrollcraft.css. The engine sheet is last so the page can override its
   tokens; folio.css restates every engine base rule it changes at a higher
   specificity.

   CANDIDATE FOR PROMOTION INTO folio.css
   .fbv-rules-2 (a .rules list laid out as a two-column grid) is page-scoped
   here because the shared layer has only the column form. capabilities-v2.css
   carries the same idea as .cap-rules-2 / .cap-rules-3; a third page wanting
   it is the moment to promote one of them. See docs/free-build-v2.md.
   ========================================================== */

/* ---------------- hero ----------------
   Ink only, lifted by one very faint radial so the field is not a flat wall.
   No photograph: the product is the picture. */
.hero{position:relative;min-height:100svh}
/* The act is only a length. Everything the reader sees lives on the stage, which is what the
   engine sticks; when the hero is not pinned (phones, coarse pointers, reduced motion) the same
   block simply flows, because every rule here is on the stage rather than on the act. */
.hero-stage{display:flex;flex-direction:column;background:var(--ink);
  padding:calc(var(--nav-h) + var(--nav-inset) + 34px) 0 0}
.hero-stage::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:0;
  background:radial-gradient(ellipse at 50% 0%,rgba(244,241,234,.06),transparent 60%)}
.hero-in{position:relative;z-index:1;width:100%;min-width:0;display:flex;flex-direction:column;align-items:center;text-align:center}
/* margin-inline:auto on a flex item cancels the cross-axis stretch, so a .wrap inside a stage
   shrinks to its content. The hero figure holds only absolutely positioned media and would
   collapse to nothing without this. */
.hero-stage>.stage.wrap,.offer-stage>.wrap,.closer-stage>.wrap{width:100%}
/* Line masks reserve descender room. Instrument Serif hangs its g, y and p a long way below the
   baseline, and the engine's default 0.14em shears them at this line-height. */
.hero h1 .sc-split--line{padding-bottom:.26em;margin-bottom:-.26em}
.hero h1{margin-top:22px}
.hero-lead{margin-top:20px;max-width:52ch;font-size:clamp(16px,1.4vw,18.5px)}
.hero-cta{margin-top:28px;display:flex;align-items:center;gap:22px;flex-wrap:wrap;justify-content:center}
/* The offer in three clauses, under the control it qualifies. Mono and dim: it is the terms
   line, not a second claim, and the only amber on this page is the manifesto's $0. */
.fbv-terms{margin:22px 0 4px;text-align:center;letter-spacing:.12em;text-transform:uppercase;font-size:13.5px;color:var(--text)}
/* The boundary line. Mono, quiet, under the control: it answers the objection without arguing
   with the offer line above the CTA. */
.fbv-note{margin:20px 0 0;text-align:center;letter-spacing:.13em;text-transform:uppercase}

/* ---------------- this page's seams ----------------
   Stated by id so the value survives an act being demoted to flow on a phone or under reduce.
   #product's heading is what the emptied manifesto stage hands over to as it slides off, and the
   last question has to hand over to the close rather than to half a screen of ground. */
#product{padding-top:calc(var(--sec-pad) * .5)}
#faq{padding-bottom:calc(var(--sec-pad) * .62)}
/* Two pinned acts run back to back at the top of this page, so the statement after the trio is
   the page's first real breather and takes the full rhythm folio.css gives it. */

/* ---------------- the hero frame ---------------- */
.frame-hero{aspect-ratio:16/10}
.frame-hero .poster{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.frame-hero .clip{position:relative;z-index:1;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity 600ms var(--ease-out);background:var(--surface)}
.frame-hero .clip.is-playing,html.no-js .frame-hero .clip{opacity:1}

/* ---------------- the product crops ----------------
   The trio is cut out of the /product/ kit rather than out of /free-build/, so the shared
   .shot-search / .shot-detail / .shot-library numbers do not apply: those are tuned to a
   different set of captures at a different framing. Each crop below is chosen so the sentence
   above it is the thing the reader can actually see, and each authors a second crop so the
   window has somewhere to travel. */
.fbv-shot-search{aspect-ratio:4/3;--zoom:200%;--sx:-31%;--sy:-22%;--w-from:0px;--w-to:-88px}
/* The detail panel is the right third of its own screen, so the crop travels most of a picture
   width to reach it. At a gentler zoom the panel came out a fifth of the card wide with the
   search results it sits over filling the rest, and the card read as "an app" rather than as
   "a clip playing while its drive is offline". */
.fbv-shot-detail{aspect-ratio:4/3;--zoom:240%;--sx:-152%;--sy:-20%;--w-from:0px;--w-to:-96px}
/* The library screen is drawn in its top 42% and its left rail, and the rest of the picture is
   empty ground: a centred crop of it photographs as a card that is half nothing, which is the
   same fault capabilities-v2 found on the Relay screen. So this crop is the rail and the first
   volume tile rather than the volume grid, which is also the better picture for the sentence
   above it: the rail names all four volumes, with their kind and their online state. */
.fbv-shot-library{aspect-ratio:4/3;--zoom:242%;--sx:0;--sy:-32%;--w-from:0px;--w-to:-74px}

/* ---------------- the four principles ----------------
   The shared .rules is a column, written for the text half of a pair. Four principles down one
   column is a screen and a half of list, so this page lays the same markup out as two columns of
   two under a centred statement. The list keeps its own top hairline, so the second cell, which
   is the top of the right-hand column, must not draw a second one under it. */
.fbv-rules-2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:56px;
  flex:none;max-width:1000px;margin:52px auto 0}
.fbv-rules-2 li:nth-child(2){border-top:0}
.fbv-rules-2 li:last-child{padding-bottom:22px}
.fbv-rules-2 li p{max-width:44ch}

/* ---------------- the manifesto: the peak ---------------- */
/* Five sentences, centred in one column, each separated by a hairline that spans it. The column
   is narrower than the section so the lines break where the sentence breaks. */
.sec-offer .lines{margin-top:52px;max-width:860px;margin-left:auto;margin-right:auto}
.line{font-size:clamp(22px,2.9vw,40px);line-height:1.18;letter-spacing:-.025em;font-weight:500;
  color:var(--text);text-align:center;text-wrap:balance;padding:24px 0;
  border-top:1px solid var(--line)}
.line:first-child{border-top:0;padding-top:0}
.offer-stage{display:flex;flex-direction:column;justify-content:center;background:var(--ink);
  padding:calc(var(--nav-h) + var(--nav-inset) + 26px) 0 46px}
.offer-stage .act-mark{margin-bottom:20px}
.offer-stage .head .display{font-size:clamp(30px,3.6vw,50px)}
.offer-stage .lines{margin-top:34px}
.offer-stage .line{font-size:clamp(21px,2.5vw,34px)}
.offer-stage .inline-cta{margin-top:32px}
/* Stacked in one box, not down a column. Five sentences crossing over in the same place is the
   beat; five rows lit one at a time marches the reader's eye down the screen and leaves a hole
   above whichever one is lit. The hairline column is the static layout, and it comes back
   whenever the stage is not actually pinned (no script, ?static=1, a capture tool). */
.offer-stage.sc-stage .lines{position:relative;min-height:clamp(112px,19vh,180px)}
/* Centred with the `translate` property, not with flex and not with `transform`. Flex turns each
   run of text either side of <b class="price"> or <span class="nb"> into its own anonymous item
   and the sentence comes apart; transform is what the engine writes on a cue element. */
.offer-stage.sc-stage .line{position:absolute;left:0;right:0;top:50%;translate:0 -50%;
  margin:0;padding:0;border-top:0}

/* ---------------- closer ----------------
   The hero's formula again, so the last screen repeats the first. */
/* clip, not hidden: an overflow:hidden ancestor turns into the scrollport a sticky stage
   measures itself against, and the pin stops engaging. */
.sec-closer{border-top:1px solid var(--line);padding-bottom:0;overflow:clip}
.closer-in{max-width:820px;margin:0 auto;text-align:center;display:flex;flex-direction:column;align-items:center}
.closer-in .display{margin-top:22px}
.closer-in .lead{margin-top:20px;text-align:center;margin-left:auto;margin-right:auto}
.closer-in .inline-cta{margin-top:32px}
/* The closer carries the hero's own frame, held still, so the exit repeats the entrance. The
   fade only takes the bottom third: the search results above it have to stay readable. */
.stage-closer{position:relative;perspective:1500px;max-width:900px;margin:64px auto 0}
/* The window's bottom edge is the page's edge: the footer's own hairline sits on it. A gradient
   that is still translucent when it gets there cuts the console through whatever row happens to be
   under it, and a screenshot cut mid-row reads as a rendering fault rather than as a crop. So the
   fade reaches solid ground well before the edge and holds it: whatever the console is showing,
   the cut lands on ink. */
.stage-closer::after{content:"";position:absolute;left:0;right:0;bottom:0;height:38%;z-index:3;
  pointer-events:none;background:linear-gradient(180deg,transparent,rgba(11,15,20,.62) 42%,var(--ink) 91%,var(--ink))}
.closer-stage{display:flex;flex-direction:column;background:var(--ink);
  padding:calc(var(--nav-h) + var(--nav-inset) + 24px) 0 0}
.closer-stage .closer-in .display{font-size:clamp(30px,3.4vw,50px)}
.closer-stage .stage-closer{margin-top:auto;max-width:780px;padding-top:34px}
/* The frame is cut by the bottom of the stage rather than by its own fade, so the fade moves to
   the stage edge; otherwise the window ends on a hard line. */
.closer-stage.sc-stage .stage-closer::after{display:none}
/* The fade has to reach solid ink on the gutter BETWEEN two rows, not part way through one: a
   console that dims out half way down a transcript row reads as a rendering fault, and a fade
   that is still translucent at the footer's hairline is the guillotine the crop was meant to
   avoid. The stop below is the row boundary measured on this page's own console at 1440. */
.closer-stage.sc-stage::after{content:"";position:absolute;left:0;right:0;bottom:0;height:20%;
  z-index:3;pointer-events:none;
  background:linear-gradient(180deg,transparent,rgba(11,15,20,.6) 30%,var(--ink) 56%,var(--ink))}

/* ---------------- motion ---------------- */
/* --- the lift ---
   Two nested transforms on purpose. The lean follows the pointer, and needs a transition or it
   tracks the mouse with no weight; the lift is written from --sc-p every frame, and a transition
   on it would make the console lag the hand. One element cannot have both. */
.hero-lean{transform-style:preserve-3d;
  transform:rotateX(calc((var(--sc-my,.5) - .5) * -2.4deg)) rotateY(calc((var(--sc-mx,.5) - .5) * 2.6deg));
  transition:transform 620ms var(--ease-out)}
.hero-lift,.closer-lift{
  /* --sc-p defaults to 1 so the plane is already level before the engine writes anything, and
     stays level for a browser that never runs the script. */
  --l:clamp(0,calc(var(--sc-p,1) / .6),1);
  transform-origin:50% 100%;
  transform:rotateX(calc((1 - var(--l)) * 9deg))
            scale(calc(.96 + .04 * var(--l)))
            translate3d(0,calc((1 - var(--l)) * 40px),0)}
/* The hero's console does not merely level, it travels: it starts where the first screen shows
   it, a tilted plane whose top edge is most of a screen down, and arrives level and full with its
   top 24px under the nav pill. --hero-rise is that distance, measured from the real layout by
   free-build-v2.js rather than hardcoded at one viewport. Two ramps, not one: the plane
   straightens by p = 0.72 and finishes arriving by p = 0.85, so it reads as a console standing up
   as it comes rather than as one flat rectangle sliding, and the last 15% of the travel is a
   hold. */
.hero-lift{
  --l:clamp(0,calc(var(--sc-p,1) / .72),1);
  --u:clamp(0,calc(var(--sc-p,1) / .85),1);
  transform:rotateX(calc((1 - var(--l)) * 9deg))
            scale(calc(.96 + .04 * var(--u)))
            translate3d(0,calc((1 - var(--u)) * 40px - var(--u) * var(--hero-rise,0px)),0)}
/* The copy greets, then gets out of the console's way: it drifts up while its cues ramp out, so
   the two moves are one gesture rather than a fade over a rise. The drift lives on the block,
   which no cue owns; a cue writes transform on its own element, and stacking a second transform
   there would silently discard one of them. */
.hero-in{transform:translate3d(0,calc(clamp(0,calc(var(--sc-p,0) / .5),1) * -90px),0)}

/* --- this page's windows ---
   The hero and closer screenshots are cut to their frame exactly, so the window has to be made:
   a small scale gives the picture room to travel without ever showing an edge. folio.css states
   the .frame-still half of this for every page; the pair is restated here because the hero
   shares it. */
.frame-hero .media,.frame-still .media{position:absolute;inset:0;transform-origin:50% 50%;
  --w-s:1.05;--w-from:16px;--w-to:-16px;
  transform:translate3d(0,var(--w-y,0px),0) scale(var(--w-s,1))}
/* The hero is the exception: its motion is the lift, and the act ends with the console filling
   the stage, where any pan cuts the app's own header bar off the top of the picture. Scale 1
   means no overflow, and a window with no overflow never pans. */
.frame-hero .media{--w-s:1;--w-from:0px;--w-to:0px}

/* --- the landing ---
   A greet cue is already at full opacity when the act begins, so the engine has nothing to
   animate on the one screen every visitor sees. One CSS animation, played once, gives the hero
   its arrival; animation-fill-mode is backwards only, so the engine's inline values take over the
   instant it ends and scroll owns the element from then on. */
@keyframes fbv-rise{from{opacity:0;transform:translate3d(0,112%,0)}}
@keyframes fbv-greet{from{opacity:0;transform:translate3d(0,14px,0)}}
@media(prefers-reduced-motion:no-preference){
  .hero h1 .sc-split__i{animation:fbv-rise 980ms var(--ease-out) backwards}
  .hero h1 .sc-split--line:nth-child(2) .sc-split__i{animation-delay:100ms}
  .hero h1 .sc-split--line:nth-child(3) .sc-split__i{animation-delay:200ms}
  .hero-in>[data-sc-cue]{animation:fbv-greet 720ms var(--ease-out) backwards}
  .hero-in>h1[data-sc-cue]{animation:none}
  .hero-in>.chip{animation-delay:40ms}
  .hero-in>.hero-lead{animation-delay:420ms}
  .hero-in>.hero-cta{animation-delay:530ms}
  .hero-in>.fbv-terms{animation-delay:630ms}
  .hero-in>.fbv-note{animation-delay:700ms}
}

/* Inside the pin the console is the act's destination, so it is sized to fill the held stage once
   it arrives: as wide as the page's own column, and capped by the viewport's height so a short
   screen never ends the travel with the frame running off the bottom. 130px is the nav pill, its
   inset, the 24px gap under it and a breath at the foot; 1.6 is the frame's 16/10. */
.hero-stage.sc-stage>.stage.wrap{
  max-width:min(1240px,calc((100svh - 130px) * 1.6 + 64px));
  padding-top:38px;margin-top:0}

/* If the script never runs, or a capture tool asks for the static page, nothing may stay
   invisible and nothing may sit mid-animation. folio.css covers the cues, reveals and staggers;
   these are this page's own moves. */
html.no-js .hero-lean,html.hv-static .hero-lean,
html.no-js .hero-lift,html.hv-static .hero-lift,
html.no-js .hero-in,html.hv-static .hero-in,
html.no-js .closer-lift,html.hv-static .closer-lift{transform:none;transition:none}
html.no-js .frame-hero .media,html.hv-static .frame-hero .media,
html.no-js .frame-still .media,html.hv-static .frame-still .media{transform:none}
html.no-js .hero-in>[data-sc-cue],html.hv-static .hero-in>[data-sc-cue],
html.no-js .hero h1 .sc-split__i,html.hv-static .hero h1 .sc-split__i{animation:none}

/* ---------------- reduced motion ----------------
   Everything renders in its finished state and the clip holds on its poster. */
@media(prefers-reduced-motion:reduce){
  .frame-hero .clip{opacity:1;transition:none}
  /* Fewer and gentler, not zero: the engine keeps the opacity that carries comprehension and
     drops every position change. These are this page's own position changes. */
  .hero-lean,.hero-lift,.hero-in,.closer-lift{transform:none;transition:none}
  .frame-hero .media,.frame-still .media{transform:none}
  /* The hero and the close are demoted to flow under reduce, so the pinned-only console size
     never applies and the frame is the ordinary column width. Restated rather than inherited,
     because .sc-stage is what carries that rule and a flow act never gets the class. */
  .hero-stage>.stage.wrap{max-width:none}
  .hero h1 .sc-split__i{animation:none}
}

@media(max-width:760px) and (prefers-reduced-motion:reduce){
  .frame-hero .media,.frame-still .media{transform:scale(var(--w-s,1))}
}

/* ---------------- responsive ---------------- */
@media(max-width:900px){
  /* The hero and the close are flow here, so their progress is not a pin's progress and the lift
     would arrive half-resolved. The window still pans. */
  .hero-lean,.hero-lift,.hero-in,.closer-lift{transform:none}
  .fbv-rules-2{grid-template-columns:1fr;column-gap:0;margin-top:44px}
  .fbv-rules-2 li:nth-child(2){border-top:1px solid var(--line)}
}
@media(max-width:760px){
  .hero-stage{padding-top:calc(var(--nav-h) + var(--nav-inset) + 34px)}
  .hero h1{margin-top:18px}
  .hero-lead{margin-top:16px}
  .hero-cta{margin-top:24px;gap:16px;flex-direction:column;width:100%}
  .hero-cta .btn{width:100%;justify-content:center;min-height:48px}
  .fbv-terms{margin:16px 0 2px;font-size:12px;letter-spacing:.08em}
  .fbv-note{margin-top:16px;letter-spacing:.1em}
  .stage-closer{margin-top:36px}
  /* On a phone a whole 1600px console is unreadable at 335px: the hero frame crops to the result
     column instead of shrinking the entire application to a smudge. */
  .frame-hero,.frame-still{aspect-ratio:4/5}
  /* On a phone a whole 1600px console is unreadable at 348px: every label renders at four or
     five pixels. The frame keeps its crop and the picture inside it is magnified 2.2x about the
     results column, so the quote, the timecode and the Open moment control are the picture. The
     scale rides .media, which the poster, the clip and the still all share, so the crossfade
     still lines up frame for frame. */
  .frame-hero .media,.frame-still .media{--w-s:2.2;transform-origin:6% 31%}
  /* transform:none cancels the pan under no-JS, ?static=1 and reduced motion. On a phone it would
     cancel the crop with it, and the crop is legibility rather than motion, so the scale comes
     back here without the pan. */
  html.no-js .frame-hero .media,html.hv-static .frame-hero .media,
  html.no-js .frame-still .media,html.hv-static .frame-still .media{transform:scale(var(--w-s,1))}
  .frame-hero .poster,.frame-hero .clip,.frame-still img{object-position:50% 0}
  .frame-hero{border-radius:12px}
  .offer-stage{padding:calc(var(--nav-h) + var(--nav-inset) + 16px) 0 30px}
  .offer-stage .lines{margin-top:26px}
  .offer-stage .line{padding:13px 0}
  .sec-offer .lines{margin-top:34px}
  .line{padding:16px 0;max-width:none;font-size:clamp(21px,5.4vw,26px)}
  /* --zoom, --sx and --w-to are all percentages of the card, so a crop frames the same region
     of the picture at every width and only the phone's shorter travel needs restating. The
     library's --sy is the exception: it is a percentage of the card's HEIGHT, and one column of
     cards is a different shape from three, so the band has to be re-aimed at the rail. */
  .fbv-shot-search{--w-to:-62px}
  .fbv-shot-detail{--w-to:-66px}
  .fbv-shot-library{--sy:-26%;--w-to:-52px}
}


/* --- the seam when nothing is pinned ---
   A pinned section takes padding:0 and its stage carries a top pad whose only job is to clear the
   fixed nav pill while the stage is stuck under it. With nothing stuck that is not a seam, it is
   a leftover, and it put 183px of ground between the section above and the act's first line.
   In the static state the stage takes the page's own section rhythm instead. */
html.hv-static .offer-stage{padding:var(--sec-pad) 0}

/* --- the hero when nothing is pinned ---
   100svh is the pinned stage's own height: the act holds a whole screen and the copy sits in the
   middle of it. Take the pin away, which ?static=1, reduced motion, a coarse pointer and any
   width under 900px all do, and that same screen becomes a fixed measure the copy no longer
   fills, so the fold opened on a band of ground and closed on another one before the next
   section. Unpinned, the hero is as tall as the hero. .sc-act--pinned and .sc-stage are the two
   classes the engine adds to a pinned act and its stage, so this is exactly "not pinned". */
.hero:not(.sc-act--pinned){min-height:0}
.hero-stage:not(.sc-stage){min-height:0}
