{
  "name": "Quadrant à épicentre",
  "family": "layout",
  "ref": "ref-21-finengy-quadrant-clair",
  "intent": "Poser quatre modules de VOLUMES très inégaux sans que la grille impose leurs tailles : chaque vignette n'est ancrée que par son coin vif, qui pointe le centre du bloc, et ses deux autres bords sont libres — donc sa largeur ET sa hauteur lui appartiennent. Les quatre coins vifs convergent sur un point unique, l'épicentre, où se serrent aussi les quatre numéros. C'est ce point qui tient la composition à la place des alignements de bord, et c'est pour ça que quatre tailles différentes ne s'y lisent pas comme un défaut de calage.",
  "when_to_use": "Quand quatre blocs de contenu réel — pas quatre KPI de même longueur — doivent cohabiter : un module à onze points, un à trois phrases longues, un à six points, un à une phrase. Un bento y répondrait en étirant les petits et en creusant des vides ; ici le vide n'existe pas, parce qu'aucune vignette n'est tenue de rattraper sa voisine. Vaut aussi dès que la numérotation doit se lire comme un PARCOURS et non comme quatre étiquettes indépendantes : réunis au centre, les numéros donnent l'ordre sans qu'aucune flèche ne soit dessinée.",
  "avoid_when": "À moins de quatre modules, ou à plus : la forme est celle d'un quadrant, un cinquième bloc n'a plus de coin à occuper. Sous ~1100 px de largeur non plus — les quatre vignettes ne tiennent plus côte à côte, et le repli en pile fait disparaître l'épicentre, donc le pattern avec lui (au repli, on garde les vignettes et on rend l'ordre par l'index, pas par la position). Et jamais avec des contenus de même volume : quatre tailles identiques rendent la liberté du pattern invisible et une grille ordinaire fera mieux.",
  "media": [
    "web",
    "slide"
  ],
  "tags": [
    "quadrant",
    "epicentre",
    "bento",
    "coin",
    "rayon",
    "asymetrie",
    "numerotation",
    "pastille",
    "module",
    "pilule",
    "liste",
    "fleche"
  ],
  "vars": [
    {
      "name": "--vl-qe-block",
      "role": "la largeur du bloc — le pattern vit entre 1100 et 1400 px, au-dessous il se replie en pile",
      "default": "1320px"
    },
    {
      "name": "--vl-qe-seam",
      "role": "la couture entre vignettes, et le SEUL écart admis à l'épicentre : c'est elle que mesure le benchmark de convergence",
      "default": "14px"
    },
    {
      "name": "--vl-qe-radius",
      "role": "le rayon des TROIS coins arrondis, en LONGUEUR (jamais un %) — 0,070 de la largeur du bloc ici",
      "default": "92px"
    },
    {
      "name": "--vl-qe-badge",
      "role": "le diamètre de la pastille de numéro, collée dans le coin vif ; elle dicte la réserve de place de ce côté",
      "default": "60px"
    },
    {
      "name": "--vl-qe-pad",
      "role": "le rembourrage de vignette — plancher 0,46 × le rayon, sinon la première ligne frôle l'arc",
      "default": "44px"
    },
    {
      "name": "--vl-qe-w",
      "role": "la largeur PROPRE d'une vignette, posée sur elle et mesurée sur sa substance (plafonnée par sa colonne)",
      "default": "665px / 585px / 610px / 480px"
    }
  ],
  "slots": [
    {
      "name": "numero",
      "selector": ".vl-qe__n",
      "role": "le rang du module, dans le coin vif"
    },
    {
      "name": "titre",
      "selector": ".vl-qe__h",
      "role": "le nom du module, en très gros"
    },
    {
      "name": "categorie",
      "selector": ".vl-qe__k",
      "role": "le titre catégoriel, en pilule contournée"
    },
    {
      "name": "points",
      "selector": ".vl-qe__pts li",
      "role": "un point de liste, marqué d'une flèche"
    },
    {
      "name": "phrase",
      "selector": ".vl-qe__say",
      "role": "le module sans liste : une phrase, lue en grand"
    }
  ],
  "geometry": {
    "root": ".vl-quadrant-epicentre",
    "frame": [
      1320,
      1498
    ],
    "ratios": {
      "seam_over_block": 0.0106,
      "radius_over_block": 0.0697,
      "badge_over_block": 0.0455,
      "pad_over_block": 0.0333,
      "pad_floor_over_radius": 0.46,
      "colonnes_fr": [
        1.1,
        1
      ],
      "largeurs_over_block": [
        0.5038,
        0.4432,
        0.4621,
        0.3636
      ],
      "coins_vifs": 1,
      "vignettes": 4
    },
    "type_px": {
      "titre": 54,
      "etiquette": 23,
      "corps": 21,
      "numero": 23,
      "phrase": 34
    },
    "pad_ratio": {
      "top": 0.0333,
      "x": 0.0333,
      "bottom": 0.0333
    }
  },
  "benchmarks": [
    {
      "name": "UN SEUL coin droit par vignette — deux et la forme redevient une gélule, zéro et c'est un squircle",
      "measure": "(() => { const P = ['borderTopLeftRadius','borderTopRightRadius','borderBottomRightRadius','borderBottomLeftRadius']; return ['tl','tr','bl','br'].every((k) => P.map((p) => num('.vl-qe--' + k, p)).filter((v) => v < 0.5).length === 1) ? 1 : 0; })()",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "et ce coin regarde le CENTRE, pas le coin de la planche : bas-droit en haut-gauche, bas-gauche en haut-droit, et l'inverse en bas — c'est ce qui interdit de permuter deux vignettes",
      "measure": "(() => { const P = ['borderTopLeftRadius','borderTopRightRadius','borderBottomRightRadius','borderBottomLeftRadius']; const want = { tl: 2, tr: 3, bl: 1, br: 0 }; return Object.entries(want).every(([k, i]) => P.map((p) => num('.vl-qe--' + k, p)).findIndex((v) => v < 0.5) === i) ? 1 : 0; })()",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "ÉPICENTRE — les quatre coins vifs tombent sur le même point, à la couture près : leur dispersion ne dépasse pas l'écart entre vignettes",
      "measure": "(() => { const want = { tl: 2, tr: 3, bl: 1, br: 0 }; const pts = Object.entries(want).map(([k, i]) => { const b = box('.vl-qe--' + k); return [[b.left,b.top],[b.right,b.top],[b.right,b.bottom],[b.left,b.bottom]][i]; }); const xs = pts.map((p) => p[0]), ys = pts.map((p) => p[1]); return Math.max(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys)) / W; })()",
      "max": 0.012
    },
    {
      "name": "AUCUNE SYMÉTRIE — les quatre largeurs sont distinctes, et les quatre hauteurs aussi : une vignette qui rattrape sa voisine trahit un étirement, donc un vide fabriqué",
      "measure": "(() => { const b = ['tl','tr','bl','br'].map((k) => box('.vl-qe--' + k)); return (new Set(b.map((x) => Math.round(x.w))).size === 4 && new Set(b.map((x) => Math.round(x.h))).size === 4) ? 1 : 0; })()",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "RAYON EN LONGUEUR — le même rayon en pixels sur quatre vignettes de largeurs DIFFÉRENTES : un `border-radius` en % y donnerait quatre valeurs et des arcs elliptiques",
      "measure": "(() => { const P = ['borderTopLeftRadius','borderTopRightRadius','borderBottomRightRadius','borderBottomLeftRadius']; const all = ['tl','tr','bl','br'].flatMap((k) => P.map((p) => num('.vl-qe--' + k, p)).filter((v) => v >= 0.5)); return (Math.max(...all) - Math.min(...all)) / Math.max(...all); })()",
      "max": 0.003
    },
    {
      "name": "NUMÉRO CALÉ PILE DANS L'ANGLE — la pastille touche les deux arêtes du coin vif : c'est ce qui regroupe les quatre numéros autour de l'épicentre au lieu de les laisser flotter",
      "measure": "(() => { const want = { tl: 2, tr: 3, bl: 1, br: 0 }; let worst = 0; for (const [k, i] of Object.entries(want)) { const b = box('.vl-qe--' + k), n = box('.vl-qe--' + k + ' .vl-qe__n'); const c = [[b.left,b.top],[b.right,b.top],[b.right,b.bottom],[b.left,b.bottom]][i]; const dx = Math.min(Math.abs(n.left - c[0]), Math.abs(n.right - c[0])); const dy = Math.min(Math.abs(n.top - c[1]), Math.abs(n.bottom - c[1])); worst = Math.max(worst, (dx + dy) / W); } return worst; })()",
      "max": 0.004
    },
    {
      "name": "RÉSERVE DU COIN VIF — rien ne passe sous la pastille : la vignette réserve la place du numéro du côté où il se pose",
      "measure": "(() => { let n = 0; for (const k of ['tl','tr','bl','br']) { const art = document.querySelector('.vl-qe--' + k); const b = art.querySelector('.vl-qe__n').getBoundingClientRect(); for (const el of art.querySelectorAll('.vl-qe__h, .vl-qe__k, .vl-qe__pts li, .vl-qe__say')) { const r = el.getBoundingClientRect(); if (!(r.right <= b.left || r.left >= b.right || r.bottom <= b.top || r.top >= b.bottom)) n++; } } return n; })()",
      "expect": 0,
      "tol": 0
    },
    {
      "name": "AUCUNE ENCRE GRISE — le corps des listes prend exactement l'encre de son titre : un gris intermédiaire sur bleu pâle tombe sous 5:1 et se lit comme du texte désactivé",
      "measure": "(() => { return ['tl','tr','bl'].every((k) => cs('.vl-qe--' + k + ' .vl-qe__pts li', 'color') === cs('.vl-qe--' + k + ' .vl-qe__h', 'color')) ? 1 : 0; })()",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "PUCE = FLÈCHE, jamais un tiret : le point ne sépare pas deux lignes, il en désigne une",
      "measure": "(() => { const c = getComputedStyle(document.querySelector('.vl-qe__pts li'), '::before').content; return c.includes('\\u2192') ? 1 : 0; })()",
      "expect": 1,
      "tol": 0
    },
    {
      "name": "POINTS SERRÉS — interligne ≤ 1,35 et écart entre deux points ≤ 0,35 em : au-delà, la liste redevient une suite de paragraphes",
      "measure": "(() => { const fs = num('.vl-qe__pts li', 'fontSize'); const lh = num('.vl-qe__pts li', 'lineHeight') / fs; const gap = num('.vl-qe__pts li + li', 'marginTop') / fs; return Math.max(lh / 1.35, gap / 0.35); })()",
      "max": 1
    },
    {
      "name": "PLANCHER DE CORPS — les points font au moins 0,0145 de la largeur du bloc (19 px sur 1320) : c'est le petit texte que le pattern est venu supprimer",
      "measure": "num('.vl-qe__pts li', 'fontSize') / W",
      "min": 0.0145
    },
    {
      "name": "SAUT DE HIÉRARCHIE — le titre de vignette fait au moins 2,4 fois le corps : entre les deux il n'y a que la pilule catégorielle, à 1,1 fois le corps",
      "measure": "num('.vl-qe__h', 'fontSize') / num('.vl-qe__pts li', 'fontSize')",
      "min": 2.4
    },
    {
      "name": "TITRE CATÉGORIEL EN PILULE — rayon plein (≥ la demi-hauteur) et contour : c'est la MÊME forme qui sert les deux niveaux d'étiquette du bloc",
      "measure": "(() => { const b = box('.vl-qe__k'); return num('.vl-qe__k', 'borderTopLeftRadius') / (b.h / 2); })()",
      "min": 1
    }
  ],
  "notes": "**Ce que ce pattern ajoute à `shape-02-teardrop-quadrant`, et qui n'est pas dans la primitive.** La primitive pose un carré arrondi dont un seul coin reste vif, et son coin vif désigne le coin de la planche qu'il occupe. Ici il désigne l'INVERSE — le centre — et cette inversion change la nature de la composition : les quatre formes ne bordent plus un bloc, elles RAYONNENT d'un point. Le benchmark d'orientation est donc l'exact miroir de celui de la primitive, et les deux ne peuvent pas être vrais en même temps ; c'est voulu, et c'est ce qui empêche de confondre les deux patterns.\n\n**L'ancrage par un seul coin est le mécanisme, tout le reste en découle.** Une vignette du haut est calée par le BAS (`align-self: end`) et par le CÔTÉ (`justify-self`) ; une vignette du bas par le HAUT. Ses deux autres bords ne sont tenus par rien, donc sa hauteur est celle de son contenu et sa largeur celle qu'on lui donne (`--vl-qe-w`, plafonnée par sa colonne). C'est la réponse à la faute classique du bento : quatre cellules d'une grille s'étirent à la plus grande, et le module le plus court récolte 200 px de vide qu'on essaie ensuite de meubler. Ici il n'y a rien à meubler.\n\n**Les colonnes ne fixent pas les largeurs, elles les plafonnent.** `grid-template-columns: 1.1fr 1fr` donne la place disponible de chaque côté ; la largeur réelle de chaque vignette est posée sur elle. Sans ce plafond, une vignette pourrait franchir l'axe et l'épicentre serait faux ; sans la largeur propre, on retomberait sur deux colonnes symétriques.\n\n**La pastille collée à l'angle (inset 0) n'est pas un détail de placement.** Posée à la distance du rembourrage, elle se lit comme une étiquette de coin, et les quatre se dispersent ; collée à l'arête, les quatre se rejoignent autour de l'épicentre et la numérotation devient la MARQUE du centre. C'est pour ça que la vignette réserve `badge + seam` du côté du coin vif : la place du numéro n'est pas négociable, elle est la contrepartie du geste.\n\n**Deux règles de lecture sont mesurées comme le reste, parce qu'elles ont été payées.** L'encre grise (`--fx-text-muted`, un #5A5A5A) rendait les listes molles sur bleu pâle : le corps prend désormais l'encre de sa vignette, blanche sur marine, marine sur clair. Et les filets qui séparaient les points ÉCARTAIENT autant qu'ils séparaient — supprimés, remplacés par une flèche et un interligne de 1,32.\n\n**Origine.** Reversé le 21/08/2026 depuis la section « Nos propositions de valeur » du site Finengy Advisory (refonte 2026, `finengy-refonte/site/src/pages/notre-savoir-faire.astro`), après six tours d'arbitrage avec Léo. Ce n'est donc pas un visuel tiers relevé à la pipette : les cotes du deck SONT celles de la page, mesurées à 1440 px de fenêtre.",
  "id": "layout-17-quadrant-epicentre"
}