/* =====================================================================
   VIZYPAY  ·  HERO + WHO WE'RE FOR  —  CSS pane
   COMPLETE PANE. Select all in the CSS pane, delete, paste this.

   WHAT CHANGED FROM WHAT IS LIVE RIGHT NOW
     1. every `var(--vzwf-vh, 100dvh)` stage-height rule is GONE. That
        custom property was written from JS on scroll and resize events,
        so the pinned stage was being resized mid-scroll — which is what
        made the panels grow, the content below jump, and a white band
        lag in under the video. My fault, and it is removed at the root
        rather than overridden.
     2. the stage is now sized ONCE on mobile, in `lvh`, a constant.
     3. the press rail is lifted by the exact toolbar height rather than
        a guess, so it is visible whether the toolbar is showing or not.
     4. the track is shorter (265vh -> 205vh, 240vh -> 185vh) with a
        wider sweep window in the JS, so the sweep reads at the same
        speed but the dead scrolling after it is gone.

   PAIRS WITH the JS pane shipped alongside this. The two must go in
   together: this file no longer reads --vzwf-vh, and that file no
   longer writes it.
   ===================================================================== */

/* ======================================================================
   THE DISPLAY FACES ARE LOADED HERE, NOT BY A <link>
   The module's HTML pane carried a <link> to Google Fonts, and HubSpot
   does not reliably keep a <link> inside module markup. When it is
   dropped, font-family silently falls back to Montserrat — the rule
   still applies, the face just is not there, which looks exactly like
   "the cool fonts disappeared".
   An @import in the stylesheet survives, and it has to be the FIRST
   thing in the file, before any rule.
   ====================================================================== */
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800&family=Bungee&family=Lilita+One&family=Permanent+Marker&family=Shrikhand&display=swap');

/* ====================================================================
   ONE SWEEP, FOUR BUSINESSES — v8
   --------------------------------------------------------------------
   THE HAIRLINE IS FIXED, AND IT WAS A GEOMETRY BUG, NOT A ROUNDING ONE.
   The white sheet's edge is a DIAGONAL. Every panel was clipped with
   inset(), whose edge is a VERTICAL LINE — sampled at that panel's
   middle height. So above the middle the panel's edge sat to the right
   of the sheet's edge and a sliver of the card leaked onto the video;
   below it, the reverse. On a tall card that sliver is a hairline, and
   it moves as you scroll, which is exactly what you were seeing.
   Every clipped element now uses the SAME polygon as the sheet,
   solved in its own coordinates, so the two edges are the same line.

   Also: mobile rebuilt and re-evaluated on resize, more wall, more
   nodes (including a constellation between them), and the numbers are
   no longer a footnote.
   ==================================================================== */
*{box-sizing:border-box}
:root{--vz-green:#81b338;--vz-green-dk:#6e9830;--vz-green-tx:#557625;
  --vz-ink:#191414;--vz-bright:#9acb4e}
/* the figures are real now, so they carry no placeholder marker */
.vzwf-real{border-bottom:0}

/* ---------------- the ride ---------------- */
/* ==================================================================
   THE SWEEP IS SLOWED THE ONLY HONEST WAY: MORE SCROLL
   A scroll-driven animation runs at whatever speed the scroll runs,
   so the only way to make it last longer is to give it more distance.
   Holding it back instead was what made it look skipped.

   THE TRACK LENGTH IS SET AT THE BOTTOM OF THIS FILE, not here — see
   "1. THE TRACK, WITHOUT THE DEAD TAIL". This value is the module's
   original and is left in place so the file still reads in order.
   ================================================================== */
.vzwf{position:relative;height:265vh;background:#fff}
/* ==================================================================
   THE STAGE PINS BELOW THE HEADER, NOT BEHIND IT
   There was never any need for a strip, and both of my answers to it
   were wrong. The stage was one viewport minus the header, but pinned
   to the very TOP — so its bottom edge stopped short of the fold and
   left a band to explain away.

   Pin it to the header's height instead. Then the stage occupies
   exactly the space you can see, top edge under the header and bottom
   edge on the fold, which puts the rail flush at the bottom of the
   frame AND on the first screen with nothing left over at either end.
   One number does both jobs: the height it is missing is the distance
   it is pushed down.
   ================================================================== */
.vzwf{background:#fff}
.vzwf__stage{position:sticky;top:var(--vzwf-top,0px);
  height:calc(100vh - var(--vzwf-top,0px));
  overflow:hidden;background:#000}
/* dvh on DESKTOP, where it is identical to vh and never changes.
   Mobile is handled at the bottom of this file with lvh, which is the
   same number every frame — see "2. A STAGE THAT IS SIZED ONCE". */
@supports (height:100dvh){
  .vzwf__stage{height:calc(100dvh - var(--vzwf-top,0px))}
}
/* ==================================================================
   HIDING SAFARI'S OWN PLAY BUTTON
   When iOS decides a video is user-controlled it draws its own start
   button over it — a shadow-DOM control that belongs to the browser,
   not to us, so nothing in the markup or the script can remove it.
   This pseudo-element is the one documented way to reach it. The clip
   still starts on the first touch, and the retry loop still catches it
   the moment the browser relents, but the page no longer looks like a
   broken video player while it waits.
   ================================================================== */
/* Scoped to the ELEMENT, not to our class. A vendor pseudo-element
   hangs off the shadow root of the <video> itself, and pairing it
   with a class selector is unreliable — "video::" is the form that
   actually matches. Still confined to this module by the ancestor. */
.vzwf video::-webkit-media-controls-start-playback-button,
.vzwf video::-webkit-media-controls-play-button,
.vzwf video::-webkit-media-controls-panel,
.vzwf video::-webkit-media-controls-overlay-play-button,
.vzwf video::-webkit-media-controls{
  display:none !important; -webkit-appearance:none !important;
  opacity:0 !important; width:0 !important; height:0 !important}

.vzwf__vid{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;
  }
/* ==================================================================
   THE VIDEO IS NEVER MADE TRANSPARENT ANY MORE
   This used to fade to opacity:0 once the sweep had covered it, to
   save painting a clip nobody could see. That is the one thing in
   this module that the old hero did not have, and it is a known way
   to lose autoplay on iOS: Safari decides whether a video may play
   by whether it is VISIBLE, and a video at zero opacity is not. Once
   it has refused, it does not reconsider — it draws its own play
   button instead and waits for a tap, which is exactly the behaviour
   that appeared when this module replaced the old one.
   The white sheet already covers the video completely, so the fade
   was never doing anything the sweep was not already doing.
   ================================================================== */
.vzwf__over{position:absolute;inset:0;z-index:1;display:grid;place-items:center;
  text-align:center;padding:0 24px;pointer-events:none;opacity:var(--overOp,1)}
/* the overlay ignores the pointer so it cannot block the panels — the
   button has to opt back in, or it is unclickable */
/* WIDE ENOUGH TO STOP WRAPPING. A 46ch measure is right for a column
   of body copy and wrong for a headline — it was breaking "Payments
   you can bank on" over two lines and the sub over two more. */
.vzwf__over-in{max-width:min(940px,92vw);pointer-events:auto;
  padding-bottom:clamp(62px,8.5vh,92px)}   /* clears the rail */
.vzwf__sub{margin:0 auto 24px;max-width:64ch;font-size:clamp(14.5px,1.5vw,17px);
  line-height:1.55;color:rgba(255,255,255,.9);text-shadow:0 1px 14px rgba(25,20,20,.5)}
/* ==================================================================
   THE BUTTON OUT-SPECIFIES THE THEME
   ".vzwf__cta { color }" is 0-1-0. A theme's "a:visited { color }" is
   0-1-1 and wins on specificity regardless of source order, which is
   why the label came out blue and underlined. Written as
   ".vzwf a.vzwf__cta:visited" it is 0-3-1 and cannot lose — and every
   link state is named, because missing one is how a button turns blue
   again the second time somebody visits.
   ================================================================== */
.vzwf a.vzwf__cta,
.vzwf a.vzwf__cta:link,
.vzwf a.vzwf__cta:visited,
.vzwf a.vzwf__cta:hover,
.vzwf a.vzwf__cta:focus,
.vzwf a.vzwf__cta:focus-visible,
.vzwf a.vzwf__cta:active{
  display:inline-block;background:var(--vz-green);
  color:var(--vz-ink) !important;
  text-decoration:none !important;
  border-bottom:0 !important;box-shadow:none;
  font-size:14.5px;font-weight:700;padding:13px 24px;border-radius:6px;
  transition:transform .15s ease,background-color .15s ease}
.vzwf a.vzwf__cta:hover,
.vzwf a.vzwf__cta:focus-visible{background:var(--vz-green-dk);
  transform:translateY(-2px);outline:none}
.vzwf__eb{font-size:11.5px;letter-spacing:.16em;text-transform:uppercase;font-weight:700;
  color:var(--vz-bright);margin:0 0 14px;text-shadow:0 1px 10px rgba(0,0,0,.6)}
.vzwf__over h2{font-size:clamp(28px,4.4vw,52px);line-height:1.06;letter-spacing:-.025em;
  font-weight:800;margin:0;color:#fff;text-shadow:0 2px 26px rgba(0,0,0,.6)}

/* a hair beyond the stage on every side: a sheet that stops exactly
   on the edge can leave a sub-pixel line of what it is covering */
.vzwf__sheet{position:absolute;inset:-2px;z-index:2;background:#fff;
  clip-path:polygon(-3px 0, var(--xt,-3px) 0, var(--xb,-3px) 100%, -3px 100%)}

/* ==================================================================
   THE PRESS RAIL
   Rides the video along the bottom of the frame and is simply covered
   by the white as the sweep crosses — it sits UNDER the sheet, so it
   is wiped by the same edge as everything else rather than needing a
   fade of its own.

   THE LOOP TRANSLATES BY EXACTLY -50%, which means each half has to
   be at least a viewport wide or the row runs out before it wraps.
   The set is repeated three times per half to guarantee that on any
   screen.
   ================================================================== */
.vzwf-rail{position:absolute;left:0;right:0;bottom:0;z-index:1;
  height:clamp(66px,8.5vh,92px);display:flex;align-items:center;gap:clamp(14px,2vw,30px);
  padding:0 clamp(16px,2.4vw,40px);overflow:hidden;
  background:linear-gradient(180deg,rgba(10,9,8,0),rgba(10,9,8,.62) 42%,rgba(10,9,8,.72))}
.vzwf-rail__eb{flex:0 0 auto;font-size:10px;letter-spacing:.22em;text-transform:uppercase;
  font-weight:800;color:var(--vz-bright);white-space:nowrap}
.vzwf-rail__mask{position:relative;flex:1 1 auto;overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent)}
.vzwf-rail__track{display:flex;align-items:center;width:max-content;
  /* slower than it reads at first glance: 64s across one half means a
     logo takes about seven seconds to cross a laptop screen, which is
     the speed at which a wordmark registers rather than streaks */
  animation:vzwfRail 64s linear infinite}
.vzwf-rail:hover .vzwf-rail__track{animation-play-state:paused}
@keyframes vzwfRail{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.vzwf-rail__logo{flex:0 0 auto;display:flex;align-items:center;
  padding:0 clamp(20px,2.5vw,38px);
  font-size:clamp(13px,1.15vw,17px);font-weight:700;letter-spacing:.02em;
  color:rgba(255,255,255,.78);white-space:nowrap}
/* KNOCKED TO WHITE, NOT RECOLOURED. brightness(0) crushes the logo to
   solid black whatever colours it was built from, then invert(1)
   flips that to solid white — which is how a mixed set of press marks
   ends up looking like one set. */
.vzwf-rail__logo img{display:block;height:28px;width:auto;
  filter:brightness(0) invert(1);opacity:.9}
/* 21px was the hero's mobile size, but that rail sits on white with
   nothing else near it. Here the marks are on video with a gradient
   behind them, so they need the extra height to read at all. */
@media (max-width:860px){
  .vzwf-rail__logo img{height:26px;opacity:.95}
  .vzwf-rail__logo{padding:0 16px}
}
@media (max-width:420px){ .vzwf-rail__logo img{height:23px} }

/* THE SAME EDGE, EVERYWHERE. --et and --eb are the sheet's diagonal
   solved in this element's own coordinates, so a panel can never
   disagree with the white behind it. */
.vzwf-sw{clip-path:polygon(-1px 0, var(--et,-1px) 0, var(--eb,-1px) 100%, -1px 100%)}

/* ==================================================================
   AND THE CLIP IS DROPPED THE MOMENT THE SWEEP IS DONE.
   This one line was the whole bug, and it had nothing to do with
   widths. Each panel's reveal clip is written in PIXELS, measured
   from that panel's width at the moment the sweep crosses it — about
   310px, because every panel is closed then. The clip stays on the
   element afterwards. So when a panel later opened to 889px, its
   width really was 889px — getBoundingClientRect said so — but a
   310px-wide clip was still cutting it off, and the rest of the panel
   was invisible. That is the "gap": not empty space, a clipped panel.

   I spent six rounds measuring widths that were correct all along.
   ================================================================== */
.vzwf.vzwf-is-set .vzwf-sw{clip-path:none}

/* ONE PEN WEIGHT ON SCREEN. Every mark is drawn in its own viewBox, so
   a fixed stroke-width comes out thin on a wide shape and fat on a
   small one. non-scaling-stroke keeps the line the same thickness in
   pixels however the box is scaled. */
.vzwf-stat__ic path{vector-effect:non-scaling-stroke}

.vzwf__edge{position:absolute;top:-10%;bottom:-10%;left:0;width:3px;z-index:4;
  background:linear-gradient(180deg,transparent,var(--vz-green) 14%,var(--vz-bright) 50%,var(--vz-green) 86%,transparent);
  transform:translateX(var(--ex,-40px)) skewX(var(--esk,-7deg));
  opacity:var(--eop,0);pointer-events:none;box-shadow:0 0 26px 6px rgba(129,179,56,.34)}

/* ---------------- content ---------------- */
.vzwf__in{position:absolute;inset:0;z-index:3;display:grid;
  grid-template-rows:auto minmax(0,1fr) auto;
  row-gap:clamp(14px,2.2vh,26px);
  padding:clamp(22px,3.4vh,44px) clamp(22px,3vw,54px) clamp(18px,2.6vh,34px);
  pointer-events:none}
.vzwf.vzwf-is-set .vzwf__in{pointer-events:auto}

.vzwf-head{grid-row:1;max-width:46ch}
.vzwf-head__eb{font-size:11px;letter-spacing:.24em;text-transform:uppercase;font-weight:800;
  color:var(--vz-green-tx);margin:0 0 12px}
.vzwf-head h2{font-size:clamp(24px,3.1vw,46px);line-height:1.04;letter-spacing:-.035em;
  font-weight:800;margin:0}
/* ==================================================================
   THE ONE PLACE BRAND GREEN GETS CLOSER
   Measured on white: #81b338 is 2.49:1 and fails at any size.
   #6e9830 is 3.39:1 — under the 4.5:1 needed for body copy, but over
   the 3:1 allowed for large text, and this word sets at ~46px. So the
   headline's "is" can carry the darker brand green while the 11px
   eyebrow above it stays on #557625, which is the only one of the
   three that passes at that size.
   IF THIS WORD EVER SHRINKS BELOW ~24px, it has to go back to
   --vz-green-tx. The exemption is the size, not the colour.
   ================================================================== */
.vzwf-head h2 em{font-style:normal;font-family:'Permanent Marker',cursive;letter-spacing:0;
  color:var(--vz-green-dk);font-weight:400}

/* ==================================================================
   THE PANELS
   --------------------------------------------------------------------
   THE RESIZING IS GONE, and this is the honest reason why: an
   expanding row is a LAYOUT animation. Every frame re-measures four
   panels and re-crops four pieces of media, and the fixes for that
   (fixed media widths, class-driven open states) each removed one
   symptom and left others — a gap where a photograph should be, a
   word stranded mid-panel, a clip stuttering. Four attempts is
   enough: the row is fixed now, and prominence is done with light,
   which is compositor work and cannot tear.

   The word still changes face on hover. What it no longer does is
   chase the cursor — it was measuring a home position that the row
   kept moving underneath it, which is why words ended up floating in
   the middle of a panel.
   ================================================================== */
/* ABSOLUTE, NOT FLEX. Flex means the browser decides the widths from
   my hints and I have to work backwards from a screenshot to find out
   what it decided. Each panel is now positioned outright — a left and
   a width, both in pixels, both written by script. The last panel's
   right edge is set to the row's right edge by arithmetic, so there
   is no leftover for anything to hide in, and no sibling can affect
   another because none of them are in flow. */
.vzwf-panels{grid-row:2;position:relative;--g:clamp(8px,1vw,14px);min-height:0}
.vzwf-card{position:absolute;
  left:var(--l,0px);top:var(--t,0px);
  width:var(--w,25%);height:var(--h,100%);
  overflow:hidden;
  border-radius:12px;background:#e9e9e7;
  transition:left .62s cubic-bezier(.25,.85,.3,1),
             top .62s cubic-bezier(.25,.85,.3,1),
             width .62s cubic-bezier(.25,.85,.3,1),
             height .62s cubic-bezier(.25,.85,.3,1),
             box-shadow .45s ease}
.vzwf.vzwf-is-set .vzwf-card{cursor:pointer}
.vzwf.vzwf-is-set .vzwf-card.vzwf-is-playing{box-shadow:0 20px 44px rgba(25,20,20,.22)}

/* MIN-WIDTH IS THE GUARANTEE. --mw is an optimisation — it keeps
   object-fit from re-running while the row moves. min-width:100% is
   the contract: the media can never be narrower than its panel, so a
   stale or wrongly-timed --mw costs a re-crop, never a hole. That is
   the bug you kept seeing: --mw was measured while the row was still
   a fraction of its final width, and every panel then wore that
   number for the rest of the session. */
.vzwf-card__img{position:absolute;top:0;left:50%;height:100%;
  width:var(--mw,100%);min-width:100%;
  object-fit:cover;object-position:calc(var(--fx,50) * 1%) calc(var(--fy,40) * 1%);
  transition:filter .5s ease;transform:translateX(-50%) scale(var(--iz,1))}
/* whichever clip is running keeps its light; the rest step back */
.vzwf-cards-dim .vzwf-card:not(.vzwf-is-playing) .vzwf-card__img{filter:brightness(.76) saturate(.9)}
/* a cursor overrides that — the one you are on is the bright one */
.vzwf-panels.vzwf-hovering .vzwf-card .vzwf-card__img{filter:brightness(.76) saturate(.9)}
.vzwf-panels.vzwf-hovering .vzwf-card.vzwf-is-open .vzwf-card__img{filter:none}

/* ==================================================================
   A QUIET MARK THAT SAYS "THIS MOVES"
   Separate from the green button, which only ever appears when
   something has gone wrong. This one is ordinary furniture: a small
   play glyph on any panel holding a clip that is not currently
   running — while it loads, and while it is waiting its turn. It
   fades out the moment real frames are on screen, so a panel that is
   playing is never wearing a play button.
   Bottom RIGHT, because the trade word owns the bottom left.
   ================================================================== */
.vzwf-vmark{position:absolute;right:clamp(10px,1vw,16px);bottom:clamp(10px,1.1vw,18px);
  z-index:3;width:26px;height:26px;border-radius:50%;display:grid;place-items:center;
  background:rgba(12,10,10,.40);border:1.5px solid rgba(255,255,255,.78);
  opacity:.92;transition:opacity .5s ease;pointer-events:none;
  -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}
.vzwf-vmark svg{width:9px;height:9px;margin-left:1.5px;fill:#fff}
.vzwf-card.vzwf-playing-now .vzwf-vmark{opacity:0}
.vzwf-card.vzwf-is-thumb .vzwf-vmark{width:21px;height:21px;right:8px;bottom:8px}
.vzwf-card.vzwf-is-thumb .vzwf-vmark svg{width:7px;height:7px}

.vzwf-card__grad{position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(8,8,7,.44) 0 16%,rgba(8,8,7,.04) 38%,rgba(8,8,7,.74) 100%)}
.vzwf-card__cr{position:absolute;left:clamp(12px,1.1vw,18px);top:clamp(11px,1vw,16px);
  display:inline-flex;align-items:center;gap:7px;font-size:10px;letter-spacing:.15em;
  text-transform:uppercase;font-weight:700;color:rgba(255,255,255,.9);white-space:nowrap;
  text-shadow:0 1px 8px rgba(0,0,0,.6)}
.vzwf-card__cr::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--vz-bright);flex:0 0 auto}

/* WIDTH:AUTO IS THE FIX. The word used to stretch the full width of
   the panel, so clamping its CENTRE inside the panel left it almost
   no room to move — it could travel a few pixels left and none at all
   right, which is exactly what you saw. Shrunk to its own text, it
   has the whole panel to move around in. */
.vzwf-card__h{position:absolute;left:clamp(14px,1.3vw,22px);right:auto;
  width:max-content;max-width:calc(100% - clamp(28px,2.6vw,44px));
  bottom:clamp(14px,1.6vw,26px);margin:0;z-index:2;pointer-events:none;
  font-size:clamp(17px,1.7vw,28px);font-weight:800;letter-spacing:-.025em;line-height:1.04;
  color:#fff;text-shadow:0 2px 14px rgba(0,0,0,.72);
  transform:translate3d(var(--wx,0px),var(--wy,0px),0);transform-origin:0 50%;
  transition:font-size .28s cubic-bezier(.2,.9,.3,1),letter-spacing .28s}
/* ONE OWNER. The face swap and the movement are both driven by this
   class rather than one by :hover and one by script — that split was
   how the word ended up in the display face but parked in the wrong
   place when the two disagreed. */
.vzwf-card.vzwf-loose .vzwf-card__h{
  font-family:var(--disp,'Bungee'),'Montserrat',sans-serif;font-weight:400;
  letter-spacing:.01em;font-size:clamp(19px,2vw,34px);
  text-shadow:
    2px 0 0 var(--vz-green), -2px 0 0 var(--vz-green),
    0 2px 0 var(--vz-green), 0 -2px 0 var(--vz-green),
    1.4px 1.4px 0 var(--vz-green), -1.4px -1.4px 0 var(--vz-green),
    1.4px -1.4px 0 var(--vz-green), -1.4px 1.4px 0 var(--vz-green),
    3px 4px 0 rgba(25,20,20,.38), 0 14px 34px rgba(0,0,0,.45)}

/* ---------------- the numbers ---------------- */
/* NO PANEL. The pale bar was what made these read as a footer; on the
   open white, with the street running along the top of them, they are
   part of the section instead of a summary stuck underneath it. */
.vzwf-bar{grid-row:3;position:relative;
  padding:clamp(14px,2vh,22px) 0 clamp(6px,1vh,12px)}

.vzwf-bar__eb{display:inline-flex;align-items:center;gap:10px;font-size:12.5px;letter-spacing:.22em;
  text-transform:uppercase;font-weight:800;color:var(--vz-green-tx);margin:0 0 18px}
.vzwf-bar__eb::before{content:"";width:11px;height:11px;border-radius:50%;background:var(--vz-green);
  animation:vzwfPulseDot 2.6s ease-in-out infinite}
@keyframes vzwfPulseDot{0%,100%{transform:scale(1);opacity:.95}50%{transform:scale(1.5);opacity:.4}}
.vzwf-bar__row{display:grid;grid-template-columns:repeat(5,1fr);gap:clamp(12px,1.8vw,32px)}

/* the numbers, plainly: a rule, a figure, a line of context.
   THE RULE IS TWO ELEMENTS, not a border — an ink line that draws
   left to right as the band lands, and a short green segment that
   draws after it. A border cannot be animated along its length; a
   scaled pseudo-element can, and it costs nothing because scaleX is
   compositor work rather than layout. */
.vzwf-stat{position:relative;padding-top:13px;
  transition:transform .35s cubic-bezier(.2,.8,.2,1)}
.vzwf-stat::before,.vzwf-stat::after{content:"";position:absolute;left:0;top:0;height:3px;
  transform-origin:0 50%;transform:scaleX(0)}
.vzwf-stat::before{right:0;background:var(--vz-ink)}
.vzwf-stat::after{width:28%;background:var(--vz-green);z-index:1;
  transition:width .45s cubic-bezier(.2,.8,.2,1)}
.vzwf.vzwf-is-set .vzwf-stat::before{animation:vzwfRule .62s cubic-bezier(.2,.85,.25,1) var(--rd,0s) forwards}
.vzwf.vzwf-is-set .vzwf-stat::after{animation:vzwfRule .5s cubic-bezier(.2,.85,.25,1) calc(var(--rd,0s) + .2s) forwards}
@keyframes vzwfRule{to{transform:scaleX(1)}}
.vzwf-stat:hover{transform:translateY(-4px)}
.vzwf-stat:hover::after{width:100%}
/* THE FIGURES BREAK THE BRAND, like the trade words do on hover.
   Bungee is a signage face — wider than Montserrat, so it is set a
   little smaller and still reads larger. */
.vzwf-stat b{display:block;font-family:'Bungee','Montserrat',sans-serif;font-weight:400;
  font-size:clamp(19px,2.35vw,38px);letter-spacing:-.012em;line-height:1;
  margin-bottom:9px;color:var(--vz-ink)}
/* a mark from the wall over every figure */
.vzwf-stat__ic{display:block;height:clamp(26px,3vh,38px);margin:11px 0 10px;overflow:visible}
.vzwf-stat__ic path{fill:none;stroke:var(--vz-ink);stroke-width:2.7;stroke-linecap:round;
  stroke-linejoin:round;vector-effect:non-scaling-stroke;opacity:.86}
.vzwf-stat__ic circle.vzwf-node{fill:var(--vz-green);stroke:none;opacity:.9}
.vzwf-stat:hover .vzwf-stat__ic path{stroke:var(--vz-green-dk)}
.vzwf-stat i{font-style:normal;display:block;font-size:12.5px;line-height:1.35;
  font-weight:500;color:rgba(25,20,20,.72);max-width:21ch}

/* ==================================================================
   ON A PHONE THE NUMBERS LEAVE THE STAGE
   A pinned stage is one screen, and one screen was being asked to
   hold a headline, four panels and five figures. Something had to be
   too small and it was the thing that matters most — the videos.
   So below 860px the numbers come OUT of the pinned stage and flow
   underneath it on white. The stage then has the headline and the
   panels and nothing else, and the playing clip gets roughly half a
   phone screen instead of a fifth of one.
   ================================================================== */
.vzwf-bar--flow{position:relative;clip-path:none !important;background:#fff;
  margin:0;padding:clamp(26px,5vh,40px) clamp(14px,4vw,22px) clamp(32px,6vh,46px)}
.vzwf-bar--flow .vzwf-bar__eb{margin-bottom:14px}
/* THE RULES WENT MISSING BECAUSE THE BAR MOVED HOUSE. They are drawn
   by a rule that reads ".vzwf.vzwf-is-set .vzwf-stat::before" — and once
   the bar is lifted out to sit after the track, it is no longer
   inside .vzwf, so that selector stops matching and the lines sit at
   scaleX(0) forever. Same animation, asked for again from its new
   home. */
.vzwf-bar--flow.vzwf-bar--seen .vzwf-stat::before{
  animation:vzwfRule .62s cubic-bezier(.2,.85,.25,1) var(--rd,0s) forwards}
.vzwf-bar--flow.vzwf-bar--seen .vzwf-stat::after{
  animation:vzwfRule .5s cubic-bezier(.2,.85,.25,1) calc(var(--rd,0s) + .2s) forwards}
.vzwf-bar--flow .vzwf-bar__row{grid-template-columns:1fr 1fr;gap:18px 20px}

/* ==================================================================
   MOBILE — THE SAME EXPERIENCE, RESTACKED
   The sweep, the pinned stage, the clips taking turns and the one
   that is playing being the big one all survive. What changes is the
   arrangement: four panels side by side on a 390px screen is four
   slivers, so the playing one goes full width on top and the other
   three sit in a row beneath it as thumbnails. Tapping a thumbnail
   promotes it — the same gesture hover performs on a desktop.

   THE STAGE HEIGHT IN THIS BLOCK IS SUPERSEDED at the bottom of the
   file by the lvh rule. Left here so the block still reads whole.
   ================================================================== */
@media (max-width:860px){
  .vzwf-panels{--g:8px}
  .vzwf__stage{height:calc(100dvh - var(--vzwf-top,0px))}
  .vzwf{height:240vh}
  .vzwf__in{padding:clamp(18px,3vh,30px) clamp(14px,4vw,22px) clamp(16px,2.6vh,26px);
    grid-template-rows:auto minmax(0,1fr);
    row-gap:clamp(10px,1.6vh,18px)}
  .vzwf-head{max-width:none}
  .vzwf-head h2{font-size:clamp(24px,7.2vw,34px)}
  .vzwf-head__eb{font-size:10px;margin-bottom:9px}

  .vzwf-card__cr{font-size:9px;letter-spacing:.12em;left:10px;top:9px;gap:5px}
  .vzwf-card__cr::before{width:5px;height:5px}
  .vzwf-card__h{left:11px;right:auto;bottom:10px;
    max-width:calc(100% - 22px);font-size:clamp(14px,4.4vw,20px)}
  /* the thumbnails are too small for a word to live in */
  .vzwf-card.vzwf-is-thumb .vzwf-card__h{font-size:clamp(11px,3.2vw,14px);bottom:8px;left:9px}
  .vzwf-card.vzwf-is-thumb .vzwf-card__cr{display:none}
  /* a phone has no hover, so the open panel simply wears its face */
  .vzwf-card.vzwf-is-open .vzwf-card__h{
    font-family:var(--disp,'Bungee'),'Montserrat',sans-serif;font-weight:400;
    letter-spacing:.01em;
        text-shadow:3px 3px 0 rgba(25,20,20,.5), 0 12px 34px rgba(0,0,0,.45)}

  .vzwf-bar__row{grid-template-columns:1fr 1fr;gap:14px 18px}
  .vzwf-stat__ic{height:22px;margin:8px 0 7px}
  .vzwf-stat b{font-size:clamp(20px,6.4vw,28px);margin-bottom:5px}
  .vzwf-stat i{font-size:10.5px;max-width:none}
  .vzwf-bar__eb{font-size:10.5px;margin-bottom:10px}
  .vzwf-bar{padding:clamp(8px,1.4vh,14px) 0 0}

  .vzwf-rail{height:74px;gap:10px;padding:0 12px}
  .vzwf-rail__eb{font-size:9px;letter-spacing:.16em}
  .vzwf__over h2{font-size:clamp(27px,7.4vw,36px)}
  .vzwf__eb{font-size:10px;letter-spacing:.08em;margin-bottom:14px}
  .vzwf__sub{font-size:14.5px;margin-bottom:20px;max-width:34ch}
  .vzwf__over-in{padding-bottom:78px}
  .vzwf__more{bottom:6px}
  .vzwf__more span{font-size:9px;letter-spacing:.16em}
}
@media (max-width:420px){
  .vzwf-bar__row{grid-template-columns:1fr 1fr;gap:12px 14px}
  .vzwf-stat b{font-size:clamp(19px,6.6vw,26px)}
}
@media (hover:none){
  .vzwf-card.vzwf-is-open .vzwf-card__h{
    font-family:var(--disp,'Bungee'),'Montserrat',sans-serif;font-weight:400;
    letter-spacing:.01em;
        text-shadow:3px 3px 0 rgba(25,20,20,.5), 0 12px 34px rgba(0,0,0,.45)}
}
@media (prefers-reduced-motion:reduce){
  .vzwf{height:auto}
  .vzwf__stage{position:static;height:auto;background:#fff}
  .vzwf__vid,.vzwf__over,.vzwf__sheet,.vzwf__edge,.vzwf-rail{display:none}
  .vzwf__in{position:static;display:block}
  .vzwf-sw{clip-path:none !important}
  .vzwf-panels{display:grid;grid-template-columns:1fr 1fr;gap:12px}
  .vzwf-card{position:relative;top:auto;left:auto;width:auto;height:auto;aspect-ratio:3/2}
  .vzwf-card__img{width:100% !important;left:0;transform:none}
  .vzwf-bar__eb::before,.vzwf__more svg{animation:none !important}
  .vzwf-stat::before,.vzwf-stat::after{transform:scaleX(1)}
}

/* ======================================================================
   FULL BLEED, AND FLUSH TO THE HEADER
   --vw is the real viewport width written by the script: it excludes the
   scrollbar, which plain 100vw does not, and that difference is what
   causes sideways scroll on a full-bleed row.
   ====================================================================== */
.vzwf{
  width:var(--vw,100vw); max-width:var(--vw,100vw);
  margin-left:calc(50% - var(--vw,100vw) / 2);
  margin-right:calc(50% - var(--vw,100vw) / 2);
}
.dnd-section:has(.vzwf){ padding-top:0 !important; }
.body-container:has(.vzwf){ padding-top:0 !important; }

/* ======================================================================
   HEADROOM FOR THE OVERLAID HEADER
   Below 1150px the header module lifts out of flow and floats over this
   video, so the top ~104px of the stage sits underneath it. The hero
   copy is centred in the FULL stage height, including the hidden part,
   which is what puts the eyebrow under the logo. Padding equal to the
   header height moves the centre into the band you can actually see.
   IF YOU CHANGE THE HEADER, CHANGE --vzwf-headroom.
   ====================================================================== */
@media (max-width:1149px){
  .vzwf{ --vzwf-headroom:104px; }
  .vzwf__over{ padding-top:var(--vzwf-headroom); }
}

/* the dot field over the video, carried over from the hero it replaces */
.vzwf__dots{position:absolute;inset:0;z-index:1;width:100%;height:100%;
  display:block;pointer-events:none}
/* ==================================================================
   ONE SCRIM, AND A LIGHTER ONE
   It was tuned twice — once for desktop, once for mobile — which is
   how the same footage ended up looking like two different clips. One
   value now, about half the original weight. The copy's own
   text-shadows were always doing the real work of legibility; the
   scrim is only there to stop a bright frame washing the headline out
   entirely. It is switched OFF below — see section A.
   ================================================================== */
.vzwf__scrim{position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    linear-gradient(180deg, rgba(25,20,20,.24) 0%, rgba(25,20,20,.05) 42%, rgba(25,20,20,.38) 100%),
    radial-gradient(ellipse 60% 50% at 50% 48%, rgba(25,20,20,.16) 0%, rgba(25,20,20,0) 75%)}
/* ==================================================================
   THE PAUSE CONTROL IS NOW GENUINELY NOT THERE UNTIL IT IS FOCUSED
   opacity:0 still draws an element — it has a box, a background and a
   border, and anything that overrides opacity makes it visible. A
   round control keeps appearing in the top corner of the video and
   this sits in exactly that spot, so it is clipped to a single pixel
   until a keyboard reaches it. If a circle is still there afterwards
   it is definitively Safari's and not ours.
   ================================================================== */
.vzwf__pause{position:absolute;z-index:8;right:18px;top:18px;
  width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);
  padding:0;margin:0;border:0;background:transparent;color:#fff;font:inherit;
  white-space:nowrap;pointer-events:none;cursor:pointer}
.vzwf__pause:focus-visible{width:34px;height:34px;clip:auto;clip-path:none;
  overflow:visible;display:grid;place-items:center;font-size:10px;line-height:1;
  background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.32);
  border-radius:4px;pointer-events:auto;
  outline:2px solid var(--vz-green);outline-offset:2px}
@media (prefers-reduced-motion:reduce){ .vzwf__dots,.vzwf__pause{display:none} }


/* ======================================================================
   A.  THE SCRIM, OFF

   The element and its gradients are LEFT IN PLACE and turned off with a
   single number instead of being deleted. Put `--vzwf-scrim: 1` back and
   the old look returns exactly. Any value between works: .35 is "half of
   what it was", which is the setting to try if the headline turns out to
   need a little help on the brightest cut.

   The copy's own text-shadows were always doing the real work of
   legibility — the headline carries 0 2px 26px, the eyebrow 0 1px 10px,
   the subhead 0 1px 14px. So this is a brightness change, not a
   legibility change.

   THE OTHER DARK LAYER is not in this module: the header's own 190px
   gradient lives in the vzhead pane as
   `html.vzh-hero-overlay .vzhead .vzhead-main::before`. With the hero
   scrim gone the video is brighter under the nav too, so that gradient
   now does all the work of keeping white nav links readable — which is
   why it is NOT switched off here.
   ====================================================================== */
.vzwf {
  --vzwf-scrim: 0;
}
.vzwf .vzwf__scrim {
  opacity: var(--vzwf-scrim, 1);
}

/* ----------------------------------------------------------------------
   THE `--vzwf-vh` STAGE RULES THAT USED TO LIVE HERE ARE DELETED.

   They read `height: calc(var(--vzwf-vh, 100dvh) - var(--vzwf-top, 0px))`
   and that custom property was written from the JS pane on every
   visualViewport resize AND scroll event. So the pinned stage was being
   re-sized while you scrolled, which:

     - grew the panel row's 1fr track, so the merchant videos changed size
     - changed the document height, so content below jumped and the scroll
       position stopped meaning what it meant a frame earlier
     - lagged the toolbar by a frame or two, which is the white band

   That was my change and it was the wrong idea. The stage is sized once
   now — see section 2. The JS that wrote the property is deleted from
   the JS pane in the same ship.
   ---------------------------------------------------------------------- */


/* ======================================================================
   C.  CLEARANCE UNDER THE HEADER  (desktop)

   WHAT WAS WRONG, MEASURED
     header bottom   y = 82
     headline top    y = 79
     clearance       -3px

   WHY IT APPEARED
   The header used to be `position: sticky`, which RESERVED its 82px at
   the top of the page, so the sweep page began 82px down and
   `--vzwf-top` measured 82. Going `fixed` handed that 82px back to the
   video — the point of the exercise — but the sweep page sits in the
   same pinned stage and lost the same 82px.

   THE NUMBER IS THE HEADER'S REAL HEIGHT. `--vzwf-headh` is 82px because
   that is `--main-h` in the vzhead pane at this breakpoint. IF YOU
   CHANGE THE HEADER'S HEIGHT, CHANGE THIS — a module cannot read a
   custom property set on an element outside its own tree.
   ====================================================================== */
@media (min-width: 861px) {
  .vzwf {
    --vzwf-headh: 82px;
  }

  .vzwf .vzwf__in {
    padding-top: calc(var(--vzwf-headh, 82px) + clamp(26px, 3vh, 46px));
  }
}


/* ======================================================================
   D.  ALIGNMENT WITH THE REST OF THE HOMEPAGE

   WHAT WAS WRONG, MEASURED at a 2020px viewport
     site container   max-width 1240px + 24px gutters, left edge x = 410
     sweep page       no container at all, padding 0 54px, left edge x = 54

   `.vzwf__in` is a three-row grid — headline, panels, numbers. Rows 1
   and 3 are indented into the container; row 2 is left alone, so the
   merchant panels stay full bleed.

     margin-left: max(0px, calc((100% - 1152px) / 2))

   `100%` on a grid item resolves against the grid's CONTENT box, which
   is already inside `.vzwf__in`'s own 54px padding — which is why the
   number is 1152 and not 1240. Verified: both land at x = 401.

   TO PULL THE PANELS IN TOO: add `.vzwf .vzwf-panels` to the first
   selector below.
   ====================================================================== */
@media (min-width: 861px) {
  .vzwf {
    --vzwf-container: 1152px;
  }

  .vzwf .vzwf-head,
  .vzwf .vzwf-bar {
    margin-left: max(0px, calc((100% - var(--vzwf-container, 1152px)) / 2));
  }

  /* the numbers are a five-column grid and need both sides, or they
     would stretch to the full-bleed right edge and read as lopsided */
  .vzwf .vzwf-bar {
    margin-right: max(0px, calc((100% - var(--vzwf-container, 1152px)) / 2));
  }
}


/* ======================================================================
   TRADE WORD → WHITE VIZYPAY LOGO ON HOVER

   At rest   the industry name, exactly as it looks now
   On hover  the name fades out and the white VizyPay wordmark fades in,
             same position, same optical size

   THE IMAGE URL IS DELIBERATELY NOT THE OBVIOUS ONE. The hubfs CDN
   domain REFUSES HubSpot's image resizer (404 on every form). A
   ROOT-RELATIVE `/hs-fs/hubfs/...` URL does get the resizer and resolves
   against whichever domain is serving the page, so the same line works
   on the sandbox and on production. 520px covers the 129px render at 4x.

   THE BACKGROUND IS DECLARED AT REST, AT opacity:0, so the PNG is
   fetched on page load. `display: none` would skip the fetch and the
   first card you touched would fade the word out to nothing.

   3.68 is the logo's aspect ratio (5631 / 1530). IF YOU SWAP THE FILE,
   recompute it — a wrong ratio shows as the wordmark floating in a
   letterboxed gap, which is harder to spot than distortion.
   ====================================================================== */
.vzwf {
  --vzwf-logo-h: 1.25em;
  --vzwf-logo-y: -.04em;
}

.vzwf .vzwf-card__h::after {
  content: "";
  position: absolute;
  left: 0;
  top: calc(50% + var(--vzwf-logo-y));
  height: var(--vzwf-logo-h);
  width: calc(var(--vzwf-logo-h) * 3.68);   /* 5631 / 1530 — see note */
  transform: translateY(-50%);
  background: url('/hs-fs/hubfs/White%20VizyPay%20Logos/White%20VP%20Logo%20M.png?width=520&name=White%20VP%20Logo%20M.png')
              left center / contain no-repeat;
  filter: drop-shadow(0 4px 16px rgba(0, 0, 0, .6));
  opacity: 0;
  transition: opacity .3s ease;
  pointer-events: none;
}

/* `color: transparent` rather than `opacity: 0` on the heading, because
   the heading is the pseudo-element's containing block — fading the
   element would fade the logo with it.

   `text-shadow: none` is NOT optional. A shadow is painted from the
   glyph's alpha, so transparent text still casts a perfectly visible
   dark halo in the shape of the word. */
.vzwf .vzwf-card.vzwf-loose .vzwf-card__h {
  color: transparent !important;
  text-shadow: none;
  font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI',
               Helvetica, Arial, sans-serif;
  font-weight: 800;
  font-size: clamp(17px, 1.7vw, 28px);
  letter-spacing: -.025em;
  transition: color .26s ease;
}

.vzwf .vzwf-card.vzwf-loose .vzwf-card__h::after {
  opacity: 1;
}

/* MOBILE KEEPS THE PLAIN WORD. There is no hover on a phone, so the
   logo swap never fires — but the module ALSO switched the open card to
   a display face on touch, in two separate rules. Both are neutralised,
   because a touch laptop matches only `hover: none` and missing that
   leaves the old face on exactly the devices nobody tests on. */
@media (max-width: 860px) {
  .vzwf .vzwf-card.vzwf-is-open .vzwf-card__h {
    font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI',
                 Helvetica, Arial, sans-serif;
    font-weight: 800;
    letter-spacing: -.025em;
    text-shadow: 3px 3px 0 rgba(25, 20, 20, .5), 0 12px 34px rgba(0, 0, 0, .45);
  }
}

@media (hover: none) {
  .vzwf .vzwf-card.vzwf-is-open .vzwf-card__h {
    font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI',
                 Helvetica, Arial, sans-serif;
    font-weight: 800;
    letter-spacing: -.025em;
    text-shadow: 3px 3px 0 rgba(25, 20, 20, .5), 0 12px 34px rgba(0, 0, 0, .45);
  }
}

@media (prefers-reduced-motion: reduce) {
  .vzwf .vzwf-card__h,
  .vzwf .vzwf-card__h::after {
    transition: none;
  }
}


/* ======================================================================
   THE NUMBERS  ·  HIERARCHY

   The band was not losing attention because it is understated. It was
   losing attention to four autoplaying merchant videos directly above
   it. Small animation next to large moving footage loses every time.
   So this adds no new motion — it gives the eye somewhere to land.
   Five figures at identical weight is five things asking for attention
   simultaneously, which reliably produces none.

   25,000+ LEADS, NOT $12B+. $12B+ is the bigger number; 25,000+ is the
   better one for this audience. It is also first in the array, so
   promoting it needs no reordering and no JS change.
   ====================================================================== */
@media (min-width: 861px) {

  /* 1.6fr against four 1fr. Not `align-items: end` — the rules are
     absolutely positioned at `top: 0` of each stat, so bottom-aligning
     the tracks would scatter five rules down the row. */
  .vzwf .vzwf-bar__row {
    grid-template-columns: 1.6fr repeat(4, 1fr);
  }

  .vzwf .vzwf-stat:first-child b {
    font-size: clamp(30px, 3.9vw, 58px);
  }
  .vzwf .vzwf-stat:first-child i {
    font-size: 13.5px;
    max-width: 18ch;
  }
  .vzwf .vzwf-stat:first-child .vzwf-stat__ic {
    height: clamp(30px, 3.6vh, 46px);
  }

  .vzwf .vzwf-stat:first-child::before {
    height: 4px;
    background: var(--vz-green);
  }
  .vzwf .vzwf-stat:first-child::after {
    display: none;
  }

  /* Slightly lighter ink as well as smaller. Size alone makes them
     small; size plus a touch less weight makes them deliberately
     secondary. .82 keeps them at 9.9:1 on white. */
  .vzwf .vzwf-stat:not(:first-child) b {
    font-size: clamp(17px, 1.9vw, 30px);
    color: rgba(25, 20, 20, .82);
  }
  .vzwf .vzwf-stat:not(:first-child) i {
    font-size: 11.5px;
  }
  .vzwf .vzwf-stat:not(:first-child) .vzwf-stat__ic {
    height: clamp(22px, 2.4vh, 30px);
  }
}

@media (max-width: 860px) {
  .vzwf .vzwf-bar__row .vzwf-stat:first-child {
    grid-column: 1 / -1;
  }
  .vzwf .vzwf-stat:first-child b {
    font-size: clamp(34px, 11vw, 54px);
  }
  .vzwf .vzwf-stat:first-child i {
    font-size: 12.5px;
    max-width: 26ch;
  }
  .vzwf .vzwf-stat:first-child .vzwf-stat__ic {
    height: 30px;
  }
  .vzwf .vzwf-stat:first-child::before {
    height: 4px;
    background: var(--vz-green);
  }
  .vzwf .vzwf-stat:first-child::after {
    display: none;
  }
}

/* THE STAGGER IS MULTIPLIED, NOT REPLACED. `--rd` is written INLINE on
   each stat by the JS (`style="--rd:0.18s"`), and an inline custom
   property cannot be overridden from a stylesheet at any specificity.
   So the delay is scaled inside the calc instead: 0.09s x 1.55 gives
   about 0.14s between figures, a 0.56s spread, with no JS change. */
.vzwf.vzwf-is-set .vzwf-bar .vzwf-stat::before {
  animation: vzwfRule .78s cubic-bezier(.2, .85, .25, 1)
             calc(var(--rd, 0s) * 1.55) forwards;
}
.vzwf.vzwf-is-set .vzwf-bar .vzwf-stat::after {
  animation: vzwfRule .6s cubic-bezier(.2, .85, .25, 1)
             calc(var(--rd, 0s) * 1.55 + .24s) forwards;
}

@media (prefers-reduced-motion: reduce) {
  .vzwf.vzwf-is-set .vzwf-bar .vzwf-stat::before,
  .vzwf.vzwf-is-set .vzwf-bar .vzwf-stat::after {
    animation: none;
    transform: scaleX(1);
  }
}


/* ======================================================================
   THE NUMBERS  ·  FLOATING CARDS

   White cards, a green line across every top, lift on hover, nothing
   coloured inside.

   THE GREEN LINE IS ON ALL FIVE, which means the hero no longer leads
   by its rule — it leads by size alone, 58px against 30px. That is
   enough, and it is cleaner: one signal rather than two saying the same
   thing. The green segment (`::after`) is switched off rather than
   restyled; at 4px a 28% overlay on a green line reads as a seam.

   TWO SHADOWS, NOT ONE. A single large soft shadow reads as a blur
   under the card. A tight contact shadow plus a wider ambient one is
   what reads as "lifted off the page".
   ====================================================================== */
.vzwf .vzwf-bar__row {
  gap: clamp(10px, 1.2vw, 20px);
}

.vzwf .vzwf-stat {
  background: #FFFFFF;
  border-radius: 16px;
  overflow: hidden;
  padding: 20px 17px 18px;
  box-shadow:
    0 1px 2px rgba(25, 20, 20, .06),      /* contact */
    0 4px 14px -4px rgba(25, 20, 20, .12); /* ambient */
  transition: transform .35s cubic-bezier(.2, .8, .2, 1),
              box-shadow .35s cubic-bezier(.2, .8, .2, 1);
}

.vzwf .vzwf-stat:hover {
  transform: translateY(-6px);
  box-shadow:
    0 2px 4px rgba(25, 20, 20, .07),
    0 18px 34px -12px rgba(25, 20, 20, .26);
}

.vzwf .vzwf-stat::before {
  height: 4px;
  right: 0;
  background: var(--vz-green);
}

.vzwf .vzwf-stat::after {
  display: none;
}

/* the icon's 11px top margin was spacing it off a bare rule; inside a
   padded card the card's own padding does that job */
.vzwf .vzwf-stat__ic {
  margin: 4px 0 10px;
}

@media (max-width: 860px) {
  .vzwf .vzwf-bar__row {
    gap: 12px;
  }
  .vzwf .vzwf-stat {
    padding: 17px 15px 15px;
    border-radius: 14px;
    /* no hover on a phone, so the resting shadow carries the whole
       effect and is worth a little more weight */
    box-shadow:
      0 1px 2px rgba(25, 20, 20, .07),
      0 5px 16px -5px rgba(25, 20, 20, .16);
  }
}

@media (prefers-reduced-motion: reduce) {
  .vzwf .vzwf-stat {
    transition: none;
  }
}


/* ======================================================================
   MOBILE FIX A  ·  HEADROOM FOR THE FIXED HEADER ON THE SWEEP PAGE

   The module handles this for the FIRST page: below 1149px `.vzwf__over`
   gets `padding-top: var(--vzwf-headroom)` at 104px. The SWEEP page is a
   different element — `.vzwf__in` — whose mobile padding tops out at
   30px, against a header that is 68–82px tall. Desktop was fixed with
   `--vzwf-headh`; that rule is scoped `min-width: 861px` and never
   reached the phone.

   THE TWO NUMBERS ARE THE HEADER'S REAL HEIGHTS. `--main-h` in the
   vzhead pane is 82px, dropping to 68px at 600px. IF YOU CHANGE THE
   HEADER HEIGHT, CHANGE THESE.
   ====================================================================== */
@media (max-width: 860px) {
  .vzwf .vzwf__in {
    padding-top: calc(82px + clamp(10px, 1.6vh, 18px));
  }
}

@media (max-width: 600px) {
  .vzwf .vzwf__in {
    padding-top: calc(68px + clamp(10px, 1.6vh, 18px));
  }
}


/* ======================================================================
   MOBILE FIX B  ·  THE STAT CARDS, ON A PHONE

   On mobile `placeBar()` does

     vzp.parentNode.insertBefore(bar, vzp.nextSibling);
     bar.classList.add('vzwf-bar--flow');

   The stats band is MOVED OUT of `.vzwf` and becomes its SIBLING. Which
   means every selector written as `.vzwf .vzwf-stat { … }` stops
   matching the instant that move happens — not overridden, simply no
   longer true. The module's own mobile rules are all written
   `.vzwf-bar--flow …` for exactly this reason. These now match that.
   ====================================================================== */
@media (max-width: 860px) {

  .vzwf-bar--flow .vzwf-stat {
    background: #FFFFFF;
    border-radius: 14px;
    overflow: hidden;
    padding: 17px 15px 15px;
    box-shadow:
      0 1px 2px rgba(25, 20, 20, .07),
      0 5px 16px -5px rgba(25, 20, 20, .16);
  }

  .vzwf-bar--flow .vzwf-stat::before {
    height: 4px;
    right: 0;
    background: var(--vz-green, #81b338);
  }
  .vzwf-bar--flow .vzwf-stat::after {
    display: none;
  }

  .vzwf-bar--flow .vzwf-stat__ic {
    margin: 4px 0 10px;
  }

  .vzwf-bar--flow .vzwf-bar__row {
    gap: 12px;
  }

  /* and the hierarchy, which broke for the identical reason */
  .vzwf-bar--flow .vzwf-bar__row .vzwf-stat:first-child {
    grid-column: 1 / -1;
  }
  .vzwf-bar--flow .vzwf-stat:first-child b {
    font-size: clamp(34px, 11vw, 54px);
  }
  .vzwf-bar--flow .vzwf-stat:first-child i {
    font-size: 12.5px;
    max-width: 26ch;
  }
  .vzwf-bar--flow .vzwf-stat:first-child .vzwf-stat__ic {
    height: 30px;
  }
}


/* ======================================================================
   THE SWEEP HEADLINE'S COLOUR, STATED EXPLICITLY

   The live homepage template loads template_homepage.min.css, which
   carries a bare `h2 { color: rgb(141,171,56) }`. That stylesheet does
   NOT load on the sandbox preview, which is why this only appeared at
   launch. This headline was the only one on the page inheriting its
   colour — every other module names its own — so the theme's green won
   by default rather than by specificity.

   `.vzwf .vzwf-head h2` is (0,2,1) against the theme's (0,0,1), so no
   !important is needed. The italic "is" keeps its green because the em
   carries its own colour.
   ====================================================================== */
.vzwf .vzwf-head h2 {
  color: var(--vz-ink, #191414);
}


/* ======================================================================
   ======================================================================
   SCROLL LENGTH AND A STAGE THAT STOPS MOVING
   The last three sections of this file. They have to stay last.
   ======================================================================

   Four complaints, two causes, and both causes are mine.

     1. too many scrolls to get past the sweep      -> dead tail on track
     2. panels change size as you scroll            -> stage height moving
     3. content below jumps while scrolling         -> same
     4. white space under the hero video            -> same

   THE ONE CAUSE BEHIND 2, 3 AND 4

   On a phone the browser toolbar collapses as you scroll, so the
   visible viewport GROWS mid-scroll, and the pinned stage was sized to
   follow it — first with `100dvh`, then with the `--vzwf-vh` JS I added
   to stop the press rail being born below the fold.

   Every time that number changed:
     - the stage got taller, so the panel row's `1fr` track got taller,
       so every video panel grew.
     - the document got taller, so everything below shifted, and the
       scroll position stopped meaning what it meant a frame ago.
     - and the JS version only updated on an event, so it lagged the
       toolbar by a frame or two — which is the white band.

   The fix is not to track the viewport more accurately. It is to STOP
   TRACKING IT.
   ====================================================================== */


/* ======================================================================
   1. THE TRACK, WITHOUT THE DEAD TAIL

   The sweep does not run for the whole track. Its easing window was
   (p - 0.08) / 0.72, so it finished at p = 0.80 and the last fifth of
   the scroll was the stage sitting there, already done, waiting to
   unpin. On mobile that was 48vh — roughly half a screen of scrolling
   where nothing happens, which is what reads as "why am I still here".

                    track    sweep window   sweep travel   dead tail
     mobile  before  240vh   0.08 -> 0.80      173vh         48vh
     mobile  after   185vh   0.05 -> 0.95      167vh          9vh
     desktop before  265vh   0.08 -> 0.80      191vh         53vh
     desktop after   205vh   0.05 -> 0.95      185vh         10vh

   So the sweep still takes about the same amount of scrolling to read.
   This is NOT the sweep getting faster — it is the empty scrolling
   afterwards being removed.

   THE WINDOW CHANGE IS ALREADY IN THE JS PANE SHIPPED WITH THIS FILE.
   Shorten the track without widening the window and the sweep genuinely
   does get faster, so the two files must go in together.
   ====================================================================== */
.vzwf {
  height: 205vh;
}

@media (max-width: 860px) {
  .vzwf {
    height: 185vh;
  }
}


/* ======================================================================
   2. A STAGE THAT IS SIZED ONCE

   `lvh` is the viewport with the browser chrome RETRACTED, and unlike
   `dvh` it does not change while you scroll. It is a constant, so the
   stage is sized once and never resized: nothing to grow, nothing to
   shift, no lag to show a gap.

   MOBILE ONLY. A desktop browser has no collapsing toolbar, so there
   was never anything to track there and the dvh rule above is already
   stable.

   THE @supports IS A REAL FALLBACK, not decoration: below Safari 15.4
   this rule does not apply and the module keeps its existing dvh
   behaviour, which is worse but not broken.

   THE TRADE, STATED HONESTLY: with the toolbar showing, the bottom
   ~70px of the stage sits underneath it. Nothing is cut off — the video
   fills it — but anything positioned at the very bottom edge is hidden
   until the toolbar collapses. That is exactly the press rail, and
   section 3 handles it.
   ====================================================================== */
@media (max-width: 860px) {
  @supports (height: 100lvh) {
    .vzwf .vzwf__stage {
      height: calc(100lvh - var(--vzwf-top, 0px));
    }
  }
}


/* ======================================================================
   3. THE PRESS RAIL, CLEAR OF THE TOOLBAR — MEASURED, NOT GUESSED

   My earlier attempt lifted the rail by `9vh` because that is roughly a
   toolbar. Roughly is not good enough and you could see it.

   `100lvh - 100svh` IS THE TOOLBAR HEIGHT, exactly, as the browser
   itself reports it:

     lvh = viewport with the chrome retracted
     svh = viewport with the chrome showing
     difference = the chrome

   Both are constants, so this is a fixed number computed once — it
   cannot animate, cannot lag, and cannot cause the resizing this whole
   section exists to remove. The rail then sits on the small-viewport
   bottom edge: flush above the toolbar when it is showing, floating a
   toolbar's height above the bottom when it is not. The safe-area inset
   is added on top for notched handsets, where the swipe indicator takes
   another 34px.

   ON A DEVICE WHERE lvh AND svh ARE EQUAL — a tablet in a browser with
   no collapsing chrome — the calc comes out to 0 + the inset, which is
   precisely today's behaviour. Nothing gets worse anywhere.

   The copy block's bottom padding tracks the same number so the
   headline never drifts into the logos.

   IF THE RAIL STILL SITS WRONG on your phone, the honest next step is a
   screenshot rather than another number from me — I cannot resize this
   browser to phone width and I have guessed at it twice already.
   ====================================================================== */
@media (max-width: 860px) {

  /* fallback for anything without svh/lvh: the inset alone, which is
     the behaviour that shipped before any of this */
  .vzwf .vzwf-rail {
    bottom: env(safe-area-inset-bottom, 0px);
  }
  .vzwf .vzwf__over-in {
    padding-bottom: calc(78px + env(safe-area-inset-bottom, 0px));
  }

  @supports (height: 100svh) and (height: 100lvh) {
    .vzwf .vzwf-rail {
      bottom: calc(100lvh - 100svh + env(safe-area-inset-bottom, 0px));
    }
    .vzwf .vzwf__over-in {
      padding-bottom: calc(78px + 100lvh - 100svh + env(safe-area-inset-bottom, 0px));
    }
  }
}


/* ======================================================================
   4. REDUCED MOTION HAS THE LAST WORD

   The module's reduced-motion block unpins the stage entirely
   (`position: static; height: auto`), but it appears EARLIER in this
   file than the lvh rule above and carries lower specificity, so
   without this repeat the lvh height would win and put back the very
   box that rule exists to remove.

   NOTHING MAY BE ADDED BELOW THIS BLOCK that sets a stage height.
   ====================================================================== */
@media (prefers-reduced-motion: reduce) {
  .vzwf {
    height: auto;
  }
  .vzwf .vzwf__stage {
    height: auto;
  }
}
/* ======================================================================
   TWO FIXES  —  APPEND to the END of the hero module's CSS pane.
   Nothing above needs touching. Both are corrections to my own work.

     A. "on" stranded on its own line
     B. the press rail reading as a floating dark band
   ====================================================================== */


/* ======================================================================
   A.  NO MORE ORPHANS IN THE HERO HEADLINE

   "Payments you can bank / on" — one word alone on the last line. You
   have flagged this before and it is the kind of thing that should not
   need flagging twice.

   `text-wrap: balance` hands the line-breaking to the browser with an
   instruction to even the lines out rather than greedily fill the first
   one. On a short headline that removes a single-word last line by
   construction: the browser tries every break point and picks the one
   with the smallest difference between lines, and "…bank / on" is the
   worst possible score.

   WHY NOT A NON-BREAKING SPACE BETWEEN "bank" AND "on"
   That is the old fix and it works, but it lives in the copy rather
   than the stylesheet — so it has to be re-typed by whoever next edits
   the headline in the page editor, and it silently stops working the
   day the words change. This rule holds for any headline you put here.

   IT IS SAFE WHERE IT IS NOT SUPPORTED. An unrecognised value is
   dropped, and the headline breaks exactly as it does today. No
   fallback needed, nothing to go wrong.

   `balance` is capped at a handful of lines by every engine that
   implements it, which is why the SUBHEAD gets `pretty` instead —
   that one is tuned for longer running text and only polices the last
   line. If the subhead ever strands a word too, it is already handled.

   THE SWEEP HEADLINE gets the same treatment. It is three lines on a
   phone and is one copy edit away from the identical problem.
   ====================================================================== */

.vzwf .vzwf__over h2,
.vzwf .vzwf-head h2 {
  text-wrap: balance;
}

.vzwf .vzwf__sub {
  text-wrap: pretty;
}


/* ======================================================================
   B.  THE RAIL STOPS FLOATING

   WHAT I GOT WRONG
   Last ship lifted the rail off the bottom edge with
   `bottom: calc(100lvh - 100svh + …)` so the logos would clear the
   browser toolbar. The lift worked — but the rail carries its OWN
   background, a 74px gradient fading from transparent down to
   rgba(10,9,8,.72). Moving the element moved the gradient with it.

   So the gradient now ENDS about 70px above the bottom of the frame,
   on a hard horizontal edge, with bare video below it. That is the
   "dark grey shadow behind them with clear video underneath" — a
   floating bar, which is exactly what a gradient is supposed to avoid
   being. The gradient's whole job is to have no bottom edge.

   THE FIX: MOVE THE LOGOS, NOT THE ELEMENT
   The rail goes back to `bottom: 0`, so its background still runs to
   the bottom edge of the stage and still has nowhere to end. Instead
   it is made TALLER by the toolbar height and given matching
   bottom PADDING.

   The rail is `display: flex; align-items: center` and the module sets
   `box-sizing: border-box` globally, so the flex content box is
   (height - padding-bottom) — the logos centre in the top 74px and the
   padded strip below them is pure gradient. Same lift, no seam.

     before   74px tall at bottom:70px    -> hard edge, video below
     after   ~144px tall at bottom:0      -> logos at the same height,
                                             gradient runs off the frame

   The gradient simply spans a taller box, so the fade is gentler. That
   reads better than the 74px version did even before the lift.

   HORIZONTAL PADDING IS RESTATED, not inherited — the shorthand below
   replaces the module's `padding: 0 12px` outright, and leaving the 12
   out would push the logos to the screen edge.

   THE FALLBACK IS TODAY'S BEHAVIOUR. Without svh/lvh the rail is
   74px at bottom:0 with no bottom padding, which is the module's
   original. Nothing degrades.
   ====================================================================== */

@media (max-width: 860px) {

  /* back to the bottom edge, where a gradient belongs */
  .vzwf .vzwf-rail {
    bottom: 0;
    height: 74px;
    padding: 0 12px;
  }

  .vzwf .vzwf__over-in {
    padding-bottom: calc(78px + env(safe-area-inset-bottom, 0px));
  }

  @supports (height: 100svh) and (height: 100lvh) {
    .vzwf .vzwf-rail {
      /* 100lvh - 100svh IS the collapsing toolbar's height, as the
         browser itself reports it. Zero on anything without one. */
      height: calc(74px + 100lvh - 100svh + env(safe-area-inset-bottom, 0px));
      padding: 0 12px calc(100lvh - 100svh + env(safe-area-inset-bottom, 0px));
    }

    /* the copy keeps the same gap above the logos it has today */
    .vzwf .vzwf__over-in {
      padding-bottom: calc(78px + 100lvh - 100svh + env(safe-area-inset-bottom, 0px));
    }
  }
}


/* ----------------------------------------------------------------------
   REDUCED MOTION — unchanged. That block hides the rail entirely
   (`.vzwf-rail { display: none }`), so none of the above can reach it.
   ---------------------------------------------------------------------- */
/* ======================================================================
   THE HEADLINE AND THE LINE UNDER IT
   APPEND to the END of the hero module's CSS pane, after the orphan and
   rail fix. Nothing above needs touching.

   ----------------------------------------------------------------------
   THE DIAGNOSIS, FROM THE STYLESHEET RATHER THAN FROM THE SCREENSHOT

   Read the three rules in order as they exist today:

     .vzwf__eb       margin: 0 0 14px        eyebrow -> headline   14px
     .vzwf__over h2  margin: 0               headline -> subhead    0px
     .vzwf__sub      margin: 0 auto 24px     subhead  -> button    24px

   The subhead's TOP margin is zero. So the one gap in the stack that
   does not exist is the gap between the two elements that sit closest
   together in weight — a 29px bold headline and a 14.5px line of body
   copy. Nothing separates them but line-height, which means the
   headline's descenders land roughly on the subhead's cap height.

   That is why it reads as off while every individual element looks
   fine. It is not the wording, the sizes or the centring. Two things
   are touching that should not be.

   It was always like this. It only became noticeable once `text-wrap:
   balance` pulled the headline into two short even lines — a wide
   headline hid the collision, a narrow one puts both lines directly
   over the subhead.

   ----------------------------------------------------------------------
   WHAT THIS CHANGES

     1. a real gap between headline and subhead
     2. the subhead's measure brought closer to the headline's width, so
        the two read as one column rather than a narrow block sitting on
        a wider one
     3. a little more contrast on the subhead, now the scrim is off

   NOTHING ELSE MOVES. The eyebrow, the button, the press rail and the
   vertical centring are all untouched.
   ====================================================================== */


/* ======================================================================
   1. THE GAP

   Set on the SUBHEAD's top margin rather than the headline's bottom,
   because `.vzwf__over h2` is also the selector the theme fights over
   and the headline's `margin: 0` is load-bearing elsewhere in the
   centring. Adding to the subhead cannot disturb it.

   THE NUMBER IS PROPORTIONAL, NOT FIXED. A 29px headline needs more air
   under it than a 52px one does relatively, so a flat 16px would be
   right on a phone and mean on a laptop. The clamp tracks the viewport
   the same way the headline's own `clamp(28px, 4.4vw, 52px)` does.

   TO TUNE IT, change `--vzwf-copy-gap`. That is the only number in this
   file worth touching — raise it if the pair still feels cramped, drop
   it if they now read as two separate thoughts rather than one.
   ====================================================================== */

.vzwf {
  --vzwf-copy-gap: clamp(14px, 2.2vh, 22px);
}

.vzwf .vzwf__sub {
  margin-top: var(--vzwf-copy-gap);
}


/* ======================================================================
   2. ONE COLUMN, NOT TWO WIDTHS

   On mobile the subhead is capped at `34ch`, which at 14.5px is about
   238px — WIDER than the balanced headline, which lands around 200px.
   So the block is a narrow headline sitting on a wider paragraph, and
   the eye reads the mismatch as an error even though both are centred.

   `30ch` brings the paragraph to roughly the headline's width. The two
   then share an optical edge and the group reads as a single column.

   IT IS A `ch` AND THAT IS DELIBERATE: one `ch` is the width of a `0` in
   the current font, so the measure scales with the type rather than
   with the screen. If the subhead copy is ever re-written, the line
   length stays in the same comfortable range on its own.

   TO TUNE IT, change `--vzwf-sub-w`. Larger = wider paragraph, fewer
   lines. 32ch if it breaks to four lines on a small handset; 28ch if it
   still looks wide against the headline.

   DESKTOP IS LEFT ALONE. There the headline runs to 52px across a 940px
   block and the 64ch subhead sits comfortably inside it — the mismatch
   is a small-screen artefact of the balanced two-line headline.
   ====================================================================== */

@media (max-width: 860px) {
  .vzwf {
    --vzwf-sub-w: 30ch;
  }

  .vzwf .vzwf__sub {
    max-width: var(--vzwf-sub-w, 30ch);
    /* `margin: 0 auto 20px` in the mobile block sets all four sides, so
       the auto centring has to be restated or the narrower measure
       would sit flush left. */
    margin-left: auto;
    margin-right: auto;
  }
}


/* ======================================================================
   3. A LITTLE MORE CONTRAST

   The subhead is `rgba(255,255,255,.9)` and was tuned when the video
   still carried a scrim. With the scrim at 0 it is competing with live
   footage, and the first thing to go on a bright frame is the smallest,
   palest text on screen.

   `.94` is a small change deliberately — the text-shadow is doing the
   real work and always was. Going to full white would flatten the
   hierarchy between the subhead and the headline, which is the one
   relationship this file exists to fix.

   IF IT STILL WASHES OUT on the brightest part of the clip, the better
   lever is the scrim: `--vzwf-scrim: .25` in the pane above brings back
   a quarter of the original overlay across the whole hero, which is
   kinder than making one line of type fight for itself.
   ====================================================================== */

.vzwf .vzwf__sub {
  color: rgba(255, 255, 255, .94);
}


/* ----------------------------------------------------------------------
   NOT MEASURED ON A HANDSET — Chrome is not connected from here, so the
   gap and the measure are reasoned from the module's own numbers rather
   than read off your screen. Both are single custom properties at the
   top of their sections precisely so you can nudge them yourself
   without another round trip.
   ---------------------------------------------------------------------- */