Files
o-k-i.net/src/routes/+layout.svelte
T
sucupira a6f359559b 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)
2026-07-21 12:42:02 -04:00

88 lines
2.6 KiB
Svelte

<script lang="ts">
import '$lib/styles/oki-tokens.css';
import '$lib/styles/base.css';
import { afterNavigate, onNavigate } from '$app/navigation';
import { page } from '$app/state';
import { onMount } from 'svelte';
import Footer from '$lib/components/Footer.svelte';
import Nav from '$lib/components/Nav.svelte';
import IntroLoader from '$lib/components/motion/IntroLoader.svelte';
import ScrollProgressBar from '$lib/components/motion/ScrollProgressBar.svelte';
import { getBundle, localeFromPath } from '$lib/i18n';
import { initSmoothScroll, scrollToAnchor } from '$lib/motion/scroll';
import { prefersReducedMotion } from '$lib/motion/tokens';
let { children } = $props();
const locale = $derived(localeFromPath(page.url.pathname));
const t = $derived(getBundle(locale));
// PageTransition légère : View Transitions API, durée lue depuis les tokens.
// Navigateurs sans support : navigation instantanée (fallback sobre).
onNavigate((navigation) => {
if (prefersReducedMotion()) return;
if (!document.startViewTransition) return;
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
});
// Focus déplacé sur le contenu après chaque transition de route (playbook §6)
afterNavigate(({ type, to }) => {
if (type === 'enter') return;
if (to?.url.hash) return;
document.getElementById('contenu')?.focus({ preventScroll: true });
});
onMount(() => {
let cleanupScroll: (() => void) | undefined;
let destroyed = false;
initSmoothScroll().then((fn) => {
if (destroyed) fn();
else cleanupScroll = fn;
});
// Ancres de la page courante : glisse Lenis (playbook P1)
function onClick(event: MouseEvent) {
const anchor = (event.target as HTMLElement).closest('a');
const href = anchor?.getAttribute('href');
if (href?.startsWith('#') && scrollToAnchor(href)) {
event.preventDefault();
history.replaceState(null, '', href);
}
}
document.addEventListener('click', onClick);
return () => {
destroyed = true;
cleanupScroll?.();
document.removeEventListener('click', onClick);
};
});
</script>
<a href="#contenu" class="skip-link">{locale === 'en' ? 'Skip to content' : 'Aller au contenu'}</a>
<IntroLoader />
<ScrollProgressBar />
<Nav {t} {locale} />
<main id="contenu" tabindex="-1">
{@render children()}
</main>
<Footer {t} {locale} />
<style>
main {
outline: none;
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: var(--dur-mesure);
animation-timing-function: var(--ease-ka);
}
</style>