Palette Studio — palette.amia.fr
Auteur : Dr Hamid MADANI <drmdh@msn.com> · Licence : AGPL-3.0-or-later Rôle : la source unique des palettes de l'écosystème. On y compose, l'outil dérive et valide, les applications consomment.
Un sélecteur de couleur ne sait pas dire non. Celui-ci le sait. On ne saisit pas 19 couleurs : on en donne 10 (3 de marque, 4 d'état, 3 surfaces) et 3 réglages de police. Les 31 tokens restants — textes, filets, contours de champ, survols, anneau de focus, fonds d'alerte — sont dérivés, puis validés (WCAG 2.1). Une couleur sous son seuil est corrigée, jamais émise telle quelle.
Table des matières
- L'écran
- Composer une palette
- Le magasin de thèmes
- La typographie
- Importer une palette existante
- Les 4 façons de consommer
- Brancher une application (recette complète)
- La chaîne Hadhinat : qui suit qui
- Le garde-fou de marque
- Exploitation, API, dépannage
1. L'écran
| Gauche — le compositeur | Droite — les aperçus |
|---|---|
Projet cible : l'app qui consommera cette palette (hadhinat, atc, salsabil, ou un nouveau) | 💻 Bureau 1280 px et 📱 Mobile 390 px, côte à côte |
| Magasin : 16 thèmes prêts (5 préréglages MostaJS + l'état de l'art) | De vraies iframes aux largeurs réelles : les media queries s'appliquent pour de bon |
| 10 sélecteurs de couleur → 31 tokens dérivés | La planche des 12 sections du système de design : palette, typo, boutons, champs, cartes, alertes, nav, tableaux, badges, icônes, divers, espacements |
| Typographie : police, taille de base, échelle des titres | Recalcul instantané à chaque mouvement |
| Table de contrastes : les 16 couples exigés, avec leur verdict AA/AAA | |
| Export : copier · télécharger · dossier ZIP · importer |
2. Composer une palette (manipulation)
- Choisir le projet cible (en haut du panneau). C'est lui que les apps consommeront :
…/theme.css?p=<projet>. - Régler les couleurs — au sélecteur ou en tapant le code hexadécimal. Une saisie invalide est rétablie, jamais émise.
- Surveiller la table de contrastes : elle se recalcule à chaque mouvement.
AA/AAA= conforme ;✗ échec= à corriger. - Enregistrer. Une palette non conforme est refusée (422) — on ne peut pas publier une interface illisible.
Créer un projet : sélecteur → + Nouveau projet… → nom en minuscules/chiffres/tirets → Enregistrer.
Permalien (utile pour montrer une variante sans rien enregistrer) : https://palette.amia.fr/?p=hadhinat&brand=%237C3AED&base=16&family=arabic
3. Le magasin de thèmes
Cliquer un thème charge ses couleurs de marque dans les sélecteurs — puis tout est re-dérivé et revalidé. C'est un point de départ, pas un calque.
- Préréglages MostaJS : Starter, Jakarta, Blender, Évasion, Touch&Go, Hadhinat (les 5 premiers viennent de l'ancien sélecteur 🎨 de
market.amia.fr, désormais supprimé). - État de l'art : Tailwind, Material 3, IBM Carbon, GOV.UK, Atlassian, Shopify Polaris, Ant Design, Bootstrap 5, Nord, Solarized.
L'enseignement du magasin (mesuré, cf.docs/01-ETAT-ART-THEMES.md) : 13 accents sur 16 sont illisibles en texte sur blanc — le jaune GOV.UK est à 1,3:1. Ce n'est pas une faute de ces systèmes : un accent n'est pas fait pour porter du texte. Le problème n'apparaît que quand un token nommé--goldfinit en couleur de texte. D'où la règle : nommer par la fonction (--text,--focus-ring) et dériver. Les 16 thèmes produisent une palette intégralement conforme.
API : GET /api/gallery (CORS) — pour proposer le même magasin dans une autre app.
4. La typographie
Trois réglages seulement — police, taille de base (11→20 px), échelle des titres (compacte 1,15 · standard 1,23 · ample 1,33). Les 7 niveaux (H1 → légende) sont calculés : impossible d'obtenir un H2 plus petit qu'un H3.
Avec 14 px et l'échelle standard, on retrouve le système de design : 32 / 24 / 20 / 17 / 14 / 12 / 11. Vers le bas, l'échelle est linéaire et plancherée à 11 px (une descente modulaire donnerait une légende à 9 px, illisible).
Polices proposées : Système (Inter) · Grotesque · Serif · Monospace · Arabe (Cairo/Noto).
5. Importer une palette (coller ou téléverser)
Panneau « Importer une palette » : coller un CSS exporté ou un JSON de tokens, ou téléverser le fichier. Ce que l'app exporte, elle sait le relire (round-trip complet).
⚠ On ne lit que les codes hexadécimaux. Un CSS contenant background: url(https://attaquant/…) voit sa règle écartée — seuls les tokens sont retenus. C'est ce qui rend l'import sûr : on ne stocke jamais du CSS, on stocke des tokens, et le CSS est régénéré.
6. Les 4 façons pour une app de consommer une palette
| # | Mode | Ce que l'app fait | Quand le choisir |
|---|---|---|---|
| 1 | Lien vivant (CSS) | <link rel="stylesheet" href="https://palette.amia.fr/theme.css?p=hadhinat"> | La palette change ici → l'app suit au prochain chargement. Cache : 5 min |
| 2 | Rendu serveur | GET /api/tokens?p=hadhinat au démarrage, puis CSS inline dans la coquille | Aucune requête côté visiteur ; pas de FOUC (thème correct au 1er rendu) |
| 3 | Dossier embarqué | bash sync-theme.sh (ou GET /export.zip?p=hadhinat) → un fichier de l'app | Aucun couplage réseau : l'app fonctionne même si Palette Studio est mort |
| 4 | Lien vivant CLIENT (façon analytics a.js) | <script src="https://palette.amia.fr/follow.js?p=hadhinat" defer></script> | Mise à jour LIVE sans rechargement : l'onglet ouvert suit les changements tout seul. Marche sur tout site, même statique |
Le mode 4 en détail. Une seule ligne <script> dans le <head> : le navigateur va chercher /theme.css?p=<projet>, injecte un <style> gardé en dernier (il surcharge donc le thème de la coquille), puis re-sonde périodiquement (?ttl=<secondes>, défaut 15 s) — tu édites la palette, l'onglet déjà ouvert change tout seul, sans reload ni redéploiement. Réglable : …/follow.js?p=hadhinat&ttl=30.
Recommandé — combiner les modes selon le besoin :
- 3 + 1 : fichier embarqué (filet anti-panne) + lien vivant CSS par-dessus. C'est ce que fait la vitrine (§8) : si la source répond, elle gagne ; sinon, le filet reste.
- 2 + 4 : rendu serveur pour un 1er affichage correct (pas de FOUC) +
follow.jspour la mise à jour live pendant que tu règles la palette. C'est ce que fontamia.fretdocme.amia.fr.
La propagation est automatique, et la panne est sans effet.
7. Brancher une application (recette complète)
Deux fichiers à copier, et rien d'autre — la logique vit dans le CLI de @mostajs/palette.
cd mon-app
cp <une-app-déjà-branchée>/sync-theme.sh . # IDENTIQUE partout : il remonte l'arborescence
# jusqu'à trouver mostajs/ (aucun ../../.. codé en dur)
cp <une-app-déjà-branchée>/theme.config.mjs . # le SEUL fichier à adapter
theme.config.mjs :
export default {
source: process.env.PALETTE_SOURCE || 'https://palette.amia.fr',
project: 'hadhinat', // le projet à consommer
css: 'public/theme.css', // où écrire la palette
json: 'public/tokens.json', // (optionnel) les mêmes valeurs pour le code
};
Puis :
bash sync-theme.sh # récupère la palette, vérifie les 16 contrastes, écrit les fichiers
bash sync-theme.sh --check # porte de déploiement : l'app est-elle à jour ?
Comportement de --check — et c'est ce qui garantit qu'une panne ne bloque personne :
| Situation | Résultat |
|---|---|
| Palette absente de l'app | ❌ échec — on ne déploie pas une app sans marque |
| Palette présente mais périmée | ❌ échec — bash sync-theme.sh puis redéployez |
| Source injoignable, palette embarquée présente | ✅ succès + avertissement — on déploie quand même |
Enfin, charger le CSS avant les styles de l'app, et utiliser les tokens au lieu de couleurs en dur : var(--brand) · var(--text) · var(--text-muted) · var(--line) · var(--field-border) · var(--focus-ring) · var(--radius-md) · var(--space-2) · var(--font-h1-size)…
Autres commandes du CLI :
node <…>/mosta-palette/bin/cli.mjs show --project atc # tokens + table de contrastes
node <…>/mosta-palette/bin/cli.mjs local # composer HORS LIGNE (sans source)
8. La chaîne Hadhinat : qui suit qui
La propagation va dans les DEUX sens. On peut composer indifféremment sur Palette Studio ou sur la plateforme — c'est le même résultat, et rien n'est jamais ignoré.
┌──────────────────────── palette.amia.fr (LA SOURCE) ────────────────────────┐
│ projet « hadhinat » : 10 couleurs + 3 réglages de police, VALIDÉS │
└───▲───────────────┬───────────────────────────────────┬────────────────────┘
① publie │ ② tire │ │ sert (lien vivant)
┌────────┴───────────────▼────┐ ┌─────────────▼──────────────────┐
│ market.amia.fr │ │ hadhinat.amia.fr (vitrine) │
│ /m/theme (admin) │ │ 1. theme.css EMBARQUÉ (filet) │
│ · themes/*.json │ │ 2. <link> vivant PAR-DESSUS │
│ · bascule INSTANTANÉE │ │ …/theme.css?p=hadhinat │
└─────────────────────────────┘ └────────────────────────────────┘
① Vous activez un thème sur market.amia.fr/m/theme
- La plateforme bascule immédiatement (la clé active est relue à chaque requête — aucun redémarrage).
- Le
.envest mis à jour (THEME=…) : le prochain démarrage retombera sur ce thème. - La plateforme publie la palette du thème dans le projet
hadhinatde la source. - La vitrine suit toute seule au prochain chargement, via son lien vivant. Aucun redéploiement.
Si la source est hors ligne : l'étape 3 échoue, la page le dit franchement (« activé ici, mais NON publié »), la plateforme bascule quand même, et la vitrine garde sa palette embarquée. On ne bloque jamais l'admin d'une app parce qu'une autre est en panne. Le garde-fou (§9) signale la divergence tant qu'elle dure.
② Vous composez sur palette.amia.fr — le bouton de tirage
Composer ici ne suffit pas à changer la plateforme : elle sert son thème actif, pas la palette du projet. (C'est ce qui donnait l'impression que « rien ne changeait ».) Deux surfaces, deux comportements :
| Surface | Réaction à un enregistrement dans le projet |
|---|---|
| Vitrine | automatique — elle charge le lien vivant, elle suit au prochain chargement (cache 5 min) |
| Plateforme | sur décision — un admin clique le bouton de tirage |
Le bouton — sur market.amia.fr/m/theme, en tête de la liste :
⇩ Récupérer la palette du projet « hadhinat » depuis Palette Studio
Ce qu'il fait, dans l'ordre :
GET https://palette.amia.fr/api/tokens?p=hadhinat— récupère couleurs + police.- Valide la palette (contrastes) et l'enregistre comme thème
themes/hadhinat.json. - L'active — la plateforme change instantanément : couleur de marque, taille de base et police.
- Met à jour le
.env, comme toute activation.
Si la source est injoignable : le tirage est refusé proprement et le thème actif n'est pas touché — on ne dégrade jamais une app en production parce qu'une autre ne répond pas.
(Ce comportement est délibéré : la plateforme est une app métier en production. Elle ne doit pas changer d'apparence parce que quelqu'un a enregistré un essai dans la source — un humain valide. La vitrine, elle, est une page de communication : le suivi automatique y est le bon compromis.)
Rafraîchir le filet embarqué de la vitrine
À faire de temps en temps (pas à chaque changement : le lien vivant suffit au quotidien) — c'est le filet qui protège d'une panne de la source :
cd incubator/animation/animation-v06/hadhinat_script_voix_multilang_html
bash sync-theme.sh
rsync -a index.html theme.css hmd@amia.fr:/home/hmd/prod/hadhinat/
git add theme.css && git commit -m "theme: palette mise à jour"
9. Le garde-fou de marque
cd incubator && bash check-brand.sh # 0 = cohérent · 1 = divergence · 2 = site injoignable
Il compare la marque servie par les trois surfaces : plateforme, vitrine, projet. C'est arrivé le 2026-07-14 — la plateforme en Material 3, la vitrine en navy : un visiteur qui cliquait « Rejoindre » changeait d'univers. À lancer après tout changement de thème (et il est documenté au §6.bis du RUNBOOK.md).
⚠ Un test ne doit JAMAIS écrire dans la production.app/test-scripts/themes.shforcePALETTE_SOURCE=http://127.0.0.1:1: sans cette ligne, un simplerun-tests.shpublierait vers la vraie Palette Studio et repeindrait la vitrine publique. (Ça a failli arriver.)
10. Exploitation
💡 L'aide en ligne, c'est ce document. Le bouton « ? Aide » (en haut du compositeur) sert ce README même, rendu à la volée sur https://palette.amia.fr/aide et habillé par le thème du projet. Il n'y a donc qu'une seule source de documentation : elle ne peut pas se désynchroniser de l'app.
API
| Route | Rôle |
|---|---|
GET / | le compositeur (?p=<projet> + permaliens de couleurs/police) |
GET /theme.css?p=<projet> | le lien vivant (CORS, cache 5 min) |
GET /api/tokens?p=<projet> | { project, input, font, tokens, scales, report, css } (CORS) |
GET /export.zip?p=<projet> | palette.css + tokens.json + INTEGRATION.md |
GET /api/gallery | le magasin (16 thèmes) |
GET /api/projects | les projets connus |
POST /api/save | { project, input, font } → 422 si la palette n'est pas conforme |
GET /aide | ce README, rendu dans l'app (@mostajs/markdown-html) — une seule source de doc |
GET /api/health | { ok, service, persistence, projects } |
Persistance
ORM @mostajs/orm, dialecte sqljs (SQLite WASM : aucun binaire natif à compiler — cf. le piège mediasoup du RUNBOOK). Base : data/palettes.db. Repli fichier (data/palettes.json) si l'ORM est indisponible : cette app est la source des palettes de tout l'écosystème, une base en panne la dégrade, elle ne la tue pas. GET /api/health dit laquelle est active.
Changer de SGBD : DB_DIALECT + SGBD_URI (19 dialectes supportés).
Commandes
node server.mjs # local (PORT=4800)
bash test-scripts/run-tests.sh # porte qualité : 41 + 24 + 47 = 112 tests
bash deploy.sh --check # vendor + tests, SANS déployer
bash deploy.sh # + rsync + pm2 + health
sudo bash setup-palette.sh # (une fois, en root) vhost Apache + HTTPS
pm2 palette-studio sur 127.0.0.1:4800, Apache en reverse-proxy + Let's Encrypt. data/ n'est pas transféré au déploiement : la base de production est préservée.
Architecture (DEVRULES §10 — composition)
@mostajs/palette(kernel-stack) — le calcul : contraste, dérivation, typographie, magasin, audit, CSS, CLI de consommation. 41 tests.@mostajs/palette-ui(ui-html-stack) — le rendu : compositeur, planche, aperçus. 24 tests.- cette app — routes, persistance, ZIP. 47 tests.
Les deux modules sont de l'ESM pur sans dépendance : le même code produit le premier rendu (serveur) et le recalcul (navigateur). Zéro build, zéro framework.
Dépannage
| Symptôme | Cause probable |
|---|---|
| J'ai composé ici, la plateforme n'a pas bougé | Normal : elle sert son thème actif. → cliquer ⇩ Récupérer la palette du projet sur /m/theme (§8 ②) |
| Le thème ne change pas dans une autre app | Elle consomme le fichier embarqué sans lien vivant → bash sync-theme.sh |
| La vitrine reste sur l'ancienne palette | Cache du lien vivant (5 min), ou publication échouée → voir le message de /m/theme |
POST /api/save renvoie 422 | Palette non conforme : la réponse liste les couples fautifs |
sync-theme.sh échoue | Source injoignable — aucun fichier n'est écrit (on ne devine pas une palette) |
Conception : docs/00-CARTOGRAPHIE-ET-PROPOSITION.md · État de l'art : docs/01-ETAT-ART-THEMES.md · Exploitation générale : ../RUNBOOK.md