La marque LN
Noir ou blanc, jamais autre chose. Ni bleu marine, ni teinte, jamais sur une pastille de couleur.
00 Référence vivante
Le fond de cette page est celui du site — même canvas, même repli CSS, même grain, même barre et même pied de page. Il tourne ici sur aurora, la nuit en réserve, que son contraste réserve aux fonds sans texte courant. La section champ animé la fait passer dans les autres mondes.
0 · Lire ce document
1. Le sombre est le défaut.
Blanc sur nuit bleue, partout. Une section n'écrit rien pour être dans le
ton — elle hérite. Elle pose un data-theme
uniquement pour en sortir, et l'attribut peint alors le fond et le texte
tout seul. La PAGE, elle, peut traverser jusqu'à cinq mondes du haut au
bas — voir la traversée.
2. Une seule classe de mise en page.
.layout-wrapper tient le cadre
de la page ET la grille : une colonne sur téléphone, douze de
md à lg,
vingt-quatre à partir de xl. La marge
est une COLONNE, la première et la dernière : le contenu vit entre les deux,
et un enfant qui ne déclare rien les occupe toutes. La gouttière est nulle,
donc l'air entre deux blocs est une colonne sautée — voir
la gouttière.
3. Les jetons, pas les variables.
Une couleur ou une taille absente des jetons échoue au build. En revanche,
les espacements, les rayons et les durées n'ont PAS de jeton : Tailwind les
fournit déjà — p-8,
rounded-2xl,
duration-150. Rien de tout cela
ne s'écrit en var() dans le HTML.
Où vivent les choses
.layout-wrapper et l'inspecteur de grille.section-y, measure,
eyebrow, grain, les dégradés.
.prose-rich..field, et les encarts d'erreur.Noir ou blanc, jamais autre chose. Ni bleu marine, ni teinte, jamais sur une pastille de couleur.
La marque et le mot, approche 0.16em. Le mot est en Nimbus 700.
<x-ui.wordmark />
<x-ui.wordmark tone="ink" size="sm" :name="false" />
Nimbus Sans étendu pour les titres et tout ce qui est cliquable, Söhne pour le texte courant.
font-display · NimbusSansDOT
Aa Gg 01 — Titre de démonstration
300 · 400 · 700
font-body · Söhne
Aa Gg 01 — Paragraphe de démonstration
400 · 600 · 700
font-mono
Aa Gg 01 — SFMono, Menlo, Monaco
Les héros. Poids 300, approche −0.02em. La plus grande est pour un titre qui n'a rien au-dessus de lui, la feuille de contact, qui n'a pas de titre de page pour la présenter.
Texte display
text-display-lg · clamp(40px, 5.95vw + 14.3px, 100px)
Texte display
text-display · clamp(44px, 4vw + 26px, 88px)
Texte display
text-display-sm · clamp(28px, 1.6vw + 20px, 44px)
Tout titre est en poids 400. Une seule ligne de clamp dit le minimum, la pente et le maximum.
H1 — Titre de niveau 1
text-h1 · clamp(36px, 3vw + 18px, 72px)
H2 — Titre de niveau 2
text-h2 · clamp(24px, 1.2vw + 16px, 40px)
H3 — Titre de niveau 3
text-h3 · clamp(20px, 0.4vw + 17px, 24px)
Söhne, interligne 1.65, mesure de 56 caractères avec l'utilitaire .measure.
Ceci est un paragraphe d'introduction de démonstration. Il sert à mettre en avant le message principal de la section.
text-lead · 18px
Ceci est un paragraphe de texte courant. Il sert à illustrer la taille, l'interligne et la lisibilité par défaut du contenu.
text-body · 16px
Ceci est un exemple de petit texte utilisé pour les légendes ou les mentions discrètes.
text-md · 14px
Le surtitre en capitales, et la taille partagée par tout ce qui est cliquable.
Libellé générique
.eyebrow · 0.75rem · approche 0.18em · poids 400
Un utilitaire, pas un composant : il se pose sur un
<h2> ou un
<dt> qui existe déjà.
Libellé d'action
text-md · 14px · Nimbus 700
Une seule taille pour le bouton et le lien fléché : posés côte à côte, ils doivent se lire au même niveau.
<h2 class="eyebrow">Libellé générique</h2>
Le noir radical, puis la descente vers les bleus de nuit.
black
#000000
night
#080817
ink
#101828
ink-soft
#171720
Le bleu électrique est réservé à l'unique action de conversion. Les autres portent les liens.
blue
#5243ff
blue-deep
#0047b9
periwinkle
#9bb6ed
mist
#d0d5dd
Le thème chaud — le seul ajout à la palette d'origine, qui était noire, blanche et bleue.
ember
#0b0603
rust
#7c2d12
flare
#f97316
amber
#fbbf24
Gris froids. Papier, filets, textes discrets.
white
#ffffff
paper
#f3f3f3
slate
#344054
slate-mid
#525d6e
slate-soft
#667085
Livrés, mais rares. Un site d'agence n'a presque jamais à dire « erreur ».
success-light
#ecfdf3
success
#039855
success-dark
#027a48
danger
#b42318
Chaque monde redéfinit les mêmes onze variables. Un composant posé dedans n'a rien à savoir. Montré ici sur les trois mondes de contrôle — un sombre froid, un sombre chaud, le clair.
data-theme="night"
Aa
Nuit — Le défaut du site — blanc sur nuit bleue
data-theme="ember"
Aa
Braises — Le sombre chaud — les braises
data-theme="paper"
Aa
Papier — Le clair — pour une section de lecture
Rien à écrire : la section hérite du thème sombre par défaut.
<section class="section-y">
Pour en sortir, un attribut suffit — il peint le fond et le texte.
<section data-theme="paper" class="section-y">
Trois traits, trois métiers. Un filet sépare, un séparateur interne se devine, un contour de commande désigne une cible cliquable.
Nuit — Le défaut du site — blanc sur nuit bleue
border-line
le trait qu'on voit
border-line-soft
entre deux lignes d'une liste
le bord d'une commande
Braises — Le sombre chaud — les braises
border-line
le trait qu'on voit
border-line-soft
entre deux lignes d'une liste
le bord d'une commande
Papier — Le clair — pour une section de lecture
border-line
le trait qu'on voit
border-line-soft
entre deux lignes d'une liste
le bord d'une commande
Le filet du monde clair valait #e4e7ec, soit
1,12:1 contre sa propre surface, là où les mondes
sombres tenaient 2,39:1 — sept fois plus faible. Il est désormais de l'ENCRE À OPACITÉ,
comme les mondes sombres sont du BLANC à opacité : le filet d'un monde se fait avec la
couleur de son texte, jamais avec une teinte fixe qui ne sait pas où elle est posée.
Les trois mondes mesurent maintenant 2,36 à 2,39:1.
⚠️ Ne jamais écrire
border-line/40. Le jeton porte déjà son
opacité ; la redoubler au point d'appel donnait 1,29:1 sur du sombre — par chance — et
1,04:1 sur du clair, c'est-à-dire rien. C'est ce que
border-line-soft remplace.
border-control vaut le filet dans les mondes
sombres et le pervenche #9bb6ed sur le papier, où
le survol le fait basculer sur l'encre des titres. Mesuré, ce pervenche vaut
1,84:1 sur
#f3f3f3, sous les 3:1 que les WCAG 1.4.11
demandent au contour d'une commande : c'est un choix assumé, l'anneau de focus et le
libellé portant, eux, le contraste. #6382c4
donnerait 3,43:1.
Une page ne vit pas forcément dans un seul monde : elle en traverse jusqu'à cinq, du haut au bas, et le fond animé fait le même chemin.
Un thème posé sur une section est une BASCULE — la section en sort, le
reste du document n'en sait rien. La traversée, elle, est CONTINUE :
les huit variables sont interpolées entre les arrêts au fil de la lecture
et écrites sur <html>, d'où
tout le site en hérite. Le fond WebGL lit la même liste au même rythme.
page_themes que pour en
sortir.
data-theme sur une section
reprend la main sur son sous-arbre, traversée ou pas. C'est ce qui
permet à la section claire des solutions de vivre au milieu d'une
page de nuit.
# Le front matter de l'entrée. Un à cinq mondes, dans l'ordre.
page_themes:
- night
- ember
# Rien du tout = le défaut du site, qui dit exactement la même chose.
# Une page claire de bout en bout :
page_themes:
- paper
# Trois arrêts : la page passe par la nuit à mi-hauteur.
page_themes:
- ember
- night
- ember
# Cinq, le maximum : les arrêts se répartissent sur la hauteur du document.
page_themes:
- night
- abyss
- verdigris
- plum
- ember
Un par monde, même nom. Ce sont les replis CSS du champ animé : impression, e-mail, absence de WebGL.
gradient-dusk
--gradient-dusk
5,29:1
gradient-midnight
--gradient-midnight
7,39:1
gradient-abyss
--gradient-abyss
5,29:1
gradient-glacier
--gradient-glacier
5,31:1
gradient-orchid
--gradient-orchid
5,29:1
gradient-aurora
--gradient-aurora
2,58:1
⚠ Lueur — arête claire, sous AA
gradient-tide
--gradient-tide
5,27:1
gradient-verdigris
--gradient-verdigris
5,27:1
gradient-jade
--gradient-jade
5,27:1
gradient-ember
--gradient-ember
5,32:1
gradient-gold
--gradient-gold
2,58:1
⚠ Lueur — arête claire, sous AA
gradient-dawn
--gradient-dawn
5,14:1
Le chiffre est le contraste du pixel le plus clair que la composition peut produire — un quart de bande, trois quarts d'arête, plus le tramage — et non celui des couleurs prises séparément, qui passent toutes. C'est le seul qui dise si un monde peut porter du texte blanc. Les mondes AA tombent tous sur le même point, à quatre centièmes près : celui de la nuit. Minuit vise 7,4:1, soit AAA. Et les six de la famille à arête CLAIRE — Aurora et Or — tombent, eux, sur le point d'aurora : 2,58:1, sous AA. C'est ce qui fait leur beauté et ce qu'elle coûte.
⚠️ L'alpha de l'arête est calculé, pas choisi. Il
pose le centre de la radiale à la luminance de ce pixel. À
0.55, la valeur d'avant, le repli rendait 9,6:1 là
où le champ rend 5,3 — deux fois trop sombre, donc une autre image que celle qu'il
remplace. La géométrie des quatre couches est la même partout, pour la même raison :
le shader compose la même bande quel que soit le monde.
Douze, groupés par COULEUR en quatre familles. Dix tiennent AA sous du texte blanc et se calibrent sur le pixel de la nuit ; Aurora et Or portent une arête CLAIRE en bas à droite et se calibrent sur celui d'aurora, à 2,58:1.
Bleu
Nuit
data-theme="0"
Le défaut du site — blanc sur nuit bleue
Minuit (AAA)
data-theme="1"
La nuit en plus profond — AAA, pour un texte dense
Abysse
data-theme="2"
L'océan — blanc sur bleu de grand fond
Glacier
data-theme="3"
La glace — un bleu qui tire vers le cyan
Orchidée
data-theme="4"
La fleur — du magenta au bleu, dans ce sens
Aurora (2,6:1)
data-theme="5"
La référence — bleu de marque, arête pervenche
Vert
Marée
data-theme="0"
Le large — du bleu profond au vert d’eau
Vert-de-gris
data-theme="1"
Le cuivre oxydé — un bleu qui a viré au jade
Jade
data-theme="2"
La pierre — un vert franc, le plus vif des verts
Chaud
Braises
data-theme="0"
Le sombre chaud — les braises
Or (2,6:1)
data-theme="1"
L'or franc — brume de bronze, lueur de crème
Clair
Papier
data-theme="0"
Le clair — pour une section de lecture
# Poser un monde sur une page — dans son front matter, comme les autres.
page_themes:
- abyss
# Ou en traversée, exactement comme le site fait nuit → braises.
page_themes:
- abyss
- plum
Le vrai fond de la marque : un canvas WebGL dont la bande ondule. Ce n'est pas une vignette — c'est le fond de CETTE page, et les boutons le font changer de monde.
Le repli CSS du monde courant, grain compris. Le canvas, lui, est derrière toute la page — regardez au-dessus du titre.
Un seul monde
Une traversée — puis défilez
Les boutons réécrivent data-page-themes
sur l'hôte du champ, puis émettent un événement que
site.js écoute. Le fond ET le texte
sont relus depuis le même attribut, exactement comme au chargement d'une
page : c'est le chemin de code réel qui est exercé ici, pas un second
écrit pour la démonstration.
Le CURSEUR répond tout de suite — le halo s'étire dans le sens du geste et la matière du fond se réfracte autour de lui. Trois effets, dosés pour se voir du coin de l'œil. Aucun au clic : un fond qui ondule pendant qu'on remplit un formulaire distrait au lieu de séduire.
Sans WebGL, le canvas ne se monte pas et le dégradé CSS reste seul — la
page ne casse pas, elle perd son ondulation. Sous
prefers-reduced-motion, une
seule image est rendue et le curseur n'est plus suivi.
La signature tactile. Texture argentique en fondu soft-light, qui saute huit fois par seconde comme une émulsion. Sur les dégradés comme sur les fonds pleins.
Deux niveaux pour une page d'information, trois pour une page de service. L'accueil est toujours le premier pas et n'est pas paramétrable.
night
ember
paper
Il vivait en dur dans l'ouverture des pages d'information, et n'existait donc que là. Les pages de service sont pourtant plus profondes qu'elles.
⚠️ Ses paramètres sont préfixés, contrairement aux
cinq composants d'UI : il est appelé depuis des gabarits d'ENTRÉE, où
title, url
et parent sont des champs de Statamic qui
écraseraient un paramètre de même nom, en silence.
Sa couleur est posée sur ses ITEMS et non sur sa liste :
.eyebrow porte la typographie et
--muted, or ce fil vit souvent sur le champ
animé. Deux éléments différents, aucun duel d'utilitaires.
<x-ui.breadcrumb crumb_current="Page courante" />
<x-ui.breadcrumb crumb_section="Section parente" crumb_section_url="/section" crumb_current="Sous-page courante" />
{{ partial:components/ui/breadcrumb crumb_section="Section parente" crumb_section_url="/section" :crumb_current="title" }}
La pilule pleine est l'unique action de conversion de la page. S'il y en a deux, il n'y en a plus aucune.
night
ember
paper
<x-ui.button href="/contact">Bouton principal</x-ui.button>
<x-ui.button variant="outline" size="sm" href="/projets">Bouton secondaire</x-ui.button>
{{ partial:components/ui/button variant="outline" label="Bouton secondaire" }}
Trois filets, qui disent trois choses. Flèche droite pour avancer dans le site, diagonale pour en sortir.
<x-ui.arrow-link href="/lien" rule="sweep">Lien par défaut</x-ui.arrow-link>
<x-ui.arrow-link href="https://…" arrow="diagonal" rule="none">Lien externe</x-ui.arrow-link>
Bascule de langue, compteurs, étiquettes. La puce pleine s'inverse par relative à son thème.
night
Tag Badge 1 / 6 — Catégorieember
Tag Badge 1 / 6 — Catégoriepaper
Tag Badge 1 / 6 — CatégorieUn seul style pour input, select et textarea. Le libellé est en Nimbus gras 14px — c'est une étiquette d'interface, au même titre qu'un bouton ; la saisie reste en Söhne, parce qu'un message de cinq lignes est du texte courant.
night
2 champs à revoir
ember
2 champs à revoir
paper
2 champs à revoir
Ceux de Tailwind, sans jeton maison. Rien n'a d'angle vif : le plus petit rayon employé est rounded-md, 6px.
rounded-full
rounded-3xl
rounded-2xl
rounded-xl
rounded-md
Trois, et pas une de plus. Toute autre ombre est une erreur.
shadow-media
Sous une image en cadre
shadow-card
Sous une carte
shadow-pill
Le halo du bouton de conversion
Une seule courbe pour tout le site. Les durées sont celles de Tailwind, écrites en clair.
Il n'y a pas de jeton de durée, et il n'y en aura pas : une classe
duration-150 se lit et se change sur
place, là où duration-[var(--dur-fast)]
obligeait à ouvrir un autre fichier pour savoir de combien de millisecondes on
parlait. Sous
prefers-reduced-motion, toutes les
transitions tombent à 0.01ms — la règle vit dans
base.css.
Survol, appui, focus. Passez la souris, puis refaites-le au clavier.
active:scale-[0.985], sans changement de couleur.
css/base.css — aucun composant ne le redéfinit, aucun
ne le supprime.
Un état caché posé par le CSS, levé par GSAP. Sans script, rien ne se cache.
opacity: 0 et remonte de quelques
pixels. À poser sur le bloc lui-même, jamais sur chacun de ses enfants — c'est le décalage
de la chronologie qui fait le rythme, pas la multiplication des cibles.
overflow-hidden,
la ligne porte son rembourrage de jambages en em :
les deux hauteurs restent égales, et la translation vide toujours le masque.
Les deux règles vivent dans base.css, sous
html.js — la classe que le gabarit pose dans
l'en-tête. Sans JavaScript elle n'existe pas, donc rien ne se cache et la page reste
entière. Sous prefers-reduced-motion, l'état
caché est annulé des DEUX côtés : le CSS relève l'opacité et
gsap.matchMedia() n'ouvre aucune chronologie.
Ce qui n'est pas animé ne doit jamais être caché.
Le même .layout-wrapper porte le cadre de la page et ses colonnes. Une seule variable dit combien : 1, puis 12, puis 24.
Redimensionnez la fenêtre : ces cellules sont dans un
.layout-wrapper, elles suivent donc les pistes du site et non
un dessin recopié. Les deux plus foncées sont les marges.
grid.css :
1 sur téléphone, 12 de
md à lg,
24 à partir de xl.
Les vingt-quatre n'ouvrent pas à lg : à 1024 une piste
vaudrait 39px, trop fine pour qu'une portée veuille dire quelque chose.
grid-column: 2 / -2). C'est le cas le plus
fréquent, et il ne s'écrit pas.
clamp(80px, 11vw, 180px).<section class="section-y">
<div class="layout-wrapper">
<h2 class="xl:col-start-2 xl:col-span-13">…</h2>
<p class="xl:col-start-17 xl:col-span-7">…</p>
</div>
</section>
Raccourci ⌘X / Ctrl+X : la grille s'affiche par-dessus la page.
Les colonnes se touchent : un visuel qui couvre six colonnes les couvre bord à bord. Ce qui sépare deux blocs, c'est une colonne laissée vide.
xl:col-start-2 xl:col-span-13 puis
xl:col-start-17 xl:col-span-7 : les colonnes 15 et 16 restent
à l'air, et cet air est fluide. ⚠️ Chaque bloc porte
son col-start : une portée
seule est placée automatiquement, donc à partir de la ligne 1, c'est-à-dire dans la marge.
2 span 13 · 17 span 72 span 12 · 16 span 82 span 10 · 14 span 102 span 8 · 12 span 12grid grid-cols-N avec son propre écart, dans le cadre. Ses
bords tombent sur les colonnes 2 et 23, sa découpe intérieure est la sienne.
.layout-wrapper, ils ANNULENT sa grille en
silence : un utilitaire bat toujours la couche components.
Seule la barre de navigation en use, exprès.
Une rangée répétée n'est pas une composition de page :
sa grille est la sienne, dans le cadre du contenu.
<ol class="grid gap-y-12 lg:grid-cols-3 lg:gap-x-8">
<li>…</li>
<li>…</li>
<li>…</li>
</ol>
Sept pages, un seul rythme. Ce qui change d'une ouverture à l'autre, c'est ce qu'on y met, jamais l'espacement.
fixed et ne réserve rien. C'est la seule raison pour
laquelle il ne s'écrit pas en une classe Tailwind.
gap-8 : fil d'Ariane ou surtitre,
titre, chapô. Colonnes 2 à 14 quand il y a une zone B, le cadre entier sinon — les mots ne
s'arrêtent pas à la colonne 14 pour rien.
gap-8 : actions, fiche technique,
compteur. Colonnes 17 à 23, calée sur la ligne de base par
xl:items-end. ⚠️ Elle ne prend sa colonne qu'à
xl : en dessous elle s'empile, et il n'y a rien à défaire.
mt-16 lg:mt-20 sur son filet. Un
.layout-wrapper imbriqué : le trait traverse le cadre,
le texte retrouve la colonne 2.
text-display et
text-balance, sur les sept pages. À cette taille, une
phrase de cinquante signes tient sur trois lignes et la dernière porterait un mot seul.
⚠️ Les écarts internes d'une ouverture sont portés par le
gap-8 de sa colonne, jamais par un
mt-* posé sur une ligne. Une page de service en empilait
quatre par-dessus le rembourrage de sa section, et son titre tombait à 370px du haut de la
fenêtre contre 194 sur les services.
Les deux pilules ci-dessous sortent du même composant, appelé de deux façons différentes.
Appel Blade
Rendu comme vue simple
L'appel Antlers réel tourne en production dans
plan-du-site.antlers.html,
qui rend components/ui/link-row
dans sa boucle ; la page 404 rend le même fichier en Blade.
Depuis Blade ou Livewire
<x-ui.button variant="outline" class="w-full">Bouton Blade</x-ui.button>
Depuis un gabarit Antlers — Statamic rend n'importe quelle vue en
partial, .blade.php compris
{{ partial:components/ui/button variant="outline" label="Bouton Antlers" }}
Tout ce qui n'est pas un paramètre déclaré passe par le sac d'attributs :
class,
wire:click,
x-on:click,
target,
aria-*. C'est ce qui rend un composant
adaptable sans y ajouter un paramètre à chaque cas particulier.
Chargement du formulaire…