DA
Direction Artistique

La source de vérité
du design ZLEF.

Sombre, atmosphérique, minimal. Accents olive leaf et vintage grape sur un noir profond. Une typographie toujours lisible — jamais trop petite, jamais trop grosse. Arrondis légers, composants plats et nets. Tout ce qui suit est vivant : c'est exactement le rendu à reproduire.

Fondations

Principes

Sept règles qui priment sur tout le reste. En cas de doute, importer tokens.css et puiser dans les variables plutôt qu'inventer une valeur.

Dark only
Fond noir profond #06060a. Aucun thème clair, jamais.
Deux accents
Olive leaf (primaire), vintage grape (secondaire). Le reste est neutre.
Lisible par défaut
Corps à 17px, jamais sous 16px. Interlignage 1.6, contrôles ≥44px.
Arrondis légers
Coins discrets (5px contrôles, 8px cards). Composants plats, nets, sans gradient.
Mouvement subtil
Eases doux, 120–400ms, sans rebond. Respect de prefers-reduced-motion.
Atmosphère partagée
Champ de particules + voile + vignette via assets.zlef.fr/particles.js.
Tokens

Couleurs

Deux accents profonds et désaturés — olive leaf et vintage grape — déclinés en rampes. Les teintes base servent aux fonds pleins, les soft au texte et aux liens sur fond sombre.

Surfaces

bg
#06060a
--zl-bg
surface-1
#0e0e13
--zl-surface-1
surface-2
#15151c
--zl-surface-2
surface-3
#1d1d25
--zl-surface-3

Accent primaire — Olive Leaf

olive-deep
#2c3212
--zl-olive-deep
olive
#3e4618
--zl-olive · accent
olive-mid
#59642a
--zl-olive-mid
olive-soft
#9dae50
--zl-olive-soft
olive-bright
#bdce74
--zl-olive-bright

Accent secondaire — Vintage Grape

grape-deep
#382b39
--zl-grape-deep
grape
#4c3b4d
--zl-grape · accent2
grape-mid
#6b5470
--zl-grape-mid
grape-soft
#b095b3
--zl-grape-soft
grape-bright
#cdb6cf
--zl-grape-bright

Texte

text
#e9eae2
--zl-text
text-soft
#b6b7ad
--zl-text-soft
text-muted
#7d7e76
--zl-text-muted
text-faint
#54554f
--zl-text-faint

Sémantique

success
#6f9a3a
vert feuille
warning
#c79a3e
ambre sourd
danger
#c2566a
rose-rouge
info
#b095b3
= grape, jamais bleu
Tokens

Typographie

Stack système, aucune dépendance web-font. Corps à 17px. Mono pour le code et la donnée.

4xl · 700Direction Artistique
3xl · 700Titre de section
2xl · 700Sous-titre fort
xl · 550Titre de carte
lg · 400Texte d'intro / lead
base · 400Corps de texte — la taille par défaut (17px).
sm · 400Texte secondaire, labels de champ
xs · 700 capsEyebrow / étiquette
mono · smconst accent = "#3E4618"; // olive leaf
Composants

Boutons

Plats, nets, arrondis légers. Le survol décale d'une teinte — sans halo ni gradient. .zl-btn + variantes --primary (olive), --secondary (grape), --ghost, --destructive (rose-rouge plein), --danger (contour discret), --loading (voir États). Tailles --sm / défaut / --lg. Fonctionne à l'identique sur <button> et <a> — jamais de soulignement.

Action destructive — la paire canonique

Quand l'action principale détruit quelque chose (supprimer, révoquer, purger), elle passe en --destructive : rose-rouge plein, même construction que --primary. Elle est toujours accompagnée d'un « Annuler » en --secondary (ou --ghost sur les interfaces denses). La variante --danger (contour) reste la version discrète — lignes de tableau, barres d'outils.

· ← variante discrète
<!-- copier/coller -->
<button class="zl-btn zl-btn--primary">Action principale</button>
<button class="zl-btn zl-btn--ghost zl-btn--sm">Annuler</button>

<!-- dialogue destructif : action pleine + annuler en secondaire -->
<button class="zl-btn zl-btn--destructive">Supprimer le projet</button>
<button class="zl-btn zl-btn--secondary">Annuler</button>
Composants

Champs & formulaires

Focus = bordure olive + halo doux. Enveloppe : .zl-field + .zl-label + .zl-hint.

Visible publiquement.
Adresse invalide.
Select natif — la version à garder sur iOS.

Select personnalisé — .zl-pick, partout sauf iOS

Hors iOS, le <select> natif est remplacé de bout en bout par .zl-pick — le même composant que le sélecteur de langue .zl-langpick (mêmes règles CSS dans tokens.css, le drapeau en moins) : bouton + menu stylé tokens, ✓ sur l'option choisie, clavier complet (↑/↓/Home/End/Entrée/Échap), fermeture au clic extérieur, valeur recopiée dans un input caché (ou un vrai <select>). Exception iOS : on garde le <select class="zl-select"> natif — le picker système (HIG Apple) offre une meilleure UX tactile que n'importe quel menu maison.

Plan Essayez : clic ou clavier — la valeur suit dans l'input caché.
// exception iOS : garder le <select class="zl-select"> natif (picker système, HIG Apple)
const isIOS = /iP(hone|ad|od)/.test(navigator.userAgent) ||
              (navigator.maxTouchPoints > 1 && /Mac/.test(navigator.userAgent));

// .zl-pick — comportement complet (ouverture, clavier, clic extérieur, sync)
function zlPick(root) {
  const btn  = root.querySelector('.zl-pick__btn');
  const menu = root.querySelector('.zl-pick__menu');
  const opts = [...root.querySelectorAll('.zl-pick__opt')];
  const nameEl = btn.querySelector('[data-name]');
  const input  = root.querySelector('input[type="hidden"]');  // ou un vrai <select> caché
  let hi = Math.max(0, opts.findIndex(o => o.getAttribute('aria-selected') === 'true'));

  const open   = () => { root.setAttribute('aria-expanded', 'true'); setHi(hi); menu.focus(); };
  const close  = () => { root.setAttribute('aria-expanded', 'false'); btn.focus(); };
  const isOpen = () => root.getAttribute('aria-expanded') === 'true';
  const setHi  = (i) => { hi = (i + opts.length) % opts.length;
    opts.forEach((o, k) => o.classList.toggle('is-active', k === hi));
    opts[hi].scrollIntoView({ block: 'nearest' }); };

  function select(opt) {
    opts.forEach(o => o.setAttribute('aria-selected', String(o === opt)));   // le ✓ suit aria-selected
    nameEl.textContent = opt.querySelector('.zl-pick__name').textContent;
    if (input) { input.value = opt.dataset.value;
      input.dispatchEvent(new Event('change', { bubbles: true })); }
  }

  btn.addEventListener('click', () => isOpen() ? close() : open());
  opts.forEach((o, i) => { o.addEventListener('mouseenter', () => setHi(i));
    o.addEventListener('click', () => { select(o); close(); }); });
  menu.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowDown') { e.preventDefault(); setHi(hi + 1); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); setHi(hi - 1); }
    else if (e.key === 'Home') { e.preventDefault(); setHi(0); }
    else if (e.key === 'End') { e.preventDefault(); setHi(opts.length - 1); }
    else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); select(opts[hi]); close(); }
    else if (e.key === 'Escape') { e.preventDefault(); close(); }
  });
  btn.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(); }
  });
  document.addEventListener('click', (e) => { if (isOpen() && !root.contains(e.target)) close(); });
}
if (!isIOS) document.querySelectorAll('.zl-pick').forEach(zlPick);
Mode sombre
Composants

Badges & tags

Arrondi léger (pas de pilule). .zl-badge + variantes --accent / --accent2 / sémantiques ; --dot ajoute une pastille d'état. La gamme « mood » suit le cycle de vie d'un produit ou d'une feature : --new à la sortie, --beta ouvert mais pas garanti, --wip en chantier, --draft brouillon non publié, --experimental peut disparaître, --live en production, --deprecated à quitter, --archived figé pour mémoire. Tailles --sm / défaut / --lg. --solid passe n'importe quelle variante en aplat encre sombre — réservé aux chips à haut signal, jamais en série.

Neutre Olive Grape En ligne En attente Hors ligne
Le mood du produit
New Beta WIP Draft Experimental Live Deprecated Archived
Tailles
Small Défaut Large Live Live
Intensité --solid
New Beta WIP En ligne Danger Deprecated
Composants

Alertes

Callouts en ligne : fond lavis + liseré sémantique à gauche. .zl-alert + --info (grape, jamais bleu) / --success / --warning / --danger. Le titre .zl-alert__title se place sur sa propre ligne, au-dessus du corps — comme les cards, jamais sur la même ligne. Une alerte explique quoi faire, pas seulement ce qui s'est passé.

InfoUne nouvelle version de tokens.css est disponible — rechargez la page pour l'appliquer.
EnregistréVos modifications sont en ligne.
Quota bientôt atteintIl reste 8 % du quota mensuel. Réduisez la fréquence ou augmentez la limite.
Échec de l'envoiLe serveur n'a pas répondu. Vérifiez la connexion puis réessayez.
Composants

Cards

Arrondi 8px. .zl-card avec --hover (élévation au survol) et --accent (liseré olive).

Carte standard
Surface relevée, hairline discret, ombre sombre. Survolez-moi.
Mis en avant
Carte accentuée
Liseré olive discret pour le contenu prioritaire.
Avec action
Une carte peut héberger un CTA aligné en bas.
Composants

Tableaux

Lignes en cheveu, en-têtes en étiquette, chiffres en mono tabulaire alignés à droite (.zl-num). Envelopper dans .zl-table-wrap — bord arrondi + défilement horizontal sur mobile, en-tête collant au scroll vertical.

ProjetStatutRequêtes (7 j)p95
fichedemaire.frEn ligne184 30296 ms
parole.zlef.frEn ligne42 11861 ms
essence.zlef.frDégradé9 042412 ms
montvid.zlef.frPrivé1 26088 ms
Composants

Onglets, liens & infobulles

Onglets .zl-tabs — indicateur souligné olive, jamais de fond plein. Liens de texte .zl-link, raccourcis .zl-kbd, infobulle CSS via l'attribut data-zl-tip (survol + focus clavier).

La documentation vit dans le brief LLM et les valeurs dans tokens.css — un lien discret existe aussi en variante quiet.

Recherche rapide : ⌘ K · aide contextuelle

Composants

États & chargement

Règle sitewide : rien ne doit sembler figé. Chaque action asynchrone montre un état — spinner, squelette qui reflète la mise en page finale, barre de progression. Un écran vide invite à agir. prefers-reduced-motion arrête le shimmer mais jamais le spinner (c'est le signal d'état). Les primitives se composent par cas d'usage — tableau, graphique, formulaire, galerie, pleine page, téléversement : exemples fictifs ci-dessous.

Spinner & bouton en cours

Squelette — miroir de la mise en page finale

Chargement terminé
Le squelette laisse la place au contenu réel — mêmes dimensions, aucun saut de mise en page.

Progression

Import des données64 %
Analyse en cours…

Cas d'usage — tableau en chargement

ProjetStatutRequêtes (7 j)p95

Cas d'usage — graphique en chargement

Cas d'usage — formulaire à l'envoi

Cas d'usage — galerie / grille média

Cas d'usage — chargement pleine page (transitions de route)

Chargement de la page…

Cas d'usage — téléversement

rapport-q3.pdf — téléversement…37 %

État vide — une invitation à agir

Aucun projet pour l'instant

Créez votre premier projet pour voir apparaître ses métriques ici.

Composants

Graphiques & Timelines

Un moteur SVG sans dépendance, partagé par tous les projets qui affichent des données. Importer zl-charts.css + zl-charts.js après tokens.css, puis décrire le graphique en JSON. Règles imposées : un seul axe (jamais deux échelles), palette catégorielle à ordre fixe (jamais recyclée — validée CVD sur la surface ZLEF), légende dès 2 séries, étiquettes directes parcimonieuses, grille en cheveu, tooltips par défaut (survol au bureau, tap sur mobile), vue tableau jumelle de chaque graphique, formatage i18n des nombres/dates. Les séries temporelles denses gagnent un zoom + défilement interne (axe Y figé, ⌘/Ctrl+molette ou pinch). Tous les graphiques se redessinent à la largeur réelle du conteneur : le texte garde sa taille vraie, jamais d'étirement viewBox. Thème sombre uniquement.

Chiffres — stat tiles & jauge

Graphiques — barres, lignes, aire, nuage, heatmap

Timeline — événements & périodes

Données temporelles — zoom & défilement interne

Marquer une série x.type:"time" (ou un nombre de points élevé) active auto le zoom : boutons − ⤡ +, ⌘/Ctrl + molette ou pinch pour zoomer, glisser / balayer pour défiler. L'axe Y reste figé pendant qu'on parcourt toute la plage.

Palette catégorielle — ordre fixe, assignée 1→N, jamais recyclée

slot 1
#8a9a3c
--zlc-cat-1
slot 2
#a86fb2
--zlc-cat-2
slot 3
#b8842a
--zlc-cat-3
slot 4
#cf5064
--zlc-cat-4
slot 5
#159a8b
--zlc-cat-5
slot 6
#6f7ad0
--zlc-cat-6
slot 7
#c65f37
--zlc-cat-7
slot 8
#3fa877
--zlc-cat-8
<!-- 1. importer (après tokens.css) -->
<link rel="stylesheet" href="https://da.zlef.fr/zl-charts.css">
<script defer src="https://da.zlef.fr/zl-charts.js"></script>

<!-- 2a. déclaratif (idéal en SSR) : le moteur hydrate au chargement -->
<figure class="zlc" data-zlc>
  <script type="application/json" class="zlc-spec">
    {"type":"bar","title":"Ventes",
     "x":{"values":["Nord","Sud"]},
     "series":[{"name":"2025","data":[120,90]}]}
  </script>
</figure>

// 2b. impératif
ZLC.mount(el, { type:'line', x:{ values:[2019,2020,2021] },
  series:[{ name:'Paris', data:[12.1,12.8,12.4] }], valueFmt:'number' });
Composants

Header & footer — la gamme

Quatre humeurs de header et quatre de footer, sur le même markup (__brand / __nav / .zl-navlink ; colonnes et barre __bottom côté footer). Tous sont responsive par construction — flex-wrap, grid auto-fit, min() — ils s'adaptent à la largeur de leur conteneur et ne débordent jamais, même à 360 px ; les cadres « 375 px » ci-dessous le prouvent. (Le header sticky de cette page est l'exemple live du header App.)

Header · App — .zl-header

Le chrome d'application par défaut : translucide, flou, hairline, sticky (--zl-z-nav, opt-out --static). Pour tout produit connecté ou outil du quotidien.

— contenu de page —
📱 375 px
— le nav passe à la ligne, rien ne déborde —

Header · Hero — .zl-header--hero

L'humeur marketing/landing : transparent, sans flou ni bordure, marque un cran plus grande. Posé sur un hero ou le champ de particules ; non sticky — une barre transparente n'a rien sur quoi coller.

Bêta
Le hero respire dessous.

Header · Compact — .zl-header--compact

L'humeur outil/utilitaire : fin, marque réduite, fond surface-1 plein — pas de flou, économe sur les machines modestes. Sticky comme la base. Pour consoles, dashboards, pages denses.

zlef · console admin
— interface dense —

Header · Éditorial — .zl-header--editorial

L'humeur contenu : une manchette de magazine. Marque centrée sur sa propre ligne, nav centré dessous, encadré de hairlines, padding vertical généreux. Non sticky — la manchette défile avec la page. Pour blogs, revues, longs formats.

— l'article commence ici —
📱 375 px
— le nav se recentre sur deux lignes —

Footer · Classique — .zl-footer

La ligne sobre par défaut : © à gauche, liens à droite, hairline en haut, liens qui virent à l'olive au survol. Passe à la ligne sur mobile.

— fin de page —

Footer · Colonnes — .zl-footer--columns

L'humeur plan de site : une grille auto-fit de colonnes de liens (.zl-footer__col + __col-title) qui retombe de 4 à 2 puis 1 colonne selon la largeur du conteneur, et une barre __bottom pour le © et le légal. Pour les sites riches en pages.

— fin de page —
📱 375 px

Footer · Slim — .zl-footer--slim

Une seule ligne fine, centrée, en xs — le minimum vital. Pour outils plein-écran, jeux, pages où le footer doit disparaître.

— fin de page —

Footer · CTA — .zl-footer--cta

L'humeur conversion : un bloc titre + bouton primaire (.zl-footer__cta) sur un lavis olive plat — jamais de gradient — au-dessus de la ligne classique. Pour landings et pages produit.

— fin de page —
📱 375 px
<!-- une humeur = la base + un modificateur, même markup -->
<header class="zl-header zl-header--hero">
  <div class="zl-header__brand">…</div>
  <nav class="zl-header__nav"><a class="zl-navlink">…</a></nav>
</header>

<footer class="zl-footer zl-footer--columns">
  <div class="zl-footer__col">
    <div class="zl-footer__col-title">Produit</div>
    <a href="…">Tarifs</a>
  </div>
  <div class="zl-footer__bottom">© 2026 …</div>
</footer>
Identité

Mascotte — Sylve

Sylve, la sentinelle origami low-poly de ZLEF : des plans facettés pliés dans une feuille d'olivier, assise en silence, couronne entaillée comme le wordmark et regard vintage-grape parcimonieux. Le gardien d'un écran encore sans données — média par défaut des états vides, illustrations et 404. Deux fichiers : mascot.svg (détaillé) et mascot-mini.svg (simplifié, échelle icône). SVG autonome, fond transparent, viewBox 0 0 240 200, classes olive/grape pour re-styler. Si possible (et validé), chaque produit dérive sa propre variante — même construction origami low-poly, re-thémée via les classes — plutôt que Sylve stock.

Sylve — la mascotte ZLEF
Sylve mini
Aucune donnée pour l'instant

Sylve garde l'écran vide — l'usage canonique de la mascotte : illustrer un état vide sans le rendre morne.

Usage — état vide

<!-- média d'état vide -->
<div class="zl-empty">
  <img src="https://da.zlef.fr/mascot-mini.svg" alt="Sylve" width="72">
  <div class="zl-empty__title">Rien ici pour l'instant</div>
</div>
Garde-fous

À adopter / à bannir

La liste courte qui tranche les décisions de design ZLEF.

Adopter

  • Fond #06060a + rampe de surfaces sombres.
  • Olive leaf primaire, vintage grape secondaire — et rien d'autre de saturé.
  • Arrondis légers (3–8px) ; composants plats, sans gradient ni halo.
  • Corps ≥16px (idéal 17px), interlignage généreux, contrôles ≥44px.
  • Stack de police système ; mono pour code et données.
  • Survol sobre : décalage d'une teinte, jamais de saut.
  • Mouvement subtil, eases doux, prefers-reduced-motion.
  • Variables --zl-* de tokens.css pour chaque valeur.

Bannir

  • Thème clair ou fonds blancs, sous toutes leurs formes.
  • Le bleu en accent ou en lien — utiliser l'olive. (Les séries de données zl-charts, --zlc-cat-*, sont exemptées.)
  • Le noir pur #000000 en surface.
  • Pleines pilules et gros arrondis systématiques — arrondis légers (3–8px), les cercles restent des cercles.
  • L'esthétique « générée par IA » : gradients sur boutons, halos colorés, zoom au survol. Plat, net, une teinte de décalage.
  • Texte sous 16px, gris-sur-gris peu contrasté, hairline fin illisible.
  • Dépendances web-font.
  • Dégradés brillants, skeuomorphisme, mouvements rebondissants.
  • Défauts bruts de framework (Bootstrap/Material) ignorant ces tokens.
Internationalisation

Langues & sélecteur

Chaque projet est i18n : dictionnaires de chaînes par clé, anglais (défaut, fallback) + français au minimum. Le sélecteur de langue est conditionnel — ne l'affiche pas systématiquement.

2 langues (en + fr)
Cas par défaut : aucun sélecteur. La langue est choisie en silence d'après le navigateur (navigator.language / Accept-Language), anglais en repli. Un navigateur FR obtient le français, les autres l'anglais — sans aucun chrome.
3 langues ou plus
Un unique sélecteur global : un menu déroulant dont chaque option est drapeau + nom natif (endonyme). Pas de drapeaux seuls, pas de noms anglais — l'emoji drapeau puis le nom de la langue.
Mémoire sitewide
Le choix suit le visiteur sur tout *.zlef.fr. Cookie zl-lang sur le domaine .zlef.fr (path /, ~1 an) — pas de localStorage (par-origine, ne partage pas entre sous-domaines).

Le sélecteur (3+ langues) — composant .zl-langpick, livré dans tokens.css

// résolution de la langue au chargement (ordre de priorité)
const read = () => (document.cookie.match(/(?:^|; )zl-lang=([^;]+)/)||[])[1];
let lang = read() || navigator.language.slice(0,2) || 'en';
if (!LOCALES.includes(lang)) lang = 'en';  // fallback anglais

// au changement : cookie partagé pour TOUS les sites zlef.fr
function setLang(l) {
  document.cookie = `zl-lang=${l}; domain=.zlef.fr; path=/; max-age=31536000; samesite=lax`;
  apply(l);
}
Pour la machine

Ressources & intégration

Le design system est aussi exposé de façon programmatique pour les agents et les builds.

<!-- intégrer le design ZLEF dans n'importe quel projet -->
<link rel="stylesheet" href="https://da.zlef.fr/tokens.css">
<script src="https://assets.zlef.fr/particles.js"></script>  <!-- champ atmosphérique -->

<body class="zl-body">
  <button class="zl-btn zl-btn--primary">Go</button>
</body>