/* site.css — measured fixes layered over the Framer bundle. See DESIGN-SYSTEM.md.
   Loaded on every page after the bundle's own styles. */

/* ---- CLS (audit #13) — intro band -----------------------------------------

   NEVER hide or collapse .framer-1qkjtn1. It is not an empty spacer: it hosts
   the opening intro (the violet gradient reveal). An earlier `visibility:
   hidden` on it killed the animation outright. That approach is RETIRED — do
   not revisit it, and do not "reserve space" by pinning this element's box
   either (see below for why that also breaks it).

   DIAGNOSIS (PerformanceObserver on layout-shift, `sources` captured):
   the 1.0 entry has exactly one source — .framer-1qkjtn1 itself. Nothing else
   on the page moves; documentElement.scrollHeight is constant (6506 at 1440)
   through the whole intro, and the band is position:absolute inside an
   overflow:hidden parent, so no sibling can see it.

   At ~1.6s Framer swaps the component variant to .framer-v-ng6it9, whose rule
   moves the band's box from `top:0; height:100vh` to `top:-120px;
   height:1vh` — it parks a 9px strip just above the fold. In the same commit
   Framer writes an inline `translate3d(0,565.5px,0) scale(1,100)` that
   repaints it at the identical pixels, so not one pixel actually moves. But
   Chrome deliberately discounts an element's own transform when detecting
   layout shift, so it books the raw 120px move of the layout box as a real
   shift. Value 1.0.

   The transform is FLIP-derived, not authored: it is framer-motion's
   projection, measured from the box delta. Proven by experiment — forcing the
   resting height to 2vh made Framer emit scale(1,50) instead of scale(1,100),
   with the painted result unchanged. That is why pinning the box to kill the
   delta also kills the animation: with no delta, motion computes an identity
   transform and the intro simply never plays.

   FIX: keep the delta motion measures, remove the delta Chrome measures.
   `top:-120px` is applied in BOTH variants, so the layout box never moves —
   only its height changes, and Chrome does not count a pure resize. The 120px
   the band now loses before the swap is given back with the independent
   `translate` property, which composes with `transform` instead of fighting
   it: Framer only ever writes `transform`, so the two never collide.
   getBoundingClientRect() includes `translate`, so motion still measures the
   exact same before/after rects it measured previously and emits the exact
   same FLIP transform — the animation is bit-for-bit the one it was.

   Measured (median of 4 runs, local build): CLS 1.0012 -> 0.0012 at 1440,
   1.0684 -> 0.0684 at 810, 1.0457 -> 0.0424 at 390. The band's own 1.0 entry
   is gone at all three; the remainder is a pre-existing webfont reflow at
   ~60ms, untouched. Curve, spring, negative-scale overshoot and the 9px
   parked resting position are unchanged within frame quantisation, and the
   full-page screenshot at settle is pixel-identical (0 differing pixels).

   @supports gates it so browsers without the independent `translate` property
   keep today's behaviour — correct visuals, high CLS — rather than rendering
   the intro 120px too high. --sk-intro-park must stay equal to the `top` in
   Framer's own .framer-v-ng6it9 rule; if a republish changes that value,
   change it here too. */
@supports (translate: 0 1px) {
  .framer-yvVsy .framer-1qkjtn1 {
    --sk-intro-park: -120px;
    top: var(--sk-intro-park);
  }
  .framer-yvVsy:not(.framer-v-ng6it9) .framer-1qkjtn1 {
    translate: 0 calc(var(--sk-intro-park) * -1);
  }
}

/* ---- tap targets (audit #16) --------------------------------------------
   enhance.js tags every interactive element whose hit box is under 44px with
   .sk-tap and sets --sk-tap-x/-y to the padding needed. The pseudo-element
   grows the hit area without moving a single pixel of layout. */
.sk-tap { position: relative; }
.sk-tap::after {
  content: '';
  position: absolute;
  top: calc(var(--sk-tap-y, 0px) * -1);
  bottom: calc(var(--sk-tap-y, 0px) * -1);
  left: calc(var(--sk-tap-x, 0px) * -1);
  right: calc(var(--sk-tap-x, 0px) * -1);
}

/* ---- keyboard focus ------------------------------------------------------
   The bundle ships no :focus-visible styling at all. */
a:focus-visible, button:focus-visible {
  outline: 2px solid var(--sk-violet);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---- impact strip (audit #6) --------------------------------------------
   Injected by enhance.js directly under the case-study banner. Every token is
   lifted from the Client Projects metric treatment on the homepage — the
   italic violet numerals are the site's signature. */
.sk-impact {
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px 30px 30px;
}
.sk-impact-card {
  background: var(--sk-surface);
  border-radius: var(--sk-r-card);
  padding: 30px;
  display: flex;
  flex-wrap: wrap;
  gap: 30px 50px;
  justify-content: space-evenly;
  align-items: stretch;
}
.sk-impact-stat { text-align: center; min-width: 150px; flex: 1 1 150px; }
.sk-impact-n {
  font-family: var(--sk-ui);
  font-style: italic;
  font-weight: 600;
  font-size: 40px;
  letter-spacing: -1.6px;
  color: var(--sk-violet);
  line-height: 1.15;
  white-space: nowrap;
}
.sk-impact-l {
  font-family: var(--sk-ui);
  font-style: italic;
  font-weight: 900;
  font-size: 14px;
  letter-spacing: 1px;
  color: var(--sk-violet);
  opacity: .92;
  margin-top: 6px;
}
@media (max-width: 809px) {
  .sk-impact { padding: 6px 20px 20px; }
  .sk-impact-card { padding: 20px; gap: 20px 30px; }
  .sk-impact-n { font-size: 32px; }
  .sk-impact-l { font-size: 12px; }
}

/* screen-reader-only (for the /blogs h1) */
.sk-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---- Email CTA on /about (enhance.js §10) --------------------------------
   Values measured off the home page's own CTAs — #404040 ground, 20px radius,
   14px white label — rather than cloned from Framer's generated classes,
   which change on every republish. */
.sk-cta {
  display: flex;
  justify-content: center;
  padding: 10px 20px 50px;
}
.sk-cta-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 13px 30px;
  border-radius: 20px;
  background: #404040;
  color: #fff;
  font-family: "SF Pro Display", -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.3px;
  text-decoration: none;
  transition: background 200ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.sk-cta-btn:hover,
.sk-cta-btn:focus-visible { background: #AD3BFF; transform: translateY(-2px); }

@media (prefers-reduced-motion: reduce) {
  .sk-cta-btn { transition: none; }
  .sk-cta-btn:hover, .sk-cta-btn:focus-visible { transform: none; }
}

/* ---- link cursor ----------------------------------------------------------
 * Framer's button component sets `cursor: pointer` on its base class, then
 * overrides it with `cursor: unset` on six of its variant selectors:
 *
 *   .framer-9y1Gf.framer-1w7bjqv               { cursor: pointer }   0-2-0
 *   .framer-9y1Gf.framer-v-olz0a9.framer-1w7bjqv { cursor: unset }   0-3-0  ← wins
 *
 * On a <div> that is harmless. On an <a> `unset` means inherit, which lands on
 * `auto` — so the Bud "Download" and "Contact for details" links showed a text
 * caret instead of a hand. Those two are the only offenders on the whole site
 * (audited across all 12 routes: every other anchor already computes pointer,
 * and none wants anything else).
 *
 * The doubled [class] is what beats Framer's three-class specificity without
 * naming its hashes, which are regenerated on every re-mirror. An !important
 * would also work but would then outrank any future deliberate cursor.
 */
a[href][class][class] { cursor: pointer; }

/* ---- cross-document page transition ---------------------------------------
 * Framer's router swaps pages without a reload: the outgoing content clears to
 * black, holds a beat, and the new page draws in. The five article pages under
 * /blogs/<slug> are outside that router — they are project-authored HTML with
 * no Framer runtime — so navigating to or from one is a real document load and
 * got none of it.
 *
 * This reproduces the black hold in the one place a document load leaves room
 * for it: a veil painted over the outgoing page while the next one is fetched.
 * The incoming half lives in article.css, where it can be pure CSS and so
 * cannot leave content stranded if a script fails.
 */
.sk-veil {
  position: fixed;
  inset: 0;
  z-index: 2147483647;
  background: #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 190ms cubic-bezier(0.4, 0, 1, 1);
}
/* Taking pointer events only once opaque swallows a double-click mid-flight,
   which would otherwise queue a second navigation. */
.sk-veil-on { opacity: 1; pointer-events: auto; }

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