/* ===========================================================================
   SCALUS — FILM LAYER  (film.css)

   The motion treatment that turns a Scalus page into a scroll driven film.
   Modelled on the premium SaaS demo motion Tim sent through (fantiqmotion), and
   built as a LAYER: it never replaces a component, it only adds motion over the
   top, so a page wearing it is still the same page with the same markup, the
   same videos, the same client map and the same forms.

   Loaded ONLY by a page whose <body> carries data-film, alongside sc.css. Every
   selector below is scoped to that attribute so this file can never reach the
   live home page by accident.

   The vocabulary, all six of it from the reference:
     1. type assembles word by word out of blur
     2. a conic gradient hairline chases the edge of a card, field or button,
        with a blurred copy of the same gradient behind it for the bloom
     3. a field types itself with a live caret, and a cursor prop clicks it
     4. a pill morphs into a result
     5. a list sits in depth of field, only the centred row is sharp
     6. sparkles pop, and huge soft colour blooms drift behind the stage

   film.js is the director. It loads AFTER sc.js, because sc.js auto tags
   headings and cards with its own scroll reveal and the film has to take those
   headings back off it before splitting them, or the words assemble inside a
   block that is still fading up and the two cancel each other out.
   =========================================================================== */

@property --f-ang{syntax:'<angle>';inherits:false;initial-value:0deg}

body[data-film]{
  --f-cyan:#22d3ee; --f-violet:#8b5cf6;
  --f-grad:linear-gradient(95deg,var(--green),var(--f-cyan),var(--f-violet),var(--orange));
}

/* ---------------------------------------------------------------------------
   1. THE STAGE
   Three enormous, very soft colour blooms drifting behind everything. The one
   reason the reference reads as expensive is that its black is never flat
   black, there is always a slow coloured light moving under it.

   z-index -1 puts them above the root background but below the node network
   canvas sc.js pins at z-index 0, so the nodes still draw over the colour
   rather than the colour washing them out. They are deliberately weaker than
   they would be on a bare page, because the node network is already doing half
   of this job.
   --------------------------------------------------------------------------- */
body[data-film] .film-stage{position:fixed;inset:0;z-index:-1;pointer-events:none;overflow:hidden}
/* The sky is TALLER than the window and rides against the scroll, so the light
   under the page CHANGES as you go down instead of every section wearing the
   same wash. Pinned blooms that never move read as a colour filter over the
   site after two screens, not as light.

   No green in here on purpose. sc.js already paints a green node network over a
   near black ground, so a green bloom underneath just doubles the one hue the
   page already has and the whole thing goes swampy. The blooms supply the
   colours the page does NOT have: violet over the top third, cyan through the
   middle, a warm edge near the end. */
body[data-film] .film-sky{position:absolute;left:0;right:0;top:0;height:420vh;
  transform:translate3d(0,calc(var(--f-p,0) * -320vh),0);will-change:transform}
body[data-film] .film-bloom{position:absolute;border-radius:50%;filter:blur(140px);opacity:.13}
body[data-film] .film-bloom.b1{width:66vw;height:66vw;left:-18vw;top:-12vh;background:var(--f-violet);
  animation:fdrift1 30s ease-in-out infinite}
body[data-film] .film-bloom.b2{width:58vw;height:58vw;right:-20vw;top:130vh;background:var(--f-cyan);
  opacity:.11;animation:fdrift2 36s ease-in-out infinite}
body[data-film] .film-bloom.b3{width:60vw;height:60vw;left:14vw;top:265vh;background:var(--orange);
  opacity:.12;animation:fdrift3 42s ease-in-out infinite}
@keyframes fdrift1{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(12vw,10vh,0) scale(1.14)}}
@keyframes fdrift2{0%,100%{transform:translate3d(0,0,0) scale(1.1)}50%{transform:translate3d(-14vw,-8vh,0) scale(.92)}}
@keyframes fdrift3{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(-10vw,-12vh,0) scale(1.18)}}

/* ---------------------------------------------------------------------------
   2. KINETIC TYPE
   Every word is its own inline block so it can carry its own delay. The split
   happens at runtime, so the markup in the page stays plain readable sentences
   that a screen reader and a crawler read exactly as before.
   --------------------------------------------------------------------------- */
body[data-film] .fkw{display:inline-block;will-change:transform,filter,opacity}
body[data-film].film-on .fkin .fkw{opacity:0;filter:blur(14px);transform:translateY(.42em) scale(.93)}
body[data-film].film-on .fkin.live .fkw{animation:fkin .92s cubic-bezier(.16,1,.3,1) forwards;
  animation-delay:calc(var(--i) * .055s + var(--d,0s))}
@keyframes fkin{to{opacity:1;filter:blur(0);transform:none}}

/* THE WORDMARK GOES FIRST (Tim, 19 Sep 2026). On the home page the hero now
   opens on the Scalus mark, so the headline holds until the mark is in and its
   shine has started to cross it. --d is the per scene offset the delay formula
   above already takes, so this is one number and no second timeline. Only the
   hero waits: every other heading on the page still assembles the moment it is
   scrolled to. */
body[data-film] .hero h1.fkin{--d:.75s}

/* A heading the film has taken over must stop doing sc.js's block fade, or the
   words assemble inside something that is itself still moving. */
body[data-film] .film-held,
body[data-film] .film-held.mv,
body[data-film] .film-held.reveal{opacity:1!important;transform:none!important;transition:none!important}

/* ---------------------------------------------------------------------------
   3. THE CONIC GLOW BORDER
   ::before is the hairline, ::after is the same gradient blurred out behind it.
   Both ride one animated angle, so the bloom and the line always agree about
   where the bright part of the sweep is.

   .film-glow-idle is the quiet version for anything there are a lot of: the
   sweep sits dim and only wakes on hover or focus. A page full of always on
   rainbow chases is a page nobody can read.
   --------------------------------------------------------------------------- */
body[data-film] .film-glow{position:relative;isolation:isolate}
body[data-film] .film-glow::before,
body[data-film] .film-glow::after{content:'';position:absolute;inset:-1px;border-radius:inherit;
  padding:1.5px;pointer-events:none;z-index:2;
  background:conic-gradient(from var(--f-ang),var(--green),var(--f-cyan),var(--f-violet),var(--orange),var(--green));
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude}
body[data-film] .film-glow::after{filter:blur(16px);opacity:.45;padding:2.5px;z-index:1}
body[data-film].film-on .film-glow::before,
body[data-film].film-on .film-glow::after{animation:fspin 5.5s linear infinite}
@keyframes fspin{to{--f-ang:360deg}}

body[data-film] .film-glow-idle::before{opacity:.28;transition:opacity .45s}
body[data-film] .film-glow-idle::after{opacity:0;transition:opacity .45s}
body[data-film] .film-glow-idle:hover::before,
body[data-film] .film-glow-idle:focus-within::before{opacity:1}
body[data-film] .film-glow-idle:hover::after,
body[data-film] .film-glow-idle:focus-within::after{opacity:.5}

/* The hero form already owns both of its own pseudo elements for the glass
   rim, so it gets a wrapper rather than a class. The wrapper inherits the
   card's radius so the two curves stay concentric. */
body[data-film] .film-glowwrap{position:relative;border-radius:var(--r);display:block}
body[data-film] .film-glowwrap > *{position:relative;z-index:3}
/* THE WRAPPER INHERITS THE FORM'S PLACE IN THE PHONE ORDER. The hero on a
   phone dissolves its two columns into one flex column and pulls the form up
   under the headline with order:-1. That order is written against form.form,
   and the moment this wrapper goes around the form the form stops being a flex
   child: the rule went inert and the card fell to the bottom of the hero, under
   the Google score. So the wrapper takes the order and the spacing, and the
   form inside it gives its own margin up. */
@media(max-width:900px){
  body[data-film] .hero-grid .film-glowwrap{order:-1;margin:4px 0 30px}
  body[data-film] .hero-grid .film-glowwrap form.form{margin:0}
}

/* THE RIM ARRIVES AFTER THE CARD, NEVER BEFORE IT.
   Tim, 19 Sep 2026: "make sure the outline doesn't come up first, when the form
   loads then bring the outline there, and animate it."

   The wrapper is a bare div, and the card inside it is held at opacity 0 by the
   hero entrance until 1.8s. So for the first second and a half of the page the
   two conic layers were drawing a rainbow rectangle around nothing, which is
   the first thing the eye went to and the last thing that should have been.

   They now wait for the card to finish landing at 2.55s, then light up: the
   hairline draws on, the bloom behind it flares past its resting value and
   settles back, and only then does the sweep start turning. forwards, not
   backwards, because the last keyframe of each IS the resting value, so the
   fill hands over to exactly what film.css already declares.

   Gated on .film-on, so under reduced motion (where film.js never adds it, and
   nothing is going to animate anyway) the rim is simply there from the start. */
body[data-film].film-on [data-intro] .film-glowwrap::before,
body[data-film].film-on [data-intro] .film-glowwrap::after{opacity:0}
body[data-film].film-on [data-intro] .film-glowwrap::before{
  animation:frimIn .6s cubic-bezier(.22,1,.36,1) 2.6s forwards,
            fspin 5.5s linear 2.6s infinite}
body[data-film].film-on [data-intro] .film-glowwrap::after{
  animation:frimBloom .8s cubic-bezier(.22,1,.36,1) 2.6s forwards,
            fspin 5.5s linear 2.6s infinite}
@keyframes frimIn{from{opacity:0}to{opacity:1}}
@keyframes frimBloom{0%{opacity:0}45%{opacity:.85}100%{opacity:.45}}

/* ---------------------------------------------------------------------------
   4. SPARKLES — REMOVED 19 Sep 2026 ("remove the stars"). They were the one
   piece of the vocabulary that decorated instead of directing. film.js no
   longer injects them; nothing here is left to style.
   --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   5. THE TYPING FIELD  (injected above the checker's own score dials)
   One 6s bar, shared by the typing, the click, the pill and the dials:
     0 to 26%   the address types itself
     42%        the cursor lands and the button presses
     46 to 60%  the pill
     64%        the dials are told to draw
   --------------------------------------------------------------------------- */
body[data-film] .film-check{max-width:600px;margin:0 auto 26px;text-align:left}
body[data-film] .film-check-lab{display:block;margin:0 0 10px 4px;font-size:12px;font-weight:700;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink-mute)}
body[data-film] .film-field{position:relative;display:flex;align-items:center;gap:12px;
  padding:6px 6px 6px 20px;border-radius:999px;min-height:66px;
  background:rgba(var(--wa),.045);border:1px solid var(--line)}
body[data-film] .film-field .fval{flex:1;min-width:0;font-size:clamp(15px,2.2vw,19px);font-weight:600;
  white-space:nowrap;overflow:hidden;color:var(--ink)}
body[data-film] .film-caret{display:inline-block;width:2px;height:1.05em;vertical-align:-.15em;
  margin-left:2px;background:var(--green);animation:fblink 1s step-end infinite}
@keyframes fblink{50%{opacity:0}}
body[data-film] .film-field .fgo{flex:none;height:52px;padding:0 24px;border-radius:999px;
  background:var(--green);color:#04130a;font-weight:800;font-size:15px;display:inline-flex;
  align-items:center;transition:transform .3s cubic-bezier(.16,1,.3,1)}
body[data-film] .film-field .fgo.press{transform:scale(.92)}
@media(max-width:600px){
  body[data-film] .film-field{padding:5px 5px 5px 16px;min-height:58px}
  body[data-film] .film-field .fgo{height:46px;padding:0 16px;font-size:14px}
}

body[data-film] .film-cursor{position:absolute;right:14%;bottom:-26px;width:24px;opacity:0;z-index:5;
  filter:drop-shadow(0 4px 12px rgba(0,0,0,.6))}
body[data-film].film-on .film-check.live .film-cursor{animation:fcursor 6s cubic-bezier(.3,0,.2,1) infinite}
@keyframes fcursor{
  0%{opacity:0;transform:translate(40px,44px)}
  30%{opacity:1;transform:translate(40px,44px)}
  40%{opacity:1;transform:translate(0,0)}
  44%{opacity:1;transform:translate(0,0) scale(.86)}
  50%{opacity:1;transform:translate(0,0) scale(1)}
  88%{opacity:1;transform:translate(0,0)}
  100%{opacity:0;transform:translate(0,0)}
}

/* the pill that hands over to the dials */
body[data-film] .film-pill{position:absolute;left:50%;top:calc(100% + 16px);transform:translateX(-50%);
  display:inline-flex;align-items:center;gap:9px;height:44px;padding:0 22px;border-radius:999px;
  white-space:nowrap;font-weight:700;font-size:14px;color:var(--ink);
  background:rgba(var(--wa),.06);border:1px solid var(--line);opacity:0;pointer-events:none}
body[data-film] .film-pill svg{width:15px;height:15px;color:var(--green)}
body[data-film].film-on .film-check.live .film-pill{animation:fpill 6s cubic-bezier(.65,0,.35,1) infinite}
@keyframes fpill{
  0%,42%{opacity:0;transform:translateX(-50%) scale(.82)}
  48%{opacity:1;transform:translateX(-50%) scale(1)}
  60%{opacity:1;transform:translateX(-50%) scale(1)}
  66%,100%{opacity:0;transform:translateX(-50%) scale(1.3);filter:blur(9px)}
}

/* ---------------------------------------------------------------------------
   6. DEPTH OF FIELD ROLL
   A fixed window the list rides up and down inside, so the sharp row is always
   in the middle of the frame. Without a real height there is nothing to centre
   against and the block just grows to the full list.
   --------------------------------------------------------------------------- */
body[data-film] .film-roll{position:relative;overflow:hidden;max-width:780px;margin:0 auto;
  height:clamp(230px,34vh,330px);
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 26%,#000 74%,transparent);
  mask-image:linear-gradient(180deg,transparent,#000 26%,#000 74%,transparent)}
body[data-film] .film-roll ul{list-style:none;margin:0;padding:0;
  transition:transform .55s cubic-bezier(.16,1,.3,1);will-change:transform}
body[data-film] .film-roll li{font-size:clamp(21px,4.2vw,40px);font-weight:800;letter-spacing:-.03em;
  line-height:1.5;text-align:center;color:var(--ink-soft);
  transition:filter .55s cubic-bezier(.16,1,.3,1),opacity .55s,
    transform .55s cubic-bezier(.16,1,.3,1),color .55s}
body[data-film].film-on .film-roll li{filter:blur(5px);opacity:.3;transform:scale(.9)}
body[data-film].film-on .film-roll li.on{filter:blur(0);opacity:1;transform:none}
body[data-film] .film-roll li.on{color:var(--ink)}
/* the paint stays on EVERY row, not only the focused one: painting on the class
   change made the colour snap in while the blur was still crossfading, and
   blurred colour is what the reference actually does */
body[data-film] .film-roll em{font-style:normal;background:var(--f-grad);background-size:220% 100%;
  -webkit-background-clip:text;background-clip:text;color:transparent;animation:fpan 7s linear infinite}
@keyframes fpan{to{background-position:220% 0}}

body[data-film] .film-rollband{padding:10px 0 46px;text-align:center}
body[data-film] .film-rollband .film-rolltag{display:inline-flex;align-items:center;gap:9px;
  padding:7px 15px;border-radius:999px;border:1px solid var(--line);background:rgba(var(--wa),.04);
  font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-soft);
  margin-bottom:18px}
body[data-film] .film-rollband .film-rolldot{width:7px;height:7px;border-radius:50%;background:var(--green)}

/* ---------------------------------------------------------------------------
   7. REDUCED MOTION
   The whole film stops and the page is simply the page. film.js never adds
   .film-on, so every rule gated on it is inert, and these three kill the
   endless ambient loops that are not.
   --------------------------------------------------------------------------- */
@media(prefers-reduced-motion:reduce){
  body[data-film] .film-bloom,
  body[data-film] .film-glow::before,
  body[data-film] .film-glow::after,
  body[data-film] .film-roll em{animation:none}
  body[data-film] .film-stage{display:none}
}
