/* ==========================================================================
   InesEvents — Shared Design System
   Loaded on every page. Tailwind (CDN) handles layout utilities;
   this file only carries brand tokens, type, and bespoke motion/components
   that Tailwind's utility classes don't express cleanly.
   ========================================================================== */

/* ---- Défilement fluide pour les ancres internes (ex. CTA héro → #expertise) ;
   désactivé si l'utilisateur préfère réduire les animations. ---- */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
section[id] { scroll-margin-top: 110px; }

:root {
  /* Premium motion curve (spring-like ease-out) — used everywhere instead of
     linear/ease so interactions feel weighted rather than mechanical. */
  --ease-premium: cubic-bezier(0.16, 1, 0.3, 1);

  /* Palette — premium Sky Blue identity (sugarforevents.com / akroevent.com
     styling reference): deep ocean-blue chrome (nav/footer/testimonials) with
     a light sky-blue signature accent for CTAs, links and highlights. Icy
     off-white backgrounds keep it airy and premium rather than cold. */
  --color-bg:            #F6FBFE;
  --color-bg-alt:        #E7F3FB;
  --color-primary:       #0E2A47;
  --color-primary-light: #163C63;
  --color-accent:        #2FA7DE;
  --color-accent-light:  #7DD3F0;
  --color-text:          #14202C;
  --color-text-soft:     #55707F;
  --color-text-inverse:  #EAF6FD;
  --color-border:        #D2E6F2;

  /* Gala-stage accent duo — used only by the ambient background (spotlights,
     glitter, golden waves), never by foreground UI, which stays sky-blue. */
  --color-gold:   #E8C468;
  --color-silver: #DCE6EC;

  --font-heading: 'Cormorant Garamond', serif;
  --font-body: 'Jost', sans-serif;
}

html {
  scroll-behavior: smooth;
}

body {
  background-color: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
}

h1, h2, h3, h4,
.font-heading {
  font-family: var(--font-heading);
}

.font-body {
  font-family: var(--font-body);
}

/* ---- Brand utility classes (kept out of Tailwind config since we run via CDN) ---- */
.bg-brand-bg      { background-color: var(--color-bg); }
.bg-brand-bg-alt  { background-color: var(--color-bg-alt); }
.bg-brand-primary { background-color: var(--color-primary); }
.bg-brand-accent  { background-color: var(--color-accent); }
.bg-brand-accent-light { background-color: var(--color-accent-light); }
.text-brand-primary  { color: var(--color-primary); }
.text-brand-accent   { color: var(--color-accent); }
.text-brand-text     { color: var(--color-text); }
.text-brand-soft      { color: var(--color-text-soft); }
.text-brand-inverse  { color: var(--color-text-inverse); }
.border-brand         { border-color: var(--color-border); }
.border-brand-accent  { border-color: var(--color-accent); }

.btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.9rem 2rem;
  background-color: var(--color-accent);
  color: var(--color-primary);
  font-family: var(--font-body);
  font-weight: 500;
  letter-spacing: 0.02em;
  border-radius: 999px;
  transition: background-color 0.4s var(--ease-premium), transform 0.4s var(--ease-premium), box-shadow 0.4s var(--ease-premium);
}
.btn-primary:hover {
  background-color: var(--color-accent-light);
  transform: translateY(-2px);
  box-shadow: 0 12px 28px -10px rgba(47, 167, 222, 0.5);
}
.btn-primary:active { transform: translateY(-1px) scale(0.98); }

/* ---- Shimmer (style MagicUI) : le filet de lumière glisse désormais EN
   CONTINU sur tous les boutons du site (.btn-primary / .btn-outline, avec ou
   sans la classe historique .btn-shine — même pseudo-élément ::after, donc
   jamais de doublon). Streak blanc sur les boutons pleins, teinté bleu charte
   sur les boutons à contour pour rester visible sur fond clair. ---- */
.btn-primary, .btn-outline, .btn-shine { position: relative; overflow: hidden; }
.btn-primary::after,
.btn-outline::after,
.btn-shine::after {
  content: "";
  position: absolute;
  top: 0;
  left: -60%;
  width: 45%;
  height: 100%;
  background: linear-gradient(115deg, transparent, rgba(255, 255, 255, 0.65), transparent);
  transform: skewX(-18deg);
  animation: btn-shimmer 3.4s ease-in-out infinite;
  pointer-events: none;
}
.btn-outline::after {
  background: linear-gradient(115deg, transparent, rgba(47, 167, 222, 0.35), transparent);
}
@keyframes btn-shimmer {
  0%   { left: -60%; }
  55%  { left: 130%; }
  100% { left: 130%; }
}

/* ---- Ripple (style MagicUI) : onde de choc circulaire au point exact du
   clic, créée par initButtonRipples (main.js) puis auto-retirée en fin
   d'animation. Transform/opacity uniquement (composité GPU). ---- */
.btn-ripple {
  position: absolute;
  border-radius: 999px;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.5) 0%, rgba(47, 167, 222, 0.3) 40%, transparent 70%);
  transform: scale(0);
  animation: btn-ripple 0.65s var(--ease-premium) forwards;
  pointer-events: none;
}
@keyframes btn-ripple {
  to { transform: scale(1); opacity: 0; }
}

.btn-outline {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.85rem 1.9rem;
  background-color: transparent;
  color: inherit;
  border: 1px solid currentColor;
  font-family: var(--font-body);
  font-weight: 500;
  letter-spacing: 0.02em;
  border-radius: 999px;
  transition: background-color 0.4s var(--ease-premium), color 0.4s var(--ease-premium), transform 0.4s var(--ease-premium);
}
.btn-outline:hover {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-primary);
  transform: translateY(-2px);
}
.btn-outline:active { transform: translateY(-1px) scale(0.98); }

/* ---- Magnetic buttons: JS (initMagnetic in main.js) nudges these toward
   the cursor on mousemove; this transition handles the smooth spring-back
   on mouseleave. Skipped entirely on touch devices (see main.js). ---- */
.magnetic { transition: transform 0.5s var(--ease-premium); will-change: transform; }

/* ---- Spotlight border cards (Raycast/Linear-style cursor-tracked glow) ----
   JS (initSpotlight in main.js) sets --spot-x/--spot-y on mousemove; the
   pseudo-element paints a soft gold radial gradient masked to a 1px ring,
   so only the BORDER glows — no blurry neon box-shadow. ---- */
.spotlight-card {
  position: relative;
  --spot-x: 50%;
  --spot-y: 50%;
  isolation: isolate;
}
.spotlight-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: radial-gradient(360px circle at var(--spot-x) var(--spot-y), rgba(47, 167, 222, 0.65), transparent 62%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s var(--ease-premium);
  z-index: 1;
}
.spotlight-card:hover::before,
.spotlight-card:focus-within::before { opacity: 1; }

/* ---- Double-bezel "nested hardware" card shell (outer tray + inner core),
   for the small set of cards that should feel machined rather than flat. ---- */
.bezel-shell {
  padding: 0.375rem;
  border-radius: 1.75rem;
  background: rgba(14, 42, 71, 0.04);
  border: 1px solid rgba(14, 42, 71, 0.08);
}
html.dark .bezel-shell {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.08);
}
.bezel-core {
  border-radius: 1.375rem;
  overflow: hidden;
}

/* ---- Subtle film-grain overlay (Editorial Luxury texture). Fixed,
   pointer-events:none, extremely low opacity — never applied to scrolling
   containers, so it costs nothing on scroll/repaint. ---- */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

/* ---- Language toggle ----------------------------------------------------
   Convention: wrap each language's text in its own <span data-lang="fr|en">
   INSIDE a shared parent element (link, heading, li...). Never duplicate the
   parent itself — only the text span. `revert` restores each tag's natural
   display (inline for span, list-item for li, etc.) so this also works if a
   whole <p>/<li>/<div> is tagged directly. -------------------------------- */
[data-lang] { display: none; }
html[data-active-lang="fr"] [data-lang="fr"] { display: revert; }
html[data-active-lang="en"] [data-lang="en"] { display: revert; }

.lang-btn { opacity: 0.45; transition: opacity 0.2s ease; }
.lang-btn.is-active { opacity: 1; font-weight: 600; }

/* ---- Scroll reveal ---- */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.8s var(--ease-premium), transform 0.8s var(--ease-premium);
}
.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.reveal-stagger > * {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s var(--ease-premium), transform 0.7s var(--ease-premium);
}
.reveal-stagger.is-visible > * {
  opacity: 1;
  transform: translateY(0);
}
.reveal-stagger.is-visible > *:nth-child(1) { transition-delay: 0.05s; }
.reveal-stagger.is-visible > *:nth-child(2) { transition-delay: 0.15s; }
.reveal-stagger.is-visible > *:nth-child(3) { transition-delay: 0.25s; }
.reveal-stagger.is-visible > *:nth-child(4) { transition-delay: 0.35s; }
.reveal-stagger.is-visible > *:nth-child(5) { transition-delay: 0.45s; }
.reveal-stagger.is-visible > *:nth-child(6) { transition-delay: 0.55s; }

.reveal-left {
  opacity: 0;
  transform: translateX(-32px);
  transition: opacity 0.8s var(--ease-premium), transform 0.8s var(--ease-premium);
}
.reveal-left.is-visible { opacity: 1; transform: translateX(0); }

.reveal-right {
  opacity: 0;
  transform: translateX(32px);
  transition: opacity 0.8s var(--ease-premium), transform 0.8s var(--ease-premium);
}
.reveal-right.is-visible { opacity: 1; transform: translateX(0); }

/* ---- Asymmetric entrance: each child animates in on its own axis
   (left slide / vertical rise+blur+scale / right slide) instead of a
   uniform fade — used for the hero block so the entrance feels choreographed
   rather than mechanical (sugarforevents.com / akroevent.com reference). ---- */
.reveal-asymmetric > * {
  opacity: 0;
  filter: blur(4px);
  transition: opacity 0.9s var(--ease-premium), transform 0.9s var(--ease-premium), filter 0.9s var(--ease-premium);
}
.reveal-asymmetric > *:nth-child(1) { transform: translateX(-40px); }
.reveal-asymmetric > *:nth-child(2) { transform: translateY(36px) scale(0.97); }
.reveal-asymmetric > *:nth-child(3) { transform: translateY(22px); }
.reveal-asymmetric > *:nth-child(4) { transform: translateX(28px); }
.reveal-asymmetric.is-visible > * { opacity: 1; filter: blur(0); transform: translate(0, 0) scale(1); }
.reveal-asymmetric.is-visible > *:nth-child(1) { transition-delay: 0.05s; }
.reveal-asymmetric.is-visible > *:nth-child(2) { transition-delay: 0.2s; }
.reveal-asymmetric.is-visible > *:nth-child(3) { transition-delay: 0.36s; }
.reveal-asymmetric.is-visible > *:nth-child(4) { transition-delay: 0.5s; }

/* ---- Zig-zag grid entrance: alternating children rise in from the
   left/right instead of a uniform vertical fade (portfolio grid). ---- */
.reveal-grid > * {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.7s var(--ease-premium), transform 0.7s var(--ease-premium);
}
.reveal-grid > *:nth-child(odd)  { transform: translateY(30px) translateX(-16px); }
.reveal-grid > *:nth-child(even) { transform: translateY(30px) translateX(16px); }
.reveal-grid.is-visible > * { opacity: 1; transform: translate(0, 0); }
.reveal-grid.is-visible > *:nth-child(1) { transition-delay: 0.05s; }
.reveal-grid.is-visible > *:nth-child(2) { transition-delay: 0.13s; }
.reveal-grid.is-visible > *:nth-child(3) { transition-delay: 0.21s; }
.reveal-grid.is-visible > *:nth-child(4) { transition-delay: 0.29s; }
.reveal-grid.is-visible > *:nth-child(5) { transition-delay: 0.37s; }
.reveal-grid.is-visible > *:nth-child(6) { transition-delay: 0.45s; }

/* ---- Card fan entrance: testimonial cards rise with a slight opposing
   rotation (left card tilts from -, right card tilts from +) so the trio
   settles into place rather than popping in flat. ---- */
.reveal-cards > * {
  opacity: 0;
  transform: translateY(34px) rotate(0deg);
  transition: opacity 0.75s var(--ease-premium), transform 0.75s var(--ease-premium);
  transform-origin: bottom center;
}
.reveal-cards > *:nth-child(1) { transform: translateY(34px) rotate(-3deg); }
.reveal-cards > *:nth-child(3) { transform: translateY(34px) rotate(3deg); }
.reveal-cards.is-visible > * { opacity: 1; transform: translateY(0) rotate(0deg); }
.reveal-cards.is-visible > *:nth-child(1) { transition-delay: 0.05s; }
.reveal-cards.is-visible > *:nth-child(2) { transition-delay: 0.18s; }
.reveal-cards.is-visible > *:nth-child(3) { transition-delay: 0.31s; }

/* ---- Gentle vertical bob for floating badge/pill elements. ---- */
@keyframes float-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-10px); }
}
.float-badge {
  animation: float-bob 4.5s ease-in-out infinite;
}

/* ---- Tilt-lift hover: portfolio thumbnails rise with a subtle
   counter-rotation and a soft blue glow shadow. ---- */
.tilt-hover {
  transition: transform 0.6s var(--ease-premium), box-shadow 0.6s var(--ease-premium);
}
.tilt-hover:hover {
  transform: translateY(-8px) rotate(-0.75deg);
  box-shadow: 0 28px 50px -22px rgba(14, 42, 71, 0.4);
}

/* ---- Animated underline for nav/footer text links. ---- */
.link-underline { position: relative; }
.link-underline::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 0;
  height: 1px;
  background: currentColor;
  transition: width 0.4s var(--ease-premium);
}
.link-underline:hover::after { width: 100%; }

/* ---- Hero / dark image-header text: forced to stay light in BOTH themes.
   `text-brand-inverse` is already theme-invariant, but this belt-and-braces
   rule guarantees it even if a stray untagged text node is added later —
   nothing inside .hero-locked-light can ever render as dark text. ---- */
.hero-locked-light,
.hero-locked-light h1,
.hero-locked-light p,
.hero-locked-light span:not([class*="text-brand-accent"]) {
  color: #FFFFFF;
}

/* ---- Stardust particles: tiny, soft twinkling glints — kept deliberately
   understated (small size, low peak opacity, gentle glow) so they read as
   elegant ambient dust rather than decoration competing with content. ---- */
@keyframes sparkle-twinkle {
  0%, 100% { opacity: 0; transform: scale(0.4); }
  50%      { opacity: 0.7; transform: scale(1); }
}
.sparkle {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 9999px;
  background: var(--color-accent-light);
  box-shadow: 0 0 5px 1px rgba(125, 211, 240, 0.45);
  animation-name: sparkle-twinkle;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  pointer-events: none;
  z-index: 1;
}

/* ==========================================================================
   GALA STAGE — global ambient background
   One fixed, full-viewport layer sits behind the entire page (spotlights +
   golden waves in CSS/SVG, glitter in a lightweight canvas — see
   initGlitterCanvas in main.js). Section backgrounds below are switched from
   opaque fills to translucent "veils" (.section-veil / -alt / -dark) so this
   single atmosphere reads through continuously as the page scrolls, instead
   of resetting per section. Hero keeps its own opaque photo/overlay and
   simply sits on top. Everything here is pointer-events:none and z-index:0,
   so it never intercepts a click and never sits above real content.
   ========================================================================== */
.ambient-stage {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

/* ---- Stage spotlights: soft vertical light cones, asymmetric widths/tilts,
   swaying gently like real rig lights. Kept very low-opacity so they read as
   atmosphere, not graphic shapes. ---- */
.spotlight {
  position: absolute;
  top: -12%;
  height: 130%;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(232, 196, 104, 0.05) 45%, transparent 80%);
  clip-path: polygon(48.5% 0%, 51.5% 0%, 100% 100%, 0% 100%);
  transform-origin: top center;
  transform: rotate(var(--rot, 0deg));
  animation: spotlight-sway 14s ease-in-out infinite;
}
@keyframes spotlight-sway {
  0%, 100% { transform: rotate(var(--rot, 0deg)); }
  50%      { transform: rotate(calc(var(--rot, 0deg) + 2.2deg)); }
}
html.dark .spotlight {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.14) 0%, rgba(232, 196, 104, 0.07) 45%, transparent 80%);
}

/* ---- Golden waves: sinuous flowing light lines that thread the page,
   giving the scroll a sense of structure. The dash animation makes the
   light appear to travel along the curve; the drift keyframe adds a slow
   organic bob. Very low stroke opacity — a thread of light, not a shape. ---- */
.ambient-waves {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.wave-path {
  stroke-dasharray: 16 30;
  animation: wave-flow 10s linear infinite, wave-drift 15s ease-in-out infinite;
}
.wave-path-2 { animation-duration: 13s, 18s; animation-delay: -3s, -6s; }
@keyframes wave-flow  { to { stroke-dashoffset: -230; } }
@keyframes wave-drift {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(16px); }
}

/* ---- Glitter canvas: theme-aware twinkling dust drifting slowly downward
   (see initGlitterCanvas in main.js — pure canvas 2D, no library, paused
   off-screen/on tab-hide, skipped entirely under prefers-reduced-motion).
   Per-particle alpha already encodes the intended final opacity for each
   theme (soft ~0.3 cap in light mode, brighter in dark mode) — no extra
   opacity multiplier here, or light mode would wash out to nothing on top
   of the section veils below. `pointer-events:none` + low `z-index`
   (inherited from .ambient-stage) keep it purely decorative. ---- */
.glitter-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

/* ---- Section veils: replace opaque section fills with a translucent tint
   of the same brand tone, so the fixed ambient-stage glimmers through
   everywhere while text contrast stays effectively unchanged. Lighter than
   a first pass (~0.86 instead of ~0.93) so the canvas actually reads in
   both themes instead of being smothered by the fill. ---- */
.section-veil      { background-color: rgba(246, 251, 254, 0.86); }
.section-veil-alt   { background-color: rgba(231, 243, 251, 0.86); }
.section-veil-dark  { background-color: rgba(14, 42, 71, 0.88); }
html.dark .section-veil      { background-color: rgba(10, 24, 38, 0.84); }
html.dark .section-veil-alt  { background-color: rgba(15, 35, 56, 0.84); }
html.dark .section-veil-dark { background-color: rgba(8, 18, 30, 0.86); }

/* ---- Rotating "request a quote" badge: circular SVG text spins slowly,
   the arrow glyph in the center stays static (counter-rotation isn't
   needed since it's a separate, non-rotating layer). ---- */
@keyframes spin-slow { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.rotating-badge-ring { animation: spin-slow 16s linear infinite; }

/* ---- Floating category chips over the hero image: independent, gentle
   bob so each chip drifts out of phase with the others. ---- */
.chip-float { animation: float-bob 5s ease-in-out infinite; }

/* ---- Bottom-of-hero scroll cue ---- */
@keyframes scroll-cue-dot {
  0%   { transform: translateY(0); opacity: 0; }
  30%  { opacity: 1; }
  100% { transform: translateY(14px); opacity: 0; }
}
.scroll-cue-dot { animation: scroll-cue-dot 1.8s ease-in-out infinite; }

/* ---- Ghost numerals: oversized, near-invisible numerals behind expertise
   cards for a magazine-editorial layer of scale contrast. ---- */
.ghost-numeral {
  font-family: var(--font-heading);
  line-height: 1;
  color: var(--color-primary);
  opacity: 0.06;
  pointer-events: none;
  user-select: none;
}
html.dark .ghost-numeral { color: var(--color-text-inverse); opacity: 0.05; }

/* ---- Premium light-blue focus state for contact form fields. ---- */
.form-field {
  width: 100%;
  background-color: rgba(255,255,255,0.6);
  border: 1px solid var(--color-border);
  border-radius: 0.85rem;
  padding: 0.85rem 1.1rem;
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text);
  transition: border-color 0.35s var(--ease-premium), box-shadow 0.35s var(--ease-premium), background-color 0.35s var(--ease-premium);
}
.form-field::placeholder { color: var(--color-text-soft); opacity: 0.75; }
.form-field:focus {
  outline: none;
  border-color: var(--color-accent);
  background-color: rgba(255,255,255,0.9);
  box-shadow: 0 0 0 4px rgba(47, 167, 222, 0.16), 0 10px 28px -12px rgba(47, 167, 222, 0.45);
}
html.dark .form-field { background-color: rgba(255,255,255,0.05); color: var(--color-text-inverse); border-color: rgba(255,255,255,0.14); }
html.dark .form-field:focus { background-color: rgba(255,255,255,0.08); }
.form-field.is-textarea { resize: vertical; min-height: 7.5rem; }
[data-form-status] { transition: opacity 0.4s var(--ease-premium), transform 0.4s var(--ease-premium); }

/* ---- Champs obligatoires : astérisque bleu électrique dans les labels, et
   surbrillance d'erreur élégante (rouge d'alerte + halo + secousse brève)
   posée par initContactForm (main.js) sur les champs vides à la soumission. ---- */
.req-star { color: var(--color-accent); font-weight: 600; }
@keyframes field-shake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-7px); }
  40%, 80% { transform: translateX(7px); }
}
.form-field.field-error {
  border-color: rgba(206, 66, 87, 0.9);
  background-color: rgba(206, 66, 87, 0.06);
  box-shadow: 0 0 0 4px rgba(206, 66, 87, 0.14);
  animation: field-shake 0.45s var(--ease-premium);
}
html.dark .form-field.field-error {
  background-color: rgba(206, 66, 87, 0.12);
  border-color: rgba(226, 96, 116, 0.9);
}

/* ---- Editorial event-preview cards (gallery teaser bento) ---- */
.event-card { position: relative; overflow: hidden; }
.event-card img { transition: transform 0.9s var(--ease-premium); }
.event-card:hover img { transform: scale(1.08); }
.event-card::after {
  content: "";
  position: absolute; inset: 0;
  z-index: 1;
  background: linear-gradient(to top, rgba(14,42,71,0.92) 0%, rgba(14,42,71,0.35) 45%, rgba(14,42,71,0.05) 75%);
  opacity: 0.8;
  transition: opacity 0.5s var(--ease-premium);
}
.event-card:hover::after { opacity: 1; }

/* ---- Logo marquee (trusted-by strip) ---- */
.marquee-track {
  display: flex;
  width: max-content;
  animation: marquee 32s linear infinite;
}
.marquee-track:hover { animation-play-state: paused; }
/* Second "trusted-by" strip scrolling the opposite way — same loop math
   (0 -> -50%, duplicated content) just played in reverse, so it stays
   perfectly seamless without a dedicated keyframe. */
.marquee-track.marquee-reverse { animation-direction: reverse; }
/* Logos stay in full color (brand requirement) — only a light dim/scale
   distinguishes idle vs. hovered state instead of a grayscale desaturation. */
.marquee-track img {
  opacity: 0.85;
  transition: opacity 0.3s var(--ease-premium), transform 0.3s var(--ease-premium), filter 0.3s var(--ease-premium);
}
.marquee-track img:hover { opacity: 1; transform: scale(1.08); }
/* Dark mode : les logos clients sont détourés pour fond clair et certains
   deviennent illisibles sur fond sombre. Plutôt qu'un halo lumineux en
   drop-shadow sur chaque logo (effet "shiny" jugé trop tape-à-l'œil), on
   place les deux pistes sur un bandeau clair unique (.logos-band, voir
   plus bas) qui restitue un fond proche du blanc sous les logos — ceux-ci
   gardent alors leurs couleurs d'origine, sans filtre. */
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Bandeau clair sous les deux pistes de logos — transparent en mode clair
   (le fond de section suffit déjà), et en blanc cassé en mode sombre pour
   que les logos détourés pour fond clair restent lisibles sans filtre. */
.logos-band {
  border-radius: 1.25rem;
  transition: background-color 0.3s var(--ease-premium);
}
html.dark .logos-band {
  background-color: #f8f9fa;
}

/* ---- Dotted Map (style MagicUI, page contact) ----
   Le Maroc en pointillés : un seul <path> rempli par un pattern SVG de points
   (aucune image à charger). La couleur des points passe par currentColor pour
   suivre le thème ; les villes clés portent une impulsion radar double. ---- */
.dotted-map { color: rgba(14, 42, 71, 0.22); }
html.dark .dotted-map { color: rgba(234, 246, 253, 0.16); }
/* Le bouton "Voir sur Google Maps" vit dans .dotted-map, dont le currentColor
   est le gris pâle des pointillés (via color ci-dessus) — .btn-outline hérite
   de cette couleur (color: inherit / border: currentColor) et deviendrait
   quasi invisible : on lui redonne une couleur pleine, par thème. */
.dotted-map .btn-outline { color: var(--color-primary); }
html.dark .dotted-map .btn-outline { color: var(--color-text-inverse); }
.map-core { fill: var(--color-accent); filter: drop-shadow(0 0 5px rgba(47, 167, 222, 0.9)); }
.map-pulse {
  fill: none;
  stroke: var(--color-accent);
  stroke-width: 1.5;
  transform-box: fill-box;
  transform-origin: center;
  animation: map-pulse 3s ease-out infinite;
}
.map-pulse-2 { animation-delay: 1.5s; }
.map-label {
  font-family: "Jost", sans-serif;
  font-size: 12px;
  letter-spacing: 0.04em;
  fill: var(--color-text-soft);
}
.map-label-strong { font-weight: 600; fill: var(--color-primary); }
html.dark .map-label { fill: rgba(234, 246, 253, 0.65); }
html.dark .map-label-strong { fill: var(--color-text-inverse); }
@keyframes map-pulse {
  0%   { transform: scale(0.35); opacity: 0.9; }
  100% { transform: scale(3.1);  opacity: 0; }
}

/* ---- Orbiting Circles (style MagicUI, colonne formulaire de contact) ----
   Les badges réseaux sociaux cliquables (angles équidistants posés en inline
   via --angle dans contact.html) tournent autour d'une enveloppe
   centrale. L'orbite (.orbit-spin) tourne via la propriété individuelle
   `rotate` ; chaque badge subit une contre-rotation synchronisée (même durée,
   même timing) pour rester droit pendant le voyage. Survoler le composant met
   les DEUX animations en pause (sinon désynchronisation) pour faciliter le
   clic. Les badges gardent `rotate: -angle` en base : droits même sans
   animation (prefers-reduced-motion). ---- */
.social-orbit {
  position: relative;
  width: min(100%, 240px);
  aspect-ratio: 1 / 1;
}
.social-orbit .orbit-ring {
  position: absolute;
  inset: 14%;
  border: 1.5px dashed rgba(47, 167, 222, 0.45);
  border-radius: 999px;
}
.orbit-center {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  width: 3.5rem;
  height: 3.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background-color: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(47, 167, 222, 0.35);
  box-shadow: 0 12px 30px -14px rgba(14, 42, 71, 0.45), 0 0 0 6px rgba(47, 167, 222, 0.07);
  color: var(--color-accent);
}
html.dark .orbit-center { background-color: rgba(14, 42, 71, 0.85); }
.orbit-spin {
  position: absolute;
  inset: 14%;
  border-radius: 999px;
  animation: orbit-spin 18s linear infinite;
}
.orbit-item {
  position: absolute;
  inset: 0;
  rotate: var(--angle);
  /* Chaque item est une couche plein cadre : sans ceci, la dernière couche
     empilée intercepte les clics destinés aux badges des couches inférieures. */
  pointer-events: none;
}
.orbit-badge {
  position: absolute;
  top: 0;
  left: 50%;
  translate: -50% -50%;
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background-color: rgba(255, 255, 255, 0.85);
  border: 1px solid var(--color-border);
  box-shadow: 0 8px 20px -10px rgba(14, 42, 71, 0.35);
  color: var(--color-primary);
  rotate: calc(0deg - var(--angle));
  pointer-events: auto; /* réactive le clic, neutralisé sur .orbit-item */
  animation: orbit-unspin 18s linear infinite;
  transition: color 0.3s var(--ease-premium), border-color 0.3s var(--ease-premium), box-shadow 0.3s var(--ease-premium);
}
.orbit-badge:hover {
  color: var(--color-accent);
  border-color: var(--color-accent);
  box-shadow: 0 10px 26px -10px rgba(47, 167, 222, 0.55);
}
html.dark .orbit-badge { background-color: rgba(14, 42, 71, 0.85); color: var(--color-text-inverse); }
.social-orbit:hover .orbit-spin,
.social-orbit:hover .orbit-badge { animation-play-state: paused; }
@keyframes orbit-spin {
  from { rotate: 0deg; }
  to   { rotate: 360deg; }
}
@keyframes orbit-unspin {
  from { rotate: calc(0deg - var(--angle)); }
  to   { rotate: calc(-360deg - var(--angle)); }
}

/* ---- Shine Border (style MagicUI, formulaires de contact) ----
   Faisceau lumineux qui tourne en continu autour du cadre : conic-gradient
   piloté par la propriété typée --shine-angle (@property, donc interpolable),
   masqué en anneau de 2px par la même technique mask/exclude que
   .spotlight-card. ::after car ::before est déjà pris par le halo curseur. ---- */
@property --shine-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}
.shine-border { position: relative; }
.shine-border::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 2px;
  background: conic-gradient(from var(--shine-angle),
    transparent 0deg,
    transparent 70deg,
    rgba(47, 167, 222, 0.85) 100deg,
    var(--color-accent-light) 115deg,
    rgba(47, 167, 222, 0.85) 130deg,
    transparent 160deg);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask-composite: exclude;
  pointer-events: none;
  z-index: 1;
  animation: shine-rotate 6s linear infinite;
}
@keyframes shine-rotate { to { --shine-angle: 360deg; } }
/* Cartes d'expertise côte à côte (index) : faisceau de la 2e carte déphasé
   d'un demi-tour pour éviter une rotation synchronisée trop mécanique. */
.reveal-right.shine-border::after { animation-delay: -3s; }
/* Variante épaisse (cartes d'expertise) : anneau lumineux plus marqué. */
.shine-border-thick::after { padding: 3.5px; }
/* Cartes en grille (process de services.html, etc.) : faisceaux déphasés
   d'un quart de tour chacun pour une rotation non synchrone. */
.reveal-stagger > .shine-border:nth-child(2)::after { animation-delay: -1.5s; }
.reveal-stagger > .shine-border:nth-child(3)::after { animation-delay: -3s; }
.reveal-stagger > .shine-border:nth-child(4)::after { animation-delay: -4.5s; }

/* ---- Meteors (style MagicUI) : étoiles filantes diagonales derrière le
   formulaire de la page contact uniquement. Tête lumineuse + traîne en dégradé
   qui s'estompe ; trajectoire haut-gauche → bas-droite (rotate 35deg statique,
   translateX animé dans le repère tourné). Transform/opacity uniquement :
   composité GPU, aucun impact sur le layout ni les performances. ---- */
.meteors {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.meteor {
  position: absolute;
  top: var(--m-top, 0);
  left: var(--m-left, 0);
  width: 3px;
  height: 3px;
  border-radius: 999px;
  background-color: var(--color-accent-light);
  box-shadow: 0 0 8px 1px rgba(47, 167, 222, 0.45);
  rotate: 35deg;
  opacity: 0;
  animation: meteor-fall var(--m-t, 6s) linear infinite;
  animation-delay: var(--m-delay, 0s);
}
.meteor::before {
  content: "";
  position: absolute;
  top: 50%;
  right: 100%;
  translate: 0 -50%;
  width: 110px;
  height: 1px;
  background: linear-gradient(to left, rgba(47, 167, 222, 0.55), transparent);
}
/* Variante pleine page (scène d'ambiance fixe de la page contact) : tête et
   traîne renforcées pour rester lisibles sous les voiles translucides. */
.meteors-page .meteor { box-shadow: 0 0 10px 2px rgba(47, 167, 222, 0.7); }
.meteors-page .meteor::before {
  width: 140px;
  background: linear-gradient(to left, rgba(47, 167, 222, 0.85), transparent);
}
@keyframes meteor-fall {
  0%   { transform: translateX(0);     opacity: 0; }
  8%   { opacity: 1; }
  75%  { opacity: 1; }
  100% { transform: translateX(760px); opacity: 0; }
}

/* ==========================================================================
   ONE TEAM — personnage hybride automatique (about.html)
   Aucune découpe manuelle : les deux photos plein pied (assets/equipe/)
   remplissent tout le gabarit (object-fit: cover) et un clip-path animé les
   coupe à la ligne de taille (--waist, ajustable). Survoler / toucher une
   moitié déplie la personne entière ; le clip-path délimitant aussi la zone
   de survol, chaque moitié est une cible de clic naturelle. Script
   d'interaction en bas de about.html.
   ========================================================================== */
/* -- Carrousel : glissement horizontal fluide entre les duos hybrides -- */
.ot-viewport { overflow: hidden; }
.ot-track {
  display: flex;
  transition: transform 0.65s var(--ease-premium);
}
.ot-slide {
  flex: 0 0 100%;
  display: flex;
  justify-content: center;
  padding-bottom: 18px; /* place pour l'ombre au sol */
}
/* Flèches latérales : pastilles de verre minimalistes, charte InesEvents. */
.ot-arrow {
  position: absolute;
  top: 45%;
  translate: 0 -50%;
  z-index: 5;
  width: 2.5rem;
  height: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background-color: rgba(255, 255, 255, 0.75);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--color-primary);
  cursor: pointer;
  transition: color 0.3s var(--ease-premium), border-color 0.3s var(--ease-premium), background-color 0.3s var(--ease-premium);
}
.ot-arrow:hover { color: var(--color-accent); border-color: var(--color-accent); }
html.dark .ot-arrow { background-color: rgba(14, 42, 71, 0.7); color: var(--color-text-inverse); }
.ot-arrow-prev { left: -0.25rem; }
.ot-arrow-next { right: -0.25rem; }
/* Puces indicatrices. */
.ot-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 999px;
  background-color: rgba(47, 167, 222, 0.3);
  cursor: pointer;
  transition: background-color 0.3s var(--ease-premium), transform 0.3s var(--ease-premium);
}
.ot-dot.is-active { background-color: var(--color-accent); transform: scale(1.25); }

.ot-figure {
  position: relative;
  /* Axe de symétrie commun : largeur fixe stricte, centrée — les deux boîtes
     absolues (inset: 0) partagent exactement ce même axe vertical. */
  width: 350px;
  max-width: 100%;
  margin: 0 auto;
  aspect-ratio: 1 / 2; /* gabarit d'un corps debout ; à ajuster au ratio des photos si besoin */
  --waist: 50%;
  /* Molettes d'alignement horizontal au pixel près (surchargées dans le HTML,
     par slide — chaque figure porte son propre calibrage). */
  --femme-x-offset: 0px;
  --homme-x-offset: 0px;
}
.upper-box,
.lower-box {
  position: absolute;
  inset: 0;
  display: flex;            /* centrage horizontal absolu du contenu */
  justify-content: center;
  width: 100%;
  cursor: pointer;
  outline: none;
  transition: clip-path 0.45s var(--ease-premium), filter 0.45s var(--ease-premium);
}
/* La femme est coupée net au bas de sa veste (--waist) : aucune jupe visible.
   L'homme, dont le contenu est remonté via --lower-y, commence 1% plus haut
   (--tuck) et passe SOUS la femme (elle est en z-index 2) : sa ceinture vient
   s'emboîter pile sous l'ourlet de la veste, jonction invisible. */
.upper-box {
  z-index: 2;
  clip-path: inset(0 0 calc(100% - var(--waist)) 0);
}
.lower-box {
  z-index: 1; /* au-dessus de l'ombre au sol (::after, z-index 0) */
  clip-path: inset(calc(var(--waist) - var(--tuck, 0%)) 0 0 0);
}
.upper-box img,
.lower-box img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Halo bleu discret autour du corps : lueur légère qui dessine le contour,
     bleu charte #2FA7DE. drop-shadow suit la transparence du PNG, le sujet
     reste parfaitement net. */
  filter: drop-shadow(0 0 10px rgba(47, 167, 222, 0.28)) drop-shadow(0 0 30px rgba(47, 167, 222, 0.12));
}
/* Recentrage horizontal par silhouette (les personnages ne sont pas forcément
   centrés dans leurs fichiers) + réglage fin optionnel translateX / scale. */
.upper-box img {
  object-position: var(--upper-pos-x, center) top;    /* la tête reste toujours visible */
  transform: translateX(var(--femme-x-offset, 0px)) scale(var(--upper-scale, 1));
  transform-origin: top center;
}
.lower-box img {
  object-position: var(--lower-pos-x, center) bottom; /* les pieds restent toujours visibles */
  /* --lower-y remonte le contenu de l'homme pour amener sa ceinture au niveau
     de la ligne de coupe (sa photo place la ceinture plus bas que la veste
     de la femme). */
  transform: translate(var(--homme-x-offset, 0px), var(--lower-y, 0px)) scale(var(--lower-scale, 1));
  transform-origin: bottom center;
  transition: transform 0.45s var(--ease-premium);
}
/* Homme révélé en entier : la remontée est annulée en douceur, sinon le haut
   de sa tête serait coupé par le bord du gabarit. */
.ot-figure.reveal-lower .lower-box img {
  transform: translate(var(--homme-x-offset, 0px), 0) scale(var(--lower-scale, 1));
}
/* Indice discret d'interactivité au survol / focus clavier. */
.upper-box:hover, .upper-box:focus-visible,
.lower-box:hover, .lower-box:focus-visible {
  filter: brightness(1.06) drop-shadow(0 0 14px rgba(47, 167, 222, 0.4));
}
/* Révélation : la moitié survolée se déplie sur tout le gabarit, l'autre se replie. */
.ot-figure.reveal-upper .upper-box { clip-path: inset(0 0 0 0); }
.ot-figure.reveal-upper .lower-box { clip-path: inset(100% 0 0 0); }
.ot-figure.reveal-lower .lower-box { clip-path: inset(0 0 0 0); }
.ot-figure.reveal-lower .upper-box { clip-path: inset(0 0 100% 0); }
/* Ombre au sol : ellipse douce et floue centrée sous les pieds, pour ancrer
   le personnage et supprimer l'effet de flottement (≈ décalage 10px sous les
   chaussures, flou 15px, noir 50%). Les pieds de l'homme étant remontés de
   --lower-y en état hybride, l'ombre suit ce niveau et redescend en douceur
   quand l'homme est révélé en entier. */
.ot-figure::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: calc(-1 * var(--lower-y, 0%) - 10px);
  transform: translateX(-50%);
  width: 58%;
  height: 30px;
  border-radius: 50%;
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.2) 48%, transparent 72%);
  filter: blur(15px);
  pointer-events: none;
  z-index: 0;
  transition: bottom 0.45s var(--ease-premium), width 0.45s var(--ease-premium);
}
.ot-figure.reveal-lower::after { bottom: -10px; }
.ot-figure.reveal-upper::after { bottom: calc(5% - 10px); width: 48%; }
.ot-figure.is-missing::after { display: none; }
/* Gabarit si les photos ne sont pas encore déposées (classe posée par onerror). */
.ot-figure.is-missing .upper-box,
.ot-figure.is-missing .lower-box { display: none; }
.ot-figure.is-missing::before {
  content: "fullBodyFemme.png / fullBodyHomme.png introuvables — déposez les deux fichiers dans assets/equipe/";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border: 1.5px dashed rgba(47, 167, 222, 0.5);
  border-radius: 1rem;
  background: linear-gradient(160deg, rgba(47, 167, 222, 0.08), rgba(14, 42, 71, 0.06));
  color: var(--color-text-soft);
  font-size: 0.75rem;
  line-height: 1.6;
  letter-spacing: 0.04em;
  padding: 1.5rem;
  text-align: center;
}
/* Carte de profil : un seul bloc actif à la fois, révélé en fondu montant
   (l'animation se rejoue à chaque bascule car display none → block). */
.profile-details-card { position: relative; min-height: 280px; }
.profile-details-card [data-profile-card] { display: none; }
.profile-details-card [data-profile-card].is-active {
  display: block;
  animation: fade-in-up 0.5s var(--ease-premium) both;
}
@keyframes fade-in-up {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Accessibilité : tout s'arrête proprement si l'utilisateur réduit les animations. */
@media (prefers-reduced-motion: reduce) {
  .meteor { display: none; }
  .map-pulse { animation: none; opacity: 0.35; }
  .shine-border::after { animation: none; }
  .btn-primary::after, .btn-outline::after, .btn-shine::after { animation: none; }
  .btn-ripple { display: none; }
  .orbit-spin, .orbit-badge { animation: none; }
  .upper-box, .lower-box { transition: none; }
  .ot-track { transition: none; }
  .profile-details-card [data-profile-card].is-active { animation: none; }
}

/* ---- Nav: floating glass "island" pill, always-on translucent background
   (not just after scroll) so nav text is legible over any hero/page content
   in any theme without depending on what's behind it. ---- */
#site-nav { transition: transform 0.5s var(--ease-premium); }
/* Navbar intelligente : masquée dès qu'on défile au-delà du haut de page
   (classe posée par initNavScroll, main.js) ; l'encoche .nav-notch ci-dessous
   permet de la réafficher à la demande. */
#site-nav.nav-hidden { transform: translateY(-130%); }

/* ---- Encoche de rappel de la navbar : petite languette de verre accrochée au
   bord supérieur, visible uniquement quand la barre est masquée ; un clic ou
   un survol réaffiche la navigation (voir initNavScroll, main.js). ---- */
.nav-notch {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%) translateY(-110%);
  z-index: 49;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 1.5rem;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-top: none;
  border-radius: 0 0 0.9rem 0.9rem;
  background-color: rgba(16, 19, 31, 0.85);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  color: var(--color-text-inverse);
  cursor: pointer;
  transition: transform 0.5s var(--ease-premium), background-color 0.3s var(--ease-premium), color 0.3s var(--ease-premium);
}
.nav-notch.is-visible { transform: translateX(-50%) translateY(0); }
.nav-notch:hover { background-color: var(--color-accent); color: var(--color-primary); }
.nav-pill {
  /* Fond bleu translucide toujours présent : les classes bg-brand-primary/70
     du HTML n'existent pas dans la palette Tailwind CDN, sans ce fond la
     pilule devenait transparente sur les pages claires. */
  background-color: rgba(14, 42, 71, 0.75);
  transition: background-color 0.4s var(--ease-premium), box-shadow 0.4s var(--ease-premium);
}
#site-nav.is-scrolled .nav-pill {
  background-color: rgba(16, 19, 31, 0.92);
  box-shadow: 0 16px 40px -16px rgba(0, 0, 0, 0.5);
}

#mobile-menu {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.45s var(--ease-premium);
  /* Même correctif que .nav-pill : bleu profond translucide pour que les
     liens blancs du menu mobile restent lisibles sur les pages claires. */
  background-color: rgba(14, 42, 71, 0.92);
}
#mobile-menu.is-open { max-height: 32rem; }

/* ---- Gallery lightbox ---- */
#lightbox { transition: opacity 0.3s var(--ease-premium); }
#lightbox[hidden] { display: none; }

/* Slider controls — only shown by JS when a card carries more than one
   photo (data-gallery-dir + data-gallery-count on the tile's image). */
.lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 9999px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(6px);
  transition: background-color 0.3s var(--ease-premium), color 0.3s var(--ease-premium), border-color 0.3s var(--ease-premium);
}
.lightbox-nav:hover,
.lightbox-nav:focus-visible {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-primary);
}
.lightbox-nav[hidden] { display: none; }
.lightbox-nav-prev { left: 0.75rem; }
.lightbox-nav-next { right: 0.75rem; }
@media (min-width: 1024px) {
  .lightbox-nav {
    width: 3.25rem;
    height: 3.25rem;
  }
  .lightbox-nav-prev { left: 2rem; }
  .lightbox-nav-next { right: 2rem; }
}

.lightbox-counter {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  padding: 0.3rem 0.75rem;
  border-radius: 9999px;
  background: rgba(8, 21, 37, 0.65);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #fff;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
}
.lightbox-counter[hidden] { display: none; }

/* ==========================================================================
   PRELOADER — shared elegant loading screen
   Only ever visible while <html> carries .is-loading — a class added by a
   one-line inline script in each page's <head>, so it can never appear (or
   trap the page) when JS is disabled. initPreloader (main.js) handles the
   exit at window.load: fade the curtain, unlock scroll, remove the node.
   The dark navy + gold/sky-blue wordmark works over both themes.
   ========================================================================== */
html.is-loading { overflow: hidden; } /* scroll locked while loading */

.preloader {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  background:
    radial-gradient(110% 85% at 50% 115%, rgba(47, 167, 222, 0.18), transparent 60%),
    radial-gradient(90% 70% at 50% -15%, rgba(232, 196, 104, 0.12), transparent 55%),
    #081525;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.6s var(--ease-premium), visibility 0s linear 0.6s;
}
html.is-loading .preloader:not(.is-done) {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: none; /* fully opaque from the very first paint — no fade-in flash */
}
.preloader-brand {
  font-family: var(--font-heading);
  font-size: clamp(2.75rem, 8vw, 5rem);
  line-height: 1;
  letter-spacing: 0.02em;
  animation: preloader-breathe 2.4s ease-in-out infinite;
}
.preloader-brand .brand-ines { color: var(--color-gold); }
.preloader-brand .brand-events { color: var(--color-accent); font-style: italic; }
.preloader-line {
  width: min(200px, 46vw);
  height: 1px;
  margin: 1.4rem 0 1rem;
  background: linear-gradient(90deg, transparent, var(--color-gold) 35%, var(--color-accent) 65%, transparent);
  background-size: 200% 100%;
  animation: preloader-shimmer 1.8s linear infinite;
}
.preloader-tagline {
  font-family: var(--font-body);
  font-size: 0.68rem;
  letter-spacing: 0.4em;
  text-indent: 0.4em; /* re-center: letter-spacing trails the last glyph */
  text-transform: uppercase;
  color: rgba(234, 246, 253, 0.55);
}
@keyframes preloader-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.7; }
}
@keyframes preloader-shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .preloader-brand,
  .preloader-line { animation: none; }
}

/* ---- Fluid cursor: micro-dot + soft halo ----
   Two fixed layers moved ONLY via translate3d(x,y,0) — composited, so a
   frame costs zero layout/paint. The dot (#cursor-dot) is glued to the
   pointer 1:1 straight from the mousemove handler (no perceived latency);
   the halo (#cursor-halo) trails it with a fast lerp inside a rAF loop
   that stops itself once converged — zero CPU while the mouse is idle
   (see initCustomCursor in main.js). Centering and the hover/press states
   live on the inner cores, animated with transform:scale — never
   width/height, which would repaint the layer on every hover. Sits above
   the preloader (z 10001) so the cursor stays alive over the splash.
   Fine-pointer devices only; nothing renders before the first real
   mousemove adds .has-custom-cursor. */
.cursor-dot,
.cursor-halo {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 10001;
  opacity: 0;
  transform: translate3d(-100px, -100px, 0);
  will-change: transform;
  transition: opacity 0.3s var(--ease-premium);
}
html.has-custom-cursor .cursor-dot,
html.has-custom-cursor .cursor-halo { opacity: 1; }
/* Pointer left the window, or is over a text field (native I-beam takes
   over there for comfortable text selection). */
html.cursor-hidden .cursor-dot,
html.cursor-hidden .cursor-halo,
html.cursor-typing .cursor-dot,
html.cursor-typing .cursor-halo { opacity: 0; }

.cursor-dot-core,
.cursor-halo-core {
  position: absolute;
  top: 0;
  left: 0;
  border-radius: 9999px;
  transform: translate(-50%, -50%);
  transition: transform 0.3s var(--ease-premium), background-color 0.3s var(--ease-premium), border-color 0.3s var(--ease-premium);
}
.cursor-dot-core {
  width: 6px;
  height: 6px;
  background-color: var(--color-accent);
  box-shadow: 0 0 10px 2px rgba(47, 167, 222, 0.6);
}
.cursor-halo-core {
  width: 38px;
  height: 38px;
  border: 1.5px solid rgba(47, 167, 222, 0.45);
  background: radial-gradient(circle, rgba(47, 167, 222, 0.12) 0%, rgba(47, 167, 222, 0.04) 55%, transparent 72%);
}
/* Hide the native cursor everywhere once the custom one is active… */
html.has-custom-cursor,
html.has-custom-cursor * { cursor: none !important; }
/* …except over form fields, where the native text cursor stays useful. */
html.has-custom-cursor input,
html.has-custom-cursor textarea,
html.has-custom-cursor select { cursor: auto !important; }
/* Hovering anything clickable: the halo expands and fills slightly, the
   dot contracts — the discreet "magnetic" interactive cue. */
html.cursor-hover .cursor-halo-core {
  transform: translate(-50%, -50%) scale(1.6);
  border-color: var(--color-accent-light);
  background-color: rgba(47, 167, 222, 0.14);
}
html.cursor-hover .cursor-dot-core { transform: translate(-50%, -50%) scale(0.5); }
/* Press feedback: brief halo contraction while the button is held. */
html.cursor-down .cursor-halo-core { transform: translate(-50%, -50%) scale(0.85); }
html.cursor-hover.cursor-down .cursor-halo-core { transform: translate(-50%, -50%) scale(1.35); }

/* ---- Misc ---- */
.card-hover { transition: transform 0.4s var(--ease-premium), box-shadow 0.4s var(--ease-premium); }
.card-hover:hover { transform: translateY(-6px); box-shadow: 0 24px 48px -24px rgba(14,42,71,0.35); }
.card-hover:active { transform: translateY(-3px) scale(0.99); }

::selection { background-color: var(--color-accent); color: var(--color-primary); }

/* ---- Team monogram avatars (about.html) ---- */
.avatar-monogram {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background-color: var(--color-accent);
  color: var(--color-primary);
  font-family: var(--font-heading);
  font-weight: 600;
}

/* ---- Stat tiles (index.html) ---- */
.stat-value { font-family: var(--font-heading); color: var(--color-accent); }

/* ---- Gallery filter pills (gallery.html) ---- */
.filter-btn {
  transition: background-color 0.35s var(--ease-premium), color 0.35s var(--ease-premium), border-color 0.35s var(--ease-premium), transform 0.35s var(--ease-premium);
}
.filter-btn:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  background-color: rgba(47, 167, 222, 0.08);
  transform: translateY(-2px);
}
.filter-btn.is-active:hover { color: var(--color-primary); }

/* ---- Gallery masonry tiles: image stays in normal flow (so CSS columns
   masonry keeps each tile's natural aspect-ratio height); the short caption
   now sits in normal flow UNDER the photo (always visible), and the longer
   text lives in the lightbox modal via [data-description]. The image is
   wrapped in its own overflow-hidden div so the hover zoom never bleeds
   over the caption block. ---- */
.masonry-tile img { transition: transform 0.8s var(--ease-premium); }
.masonry-tile:hover img { transform: scale(1.06); }

/* ---- Photo placeholder (services.html) ----
   Client-supplied photos (assets/images/service-*.jpg) don't exist yet in
   this repo — each <img> falls back to this gradient + camera glyph via
   onerror, instead of a broken-image icon, until the real files are added. */
.photo-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--color-accent-light), var(--color-primary-light));
}
.photo-placeholder::after {
  content: "";
  width: 15%;
  min-width: 28px;
  max-width: 48px;
  aspect-ratio: 1;
  background-color: rgba(255, 255, 255, 0.8);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M4 7h3l1.5-2h7L17 7h3a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1Z'/%3E%3Ccircle cx='12' cy='13' r='3.5'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M4 7h3l1.5-2h7L17 7h3a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1Z'/%3E%3Ccircle cx='12' cy='13' r='3.5'/%3E%3C/svg%3E") center/contain no-repeat;
}

/* ==========================================================================
   DARK MODE
   Toggled by adding class="dark" to <html> (see main.js). Rather than
   requiring every page to sprinkle Tailwind `dark:` variants, we retheme
   our own brand-* tokens/classes centrally here – existing and future pages
   that only use bg-brand [or] text-brand [or] border-brand get dark mode for free.
   `!important` is needed because Tailwind's CDN build injects its own
   <style> tag at runtime with matching specificity/ordering is unpredictable.
   ========================================================================== */
html.dark { color-scheme: dark; }

html.dark body { background-color: #0A1826; color: #E4F1FA; }

html.dark .bg-brand-bg     { background-color: #0A1826 !important; }
html.dark .bg-brand-bg-alt { background-color: #0F2338 !important; }

html.dark .text-brand-text { color: #E4F1FA !important; }
html.dark .text-brand-soft { color: #8CA5B5 !important; }

/* text-brand-primary is used two ways in the existing pages: as heading ink
   on the (normally light) page background, and as the deliberately-dark
   label color on the always-gold CTA banner. Invert the first case, keep
   the second untouched. */
html.dark .text-brand-primary { color: #E4F1FA !important; }
html.dark .bg-brand-accent.text-brand-primary,
html.dark .bg-brand-accent .text-brand-primary { color: var(--color-primary) !important; }

html.dark .border-brand { border-color: rgba(255,255,255,0.14) !important; }

/* Frosted-glass cards built with raw Tailwind opacity utilities
   (bg-white/40, bg-white/50, bg-white/60, bg-white/70, bg-white/80...)
   get a dark equivalent automatically, wherever they appear. */
html.dark [class*="bg-white/"] { background-color: rgba(255,255,255,0.06) !important; }

html.dark input,
html.dark textarea,
html.dark select {
  color: #EDE9DC;
  color-scheme: dark;
}
html.dark input::placeholder,
html.dark textarea::placeholder { color: rgba(228,241,250,0.4); }

/* ---- Day/night toggle icon swap (pure CSS, no JS needed beyond the
   `dark` class toggle already wired in main.js) ---- */
[data-theme-icon="sun"] { display: none; }
html.dark [data-theme-icon="sun"] { display: inline; }
html.dark [data-theme-icon="moon"] { display: none; }
