card-14-numbered-steps
title-05-hyphen-break
Titre display soudé par un trait d'union
Souder une locution de deux mots en un composé unique de deux lignes, en remplaçant son ESPACE par un trait d'union frappé : « Business Proposal » devient `Business-` / `Proposal`. Le retour n'est pas calculé par la mesure, il est écrit — donc il ne bouge jamais — et le trait d'union, posé en encre la plus à droite du bloc, termine la silhouette au lieu de signaler une coupure oubliée.
titleslideweb905×214px13 benchmarks
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 couverture, une slide de clôture, un hero : partout où un display de deux mots doit faire MASSE plutôt que ligne, sur une charte qui n'a pas d'accent coloré à dépenser sur son titre. Le trait d'union est alors le seul signe graphique du bloc, et il coûte zéro couleur. Sert aussi à ancrer un titre au fer à gauche d'une colonne dont le bas porte autre chose : les deux lignes serrées font un bloc franc, pas un texte qui coule.
Éviter quand
La locution ne tient pas en deux mots, ou le second mot est plus long que le premier plus son trait : le trait d'union retombe alors À L'INTÉRIEUR de la silhouette et se lit comme une césure ratée — c'est la seule contrainte dure du pattern. Écarter aussi en capitales (le trait se centre sur une hauteur d'x absente et devient un tiret de séparation), et pour un nom de marque d'un seul mot qu'on veut régler à la largeur — c'est title-04-name-fills-measure.
Notes
Le pattern ne porte pas un trait d'union, il porte une DISTINCTION. Une césure syllabique est un accident de composition : elle dépend de la mesure, elle se déplace quand la colonne bouge, et une charte la fuit dans un display. Un trait d'union de locution est un choix : il soude deux mots entiers, et le retour est frappé — donc il ne dépend de rien. Deux benchmarks séparent les deux cas, et aucun des deux n'est cosmétique. Le premier retire le `<br>` d'un clone à mesure libre et exige UNE ligne : si les deux lignes venaient de la largeur, le clone en garderait deux. Le second recompose le texte rendu et exige qu'il vaille la locution de `data-phrase` une fois l'espace et le trait retirés — un mot coupé échouerait, puisque `data-phrase` porte deux mots entiers séparés par une espace. La loi de forme n'est PAS une égalité de longueur entre les lignes, et c'est ce qu'on croit d'abord en regardant `Business-` / `Proposal`. Le second spécimen la dément : `Thank-` / `you` est franchement descendant. Ce qui est commun aux deux, et ce qui décide de l'ordre des mots, c'est que le trait d'union soit l'ENCRE LA PLUS À DROITE du bloc entier. Tant qu'il l'est, il ferme la silhouette et se lit comme un signe ; dès que la seconde ligne le dépasse, il tombe à l'intérieur du bloc et redevient une coupure oubliée. Le benchmark le mesure au `Range` sur le dernier caractère de la première ligne, en fraction du corps, avec une marge minimale de 0,02 em — une égalité stricte serait un hasard, pas une composition. Les mesures passent toutes par un `Range`, jamais par la boîte du `h2` : un titre est un BLOC, sa boîte fait la largeur utile quoi qu'on écrive dedans, donc toute comparaison de largeur écrite sur `getBoundingClientRect()` de l'élément sortirait verte sur n'importe quoi. Piège payé sur `layout-14` puis sur `title-04`. La police est épinglée dans le fragment, pour la raison établie par `shape-03` : la loi compare des largeurs d'encre, donc elle dépend de la chasse. Un fragment qui hériterait la police de la page hôte serait vert sous le harnais — qui sert la sienne — et faux chez celui qui le colle. Sur la référence, ce titre ouvre la couverture et ferme la clôture, aux deux mêmes réglages. Il n'y porte aucun accent : le jaune de la charte est ailleurs sur la slide (le carré à astérisque de `shape-04-asterisk-mark`), ce qui est cohérent avec la règle de l'accent unique — un titre qui prendrait la couleur la dépenserait pour rien, alors que le trait d'union lui donne déjà son signe.
Mots-clés
Benchmarks — la géométrie, en assertions vérifiables
Rejouables par node bin/check.mjs title-05-hyphen-break : le pattern se prouve, il ne se croit pas.
| assertion | mesure | attendu |
|---|---|---|
| DEUX lignes, exactement — le composé est un bloc, pas un paragraphe qui coule | (() => { const lines = (el) => { const r = document.createRange(); r.selectNodeContents(el); const rs = [...r.getClientRects()].filter((x) => x.width > 0.5 && x.height > 0.5); const m = new Map(); rs.forEach((x) => { const k = Math.round(x.top); const q = m.get(k); m.set(k, q ? { top: k, left: Math.min(q.left, x.left), right: Math.max(q.right, x.right) } : { top: k, left: x.left, right: x.right }); }); return [...m.values()].sort((a, b) => a.top - b.top); }; const t = [...document.querySelectorAll('.vl-hb__t')]; return t.every((e) => lines(e).length === 2) ? 1 : 0; })() | 1 ± 0 |
| LE RETOUR EST FRAPPÉ, PAS CALCULÉ : le même titre sans son <br>, à mesure libre, tient sur UNE ligne — les deux lignes ne doivent donc rien à la largeur | (() => { const lines = (el) => { const r = document.createRange(); r.selectNodeContents(el); const rs = [...r.getClientRects()].filter((x) => x.width > 0.5 && x.height > 0.5); const m = new Map(); rs.forEach((x) => { const k = Math.round(x.top); const q = m.get(k); m.set(k, q ? { top: k, left: Math.min(q.left, x.left), right: Math.max(q.right, x.right) } : { top: k, left: x.left, right: x.right }); }); return [...m.values()].sort((a, b) => a.top - b.top); }; const t = [...document.querySelectorAll('.vl-hb__t')]; const ok = t.every((e) => { const c = e.cloneNode(true); c.querySelectorAll('br').forEach((x) => x.remove()); c.style.cssText += ';position:absolute;left:-9999px;width:max-content;'; document.body.appendChild(c); const n = lines(c).length; c.remove(); return n === 1; }); return ok ? 1 : 0; })() | 1 ± 0 |
| et ce n'est pas une CÉSURE : le trait d'union remplace l'ESPACE d'une locution de deux mots entiers, jamais une syllabe coupée | (() => { const t = [...document.querySelectorAll('.vl-hb__t')]; return t.every((e) => { const p = e.dataset.phrase || ''; const rendu = e.textContent.replace(/\s+/g, ''); return /\s/.test(p) && !p.includes('-') && rendu.replace(/-/g, '') === p.replace(/\s+/g, '') && rendu.includes('-'); }) ? 1 : 0; })() | 1 ± 0 |
| LE TRAIT D'UNION EST L'ENCRE LA PLUS À DROITE DU BLOC — c'est la loi qui ordonne les deux mots, et non une égalité de longueur entre les lignes | (() => { const lines = (el) => { const r = document.createRange(); r.selectNodeContents(el); const rs = [...r.getClientRects()].filter((x) => x.width > 0.5 && x.height > 0.5); const m = new Map(); rs.forEach((x) => { const k = Math.round(x.top); const q = m.get(k); m.set(k, q ? { top: k, left: Math.min(q.left, x.left), right: Math.max(q.right, x.right) } : { top: k, left: x.left, right: x.right }); }); return [...m.values()].sort((a, b) => a.top - b.top); }; const t = [...document.querySelectorAll('.vl-hb__t')]; return Math.min(...t.map((e) => { const tn = e.firstChild; const r = document.createRange(); r.setStart(tn, tn.length - 1); r.setEnd(tn, tn.length); const h = r.getBoundingClientRect().right; const l2 = lines(e)[1]; return (h - l2.right) / parseFloat(getComputedStyle(e).fontSize); })); })() | ≥ 0.02 |
| les deux lignes partagent UN SEUL FER À GAUCHE — sans lui le trait d'union ne termine plus une silhouette, il flotte | (() => { const lines = (el) => { const r = document.createRange(); r.selectNodeContents(el); const rs = [...r.getClientRects()].filter((x) => x.width > 0.5 && x.height > 0.5); const m = new Map(); rs.forEach((x) => { const k = Math.round(x.top); const q = m.get(k); m.set(k, q ? { top: k, left: Math.min(q.left, x.left), right: Math.max(q.right, x.right) } : { top: k, left: x.left, right: x.right }); }); return [...m.values()].sort((a, b) => a.top - b.top); }; const t = [...document.querySelectorAll('.vl-hb__t')]; return Math.max(...t.map((e) => { const [a, c] = lines(e); return Math.abs(c.left - a.left) / W; })); })() | ≤ 0.002 |
| interligne ÉCRASÉ sous 1 — les deux lignes se touchent presque, sinon la soudure ne se voit pas | num('.vl-hb__t','lineHeight') / num('.vl-hb__t','fontSize') | 0.9 → 0.995 |
| et la boîte du titre ne vaut QUE ses deux interlignes : aucun air ajouté au-dessus ni au-dessous | box('.vl-hb__t').h / (num('.vl-hb__t','fontSize') * (num('.vl-hb__t','lineHeight') / num('.vl-hb__t','fontSize'))) | 1.95 → 2.05 |
| approche NÉGATIVE : elle resserre le composé, un display à approche nulle se disloque | num('.vl-hb__t','letterSpacing') / num('.vl-hb__t','fontSize') | ≤ -0.02 |
| GRAISSE DISPLAY — sous 700 le trait d'union redevient un filet et cesse d'être un signe | (() => { const w = getComputedStyle(document.querySelector('.vl-hb__t')).fontWeight; return parseFloat(w) >= 700 ? 1 : 0; })() | 1 ± 0 |
| casse MIXTE, et par la frappe — en capitales le trait se centre sur une hauteur d'x absente | (() => { const e = document.querySelector('.vl-hb__t'); const s = getComputedStyle(e); return (s.textTransform === 'none' && /[a-z]/.test(e.textContent) && /[A-Z]/.test(e.textContent)) ? 1 : 0; })() | 1 ± 0 |
| césure automatique VERROUILLÉE : le seul trait d'union du titre est celui qui est écrit | (() => { const s = getComputedStyle(document.querySelector('.vl-hb__t')); return (s.hyphens === 'manual' || s.webkitHyphens === 'manual') ? 1 : 0; })() | 1 ± 0 |
| le titre tient le plancher de contraste — c'est le plus gros objet de la slide, il n'en est pas dispensé | contrast('.vl-hb__t') | ≥ 4.5 |
| racine TRANSPARENTE et sans marge : ce bloc se pose dans une colonne qui porte déjà son fond | (() => { const s = getComputedStyle(document.querySelector('.vl-hb')); const p = ['paddingTop','paddingRight','paddingBottom','paddingLeft'].map((k) => parseFloat(s[k])); return (s.backgroundColor === 'rgba(0, 0, 0, 0)' && Math.max(...p) === 0) ? 1 : 0; })() | 1 ± 0 |
Le code
patterns/title-05-hyphen-break.html
Télécharger<!-- title-05-hyphen-break — un titre display de deux lignes dont la première se termine par un
TRAIT D'UNION FRAPPÉ. Ce n'est pas une césure : il n'y a aucun mot coupé. Le trait remplace
l'ESPACE d'une locution de deux mots — « Business Proposal » devient `Business-` / `Proposal`,
« Thank you » devient `Thank-` / `you` — et soude la locution en un composé unique.
La distinction n'est pas de vocabulaire, elle décide du rendu :
• une césure syllabique est un ACCIDENT de composition. Elle dépend de la mesure, elle se
déplace quand la colonne bouge, et une charte la fuit dans un display ;
• un trait d'union de locution est un CHOIX. Il ne bouge pas, parce qu'il ne dépend pas
de la mesure : le retour est frappé, pas calculé. Élargissez le bloc de 40 % et le
titre reste sur deux lignes, au même endroit — c'est le benchmark central.
CE QUI FAIT TENIR LA FORME, et qui explique pourquoi les deux spécimens ci-dessous ont des
longueurs de ligne très différentes sans que la règle change : le trait d'union doit être
l'ENCRE LA PLUS À DROITE du bloc entier. Tant qu'il l'est, il termine la silhouette et se
lit comme un signe. Dès que la seconde ligne le dépasse, il tombe à l'intérieur du bloc et
redevient ce qu'une charte évite — une coupure oubliée. C'est cette contrainte, et non une
égalité de longueur entre les lignes, qui ordonne les deux mots : le plus long d'abord.
Interligne écrasé à 0,98 et approche à -0,03em : les deux lignes se touchent presque, ce qui
est nécessaire ici — un composé soudé par un trait ne peut pas être écarté par de l'air.
Aucune couleur en dur : encre par `--vl-ink`, le fragment n'a ni fond ni marge. -->
<div class="vl-hb">
<h2 class="vl-hb__t" data-phrase="Business Proposal">Business-<br>Proposal</h2>
<h2 class="vl-hb__t" data-phrase="Thank you">Thank-<br>you</h2>
</div>
<style>
/* La police est ÉPINGLÉE, et ce n'est pas décoratif : la loi du fragment porte sur des largeurs
d'encre comparées (le trait d'union contre la seconde ligne). Une chasse héritée de la page
hôte les déplacerait toutes les deux sans qu'aucun benchmark ne bouge — le harnais sert sa
propre police, donc il ne peut pas voir ce défaut-là. Leçon payée sur `shape-03`. */
.vl-hb {
--vl-hb-size: 109px;
--vl-hb-leading: 0.98;
--vl-hb-tracking: -0.03em;
--vl-hb-gap: 72px;
display: flex;
align-items: flex-start;
gap: var(--vl-hb-gap);
width: max-content;
font-family: "Hanken Grotesk", Inter, "Helvetica Neue", Arial, sans-serif;
}
/* Casse MIXTE, et frappée telle quelle. En capitales le trait d'union se centrerait sur une
hauteur d'x qui n'existe plus : il remonterait au milieu des capitales et cesserait de se
lire comme une soudure entre deux mots pour devenir un tiret de séparation. */
.vl-hb__t {
margin: 0;
font-size: var(--vl-hb-size);
line-height: var(--vl-hb-leading);
letter-spacing: var(--vl-hb-tracking);
font-weight: 800;
text-transform: none;
color: var(--vl-black);
/* `manual` verrouille l'intention : même sous un moteur qui coupe automatiquement, le seul
trait d'union du titre reste celui qui est frappé. */
hyphens: manual;
-webkit-hyphens: manual;
}
</style>
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/title-05-hyphen-break.json (télécharger)
{
"name": "Titre display soudé par un trait d'union",
"family": "title",
"ref": "ref-05-proposal-acid-yellow",
"intent": "Souder une locution de deux mots en un composé unique de deux lignes, en remplaçant son ESPACE par un trait d'union frappé : « Business Proposal » devient `Business-` / `Proposal`. Le retour n'est pas calculé par la mesure, il est écrit — donc il ne bouge jamais — et le trait d'union, posé en encre la plus à droite du bloc, termine la silhouette au lieu de signaler une coupure oubliée.",
"when_to_use": "Une couverture, une slide de clôture, un hero : partout où un display de deux mots doit faire MASSE plutôt que ligne, sur une charte qui n'a pas d'accent coloré à dépenser sur son titre. Le trait d'union est alors le seul signe graphique du bloc, et il coûte zéro couleur. Sert aussi à ancrer un titre au fer à gauche d'une colonne dont le bas porte autre chose : les deux lignes serrées font un bloc franc, pas un texte qui coule.",
"avoid_when": "La locution ne tient pas en deux mots, ou le second mot est plus long que le premier plus son trait : le trait d'union retombe alors À L'INTÉRIEUR de la silhouette et se lit comme une césure ratée — c'est la seule contrainte dure du pattern. Écarter aussi en capitales (le trait se centre sur une hauteur d'x absente et devient un tiret de séparation), et pour un nom de marque d'un seul mot qu'on veut régler à la largeur — c'est title-04-name-fills-measure.",
"media": [
"slide",
"web"
],
"tags": [
"titre",
"display",
"trait-union",
"cesure",
"couverture",
"cloture",
"locution",
"interligne-ecrase",
"fer-a-gauche",
"grotesk"
],
"vars": [
{
"name": "--vl-black",
"role": "encre du titre — une seule valeur pour tout le bloc, trait d'union compris : c'est ce qui le fait lire comme un signe typographique et non comme un ornement",
"default": "#111111"
},
{
"name": "--vl-hb-size",
"role": "corps du display, pris dans l'échelle de la charte (109 px sur une slide 1600×900)",
"default": "109px"
},
{
"name": "--vl-hb-leading",
"role": "interligne, écrasé sous 1 — un composé soudé par un trait ne peut pas être écarté par de l'air",
"default": "0.98"
},
{
"name": "--vl-hb-tracking",
"role": "approche négative du display",
"default": "-0.03em"
},
{
"name": "--vl-hb-gap",
"role": "écart entre les deux spécimens de la démonstration — sans effet sur la règle",
"default": "72px"
}
],
"slots": [
"title"
],
"geometry": {
"root": ".vl-hb",
"frame": [
905,
214
],
"ratios": {
"leading": 0.98,
"tracking": -0.03,
"block_height_in_lines": 2
},
"type_px": {
"display": 109
},
"pad_ratio": {
"top": 0,
"x": 0,
"bottom": 0
}
},
"benchmarks": [
{
"name": "DEUX lignes, exactement — le composé est un bloc, pas un paragraphe qui coule",
"measure": "(() => { const lines = (el) => { const r = document.createRange(); r.selectNodeContents(el); const rs = [...r.getClientRects()].filter((x) => x.width > 0.5 && x.height > 0.5); const m = new Map(); rs.forEach((x) => { const k = Math.round(x.top); const q = m.get(k); m.set(k, q ? { top: k, left: Math.min(q.left, x.left), right: Math.max(q.right, x.right) } : { top: k, left: x.left, right: x.right }); }); return [...m.values()].sort((a, b) => a.top - b.top); }; const t = [...document.querySelectorAll('.vl-hb__t')]; return t.every((e) => lines(e).length === 2) ? 1 : 0; })()",
"expect": 1,
"tol": 0
},
{
"name": "LE RETOUR EST FRAPPÉ, PAS CALCULÉ : le même titre sans son <br>, à mesure libre, tient sur UNE ligne — les deux lignes ne doivent donc rien à la largeur",
"measure": "(() => { const lines = (el) => { const r = document.createRange(); r.selectNodeContents(el); const rs = [...r.getClientRects()].filter((x) => x.width > 0.5 && x.height > 0.5); const m = new Map(); rs.forEach((x) => { const k = Math.round(x.top); const q = m.get(k); m.set(k, q ? { top: k, left: Math.min(q.left, x.left), right: Math.max(q.right, x.right) } : { top: k, left: x.left, right: x.right }); }); return [...m.values()].sort((a, b) => a.top - b.top); }; const t = [...document.querySelectorAll('.vl-hb__t')]; const ok = t.every((e) => { const c = e.cloneNode(true); c.querySelectorAll('br').forEach((x) => x.remove()); c.style.cssText += ';position:absolute;left:-9999px;width:max-content;'; document.body.appendChild(c); const n = lines(c).length; c.remove(); return n === 1; }); return ok ? 1 : 0; })()",
"expect": 1,
"tol": 0
},
{
"name": "et ce n'est pas une CÉSURE : le trait d'union remplace l'ESPACE d'une locution de deux mots entiers, jamais une syllabe coupée",
"measure": "(() => { const t = [...document.querySelectorAll('.vl-hb__t')]; return t.every((e) => { const p = e.dataset.phrase || ''; const rendu = e.textContent.replace(/\\s+/g, ''); return /\\s/.test(p) && !p.includes('-') && rendu.replace(/-/g, '') === p.replace(/\\s+/g, '') && rendu.includes('-'); }) ? 1 : 0; })()",
"expect": 1,
"tol": 0
},
{
"name": "LE TRAIT D'UNION EST L'ENCRE LA PLUS À DROITE DU BLOC — c'est la loi qui ordonne les deux mots, et non une égalité de longueur entre les lignes",
"measure": "(() => { const lines = (el) => { const r = document.createRange(); r.selectNodeContents(el); const rs = [...r.getClientRects()].filter((x) => x.width > 0.5 && x.height > 0.5); const m = new Map(); rs.forEach((x) => { const k = Math.round(x.top); const q = m.get(k); m.set(k, q ? { top: k, left: Math.min(q.left, x.left), right: Math.max(q.right, x.right) } : { top: k, left: x.left, right: x.right }); }); return [...m.values()].sort((a, b) => a.top - b.top); }; const t = [...document.querySelectorAll('.vl-hb__t')]; return Math.min(...t.map((e) => { const tn = e.firstChild; const r = document.createRange(); r.setStart(tn, tn.length - 1); r.setEnd(tn, tn.length); const h = r.getBoundingClientRect().right; const l2 = lines(e)[1]; return (h - l2.right) / parseFloat(getComputedStyle(e).fontSize); })); })()",
"min": 0.02
},
{
"name": "les deux lignes partagent UN SEUL FER À GAUCHE — sans lui le trait d'union ne termine plus une silhouette, il flotte",
"measure": "(() => { const lines = (el) => { const r = document.createRange(); r.selectNodeContents(el); const rs = [...r.getClientRects()].filter((x) => x.width > 0.5 && x.height > 0.5); const m = new Map(); rs.forEach((x) => { const k = Math.round(x.top); const q = m.get(k); m.set(k, q ? { top: k, left: Math.min(q.left, x.left), right: Math.max(q.right, x.right) } : { top: k, left: x.left, right: x.right }); }); return [...m.values()].sort((a, b) => a.top - b.top); }; const t = [...document.querySelectorAll('.vl-hb__t')]; return Math.max(...t.map((e) => { const [a, c] = lines(e); return Math.abs(c.left - a.left) / W; })); })()",
"max": 0.002
},
{
"name": "interligne ÉCRASÉ sous 1 — les deux lignes se touchent presque, sinon la soudure ne se voit pas",
"measure": "num('.vl-hb__t','lineHeight') / num('.vl-hb__t','fontSize')",
"min": 0.9,
"max": 0.995
},
{
"name": "et la boîte du titre ne vaut QUE ses deux interlignes : aucun air ajouté au-dessus ni au-dessous",
"measure": "box('.vl-hb__t').h / (num('.vl-hb__t','fontSize') * (num('.vl-hb__t','lineHeight') / num('.vl-hb__t','fontSize')))",
"min": 1.95,
"max": 2.05
},
{
"name": "approche NÉGATIVE : elle resserre le composé, un display à approche nulle se disloque",
"measure": "num('.vl-hb__t','letterSpacing') / num('.vl-hb__t','fontSize')",
"max": -0.02
},
{
"name": "GRAISSE DISPLAY — sous 700 le trait d'union redevient un filet et cesse d'être un signe",
"measure": "(() => { const w = getComputedStyle(document.querySelector('.vl-hb__t')).fontWeight; return parseFloat(w) >= 700 ? 1 : 0; })()",
"expect": 1,
"tol": 0
},
{
"name": "casse MIXTE, et par la frappe — en capitales le trait se centre sur une hauteur d'x absente",
"measure": "(() => { const e = document.querySelector('.vl-hb__t'); const s = getComputedStyle(e); return (s.textTransform === 'none' && /[a-z]/.test(e.textContent) && /[A-Z]/.test(e.textContent)) ? 1 : 0; })()",
"expect": 1,
"tol": 0
},
{
"name": "césure automatique VERROUILLÉE : le seul trait d'union du titre est celui qui est écrit",
"measure": "(() => { const s = getComputedStyle(document.querySelector('.vl-hb__t')); return (s.hyphens === 'manual' || s.webkitHyphens === 'manual') ? 1 : 0; })()",
"expect": 1,
"tol": 0
},
{
"name": "le titre tient le plancher de contraste — c'est le plus gros objet de la slide, il n'en est pas dispensé",
"measure": "contrast('.vl-hb__t')",
"min": 4.5
},
{
"name": "racine TRANSPARENTE et sans marge : ce bloc se pose dans une colonne qui porte déjà son fond",
"measure": "(() => { const s = getComputedStyle(document.querySelector('.vl-hb')); const p = ['paddingTop','paddingRight','paddingBottom','paddingLeft'].map((k) => parseFloat(s[k])); return (s.backgroundColor === 'rgba(0, 0, 0, 0)' && Math.max(...p) === 0) ? 1 : 0; })()",
"expect": 1,
"tol": 0
}
],
"notes": "Le pattern ne porte pas un trait d'union, il porte une DISTINCTION. Une césure syllabique est un accident de composition : elle dépend de la mesure, elle se déplace quand la colonne bouge, et une charte la fuit dans un display. Un trait d'union de locution est un choix : il soude deux mots entiers, et le retour est frappé — donc il ne dépend de rien. Deux benchmarks séparent les deux cas, et aucun des deux n'est cosmétique. Le premier retire le `<br>` d'un clone à mesure libre et exige UNE ligne : si les deux lignes venaient de la largeur, le clone en garderait deux. Le second recompose le texte rendu et exige qu'il vaille la locution de `data-phrase` une fois l'espace et le trait retirés — un mot coupé échouerait, puisque `data-phrase` porte deux mots entiers séparés par une espace.\n\nLa loi de forme n'est PAS une égalité de longueur entre les lignes, et c'est ce qu'on croit d'abord en regardant `Business-` / `Proposal`. Le second spécimen la dément : `Thank-` / `you` est franchement descendant. Ce qui est commun aux deux, et ce qui décide de l'ordre des mots, c'est que le trait d'union soit l'ENCRE LA PLUS À DROITE du bloc entier. Tant qu'il l'est, il ferme la silhouette et se lit comme un signe ; dès que la seconde ligne le dépasse, il tombe à l'intérieur du bloc et redevient une coupure oubliée. Le benchmark le mesure au `Range` sur le dernier caractère de la première ligne, en fraction du corps, avec une marge minimale de 0,02 em — une égalité stricte serait un hasard, pas une composition.\n\nLes mesures passent toutes par un `Range`, jamais par la boîte du `h2` : un titre est un BLOC, sa boîte fait la largeur utile quoi qu'on écrive dedans, donc toute comparaison de largeur écrite sur `getBoundingClientRect()` de l'élément sortirait verte sur n'importe quoi. Piège payé sur `layout-14` puis sur `title-04`.\n\nLa police est épinglée dans le fragment, pour la raison établie par `shape-03` : la loi compare des largeurs d'encre, donc elle dépend de la chasse. Un fragment qui hériterait la police de la page hôte serait vert sous le harnais — qui sert la sienne — et faux chez celui qui le colle.\n\nSur la référence, ce titre ouvre la couverture et ferme la clôture, aux deux mêmes réglages. Il n'y porte aucun accent : le jaune de la charte est ailleurs sur la slide (le carré à astérisque de `shape-04-asterisk-mark`), ce qui est cohérent avec la règle de l'accent unique — un titre qui prendrait la couleur la dépenserait pour rien, alors que le trait d'union lui donne déjà son signe.",
"id": "title-05-hyphen-break"
}
À côté
layout-16-header-tripartite
Header tri-parti à filet, centre sur l'axe
shape-04-asterisk-mark
Astérisque de marque, six branches
title-01-leading-rule
Titre à filet d'accent collé
title-02-condensed-overlay-stack
Cadre typographique condensé en overlay
title-03-wordmark-bottom-left