/* MRLN MEDIA — supplementary stylesheet (Client-First build)
   ONLY what the Webflow style API genuinely cannot express. Everything else —
   including hover/focus states, pseudo-element content, the grain texture,
   placeholder colour, step dividers and the flood alternation — now lives as
   native Webflow styles and is visible in the Designer.

   What remains here, and why:
     1. tag-level resets  — tag styles cannot be created via the API
     2. @keyframes        — no API representation
     3. runtime state classes the motion layer adds (.anim-ready, .is-open,
        .is-hot, .is-stuck, .is-on, .is-busy, .split_*, .lenis.*)
     4. descendant + combined pseudo selectors (:hover::after)
     5. attribute selectors ([disabled], [aria-current], [data-anim])
     6. browser surfaces (::selection, scrollbar, per-ground focus rings)
     7. prefers-reduced-motion, vendor-prefixed properties
     8. exact small-screen breakpoints (700px / 600px do not map to Webflow's) */

/* ---- 1. tag resets (Webflow's own defaults would otherwise leak) ---- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--color--paper,#FBF5EE);color:var(--color--ink,#150A11);
  /* clip, NOT hidden. overflow-x:hidden computes overflow-y to auto, which
     makes this element a scroll container - and then position:sticky on the
     case cards resolves against a box that never scrolls, so nothing pins.
     overflow-x:clip does the same horizontal job and leaves overflow-y
     visible. Same change is on .page-wrapper. */
  overflow-x:clip;overflow-y:visible;-webkit-font-smoothing:antialiased;
  font-feature-settings:"kern" 1,"liga" 1,"cv11" 1}
img,svg,video{display:block;max-width:100%}
h1,h2,h3,h4,p,figure,blockquote{margin:0}
ul,ol{padding:0;margin:0;list-style:none}
a{color:inherit;text-decoration:none;text-underline-offset:.18em;text-decoration-thickness:.08em}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
input,textarea{font:inherit;color:inherit}

/* ---- 6. browser surfaces ---- */
::selection{background:#FF66C4;color:#150A11}
:root{scrollbar-color:#FF66C4 #F3E9DD;scrollbar-width:thin}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-track{background:#F3E9DD}
::-webkit-scrollbar-thumb{background:#FF66C4;border:3px solid #F3E9DD;border-radius:99px}
::-webkit-scrollbar-thumb:hover{background:#D91E8F}
:focus{outline:none}
:focus-visible{outline:3px solid #D91E8F;outline-offset:3px;border-radius:2px}
.is-ink :focus-visible,.is-pink :focus-visible{outline-color:#FFE600}
.is-yellow :focus-visible{outline-color:#150A11}
[type=text],[type=email],textarea{caret-color:#D91E8F}

/* ---- 4. combined pseudo: the button wipe trigger + icon nudge ---- */
.button:hover::after,.button:focus-visible::after{transform:translateY(0)}
.button:hover .button_icon{transform:translateX(4px)}
.button:active{transform:translateY(-1px) scale(.99)}
.text-link:hover::before,.text-link:focus-visible::before{transform:scaleX(1)}
.wordmark.is-cta:hover .wordmark_icon{transform:translateX(4px)}
.wordmark_icon{transition:transform .45s cubic-bezier(.16,1,.3,1)}
.button_icon{transition:transform .5s cubic-bezier(.16,1,.3,1)}

/* ---- 5. attribute selectors ---- */
.button[disabled]{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none}
.button[disabled]::after{display:none}
.nav_link[aria-current=true]{color:#FF66C4}

/* ---- 2 + 3. keyframes and the runtime state classes that use them ---- */
.button.is-busy{cursor:progress}
.button.is-busy .button_icon{animation:spin 1s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

.cursor__d,.cursor__r{position:absolute;left:0;top:0;border-radius:99px;transform:translate(-50%,-50%)}
/* The dot used to grow by transitioning width and height, which animates
   LAYOUT for 450ms every time the pointer crosses a link. scale() is the same
   picture on the compositor: 15/10 = 1.5, 7/10 = 0.7. */
.cursor__d{width:10px;height:10px;background:#F53FAE;
  transition:transform .45s cubic-bezier(.16,1,.3,1)}
.cursor__r{width:10px;height:10px;border:1.5px solid #F53FAE;opacity:0}
/* No will-change here on purpose. It was tried and measured: with the pointer
   swept 180 times, style/prepaint/layerize/raster counts were IDENTICAL with
   and without it (397/397/217/5), so a permanent GPU layer would buy nothing.
   Chrome already handles this transform write cheaply. */
.cursor{transition:opacity .3s linear}
.cursor.is-on{opacity:1}
.cursor.is-hot .cursor__d{transform:translate(-50%,-50%) scale(1.5)}
.cursor.is-hot .cursor__r{animation:cur-ripple 1.25s cubic-bezier(.16,1,.3,1) infinite}
.cursor.is-down .cursor__d{transform:translate(-50%,-50%) scale(.7)}
@keyframes cur-ripple{
  0%{transform:translate(-50%,-50%) scale(1);opacity:.85}
  100%{transform:translate(-50%,-50%) scale(4.4);opacity:0}
}
@media (hover:none),(pointer:coarse){.cursor{display:none}}

/* ---- 3 + 4. case stack: staggered pin offsets ------------------------------
   React Bits ScrollStack pins each card `itemStackDistance` (30px) lower than
   the one before it, so the stack reads as a deck with visible edges rather
   than each card hiding the last completely. `.case-card` supplies the base
   `top`; these add the per-index offset. nth-child, so it cannot be expressed
   as a Webflow class. */
.case-stack .case-card:nth-child(2){top:calc(clamp(4.5rem,10vh,7rem) + 30px)}
.case-stack .case-card:nth-child(3){top:calc(clamp(4.5rem,10vh,7rem) + 60px)}
.case-stack .case-card:nth-child(4){top:calc(clamp(4.5rem,10vh,7rem) + 90px)}
/* Below 920px the card is a single column (text above image) and is taller
   than a pinned card's room, so the image fell under the next card and could
   never be reached. Measured up to 211px lost at 360x600, and the per-card
   offsets above cost the third card another 60px. Let them flow instead; the
   stacking deck is a desktop-width effect. The offsets are inert once static. */
@media (max-width:919px){
  .case-card{position:static}
}

/* ---- 8. nav panel: short-viewport guard ------------------------------------
   The panel centres its content, and its vertical padding is symmetric so the
   visible gap above the links equals the gap below. It used to carry
   `padding-top: calc(4.6rem + ...)` as clearance for the nav bar, which sits
   OVER the panel at z-index 1004 - but combined with justify-content:center
   that made the top gap 65px larger than the bottom one (measured 283 vs 218
   at 900px tall).

   The clearance only actually matters when the viewport is short enough that
   centred content would rise under the bar. Measured: with symmetric padding
   the first link sits at 111px at 600px tall and 91px at 560px, against a bar
   bottom edge of 86px - so symmetry is safe to about 550px. The guard starts
   at 580px, which keeps ~15px of clearance at the boundary.
   Webflow has no max-height breakpoints, so this lives here. */
@media (max-height: 580px){
  .nav_panel{padding-top:calc(4.6rem + 1.5rem)}
}


.scroll-cue_line::after{animation:drop 1.9s cubic-bezier(.16,1,.3,1) infinite}
@keyframes drop{0%{transform:translateY(-100%)}100%{transform:translateY(250%)}}

.plate{transform:translate(-50%,-50%) scale(.85) rotate(-6deg);
  transition:opacity .4s cubic-bezier(.16,1,.3,1),transform .55s cubic-bezier(.16,1,.3,1);
  box-shadow:0 30px 60px -22px rgba(21,10,17,.55)}
.plate.is-on{opacity:1;transform:translate(-50%,-50%) scale(1) rotate(-4deg)}
@media (max-width:1023px),(hover:none){.plate{display:none}}

/* ---- 3. nav open / stuck states (descendant selectors) ---- */
.nav_scrim{backdrop-filter:blur(0px);
  transition:background .45s cubic-bezier(.16,1,.3,1),backdrop-filter .45s cubic-bezier(.16,1,.3,1),box-shadow .45s cubic-bezier(.16,1,.3,1)}
.nav.is-stuck .nav_scrim{background:rgba(251,245,238,.82);backdrop-filter:blur(14px) saturate(140%);
  box-shadow:0 1px 0 rgba(21,10,17,.14)}
.nav.is-open .nav_scrim{background:rgba(251,245,238,0);backdrop-filter:blur(0px);box-shadow:none}
.nav_logo{transition:opacity .4s cubic-bezier(.16,1,.3,1)}
@media (max-width:860px){.nav.is-open .nav_logo{opacity:0;pointer-events:none}}
.nav_toggle{transition:color .4s cubic-bezier(.16,1,.3,1)}
.nav.is-open .nav_toggle{color:#FBF5EE}
.nav.is-open .nav_panel,.nav.is-open .nav_prelayer{pointer-events:auto}
.nav_link.is-accent .nav_number{color:#FFE600}

/* ---- 3. services accordion states (descendant selectors) ---- */
.services_flood{transition:transform .6s cubic-bezier(.16,1,.3,1),opacity .2s linear}
.services_row.is-hot .services_flood{transform:scaleY(1);opacity:1;
  transition:transform .6s cubic-bezier(.16,1,.3,1),opacity .12s linear}
/* The flood is position:absolute;inset:0 on the ROW, so it also sits behind
   the accordion body. Only .services_head used to invert, which left the
   body's inherited paper text sitting on a spot colour: measured 1.07:1 on
   the yellow rows (2 and 4) and 2.44:1 on the pink ones (1 and 3). Invert the
   whole row instead, so the body follows the head. Ink gives 15.3:1 on yellow
   and 7.34:1 on pink. */
.services_row{transition:color .5s cubic-bezier(.16,1,.3,1)}
.services_row.is-hot{color:#150A11}
.services_head{transition:color .5s cubic-bezier(.16,1,.3,1),padding-inline .6s cubic-bezier(.16,1,.3,1)}
.services_row.is-hot .services_head{color:#150A11;padding-inline:clamp(.5rem,1.6vw,1.5rem)}
.services_number{transition:color .5s cubic-bezier(.16,1,.3,1)}
.services_row.is-hot .services_number{color:#57082F}
.services_row:nth-child(2n).is-hot .services_number{color:#5C4200}
.services_plus{transition:transform .55s cubic-bezier(.16,1,.3,1)}
.services_row.is-open .services_plus{transform:rotate(135deg)}

/* ---- 3. split-text primitives (generated by the motion layer) ---- */
.split_word{display:inline-block}
.split_char{display:inline-block}
.split_line{display:block;overflow:hidden}
.split_line-inner{display:block}
/* NB: no will-change here on purpose. These reveal once and never animate
   again; 55 permanent GPU layers is a bad trade for a one-shot tween, and
   GSAP promotes during the tween anyway. */
.reveal-words span{transition:color .3s linear}

/* ---- 3 + 5. animation defaults (JS adds .anim-ready to arm) ---- */
.anim-ready [data-anim=up]{opacity:0;transform:translateY(28px)}
.anim-ready [data-anim=fade]{opacity:0}
.anim-ready [data-anim=blur]{opacity:0;filter:blur(14px);transform:translateY(14px)}
.anim-ready [data-anim=clip]{clip-path:inset(0 0 100% 0)}
.anim-ready .split_char{opacity:0}
.anim-ready .reveal-words span{color:rgba(21,10,17,.24)}

/* ---- 3 + 5. pre-arm, stamped by an inline <head> script ----------------
   The rules above are armed by .anim-ready, which the motion layer adds only
   after GSAP, ScrollTrigger, Lenis and mrln-motion.js have all loaded. That
   keeps the page readable without JavaScript, but it also means the hero is
   painted in its final state first and then vanishes to animate in — measured
   at ~120ms visible on a throttled connection, longer on a slow one.

   .anim-pre carries the same hidden state from the very first paint. It is
   added by a two-line inline script in the <head>, which runs before the body
   is painted, and it removes itself after 3s so a failed CDN fetch degrades to
   visible content rather than a permanently blank hero. Reduced-motion
   visitors never get the class at all.

   [data-split] needs the container hidden, not .split_char: the characters do
   not exist until JS splits the text, so pre-JS there is nothing to hide. */
.anim-pre [data-anim=up]{opacity:0;transform:translateY(28px)}
.anim-pre [data-anim=fade]{opacity:0}
.anim-pre [data-anim=blur]{opacity:0;filter:blur(14px);transform:translateY(14px)}
.anim-pre [data-anim=clip]{clip-path:inset(0 0 100% 0)}
.anim-pre [data-split]{opacity:0}
/* .anim-ready lands before the split runs, so the container comes back while
   the individual characters are still hidden by the rule above. */
.anim-ready [data-split]{opacity:1}

/* ---- native Webflow form messages (auto-generated markup) ---- */
.contact_form .w-form-done,.contact_form .w-form-fail{border-radius:3px;padding:1rem 1.1rem;font-size:.9375rem}
.contact_form .w-form-done{background:#FFE600;color:#150A11;border:1.5px solid #150A11}

/* ---- 8. exact small-screen breakpoints ---- */
@media (max-width:700px){
  .section-head_row{flex-direction:column;align-items:flex-start;gap:1rem}
  .section-head_row>p{max-width:none!important}
}
@media (max-width:600px){
  .scroll-cue{display:none}
  .case-card_stats{grid-template-columns:1fr 1fr}
  .loves_item{flex-direction:column;gap:.25rem}
  .loves_sub{text-align:left}
  .tilt_badge{right:-.25rem;bottom:-1.25rem}
}

/* ---- 7. reduced motion ---- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;scroll-behavior:auto!important}
  .anim-ready [data-anim]{opacity:1!important;transform:none!important;filter:none!important;clip-path:none!important}
  .anim-ready .split_char{opacity:1!important;transform:none!important}
  .anim-ready .reveal-words span{color:inherit!important}
  .blob{display:none}
  .grain{animation:none!important}
}

/* ---- lenis ---- */
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto!important}
.lenis.lenis-smooth [data-lenis-prevent]{overscroll-behavior:contain}
.lenis.lenis-stopped{overflow:hidden}
.lenis.lenis-smooth iframe{pointer-events:none}
