feat: passe expérience — motion system niveau Awwwards (DOM/SVG, zéro WebGL)

- IntroLoader : cérémonie d'entrée (flag-bar, monogramme, titrage), 1re visite, skippable
- Lenis au layout (ticker GSAP, imports dynamiques) + ancres interceptées
- Hero : titrage oversized en masques, filets drapeau en parallaxe, sortie scrubée (scroll-driven CSS)
- Reveal syncopé gwoka (use:reveal) sur 83 éléments, flag chips, icônes pop
- Marquee kréyòl (tagline + valeurs), pause au survol
- Hover craft : boutons à balayage or, nav soulignée + section active, pochettes DJANGOKAM en wipe clip-path + tilt 3D (use:tilt) avec reflet or
- Gate unique prefers-reduced-motion partout ; contenu complet sans JS
- Lighthouse mobile 91/100/100/100 — JS initial 69 Ko gzip (budget 170 Ko)
This commit is contained in:
sucupira
2026-07-21 12:42:02 -04:00
parent 30858926fa
commit a6f359559b
27 changed files with 1018 additions and 66 deletions
+52 -2
View File
@@ -1,16 +1,40 @@
<script lang="ts">
import { page } from '$app/state';
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
import { alternatePath, type Bundle, type Locale } from '$lib/i18n';
let { t, locale }: { t: Bundle; locale: Locale } = $props();
let menuOpen = $state(false);
let openDropdown = $state<string | null>(null);
let activeSection = $state('');
const altLocale: Locale = $derived(locale === 'fr' ? 'en' : 'fr');
const altHref = $derived(alternatePath(page.url.pathname, altLocale));
/** Extrait l'ancre d'une URL de nav (`/#solutions` → `solutions`). */
function anchorOf(url: string): string {
const i = url.indexOf('#');
return i >= 0 ? url.slice(i + 1) : '';
}
// Section courante surlignée : la bande médiane du viewport désigne l'actif
onMount(() => {
const sections = [...document.querySelectorAll('section[id]')];
if (sections.length === 0) return;
const io = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) activeSection = entry.target.id;
}
},
{ rootMargin: '-40% 0px -55% 0px' }
);
for (const section of sections) io.observe(section);
return () => io.disconnect();
});
function rememberLangChoice() {
localStorage.setItem('oki-lang-pref', altLocale);
}
@@ -78,7 +102,12 @@
</li>
{:else}
<li>
<a href={resolve(item.url)} onclick={closeMenu}>{item.text}</a>
<a
href={resolve(item.url)}
class:active={anchorOf(item.url) !== '' && anchorOf(item.url) === activeSection}
aria-current={anchorOf(item.url) === activeSection ? 'true' : undefined}
onclick={closeMenu}>{item.text}</a
>
</li>
{/if}
{/each}
@@ -177,15 +206,36 @@
}
.nav-links a {
position: relative;
color: var(--blanc-creme);
font-weight: 500;
font-size: 0.95rem;
}
.nav-links a:hover {
/* Souligné or qui se dessine depuis la gauche (hover + section active) */
.nav-links a::after {
content: '';
position: absolute;
left: 0;
bottom: -6px;
width: 100%;
height: 2px;
background: var(--or-oki);
transform: scaleX(0);
transform-origin: left center;
transition: transform var(--dur-tanbou) var(--ease-syncope);
}
.nav-links a:hover,
.nav-links a.active {
color: var(--or-oki);
}
.nav-links a:hover::after,
.nav-links a.active::after {
transform: scaleX(1);
}
.lang-switch {
border: 1px solid var(--line);
border-radius: var(--radius-sm);