card-01-ghost-icon
card-09-gradient-metric-curve
KPI sur aplat coloré à courbe annotée
Faire de la seule carte colorée d'un écran le porteur d'un indicateur unique : la pile libellé / chiffre / cadrage en haut-gauche, une courbe sans axe qui traverse la carte derrière elle, annotée à ses deux extrémités seulement.
cardslideweb790×238px10 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
Un indicateur de premier rang doit émerger d'un tableau de bord par ailleurs neutre, et sa trajectoire compte autant que sa valeur du jour.
Éviter quand
Deux indicateurs se disputent le premier rang (deux cartes colorées et il n'y en a plus aucune), ou les valeurs intermédiaires doivent être lisibles : ici la courbe n'a ni axe ni grille, elle donne une forme, pas des chiffres.
Notes
Le seuil de contraste est à 3 et non à 4,5 : du blanc sur un bleu de marque saturé plafonne autour de 3,3:1, et exiger 4,5 obligerait à noircir l'aplat — c'est-à-dire à perdre exactement ce qui met la carte au premier rang. Ce que le benchmark garantit en échange, c'est que personne ne glisse vers un bleu plus clair sans s'en apercevoir. Le dégradé déclare `background-color` sous son `background-image`, sinon `bgOf()` traverse la carte et mesure le contraste contre la page. Le prolongement de la courbe après le point d'arrivée est tracé dans une SECONDE `<path>` en encre atténuée : c'est ce qui distingue le mesuré du décoratif, et le fusionner en un seul tracé ferait mentir la carte.
Mots-clés
Benchmarks — la géométrie, en assertions vérifiables
Rejouables par node bin/check.mjs card-09-gradient-metric-curve : le pattern se prouve, il ne se croit pas.
| assertion | mesure | attendu |
|---|---|---|
| le chiffre pèse au moins le double du libellé qui le nomme | num('.vl-metric__fig','fontSize') / num('.vl-metric__label','fontSize') | ≥ 2 |
| la pile de KPI tient dans la moitié gauche : la courbe garde sa place | box('.vl-metric__head').w / W | ≥ 0.85 |
| le chiffre ne descend pas dans la moitié basse de la carte (là où passe la courbe) | box('.vl-metric__fig').bottom / H | ≤ 0.5 |
| l'annotation d'arrivée est au-dessus de celle de départ : la courbe monte | box('.vl-metric__tag--from').top - box('.vl-metric__tag--to').top | ≥ 60 |
| les deux annotations encadrent la course mesurée (≥ 60 % de la largeur) | (box('.vl-metric__tag--to').left - box('.vl-metric__tag--from').left) / W | ≥ 0.6 |
| exactement deux annotations : on annote les bouts, pas les points intermédiaires | document.querySelectorAll('.vl-metric__tag').length | 2 ± 0 |
| la courbe est en fond et occupe toute la carte | box('.vl-metric__curve').w / W | 1 ± 0.02 |
| contraste du chiffre sur l'aplat ≥ 3 (blanc sur bleu saturé) | contrast('.vl-metric__fig') | ≥ 3 |
| contraste du libellé sur l'aplat ≥ 3 | contrast('.vl-metric__label') | ≥ 3 |
| aucun débordement du contenu hors de la carte | overflow('.vl-metric') | ≤ 0 |
Le code
patterns/card-09-gradient-metric-curve.html
Télécharger<!-- card-09-gradient-metric-curve — fragment autonome. Couleurs via --vl-* uniquement, aucune valeur en dur. -->
<div class="vl-metric">
<svg class="vl-metric__curve" viewBox="0 0 790 238" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M50 186 C 100 168, 128 200, 175 182 C 222 164, 240 190, 283 172
C 326 154, 337 184, 380 158 C 423 132, 442 170, 482 145
C 528 116, 555 118, 600 88"
stroke="var(--vl-ink-invert)" stroke-width="2.4" stroke-linecap="round"/>
<path d="M600 88 C 640 62, 675 70, 710 102 C 742 132, 762 152, 790 164"
stroke="var(--vl-ink-invert-muted)" stroke-width="2" stroke-linecap="round"/>
<circle cx="50" cy="186" r="4.5" fill="var(--vl-ink-invert)"/>
<circle cx="600" cy="88" r="6" fill="var(--vl-ink-invert)"/>
</svg>
<span class="vl-metric__tag vl-metric__tag--from">7.26k</span>
<span class="vl-metric__tag vl-metric__tag--to">16.75k</span>
<div class="vl-metric__head">
<div>
<span class="vl-metric__label">Financial health</span>
<div class="vl-metric__fig">85%</div>
<div class="vl-metric__sub">since last month</div>
</div>
<span class="vl-metric__btn">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.2 6.4A5.4 5.4 0 0 0 3.4 5.2M2.8 9.6a5.4 5.4 0 0 0 9.8 1.2"
stroke="var(--vl-ink-invert)" stroke-width="1.4" stroke-linecap="round"/>
<path d="M13.6 2.9v3.6h-3.6M2.4 13.1V9.5h3.6"
stroke="var(--vl-ink-invert)" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
</div>
</div>
<style>
/* La SEULE carte colorée d'une planche par ailleurs en verre : c'est son aplat qui la met au
premier rang, pas sa taille. Le KPI est empilé en haut-gauche (libellé → chiffre → cadrage
temporel) et la courbe traverse toute la carte derrière lui, sans axe ni grille. Deux
annotations seulement, aux DEUX bouts de ce qui est mesuré ; après le point d'arrivée le
trait continue en blanc atténué — ce prolongement est du décor, et son atténuation dit
exactement ça. Le chiffre ne se pose jamais sur la courbe : la pile occupe le quart
supérieur gauche, la courbe part du quart inférieur gauche. */
.vl-metric {
position: relative; width: 790px; height: 238px; padding: 24px 28px; overflow: hidden;
border-radius: var(--vl-radius-card);
color: var(--vl-ink-invert);
background-color: var(--vl-blue);
background-image: linear-gradient(128deg, var(--vl-blue) 0%, var(--vl-blue) 34%, var(--vl-blue-light) 100%);
font-family: Inter, -apple-system, "Helvetica Neue", Arial, sans-serif;
}
.vl-metric__curve { position: absolute; inset: 0; width: 100%; height: 100%; }
.vl-metric__head { position: relative; z-index: 2; display: flex; align-items: flex-start; justify-content: space-between; }
.vl-metric__label { font-size: 15px; font-weight: 500; letter-spacing: -0.01em; }
.vl-metric__fig { margin-top: 12px; font-size: 32px; font-weight: 600; letter-spacing: -0.025em; line-height: 1; }
.vl-metric__sub { margin-top: 5px; font-size: 11px; color: var(--vl-ink-invert-muted); }
.vl-metric__btn {
width: 34px; height: 34px; border-radius: 50%; flex: 0 0 34px;
display: inline-flex; align-items: center; justify-content: center;
background-color: var(--vl-surface-glass-soft);
box-shadow: inset 0 1px 0 var(--vl-sheen);
}
.vl-metric__btn svg { width: 16px; height: 16px; }
.vl-metric__tag { position: absolute; z-index: 2; font-size: 11px; font-weight: 500; color: var(--vl-ink-invert); }
.vl-metric__tag--from { left: 60px; top: 154px; }
.vl-metric__tag--to { left: 616px; top: 52px; }
</style>
systems/ref-13-glass-fintech-dashboard.json
Télécharger:root {
--vl-bg: #B4BEDD;
--vl-grad-1: #A3B0DA;
--vl-grad-2: #98A9D6;
--vl-grad-3: #C8CEDA;
--vl-surface-glass: rgba(255, 255, 255, 0.45);
--vl-surface-glass-strong: rgba(255, 255, 255, 0.68);
--vl-surface-glass-soft: rgba(255, 255, 255, 0.24);
--vl-surface-dark: #101625;
--vl-sheen: rgba(255, 255, 255, 0.45);
--vl-blue: #4F7CF7;
--vl-blue-light: #8FB6FA;
--vl-violet: #8B5CF6;
--vl-violet-deep: #5B7CF0;
--vl-violet-halo: rgba(123, 106, 240, 0.42);
--vl-ink: #1A1F2E;
--vl-ink-muted: #565C72;
--vl-ink-invert: #FFFFFF;
--vl-ink-invert-muted: rgba(255, 255, 255, 0.72);
--vl-radius-card: 28px;
--vl-radius-sub: 18px;
--vl-gap: 22px;
--vl-margin: 44px;
--vl-blur: 18px;
}
les métadonnées — patterns/card-09-gradient-metric-curve.json (télécharger)
{
"name": "KPI sur aplat coloré à courbe annotée",
"family": "card",
"ref": "ref-13-glass-fintech-dashboard",
"intent": "Faire de la seule carte colorée d'un écran le porteur d'un indicateur unique : la pile libellé / chiffre / cadrage en haut-gauche, une courbe sans axe qui traverse la carte derrière elle, annotée à ses deux extrémités seulement.",
"when_to_use": "Un indicateur de premier rang doit émerger d'un tableau de bord par ailleurs neutre, et sa trajectoire compte autant que sa valeur du jour.",
"avoid_when": "Deux indicateurs se disputent le premier rang (deux cartes colorées et il n'y en a plus aucune), ou les valeurs intermédiaires doivent être lisibles : ici la courbe n'a ni axe ni grille, elle donne une forme, pas des chiffres.",
"media": [
"slide",
"web"
],
"tags": [
"carte",
"kpi",
"indicateur",
"courbe",
"sparkline",
"degrade",
"aplat",
"annotation",
"trajectoire",
"dashboard",
"premier-rang"
],
"vars": [
{
"name": "--vl-blue",
"role": "extrémité saturée de l'aplat",
"default": "#4F7CF7"
},
{
"name": "--vl-blue-light",
"role": "extrémité claire de l'aplat",
"default": "#8FB6FA"
},
{
"name": "--vl-ink-invert",
"role": "texte, courbe mesurée et points",
"default": "#FFFFFF"
},
{
"name": "--vl-ink-invert-muted",
"role": "cadrage temporel + prolongement décoratif de la courbe",
"default": "rgba(255, 255, 255, 0.72)"
},
{
"name": "--vl-surface-glass-soft",
"role": "bouton rond translucide",
"default": "rgba(255, 255, 255, 0.24)"
}
],
"slots": [
"label",
"figure",
"sub",
"action",
"curve",
"tags"
],
"geometry": {
"root": ".vl-metric",
"frame": [
790,
238
],
"ratios": {
"curve_span_over_width": 0.696,
"rise_over_height": 0.412,
"btn_over_width": 0.043
},
"type_px": {
"label": 15,
"figure": 32,
"micro": 11
},
"pad_ratio": {
"top": 0.0304,
"x": 0.0354,
"bottom": 0.0304
}
},
"benchmarks": [
{
"name": "le chiffre pèse au moins le double du libellé qui le nomme",
"measure": "num('.vl-metric__fig','fontSize') / num('.vl-metric__label','fontSize')",
"min": 2
},
{
"name": "la pile de KPI tient dans la moitié gauche : la courbe garde sa place",
"measure": "box('.vl-metric__head').w / W",
"min": 0.85
},
{
"name": "le chiffre ne descend pas dans la moitié basse de la carte (là où passe la courbe)",
"measure": "box('.vl-metric__fig').bottom / H",
"max": 0.5
},
{
"name": "l'annotation d'arrivée est au-dessus de celle de départ : la courbe monte",
"measure": "box('.vl-metric__tag--from').top - box('.vl-metric__tag--to').top",
"min": 60
},
{
"name": "les deux annotations encadrent la course mesurée (≥ 60 % de la largeur)",
"measure": "(box('.vl-metric__tag--to').left - box('.vl-metric__tag--from').left) / W",
"min": 0.6
},
{
"name": "exactement deux annotations : on annote les bouts, pas les points intermédiaires",
"measure": "document.querySelectorAll('.vl-metric__tag').length",
"expect": 2,
"tol": 0
},
{
"name": "la courbe est en fond et occupe toute la carte",
"measure": "box('.vl-metric__curve').w / W",
"expect": 1,
"tol": 0.02
},
{
"name": "contraste du chiffre sur l'aplat ≥ 3 (blanc sur bleu saturé)",
"measure": "contrast('.vl-metric__fig')",
"min": 3
},
{
"name": "contraste du libellé sur l'aplat ≥ 3",
"measure": "contrast('.vl-metric__label')",
"min": 3
},
{
"name": "aucun débordement du contenu hors de la carte",
"measure": "overflow('.vl-metric')",
"max": 0
}
],
"notes": "Le seuil de contraste est à 3 et non à 4,5 : du blanc sur un bleu de marque saturé plafonne autour de 3,3:1, et exiger 4,5 obligerait à noircir l'aplat — c'est-à-dire à perdre exactement ce qui met la carte au premier rang. Ce que le benchmark garantit en échange, c'est que personne ne glisse vers un bleu plus clair sans s'en apercevoir. Le dégradé déclare `background-color` sous son `background-image`, sinon `bgOf()` traverse la carte et mesure le contraste contre la page. Le prolongement de la courbe après le point d'arrivée est tracé dans une SECONDE `<path>` en encre atténuée : c'est ce qui distingue le mesuré du décoratif, et le fusionner en un seul tracé ferait mentir la carte.",
"id": "card-09-gradient-metric-curve"
}
À côté
card-02-notched-brief
Carte chanfreinée de second rang
card-03-stat-accent
Vignette statistique sur accent
card-04-stat-barchart
Carte statistique à barres empilées
card-05-balance-gauge
Carte solde à jauge compteur
card-06-forecast-timeline