forked from ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI
Réécriture SvelteKit statique (Svelte 5 runes + TS, adapter-static)
- Données PeerTube (GADE), Castopod (KUTE) et Mastodon (BOKANTE) bakées au build via +page.server.ts (throttle + retry 429 + déduplication) - Charte OKI complète : tokens, thème sombre par défaut (clair opt-in, anti-FOUC), Archivo/Inter self-hébergées, flag-bar, sprite SVG (zéro emoji en interface, zéro Font Awesome/CDN) - i18n FR/EN par routage [[locale]], bundles JSON, hreflang, %lang% serveur - Pages : accueil, /video/[uuid] (embed, téléchargements, partage, commentaires, JSON-LD VideoObject), /categories/[id], /recherche (index JSON baké, recherche client), /direct (live + annonce multi-fuseaux), /dons, /mentions-legales, /offline + 404 kréyòl - Motion gwoka : KineticText (scrub view()), ScrollProgressBar, FlagChip, reveal syncopé, View Transitions, gate prefers-reduced-motion unique - PWA : manifest + Workbox generateSW (fallback /offline/), registerSW statique - Sécurité : CSP par page en <meta> (SHA-256 des inline via scripts/postbuild-csp.mjs) + headers globaux (_headers Cloudflare et .htaccess o2switch), redirections des anciennes URLs PHP - Doc : docs/DEPLOIEMENT-SVELTEKIT.md
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
<script lang="ts">
|
||||
import '$lib/styles/oki-tokens.css';
|
||||
import '$lib/styles/base.css';
|
||||
import { onNavigate } from '$app/navigation';
|
||||
import Footer from '$lib/components/Footer.svelte';
|
||||
import Nav from '$lib/components/Nav.svelte';
|
||||
import Sidebar from '$lib/components/Sidebar.svelte';
|
||||
import ScrollProgressBar from '$lib/components/motion/ScrollProgressBar.svelte';
|
||||
import { prefersReducedMotion } from '$lib/motion/tokens';
|
||||
import type { LayoutProps } from './$types';
|
||||
|
||||
let { data, children }: LayoutProps = $props();
|
||||
|
||||
let menuOpen = $state(false);
|
||||
|
||||
onNavigate((navigation) => {
|
||||
if (prefersReducedMotion()) return;
|
||||
if (!('startViewTransition' in document)) return;
|
||||
return new Promise((resolve) => {
|
||||
document.startViewTransition(async () => {
|
||||
resolve();
|
||||
await navigation.complete;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta name="generator" content="SvelteKit" />
|
||||
</svelte:head>
|
||||
|
||||
<a href="#main-content" class="skip-link">{data.t.nav.skipToContent}</a>
|
||||
<ScrollProgressBar label={data.t.a11y.progressLabel} />
|
||||
|
||||
<Nav
|
||||
locale={data.locale}
|
||||
t={data.t}
|
||||
{menuOpen}
|
||||
onToggleMenu={() => (menuOpen = !menuOpen)}
|
||||
/>
|
||||
|
||||
<div class="shell">
|
||||
<aside class="sidebar">
|
||||
<Sidebar locale={data.locale} t={data.t} />
|
||||
</aside>
|
||||
|
||||
{#if menuOpen}
|
||||
<div class="drawer-backdrop" onclick={() => (menuOpen = false)} aria-hidden="true"></div>
|
||||
{/if}
|
||||
<aside
|
||||
id="mobile-menu"
|
||||
class="drawer"
|
||||
class:open={menuOpen}
|
||||
aria-label={data.t.nav.mainLabel}
|
||||
inert={!menuOpen}
|
||||
>
|
||||
<Sidebar locale={data.locale} t={data.t} onNavigate={() => (menuOpen = false)} />
|
||||
</aside>
|
||||
|
||||
<div class="content">
|
||||
<main id="main-content" class="main-content">
|
||||
{@render children()}
|
||||
</main>
|
||||
<Footer locale={data.locale} t={data.t} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.shell {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-w) 1fr;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
border-right: 1px solid var(--line);
|
||||
background: var(--surface);
|
||||
overflow-y: auto;
|
||||
position: sticky;
|
||||
top: calc(var(--header-h) + 6px);
|
||||
height: calc(100dvh - var(--header-h) - 6px);
|
||||
}
|
||||
|
||||
.content {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: var(--space-3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.drawer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.shell {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.drawer {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: min(300px, 85vw);
|
||||
z-index: 280;
|
||||
background: var(--surface);
|
||||
border-right: 1px solid var(--line);
|
||||
overflow-y: auto;
|
||||
transform: translateX(-100%);
|
||||
transition: transform var(--dur-mesure) var(--ease-ka);
|
||||
}
|
||||
|
||||
.drawer.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.drawer-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 270;
|
||||
background: rgba(13, 13, 13, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.drawer {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
@view-transition {
|
||||
navigation: auto;
|
||||
}
|
||||
|
||||
@keyframes vt-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vt-fade-out {
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
::view-transition-old(root) {
|
||||
animation: vt-fade-out var(--dur-mesure) var(--ease-ka) both;
|
||||
}
|
||||
|
||||
::view-transition-new(root) {
|
||||
animation: vt-fade-in var(--dur-mesure) var(--ease-ka) both;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user