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

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

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

cartekpiindicateurcourbesparklinedegradeaplatannotationtrajectoiredashboardpremier-rang

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.

assertionmesureattendu
le chiffre pèse au moins le double du libellé qui le nommenum('.vl-metric__fig','fontSize') / num('.vl-metric__label','fontSize')≥ 2
la pile de KPI tient dans la moitié gauche : la courbe garde sa placebox('.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 montebox('.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édiairesdocument.querySelectorAll('.vl-metric__tag').length2 ± 0
la courbe est en fond et occupe toute la cartebox('.vl-metric__curve').w / W1 ± 0.02
contraste du chiffre sur l'aplat ≥ 3 (blanc sur bleu saturé)contrast('.vl-metric__fig')≥ 3
contraste du libellé sur l'aplat ≥ 3contrast('.vl-metric__label')≥ 3
aucun débordement du contenu hors de la carteoverflow('.vl-metric')≤ 0

Le code

fragment — 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>
tokens de la charte — 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é