layout-08-sheet-handle-tab

Feuille modale à poignée soudée par congés concaves

Ouvrir un dossier par-dessus une application sans que la modale se lise comme « une carte de plus » : un voile sombre qui PORTE de l'interface (onglets, fermeture), une feuille claire qui saigne à quelques pixels des bords, et un onglet-poignée titré soudé à cette feuille par DEUX congés concaves — la seule courbe inversée du corpus, et ce qui fait que la poignée appartient à la feuille au lieu de flotter au-dessus d'elle.

layoutweb1440×929px17 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

Dossier, fiche détaillée ou vue plein écran ouverte depuis une liste, quand le contexte sous-jacent doit rester senti sans être lisible. Le voile accueille la navigation transverse (onglets de section, fermeture), la feuille accueille le contenu, et la poignée porte le titre de ce qu'on a ouvert.

Éviter quand

La feuille est blanche (les modules blancs qu'elle portera devront s'entourer d'un liseré, et le système n'en a nulle part), le voile ne porte rien (il redevient une simple opacité, et deux rectangles empilés suffisent), ou la vue est une PAGE et non une couche — la poignée dit « ceci est ouvert par-dessus », ce qui devient un mensonge sur une page de plein droit.

Notes

LE TEST QUI TRANCHE ENTRE UN VOILE ET UN BEZEL DE MAQUETTE : le voile PORTE du contenu. Un bezel n'en porte jamais. C'est ce critère — et pas l'apparence — qui a fait garder cette couche sombre alors que les deux autres références du lot en ont perdu une ou deux. Le benchmark le mesure en comptant les enfants directs du voile qui ne sont pas la feuille ; il tomberait rouge sur une modale dont le scrim serait décoratif, et c'est exactement ce qu'on veut savoir. LE CONGÉ CONCAVE EST LA SEULE COURBE INVERSÉE DU CORPUS. Il est fabriqué par un carré de la couleur de la feuille dans lequel un `radial-gradient` évide un quart de disque à partir du coin — pas par un SVG, pour que la forme suive le token de couleur sans être redessinée. Deux assertions le tiennent : les DEUX pseudo-éléments portent un dégradé radial (un seul congé donnerait un onglet de travers), et ce dégradé démarre bien sur du TRANSPARENT (un congé convexe, ou un congé plein, passerait la première assertion sans rien évider). La preuve que poignée et feuille sont une seule surface, et non deux rectangles séparés, vient de la source elle-même : la frontière voile/feuille descend continûment (y=131 à x=200, 150 à x=350, 186 à x=500 sur l'image d'origine). Deux rectangles auraient donné une frontière en marches. C'est le genre de fait qu'un relevé par scan établit et qu'un coup d'œil rate — j'avais d'abord lu une saignée sombre entre les deux. La feuille est mesurée en LUMINANCE et bornée des deux côtés (0,5–0,85) : une feuille blanche passerait toute autre vérification et casserait le système au premier module blanc qu'on y pose.

Mots-clés

modalesheetfeuillevoilescrimoverlayongletpoigneetabcongéconcavedossierficheapplication

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

Rejouables par node bin/check.mjs layout-08-sheet-handle-tab : le pattern se prouve, il ne se croit pas.

assertionmesureattendu
LA FEUILLE EST GRISE, PAS BLANCHE — c'est ce qui fait monter les modules sans liserélum(parseRgb(cs('.vl-sheet__panel','backgroundColor')))0.5 → 0.85
le voile est franchement sombre : au moins 6 fois moins lumineux que la feuillelum(parseRgb(cs('.vl-sheet','backgroundColor'))) / lum(parseRgb(cs('.vl-sheet__panel','backgroundColor')))≤ 0.17
LE VOILE PORTE DE L'INTERFACE : au moins deux objets vivent dessus, hors de la feuille[...document.querySelectorAll('.vl-sheet > *')].filter((e) => !e.classList.contains('vl-sheet__panel')).length≥ 2
la saignée de voile est fine et ÉGALE des deux côtésMath.abs(box('.vl-sheet__panel').left - (W - box('.vl-sheet__panel').right))≤ 1.5
… et elle vaut ≈ 1,4 % de la largeur : un liseré de contexte, pas une margebox('.vl-sheet__panel').left / W0.0139 ± 0.008
LA POIGNÉE EST SOUDÉE : son bas coïncide au pixel près avec le haut de la feuilleMath.abs(box('.vl-sheet__handle').bottom - box('.vl-sheet__panel').top)≤ 1
… par DEUX congés, un de chaque côté — pas un seul[getComputedStyle($('.vl-sheet__handle'), '::before').backgroundImage, getComputedStyle($('.vl-sheet__handle'), '::after').backgroundImage].filter((v) => v.includes('radial-gradient')).length2 ± 0
les congés sont bien CONCAVES : le quart de disque évidé est transparent[getComputedStyle($('.vl-sheet__handle'), '::before').backgroundImage, getComputedStyle($('.vl-sheet__handle'), '::after').backgroundImage].filter((v) => /rgba\(0,\s*0,\s*0,\s*0\)/.test(v)).length2 ± 0
la poignée est RENTRÉE : elle ne part pas du bord gauche de la feuillebox('.vl-sheet__handle').left - box('.vl-sheet__panel').left≥ 20
… et elle reste minoritaire : au plus un tiers de la largeurbox('.vl-sheet__handle').w / W≤ 0.34
le congé vaut environ la moitié de la hauteur de poignéeparseFloat(getComputedStyle($('.vl-sheet__handle'), '::before').width) / box('.vl-sheet__handle').h0.482 ± 0.15
le bouton de fermeture vit sur le VOILE, à gauche de la poignéebox('.vl-sheet__handle').left - box('.vl-sheet__close').right≥ 4
… et c'est un disquebox('.vl-sheet__close').w / box('.vl-sheet__close').h1 ± 0.02
le titre de la poignée est le plus grand corps de la coquenum('.vl-sheet__title','fontSize') / num('.vl-sheet__tab','fontSize')≥ 2.5
contraste du titre sur la feuille ≥ 4,5contrast('.vl-sheet__title')≥ 4.5
contraste d'un onglet sur son aplat ≥ 4,5contrast('.vl-sheet__tab')≥ 4.5
aucun débordement de la feuilleoverflow('.vl-sheet__panel')≤ 0

Le code

fragment — patterns/layout-08-sheet-handle-tab.html Télécharger
<!-- layout-08-sheet-handle-tab — une feuille modale ouverte par-dessus une application.
     Couleurs via --vl-* uniquement.

     LE VOILE RESTE PARCE QU'IL PORTE DE L'INTERFACE (onglets, fermeture). Un bezel de maquette
     n'en porte jamais : c'est ce test-là qui distingue une couche de système d'une couche de
     mise en scène, pas l'apparence.

     LA POIGNÉE ET LA FEUILLE SONT UNE SEULE SURFACE, soudées par DEUX CONGÉS CONCAVES — un
     carré de la couleur de la feuille dans lequel un `radial-gradient` évide un quart de
     disque. Pas de SVG : la forme doit suivre le token de couleur sans être redessinée. -->
<div class="vl-sheet">

  <nav class="vl-sheet__tabs">
    <span class="vl-sheet__tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><circle cx="11" cy="11" r="6.4"/><path d="m15.8 15.8 3.4 3.4M9 11h4M11 9v4"/></svg>Treatment Dynamics</span>
    <span class="vl-sheet__tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><rect x="6" y="3.5" width="12" height="17" rx="2.2"/><path d="M9.5 8h5M9.5 12h5"/></svg>Visits</span>
    <span class="vl-sheet__tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><rect x="3.4" y="8.6" width="17" height="7" rx="3.5" transform="rotate(-45 12 12)"/><path d="m9.5 9.5 5 5"/></svg>Medications</span>
    <span class="vl-sheet__tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M10 3.5v6L5.5 18a2 2 0 0 0 1.8 3h9.4a2 2 0 0 0 1.8-3L14 9.5v-6M8.5 3.5h7"/></svg>Labs</span>
  </nav>

  <span class="vl-sheet__close" aria-hidden="true">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="m6.5 6.5 11 11M17.5 6.5l-11 11"/></svg>
  </span>

  <div class="vl-sheet__panel">
    <div class="vl-sheet__handle"><h1 class="vl-sheet__title">Cardiology</h1></div>
    <div class="vl-sheet__content"></div>
  </div>

</div>

<style>
.vl-sheet {
  width: 1440px; height: 929px;
  box-sizing: border-box;
  position: relative;
  background: var(--vl-veil);
  border-radius: 44px;
  overflow: hidden;
  --vl-notch: 40px;
  font-family: Montserrat, Poppins, Inter, "Helvetica Neue", Arial, sans-serif;
}
/* Deux objets vivent sur le VOILE, hors de la feuille : les onglets et la fermeture. C'est ce
   qui fait de cette couche sombre de l'interface et non un cadre. */
.vl-sheet__tabs {
  position: absolute; top: 49px; left: 500px; right: 26px;
  display: flex; gap: 9px; align-items: center; justify-content: flex-end;
}
.vl-sheet__tab {
  height: 56px; padding: 0 17px;
  border-radius: var(--vl-radius-pill, 999px);
  background: var(--vl-module-hi);
  color: var(--vl-ink);
  display: flex; align-items: center; gap: 8px;
  font-size: 15px; font-weight: 500; white-space: nowrap;
}
.vl-sheet__tab svg { width: 19px; height: 19px; }
.vl-sheet__close {
  position: absolute; left: 15px; top: 42px;
  width: 60px; height: 60px; border-radius: 50%;
  background: var(--vl-module-hi);
  color: var(--vl-ink);
  display: flex; align-items: center; justify-content: center;
}
.vl-sheet__close svg { width: 22px; height: 22px; }

/* La feuille SAIGNE : quelques pixels de voile restent visibles tout autour, à gauche comme à
   droite. C'est ce liseré de contexte qui dit « il y a quelque chose derrière », et il doit
   rester fin — au-delà, ce n'est plus une saignée, c'est une marge. */
.vl-sheet__panel {
  position: absolute;
  left: var(--vl-bleed, 20px); right: var(--vl-bleed, 20px);
  top: 119px; bottom: 21px;
  padding: var(--vl-margin, 34px);
  background: var(--vl-sheet);
  border-radius: var(--vl-radius-sheet, 34px);
  display: flex; flex-direction: column;
}
/* `bottom: 100%` colle la poignée au bord haut de la feuille au pixel près : les deux ne font
   qu'une surface. `left` la RENTRE — une poignée partant du bord gauche se lirait comme un
   en-tête de carte, pas comme un onglet. */
.vl-sheet__handle {
  position: absolute; left: 66px; bottom: 100%;
  height: 83px; padding: 0 40px;
  background: var(--vl-sheet);
  border-radius: var(--vl-radius-sheet, 34px) var(--vl-radius-sheet, 34px) 0 0;
  display: flex; align-items: center;
}
/* LES DEUX CONGÉS CONCAVES. Chacun est un carré de la couleur de la feuille dont le coin
   intérieur est évidé par un quart de disque TRANSPARENT. Un seul congé donnerait un onglet
   de travers ; un dégradé qui ne partirait pas du transparent n'évidérait rien du tout. */
.vl-sheet__handle::before, .vl-sheet__handle::after {
  content: ''; position: absolute; bottom: 0;
  width: var(--vl-notch); height: var(--vl-notch);
}
.vl-sheet__handle::before {
  left: calc(var(--vl-notch) * -1);
  background: radial-gradient(circle at 0 0, transparent var(--vl-notch), var(--vl-sheet) calc(var(--vl-notch) + 0.5px));
}
.vl-sheet__handle::after {
  right: calc(var(--vl-notch) * -1);
  background: radial-gradient(circle at 100% 0, transparent var(--vl-notch), var(--vl-sheet) calc(var(--vl-notch) + 0.5px));
}
.vl-sheet__title {
  margin: 0; font-size: 48px; line-height: 1; font-weight: 500; letter-spacing: -0.02em;
  color: var(--vl-ink);
}
.vl-sheet__content { flex: 1 1 auto; min-height: 0; }
</style>
tokens de la charte — systems/ref-19-acid-clinical-timeline.json Télécharger
:root {
  --vl-veil: #4A4A48;
  --vl-veil-deep: #1B1D1D;
  --vl-sheet: #DFDFD7;
  --vl-module: #F0EFEB;
  --vl-module-hi: #FFFFFF;
  --vl-accent: #F5FE49;
  --vl-alert: #E8756B;
  --vl-recover: #7FA88C;
  --vl-ink: #262C2C;
  --vl-ink-soft: #5F615E;
  --vl-on-veil: #FFFFFF;
  --vl-on-veil-soft: rgba(255,255,255,0.72);
  --vl-radius-sheet: 34px;
  --vl-radius-module: 22px;
  --vl-radius-pill: 999px;
  --vl-notch: 26px;
  --vl-bleed: 20px;
  --vl-tabbar-h: 106px;
  --vl-ribbon-h: 74px;
  --vl-gutter: 18px;
  --vl-margin: 34px;
}
les métadonnées — patterns/layout-08-sheet-handle-tab.json (télécharger)
{
  "name": "Feuille modale à poignée soudée par congés concaves",
  "family": "layout",
  "ref": "ref-19-acid-clinical-timeline",
  "intent": "Ouvrir un dossier par-dessus une application sans que la modale se lise comme « une carte de plus » : un voile sombre qui PORTE de l'interface (onglets, fermeture), une feuille claire qui saigne à quelques pixels des bords, et un onglet-poignée titré soudé à cette feuille par DEUX congés concaves — la seule courbe inversée du corpus, et ce qui fait que la poignée appartient à la feuille au lieu de flotter au-dessus d'elle.",
  "when_to_use": "Dossier, fiche détaillée ou vue plein écran ouverte depuis une liste, quand le contexte sous-jacent doit rester senti sans être lisible. Le voile accueille la navigation transverse (onglets de section, fermeture), la feuille accueille le contenu, et la poignée porte le titre de ce qu'on a ouvert.",
  "avoid_when": "La feuille est blanche (les modules blancs qu'elle portera devront s'entourer d'un liseré, et le système n'en a nulle part), le voile ne porte rien (il redevient une simple opacité, et deux rectangles empilés suffisent), ou la vue est une PAGE et non une couche — la poignée dit « ceci est ouvert par-dessus », ce qui devient un mensonge sur une page de plein droit.",
  "media": [
    "web"
  ],
  "tags": [
    "modale",
    "sheet",
    "feuille",
    "voile",
    "scrim",
    "overlay",
    "onglet",
    "poignee",
    "tab",
    "congé",
    "concave",
    "dossier",
    "fiche",
    "application"
  ],
  "vars": [
    {
      "name": "--vl-veil",
      "role": "voile sombre — il PORTE de l'interface, ce n'est pas un bezel",
      "default": "#4A4A48"
    },
    {
      "name": "--vl-sheet",
      "role": "feuille — GRISE, jamais blanche : c'est ce qui fait monter les modules posés dessus",
      "default": "#DFDFD7"
    },
    {
      "name": "--vl-module-hi",
      "role": "aplat des onglets et du bouton de fermeture, posés sur le voile",
      "default": "#FFFFFF"
    },
    {
      "name": "--vl-ink",
      "role": "titre de la poignée, libellés d'onglets",
      "default": "#262C2C"
    },
    {
      "name": "--vl-radius-sheet",
      "role": "rayon de la feuille ET de la poignée",
      "default": "34px"
    },
    {
      "name": "--vl-notch",
      "role": "rayon des DEUX congés concaves qui soudent la poignée à la feuille",
      "default": "40px"
    },
    {
      "name": "--vl-bleed",
      "role": "saignée de voile visible autour de la feuille",
      "default": "20px"
    }
  ],
  "slots": [
    "tabs",
    "close",
    "handle",
    "content"
  ],
  "geometry": {
    "root": ".vl-sheet",
    "frame": [
      1440,
      929
    ],
    "ratios": {
      "bleed_over_width": 0.0139,
      "tabbar_over_height": 0.128,
      "handle_over_width": 0.247,
      "handle_h_over_height": 0.089,
      "notch_over_handle_h": 0.482,
      "close_over_width": 0.042
    },
    "type_px": {
      "handle": 48,
      "tab": 15
    },
    "pad_ratio": {
      "top": 0.037,
      "x": 0.024,
      "bottom": 0.037
    }
  },
  "benchmarks": [
    {
      "name": "LA FEUILLE EST GRISE, PAS BLANCHE — c'est ce qui fait monter les modules sans liseré",
      "measure": "lum(parseRgb(cs('.vl-sheet__panel','backgroundColor')))",
      "min": 0.5,
      "max": 0.85
    },
    {
      "name": "le voile est franchement sombre : au moins 6 fois moins lumineux que la feuille",
      "measure": "lum(parseRgb(cs('.vl-sheet','backgroundColor'))) / lum(parseRgb(cs('.vl-sheet__panel','backgroundColor')))",
      "max": 0.17
    },
    {
      "name": "LE VOILE PORTE DE L'INTERFACE : au moins deux objets vivent dessus, hors de la feuille",
      "measure": "[...document.querySelectorAll('.vl-sheet > *')].filter((e) => !e.classList.contains('vl-sheet__panel')).length",
      "min": 2
    },
    {
      "name": "la saignée de voile est fine et ÉGALE des deux côtés",
      "measure": "Math.abs(box('.vl-sheet__panel').left - (W - box('.vl-sheet__panel').right))",
      "max": 1.5
    },
    {
      "name": "… et elle vaut ≈ 1,4 % de la largeur : un liseré de contexte, pas une marge",
      "measure": "box('.vl-sheet__panel').left / W",
      "expect": 0.0139,
      "tol": 0.008
    },
    {
      "name": "LA POIGNÉE EST SOUDÉE : son bas coïncide au pixel près avec le haut de la feuille",
      "measure": "Math.abs(box('.vl-sheet__handle').bottom - box('.vl-sheet__panel').top)",
      "max": 1
    },
    {
      "name": "… par DEUX congés, un de chaque côté — pas un seul",
      "measure": "[getComputedStyle($('.vl-sheet__handle'), '::before').backgroundImage, getComputedStyle($('.vl-sheet__handle'), '::after').backgroundImage].filter((v) => v.includes('radial-gradient')).length",
      "expect": 2,
      "tol": 0
    },
    {
      "name": "les congés sont bien CONCAVES : le quart de disque évidé est transparent",
      "measure": "[getComputedStyle($('.vl-sheet__handle'), '::before').backgroundImage, getComputedStyle($('.vl-sheet__handle'), '::after').backgroundImage].filter((v) => /rgba\\(0,\\s*0,\\s*0,\\s*0\\)/.test(v)).length",
      "expect": 2,
      "tol": 0
    },
    {
      "name": "la poignée est RENTRÉE : elle ne part pas du bord gauche de la feuille",
      "measure": "box('.vl-sheet__handle').left - box('.vl-sheet__panel').left",
      "min": 20
    },
    {
      "name": "… et elle reste minoritaire : au plus un tiers de la largeur",
      "measure": "box('.vl-sheet__handle').w / W",
      "max": 0.34
    },
    {
      "name": "le congé vaut environ la moitié de la hauteur de poignée",
      "measure": "parseFloat(getComputedStyle($('.vl-sheet__handle'), '::before').width) / box('.vl-sheet__handle').h",
      "expect": 0.482,
      "tol": 0.15
    },
    {
      "name": "le bouton de fermeture vit sur le VOILE, à gauche de la poignée",
      "measure": "box('.vl-sheet__handle').left - box('.vl-sheet__close').right",
      "min": 4
    },
    {
      "name": "… et c'est un disque",
      "measure": "box('.vl-sheet__close').w / box('.vl-sheet__close').h",
      "expect": 1,
      "tol": 0.02
    },
    {
      "name": "le titre de la poignée est le plus grand corps de la coque",
      "measure": "num('.vl-sheet__title','fontSize') / num('.vl-sheet__tab','fontSize')",
      "min": 2.5
    },
    {
      "name": "contraste du titre sur la feuille ≥ 4,5",
      "measure": "contrast('.vl-sheet__title')",
      "min": 4.5
    },
    {
      "name": "contraste d'un onglet sur son aplat ≥ 4,5",
      "measure": "contrast('.vl-sheet__tab')",
      "min": 4.5
    },
    {
      "name": "aucun débordement de la feuille",
      "measure": "overflow('.vl-sheet__panel')",
      "max": 0
    }
  ],
  "notes": "LE TEST QUI TRANCHE ENTRE UN VOILE ET UN BEZEL DE MAQUETTE : le voile PORTE du contenu. Un bezel n'en porte jamais. C'est ce critère — et pas l'apparence — qui a fait garder cette couche sombre alors que les deux autres références du lot en ont perdu une ou deux. Le benchmark le mesure en comptant les enfants directs du voile qui ne sont pas la feuille ; il tomberait rouge sur une modale dont le scrim serait décoratif, et c'est exactement ce qu'on veut savoir.\n\nLE CONGÉ CONCAVE EST LA SEULE COURBE INVERSÉE DU CORPUS. Il est fabriqué par un carré de la couleur de la feuille dans lequel un `radial-gradient` évide un quart de disque à partir du coin — pas par un SVG, pour que la forme suive le token de couleur sans être redessinée. Deux assertions le tiennent : les DEUX pseudo-éléments portent un dégradé radial (un seul congé donnerait un onglet de travers), et ce dégradé démarre bien sur du TRANSPARENT (un congé convexe, ou un congé plein, passerait la première assertion sans rien évider).\n\nLa preuve que poignée et feuille sont une seule surface, et non deux rectangles séparés, vient de la source elle-même : la frontière voile/feuille descend continûment (y=131 à x=200, 150 à x=350, 186 à x=500 sur l'image d'origine). Deux rectangles auraient donné une frontière en marches. C'est le genre de fait qu'un relevé par scan établit et qu'un coup d'œil rate — j'avais d'abord lu une saignée sombre entre les deux.\n\nLa feuille est mesurée en LUMINANCE et bornée des deux côtés (0,5–0,85) : une feuille blanche passerait toute autre vérification et casserait le système au premier module blanc qu'on y pose.",
  "id": "layout-08-sheet-handle-tab"
}

À côté