/* HVO fx bundle (Mena, 2026-07-15) — Skiper UI ports:
   skiper61 spring mouse follower · skiper63 Apple squircle
   (corner-shape, wheel photos + all containers) · skiper106 smooth
   caret + auto-grow on text inputs · skiper26 circle-blur View
   Transition for the theme toggle (from top-right) · skiper94 page
   scroll progress. */

/* ── skiper61: spring mouse follower ─────────────────────────────── */
.hvo-cursor {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 99999;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #d6fd70;
  mix-blend-mode: difference;
  pointer-events: none;
  opacity: 0;
  will-change: transform, opacity;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  transition:
    width 0.2s cubic-bezier(0.2, 0.7, 0.2, 1),
    height 0.2s cubic-bezier(0.2, 0.7, 0.2, 1),
    border-radius 0.2s cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* (07-19: the 07-17 .is-grab lime-ring state over the creator wheel
   was removed on Mena's call — dot stays plain there. Later the same
   day the carousel level-up added .is-see below: over a wheel card
   WITH creator data the dot morphs into a "See {Name}" pill — that
   one is Mena's spec decision 4, not a revival of the grab ring.) */

/* ── is-see: "See {Name}" cursor pill over wheel cards (Task 3).
   fx.js sets data-label + animates the dot to the measured label
   width (width:auto can't animate). mix-blend-mode goes NORMAL while
   in pill form — difference blending would mangle the text. Lime bg +
   near-black text reads in both themes. */
/* Over a full-screen blurred overlay the difference-blend is both pointless
   (those backdrops are uniformly dark, lime stays lime) and expensive: a
   blend-mode element moving above a backdrop-filter region makes Chromium
   re-run the blur under the dot every frame - the "blur in the bg gets
   buggy, the cursor gets laggy" Mena reported on the Get Started form
   (2026-08-23). Blend goes normal while any such layer exists; the lead
   form and creator popup remove their layers from the DOM on close and a
   closed <dialog> drops [open], so the gate self-clears. Small blurred
   chips (player pills, footer language chip) are left alone: the dot only
   grazes them and their look depends on the blend. */
body:has(.hvo-lead-layer, .hvo-popup-layer, .hvo-dialog[open]) .hvo-cursor {
  mix-blend-mode: normal;
}

.hvo-cursor::after {
  content: attr(data-label);
  font: 600 13px/1 "Plus Jakarta Sans", sans-serif;
  letter-spacing: -0.01em;
  color: #131313;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.12s;
}
.hvo-cursor.is-link {
  width: 34px;
  height: 34px;
  mix-blend-mode: normal;
}
.hvo-cursor.is-see {
  mix-blend-mode: normal;
  border-radius: 999px;
  box-shadow: 0 2px 10px -2px rgba(9, 30, 60, 0.35);
}
.hvo-cursor.is-see::after {
  opacity: 1;
  transition: opacity 0.15s 0.06s;
}
/* mid label-swap (fx.js): text dips while the pill glides to the next
   name's width — card-to-card sweeps read as one continuous pill */
.hvo-cursor.is-see.is-swap::after {
  opacity: 0.15;
  transition: opacity 0.08s;
}
/* hidden measurer (fx.js) — font + horizontal padding MUST match the
   pill face above: the measured width IS the pill width */
.hvo-see-measure {
  position: absolute;
  left: -9999px;
  top: 0;
  visibility: hidden;
  font: 600 13px/1 "Plus Jakarta Sans", sans-serif;
  letter-spacing: -0.01em;
  white-space: nowrap;
  padding: 0 14px;
}
@media (hover: none), (prefers-reduced-motion: reduce) {
  .hvo-cursor { display: none; }
}

/* The dot IS the cursor (2026-07-17, Mena — matches the skiper61
   original, which sets cursor-none on its demo area; without this the
   native arrow rides on top of the dot). Gated to fine hover pointers
   with motion allowed — the same conditions under which fx.js creates
   the dot — so touch devices and reduced-motion users keep the native
   cursor. Exceptions: text-entry keeps the native I-beam (the dot
   hides there, fx.js) and fullscreen keeps a native cursor (the dot
   can't render inside a fullscreened subtree). */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  * { cursor: none !important; }
  /* text ENTRY keeps the I-beam; range sliders + selects ride the dot
     (2026-07-20 Mena: player controls must keep the site cursor) */
  input:not([type="range"]), textarea, [contenteditable="true"] { cursor: auto !important; }
  :fullscreen, :fullscreen * { cursor: auto !important; }

  /* The audience card stack was the one exception (native grab hand over
     the cards). Removed 2026-08-22 (Mena: "bring the dot"); see fx.js
     refreshState for why. */
}

/* ── skiper63: Apple squircle corners ────────────────────────────── */
/* NOT the demo's SVG goo filter: that only works on solid-color blocks
   — on photos it blurs the image's own colors into a halo past every
   corner (shipped wrong 07-14, caught by Mena 07-15). The real
   one-liner is CSS corner-shape (Chromium 139+): the element's
   existing border-radius corners render as superellipse (iOS-icon)
   corners. Browsers without support just keep plain rounded corners —
   nothing breaks. Composes freely with dark.css's brightness filter. */
.img3d .image3d {
  border-radius: 18%;
  corner-shape: squircle;
}
/* every content container gets the same corner language */
.services_card,
.expertise_card,
.pricing_card,
.card_about,
.team_card,
.testi-service_card,
.faq_item,
.blog_card,
.blur-card,
.hero-contact_form,
.sk97_frame,
.cta-wrap,
.footer_wrap {
  corner-shape: squircle;
}

/* ── skiper106: smooth caret ─────────────────────────────────────── */
.hvo-caret-wrap {
  position: relative;
}
.hvo-caret-wrap input {
  caret-color: transparent;
}
.hvo-caret {
  position: absolute;
  top: 50%;
  width: 1.5px;
  height: 1.2em;
  transform: translateY(-50%);
  background: currentColor;
  border-radius: 1px;
  pointer-events: none;
  opacity: 0;
  will-change: transform, opacity;
}
.hvo-caret.is-on {
  opacity: 1;
  animation: hvo-caret-blink 1.1s steps(1) infinite 0.4s;
}
@keyframes hvo-caret-blink {
  0%, 60% { opacity: 1; }
  60.01%, 100% { opacity: 0.15; }
}

/* ── skiper26: circle-blur View Transition (start: top-right) ────── */
html.vt-circle-blur {
  --expo-out: cubic-bezier(0.16, 1, 0.3, 1);
}
html.vt-circle-blur::view-transition-group(root) {
  animation-timing-function: var(--expo-out);
}
html.vt-circle-blur::view-transition-new(root) {
  mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><filter id="blur"><feGaussianBlur stdDeviation="2"/></filter></defs><circle cx="40" cy="0" r="18" fill="white" filter="url(%23blur)"/></svg>')
    top right / 0 no-repeat;
  mask-origin: content-box;
  /* `forwards` is load-bearing (flicker fix, Mena 2026-08-31): without a
     fill mode the mask snaps back to size 0 the instant the 1s keyframe
     ends, blanking the new theme for the teardown frame(s) and flashing
     the old one. */
  animation: hvo-vt-scale 1s forwards;
  transform-origin: top right;
}
html.vt-circle-blur::view-transition-old(root) {
  animation: hvo-vt-scale 1s;
  transform-origin: top right;
  z-index: -1;
}
@keyframes hvo-vt-scale {
  to { mask-size: 350vmax; }
}

/* ── skiper94: Scroll progress 002 ──────────────────────────────── */
/* The original 240×6px geometry, 16×1px leader, lime fill, and live
   counter are preserved, but the indicator now reads horizontally at
   bottom-center instead of vertically in the left gutter. */
/* ── DARK-MODE ELEVATION, ONE RECIPE (Mena 2026-08-18) ──────────────
   "the same way u fixed the dark mode shadow for the services cards do to
   the portal dark mode shadow add it to the expertise sections so its
   cohesive. make sure the faq container and blog post images matches."

   Why a shared recipe: on a #131313 page a soft dark shadow is a 1-to-2 level
   change, which bands into a smudge instead of reading as lift (measured on
   the Services deck). On dark, elevation has to be DRAWN — a hairline rim
   defines the edge, and a tighter genuinely black shadow does the drop. The
   Portal frame already used this and looked right; these tokens generalise it
   so every raised surface on the page shares one language.
   Loaded last, and the html[data-theme] prefix outranks the component rules,
   so this wins without touching each component file. */
html[data-theme="dark"] {
  --hvo-dark-rim: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
  --hvo-dark-drop: 0 0.75rem 2rem rgba(0, 0, 0, 0.55);
}

/* Expertise panel, FAQ container, blog cards and the Portal frame, all on the
   same recipe. The Portal had its own near-miss version (rim at 8%, a much
   larger drop); folded in so the five raised surfaces are identical rather
   than merely similar. The Services deck keeps its rule in its own file with
   the same values. */
html[data-theme="dark"] .hvo-cardstack,
html[data-theme="dark"] #hvo-faq-skiper103 ul,
html[data-theme="dark"] .blog_card,
html[data-theme="dark"] .section_portal-demo .hvo-pd-frame {
  box-shadow: var(--hvo-dark-rim), var(--hvo-dark-drop);
}

/* The blog thumbnails sit inside a rounded anchor; without their own rim the
   photo edge and the card edge read as two different radii in dark. */
html[data-theme="dark"] .blog_card {
  overflow: hidden;
}

html[data-theme="dark"] .blog_card img {
  border-radius: inherit;
}

/* SECTION RHYTHM — one section on screen at a time (Mena 2026-08-18).
   "I want to add some more space per section so that you can only really see
   one section at a time."
   The template ships .padding-section-large as a fixed 4.5rem (72px), used 13
   times on the homepage, so section-to-section read 144px at every window
   size. On a 1350px-tall display that let two whole sections share a screen.
   Now viewport-relative, so the breathing room grows with the window instead
   of staying a laptop-sized constant:
       900px tall  -> 99px a side  -> 198px between sections
      1200px tall  -> 132px a side -> 264px between
      1350px tall  -> 148px a side -> 297px between
   The rem floor and ceiling keep short windows and very tall displays sane.
   Loaded last, so this overrides the Astro bundle's value at equal
   specificity. NOTE: spacing alone cannot stop two morphing headlines sharing
   a screen — the About and Portal headlines are only 666px apart and would
   need ~736px more between them. That is handled by morph-gate.js instead.
   .padding-section-large is an EMPTY spacer div, so its rendered height IS
   its padding — setting both top and bottom would double it. One side only. */
.padding-section-large {
  padding-top: clamp(4.5rem, 11vh, 13rem);
  padding-bottom: 0;
}

/* Scroll progress — MOVED TO THE WINDOW EDGE (Mena 2026-08-18).
   It used to be a 15rem pill floating ~30px above the bottom edge, dead
   centre, inside the reading column. Measured across 73 scroll positions,
   it printed on top of readable text or media at 37 of them — over half the
   page, including two section headlines it appeared to strike through, the
   hero video, Nick's testimonial and the FAQ subhead. A frame-by-frame
   review reached the same conclusion independently and ranked it the single
   worst thing on the scroll.
   Now it is a hairline flush to the bottom edge, full width: it can never
   land on copy, and it reads as browser furniture instead of as part of the
   page. The floating counter is hidden for the same reason (see
   .hvo-scroll-progress__value below) — nothing in the content area. */
.hvo-scroll-progress {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  width: 100%;
  height: 0.1875rem;
  border-radius: 0;
  background: rgba(19, 19, 19, 0.08);
  pointer-events: none;
  opacity: 1;
  visibility: visible;
  will-change: opacity;
}
.hvo-scroll-progress.is-hidden {
  opacity: 0;
  visibility: hidden;
}

/* Phones (Mena 2026-08-28: "get rid of the progress bar on the bottom for
   mobile"). On a phone this hairline sits directly under the content and
   directly above Safari's own bottom chrome, so it reads as a second,
   competing browser UI rather than as part of the page. The desktop rationale
   above still holds at desktop widths, so this is scoped, not deleted.
   fx.js keeps building and updating the element; only its display is dropped,
   which is cheaper and less brittle than making the builder conditional. */
@media (max-width: 767px) {
  .hvo-scroll-progress {
    display: none;
  }
}
.hvo-scroll-progress__fill {
  width: 100%;
  height: 100%;
  border-radius: inherit;
  background: var(--base--green, #d6fd70);
  clip-path: inset(0 100% 0 0);
  will-change: clip-path;
}
/* The tick that rode the fill edge is redundant on an edge hairline and only
   added a second thing to notice. */
.hvo-scroll-progress__marker {
  display: none;
}
/* The floating "51" was the worst offender: no background, mix-blend-mode
   difference, sitting 1.5rem above the bar and therefore squarely inside the
   copy. Hidden rather than repositioned — anywhere inside the content area
   reproduces the problem, and the corner is already taken by the theme
   toggle. If the number is wanted back, it belongs docked to the right end
   of the edge bar with its own solid backing. */
.hvo-scroll-progress__value {
  display: none;
}
.hvo-scroll-progress__value.is-hidden {
  opacity: 0;
  visibility: hidden;
}
html[data-theme="dark"] .hvo-scroll-progress {
  background: rgba(255, 255, 255, 0.14);
}

/* Phones use the same edge hairline, one notch thinner. */
@media (max-width: 767px) {
  .hvo-scroll-progress {
    height: 0.125rem;
  }
}

/* skiper39 crowd canvas REMOVED 2026-07-15 on Mena's call (crowd.js
   deleted, script tags stripped from all pages, footer padding
   restored to the template default). */

/* Homepage pricing and testimonials blocks removed 2026-08-06. The standalone
   Pricing page and navigation link remain available. */
.section_pricing,
.section_testimonials {
  display: none !important;
}

/* ── Blog cards: three times too tall in Safari ──────────────────────────
   Mena 2026-08-28: "the blog section to be messed up" in desktop Safari but
   not Chrome. Reproduced in real WebKit through Playwright at 1440px:

       Chromium   .blog_card  411 x 411
       WebKit     .blog_card  411 x 1280

   The card is square by `aspect-ratio: 1 / 1` and is a flex item in
   .blog_track, whose default align-items is stretch. The two engines resolve
   that pair in the opposite order: Chromium sizes from the aspect ratio and
   Chromium's track ends up 411 tall, while WebKit stretches the item to the
   line first and the ratio never gets to constrain it, so the card inherits
   the track's 1280px width as its height.

   align-self opts the card out of the stretch, so the aspect ratio is the
   only thing left deciding its height. Verified in both engines through
   Playwright before shipping: WebKit 1280 -> 411, Chromium unchanged at 411.

   Scoped to .blog_track so it cannot reach the audience deck or the
   testimonials, whose slides DO want to stretch. `height: auto` was also
   tested and does NOT fix it: WebKit stayed at 1280. */
.blog_track > .blog_card {
  align-self: flex-start;
}

/* ── Wheel card shadows off in WebKit ────────────────────────────────────
   Mena 2026-08-28: the hero is "laggier" in desktop Safari than in Chrome.
   Measured in real (headed) WebKit through Playwright at 2560x1400 dpr2,
   three runs each, counting frames over 32ms in a 220-frame sample:

       as shipped              8, 7, 9
       wheel shadows off       3, 3, 1
       ENTIRE WHEEL HIDDEN     9, 4, 10

   Turning the shadows off beats deleting the whole wheel. 54 cards each
   painting two soft shadows, all rotating every frame, and WebKit re-rasters
   every one of them. The cost scales with window size (13 -> 18 -> 20 dropped
   frames at 1440 -> 2000 -> 2560 wide), which is why it shows on a Studio
   Display and never on a phone.

   Nothing free fixes it. will-change:transform, translateZ promotion and
   contain:paint were each measured and all three made it WORSE, because
   promoting 54 layers costs more than it saves. Halving the blur did nothing:
   it is the presence of a shadow, not its size.

   The visual cost of removing it was measured rather than guessed. Diffing
   the rendered wheel with and against: max difference 118/255 on a handful of
   edge pixels, mean 0.02, and 601 of 1,446,144 pixels changed. 0.04%.

   Scoped to WebKit so Chrome and Firefox render byte-identically to before.
   To revert, delete this block. */
html[data-engine="webkit"] .img3d .image3d,
html[data-engine="webkit"] .img3d .hvo-card-fx {
  box-shadow: none;
}
