/* ============================================================
   WestSite — Global Styles
   Strictly based on DESIGN-TOKENS.md v1.0
   ============================================================ */

/* ============================================================
   0. FONTS (self-hosted)
   Was a render-blocking <link> to fonts.googleapis.com, which cost two
   extra origins (DNS + TLS + CSS round-trip) before a single glyph could
   paint. The woff2 files are Google's own, downloaded once and served
   from our origin instead.

   These are VARIABLE fonts: one file per family+subset carries the whole
   weight axis, which is why each rule declares a font-weight RANGE rather
   than a single value. Google's own CSS declares one @font-face per weight
   all pointing at the same url — downloading per weight duplicates
   identical bytes (it doubled the payload to 179Ko before this was caught).
   If you ever add a font, check whether the urls differ per weight before
   assuming static instances.

   Ranges cover exactly the weights the pages render — Space Grotesk
   600/700, DM Sans 400/500, JetBrains Mono 400/500/600 — verified against
   every font-weight rule in this file AND against document.fonts at
   runtime. A weight outside its range gets clamped, not faux-bolded, but
   keep CSS and ranges in sync anyway.

   latin covers all of French (including the œ ligature, U+0152-0153);
   latin-ext is insurance for an unusual client name and, thanks to
   unicode-range, is never downloaded unless such a character appears.

   font-display: swap — text paints immediately in the fallback and swaps
   in, rather than staying invisible. Licence: SIL OFL 1.1, see
   assets/fonts/LICENSE.txt.
   ============================================================ */
/* DM Sans 400–500 (variable) — latin-ext */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('../assets/fonts/dm-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* DM Sans 400–500 (variable) — latin */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('../assets/fonts/dm-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* JetBrains Mono 400–600 (variable) — latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* JetBrains Mono 400–600 (variable) — latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Space Grotesk 600–700 (variable) — latin-ext */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 600 700;
  font-display: swap;
  src: url('../assets/fonts/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Space Grotesk 600–700 (variable) — latin */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 600 700;
  font-display: swap;
  src: url('../assets/fonts/space-grotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- 1. Design Tokens (CSS Variables) ---------- */
:root {
  /* Background Colors */
  --color-bg-primary: #0A0A0C;
  --color-bg-secondary: #13131A;
  --color-bg-tertiary: #16161A;
  --color-bg-overlay: rgba(10, 10, 12, 0.8);

  /* Electric Accents */
  --color-purple-electric: #9D00FF;
  --color-purple-electric-dim: #7A0FD8;
  --color-purple-electric-bright: #B100FF;
  --color-yellow-electric: #F5FF00;
  --color-yellow-electric-dim: #CCCC00;
  --color-yellow-electric-muted: #E8E800;

  /* Gradients */
  --gradient-electric-ltr: linear-gradient(90deg, #9D00FF 0%, #F5FF00 100%);
  --gradient-electric-ttb: linear-gradient(180deg, #9D00FF 0%, #F5FF00 100%);
  --gradient-electric-diagonal: linear-gradient(135deg, #9D00FF 0%, #F5FF00 100%);
  --gradient-text: linear-gradient(90deg, #B100FF 0%, #F5FF00 100%);

  /* Text Colors */
  --color-text-primary: #F8F8FB;
  --color-text-secondary: #B8B8C8;
  --color-text-muted: #8888A0;
  --color-text-contrast: #0A0A0C;

  /* Functional Colors */
  --color-success: #00D96F;
  --color-warning: #FFB84D;
  --color-error: #FF6B6B;
  --color-info: #00C9FF;

  /* Borders */
  --color-border: #2A2A35;
  --color-border-active: #9D00FF;
  --color-border-subtle: #1F1F28;

  /* Typography */
  --font-heading: 'Space Grotesk', sans-serif;
  --font-body: 'DM Sans', sans-serif;
  --font-mono: 'JetBrains Mono', monospace;

  /* Spacing (8px scale) */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;
  --space-3xl: 64px;
  --space-4xl: 80px;

  /* Border Radius */
  --radius-none: 0px;
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-full: 9999px;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 4px 12px rgba(9, 0, 255, 0.15), 0 2px 4px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 12px 32px rgba(9, 0, 255, 0.2), 0 4px 8px rgba(0, 0, 0, 0.4);
  --shadow-glow: 0 0 20px rgba(157, 0, 255, 0.3), 0 0 40px rgba(245, 255, 0, 0.1);

  /* Z-Index */
  --z-base: 0;
  --z-texture: 1;
  --z-content: 2;
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-fixed: 300;
  --z-modal-backdrop: 400;
  --z-modal: 500;
  --z-popover: 600;
  --z-notification: 700;
  --z-cursor: 9000;

  /* Animation */
  --duration-fast: 100ms;
  --duration-normal: 200ms;
  --duration-slow: 400ms;
  --duration-slower: 600ms;
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-elastic: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* Layout */
  --container-max: 1200px;
  --nav-height: 72px;
}

/* ---------- 2. Reset & Base ---------- */
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* Offset anchor targets so sticky nav doesn't cover them */
  scroll-padding-top: var(--nav-height);
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  color: var(--color-text-primary);
  background-color: var(--color-bg-primary);
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  color: var(--color-text-primary);
  line-height: 1.2;
}

a {
  color: inherit;
  text-decoration: none;
}

ul {
  list-style: none;
}

img {
  max-width: 100%;
  display: block;
}

button {
  font-family: inherit;
  cursor: pointer;
  border: none;
  background: none;
}

/* ---------- 3. Layout Utilities ---------- */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--space-lg);
}

.section {
  padding-block: var(--space-4xl);
}

/* Gradient text helper (used for logo, headlines) */
.gradient-text {
  background: var(--gradient-text);
  background-size: 100% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

/* Animated variant — reserved for a single hero/headline accent, never
   the nav logo or footer brand (constant motion in chrome would be noisy).
   Ref: react-bits GradientText, re-authored in plain CSS for our palette. */
.gradient-text-animated {
  background-size: 200% 100%;
  animation: gradientSweep 6s var(--ease-in-out) infinite alternate;
}

@keyframes gradientSweep {
  0%   { background-position: 0% 50%; }
  100% { background-position: 100% 50%; }
}

/* ---------- 4. Buttons (from component specs) ---------- */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 14px;
  border-radius: var(--radius-md);
  padding: var(--space-md) var(--space-lg);
  transition: all var(--duration-normal) var(--ease-out);
  white-space: nowrap;
}

.btn:focus-visible {
  outline: 2px solid var(--color-purple-electric);
  outline-offset: 2px;
}

/* Specular sheen — a glassy highlight that tracks the cursor across the
   button surface (JS sets --spec-x / --spec-y on pointermove).
   Ref: react-bits "SpecularButton" (WebGL/OGL shader) — re-authored as a
   dependency-free CSS radial highlight that captures the same intent:
   a rim/surface light that follows the pointer. */
.btn::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(
    140px circle at var(--spec-x, 50%) var(--spec-y, 0%),
    rgba(255, 255, 255, 0.16),
    rgba(255, 255, 255, 0.04) 35%,
    transparent 65%
  );
  opacity: 0;
  transition: opacity var(--duration-normal) var(--ease-out);
}

.btn:hover::after {
  opacity: 1;
}

/* Keep the button label above the sheen */
.btn > * {
  position: relative;
  z-index: 2;
}

/* Primary — signature gradient border, gradient text on hover.
   ::before carries a blurred rotating conic-gradient glow, kept inert
   (opacity 0) until hover so it costs nothing at rest.
   Ref: 21st.dev "Glow Button" (unlumen), re-authored in plain CSS,
   restricted to our purple/yellow pair and toned down for restraint.

   The glow is clipped to the button (overflow: hidden) — it used to sit
   at inset:-10px with an 18px blur, i.e. ~28px of purple/yellow bleeding
   past every edge, which crossed the header's bottom border under the nav
   CTA (only 7px of clearance there). It already tinted the button's own
   surface before this change; only the spill outside is gone. */
.btn-primary {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--color-bg-secondary);
  color: var(--color-text-primary);
  border: 2px solid var(--color-purple-electric);
}

.btn-primary::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  /* A square wider than the button's diagonal, so the spin never sweeps
     the gradient's own edges into view. Not the inset:-50% trick used by
     .card-glow: that follows the element's aspect ratio, and on a button
     (wide and flat) the box would be narrower than the button at 90°,
     leaving unlit gaps at the left/right edges. */
  width: 150%;
  aspect-ratio: 1;
  /* Separate property on purpose: `transform` belongs to the spin below,
     and setting it here would cancel the centering (or vice versa). */
  translate: -50% -50%;
  /* Load-bearing pair with `isolation: isolate` above. The button forms a
     stacking context, so this negative level paints *after* the button's
     own background and *before* its inline text (CSS 2.1 §E.2 steps 1 → 2
     → 5) — i.e. over the surface, under the label. Don't "fix" it to 0:
     the labels are bare text nodes, so `.btn > * { z-index: 2 }` doesn't
     cover them and the glow would wash over the text. */
  z-index: -1;
  background: conic-gradient(
    from 0deg,
    var(--color-purple-electric),
    var(--color-yellow-electric),
    var(--color-purple-electric)
  );
  filter: blur(18px);
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-out);
}

.btn-primary:hover {
  border-color: var(--color-yellow-electric);
  box-shadow: var(--shadow-md);
}

.btn-primary:hover::before {
  opacity: 0.45;
  animation: btnGlowRotate 4s linear infinite;
}

.btn-primary:active {
  border-color: var(--color-yellow-electric);
  color: var(--color-yellow-electric);
  box-shadow: var(--shadow-glow);
}

@keyframes btnGlowRotate {
  to { transform: rotate(360deg); }
}

/* Secondary — subtle outline */
.btn-secondary {
  background: transparent;
  color: var(--color-text-secondary);
  border: 1px solid var(--color-border);
}

.btn-secondary:hover {
  background: var(--color-bg-secondary);
  border-color: var(--color-text-secondary);
  color: var(--color-text-primary);
}

/* ============================================================
   5. HEADER / STICKY NAVIGATION
   ============================================================ */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  height: var(--nav-height);
  background: var(--color-bg-overlay);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--color-border-subtle);
  transition: border-color var(--duration-normal) var(--ease-out);
}

/* .container has no explicit height, so .nav's height:100% had nothing
   definite to resolve against and collapsed to content size (~44px instead
   of the header's 72px) — logo/links ended up pinned to the top instead of
   vertically centered. */
.site-header .container {
  height: 100%;
}

/* Slightly stronger border once the user scrolls */
.site-header.is-scrolled {
  border-bottom-color: var(--color-border);
  box-shadow: var(--shadow-sm);
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 100%;
}

/* Logo — framed to the nav height regardless of the source image's aspect
   ratio, vertically centered via flex rather than relying on line-height. */
.nav-logo {
  display: flex;
  align-items: center;
  height: 100%;
  flex-shrink: 0;
}

.nav-logo-img {
  display: block;
  height: 60%;
  max-height: 44px;
  width: auto;
  object-fit: contain;
}

/* Text fallback if the logo image is missing/fails to load */
.nav-logo-fallback {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 24px;
  letter-spacing: -0.02em;
}

/* Desktop nav links — centred in the header regardless of how wide the logo
   or the right-hand actions are (previously space-between let them hug the
   logo, leaving a large empty gap before the CTA). */
/* Absolutely centred in the nav (so the row stays optically centred whatever
   the logo/CTA widths). Trade-off: it can't push its neighbours, so its width
   has to stay under (viewport − CTA×2 − padding) — see the drawer breakpoint. */
.nav-links {
  display: flex;
  align-items: center;
  /* --space-sm, not --space-md: the 6th item (FAQ) pushed the row 6px past
     what fits at 1024px. The links already carry 16px of horizontal padding
     each, so the gap between labels stays comfortable at 40px. */
  gap: var(--space-sm);
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

/* Horizontal padding gives the gooey pill (sized off this element's own
   rect, see main.js) room to breathe around the label instead of hugging
   the text tightly. */
.nav-link {
  position: relative;
  z-index: 1;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 15px;
  color: var(--color-text-secondary);
  padding: var(--space-xs) var(--space-md);
  /* The centred row shrinks to its containing block, so labels would wrap
     onto two lines ("À / propos") before the drawer takes over. */
  white-space: nowrap;
  transition: color var(--duration-normal) var(--ease-out);
}

.nav-link:hover {
  color: var(--color-text-primary);
}

/* Animated gradient underline for active link (fallback when JS/pill absent) */
.nav-link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 100%;
  height: 2px;
  background: var(--gradient-electric-ltr);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--duration-normal) var(--ease-out);
}

.nav-link.is-active {
  color: var(--color-text-primary);
}

.nav-link.is-active::after {
  transform: scaleX(1);
}

/* ---- Gooey Nav (desktop) ----
   Ref: react-bits "GooeyNav" — official source, ported to vanilla JS/CSS.
   A single shared "goo" layer snaps under the active link and pops in with
   an organic, elastic blob edge (blur+contrast filter on a bright pill).
   The bubble/particle burst from the original component is removed per
   request; the pop itself carries the "gooey" read on its own.
   Colors: bright purple pill + dark backdrop instead of the source's plain
   white/black, so it stays on the brand palette. */
.gooey-ease {
  /* Elastic overshoot easing, reused for the blob's pop-in */
  --gooey-ease: linear(
    0, 0.068, 0.19 2.7%, 0.804 8.1%, 1.037, 1.199 13.2%, 1.245, 1.27 15.8%,
    1.274, 1.272 17.4%, 1.249 19.1%, 0.996 28%, 0.949, 0.928 33.3%, 0.926,
    0.933 36.8%, 1.001 45.6%, 1.013, 1.019 50.8%, 1.018 54.4%, 1 63.1%,
    0.995 68%, 1.001 85%, 1
  );
}

.nav-links.has-gooey .nav-link::after {
  display: none; /* blob replaces the underline indicator */
}

/* Desktop only: the gooey blob is only positioned/visible at >=1024px (see
   main.js isDesktopNav), but .has-gooey stays on .nav-links at every width —
   unscoped, this rule's higher specificity beat the mobile active-link color
   below and rendered the drawer's active item in near-black bg-primary. */
@media (min-width: 1024px) {
  .nav-links.has-gooey .nav-link.is-active {
    color: var(--color-bg-primary); /* dark text reads on the bright blob below it */
  }
}

.gooey-effect {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  z-index: 0;
  pointer-events: none;
}

/* The blob's softness lives on ::after itself (blur that resolves to sharp
   as it pops in) rather than a large blurred+contrasted backdrop — the
   official source's -75px backdrop trick assumes a solid, matching-color
   page background to blend into; ours is a translucent, blurred header, so
   that backdrop rendered as a visible dark slab bleeding past the header. */
.gooey-effect::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-full);
  background: var(--color-purple-electric);
  box-shadow: 0 0 18px rgba(157, 0, 255, 0.5);
  transform: scale(0.6);
  opacity: 0;
  filter: blur(10px);
}

.gooey-effect.is-popped::after {
  animation: gooeyPop 0.5s var(--gooey-ease) both;
}

@keyframes gooeyPop {
  0%   { transform: scale(0.6); opacity: 0; filter: blur(10px); }
  60%  { opacity: 1; filter: blur(2px); }
  100% { transform: scale(1); opacity: 1; filter: blur(0); }
}

/* Right cluster: CTA + burger */
.nav-actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

/* Burger toggle (hidden on desktop) */
.nav-burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
}

.nav-burger span {
  display: block;
  width: 24px;
  height: 2px;
  background: var(--color-text-primary);
  border-radius: var(--radius-full);
  transition: transform var(--duration-normal) var(--ease-out),
              opacity var(--duration-fast) var(--ease-out);
}

.nav-burger:focus-visible {
  outline: 2px solid var(--color-purple-electric);
  outline-offset: 2px;
}

/* Burger → X when menu open */
.nav-burger.is-open span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.nav-burger.is-open span:nth-child(2) {
  opacity: 0;
}
.nav-burger.is-open span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* Mobile-only CTA hidden on desktop by default; the mobile media query
   below overrides this back to display:block. Must come BEFORE that media
   query in source order — a class selector of equal specificity placed
   after a media block beats it regardless of the media condition, which
   previously kept this CTA permanently hidden even on mobile. */
.nav-mobile-cta {
  display: none;
}

/* ---------- Mobile / tablet Navigation ----------
   Breakpoint raised from 767px to 1023px when the FAQ link brought the nav
   to 6 items. .nav-links is absolutely centred, so it doesn't push the CTA
   aside — it overlaps it, and squeezes link labels onto two lines. The list
   is ~600px and the CTA ~181px, so the row only fits from ~990px up; below
   that the drawer is the only layout that works. It was already overlapping
   between 768 and 954px with 5 links — this fixes that too.
   ⚠️ main.js mirrors this breakpoint (sections 1 and 8) — change both. */
@media (max-width: 1023px) {
  .nav-burger {
    display: flex;
  }

  /* CTA hides in the top bar on mobile (lives inside the drawer instead) */
  .nav-actions .btn-primary {
    display: none;
  }

  .nav-links {
    position: fixed;
    top: var(--nav-height);
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0;
    padding: var(--space-lg);
    background: var(--color-bg-secondary);
    border-bottom: 1px solid var(--color-border);
    transform: translateY(-120%);
    opacity: 0;
    pointer-events: none;
    transition: transform var(--duration-slow) var(--ease-out),
                opacity var(--duration-normal) var(--ease-out);
    z-index: var(--z-dropdown);
  }

  .nav-links.is-open {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
  }

  /* Drawer items are <li> wrappers around .nav-link — stretch them full-width
     so the centred text lines up as one column instead of hugging content width. */
  .nav-links > li {
    width: 100%;
  }

  .nav-link {
    display: block;
    width: 100%;
    padding: var(--space-md) 0;
    font-size: 18px;
    border-bottom: 1px solid var(--color-border-subtle);
    /* Mobile browsers flash a default black overlay on tap; swap it for a
       transparent tap + a brand-gradient press state instead (see :active below). */
    -webkit-tap-highlight-color: transparent;
    transition: color var(--duration-normal) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
  }

  .nav-link:active {
    background: var(--gradient-electric-diagonal);
    color: var(--color-bg-primary);
  }

  .nav-link::after {
    display: none;
  }

  /* Current page/section highlighted with the brand gradient instead of a
     flat color — same recipe as the :active press state above, but persistent
     while the item is active. */
  .nav-link.is-active {
    background: var(--gradient-electric-diagonal);
    color: var(--color-bg-primary);
    border-radius: var(--radius-sm);
    border-bottom-color: transparent; /* the gradient pill replaces the row separator */
  }

  /* Full-width CTA at the bottom of the drawer */
  .nav-mobile-cta {
    display: block;
    margin-top: var(--space-lg);
    width: 100%;
  }
}

/* ============================================================
   6. SECTION PLACEHOLDERS (structure only)
   ============================================================ */
main > section {
  min-height: 40vh;
  scroll-margin-top: var(--nav-height);
}

/* ============================================================
   6b. HERO
   ============================================================ */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: calc(100dvh - var(--nav-height));
  overflow: hidden;
  padding-block: var(--space-3xl) var(--space-2xl);
  text-align: center;
}

/* --- Animated plasma-wave background (WebGL canvas, driven by main.js).
   Ref: react-bits "PlasmaWave", re-authored as raw WebGL. Radially masked
   so it concentrates upper-centre and fades to the dark bg at the edges,
   and toned down (opacity) so it stays a premium ambient wash, not garish.
   A CSS gradient fallback shows if WebGL is unavailable. --- */
.hero-plasma {
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
  width: 100%;
  height: 100%;
  opacity: 0.55;
  pointer-events: none;
  mask-image: radial-gradient(ellipse 75% 65% at 50% 38%, black 30%, transparent 100%);
  -webkit-mask-image: radial-gradient(ellipse 75% 65% at 50% 38%, black 30%, transparent 100%);
}

/* Fallback when WebGL can't initialise: a static on-brand gradient wash */
.hero-plasma.is-fallback {
  background:
    radial-gradient(60% 50% at 30% 30%, rgba(157, 0, 255, 0.35), transparent 70%),
    radial-gradient(50% 45% at 72% 60%, rgba(245, 255, 0, 0.14), transparent 70%);
}

/* --- Cursor-reactive spotlight (fine-pointer devices only, see main.js).
   Ref: react-bits "SpotlightCard", re-authored as a hero-wide ambient
   layer instead of a per-card effect. --- */
.hero-spotlight {
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
  pointer-events: none;
  background: radial-gradient(
    500px circle at var(--spot-x, 50%) var(--spot-y, 30%),
    rgba(157, 0, 255, 0.16),
    transparent 70%
  );
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-out);
}

.hero:hover .hero-spotlight {
  opacity: 1;
}

/* --- Content --- */
.hero-content {
  position: relative;
  z-index: var(--z-content);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
  max-width: 720px;
}

/* Eyebrow badge — small, specific, and immediately reads as "designed"
   rather than a generic title/subtitle/CTA template. */
.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-bg-secondary);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--color-text-secondary);
}

.hero-eyebrow-dot {
  width: 6px;
  height: 6px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--gradient-electric-ltr);
}

.hero-title {
  font-size: clamp(2.25rem, 6vw + 1rem, 4rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.025em;
  text-wrap: balance;
  text-shadow: 0 4px 32px rgba(157, 0, 255, 0.18);
}

.hero-subtitle {
  font-family: var(--font-body);
  font-size: clamp(1rem, 1.2vw + 0.85rem, 1.125rem);
  line-height: 1.6;
  color: var(--color-text-secondary);
  max-width: 56ch;
}

.hero-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-md);
  width: 100%;
  max-width: 360px;
  margin-top: var(--space-sm);
}

.hero-actions .btn {
  width: 100%;
}

.btn-icon {
  transition: transform var(--duration-normal) var(--ease-out);
}

.btn-primary:hover .btn-icon {
  transform: translateX(4px);
}

/* Trust / proof row — specific, concrete claims instead of ending on
   the CTA pair alone. */
.hero-proof {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm) var(--space-lg);
  margin-top: var(--space-xs);
  font-size: 13px;
  color: var(--color-text-muted);
}

.hero-proof li {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.hero-proof li::before {
  content: '';
  width: 4px;
  height: 4px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--color-purple-electric);
}

/* --- Scroll indicator (discreet) --- */
.scroll-indicator {
  position: absolute;
  bottom: var(--space-lg);
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-content);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  width: 22px;
  height: 36px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  padding-top: 6px;
  transition: border-color var(--duration-normal) var(--ease-out);
}

.scroll-indicator:hover {
  border-color: var(--color-text-secondary);
}

.scroll-indicator:focus-visible {
  outline: 2px solid var(--color-purple-electric);
  outline-offset: 2px;
}

.scroll-indicator-line {
  width: 3px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--gradient-electric-ttb);
  animation: scrollDrop 1.8s var(--ease-in-out) infinite;
}

@keyframes scrollDrop {
  0%   { transform: translateY(0); opacity: 1; }
  60%  { transform: translateY(10px); opacity: 0.3; }
  100% { transform: translateY(0); opacity: 0; }
}

/* --- Tablet and up --- */
@media (min-width: 768px) {
  .hero-actions {
    flex-direction: row;
    justify-content: center;
    max-width: none;
    width: auto;
  }

  .hero-actions .btn {
    width: auto;
  }
}

/* ============================================================
   6c. MARQUEE — decorative service-keyword strip
   Ref: react-bits "LogoLoop" (seamless duplicate track + edge fade +
   hover-pause), re-authored in plain CSS keyframes for a text list.
   ============================================================ */
.marquee {
  position: relative;
  overflow: hidden;
  border-block: 1px solid var(--color-border-subtle);
  background: var(--color-bg-secondary);
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0%,
    black 8%,
    black 92%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    transparent 0%,
    black 8%,
    black 92%,
    transparent 100%
  );
}

.marquee-track {
  display: flex;
  width: max-content;
  animation: marqueeScroll 28s linear infinite;
}

.marquee:hover .marquee-track {
  animation-play-state: paused;
}

.marquee-list {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding-block: var(--space-md);
  padding-inline-end: var(--space-lg);
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.02em;
  color: var(--color-text-muted);
}

.marquee-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--color-purple-electric);
  flex-shrink: 0;
}

@keyframes marqueeScroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .marquee-track {
    animation: none;
  }
}

/* ============================================================
   6d. SERVICES
   Section intro (eyebrow/title/subtitle) + responsive card grid
   (1 col mobile / 2 tablet / 3-4 desktop). Cards reuse the .card
   border-elevate pattern already defined for hover states, with an
   added gradient-glow border on hover per the section brief.
   ============================================================ */
.section-heading {
  max-width: 640px;
  margin-inline: auto;
  margin-bottom: var(--space-2xl);
  text-align: center;
}

.section-eyebrow {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-purple-electric-bright);
  margin-bottom: var(--space-sm);
}

.section-title {
  font-size: clamp(1.75rem, 3.5vw + 1rem, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-md);
}

.section-subtitle {
  font-size: 18px;
  color: var(--color-text-secondary);
}

.services-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
}

.service-card {
  position: relative;
  overflow: hidden;
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  transition: transform var(--duration-normal) var(--ease-out),
              border-color var(--duration-normal) var(--ease-out),
              box-shadow var(--duration-normal) var(--ease-out);
}

.service-card:hover,
.service-card:focus-within {
  transform: translateY(-4px);
  border-color: var(--color-border-active);
  box-shadow: var(--shadow-md);
}

/* Cursor-tracked spotlight — main.js sets --spot-x/--spot-y (px, relative
   to the card) on pointermove and toggles --spot-opacity on enter/leave/
   focus/blur. Ref: react-bits "SpotlightCard", re-authored dependency-free
   (no React state) in the brand purple, replacing the earlier static
   diagonal-gradient border glow. */
.service-card-spotlight {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: var(--spot-opacity, 0);
  transition: opacity 500ms var(--ease-in-out);
  background: radial-gradient(
    circle at var(--spot-x, 50%) var(--spot-y, 50%),
    var(--spot-color, rgba(157, 0, 255, 0.25)),
    transparent 80%
  );
}

.service-icon {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-md);
  background: var(--color-bg-tertiary);
  color: var(--color-purple-electric-bright);
  margin-bottom: var(--space-md);
}

.service-title {
  position: relative;
  z-index: 1;
  font-size: 20px;
  font-weight: 600;
  margin-bottom: var(--space-sm);
}

.service-desc {
  position: relative;
  z-index: 1;
  font-size: 15px;
  color: var(--color-text-secondary);
}

@media (min-width: 768px) {
  .services-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .services-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* ============================================================
   6e. STATS
   A slightly elevated band (bg-secondary + subtle top/bottom borders,
   same treatment as the marquee) separating Services from Réalisations.
   Big gradient numbers + short labels, same responsive grid steps as
   the services cards (1 col mobile / 2 tablet / 4 desktop).
   ============================================================ */
.stats-section {
  background: var(--color-bg-secondary);
  border-block: 1px solid var(--color-border-subtle);
}

.stats-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-2xl);
}

.stat-item {
  text-align: center;
}

.stat-value {
  display: block;
  font-family: var(--font-heading);
  font-size: clamp(2.25rem, 4vw + 1rem, 3rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.1;
  margin-bottom: var(--space-sm);
}

.stat-label {
  font-size: 15px;
  color: var(--color-text-secondary);
}

@media (min-width: 768px) {
  .stats-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .stats-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-lg);
  }

  .stat-item {
    position: relative;
  }

  .stat-item:not(:first-child)::before {
    content: '';
    position: absolute;
    left: calc(var(--space-lg) / -2);
    top: 4px;
    bottom: 4px;
    width: 1px;
    background: var(--color-border-subtle);
  }
}

/* ============================================================
   6f. RÉALISATIONS — project cards (fake-browser preview + 3D tilt)
   Cards are rendered into #projects-grid by main.js from the PROJECTS
   data array. Chrome (tabs/url bar) styled in WestSite tokens instead
   of the original Uiverse component's neutral greys. Ref: Uiverse
   "browser" mockup (Vue SFC, supplied by the user), re-authored in
   plain CSS + a vanilla pointermove 3D tilt (no library).
   ============================================================ */
.projects-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--space-lg);
  perspective: 1200px;
}

.project-card {
  position: relative;
  display: block;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transform-style: preserve-3d;
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transition: transform 150ms var(--ease-out), box-shadow var(--duration-normal) var(--ease-out);
}

.project-card:hover,
.project-card:focus-visible {
  box-shadow: var(--shadow-lg);
}

.project-card:focus-visible {
  outline: 2px solid var(--color-purple-electric);
  outline-offset: 4px;
}

.project-browser {
  border-radius: inherit;
  overflow: hidden;
  background: var(--color-bg-tertiary);
  border: 1px solid var(--color-border);
  transform: translateZ(20px);
}

.browser-tabs {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 34px;
  padding-inline: var(--space-sm);
  background: var(--color-bg-secondary);
  border-bottom: 1px solid var(--color-border-subtle);
}

.browser-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  max-width: 70%;
  padding: 4px var(--space-sm);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--color-bg-tertiary);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.browser-tab-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gradient-electric-ltr);
  flex-shrink: 0;
}

.browser-dots {
  display: flex;
  gap: 5px;
}

.browser-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-border);
}

.browser-urlbar {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm);
  background: var(--color-bg-secondary);
}

.browser-lock {
  flex-shrink: 0;
  color: var(--color-text-muted);
}

.browser-url {
  flex: 1;
  padding: 4px var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-bg-primary);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--color-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.browser-viewport {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--color-bg-primary);
}

.browser-screenshot {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.browser-placeholder {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  font-size: 12px;
}

/* Hover overlay — low-opacity brand gradient wash + "Voir le site" prompt */
.project-overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: inherit;
  background: var(--gradient-electric-diagonal);
  opacity: 0;
  transition: opacity var(--duration-normal) var(--ease-out);
}

.project-card:hover .project-overlay,
.project-card:focus-visible .project-overlay {
  opacity: 0.22;
}

.project-overlay-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-full);
  background: var(--color-bg-primary);
  color: var(--color-text-primary);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 13px;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--duration-normal) var(--ease-out),
              transform var(--duration-normal) var(--ease-out);
}

.project-card:hover .project-overlay-label,
.project-card:focus-visible .project-overlay-label {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .project-card {
    transform: none !important;
  }
}

/* ============================================================
   6g. TARIFS — 3-tier pricing cards
   Base .pricing-card style shared by all three; the featured "Pro"
   tier reuses .card-glow (spinning gradient ring, see 7b) inside an
   extra wrapper that adds a stationary blurred halo + a "Le plus
   choisi" badge, without touching the shared .card-glow ring itself
   (which needs overflow:hidden — the halo must sit outside that).
   ============================================================ */
.pricing-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-xl);
  align-items: start;
}

.pricing-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-xl) var(--space-lg);
  transition: transform var(--duration-normal) var(--ease-out),
              border-color var(--duration-normal) var(--ease-out),
              box-shadow var(--duration-normal) var(--ease-out);
}

.pricing-card:hover,
.pricing-card:focus-within {
  transform: translateY(-4px);
  border-color: var(--color-border-active);
  box-shadow: var(--shadow-md);
}

.pricing-head {
  margin-bottom: var(--space-lg);
}

.pricing-name {
  font-size: 22px;
  font-weight: 600;
  margin-bottom: var(--space-xs);
}

.pricing-desc {
  font-size: 14px;
  color: var(--color-text-secondary);
}

.pricing-price {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  margin-bottom: var(--space-lg);
  padding-bottom: var(--space-lg);
  border-bottom: 1px solid var(--color-border-subtle);
}

.pricing-price-from {
  font-size: 13px;
  color: var(--color-text-muted);
}

.pricing-price-amount {
  font-family: var(--font-heading);
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.pricing-features {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-bottom: var(--space-xl);
  flex-grow: 1;
}

.pricing-features li {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: 14px;
  color: var(--color-text-secondary);
}

.feature-check {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-full);
  background: var(--gradient-electric-diagonal);
  color: var(--color-bg-primary);
}

.pricing-cta {
  width: 100%;
}

/* Featured tier ("Pro") */
.pricing-featured-wrap {
  position: relative;
  isolation: isolate;
}

/* Stationary blurred halo — sits outside .card-glow so it isn't
   clipped by that element's overflow:hidden (needed to mask its own
   spinning conic-gradient ring down to a 1px border). */
.pricing-featured-wrap::before {
  content: '';
  position: absolute;
  inset: -28px;
  z-index: -1;
  filter: blur(48px);
  opacity: 0.35;
  background: radial-gradient(
    ellipse at 50% 30%,
    var(--color-purple-electric) 0%,
    var(--color-yellow-electric) 55%,
    transparent 75%
  );
}

.pricing-featured-wrap .card-glow {
  height: 100%;
}

.pricing-card--featured {
  border-radius: calc(var(--radius-lg) - 1px);
}

.pricing-card--featured:hover,
.pricing-card--featured:focus-within {
  transform: none;
  box-shadow: none;
}

.pricing-badge {
  position: absolute;
  top: -14px;
  left: 50%;
  z-index: 2;
  transform: translateX(-50%);
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-full);
  background: var(--gradient-electric-ltr);
  color: var(--color-bg-primary);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.02em;
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
}

@media (min-width: 1024px) {
  .pricing-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-lg);
  }

  /* Slight elevation for the featured tier, without disturbing the
     grid row height (siblings size to content, not to this card). */
  .pricing-featured-wrap {
    margin-top: calc(-1 * var(--space-lg));
    margin-bottom: var(--space-lg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .card-glow::before {
    animation: none;
  }
}

/* ============================================================
   6h. CONTACT — SMS-redirect form + Instagram sidebar card
   No backend: main.js builds an sms: URI from these fields on submit
   instead of posting anywhere. Styled per the "Form Input" spec in
   DESIGN-TOKENS.md §12 (bg-tertiary fields, purple focus border).
   ============================================================ */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-2xl);
  align-items: start;
}

.contact-form {
  max-width: 640px;
  margin-inline: auto;
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-xl) var(--space-lg);
}

.form-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-bottom: var(--space-lg);
}

.form-label {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  color: var(--color-text-secondary);
}

.form-label-optional {
  color: var(--color-text-muted);
  font-weight: 400;
}

.form-input {
  width: 100%;
  background: var(--color-bg-tertiary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--color-text-primary);
  transition: border-color var(--duration-normal) var(--ease-out);
}

.form-input::placeholder {
  color: var(--color-text-muted);
}

.form-input:focus-visible {
  outline: none;
  border-color: var(--color-purple-electric);
}

.form-select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%23B8B8C8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-md) center;
  padding-right: var(--space-2xl);
}

.form-textarea {
  resize: vertical;
  min-height: 96px;
}

.contact-submit {
  width: 100%;
}

.contact-fallback {
  margin-top: var(--space-lg);
  text-align: center;
  font-size: 14px;
  color: var(--color-text-muted);
}

.contact-fallback-link {
  color: var(--color-text-primary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

@media (min-width: 640px) {
  .form-row {
    grid-template-columns: repeat(2, 1fr);
  }

  .contact-submit {
    width: auto;
    min-width: 220px;
    margin-inline: auto;
    display: flex;
  }
}

/* Instagram sidebar card — same base card language as .service-card /
   .pricing-card (bg-secondary, border, radius-lg), icon treated like
   .service-icon (bg-tertiary square, purple-electric-bright stroke)
   instead of Instagram's own pink/orange gradient, per explicit
   request to stay faithful to WestSite's charte graphique. */
.contact-social-card {
  max-width: 360px;
  margin-inline: auto;
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-xl) var(--space-lg);
  text-align: center;
  transition: border-color var(--duration-normal) var(--ease-out),
              box-shadow var(--duration-normal) var(--ease-out);
}

.contact-social-card:hover,
.contact-social-card:focus-within {
  border-color: var(--color-border-active);
  box-shadow: var(--shadow-md);
}

.contact-social-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-md);
  background: var(--color-bg-tertiary);
  color: var(--color-purple-electric-bright);
  margin-bottom: var(--space-md);
}

.contact-social-title {
  font-size: 18px;
  font-weight: 600;
  margin-bottom: var(--space-sm);
}

.contact-social-text {
  font-size: 14px;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-lg);
}

.contact-social-cta {
  width: 100%;
}

@media (min-width: 1024px) {
  .contact-grid {
    grid-template-columns: 1.6fr 1fr;
    gap: var(--space-3xl);
  }

  .contact-form,
  .contact-social-card {
    margin-inline: 0;
  }
}

/* ============================================================
   6i. À PROPOS — headline paragraph + inline highlights + side card
   Adapted from a user-supplied React reference ("AboutSection2"): a
   large statement paragraph with inline colour-highlighted phrases,
   a smaller supporting line below, and a staggered blur/fade-up
   reveal on scroll (the reference used framer-motion "TimelineContent";
   re-authored here with the existing .reveal-on-scroll + its new
   --reveal-delay stagger, no animation library). Highlight colours
   recoloured from the source's blue/orange/green to the brand's
   purple/yellow pair (never both on the same word — see gradient
   rules in DESIGN-TOKENS.md §5, "never place purple/yellow text on
   each other").
   ============================================================ */
.about-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-2xl);
  align-items: center;
}

.about-content .section-eyebrow {
  display: block;
  margin-bottom: var(--space-md);
}

.about-lead {
  font-family: var(--font-heading);
  font-size: clamp(1.5rem, 2vw + 1.1rem, 2rem);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-lg);
}

.about-text {
  font-size: 17px;
  line-height: 1.6;
  color: var(--color-text-secondary);
}

.about-highlight {
  display: inline-block;
  padding-inline: var(--space-xs);
  border: 2px dotted;
  border-radius: var(--radius-md);
}

.about-highlight-purple {
  color: var(--color-purple-electric-bright);
  border-color: var(--color-purple-electric);
}

.about-highlight-yellow {
  color: var(--color-yellow-electric);
  border-color: var(--color-yellow-electric-dim);
}

/* Side card — logo on a soft stationary glow, no spinning ring (that
   accent is reserved for the "Pro" pricing tier so it keeps reading
   as an exception, not a reused decoration). */
.about-card {
  position: relative;
  isolation: isolate;
  margin-inline: auto;
  max-width: 360px;
  width: 100%;
}

.about-card::before {
  content: '';
  position: absolute;
  inset: 10%;
  z-index: -1;
  filter: blur(48px);
  opacity: 0.25;
  background: radial-gradient(
    circle,
    var(--color-purple-electric) 0%,
    var(--color-yellow-electric) 60%,
    transparent 75%
  );
}

.about-card-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-2xl) var(--space-lg);
}

.about-card-logo {
  width: 100%;
  max-width: 180px;
  height: auto;
}

.about-card-caption {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
}

.about-card-names {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 15px;
  color: var(--color-text-primary);
}

.about-card-role {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--color-text-muted);
}

@media (min-width: 1024px) {
  .about-grid {
    grid-template-columns: 1.6fr 1fr;
    gap: var(--space-3xl);
  }

  .about-card {
    margin-inline: 0;
  }
}

/* ============================================================
   7. FOOTER
   ============================================================ */
.site-footer {
  position: relative;
  background: var(--color-bg-secondary);
}

/* Thin signature-gradient line standing in for the reference's full-bleed
   purple background — an accent line is within the gradient-usage rules
   (DESIGN-TOKENS.md §5), a full-bleed gradient fill isn't. */
.site-footer::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--gradient-electric-ltr);
  opacity: 0.7;
}

.footer-main {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-lg);
  padding-block: var(--space-3xl) var(--space-2xl);
}

.footer-logo-img {
  height: 40px;
  width: auto;
  object-fit: contain;
}

.footer-logo-fallback {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 20px;
}

.footer-tagline {
  max-width: 420px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--color-text-secondary);
}

.footer-social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted);
  transition: color var(--duration-normal) var(--ease-out),
              transform var(--duration-normal) var(--ease-out);
}

.footer-social:hover,
.footer-social:focus-visible {
  color: var(--color-purple-electric-bright);
  transform: translateY(-2px);
}

.footer-social:focus-visible {
  outline: 2px solid var(--color-purple-electric);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

.footer-bottom {
  border-top: 1px solid var(--color-border-subtle);
  padding-block: var(--space-lg);
}

.footer-copy {
  text-align: center;
  font-size: 13px;
  color: var(--color-text-muted);
}

/* ============================================================
   7b. CARD — Featured / Accent variant (spinning gradient border)
   Not yet used on the page (Services/Réalisations content comes in a
   later pass) — documented here and in DESIGN-TOKENS.md so it's ready.
   Ref: 21st.dev "Neon Gradient Card" (magicui), re-authored in plain
   CSS: an oversized rotating conic-gradient sits behind a 1px inset,
   solid-bg inner card, so only a thin animated ring shows.
   ============================================================ */
.card-glow {
  position: relative;
  isolation: isolate;
  padding: 1px;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.card-glow::before {
  content: '';
  position: absolute;
  inset: -50%;
  z-index: -1;
  background: conic-gradient(
    from 0deg,
    var(--color-purple-electric),
    var(--color-yellow-electric),
    var(--color-purple-electric)
  );
  animation: cardBorderSpin 8s linear infinite;
  opacity: 0.6;
}

.card-glow > .card {
  position: relative;
  height: 100%;
  background: var(--color-bg-secondary);
  border-radius: calc(var(--radius-lg) - 1px);
  padding: var(--space-lg);
}

@keyframes cardBorderSpin {
  to { transform: rotate(360deg); }
}

/* ============================================================
   7c. AMBIENT NOISE / GRAIN OVERLAY
   Full-viewport film-grain texture, extremely subtle (alpha ~5/255).
   Sits above the dark background and hero glows, below all real
   content, blended with mix-blend-mode so it never washes out text.
   Ref: react-bits "Noise", re-authored: lower alpha, slower refresh.
   ============================================================ */
.noise-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-texture);
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  mix-blend-mode: overlay;
}

/* ============================================================
   7d. ENTRANCE REVEAL (staggered fade-up)
   Pure CSS, no JS/observer needed since these are above-the-fold
   elements: each carries its own --reveal-delay inline var so the
   sequence reads badge → title → subtitle → actions → proof.
   ============================================================ */
.reveal-up {
  animation: revealUp 700ms var(--ease-out) both;
  animation-delay: var(--reveal-delay, 0ms);
}

@keyframes revealUp {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ============================================================
   7e. SCROLL REVEAL (below-the-fold content)
   IntersectionObserver-driven (main.js) — unlike .reveal-up this
   waits for the element to enter the viewport, since it's meant for
   content further down the page. Ref: react-bits "AnimatedContent"
   (GSAP+ScrollTrigger), re-authored with plain CSS transitions +
   native IntersectionObserver so no animation library is needed.
   ============================================================ */
.reveal-on-scroll {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 700ms var(--ease-out), transform 700ms var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}

.reveal-on-scroll.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

/* ============================================================
   7f. CLICK SPARK — brand-coloured spark burst on click
   Ref: react-bits "ClickSpark", re-authored in vanilla JS/canvas. A single
   full-viewport canvas draws short spark lines radiating from each click,
   alternating the signature purple/yellow. pointer-events: none so it never
   intercepts clicks. Drawn in main.js.
   ============================================================ */
.click-spark {
  position: fixed;
  inset: 0;
  z-index: var(--z-cursor);
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ============================================================
   7g. SCROLL PROGRESS RAIL
   A reading-progress indicator: a gradient thumb tracks the scrolled
   portion of <main>, with a clickable marker per section (labels from
   the nav). Vertical rail on desktop, horizontal top bar on mobile.
   Markers are injected by main.js.
   ============================================================ */
.scroll-progress {
  display: none; /* main.js flips to block once initialised */
  position: fixed;
  z-index: var(--z-fixed);
  background: var(--color-border-subtle);
  border-radius: var(--radius-full);
}

.scroll-progress-indicator {
  position: absolute;
  border-radius: var(--radius-full);
  transition: top 0.12s linear, height 0.12s linear, width 0.12s linear;
}

.heading-marker-container {
  position: absolute;
  display: block;
}

/* Marker ticks/dots hidden per request — the rail keeps only the gradient
   thumb and the hover labels (desktop). The marker link stays as an
   invisible hover/click zone. */
.heading-indicator {
  display: none;
}

.heading-label {
  position: absolute;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--color-text-secondary);
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  padding: 3px 9px;
  border-radius: var(--radius-full);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--duration-normal) var(--ease-out),
              transform var(--duration-normal) var(--ease-out);
}

/* ---- Desktop: vertical rail on the right ---- */
@media (min-width: 701px) {
  .scroll-progress {
    top: 50%;
    right: 22px;
    transform: translateY(-50%);
    width: 3px;
    height: 46vh;
    max-height: 440px;
  }

  .scroll-progress-indicator {
    left: 0;
    width: 100%;
    min-height: 10px;
    background: var(--gradient-electric-ttb);
    box-shadow: 0 0 10px rgba(157, 0, 255, 0.45);
  }

  .heading-marker-container {
    left: 50%;
    transform: translate(-50%, -50%);
    width: 16px;
    height: 16px;
  }

  .heading-indicator {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 11px;
    height: 2px;
    border-radius: 2px;
  }

  .heading-marker-container:hover .heading-indicator {
    background: var(--color-yellow-electric);
    width: 16px;
  }

  .heading-label {
    right: calc(100% + 6px);
    top: 50%;
    transform: translateY(-50%) translateX(6px);
  }

  .heading-marker-container:hover .heading-label {
    opacity: 1;
    transform: translateY(-50%) translateX(0);
  }
}

/* ---- Mobile (≤700px): horizontal bar under the nav ---- */
@media (max-width: 700px) {
  .scroll-progress {
    top: var(--nav-height);
    left: 0;
    width: 100%;
    height: 3px;
    border-radius: 0;
  }

  .scroll-progress-indicator {
    top: 0;
    left: 0;
    height: 100%;
    background: var(--gradient-electric-ltr);
    border-radius: 0 var(--radius-full) var(--radius-full) 0;
  }

  .heading-marker-container {
    top: 50%;
    transform: translate(-50%, -50%);
    width: 8px;
    height: 8px;
  }

  .heading-indicator {
    width: 6px;
    height: 6px;
    border-radius: 50%;
  }
}

/* ============================================================
   7h. FAQ PAGE (faq.html)
   Adapted from a user-supplied React/Tailwind reference
   ("faq-monochrome"): intro pill with a spinning conic beam, a
   single-open accordion of bordered cards, plus-icon rotating to a
   cross, meta chip per question. Re-authored in vanilla CSS like every
   other borrowed component here (no React/Tailwind in this project).
   Two deliberate departures from the source:
   - No light/dark toggle. The source shipped a whole light palette and
     a Day/Night button; WestSite is dark-only by charte, so the palette
     collapses to our tokens and the button is dropped.
   - The source's neutral greys/white glow become the brand purple →
     yellow pair; the full-viewport `aurora` becomes the same kind of
     low-opacity radial glow the hero already uses (DESIGN-TOKENS §5
     rules out full-bleed gradients on large areas).
   ============================================================ */
.faq-page {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

/* Ambient glow — stands in for the home page's WebGL plasma, which
   stays exclusive to the hero. */
.faq-aurora {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(ellipse 55% 70% at 12% 0%, rgba(157, 0, 255, 0.20), transparent 65%),
    radial-gradient(ellipse 45% 55% at 92% 6%, rgba(245, 255, 0, 0.05), transparent 70%);
}

.faq-shell {
  max-width: 860px;
  margin-inline: auto;
}

/* ---- Intro pill ---- */
.faq-intro {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  width: 100%;
  max-width: 320px;
  margin-inline: auto;
  margin-bottom: var(--space-2xl);
  padding: 10px var(--space-lg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-bg-secondary);
  color: var(--color-text-secondary);
}

/* Rotating light filling the pill. Same recipe as .btn-primary::before —
   see that rule for why it's a centred square rather than an inset, and why
   z-index:-1 (over the pill's background, under its label) is load-bearing.

   The source's version was an inset:-110% ellipse with no blur and a conic
   full of `transparent` stops: on a 320x40 pill that put the gradient's
   centre right in the visible strip, so you saw its raw angular sectors as
   hard purple/yellow wedges instead of a glow. Blur + a seamless
   purple → yellow → purple conic melt those sectors into a single wash. */
.faq-intro-beam {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 150%;
  aspect-ratio: 1;
  translate: -50% -50%;
  z-index: -1;
  pointer-events: none;
  /* Purple only, varying in lightness rather than hue. Blurring the brand's
     purple → yellow pair together averages two near-complementary colours
     into olive/khaki — nowhere near the electric palette. Same reasoning as
     the Services spotlight, which is purple-only for this exact reason; the
     yellow stays crisp on the tick instead. The lightness steps keep the
     rotation readable as a drifting light. */
  background: conic-gradient(
    from 160deg,
    var(--color-purple-electric),
    var(--color-purple-electric-bright),
    var(--color-purple-electric-dim),
    var(--color-purple-electric)
  );
  filter: blur(24px);
  opacity: 0.55;
  animation: faqBeamSpin 18s linear infinite;
}

/* One readout each side of the label. Both get an equal share of the free
   space, which is what centres the label — no magic numbers. */
.faq-intro-side {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex: 1 1 0;
  min-width: 0;
}

/* Mirrors the left readout instead of duplicating faqMeter/faqTick as
   reversed keyframes: the flip composes with the children's own transforms,
   so the meter grows and the tick drifts *outward* from the label on both
   sides, in sync. */
.faq-intro-side--flip {
  transform: scaleX(-1);
}

.faq-intro-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--color-text-primary);
  /* The label's own tracking adds a trailing space; nudge it back so the
     glyphs read optically centred rather than mathematically centred. */
  padding-left: 0.3em;
}

.faq-intro-meter {
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(90deg, transparent, currentColor 35%, transparent 85%);
  transform: scaleX(0);
  transform-origin: left;
  opacity: 0.7;
  animation: faqMeter 5.8s var(--ease-in-out) infinite;
}

.faq-intro-tick {
  width: 6px;
  height: 6px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--color-yellow-electric);
  box-shadow: 0 0 0 4px rgba(245, 255, 0, 0.08);
  animation: faqTick 3.2s var(--ease-in-out) infinite;
}

@keyframes faqBeamSpin {
  to { transform: rotate(360deg); }
}

@keyframes faqMeter {
  0%, 20%   { transform: scaleX(0); transform-origin: left; }
  45%, 60%  { transform: scaleX(1); transform-origin: left; }
  80%, 100% { transform: scaleX(0); transform-origin: right; }
}

@keyframes faqTick {
  0%, 30% { transform: translateX(-6px); opacity: 0.4; }
  50%     { transform: translateX(2px); opacity: 1; }
  100%    { transform: translateX(16px); opacity: 0; }
}

/* ---- Accordion ---- */
.faq-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.faq-item {
  /* Left inset shared by the trigger's icon column and the answer below it:
     card padding + icon width + gap */
  --faq-indent: calc(var(--space-lg) + 44px + var(--space-md));
  position: relative;
  overflow: hidden;
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  transition: transform var(--duration-normal) var(--ease-out),
              border-color var(--duration-normal) var(--ease-out),
              box-shadow var(--duration-normal) var(--ease-out);
}

.faq-item:hover,
.faq-item:focus-within {
  transform: translateY(-2px);
  border-color: var(--color-border-active);
  box-shadow: var(--shadow-md);
}

.faq-item.is-open {
  border-color: var(--color-border-active);
}

/* The heading is a plain wrapper: it carries the document outline while the
   button inside carries the interaction (WAI-ARIA accordion pattern). The
   source nested an <h2> inside the <button>, which isn't valid HTML and
   hides the heading from assistive tech. */
.faq-heading {
  margin: 0;
  font: inherit;
}

.faq-trigger {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  width: 100%;
  padding: var(--space-lg);
  text-align: left;
  color: inherit;
}

.faq-trigger:focus-visible {
  outline: 2px solid var(--color-purple-electric);
  outline-offset: -4px;
  border-radius: var(--radius-lg);
}

.faq-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-bg-tertiary);
  color: var(--color-purple-electric-bright);
  transition: transform var(--duration-normal) var(--ease-out);
}

.faq-item:hover .faq-icon {
  transform: scale(1.06);
}

.faq-icon svg {
  transition: transform var(--duration-slow) var(--ease-elastic);
}

.faq-item.is-open .faq-icon svg {
  transform: rotate(45deg);
}

/* Ring that pings out of the icon while the answer is open */
.faq-icon-ring {
  position: absolute;
  inset: -1px;
  border: 1px solid currentColor;
  border-radius: var(--radius-full);
  opacity: 0;
  pointer-events: none;
}

.faq-item.is-open .faq-icon-ring {
  animation: faqPing 1.8s var(--ease-out) infinite;
}

@keyframes faqPing {
  0%        { transform: scale(1); opacity: 0.5; }
  80%, 100% { transform: scale(1.45); opacity: 0; }
}

.faq-head {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-sm);
  /* Vertically centre the question against the 44px icon */
  min-height: 44px;
  justify-content: center;
}

.faq-question {
  font-family: var(--font-heading);
  font-size: 17px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--color-text-primary);
}

.faq-meta {
  display: inline-flex;
  width: fit-content;
  padding: 3px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--color-text-muted);
}

/* Open height is set inline by main.js from the panel's scrollHeight — the
   source's fixed max-h-64 would clip the longer French answers. Padding
   lives on .faq-answer, not here: padding on a max-height:0 element would
   still show when the panel is closed. */
.faq-panel {
  overflow: hidden;
  max-height: 0;
  transition: max-height var(--duration-slow) var(--ease-out);
}

/* Aligns the answer under the question, past the icon column */
.faq-answer {
  padding: 0 var(--space-lg) var(--space-lg) var(--faq-indent);
  font-size: 15px;
  color: var(--color-text-secondary);
}

@media (min-width: 640px) {
  .faq-head {
    flex-direction: row;
    align-items: center;
    gap: var(--space-md);
  }

  .faq-meta {
    margin-left: auto;
  }

  .faq-question {
    font-size: 19px;
  }

  .faq-trigger {
    padding: var(--space-xl) var(--space-lg);
    gap: var(--space-lg);
  }

  .faq-item {
    --faq-indent: calc(var(--space-lg) + 44px + var(--space-lg));
  }
}

/* ---- Closing CTA ---- */
.faq-cta {
  position: relative;
  isolation: isolate;
  margin-top: var(--space-2xl);
  text-align: center;
}

.faq-cta::before {
  content: '';
  position: absolute;
  inset: 15% 20%;
  z-index: -1;
  filter: blur(48px);
  opacity: 0.22;
  background: radial-gradient(
    circle,
    var(--color-purple-electric) 0%,
    var(--color-yellow-electric) 60%,
    transparent 75%
  );
}

.faq-cta-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-2xl) var(--space-lg);
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.faq-cta-title {
  font-size: 22px;
  font-weight: 600;
}

.faq-cta-text {
  max-width: 460px;
  font-size: 15px;
  color: var(--color-text-secondary);
}

/* ============================================================
   8. Accessibility — reduced motion
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Skip link for keyboard users */
.skip-link {
  position: absolute;
  left: var(--space-md);
  top: -100px;
  z-index: var(--z-notification);
  background: var(--color-bg-secondary);
  color: var(--color-text-primary);
  border: 1px solid var(--color-purple-electric);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  transition: top var(--duration-normal) var(--ease-out);
}

.skip-link:focus {
  top: var(--space-md);
}
