tag-02-centered-cloud

Nuage de mots-clés centré rangée par rangée

Poser une dizaine de mots-clés de même rang au bas d'un module sans en faire une liste : chaque RANGÉE est centrée sur elle-même, si bien que les lignes de largeur inégale dessinent une silhouette de nuage — c'est le centrage par rangée, et pas le centrage du bloc, qui produit l'effet.

tagwebslide287×140px13 benchmarks

Télécharger le .zip

Le fragment RÉEL, dans son propre document, sur le sol de sa charte — pas une image. Ce que vous voyez est exactement ce que vous collerez.

Employer quand

Fin d'une vignette de service, pied d'une carte de rubrique, bandeau de compétences : dix à quinze termes courts qu'on veut donner à VOIR en volume plutôt qu'à lire un par un, sans hiérarchie entre eux et sans ordre significatif.

Éviter quand

L'ordre compte (un nuage le détruit visuellement), les termes sont cliquables et de longueurs très inégales (les cibles deviennent illisibles à balayer), il y en a moins de six — trois rangées sont le minimum pour qu'une silhouette existe — ou l'un d'eux doit être mis en avant : ce système n'a aucune ressource pour distinguer une pilule des autres.

Notes

L'assertion centrale est écrite sur les RANGÉES et pas sur le conteneur, parce que `justify-content: center` seul ne prouve rien : un bloc de pilules ferrées à gauche mais centré dans son module passerait ce contrôle et n'aurait aucune silhouette. La mesure regroupe donc les pilules par ordonnée et vérifie, ligne par ligne, que la marge gauche égale la marge droite. Deux assertions négatives complètent le geste. « Les rangées sont de largeurs inégales » écarte le pavé centré, qui est visuellement l'exact contraire d'un nuage tout en satisfaisant le centrage. « Aucune rangée orpheline » écarte le cas mesuré pendant le lot : dix termes courts dans un module de 287 px se rangeaient 3/3/3/1, et la pilule seule de la dernière ligne suffit à ruiner la silhouette. Le remède n'est pas dans le CSS — il est dans le choix des termes, et le benchmark est là pour l'imposer. Ce pattern ne double pas tag-01-gooey-capsule : celui-ci fabrique UN objet (un contour continu qui soude un glyphe à un mot), celui-là ne dit rien de la pilule et tout de leur ARRANGEMENT. On peut d'ailleurs servir l'un avec l'autre.

Mots-clés

tagmot-clekeywordnuagecloudpilulepillchipcompetencesserviceswrapmonochrome

Benchmarks — la géométrie, en assertions vérifiables

Rejouables par node bin/check.mjs tag-02-centered-cloud : le pattern se prouve, il ne se croit pas.

assertionmesureattendu
les pilules passent à la ligne : c'est un nuage, pas une barre qui débordecs('.vl-cloud','flexWrap') === 'wrap' ? 1 : 01
CHAQUE RANGÉE est centrée sur elle-même — l'écart à gauche égale l'écart à droite, ligne par ligne(() => { const r = $('.vl-cloud').getBoundingClientRect(); const rows = {}; for (const e of document.querySelectorAll('.vl-cloud > *')) { const b = e.getBoundingClientRect(); const k = Math.round(b.top); (rows[k] = rows[k] || []).push(b); } return Math.max(...Object.values(rows).map((g) => Math.abs((Math.min(...g.map((b) => b.left)) - r.left) - (r.right - Math.max(...g.map((b) => b.right)))))) / r.width; })()≤ 0.012
au moins TROIS rangées : en dessous, aucune silhouette n'existenew Set([...document.querySelectorAll('.vl-cloud > *')].map((e) => Math.round(e.getBoundingClientRect().top))).size≥ 3
les rangées sont de largeurs INÉGALES — sinon c'est un pavé centré, pas un nuage(() => { const rows = {}; for (const e of document.querySelectorAll('.vl-cloud > *')) { const b = e.getBoundingClientRect(); const k = Math.round(b.top); (rows[k] = rows[k] || []).push(b); } const w = Object.values(rows).map((g) => Math.max(...g.map((b) => b.right)) - Math.min(...g.map((b) => b.left))); return (Math.max(...w) - Math.min(...w)) / W; })()≥ 0.06
aucune rangée orpheline : la plus courte porte au moins deux pilules(() => { const rows = {}; for (const e of document.querySelectorAll('.vl-cloud > *')) { const k = Math.round(e.getBoundingClientRect().top); rows[k] = (rows[k] || 0) + 1; } return Math.min(...Object.values(rows)); })()≥ 2
une pilule EST une pilule : rayon ≥ sa demi-hauteurnum('.vl-cloud > *','borderTopLeftRadius') / box('.vl-cloud > *').h≥ 0.5
la pilule est PLUS CLAIRE que le fond qui la porte — le rang de blanc se dépense vers le hautlum(parseRgb(cs('.vl-cloud > *','backgroundColor'))) - lum(bgOf($('.vl-cloud')))≥ 0.01
pilule basse : sa hauteur reste sous 12 % de la largeur du nuagebox('.vl-cloud > *').h / W≤ 0.12
la gouttière verticale est un peu plus large que l'horizontale : les rangées se lisent avant les colonnesnum('.vl-cloud','rowGap') - num('.vl-cloud','columnGap')0 → 6
aucun terme ne se coupe : chaque pilule tient sur une ligne[...document.querySelectorAll('.vl-cloud > *')].filter((e) => e.getBoundingClientRect().height > box('.vl-cloud > *').h * 1.4).length0 ± 0
aucun liseré sur les pilulesnum('.vl-cloud > *','borderTopWidth')0 ± 0
contraste du texte de pilule ≥ 4,5contrast('.vl-cloud > *')≥ 4.5
aucun débordement du nuageoverflow('.vl-cloud')≤ 0

Le code

fragment — patterns/tag-02-centered-cloud.html Télécharger
<!-- tag-02-centered-cloud — dix mots-clés en nuage. Couleurs via --vl-* uniquement.
     TOUT le pattern tient dans `flex-wrap: wrap` + `justify-content: center` : chaque rangée
     se centre SUR ELLE-MÊME, et ce sont les largeurs inégales qui dessinent la silhouette.
     Un bloc centré dont les rangées seraient ferrées à gauche donnerait un pavé, pas un nuage.

     Le choix des TERMES fait partie du pattern : dix mots trop courts dans un module étroit
     se rangent 3/3/3/1, et la pilule orpheline de la dernière ligne ruine la silhouette.
     Le remède est dans la longueur des libellés, pas dans le CSS — d'où le benchmark.

     `.vl-cloud__field` n'est PAS le nuage : c'est le module qui l'accueillera, posé ici pour
     que la règle de rang de blanc (« la pilule est plus claire que le fond qui la porte »)
     soit MESURABLE sur le fragment seul. Le nuage, lui, n'a aucun fond propre — le retirer
     rendrait le fragment collable partout et la règle invérifiable ; on garde donc le champ
     et on le nomme pour ce qu'il est. -->
<div class="vl-cloud__field">
<div class="vl-cloud">
  <span>family law</span><span>commercial disputes</span>
  <span>property &amp; housing</span><span>tax and duties</span>
  <span>employment law</span><span>filings</span><span>claims</span>
  <span>contracts</span><span>case support</span><span>advice</span>
</div>
</div>

<style>
.vl-cloud__field {
  width: 343px; padding: 28px;
  box-sizing: border-box;
  background: var(--vl-module);
  border-radius: var(--vl-radius-module, 24px);
}
.vl-cloud {
  width: 287px;
  box-sizing: border-box;
  display: flex; flex-wrap: wrap;
  justify-content: center;
  /* La gouttière verticale est un cran plus large que l'horizontale : les rangées se lisent
     alors avant les colonnes, ce qui est la lecture voulue d'un nuage. À gouttières égales,
     l'œil se met à chercher des colonnes qui n'existent pas. */
  gap: 8px 7px;
  font-family: Inter, "Helvetica Neue", Arial, sans-serif;
}
.vl-cloud > span {
  background: var(--vl-object);
  color: var(--vl-ink);
  border-radius: var(--vl-radius-pill, 999px);
  padding: 6px 12px;
  font-size: 11px; line-height: 1.2; font-weight: 500;
  white-space: nowrap;
}
</style>
tokens de la charte — systems/ref-17-mono-bento-legal.json Télécharger
:root {
  --vl-page: #FEFEFE;
  --vl-module: #EDEDED;
  --vl-object: #FFFFFF;
  --vl-invert: #000000;
  --vl-ink: #111111;
  --vl-ink-soft: #5F5F5F;
  --vl-on-invert: #FFFFFF;
  --vl-on-invert-soft: rgba(255,255,255,0.62);
  --vl-line: rgba(17,17,17,0.55);
  --vl-radius-page: 38px;
  --vl-radius-module: 24px;
  --vl-radius-bar: 20px;
  --vl-radius-pill: 999px;
  --vl-gutter: 26px;
  --vl-margin: 57px;
  --vl-bar-h: 69px;
  --vl-col-1: 343fr;
  --vl-col-2: 466fr;
  --vl-col-3: 464fr;
  --vl-row-1: 302fr;
  --vl-row-2: 232fr;
  --vl-row-3: 243fr;
}
les métadonnées — patterns/tag-02-centered-cloud.json (télécharger)
{
  "name": "Nuage de mots-clés centré rangée par rangée",
  "family": "tag",
  "ref": "ref-17-mono-bento-legal",
  "intent": "Poser une dizaine de mots-clés de même rang au bas d'un module sans en faire une liste : chaque RANGÉE est centrée sur elle-même, si bien que les lignes de largeur inégale dessinent une silhouette de nuage — c'est le centrage par rangée, et pas le centrage du bloc, qui produit l'effet.",
  "when_to_use": "Fin d'une vignette de service, pied d'une carte de rubrique, bandeau de compétences : dix à quinze termes courts qu'on veut donner à VOIR en volume plutôt qu'à lire un par un, sans hiérarchie entre eux et sans ordre significatif.",
  "avoid_when": "L'ordre compte (un nuage le détruit visuellement), les termes sont cliquables et de longueurs très inégales (les cibles deviennent illisibles à balayer), il y en a moins de six — trois rangées sont le minimum pour qu'une silhouette existe — ou l'un d'eux doit être mis en avant : ce système n'a aucune ressource pour distinguer une pilule des autres.",
  "media": [
    "web",
    "slide"
  ],
  "tags": [
    "tag",
    "mot-cle",
    "keyword",
    "nuage",
    "cloud",
    "pilule",
    "pill",
    "chip",
    "competences",
    "services",
    "wrap",
    "monochrome"
  ],
  "vars": [
    {
      "name": "--vl-object",
      "role": "aplat d'une pilule — PLUS CLAIR que le module qui la porte",
      "default": "#FFFFFF"
    },
    {
      "name": "--vl-ink",
      "role": "texte d'une pilule",
      "default": "#111111"
    },
    {
      "name": "--vl-radius-pill",
      "role": "rayon d'une pilule",
      "default": "999px"
    }
  ],
  "slots": [
    "items"
  ],
  "geometry": {
    "root": ".vl-cloud",
    "frame": [
      287,
      140
    ],
    "ratios": {
      "pill_height_over_width": 0.091,
      "gap_x_over_width": 0.024,
      "gap_y_over_width": 0.028,
      "pad_x_over_pill_height": 0.46
    },
    "type_px": {
      "tag": 11
    },
    "pad_ratio": {
      "top": 0,
      "x": 0,
      "bottom": 0
    }
  },
  "benchmarks": [
    {
      "name": "les pilules passent à la ligne : c'est un nuage, pas une barre qui déborde",
      "measure": "cs('.vl-cloud','flexWrap') === 'wrap' ? 1 : 0",
      "expect": 1
    },
    {
      "name": "CHAQUE RANGÉE est centrée sur elle-même — l'écart à gauche égale l'écart à droite, ligne par ligne",
      "measure": "(() => { const r = $('.vl-cloud').getBoundingClientRect(); const rows = {}; for (const e of document.querySelectorAll('.vl-cloud > *')) { const b = e.getBoundingClientRect(); const k = Math.round(b.top); (rows[k] = rows[k] || []).push(b); } return Math.max(...Object.values(rows).map((g) => Math.abs((Math.min(...g.map((b) => b.left)) - r.left) - (r.right - Math.max(...g.map((b) => b.right)))))) / r.width; })()",
      "max": 0.012
    },
    {
      "name": "au moins TROIS rangées : en dessous, aucune silhouette n'existe",
      "measure": "new Set([...document.querySelectorAll('.vl-cloud > *')].map((e) => Math.round(e.getBoundingClientRect().top))).size",
      "min": 3
    },
    {
      "name": "les rangées sont de largeurs INÉGALES — sinon c'est un pavé centré, pas un nuage",
      "measure": "(() => { const rows = {}; for (const e of document.querySelectorAll('.vl-cloud > *')) { const b = e.getBoundingClientRect(); const k = Math.round(b.top); (rows[k] = rows[k] || []).push(b); } const w = Object.values(rows).map((g) => Math.max(...g.map((b) => b.right)) - Math.min(...g.map((b) => b.left))); return (Math.max(...w) - Math.min(...w)) / W; })()",
      "min": 0.06
    },
    {
      "name": "aucune rangée orpheline : la plus courte porte au moins deux pilules",
      "measure": "(() => { const rows = {}; for (const e of document.querySelectorAll('.vl-cloud > *')) { const k = Math.round(e.getBoundingClientRect().top); rows[k] = (rows[k] || 0) + 1; } return Math.min(...Object.values(rows)); })()",
      "min": 2
    },
    {
      "name": "une pilule EST une pilule : rayon ≥ sa demi-hauteur",
      "measure": "num('.vl-cloud > *','borderTopLeftRadius') / box('.vl-cloud > *').h",
      "min": 0.5
    },
    {
      "name": "la pilule est PLUS CLAIRE que le fond qui la porte — le rang de blanc se dépense vers le haut",
      "measure": "lum(parseRgb(cs('.vl-cloud > *','backgroundColor'))) - lum(bgOf($('.vl-cloud')))",
      "min": 0.01
    },
    {
      "name": "pilule basse : sa hauteur reste sous 12 % de la largeur du nuage",
      "measure": "box('.vl-cloud > *').h / W",
      "max": 0.12
    },
    {
      "name": "la gouttière verticale est un peu plus large que l'horizontale : les rangées se lisent avant les colonnes",
      "measure": "num('.vl-cloud','rowGap') - num('.vl-cloud','columnGap')",
      "min": 0,
      "max": 6
    },
    {
      "name": "aucun terme ne se coupe : chaque pilule tient sur une ligne",
      "measure": "[...document.querySelectorAll('.vl-cloud > *')].filter((e) => e.getBoundingClientRect().height > box('.vl-cloud > *').h * 1.4).length",
      "expect": 0,
      "tol": 0
    },
    {
      "name": "aucun liseré sur les pilules",
      "measure": "num('.vl-cloud > *','borderTopWidth')",
      "expect": 0,
      "tol": 0
    },
    {
      "name": "contraste du texte de pilule ≥ 4,5",
      "measure": "contrast('.vl-cloud > *')",
      "min": 4.5
    },
    {
      "name": "aucun débordement du nuage",
      "measure": "overflow('.vl-cloud')",
      "max": 0
    }
  ],
  "notes": "L'assertion centrale est écrite sur les RANGÉES et pas sur le conteneur, parce que `justify-content: center` seul ne prouve rien : un bloc de pilules ferrées à gauche mais centré dans son module passerait ce contrôle et n'aurait aucune silhouette. La mesure regroupe donc les pilules par ordonnée et vérifie, ligne par ligne, que la marge gauche égale la marge droite.\n\nDeux assertions négatives complètent le geste. « Les rangées sont de largeurs inégales » écarte le pavé centré, qui est visuellement l'exact contraire d'un nuage tout en satisfaisant le centrage. « Aucune rangée orpheline » écarte le cas mesuré pendant le lot : dix termes courts dans un module de 287 px se rangeaient 3/3/3/1, et la pilule seule de la dernière ligne suffit à ruiner la silhouette. Le remède n'est pas dans le CSS — il est dans le choix des termes, et le benchmark est là pour l'imposer.\n\nCe pattern ne double pas tag-01-gooey-capsule : celui-ci fabrique UN objet (un contour continu qui soude un glyphe à un mot), celui-là ne dit rien de la pilule et tout de leur ARRANGEMENT. On peut d'ailleurs servir l'un avec l'autre.",
  "id": "tag-02-centered-cloud"
}

À côté