/* Vivia — motion layer.
   Purely additive: transforms, opacity and interaction states only.
   No layout, spacing, colour or copy is altered. All hidden states are
   applied by JS (anim.js); if scripts fail or motion is reduced, everything
   stays visible. */

/* ---- Lenis smooth scroll (official reset) ---- */
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: clip; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* ---- Paint gate ----
   Held only while JS assigns from-states, then removed the same frame.
   A 3s failsafe (anim.js) drops it even if GSAP never loads. */
html.v-pre main,
html.v-pre header nav { opacity: 0 !important; }

/* ---- Ambient: a faint blue wash over the white background ---- */
/* Painted as a fixed compositor layer rather than `background-attachment: fixed`,
   which forces a full-page repaint on every scroll frame (major scroll jank). */
body { background: var(--surface-page); }
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(90% 55% at 12% 0%, rgba(37,99,235,0.06), rgba(37,99,235,0) 60%),
    radial-gradient(85% 55% at 100% 100%, rgba(37,99,235,0.05), rgba(37,99,235,0) 60%);
}

/* ---- Hairline rule that draws in left→right on scroll ----
   Overlays the row's existing 1px top rule (which JS turns transparent),
   so the resting appearance is identical to the original. */
.v-rule-draw {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 1px;
  background: var(--rule);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}

/* Split lines get a compositor hint */
.v-split-line { will-change: transform; }

/* ---- HOVER: nav links — underline slides in ---- */
.v-navlink::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 2px;
  background: var(--cobalt);
  opacity: .55;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 200ms ease-out;
  pointer-events: none;
}
.v-navlink:hover::after { transform: scaleX(1); }
.v-navlink.is-active::after { display: none; }

/* Keep nav items on one line — the 2x logo squeezes the bar at narrower
   widths and "À propos" would otherwise break across two lines. */
.v-navlink { white-space: nowrap; }

/* Nav scroll-spy: the current section's link turns blue. Class-driven so it
   fully overrides the component's inline active border. */
.v-navlink { transition: color 220ms ease-out, border-color 220ms ease-out !important; }
.v-navlink { border-bottom-color: transparent !important; }
.v-navlink.is-active { color: var(--cobalt) !important; border-bottom-color: var(--cobalt) !important; }

/* ---- HOVER: email CTA — underline slides in (it has none at rest) ---- */
#contact a[href^="mailto"] { position: relative; }
#contact a[href^="mailto"]::after {
  content: "";
  position: absolute;
  left: 0; bottom: .06em;
  width: 100%; height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 200ms ease-out;
  pointer-events: none;
}
#contact a[href^="mailto"]:hover::after { transform: scaleX(1); }

/* ---- HOVER: buttons invert over 200ms (the documented primary/outline behaviour) ---- */
.v-btn { transition: background 200ms ease-out, color 200ms ease-out, box-shadow 200ms ease-out; }
.v-btn--primary:hover { background: var(--paper); color: var(--cobalt); box-shadow: inset 0 0 0 1px var(--cobalt); }
.v-btn--outline:hover { background: var(--cobalt); color: var(--paper); box-shadow: inset 0 0 0 1px var(--cobalt); }

/* ---- Header logo: a subtle light glint sweeps across the mark now and then ---- */
.v-logo::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  -webkit-mask: var(--logo-mask) center / contain no-repeat;
          mask: var(--logo-mask) center / contain no-repeat;
  background: linear-gradient(105deg, transparent 42%, rgba(150,180,255,.45) 50%, transparent 58%);
  background-size: 250% 100%;
  background-position: 160% 0;
  animation: vLogoShine 6s ease-in-out 2s infinite;
}
@keyframes vLogoShine {
  0%, 14% { background-position: 160% 0; }
  32%     { background-position: -60% 0; }
  100%    { background-position: -60% 0; }
}

/* ---- Eye-catcher: scrolling marquee strip (studio keyword ticker) ---- */
.v-marquee {
  overflow: hidden;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  padding: clamp(16px, 2.2vw, 26px) 0;
  background: var(--paper);
  /* Feather both ends so the text dissolves instead of hard-cutting at the edge. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
}
.v-marquee__track {
  display: flex;
  width: max-content;
  animation: vMarquee 32s linear infinite;   /* JS takes over and drives this by scroll velocity */
  will-change: transform, filter;
}
/* Only relevant before JS hands over (and under reduced motion). */
.v-marquee:hover .v-marquee__track { animation-play-state: paused; }
.v-marquee__set { display: flex; align-items: center; flex: none; }
.v-marquee__set span {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 3.2vw, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
  white-space: nowrap;
  padding-left: clamp(24px, 3vw, 48px);
}
.v-marquee__dot {
  width: 0.32em; height: 0.32em;
  border-radius: 50%;
  background: var(--cobalt);
  display: inline-block;
  margin-left: clamp(24px, 3vw, 48px);
}
@keyframes vMarquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ---- Eye-catcher: soft drifting accent glow behind the hero ---- */
/* No clip here — the full-width dot canvas needs to escape the centred hero box.
   The soft glow's edges are already transparent, and body has overflow-x: clip
   so nothing causes a horizontal scrollbar. */
#top { position: relative; }
#top::before {
  content: "";
  position: absolute;
  top: -18%; right: -8%;
  width: min(640px, 58vw); height: min(640px, 58vw);
  background: radial-gradient(circle, rgba(37,99,235,0.14), rgba(37,99,235,0) 68%);
  z-index: 0;
  pointer-events: none;
  animation: vDrift 16s ease-in-out infinite alternate;
}
#top > * { position: relative; z-index: 1; }
/* Dot canvas sits behind the hero content (needs to outrank `#top > *` above),
   and spans the FULL viewport width — the hero is a centred 1200px box, so
   inset:0 alone would leave the dots short of the screen edges. */
#top > canvas.v-dots {
  position: absolute;
  top: 0;
  left: 50%;
  width: 100vw;
  max-width: none;   /* override the global `canvas { max-width: 100% }`, which
                        otherwise clamps this to the 1200px hero box */
  height: 100%;
  transform: translateX(-50%);
  z-index: 0;
  pointer-events: none;
}
@keyframes vDrift { from { transform: translate(0,0) scale(1); } to { transform: translate(-46px,34px) scale(1.1); } }

/* ---- Laptop reveal: lid opens on scroll, screen shows the mark over waves ---- */
.v-laptop-stage {
  perspective: 1500px;
  padding: clamp(56px, 8vw, 110px) var(--gutter) clamp(72px, 9vw, 130px);
  display: flex;
  justify-content: center;
  background: var(--paper);
}
.v-devices {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: clamp(14px, 2.4vw, 34px);
  width: 100%;
}
.v-laptop { width: min(560px, 76%); transform-style: preserve-3d; }

/* ---- Phone: lock screen slides up to reveal the mark ---- */
.v-phone { width: clamp(88px, 15%, 128px); flex: none; }
.v-phone__screen {
  position: relative;
  aspect-ratio: 9 / 19;
  background: #000;
  border: 5px solid #141518;
  border-radius: 20px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 18px 42px rgba(15,17,21,0.18);
}
.v-phone__waves { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; }
.v-phone__logo { position: relative; z-index: 2; width: 68%; height: auto; display: block; }
.v-phone__lock {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: #0a0b0d;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translateY(-100%);   /* unlocked by default; GSAP scrubs it down→up */
}
.v-phone__lockicon {
  width: 15px; height: 12px;
  background: rgba(255,255,255,0.88);
  border-radius: 2px;
  position: relative;
}
.v-phone__lockicon::before {
  content: "";
  position: absolute;
  left: 50%; top: -9px;
  transform: translateX(-50%);
  width: 11px; height: 11px;
  border: 2px solid rgba(255,255,255,0.88);
  border-bottom: 0;
  border-radius: 6px 6px 0 0;
}
.v-laptop__lid {
  transform-origin: 50% 100%;
  transform: rotateX(0deg);   /* open by default; GSAP scrubs it from -90 */
  background: #141518;
  padding: 10px 10px 0;
  border-radius: 12px 12px 0 0;
  box-shadow: 0 24px 60px rgba(15,17,21,0.18);
}
.v-laptop__screen {
  position: relative;
  aspect-ratio: 16 / 10;
  background: #000;
  overflow: hidden;
  border-radius: 6px 6px 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.v-laptop__waves { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; }
.v-wave { animation: vWaveSlide 14s linear infinite; }
.v-wave--b { animation-duration: 21s; }
.v-wave--c { animation-duration: 28s; animation-direction: reverse; }
@keyframes vWaveSlide { from { transform: translateX(0); } to { transform: translateX(-1200px); } }
.v-laptop__logo { position: relative; z-index: 2; width: min(34%, 220px); height: auto; display: block; }
.v-laptop__base {
  position: relative;
  height: 14px;
  background: linear-gradient(#d9dce1, #b4b9c1);
  border-radius: 0 0 14px 14px;
  box-shadow: 0 14px 26px rgba(15,17,21,0.16);
}
.v-laptop__notch {
  position: absolute; left: 50%; top: 0;
  width: 84px; height: 5px;
  transform: translateX(-50%);
  background: rgba(15,17,21,0.16);
  border-radius: 0 0 6px 6px;
}

/* ---- Services: turn the stacked "Inclus" column into flat tags ---- */
.v-ruledrow > div:nth-child(3) {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-content: flex-start;
}
.v-ruledrow > div:nth-child(3) > div:first-child { flex: 0 0 100%; margin-bottom: 2px !important; }
.v-ruledrow > div:nth-child(3) > div:not(:first-child) {
  border: 1px solid var(--hairline);
  padding: 5px 11px;
  font-size: 0.8125rem;
  color: var(--grey);
  background: var(--paper);
  white-space: nowrap;
}

/* ---- Eye-catcher: service rows light up on hover ---- */
.v-ruledrow { transition: background-color 220ms ease; }
.v-ruledrow:hover { background-color: rgba(37,99,235,0.045); }
.v-ruledrow h3 { transition: color 200ms ease; }
.v-ruledrow:hover h3 { color: var(--cobalt); }
.v-ruledrow h3::after {
  content: "→";
  margin-left: .45em;
  color: var(--cobalt);
  font-family: var(--font-display);
  display: inline-block;
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity .22s ease, transform .22s ease;
}
.v-ruledrow:hover h3::after { opacity: 1; transform: translateX(0); }

/* =====================================================================
   RESPONSIVE
   The layout components hard-code their 12-column spans as inline styles
   (gridColumn: '1 / 9' etc.), which CSS can't beat without !important —
   hence the overrides below.
   ===================================================================== */

/* No sideways scroll. `clip` rather than `hidden` so sticky headers survive. */
html, body { overflow-x: clip; }
img, canvas, svg { max-width: 100%; }

/* The 12-col editorial grid gets unreadable below ~900px — go single column. */
@media (max-width: 900px) {
  .v-grid { grid-template-columns: 1fr; }
  .v-grid > * { grid-column: 1 / -1 !important; }
  #top .v-grid { row-gap: 44px; }
  #contact form { margin-top: 4px; }
}

/* Services ledger rows: index / body / tags stack instead of squeezing. */
@media (max-width: 760px) {
  .v-ruledrow {
    grid-template-columns: 1fr !important;
    gap: 14px !important;
    padding: 28px 0 !important;
  }
}

/* Approche: 4 steps across → 2 → 1. */
@media (max-width: 900px) {
  .v-steps { grid-template-columns: repeat(2, 1fr) !important; row-gap: 40px !important; }
}
@media (max-width: 520px) {
  .v-steps { grid-template-columns: 1fr !important; }
}

/* ---- Header / mobile hamburger ---- */

/* Desktop: nav + language sit together on the right, burger hidden. */
.v-navwrap { display: flex; align-items: baseline; gap: 28px; margin-left: auto; }
.v-burger { display: none; }

@media (max-width: 820px) {
  header img { height: 42px !important; }
  .v-headinner { align-items: center !important; padding-top: 14px !important; padding-bottom: 14px !important; }

  /* Keep the mark visible above the open panel, alongside the close button. */
  .v-headinner > a:first-of-type { position: relative; z-index: 70; }

  .v-burger {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 44px;
    height: 44px;
    padding: 11px 9px;
    margin-left: auto;
    background: none;
    border: 0;
    cursor: pointer;
    position: relative;
    z-index: 70;   /* stays clickable above the open panel */
  }
  .v-burger span {
    display: block;
    width: 100%;
    height: 2px;
    background: var(--ink);
    border-radius: 1px;
    transform-origin: center;
    transition: transform 380ms cubic-bezier(.16,1,.3,1), opacity 160ms ease, width 320ms ease;
  }
  /* Closed: middle bar sits short, so it reads as a mark rather than 3 equal lines. */
  .v-burger span:nth-child(2) { width: 68%; }
  .v-burger:hover span:nth-child(2) { width: 100%; }

  /* Bars converge, then cross into an X — the middle collapses first. */
  .v-burger span:nth-child(1) { transition-delay: 60ms; }
  .v-burger span:nth-child(3) { transition-delay: 60ms; }
  .v-burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .v-burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; transform: scaleX(0); transition-delay: 0ms; }
  .v-burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  /* Frosted overlay — the page stays legible behind it, so it reads as a layer
     on top rather than a separate white screen. Fades in with a short lift
     instead of a full slide: a big translate under backdrop-filter drags the
     blur region across the viewport and looks smeary. */
  .v-navwrap {
    position: fixed;
    inset: 0;
    z-index: 60;
    margin-left: 0;
    background: rgba(255, 255, 255, 0.72);                      /* fallback */
    background: color-mix(in srgb, var(--paper) 72%, transparent);
    -webkit-backdrop-filter: blur(22px) saturate(150%);
            backdrop-filter: blur(22px) saturate(150%);
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 10px;
    padding: 88px var(--gutter) var(--gutter);
    opacity: 0;
    transform: translateY(-10px);
    visibility: hidden;
    transition: opacity 300ms ease,
                transform 440ms cubic-bezier(.16,1,.3,1),
                visibility 0s linear 440ms;
  }
  .v-navwrap.is-open {
    opacity: 1;
    transform: translateY(0);
    visibility: visible;
    transition: opacity 300ms ease,
                transform 440ms cubic-bezier(.16,1,.3,1),
                visibility 0s;
  }

  /* The sticky header is opaque, which would leave a solid strip across the top
     of the overlay — drop it out so the frost is uniform edge to edge. */
  html.v-navopen header {
    background: transparent !important;
    border-bottom-color: transparent !important;
  }

  .v-navwrap nav {
    flex-direction: column;
    align-items: flex-start !important;
    gap: 2px !important;
    width: 100%;
  }
  .v-navwrap .v-navlink {
    font-size: 1.9rem !important;
    font-weight: 600 !important;
    letter-spacing: -0.02em;
    padding: 14px 0 !important;
    border-bottom: 0 !important;
    width: 100%;
  }
  /* The desktop underline reads as a stray rule at this size. */
  .v-navwrap .v-navlink::after { display: none !important; }
  .v-navwrap .v-langlink { margin-top: 18px; font-size: 0.875rem !important; }

  /* Links rise in one after another once the panel lands, rather than the whole
     block arriving flat. Delays only apply while opening — closing snaps back
     together so the panel doesn't linger on the way out. */
  /* NOTE: the scroll-spy rule sets `transition: color, border-color !important`
     on .v-navlink, which would wipe out opacity/transform here and force the
     delays to 0 — so these have to be !important too, and must re-declare the
     colour transition the spy relies on. */
  .v-navwrap .v-navlink,
  .v-navwrap .v-langlink {
    opacity: 0;
    transform: translateY(26px);
    transition: opacity 380ms ease,
                transform 620ms cubic-bezier(.16, 1, .3, 1),
                color 220ms ease-out !important;
  }
  .v-navwrap.is-open .v-navlink,
  .v-navwrap.is-open .v-langlink { opacity: 1; transform: none; }
  .v-navwrap.is-open nav .v-navlink:nth-child(1) { transition-delay: 150ms !important; }
  .v-navwrap.is-open nav .v-navlink:nth-child(2) { transition-delay: 215ms !important; }
  .v-navwrap.is-open nav .v-navlink:nth-child(3) { transition-delay: 280ms !important; }
  .v-navwrap.is-open nav .v-navlink:nth-child(4) { transition-delay: 345ms !important; }
  .v-navwrap.is-open .v-langlink { transition-delay: 420ms !important; }

  /* Accent rule that wipes in beside the active/pressed link. */
  .v-navwrap .v-navlink { position: relative; padding-left: 0 !important; }
  .v-navwrap .v-navlink::before {
    content: "";
    position: absolute;
    left: -14px;
    top: 50%;
    width: 6px;
    height: 6px;
    margin-top: -3px;
    border-radius: 50%;
    background: var(--cobalt);
    opacity: 0;
    transform: scale(.4);
    transition: opacity 220ms ease, transform 260ms cubic-bezier(.16,1,.3,1);
  }
  .v-navwrap .v-navlink.is-active::before { opacity: 1; transform: scale(1); }

  /* Freeze the page behind the panel. */
  html.v-navopen, html.v-navopen body { overflow: hidden; }
}

@media (max-width: 620px) {
  header img { height: 38px !important; }
  .v-navwrap .v-navlink { font-size: 1.65rem !important; }
}

/* Reduced motion: no sliding, just appear. */
html.v-reduce .v-navwrap { transition: none; }
html.v-reduce .v-burger span { transition: none; }

/* Hero headline: the clamp floor (3.25rem) still overflows a 375px screen. */
@media (max-width: 520px) {
  #top h1 { font-size: clamp(2.1rem, 10.5vw, 3.25rem) !important; }
}

/* Devices: laptop and phone stack rather than shrinking into nothing. */
@media (max-width: 700px) {
  .v-devices { flex-direction: column; align-items: center; gap: 26px; }
  .v-laptop { width: 100%; }
  .v-phone { width: 108px; }
}

/* Marquee scales down so it doesn't dominate a phone screen. */
@media (max-width: 520px) {
  .v-marquee__set span { font-size: 1.2rem; padding-left: 20px; }
  .v-marquee__dot { margin-left: 20px; }
}

/* Contact links wrap instead of overflowing. */
.v-contactlink { word-break: break-word; }

/* ---- Reduced motion: disable everything, show content immediately ----
   Gated on the JS-set `html.v-reduce` class (added by the inline head script
   when it reads prefers-reduced-motion) rather than a raw @media query, so a
   single source of truth drives both the JS and CSS paths. */
html.v-reduce .v-rule-draw { transform: none !important; }
html.v-reduce .v-navlink::after,
html.v-reduce #contact a[href^="mailto"]::after { transition: none; }
html.v-reduce .v-btn { transition: none; }
html.v-reduce .v-marquee__track { animation: none; }
html.v-reduce #top::before { animation: none; }
html.v-reduce .v-ruledrow h3::after { transition: none; }
html.v-reduce .v-wave { animation: none; }
html.v-reduce .v-laptop__lid { transform: none; }
html.v-reduce .v-phone__lock { transform: translateY(-100%); }
