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:
sucupira
2026-07-23 00:49:02 -04:00
parent 5b1a78782a
commit d6efb6736f
147 changed files with 14567 additions and 15145 deletions
+80
View File
@@ -0,0 +1,80 @@
<script lang="ts">
import { onMount } from 'svelte';
import type { Locale } from '$lib/types';
let { target, locale }: { target: string; locale: Locale } = $props();
let remaining = $state<{ days: number; hours: number; minutes: number; seconds: number } | null>(
null
);
function compute(): typeof remaining {
const targetTime = new Date(target.replace(' ', 'T')).getTime();
const diff = targetTime - Date.now();
if (Number.isNaN(targetTime) || diff <= 0) return null;
return {
days: Math.floor(diff / 86_400_000),
hours: Math.floor((diff % 86_400_000) / 3_600_000),
minutes: Math.floor((diff % 3_600_000) / 60_000),
seconds: Math.floor((diff % 60_000) / 1000)
};
}
onMount(() => {
remaining = compute();
const interval = setInterval(() => (remaining = compute()), 1000);
return () => clearInterval(interval);
});
const labels = $derived(
locale === 'en'
? { days: 'days', hours: 'hours', minutes: 'minutes', seconds: 'seconds' }
: { days: 'jours', hours: 'heures', minutes: 'minutes', seconds: 'secondes' }
);
</script>
{#if remaining}
<div class="countdown" role="timer" aria-live="off">
{#each [['days', remaining.days], ['hours', remaining.hours], ['minutes', remaining.minutes], ['seconds', remaining.seconds]] as [key, value] (key)}
<div class="unit">
<span class="value">{String(value).padStart(2, '0')}</span>
<span class="label">{labels[key as keyof typeof labels]}</span>
</div>
{/each}
</div>
{/if}
<style>
.countdown {
display: flex;
gap: var(--space-2);
flex-wrap: wrap;
}
.unit {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--space-1) var(--space-2);
background: var(--card-bg);
border: var(--border-card);
border-top: 4px solid var(--accent);
border-radius: var(--radius-sm);
min-width: 80px;
}
.value {
font-family: var(--font-display);
font-weight: 900;
font-size: 2rem;
font-variant-numeric: tabular-nums;
color: var(--accent);
}
.label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.03em;
color: var(--muted);
}
</style>