/* ============================================================
   flow.css - Vail Digital motion layer
   Shared by every page. Motion primitives only.
   Pairs with /assets/flow.js. Everything degrades to
   "visible and static" if flow.js never runs.

   CACHE
   -----
   Every page links this file with ?v=<date>. After editing it, bump that
   query on ALL pages or returning visitors keep the old copy until their
   cache expires. Current: v=20260923

     python3 - <<'EOF'
     import re, glob
     V = 'YYYYMMDD'
     p = re.compile(r'((?:href|src)=")(/assets/(?:flow|splash)\.(?:css|js))(\?v=[^"]*)?(")')
     for f in glob.glob('*.html'):
         s = open(f).read()
         open(f, 'w').write(p.sub(lambda m: m.group(1) + m.group(2) + '?v=' + V + m.group(4), s))
     EOF

   ============================================================ */

:root{
  --flow-ease: cubic-bezier(.16,.84,.28,1);
  --flow-ease-out: cubic-bezier(.22,1,.36,1);
  --flow-dur: .82s;
  --flow-dur-slow: 1.15s;
}

/* ---------- 1. REVEALS ----------
   Visible by default. flow.js adds .flow-ready to <html>, which
   is what arms the hidden state - so a script failure or a
   crawler always sees the finished page. */
.flow-ready [data-flow]{
  opacity:0;
  will-change:opacity,transform;
  transition:
    opacity var(--flow-dur) var(--flow-ease),
    transform var(--flow-dur) var(--flow-ease),
    filter var(--flow-dur) var(--flow-ease);
  transition-delay:var(--flow-delay,0ms);
}
.flow-ready [data-flow="up"]{transform:translate3d(0,34px,0)}
.flow-ready [data-flow="rise"]{transform:translate3d(0,72px,0);transition-duration:var(--flow-dur-slow)}
.flow-ready [data-flow="fade"]{transform:none}
.flow-ready [data-flow="scale"]{transform:scale(.94)}
.flow-ready [data-flow="left"]{transform:translate3d(-40px,0,0)}
.flow-ready [data-flow="right"]{transform:translate3d(40px,0,0)}
.flow-ready [data-flow="blur"]{filter:blur(14px);transform:translate3d(0,20px,0)}
.flow-ready [data-flow].is-in{opacity:1;transform:none;filter:none}

/* Line-by-line headline reveal */
.flow-line{display:block;overflow:hidden;padding-block:.04em}
.flow-line > span{
  display:block;
  transform:translate3d(0,105%,0);
  transition:transform var(--flow-dur-slow) var(--flow-ease-out);
  transition-delay:var(--flow-delay,0ms);
}
.flow-ready [data-flow-lines].is-in .flow-line > span{transform:none}
html:not(.flow-ready) .flow-line > span{transform:none}

/* ---------- 2. SCRUBBED ELEMENTS ----------
   flow.js writes --flow-p (0 to 1) as the element crosses. */
[data-flow-scrub]{--flow-p:0}
[data-flow-scrub="drift"]{transform:translate3d(0,calc((1 - var(--flow-p)) * 48px),0)}
[data-flow-scrub="settle"]{
  opacity:calc(.25 + var(--flow-p) * .75);
  letter-spacing:calc(.06em - var(--flow-p) * .06em);
}
[data-flow-scrub="parallax"]{transform:translate3d(0,calc(var(--flow-p) * -64px),0)}
[data-flow-scrub="grow"]{transform:scale(calc(.9 + var(--flow-p) * .1))}

/* ---------- 3. MOOD ----------
   One fixed layer, cross-faded per section. Sits at z-index -1
   so it stays behind the existing #bg-canvas / .gradient-orb /
   .particle layers, which are all fixed at z-index 0. */
.flow-mood{
  position:fixed;inset:0;z-index:0;pointer-events:none;
  background:var(--flow-mood-a,transparent);
  transition:background 1.1s var(--flow-ease);
}
.flow-mood__orb{
  position:absolute;border-radius:50%;filter:blur(90px);
  opacity:.5;transition:background 1.2s var(--flow-ease), opacity 1.2s var(--flow-ease);
}

/* ---------- 4. NARRATIVE ----------
   Sticky media, scrolling copy. */
.flow-narrative{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(32px,6vw,88px);
  align-items:start;
}
.flow-narrative__media{
  position:sticky;
  top:calc(50vh - min(29vh, 300px));
  height:min(58vh,600px);
  border-radius:18px;
  overflow:hidden;
  isolation:isolate;
}
.flow-narrative__panel{
  position:absolute;inset:0;opacity:0;transform:scale(1.03);
  transition:opacity .66s var(--flow-ease), transform .9s var(--flow-ease);
}
.flow-narrative__panel.is-active{opacity:1;transform:none}
.flow-narrative__panel > img,
.flow-narrative__panel > video{width:100%;height:100%;object-fit:cover;display:block}
html:not(.flow-ready) .flow-narrative__panel{position:relative;opacity:1;transform:none}
html:not(.flow-ready) .flow-narrative__media{position:static;height:auto}
.flow-narrative__steps{display:flex;flex-direction:column}
.flow-narrative__step{min-height:74vh;display:flex;flex-direction:column;justify-content:center}

@media (max-width:880px){
  .flow-narrative{grid-template-columns:1fr;gap:0}
  .flow-narrative__media{position:sticky;top:72px;height:42vh;margin-bottom:28px}
  .flow-narrative__step{min-height:0;padding-block:34px}
}

/* ---------- 5. PROGRESS RAIL ---------- */
.flow-rail{position:fixed;top:0;left:0;right:0;height:2px;z-index:1200;background:transparent;pointer-events:none}
.flow-rail__fill{
  height:100%;width:100%;transform-origin:0 50%;
  transform:scaleX(var(--flow-progress,0));
  background:linear-gradient(90deg,var(--cyan,#00e5cc),var(--gold,#ffc145));
  transition:transform .08s linear;
}

/* ---------- 6. SMOOTH SCROLL ---------- */
html.flow-smooth{scroll-behavior:auto}

/* Stop the page rubber-banding past the footer into empty background.
   This has to be on the ROOT element - setting it only on <body> leaves the
   document-level bounce in place, which is what "the page scrolls below the
   bottom" actually is. Not gated on .flow-smooth: it should hold even if
   Lenis never loads.
   Trade-off: this also disables pull-to-refresh on the site. */
html{overscroll-behavior-y:none}
html.flow-smooth body{overscroll-behavior:none}

/* ---------- 7. REDUCED MOTION ---------- */
@media (prefers-reduced-motion:reduce){
  .flow-ready [data-flow]{opacity:1!important;transform:none!important;filter:none!important;transition:none}
  .flow-line > span{transform:none!important;transition:none}
  [data-flow-scrub]{transform:none!important;opacity:1!important;letter-spacing:normal!important}
  .flow-mood,.flow-mood__orb,.flow-narrative__panel{transition:none}
  .flow-rail__fill{transition:none}
}

/* ---------- 8. HEADING LINE BREAKS ----------
   Headings were stranding single words on a line of their own at phone
   width - "Five Steps. No / Surprises.", "29% revenue growth wasn't an /
   accident", "Ready to build a / system / that actually / grows?".

   text-wrap:balance lets the browser even out the lines instead of filling
   greedily and leaving an orphan. Measured at 375px on the live page: every
   one of those becomes a clean two-line break. Browsers without support
   ignore it and wrap exactly as before, so it costs nothing.

   The hard <br> in a few headings is deliberate art direction at desktop
   width, but at phone width it lands mid-phrase and fights the wrapping.
   Those are now <br class="hbr"> with a real space in front, and the break
   itself is hidden below 820px - the space is what keeps it reading
   "Software, Not" rather than "Software,Not" once the <br> is gone. */
h1, h2, h3,
.section-title,
.flow-line > span{
  text-wrap:balance;
}
@media (max-width:820px){
  .hbr{display:none}
}

/* ---------- 9. GRADIENT PHRASES IN HEADINGS ----------
   A highlighted or gradient phrase inside a heading was being split across
   two lines on phones, so half the coloured phrase sat on one line and half
   on the next - "earn recurring / revenue without tech" on /consulting,
   "can't afford to / break" on /about, "portfolio / website" on /realestate.
   The colour is doing the emphasising, so cutting it in half undoes the
   point of it.

   Measured at 375px on the live site: every one of these fits on a single
   line inside its heading, so holding them together costs nothing.

   .flow-line and its inner span are deliberately NOT matched - those are the
   hero's line wrappers, they are display:block, and making them nowrap would
   wreck the hero.

   CAUTION: a phrase wider than its heading will overflow horizontally rather
   than wrap. One already does - "They're maintained every month." on /plans
   is 465px inside a 335px heading - and it carries .hl-wrap to opt out. Any
   future gradient phrase longer than roughly 20 characters needs the same
   escape hatch, so check before adding one. */
@media (max-width:820px){
  h1 .highlight, h2 .highlight, h3 .highlight,
  h1 .accent,    h2 .accent,    h3 .accent,
  h1 .hl,        h2 .hl,        h3 .hl,
  h1 .av-hl,     h2 .av-hl,     h3 .av-hl,
  h1 span[style*="gradient"], h2 span[style*="gradient"], h3 span[style*="gradient"],
  h1 span[style*="color:"],   h2 span[style*="color:"],   h3 span[style*="color:"]{
    white-space:nowrap;
  }
  /* Too long to hold on one line - must be allowed to wrap. */
  .hl-wrap{white-space:normal !important}
}
