card-14-numbered-steps

Rangée d'étapes numérotées, un seul accent

Poser une méthode en quatre temps sur une seule rangée, en tenant DEUX rails : le chiffre de chaque étape pend du même plafond, son texte pousse depuis le même plancher — donc une phrase de deux lignes et une de quatre ne décalent rien. Une seule carte est accentuée, et elle l'est par le FOND et rien d'autre ; la dernière est noire parce qu'elle est une fin, pas un second accent.

cardslideweb1456×440px19 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

Une démarche, un process ou un plan en 3 à 5 étapes de même nature, dont une seule est « celle qui compte » (l'étape courante, le livrable qui décide). Sur une slide de méthode, une page « comment on travaille », un bloc de parcours.

Éviter quand

Les étapes ne sont pas de même nature ou n'ont pas de rang (là il faut une liste, pas une rangée) ; il en faut plus de cinq (les colonnes tombent sous la largeur d'un mot) ; ou deux étapes doivent être accentuées — le pattern porte l'accent UNIQUE, et le dupliquer supprime le point d'entrée du regard.

Notes

Ce que le pattern porte et qu'on ne réécrit pas de tête, ce sont DEUX RAILS obtenus d'un seul `justify-content: space-between` : le chiffre pend du plafond de sa carte, le bloc de texte pousse depuis le plancher. Conséquence directe et mesurée : les quatre blocs de texte n'ont PAS la même hauteur (rapport 1,4 entre le plus haut et le plus bas) et pourtant leurs bases sont sur la même ligne, à moins de 0,4 % de la hauteur près. Une pile centrée dans chaque carte — le réflexe — fait onduler la rangée dès qu'une phrase gagne une ligne, et rien dans le rendu ne dit d'où vient le défaut. LA COTE QUI COMPTE EST LA HAUTEUR, et c'est l'acquis du lot ref-05. Elle est BORNÉE (440 px, 0,30 de la largeur de la rangée), jamais `flex: 1`. La charte tient sur « neutre + UN accent », règle qui se chiffre en SURFACE : le jaune reste sous 12 % de la slide. Laissée à hauteur libre de colonne, la rangée mange la place disponible et la carte accentuée passe de 10,6 % à 11,9 % — puis à 16,8 % si la colonne est haute — alors qu'elle est toujours le seul objet coloré. La règle de l'accent unique est donc une contrainte de DIMENSION autant que de compte : être seul ne suffit pas. Le benchmark de contre-épreuve reparamètre `--vl-nsteps-h` à 700 et exige que le plafond soit dépassé de 40 % — c'est-à-dire qu'il prouve que la borne SERT à quelque chose, au lieu de constater que la valeur par défaut passe. L'accent ne se pose QUE par le fond : trois assertions l'imposent (même largeur, aucune bordure ni ombre, même corps de chiffre). C'est ce qui distingue une rangée à accent d'une rangée où l'on aurait « mis en valeur » une carte par accumulation de signaux. La quatrième carte est sombre et n'est PAS un second accent : elle marque une fin, et un benchmark exige que son fond ne soit pas la couleur d'accent — c'est la faute que ferait quelqu'un qui reprend le fragment en croyant que « la carte spéciale » est un rôle unique. L'encre suit son sol sur les trois fonds, corps compris (4,56:1 au pire, sur le blanc). Le gris de la charte a dû être corrigé pour cela : le relevé donnait #7A7A7A, sous 4,5 des DEUX côtés (3,73:1 sur le fond, 4,29:1 sur le blanc). La bibliothèque ne capitalise pas le défaut de la source. Dernier point, hérité de ref-01 : le fragment ÉPINGLE sa famille. Le chiffre de 90 est cadré par sa chasse ; sans famille déclarée il hériterait celle de la page qui le colle et la rangée rendrait autrement chez le suivant, pendant que tous les benchmarks resteraient verts — un harnais qui sert sa propre police ne peut pas voir ce défaut-là. L'assertion impose donc un serif à la racine du document et exige que l'encre du chiffre ne bouge pas.

Mots-clés

etapesprocessmethodenumeroteerangeechiffreaccentaccent-uniquetimelineparcourscarteplan

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

Rejouables par node bin/check.mjs card-14-numbered-steps : le pattern se prouve, il ne se croit pas.

assertionmesureattendu
la rangée est BORNÉE en hauteur : 0,30 de sa largeur — ce n'est pas un reste de colonneH / W0.302 ± 0.02
quatre colonnes ÉGALES — aucune étape ne prime par la tailleMath.max(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ')').w)) / Math.min(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ')').w))1 ± 0.005
…et l'ÉTAPE ACCENTUÉE n'est pas plus large que les autres : l'accent ne se pose que par le fondbox('.vl-nsteps__step--on').w / box('.vl-nsteps__step:nth-child(1)').w1 ± 0.005
gouttière constante entre les quatre, à 1,65 % de la largeur(box('.vl-nsteps__step:nth-child(2)').left - box('.vl-nsteps__step:nth-child(1)').right) / W0.0165 ± 0.004
RAIL DU HAUT — les quatre chiffres pendent du même plafond (écart nul, en fraction de la hauteur)(Math.max(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__n').top)) - Math.min(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__n').top))) / H0 ± 0.004
RAIL DU BAS — les quatre textes poussent du même plancher (écart nul, en fraction de la hauteur)(Math.max(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__txt').bottom)) - Math.min(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__txt').bottom))) / H0 ± 0.004
…et les deux rails tiennent alors même que les blocs de texte n'ont PAS la même hauteur — c'est là qu'une pile centrée onduleraitMath.max(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__txt').h)) / Math.min(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__txt').h))1.4 ± 0.35
UN SEUL ACCENT dans toute la rangée — la règle de la charte, comptéedocument.querySelectorAll('.vl-nsteps__step--on').length1 ± 0
…et l'aplat sombre n'en est pas un second : son fond n'est pas la couleur d'accentgetComputedStyle(document.querySelector('.vl-nsteps__step--end')).backgroundColor === getComputedStyle(document.querySelector('.vl-nsteps__step--on')).backgroundColor ? 1 : 00 ± 0
L'ACCENT NE SE POSE QUE PAR LE FOND : ni bordure, ni ombre sur la carte accentuéenum('.vl-nsteps__step--on','borderTopWidth') + (cs('.vl-nsteps__step--on','boxShadow') === 'none' ? 0 : 1)0 ± 0
…ni chiffre grossi : le corps du 02 est celui du 01num('.vl-nsteps__step--on .vl-nsteps__n','fontSize') / num('.vl-nsteps__step:nth-child(1) .vl-nsteps__n','fontSize')1 ± 0.01
LA SURFACE ACCENTUÉE TIENT SOUS LE PLAFOND DE CHARTE : la carte jaune couvre 10,6 % d'une slide 1600×900, pour un plafond de 12 %(box('.vl-nsteps__step--on').w * box('.vl-nsteps__step--on').h) / (1600 * 900)0.106 ± 0.014
CONTRE-ÉPREUVE — c'est bien la BORNE qui tient la règle : à hauteur libre de colonne (700), la même carte, toujours seule accentuée, dépasse le plafond de 40 %(() => { const r = document.querySelector('.vl-nsteps'); const old = r.style.getPropertyValue('--vl-nsteps-h'); r.style.setProperty('--vl-nsteps-h','700px'); r.getBoundingClientRect(); const b = document.querySelector('.vl-nsteps__step--on').getBoundingClientRect(); const out = (b.width * b.height) / (1600 * 900) / 0.12; if (old) r.style.setProperty('--vl-nsteps-h', old); else r.style.removeProperty('--vl-nsteps-h'); return out; })()1.4 ± 0.08
le chiffre domine son libellé d'un facteur 4,7 — c'est le rang qui se lit en premier, pas l'intitulénum('.vl-nsteps__n','fontSize') / num('.vl-nsteps__label','fontSize')4.74 ± 0.3
rayon en LONGUEUR, constante de charte : 32 px, soit 9 % de la largeur d'une cartenum('.vl-nsteps__step','borderTopLeftRadius') / box('.vl-nsteps__step:nth-child(1)').w0.0925 ± 0.015
L'ENCRE SUIT SON SOL, sur les trois fonds — le corps de chaque carte tient 4,5:1 (pire cas mesuré)Math.min(contrast('.vl-nsteps__step:nth-child(1) .vl-nsteps__body'), contrast('.vl-nsteps__step--on .vl-nsteps__body'), contrast('.vl-nsteps__step--end .vl-nsteps__body'))5.4 ± 0.9
…et les libellés aussi, sur les trois fondsMath.min(contrast('.vl-nsteps__step:nth-child(1) .vl-nsteps__label'), contrast('.vl-nsteps__step--on .vl-nsteps__label'), contrast('.vl-nsteps__step--end .vl-nsteps__label'))14 ± 4
rien ne déborde d'une carte[1,2,3,4].reduce((s,i) => s + overflow('.vl-nsteps__step:nth-child(' + i + ')'), 0)0 ± 0
AUTONOMIE TYPOGRAPHIQUE — un serif imposé à la page hôte ne change pas d'un pixel l'encre du chiffre : le fragment épingle sa famille(() => { const ink = () => { const r = document.createRange(); r.selectNodeContents(document.querySelector('.vl-nsteps__n')); return r.getBoundingClientRect().width; }; const a = ink(); const h = document.documentElement, old = h.style.fontFamily; h.style.fontFamily = 'Georgia, "Times New Roman", serif'; const b = ink(); h.style.fontFamily = old; return b / a; })()1 ± 0.01

Le code

fragment — patterns/card-14-numbered-steps.html Télécharger
<!-- card-14-numbered-steps — une méthode en quatre temps, posée en rangée : un chiffre en tête,
     un intitulé et sa phrase collés en bas, et UNE SEULE carte accentuée dans toute la rangée.

     CE QUI EN FAIT UN PATTERN plutôt que quatre cartes côte à côte, ce sont deux rails :
     le chiffre de chaque carte pend du même plafond, le texte de chaque carte pousse depuis le
     même plancher (`justify-content: space-between`). Une phrase de deux lignes et une phrase
     de quatre ne décalent donc RIEN — c'est ce que ne fait pas une pile centrée, où chaque
     carte flotte à sa propre hauteur et où la rangée se met à onduler dès qu'un texte grandit.

     LA COTE QUI COMPTE, et c'est la leçon du lot : LA HAUTEUR EST BORNÉE, jamais `flex: 1`.
     La charte d'origine tient sur « neutre + UN accent », règle qui se chiffre — la surface
     accentuée reste sous 12 % de la slide. Laissée libre, la rangée mange la colonne : la
     carte jaune passe de 10,6 % à 11,9 % pour un plafond de 12 %, sans qu'un seul alignement
     ne casse. La règle de l'accent unique est donc une contrainte de DIMENSION autant que de
     compte : être le seul objet coloré ne suffit pas, encore faut-il ne pas s'étaler.

     L'ACCENT NE SE POSE QUE PAR LE FOND. Aucune bordure, aucune ombre, aucun décalage de
     taille : la carte courante est jaune, la dernière est noire (c'est une FIN, pas un accent),
     les autres sont blanches. Trois fonds, une seule couleur — et l'encre suit son sol, sinon
     le jaune, à 1,03:1 contre le fond clair de la charte, ne se lit plus du tout. -->
<div class="vl-nsteps">

  <article class="vl-nsteps__step">
    <div class="vl-nsteps__n">01</div>
    <div class="vl-nsteps__txt">
      <h4 class="vl-nsteps__label">Read the book</h4>
      <p class="vl-nsteps__body">Two years of invoices, every discount, every exception granted.</p>
    </div>
  </article>

  <article class="vl-nsteps__step vl-nsteps__step--on">
    <div class="vl-nsteps__n">02</div>
    <div class="vl-nsteps__txt">
      <h4 class="vl-nsteps__label">Set the line</h4>
      <p class="vl-nsteps__body">One workshop, one page out: what is charged, and what stops being free.</p>
    </div>
  </article>

  <article class="vl-nsteps__step">
    <div class="vl-nsteps__n">03</div>
    <div class="vl-nsteps__txt">
      <h4 class="vl-nsteps__label">Build the surface</h4>
      <p class="vl-nsteps__body">Tiers, guardrails, the quote tool your team actually opens.</p>
    </div>
  </article>

  <article class="vl-nsteps__step vl-nsteps__step--end">
    <div class="vl-nsteps__n">04</div>
    <div class="vl-nsteps__txt">
      <h4 class="vl-nsteps__label">Hand it over</h4>
      <p class="vl-nsteps__body">Two weeks of pairing, then we are gone and it still runs.</p>
    </div>
  </article>

</div>

<style>
.vl-nsteps {
  /* La hauteur est une COTE, pas un reste de colonne. Voir l'en-tête du fragment. */
  --vl-nsteps-w: 1456px;
  --vl-nsteps-h: 440px;
  --vl-nsteps-gap: var(--vl-gap);
  --vl-nsteps-radius: var(--vl-r-step);
  --vl-nsteps-pad: 32px;

  --vl-nsteps-plain: var(--vl-white);
  --vl-nsteps-accent: var(--vl-acid);
  --vl-nsteps-end: var(--vl-black);
  --vl-nsteps-ink: var(--vl-black);
  --vl-nsteps-ink-invert: var(--vl-white);
  --vl-nsteps-muted: var(--vl-ink-muted);
  --vl-nsteps-muted-invert: var(--vl-ink-muted-invert);

  /* La police est ÉPINGLÉE : le chiffre de 90 est cadré par sa chasse, et un fragment dont
     une cote dépend d'une chasse ne peut pas hériter la police de la page qui le colle. */
  font-family: var(--vl-font-body, "Hanken Grotesk", "Helvetica Neue", Arial, sans-serif);
  width: var(--vl-nsteps-w);
  height: var(--vl-nsteps-h);
  box-sizing: border-box;
  display: flex;
  gap: var(--vl-nsteps-gap);
}

.vl-nsteps__step {
  flex: 1;                       /* les quatre colonnes sont ÉGALES : aucune étape ne prime */
  min-width: 0;
  border-radius: var(--vl-nsteps-radius);
  padding: var(--vl-nsteps-pad);
  background: var(--vl-nsteps-plain);
  color: var(--vl-nsteps-ink);
  /* LES DEUX RAILS : chiffre au plafond, texte au plancher. */
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.vl-nsteps__n {
  font-size: 90px;
  line-height: 1;
  font-weight: 800;
  letter-spacing: -0.03em;
}

.vl-nsteps__txt { display: flex; flex-direction: column; gap: 10px; }

.vl-nsteps__label {
  margin: 0;
  font-size: 19px;
  line-height: 1.3;
  font-weight: 700;
}

.vl-nsteps__body {
  margin: 0;
  font-size: 19px;
  line-height: 1.55;
  font-weight: 400;
  color: var(--vl-nsteps-muted);
}

/* L'accent ne fait qu'UNE chose : il change le FOND. Pas de bordure, pas d'ombre, pas de
   changement de taille — et l'encre suit son sol. */
.vl-nsteps__step--on  { background: var(--vl-nsteps-accent); }
.vl-nsteps__step--on  .vl-nsteps__body { color: var(--vl-nsteps-ink); }

.vl-nsteps__step--end { background: var(--vl-nsteps-end); color: var(--vl-nsteps-ink-invert); }
.vl-nsteps__step--end .vl-nsteps__body { color: var(--vl-nsteps-muted-invert); }
</style>
tokens de la charte — systems/ref-05-proposal-acid-yellow.json Télécharger
:root {
  --vl-bg: #EFEFED;
  --vl-board: #DCDCD8;
  --vl-white: #FFFFFF;
  --vl-black: #111111;
  --vl-acid: #EAFF00;
  --vl-ink-muted: #6C6C6C;
  --vl-ink-muted-invert: #9A9A9A;
  --vl-hairline: #11111118;
  --vl-hairline-invert: #FFFFFF24;
  --vl-blob-light: #E8E8E6;
  --vl-blob-dark: #171717;
  --vl-margin: 72px;
  --vl-gap: 24px;
  --vl-gap-block: 32px;
  --vl-r-media: 19px;
  --vl-r-list: 22px;
  --vl-r-band: 26px;
  --vl-r-step: 32px;
}
les métadonnées — patterns/card-14-numbered-steps.json (télécharger)
{
  "name": "Rangée d'étapes numérotées, un seul accent",
  "family": "card",
  "ref": "ref-05-proposal-acid-yellow",
  "intent": "Poser une méthode en quatre temps sur une seule rangée, en tenant DEUX rails : le chiffre de chaque étape pend du même plafond, son texte pousse depuis le même plancher — donc une phrase de deux lignes et une de quatre ne décalent rien. Une seule carte est accentuée, et elle l'est par le FOND et rien d'autre ; la dernière est noire parce qu'elle est une fin, pas un second accent.",
  "when_to_use": "Une démarche, un process ou un plan en 3 à 5 étapes de même nature, dont une seule est « celle qui compte » (l'étape courante, le livrable qui décide). Sur une slide de méthode, une page « comment on travaille », un bloc de parcours.",
  "avoid_when": "Les étapes ne sont pas de même nature ou n'ont pas de rang (là il faut une liste, pas une rangée) ; il en faut plus de cinq (les colonnes tombent sous la largeur d'un mot) ; ou deux étapes doivent être accentuées — le pattern porte l'accent UNIQUE, et le dupliquer supprime le point d'entrée du regard.",
  "media": [
    "slide",
    "web"
  ],
  "tags": [
    "etapes",
    "process",
    "methode",
    "numerotee",
    "rangee",
    "chiffre",
    "accent",
    "accent-unique",
    "timeline",
    "parcours",
    "carte",
    "plan"
  ],
  "vars": [
    {
      "name": "--vl-nsteps-w",
      "role": "la largeur de la rangée — la bande de contenu qui la reçoit",
      "default": "1456px"
    },
    {
      "name": "--vl-nsteps-h",
      "role": "la hauteur de la rangée — une COTE, jamais un reste de colonne : c'est elle qui tient la surface accentuée sous le plafond de la charte",
      "default": "440px"
    },
    {
      "name": "--vl-nsteps-gap",
      "role": "la gouttière entre deux étapes",
      "default": "var(--vl-gap)"
    },
    {
      "name": "--vl-nsteps-radius",
      "role": "le rayon des cartes — constante de charte en longueur, pas une fraction du côté",
      "default": "var(--vl-r-step)"
    },
    {
      "name": "--vl-nsteps-pad",
      "role": "la marge intérieure d'une carte, la même sur les quatre côtés",
      "default": "32px"
    },
    {
      "name": "--vl-nsteps-plain",
      "role": "le fond des étapes ordinaires",
      "default": "var(--vl-white)"
    },
    {
      "name": "--vl-nsteps-accent",
      "role": "le fond de l'ÉTAPE ACCENTUÉE — le seul emploi de la couleur d'accent dans le fragment",
      "default": "var(--vl-acid)"
    },
    {
      "name": "--vl-nsteps-end",
      "role": "le fond de l'étape terminale : un aplat sombre, qui marque une FIN et non un second accent",
      "default": "var(--vl-black)"
    },
    {
      "name": "--vl-nsteps-ink",
      "role": "l'encre sur fond clair et sur l'accent",
      "default": "var(--vl-black)"
    },
    {
      "name": "--vl-nsteps-ink-invert",
      "role": "l'encre sur l'aplat sombre",
      "default": "var(--vl-white)"
    },
    {
      "name": "--vl-nsteps-muted",
      "role": "l'encre du corps sur fond clair",
      "default": "var(--vl-ink-muted)"
    },
    {
      "name": "--vl-nsteps-muted-invert",
      "role": "l'encre du corps sur l'aplat sombre",
      "default": "var(--vl-ink-muted-invert)"
    },
    {
      "name": "--vl-font-body",
      "role": "la famille du fragment — ÉPINGLÉE : le chiffre de 90 est cadré par sa chasse, donc le fragment ne peut pas hériter la police de la page qui le colle",
      "default": "\"Hanken Grotesk\", \"Helvetica Neue\", Arial, sans-serif"
    }
  ],
  "slots": [
    "n",
    "label",
    "body"
  ],
  "geometry": {
    "root": ".vl-nsteps",
    "frame": [
      1456,
      440
    ],
    "ratios": {
      "height_over_width": 0.302,
      "step_over_width": 0.2376,
      "gutter_over_width": 0.0165,
      "pad_over_step_width": 0.0925,
      "n_over_label": 4.74,
      "accent_over_slide": 0.106
    },
    "type_px": {
      "n": 90,
      "label": 19,
      "body": 19
    },
    "pad_ratio": {
      "top": 0.0727,
      "x": 0.022,
      "bottom": 0.0727
    }
  },
  "benchmarks": [
    {
      "name": "la rangée est BORNÉE en hauteur : 0,30 de sa largeur — ce n'est pas un reste de colonne",
      "measure": "H / W",
      "expect": 0.302,
      "tol": 0.02
    },
    {
      "name": "quatre colonnes ÉGALES — aucune étape ne prime par la taille",
      "measure": "Math.max(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ')').w)) / Math.min(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ')').w))",
      "expect": 1,
      "tol": 0.005
    },
    {
      "name": "…et l'ÉTAPE ACCENTUÉE n'est pas plus large que les autres : l'accent ne se pose que par le fond",
      "measure": "box('.vl-nsteps__step--on').w / box('.vl-nsteps__step:nth-child(1)').w",
      "expect": 1,
      "tol": 0.005
    },
    {
      "name": "gouttière constante entre les quatre, à 1,65 % de la largeur",
      "measure": "(box('.vl-nsteps__step:nth-child(2)').left - box('.vl-nsteps__step:nth-child(1)').right) / W",
      "expect": 0.0165,
      "tol": 0.004
    },
    {
      "name": "RAIL DU HAUT — les quatre chiffres pendent du même plafond (écart nul, en fraction de la hauteur)",
      "measure": "(Math.max(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__n').top)) - Math.min(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__n').top))) / H",
      "expect": 0,
      "tol": 0.004
    },
    {
      "name": "RAIL DU BAS — les quatre textes poussent du même plancher (écart nul, en fraction de la hauteur)",
      "measure": "(Math.max(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__txt').bottom)) - Math.min(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__txt').bottom))) / H",
      "expect": 0,
      "tol": 0.004
    },
    {
      "name": "…et les deux rails tiennent alors même que les blocs de texte n'ont PAS la même hauteur — c'est là qu'une pile centrée ondulerait",
      "measure": "Math.max(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__txt').h)) / Math.min(...[1,2,3,4].map(i => box('.vl-nsteps__step:nth-child(' + i + ') .vl-nsteps__txt').h))",
      "expect": 1.4,
      "tol": 0.35
    },
    {
      "name": "UN SEUL ACCENT dans toute la rangée — la règle de la charte, comptée",
      "measure": "document.querySelectorAll('.vl-nsteps__step--on').length",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "…et l'aplat sombre n'en est pas un second : son fond n'est pas la couleur d'accent",
      "measure": "getComputedStyle(document.querySelector('.vl-nsteps__step--end')).backgroundColor === getComputedStyle(document.querySelector('.vl-nsteps__step--on')).backgroundColor ? 1 : 0",
      "expect": 0,
      "tol": 0
    },
    {
      "name": "L'ACCENT NE SE POSE QUE PAR LE FOND : ni bordure, ni ombre sur la carte accentuée",
      "measure": "num('.vl-nsteps__step--on','borderTopWidth') + (cs('.vl-nsteps__step--on','boxShadow') === 'none' ? 0 : 1)",
      "expect": 0,
      "tol": 0
    },
    {
      "name": "…ni chiffre grossi : le corps du 02 est celui du 01",
      "measure": "num('.vl-nsteps__step--on .vl-nsteps__n','fontSize') / num('.vl-nsteps__step:nth-child(1) .vl-nsteps__n','fontSize')",
      "expect": 1,
      "tol": 0.01
    },
    {
      "name": "LA SURFACE ACCENTUÉE TIENT SOUS LE PLAFOND DE CHARTE : la carte jaune couvre 10,6 % d'une slide 1600×900, pour un plafond de 12 %",
      "measure": "(box('.vl-nsteps__step--on').w * box('.vl-nsteps__step--on').h) / (1600 * 900)",
      "expect": 0.106,
      "tol": 0.014
    },
    {
      "name": "CONTRE-ÉPREUVE — c'est bien la BORNE qui tient la règle : à hauteur libre de colonne (700), la même carte, toujours seule accentuée, dépasse le plafond de 40 %",
      "measure": "(() => { const r = document.querySelector('.vl-nsteps'); const old = r.style.getPropertyValue('--vl-nsteps-h'); r.style.setProperty('--vl-nsteps-h','700px'); r.getBoundingClientRect(); const b = document.querySelector('.vl-nsteps__step--on').getBoundingClientRect(); const out = (b.width * b.height) / (1600 * 900) / 0.12; if (old) r.style.setProperty('--vl-nsteps-h', old); else r.style.removeProperty('--vl-nsteps-h'); return out; })()",
      "expect": 1.4,
      "tol": 0.08
    },
    {
      "name": "le chiffre domine son libellé d'un facteur 4,7 — c'est le rang qui se lit en premier, pas l'intitulé",
      "measure": "num('.vl-nsteps__n','fontSize') / num('.vl-nsteps__label','fontSize')",
      "expect": 4.74,
      "tol": 0.3
    },
    {
      "name": "rayon en LONGUEUR, constante de charte : 32 px, soit 9 % de la largeur d'une carte",
      "measure": "num('.vl-nsteps__step','borderTopLeftRadius') / box('.vl-nsteps__step:nth-child(1)').w",
      "expect": 0.0925,
      "tol": 0.015
    },
    {
      "name": "L'ENCRE SUIT SON SOL, sur les trois fonds — le corps de chaque carte tient 4,5:1 (pire cas mesuré)",
      "measure": "Math.min(contrast('.vl-nsteps__step:nth-child(1) .vl-nsteps__body'), contrast('.vl-nsteps__step--on .vl-nsteps__body'), contrast('.vl-nsteps__step--end .vl-nsteps__body'))",
      "expect": 5.4,
      "tol": 0.9
    },
    {
      "name": "…et les libellés aussi, sur les trois fonds",
      "measure": "Math.min(contrast('.vl-nsteps__step:nth-child(1) .vl-nsteps__label'), contrast('.vl-nsteps__step--on .vl-nsteps__label'), contrast('.vl-nsteps__step--end .vl-nsteps__label'))",
      "expect": 14,
      "tol": 4
    },
    {
      "name": "rien ne déborde d'une carte",
      "measure": "[1,2,3,4].reduce((s,i) => s + overflow('.vl-nsteps__step:nth-child(' + i + ')'), 0)",
      "expect": 0,
      "tol": 0
    },
    {
      "name": "AUTONOMIE TYPOGRAPHIQUE — un serif imposé à la page hôte ne change pas d'un pixel l'encre du chiffre : le fragment épingle sa famille",
      "measure": "(() => { const ink = () => { const r = document.createRange(); r.selectNodeContents(document.querySelector('.vl-nsteps__n')); return r.getBoundingClientRect().width; }; const a = ink(); const h = document.documentElement, old = h.style.fontFamily; h.style.fontFamily = 'Georgia, \"Times New Roman\", serif'; const b = ink(); h.style.fontFamily = old; return b / a; })()",
      "expect": 1,
      "tol": 0.01
    }
  ],
  "notes": "Ce que le pattern porte et qu'on ne réécrit pas de tête, ce sont DEUX RAILS obtenus d'un seul `justify-content: space-between` : le chiffre pend du plafond de sa carte, le bloc de texte pousse depuis le plancher. Conséquence directe et mesurée : les quatre blocs de texte n'ont PAS la même hauteur (rapport 1,4 entre le plus haut et le plus bas) et pourtant leurs bases sont sur la même ligne, à moins de 0,4 % de la hauteur près. Une pile centrée dans chaque carte — le réflexe — fait onduler la rangée dès qu'une phrase gagne une ligne, et rien dans le rendu ne dit d'où vient le défaut.\n\nLA COTE QUI COMPTE EST LA HAUTEUR, et c'est l'acquis du lot ref-05. Elle est BORNÉE (440 px, 0,30 de la largeur de la rangée), jamais `flex: 1`. La charte tient sur « neutre + UN accent », règle qui se chiffre en SURFACE : le jaune reste sous 12 % de la slide. Laissée à hauteur libre de colonne, la rangée mange la place disponible et la carte accentuée passe de 10,6 % à 11,9 % — puis à 16,8 % si la colonne est haute — alors qu'elle est toujours le seul objet coloré. La règle de l'accent unique est donc une contrainte de DIMENSION autant que de compte : être seul ne suffit pas. Le benchmark de contre-épreuve reparamètre `--vl-nsteps-h` à 700 et exige que le plafond soit dépassé de 40 % — c'est-à-dire qu'il prouve que la borne SERT à quelque chose, au lieu de constater que la valeur par défaut passe.\n\nL'accent ne se pose QUE par le fond : trois assertions l'imposent (même largeur, aucune bordure ni ombre, même corps de chiffre). C'est ce qui distingue une rangée à accent d'une rangée où l'on aurait « mis en valeur » une carte par accumulation de signaux. La quatrième carte est sombre et n'est PAS un second accent : elle marque une fin, et un benchmark exige que son fond ne soit pas la couleur d'accent — c'est la faute que ferait quelqu'un qui reprend le fragment en croyant que « la carte spéciale » est un rôle unique.\n\nL'encre suit son sol sur les trois fonds, corps compris (4,56:1 au pire, sur le blanc). Le gris de la charte a dû être corrigé pour cela : le relevé donnait #7A7A7A, sous 4,5 des DEUX côtés (3,73:1 sur le fond, 4,29:1 sur le blanc). La bibliothèque ne capitalise pas le défaut de la source.\n\nDernier point, hérité de ref-01 : le fragment ÉPINGLE sa famille. Le chiffre de 90 est cadré par sa chasse ; sans famille déclarée il hériterait celle de la page qui le colle et la rangée rendrait autrement chez le suivant, pendant que tous les benchmarks resteraient verts — un harnais qui sert sa propre police ne peut pas voir ce défaut-là. L'assertion impose donc un serif à la racine du document et exige que l'encre du chiffre ne bouge pas.",
  "id": "card-14-numbered-steps"
}

À côté