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

  1. L'écran
  2. Composer une palette
  3. Le magasin de thèmes
  4. La typographie
  5. Importer une palette existante
  6. Les 4 façons de consommer
  7. Brancher une application (recette complète)
  8. La chaîne Hadhinat : qui suit qui
  9. Le garde-fou de marque
  10. Exploitation, API, dépannage

1. L'écran

Gauche — le compositeurDroite — 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ésLa 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 titresRecalcul 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)

  1. Choisir le projet cible (en haut du panneau). C'est lui que les apps consommeront : …/theme.css?p=<projet>.
  2. Régler les couleurs — au sélecteur ou en tapant le code hexadécimal. Une saisie invalide est rétablie, jamais émise.
  3. Surveiller la table de contrastes : elle se recalcule à chaque mouvement. AA / AAA = conforme ; ✗ échec = à corriger.
  4. 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.

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é --gold finit 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

#ModeCe que l'app faitQuand le choisir
1Lien 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
2Rendu serveurGET /api/tokens?p=hadhinat au démarrage, puis CSS inline dans la coquilleAucune requête côté visiteur ; pas de FOUC (thème correct au 1er rendu)
3Dossier embarquébash sync-theme.sh (ou GET /export.zip?p=hadhinat) → un fichier de l'appAucun couplage réseau : l'app fonctionne même si Palette Studio est mort
4Lien 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 :

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 :

SituationRésultat
Palette absente de l'appéchec — on ne déploie pas une app sans marque
Palette présente mais périméeéchecbash sync-theme.sh puis redéployez
Source injoignable, palette embarquée présentesuccè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

  1. La plateforme bascule immédiatement (la clé active est relue à chaque requête — aucun redémarrage).
  2. Le .env est mis à jour (THEME=…) : le prochain démarrage retombera sur ce thème.
  3. La plateforme publie la palette du thème dans le projet hadhinat de la source.
  4. 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 :

SurfaceRéaction à un enregistrement dans le projet
Vitrineautomatique — elle charge le lien vivant, elle suit au prochain chargement (cache 5 min)
Plateformesur 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 :

  1. GET https://palette.amia.fr/api/tokens?p=hadhinat — récupère couleurs + police.
  2. Valide la palette (contrastes) et l'enregistre comme thème themes/hadhinat.json.
  3. L'active — la plateforme change instantanément : couleur de marque, taille de base et police.
  4. 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.sh force PALETTE_SOURCE=http://127.0.0.1:1 : sans cette ligne, un simple run-tests.sh publierait 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

RouteRô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/galleryle magasin (16 thèmes)
GET /api/projectsles projets connus
POST /api/save{ project, input, font }422 si la palette n'est pas conforme
GET /aidece 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)

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ômeCause 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 appElle consomme le fichier embarqué sans lien vivant → bash sync-theme.sh
La vitrine reste sur l'ancienne paletteCache du lien vivant (5 min), ou publication échouée → voir le message de /m/theme
POST /api/save renvoie 422Palette non conforme : la réponse liste les couples fautifs
sync-theme.sh échoueSource 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