/* =====================================================================
   SERRES DRIVE — css/preloader.css
   "SERRES → DRIVE" split-overlay preloader.

   Sequence: three tag words drift in → SERRES reveals character by
   character out of per-glyph masks → DRIVE slides in to its right and
   completes the lockup → the lockup tightens → tags leave → the overlay
   splits through its horizontal centre (top half peels up, bottom half
   peels down) and the untouched page is revealed underneath.

   ---------------------------------------------------------------------
   WHY THE OVERLAY SPLITS INSTEAD OF THE PAGE BEING CLIPPED
   The usual way to build this is to wrap the whole page in a container
   and animate a clip-path on it. That is not survivable here: the home
   page runs a position:fixed WebGL stage (.sd-model), a fixed backdrop,
   Lenis smooth scroll and ScrollTrigger-pinned scrollytelling. A
   transformed/clipped ancestor re-parents every fixed child and desyncs
   every trigger. So the PAGE IS NEVER TOUCHED — the overlay itself is
   two halves that peel apart. Visually identical, zero layout risk.

   ---------------------------------------------------------------------
   WHY PERCENTAGE INSETS AND NOT 50svh/50vh
   The brief asked for 50svh with a 50vh fallback for mobile URL-bar
   correctness. Percentage insets are strictly better at the same job:
   .pl is position:fixed inset:0, so it IS the visual viewport whatever
   the URL bar is doing, and bottom:50% / top:50% split that box exactly
   in two. No unit ever has to guess the viewport height, the halves can
   never fail to cover it, and — critically — the two halves can never
   disagree about where the centre is, which is what keeps the wordmark
   seamless across the seam.

   ---------------------------------------------------------------------
   NOTHING HERE CAN HIDE PAGE CONTENT
   Every selector is scoped under .pl / .pl-*, which only ever exist
   inside the overlay js/preloader.js builds at runtime. If JS is off or
   throws, this stylesheet matches nothing at all. The one exception is
   html.pl-lock (the scroll lock) — a class only preloader.js adds, and
   which it removes on every exit path including its own watchdog.

   All motion lives under .pl--css (the no-GSAP fallback). When GSAP is
   present JS drives inline transforms instead, and there must be no CSS
   transition competing with it for the same property — hence the gate.
   ===================================================================== */


/* ---------- Scroll lock ----------
   Held only while the overlay is on screen. preloader.js also calls
   lenis.stop() when Lenis happens to exist, but this class is the part
   that must work on every page, with or without it. */
html.pl-lock,
html.pl-lock body{overflow:hidden}


/* ---------- Root ----------
   z-index 9999 clears the site's ladder (.nav 60, .mobile-menu 55,
   .wa-float 50). pointer-events stay live so a click anywhere skips —
   a preloader must never be able to trap someone. */
.pl{
  position:fixed;
  inset:0;
  z-index:9999;
  pointer-events:auto;
  touch-action:none;
  user-select:none;
  -webkit-user-select:none;
  -webkit-tap-highlight-color:transparent;
  cursor:default;
}

/* Skip / bail-out fade. Deliberately NOT gated on .pl--css: it is the
   one transition that has to work in the GSAP path too, and GSAP never
   animates opacity on the root, so nothing can fight it. */
.pl--out{opacity:0;pointer-events:none;transition:opacity .28s linear}


/* ---------- The two halves ----------
   Absolute children of a fixed inset:0 parent, so both are measured
   against the identical box. box-shadow on the top half paints a 1px
   skirt of --bg just past the seam, which swallows any sub-pixel
   hairline between the two without changing either one's layout (and
   therefore without shifting the wordmark by even a fraction). */
.pl-half{
  position:absolute;
  left:0;right:0;
  overflow:hidden;
  background:var(--bg,#0a0a0b);
  will-change:transform;
  backface-visibility:hidden;
}
.pl-half--top{top:0;bottom:50%;box-shadow:0 1px 0 0 var(--bg,#0a0a0b)}
.pl-half--bot{top:50%;bottom:0}


/* ---------- Viewport-coordinate layer ----------
   The heart of the seam trick. Each half contains one .pl-vp that is
   exactly 200% of its half — i.e. exactly one viewport tall — anchored
   to the outer edge. The top half's .pl-vp therefore spans viewport
   rows 0→100% and so does the bottom half's; each is simply clipped to
   its own half. Consequence: the two halves hold IDENTICAL markup at
   IDENTICAL viewport coordinates, so anything placed at top:50% is cut
   perfectly in two by the seam, and tag words can be positioned in
   plain viewport percentages. */
.pl-vp{position:absolute;left:0;width:100%;height:200%}
.pl-half--top .pl-vp{top:0}
.pl-half--bot .pl-vp{bottom:0}


/* ---------- Lockup ----------
   Three nested transform layers, one job each, so they can never
   overwrite one another:
     .pl-lockup-pos  centres it on the seam           (translateY(-50%))
     .pl-shift       holds SERRES centred pre-DRIVE   (translateX)
     .pl-lockup      the settle/tighten               (scale) */
.pl-lockup-pos{
  position:absolute;
  left:0;right:0;
  top:50%;
  transform:translateY(-50%);
  display:flex;
  justify-content:center;
  /* font-size lives HERE, not on .pl-lockup, so that .pl-shift below
     inherits it and can express its offset in em. See --pl-shift. */
  font-size:clamp(2.2rem,11vw,7rem);
}
.pl-shift{
  display:flex;
  /* --pl-shift is measured at runtime = (lockup width − SERRES width)/2,
     which puts SERRES on the optical centre before DRIVE arrives.

     It is published in em, NOT px, and that is load-bearing. The offset
     is a fixed proportion of the type size, and the type size is
     viewport-relative (11vw) — so a px value measured at load is wrong
     the instant the device is rotated, leaving SERRES visibly off
     centre. In em the ratio holds at every width for free, with no
     resize listener to register, debounce or tear down.

     Defaults to 0, so a failed measurement is a slightly off-centre
     word — never a broken or invisible one. */
  transform:translateX(var(--pl-shift,0em));
}
.pl-lockup{
  display:flex;
  align-items:flex-end;
  gap:.28em;                 /* the word space between SERRES and DRIVE */
  font-family:var(--font-head,"Barlow Condensed","Arial Narrow",system-ui,sans-serif);
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.02em;
  line-height:1;
  white-space:nowrap;
  transform-origin:50% 50%;
}
/* Deliberately NOT flex. letter-spacing only applies between inline
   boxes — turning a word into a flex container blockifies its glyphs and
   silently drops the .02em tracking. Inline-block keeps the glyphs in a
   line box where the tracking still lands between them. */
.pl-word{display:inline-block;line-height:1;white-space:nowrap}


/* ---------- Per-character mask (SERRES only) ----------
   padding-top gives the mask a little headroom above the cap line so no
   font's overshoot can be clipped at line-height:1; the matching
   negative margin cancels it out of the layout. Both halves get exactly
   the same treatment, so even if a font disagreed the two copies would
   disagree identically and the seam would still line up. */
.pl-glyph{
  display:inline-block;
  overflow:hidden;
  vertical-align:bottom;
  padding-top:.12em;
  margin-top:-.12em;
}

/* ---------- Brushed chrome fill ----------
   The gradient sits on the CHARACTER, not on the word. background-clip:
   text on a word whose children are individually transformed is the
   fragile case — the clipped background is painted by the ancestor and
   does not follow a promoted, transformed descendant, so the fill can
   detach or vanish. Giving each character its own background makes the
   fill part of the moving element and immune to that. --chrome-grad is
   176deg (4deg off vertical) and every character box is the same
   height, so per-character and per-word rendering are indistinguishable.

   @supports below is the last line of defence: if background-clip:text
   is unavailable the text falls back to solid --white. The wordmark is
   never allowed to end up invisible. */
.pl-char{
  display:inline-block;
  /* Every inline box in the lockup aligns to its line-box BOTTOM rather
     than to a baseline. Baseline alignment of nested inline-blocks with
     overflow:hidden is defined differently per level and lets SERRES and
     DRIVE drift a pixel or two apart; bottom alignment is unambiguous
     and, since every box shares one font-size and line-height:1, it puts
     both words on the same baseline by construction. */
  vertical-align:bottom;
  background:var(--chrome-grad,linear-gradient(176deg,#fdfdfe 0%,#cfcfd6 32%,#8d8d97 52%,#f0f0f4 72%,#a6a6b0 100%));
  -webkit-background-clip:text;
  background-clip:text;
  color:transparent;
  -webkit-text-fill-color:transparent;
}
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  .pl-char{
    background:none;
    color:var(--white,#fdfdfe);
    -webkit-text-fill-color:currentColor;
  }
}

/* Start state for the SERRES characters: parked above their own mask.
   Declared in CSS (not JS) so there is no frame where they are visible
   in the wrong place. The GSAP path re-asserts the same value with
   gsap.set() before tweening, so both engines start identically. */
.pl-char--up{transform:translateY(-115%)}

/* DRIVE slides in as one word, masked at its own edge. */
.pl-drive-mask{
  display:inline-block;
  overflow:hidden;
  padding-top:.12em;
  margin-top:-.12em;
}
.pl-word--drive{vertical-align:bottom;transform:translateX(105%)}


/* ---------- Tag words ----------
   Positioned in viewport percentages against .pl-vp (see above), well
   clear of the centre band the lockup occupies. */
.pl-tag{
  position:absolute;
  top:var(--pl-top,20%);
  left:var(--pl-left,12%);
  font-family:var(--font-head,"Barlow Condensed","Arial Narrow",system-ui,sans-serif);
  font-weight:600;
  text-transform:uppercase;
  letter-spacing:.2em;
  font-size:.72rem;
  line-height:1;
  color:var(--text-mute,#71717a);
  white-space:nowrap;
  opacity:0;
  transform:translate(var(--pl-dx,0px),8px);
  will-change:transform,opacity;
}
/* Right-anchored tags grow leftwards, so a long EN string can never
   push past the right edge on a narrow phone. */
.pl-tag--right{left:auto;right:var(--pl-right,12%)}

/* Vertical placement is authored HERE rather than emitted inline by
   preloader.js. An inline custom property beats any author-stylesheet
   rule without !important, so a style="--pl-top:80%" would make the
   (max-height:520px) block at the foot of this file dead CSS — the
   landscape-phone adjustment would simply not exist at runtime. data-i
   mirrors the index in the TAGS array; the two lists move together. */
.pl-tag[data-i="0"]{--pl-top:17%}
.pl-tag[data-i="1"]{--pl-top:29%}
.pl-tag[data-i="2"]{--pl-top:80%}

/* The bilingual swap, restated locally. styles.css already declares it
   site-wide, but this file must not depend on load order or on being
   paired at all: on its own, .pl-tag{white-space:nowrap} would render
   both sibling spans concatenated ("BarcelonaBarcelona"). */
.pl-tag [data-en]{display:none}
html[lang="en"] .pl-tag [data-es]{display:none}
html[lang="en"] .pl-tag [data-en]{display:inline}


/* =====================================================================
   NO-GSAP FALLBACK
   js/preloader.js adds .pl--css only when window.gsap is absent, then
   toggles the beat classes on a timer. Gating every transition on that
   class means the GSAP path has no CSS transition to fight with.
   Easings mirror the JS ones; the split uses the literal "hop" curve.
   ===================================================================== */

/* t=0.0 — tags in */
.pl--css .pl-tag{
  transition:opacity .7s cubic-bezier(.2,.7,.3,1),transform .7s cubic-bezier(.2,.7,.3,1);
  transition-delay:calc(var(--i,0) * .12s);
}
.pl--css.is-tags .pl-tag{opacity:1;transform:translate(0,0)}

/* t=0.5 — SERRES, character by character */
.pl--css .pl-char--up{
  transition:transform .85s cubic-bezier(.16,1,.3,1);
  transition-delay:calc(var(--i,0) * .05s);
}
.pl--css.is-serres .pl-char--up{transform:translateY(0)}

/* t=2.3 — DRIVE arrives and the lockup re-centres around it */
.pl--css .pl-word--drive,
.pl--css .pl-shift{transition:transform .9s cubic-bezier(.16,1,.3,1)}
.pl--css.is-drive .pl-word--drive{transform:translateX(0)}
.pl--css.is-drive .pl-shift{transform:translateX(0)}

/* t=3.6 — the assembled lockup settles/tightens */
.pl--css .pl-lockup{transition:transform .7s cubic-bezier(.45,.05,.25,1)}
.pl--css.is-settle .pl-lockup{transform:scale(.94)}

/* t=4.2 — tags leave the way they came.
   Same specificity as .is-tags above, so this must stay below it. */
.pl--css.is-tagsout .pl-tag{
  opacity:0;
  transform:translate(var(--pl-dx,0px),8px);
  transition-duration:.5s;
  transition-delay:calc(var(--i,0) * .08s);
}

/* t=4.4 — THE SPLIT */
.pl--css .pl-half{transition:transform 1s cubic-bezier(.8,0,.3,1)}
.pl--css.is-split .pl-half--top{transform:translateY(-100%)}
.pl--css.is-split .pl-half--bot{transform:translateY(100%)}


/* =====================================================================
   REDUCED MOTION
   No stagger, no slide, no split. preloader.js adds .pl--reduced and
   shows the finished lockup statically for ~600ms, then cross-fades the
   whole overlay out via .pl--out. Degrading to a STATIC READABLE state,
   never a blank one.
   ===================================================================== */
.pl--reduced .pl-char--up,
.pl--reduced .pl-word--drive,
.pl--reduced .pl-shift,
.pl--reduced .pl-lockup{transform:none}
.pl--reduced .pl-tag{opacity:1;transform:none}

@media (prefers-reduced-motion:reduce){
  /* Belt and braces: even if the JS branch were somehow missed, nothing
     inside the overlay may animate. The root's opacity fade is left
     alone on purpose — it is how the overlay gets out of the way. */
  .pl *{animation:none !important}
  .pl--css .pl-tag,
  .pl--css .pl-char--up,
  .pl--css .pl-word--drive,
  .pl--css .pl-shift,
  .pl--css .pl-lockup,
  .pl--css .pl-half{
    transition:none !important;
    transform:none !important;
    opacity:1 !important;
  }
}


/* =====================================================================
   BREAKPOINTS — the site uses 1080 / 860 / 620, no tokens exist.
   ===================================================================== */
@media (max-width:860px){
  .pl-tag{font-size:.68rem;letter-spacing:.18em}
}
@media (max-width:620px){
  .pl-tag{font-size:.62rem;letter-spacing:.16em}
  /* Pull the scattered tags in towards the edges so a long Spanish
     string ("Entrega a domicilio") still clears the viewport. */
  .pl-tag{left:7%}
  .pl-tag--right{left:auto;right:7%}
  .pl-lockup{gap:.24em}
}
/* Short/landscape viewports: the lockup grows with vw, so on a wide but
   shallow screen it can reach up towards the tags. Push them outward. */
@media (max-height:520px){
  /* Attribute-qualified to match the specificity of the [data-i] defaults
     above (0,2,0) — source order then decides, which is what we want. */
  .pl-tag[data-i]{--pl-top:10%}
  .pl-tag--low[data-i]{--pl-top:87%}
}
