feat: migre le site vers SvelteKit (Svelte 5, statique, zéro tiers)

- Rebuild complet SvelteKit 2 + Svelte 5 runes + TypeScript, adapter-static
- Images self-hébergées optimisées au build (vite-imagetools), fonts Archivo/Inter
  en woff2 local — BunnyCDN et Google Fonts supprimés (corrige les 403 d'images)
- Tokens charte OKI, thème sombre par défaut / clair opt-in, zéro emoji (set SVG)
- Motion : KineticText (stagger syncopé gwoka), ScrollProgressBar, View Transitions
- Village SVG isométrique pour les services hébergés + fallback accessible
- PWA (manifest + SW hors-ligne), 404 en KA, CSP nettoyée (.htaccess + _headers)
- Lighthouse mobile : 91/100/100/100 — JS initial 67 Ko gzip
This commit is contained in:
sucupira
2026-07-21 08:00:16 -04:00
parent d0fc7207f2
commit d0d80a105e
175 changed files with 15603 additions and 5911 deletions
+50
View File
@@ -0,0 +1,50 @@
<IfModule mod_rewrite.c>
RewriteEngine On
# Redirect HTTP to HTTPS, including behind reverse proxies
RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
# Page 404 designée (statique, autonome)
ErrorDocument 404 /404.html
<IfModule mod_headers.c>
# Force HTTPS for one year
Header always set Strict-Transport-Security "max-age=31536000"
# Prevent MIME sniffing
Header always set X-Content-Type-Options "nosniff"
# Prevent clickjacking (also enforced via CSP frame-ancestors)
Header always set X-Frame-Options "DENY"
# Control referrer leakage
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Restrict browser features
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=()"
# Content Security Policy (no unsafe-inline/unsafe-eval in script-src)
# Toutes les ressources sont auto-hebergees : aucun domaine tiers.
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'"
</IfModule>
# Cache long pour les polices et images optimisees au build
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType image/avif "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
</IfModule>
# Assets fingerprintes par Vite : cache immutable d'un an
<IfModule mod_headers.c>
<LocationMatch "^/_app/">
Header set Cache-Control "public, max-age=31536000, immutable"
</LocationMatch>
<LocationMatch "^/fonts/">
Header set Cache-Control "public, max-age=31536000, immutable"
</LocationMatch>
</IfModule>
+164
View File
@@ -0,0 +1,164 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex" />
<meta name="theme-color" content="#0D0D0D" />
<title>404 — Paj la rivé périyòd… · ORGANISATION KA INTERNATIONALE</title>
<link rel="icon" href="/favicon.ico" sizes="48x48" />
<link rel="icon" href="/images/logo-512x512.png" type="image/png" />
<link rel="stylesheet" href="/fonts/fonts.css" />
<style>
/* 404 autonome — tokens OKI recopiés (charte §2), aucune dépendance JS */
:root {
--noir-oki: #0d0d0d;
--blanc-creme: #fff8e7;
--line: rgba(255, 255, 255, 0.1);
--or-oki: #fdb813;
--or-clair: #ffe066;
--vert-oki: #00d66c;
--rouge-oki: #ff1654;
--font-display: 'Archivo', 'Arial Black', sans-serif;
--font-body: 'Inter', system-ui, sans-serif;
--radius-sm: 3px;
}
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-body);
background: var(--noir-oki);
color: var(--blanc-creme);
min-height: 100vh;
line-height: 1.5;
display: flex;
flex-direction: column;
-webkit-font-smoothing: antialiased;
}
/* Flag-bar — 6 px, 4 segments francs, arrêts nets (charte §2.4) */
.flag-bar {
height: 6px;
width: 100%;
flex-shrink: 0;
background: linear-gradient(
to right,
#0d0d0d 0 25%,
#fdb813 25% 50%,
#00d66c 50% 75%,
#ff1654 75% 100%
);
}
main {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 4rem 2rem;
gap: 1.5rem;
}
.ka-drum {
width: 6rem;
height: 6rem;
color: var(--or-oki);
}
h1 {
font-family: var(--font-display);
font-weight: 900;
text-transform: uppercase;
letter-spacing: -0.01em;
line-height: 1.1;
font-size: clamp(4rem, 15vw, 8rem);
color: var(--blanc-creme);
}
.ka {
font-size: 1.25rem;
color: var(--or-oki);
}
.fr {
max-width: 52ch;
color: color-mix(in srgb, var(--blanc-creme) 70%, transparent);
}
/* Bouton or : bordure 2 px, angles nets (charte §2.2) */
.btn {
display: inline-block;
font-family: var(--font-display);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
padding: 0.75rem 1.5rem;
border: 2px solid var(--or-oki);
border-radius: var(--radius-sm);
background: transparent;
color: var(--or-oki);
text-decoration: none;
transition:
background 120ms cubic-bezier(0.22, 1, 0.36, 1),
color 120ms cubic-bezier(0.22, 1, 0.36, 1),
transform 120ms cubic-bezier(0.22, 1, 0.36, 1);
}
.btn:hover {
background: var(--or-oki);
color: var(--noir-oki);
transform: translateY(-2px);
}
.btn:focus-visible {
outline: 2px solid var(--or-oki);
outline-offset: 3px;
}
footer {
padding: 1.5rem 2rem;
border-top: 1px solid var(--line);
text-align: center;
font-size: 0.85rem;
color: color-mix(in srgb, var(--blanc-creme) 70%, transparent);
}
</style>
</head>
<body>
<div class="flag-bar" aria-hidden="true"></div>
<main>
<!-- Tambour ka : fût conique + sangles croisées, stroke 2 px, angles nets (charte §4) -->
<svg
class="ka-drum"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="square"
stroke-linejoin="miter"
aria-hidden="true"
>
<path d="M6 4 H18" />
<path d="M6 4 L4 20" />
<path d="M18 4 L20 20" />
<path d="M4 20 H20" />
<path d="M6 4 L20 20" />
<path d="M18 4 L4 20" />
</svg>
<h1>404</h1>
<p class="ka" lang="gcf">« Paj la rivé périyòd… »</p>
<p class="fr">La page que vous cherchez n'existe pas ou a été déplacée. La route continue ailleurs.</p>
<a class="btn" href="/">Retour à l'accueil</a>
</main>
<footer>ORGANISATION KA INTERNATIONALE · o-k-i.net</footer>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
/*
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=()
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
/_app/*
Cache-Control: public, max-age=31536000, immutable
/fonts/*
Cache-Control: public, max-age=31536000, immutable
/assets/press/*
Cache-Control: public, max-age=2592000
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+66
View File
@@ -0,0 +1,66 @@
/* Archivo — display (titres + boutons), graisses 600-900 */
@font-face {
font-family: 'Archivo';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('/fonts/archivo-latin-600-normal.woff2') format('woff2');
}
@font-face {
font-family: 'Archivo';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/fonts/archivo-latin-700-normal.woff2') format('woff2');
}
@font-face {
font-family: 'Archivo';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url('/fonts/archivo-latin-800-normal.woff2') format('woff2');
}
@font-face {
font-family: 'Archivo';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url('/fonts/archivo-latin-900-normal.woff2') format('woff2');
}
/* Inter — corps de texte, graisses 400-800 */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/inter-latin-400-normal.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('/fonts/inter-latin-500-normal.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('/fonts/inter-latin-600-normal.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('/fonts/inter-latin-700-normal.woff2') format('woff2');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url('/fonts/inter-latin-800-normal.woff2') format('woff2');
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+70
View File
@@ -0,0 +1,70 @@
<svg xmlns="http://www.w3.org/2000/svg">
<!-- Set iconographique OKI — charte §4 : viewBox 24×24, stroke 2 px, angles nets, currentColor -->
<symbol id="ka" viewBox="0 0 24 24">
<!-- tambour ka : collerette hexagonale, fût conique, sangles en zigzag -->
<path d="M4 7 L20 7 L22 9 L20 11 L4 11 L2 9 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M4 11 L7 21 L17 21 L20 11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M8 11 L10.5 16 L9 21 M16 11 L13.5 16 L15 21" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="lambi" viewBox="0 0 24 24">
<!-- conque lambi : dome en spirale, lèvre angulaire -->
<path d="M4 14 C4 8 9 4 14 4 C18 4 21 7 21 11 L17 14 L20 18 L13 20 C8 21 4 18 4 14 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M11 14 C11 12.5 13.5 12.5 13.5 14 C13.5 16 9.5 16 9.5 13 C9.5 9.5 14 8.5 16 10.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="zetwal" viewBox="0 0 24 24">
<!-- étoile 4 branches -->
<path d="M12 3 L14.5 9.5 L21 12 L14.5 14.5 L12 21 L9.5 14.5 L3 12 L9.5 9.5 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="mawon" viewBox="0 0 24 24">
<!-- brisure de chaîne : deux demi-maillons octogonaux, connexion rompue -->
<path d="M9 7 L5 7 L3 9 L3 15 L5 17 L9 17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M15 7 L19 7 L21 9 L21 15 L19 17 L15 17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M12 9.5 L12 11 M12 13 L12 14.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="lakanmou" viewBox="0 0 24 24">
<!-- flamme : contour angulaire à base courbe, languette interne -->
<path d="M12 3 L15 8 L18 12 C19 15 18 18 15.5 20 L12 21 L8.5 20 C6 18 5 15 6 12 L9 8 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M12 12 L14 14.5 C14.3 16.5 13 18 12 18 C11 18 9.7 16.5 10 14.5 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="jaden" viewBox="0 0 24 24">
<!-- pousse : tige et deux feuilles angulaires en cerf-volant -->
<path d="M12 21 L12 10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M12 13 L3 5 L6 12 L10 16 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M12 13 L21 5 L18 12 L14 16 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="kannen" viewBox="0 0 24 24">
<!-- canne à sucre : tige segmentée, trois lames au sommet -->
<path d="M10 21 L10 6 L14 6 L14 21 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M10 17 L14 17 M10 13 L14 13 M10 9 L14 9" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M10 6 L5 3 M14 6 L19 3 M12 6 L12 2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="lanme" viewBox="0 0 24 24">
<!-- vague angulaire : trois lignes brisées superposées -->
<path d="M2 8 L7 4 L12 8 L17 4 L22 8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M2 14 L7 10 L12 14 L17 10 L22 14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M2 20 L7 16 L12 20 L17 16 L22 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="glo" viewBox="0 0 24 24">
<!-- poing levé géométrique : bloc poing + avant-bras, doigts segmentés, pouce -->
<path d="M8 20 L8 15 L6 15 L6 6 L8 4 L18 4 L18 15 L16 15 L16 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M6 9 L18 9" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M9 4 L9 9 M12 4 L12 9 M15 4 L15 9 M6 12 L10 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="kle" viewBox="0 0 24 24">
<!-- cadenas ouvert : corps rectangulaire, anse angulaire levée -->
<path d="M5 11 L19 11 L19 21 L5 21 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M8 11 L8 6 L10 4 L14 4 L16 6 L16 8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M12 14.5 L12 17.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="pawol" viewBox="0 0 24 24">
<!-- bulle de parole angulaire : rectangle à queue, deux lignes de texte -->
<path d="M3 4 L21 4 L21 15 L13 15 L8 20 L8 15 L3 15 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M7 8 L17 8 M7 11.5 L13 11.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
<symbol id="mizik-note" viewBox="0 0 24 24">
<!-- double croche : deux tiges reliées par un croc, têtes en losange -->
<path d="M9 6 L9 19 M19 4 L19 17 M9 6 L19 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M9 16.5 L11.5 19 L9 21.5 L6.5 19 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
<path d="M19 14.5 L21.5 17 L19 19.5 L16.5 17 Z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+15
View File
@@ -0,0 +1,15 @@
(function() {
// Redirection automatique selon la langue du navigateur, sauf choix explicite déjà mémorisé
var pref = localStorage.getItem('oki-lang-pref');
if (pref) return;
var browserLang = (navigator.language || navigator.userLanguage || '').toLowerCase();
var wantsEnglish = browserLang.indexOf('en') === 0;
var currentLocale = document.documentElement.dataset.locale || 'fr';
if (wantsEnglish && currentLocale !== 'en') {
window.location.replace('/en/' + window.location.hash);
} else if (!wantsEnglish && currentLocale === 'en') {
window.location.replace('/' + window.location.hash);
}
})();
+30
View File
@@ -0,0 +1,30 @@
{
"name": "ORGANISATION KA INTERNATIONALE",
"short_name": "OKI",
"description": "Studio web militant caribéen — des solutions numériques libres pour les organisations engagées.",
"lang": "fr",
"start_url": "/",
"scope": "/",
"display": "standalone",
"theme_color": "#0D0D0D",
"background_color": "#0D0D0D",
"icons": [
{
"src": "/images/logo-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/images/logo-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/favicon.ico",
"sizes": "48x48",
"type": "image/x-icon"
}
]
}
+6
View File
@@ -0,0 +1,6 @@
// Enregistrement du service worker — chemins absolus (le site a des routes imbriquées /en/, /dons/…).
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js', { scope: '/' });
});
}
+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://o-k-i.net/sitemap.xml
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://o-k-i.net/</loc>
<lastmod>2026-07-21</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://o-k-i.net/en/</loc>
<lastmod>2026-07-21</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://o-k-i.net/presse/</loc>
<lastmod>2026-07-21</lastmod>
<changefreq>monthly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://o-k-i.net/en/press/</loc>
<lastmod>2026-07-21</lastmod>
<changefreq>monthly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://o-k-i.net/dons/</loc>
<lastmod>2026-07-21</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://o-k-i.net/en/donate/</loc>
<lastmod>2026-07-21</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://o-k-i.net/mentions-legales/</loc>
<lastmod>2026-07-21</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://o-k-i.net/en/legal-notice/</loc>
<lastmod>2026-07-21</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
</urlset>
+17
View File
@@ -0,0 +1,17 @@
(function() {
// Récupérer le thème stocké ou détecter le thème système
const storedTheme = localStorage.getItem('oki-theme');
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
let theme = storedTheme;
// Si pas de préférence stockée, utiliser le thème système
if (!storedTheme) {
theme = systemPrefersDark ? 'dark' : 'light';
}
// Appliquer le thème immédiatement
if (theme === 'light') {
document.documentElement.classList.add('light-theme');
}
})();