feat(oki): fondations de la migration vers la stack souveraine OKI
Architecture décidée et documentée dans ADR.md : SvelteKit natif en fichiers plats plutôt que Strapi (55 outils, données statiques, souveraineté et budget performance prioritaires — doctrine §1.2, §4.4). - frontend/ : SvelteKit 2 + Svelte 5 (runes), adapter-static, 58 pages prérendues, CSS natif en @layer (reset→tokens→base→layouts→components), tokens oklch(), container queries, typo fluide clamp(), 2 polices WOFF2 auto-hébergées (Atkinson Hyperlegible, Fraunces), zéro CDN tiers - i18n FR/gcf typé (clé manquante = erreur de build), bascule Kréyòl persistée en localStorage, micro-textes créoles (« Ou pa manké ayen ») - Composants : ToolCard, DifficultyBadge (forme+texte+couleur, jamais la couleur seule), CategoryNav (≤5 entrées, divulgation progressive), SearchBar (raccourci /), LanguageSwitcher, EndMarker - content/ : 55 fiches migrées depuis src/ (script frontend/scripts/ migrate-tools.mjs, idempotent), licences SPDX reprises de directory.json - docs/ : DESIGN_SYSTEM.md et CONTENT_GUIDE.md Budgets vérifiés : JS 41 Ko gzip, CSS 2,9 Ko gzip, svelte-check 0 erreur. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* base.css — HTML sémantique nu (doctrine §4.3 : l'accessibilité est
|
||||
* d'abord un problème de HTML correct).
|
||||
*/
|
||||
body {
|
||||
font-family: var(--font-text);
|
||||
font-size: var(--text-1);
|
||||
line-height: var(--leading);
|
||||
color: var(--oki-ink);
|
||||
background: var(--oki-surface);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-family: var(--font-display);
|
||||
line-height: 1.15;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--text-5);
|
||||
}
|
||||
h2 {
|
||||
font-size: var(--text-4);
|
||||
}
|
||||
h3 {
|
||||
font-size: var(--text-2);
|
||||
}
|
||||
|
||||
p,
|
||||
li {
|
||||
max-width: var(--measure);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-ink);
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
|
||||
/* focus visible sur TOUT élément interactif (doctrine §4.3) */
|
||||
:focus-visible {
|
||||
outline: var(--focus-ring);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--radius-1);
|
||||
}
|
||||
|
||||
button {
|
||||
min-height: var(--tap-target); /* Fitts, doctrine §1.3 */
|
||||
padding-inline: var(--space-3);
|
||||
border: 1px solid var(--oki-line);
|
||||
border-radius: var(--radius-2);
|
||||
background: var(--oki-surface-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.9em;
|
||||
background: var(--oki-surface-2);
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: var(--radius-1);
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
padding: var(--space-3);
|
||||
background: var(--oki-surface-2);
|
||||
border-radius: var(--radius-2);
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--oki-jaune);
|
||||
color: var(--oki-noir);
|
||||
}
|
||||
|
||||
/* lien d'évitement clavier */
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
inset-inline-start: var(--space-3);
|
||||
inset-block-start: var(--space-3);
|
||||
translate: 0 -300%;
|
||||
background: var(--oki-surface);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
translate: 0 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Polices auto-hébergées, servies depuis notre domaine — jamais de Google
|
||||
* Fonts (doctrine §5.3 : performance, RGPD, souveraineté).
|
||||
* Budget : 2 fichiers WOFF2 maximum. Le gras du corps est synthétisé.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/fonts/atkinson-hyperlegible-latin-400-normal.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fraunces';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/fonts/fraunces-latin-700-normal.woff2') format('woff2');
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* layouts.css — primitives de composition (approche « Every Layout » :
|
||||
* des compositions, pas des pages — doctrine §5.2).
|
||||
* Le responsive passe par container queries, jamais par media queries
|
||||
* de largeur d'écran (doctrine §2.2 point 6).
|
||||
*/
|
||||
.stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap, var(--space-3));
|
||||
}
|
||||
|
||||
.cluster {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap, var(--space-2));
|
||||
align-items: var(--align, center);
|
||||
}
|
||||
|
||||
.center {
|
||||
box-sizing: content-box;
|
||||
max-inline-size: var(--max, 70rem);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--space-3);
|
||||
}
|
||||
|
||||
.prose {
|
||||
max-inline-size: var(--measure);
|
||||
}
|
||||
|
||||
/* grille de cartes : pilotée par le conteneur, pas par l'écran */
|
||||
.card-grid {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.card-grid > ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@container (min-width: 44rem) {
|
||||
.card-grid > ul {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: 68rem) {
|
||||
.card-grid > ul {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* Reset moderne minimal (doctrine §5.2 — ~30 lignes, rien de plus). */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
/* jamais de font-size en px sur html : respect de la taille utilisateur (doctrine §4.3) */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100dvh;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
img,
|
||||
picture,
|
||||
svg,
|
||||
video {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
p,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* tokens.css — design tokens OKI (doctrine §5.2).
|
||||
* Toute valeur visuelle du site vit ici ; les composants Svelte consomment
|
||||
* les tokens, jamais de valeur brute (couleur, taille) dans un composant.
|
||||
* Couleurs en oklch() : luminance perceptuelle maîtrisée => contraste WCAG
|
||||
* calculable (méthode modus-vivendi, doctrine §4.3).
|
||||
*/
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
|
||||
/* ── Identité panafricaine OKI ─────────────────────────────────────── */
|
||||
--oki-ink: light-dark(oklch(18% 0.02 260), oklch(94% 0.01 260));
|
||||
--oki-surface: light-dark(oklch(97% 0.005 90), oklch(14% 0.02 260));
|
||||
--oki-surface-2: light-dark(oklch(93% 0.008 90), oklch(20% 0.02 260));
|
||||
--oki-line: light-dark(oklch(85% 0.01 90), oklch(32% 0.02 260));
|
||||
--oki-ink-2: light-dark(oklch(38% 0.02 260), oklch(75% 0.01 260));
|
||||
|
||||
/* Couleurs identitaires (drapeau panafricain — jaune/vert/rouge/noir) */
|
||||
--oki-jaune: oklch(75% 0.18 85);
|
||||
--oki-vert: oklch(55% 0.18 145);
|
||||
--oki-rouge: oklch(55% 0.22 25);
|
||||
--oki-noir: oklch(15% 0.01 260);
|
||||
|
||||
/* Accent principal : le jaune OKI, décliné pour rester AA sur chaque fond */
|
||||
--accent: light-dark(oklch(52% 0.14 85), var(--oki-jaune));
|
||||
--accent-ink: light-dark(oklch(35% 0.1 85), oklch(85% 0.16 85));
|
||||
|
||||
/* ── Difficulté — jamais portée par la couleur seule (doctrine §1.1) ──
|
||||
Chaque niveau = couleur + texte + icône dans DifficultyBadge. */
|
||||
--diff-beginner: oklch(65% 0.15 145);
|
||||
--diff-intermediate: oklch(75% 0.16 85);
|
||||
--diff-advanced: oklch(55% 0.18 25);
|
||||
/* variantes texte, contrastées AA sur --oki-surface */
|
||||
--diff-beginner-ink: light-dark(oklch(42% 0.14 145), oklch(78% 0.15 145));
|
||||
--diff-intermediate-ink: light-dark(oklch(48% 0.13 85), oklch(80% 0.15 85));
|
||||
--diff-advanced-ink: light-dark(oklch(46% 0.18 25), oklch(72% 0.16 25));
|
||||
|
||||
/* ── Typographie fluide (clamp, zéro media query — doctrine §5.2) ────
|
||||
Échelle modulaire ratio 1.25, corps 16px mobile → 18px desktop. */
|
||||
--text-0: clamp(0.875rem, 0.85rem + 0.15vw, 0.9375rem);
|
||||
--text-1: clamp(1rem, 0.95rem + 0.4vw, 1.125rem);
|
||||
--text-2: clamp(1.25rem, 1.17rem + 0.5vw, 1.4rem);
|
||||
--text-3: clamp(1.56rem, 1.42rem + 0.8vw, 1.75rem);
|
||||
--text-4: clamp(1.95rem, 1.7rem + 1.4vw, 2.44rem);
|
||||
--text-5: clamp(2.44rem, 2rem + 2.4vw, 3.4rem);
|
||||
|
||||
--font-text: 'Atkinson Hyperlegible', system-ui, sans-serif;
|
||||
--font-display: 'Fraunces', var(--font-text);
|
||||
|
||||
--measure: 65ch; /* largeur de lecture 45-75ch (doctrine §2.2) */
|
||||
--leading: 1.6;
|
||||
|
||||
/* ── Espacement modulaire ────────────────────────────────────────── */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 1rem;
|
||||
--space-4: 1.5rem;
|
||||
--space-5: 2.5rem;
|
||||
--space-6: 4rem;
|
||||
|
||||
/* ── Interaction ─────────────────────────────────────────────────── */
|
||||
--tap-target: 48px; /* Fitts (doctrine §1.3) */
|
||||
--radius-1: 4px;
|
||||
--radius-2: 10px;
|
||||
--duration-1: 120ms;
|
||||
--duration-2: 240ms;
|
||||
--ease-out: cubic-bezier(0.2, 0, 0, 1);
|
||||
|
||||
--focus-ring: 3px solid var(--accent);
|
||||
}
|
||||
|
||||
@media (prefers-contrast: more) {
|
||||
:root {
|
||||
--oki-ink: light-dark(oklch(5% 0.01 260), oklch(99% 0 0));
|
||||
--oki-line: light-dark(oklch(40% 0.01 260), oklch(70% 0.01 260));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user