/* ENGENDRÉ — ne pas modifier à la main.
   Source : frontend/plateforme.html, tout ce qui suit la sentinelle de la
   coquille : la planche annotée, ses cotes, les sept écrans reconstitués.
   Régénérer : python3 scripts/vitrine/build.py */
/* ══ LA PLANCHE ══════════════════════════════════════════════════════
       Un écran RÉEL du produit, simplifié et encadré ; des pastilles
       numérotées posées sur les éléments qui comptent ; à côté, les
       explications numérotées, et chaque explication est un LIEN vers la
       section qui la détaille.

       LE LIEN ENTRE L'EXPLICATION ET L'ÉCRAN EST UN CHIFFRE, PAS UN TRAIT,
       et c'est la décision structurante :
         · un « 1 » fait la même largeur en FR, IT, DE et EN ; un trait dessiné
           dépend de la longueur du texte, et l'allemand est ~30 % plus long ;
         · la page d'essai le prouve elle-même — elle ÉTEINT tout son calque de
           traits sous 1200 px et retombe sur une liste : sa géométrie ne
           survit déjà pas à un écran étroit ;
         · la pastille est ancrée à l'ÉLÉMENT qu'elle désigne, dans le repère
           interne de l'écran (déjà en pourcentages et en unités de conteneur).
           Elle suit donc l'écran de 390 à 1920 px sans une seule requête média.
       Ce qu'on perd, et je l'assume : le geste de main qui entre dans l'écran. */
    /* ══ PAS DE RÉCIT DE JOURNÉE SUR CES PLANCHES ══════════════════════════
       Demande de Dany, 2 septembre 2026 : « il faut enlever cet esprit journée
       type ; je ne trouve pas ça joli ni utile. » Il avait raison, et le défaut
       était plus profond qu'une sur-ligne : les TITRES racontaient (« Une
       matinée… », « Ce que l'appel laisse derrière lui »), et les annotations
       parlaient au passé narratif (« Elle a décroché, vérifié, et l'a écrit
       ici »). Trois endroits pour une même histoire.
       LA RÈGLE, POUR LA SUITE : une planche montre CE QUE LE PRODUIT FAIT, pas
       ce qui s'est passé un jeudi. Une heure LUE DANS un écran est une donnée
       d'agenda et reste ; une heure écrite EN TÊTE de planche est un décor et
       part. C'est pourquoi la ligne rouge de l'instant présent et son « 09:06 »
       restent dessinés — ce sont des dispositifs réels du produit — alors que
       la sur-ligne « Jeudi, 9 h 06 » a disparu avec sa règle CSS.
       ⚠️ Deux textes du même récit attendent encore : la légende du calendrier
       et l'étiquette « La ligne du temps ». Ils décrivent un dessin qu'on n'a
       pas refait ; les retirer maintenant remplacerait une légende qui raconte
       par une légende qui ment. Ils partent avec le calendrier. */
    /* ══ LE TITRE PEND AU FILET, PAS AU MILIEU DU VIDE (17 sept. 2026) ═══
       Retour de Dany : « que les titres fassent plus titre, qu'on comprenne
       que c'est un titre ». Mesuré à 1440 : le filet de section était à
       102 px AU-DESSUS du titre et la scène à 40 px EN DESSOUS — la proximité
       collait donc le titre à la première étiquette (18 px, grasse, même
       marge gauche), et l'œil lisait une colonne « titre + étiquette » plutôt
       qu'un en-tête de planche. L'air se répartit à l'inverse : 56 px sous le
       filet, 64 px avant la scène. Le filet appartient au titre qui le suit,
       comme dans une fiche technique. Le bas de la section garde son air. */
    .pl { padding: clamp(36px, 4vw, 56px) 0 clamp(48px, 7vw, 110px); border-top: 1px solid var(--line); }
    /* La section de tête n'a pas de filet : elle prolonge le héros au lieu de
       commencer une nouvelle partie. */
    /* La planche qui suit le héros n'a pas de filet : elle le prolonge au
       lieu de commencer une nouvelle partie, et garde l'air d'une section.
       Sélectionnée par sa POSITION (`.hero + .pl`), pas par une classe : les
       tests de `vitrine-scenes` reconnaissent une planche à `class="pl"`
       exactement, et une classe de plus l'aurait fait disparaître du compte. */
    .hero + .pl { border-top: none; padding-top: clamp(48px, 7vw, 110px); }
    /* ══ TITRE, PUIS LEAD, CENTRÉS (17 sept. 2026) ═══════════════════════
       Retour de Dany en deux temps : « le titre et la description liée au
       titre, plus visibles et centrés », puis « le titre AVANT la
       description ». Le résumé était à gauche, en gris, à 20 px, dans sa
       propre section AU-DESSUS du titre, avec un filet entre les deux : une
       description qui précédait ce qu'elle décrit, et deux objets là où il
       n'y en a qu'un. Il est désormais la lead de la première planche : sous
       le titre, à l'encre, à la taille de la lead du héros, centré. Le titre
       resserre sa marge quand une lead le suit — c'est elle qui porte l'air
       avant la scène.
       PUIS CHAQUE PLANCHE A REÇU LA SIENNE (troisième retour de Dany, le même
       jour : « je veux que chaque titre ait sa description »). Six leads
       neuves, écrites sur les seules étiquettes de la planche — chacune
       énonce un comportement du code déjà vérifié là, jamais une promesse
       de plus. Clés `vitrine2.pN.lead`, quatre langues. */
    .pl-resume { max-width: 44ch; margin: 0 auto clamp(32px, 4.5vw, 64px); text-align: center;
      font-size: clamp(1.15rem, 1.7vw, 1.4rem); line-height: 1.5;
      color: var(--ink); text-wrap: pretty; }
    .pl > .wrap > h2:has(+ .pl-resume) { margin-bottom: clamp(14px, 1.6vw, 22px); }
    /* ══ UN SEUL SYSTÈME DE TITRES SUR LA PAGE (17 sept. 2026) ══════════
       Le titre de planche était plus PETIT que les autres titres de section
       (34 px contre 40 à 1440) et RESSERRÉ à 84 % de chasse — le seul texte
       de la page à employer l'axe de largeur. Résultat, mesuré sur la
       capture : un bloc de 384 px de large, à 40 px d'une étiquette grasse de
       18 px, qui se lisait comme une légende et non comme un en-tête. La
       tension d'affiche du 84 % (note du 2 sept.) était réelle, mais elle
       éloignait le titre du h1 du héros au lieu de l'en rapprocher.
       Il reprend donc la coupe du h1 (graisse 700, chasse normale,
       interlettrage −.02em), un cran au-dessus de `.titre` (44 px contre 40 à
       1440 : la planche est plus large que la prose, et c'est elle qui porte
       la page), et il se CENTRE — la scène est centrée dans le cadre, un
       titre calé à gauche au-dessus d'elle avait l'air posé à côté. Mesuré
       dans les quatre langues à 1440 et 390 : 1 à 3 lignes, 0 débord. */
    .pl > .wrap > h2 { margin: 0 auto clamp(32px, 4.5vw, 64px); text-align: center;
      font-size: clamp(1.6rem, 1.2rem + 2.4vw, 2.75rem); font-weight: 700; line-height: 1.1;
      letter-spacing: -.02em; text-wrap: balance; max-width: 27ch; }

    /* ══ LA SCÈNE ═══════════════════════════════════════════════════════
       Un bloc à rapport FIXE : le SVG des flèches le remplit avec le même
       `viewBox`, et l'écran comme les étiquettes y sont posés en
       POURCENTAGES du même repère. Les deux bouts d'une flèche vivent donc
       dans le même système de coordonnées — ils ne peuvent pas se
       désaligner, à aucune largeur. Les cotes sont ENGENDRÉES : voir le bloc
       FIG:p1-cotes plus bas. */
    /* ══ LA PLANCHE EST PLUS LARGE QUE LA PROSE ═════════════════════════
       La page entière tient dans 1140 px, ce qui est juste pour du texte et
       faux pour un schéma : à 2560 px, la moitié de l'écran restait vide et
       l'agenda faisait 576 px — une vignette. On élargit donc la planche, et
       elle SEULE. Rien ne s'étire au passage : le titre est déjà borné à
       22 caractères, le socle à 68, et la mention tient sur une ligne.
       Le plafond n'est pas rond : au-delà, le texte DE L'ÉCRAN dépasserait
       celui des étiquettes qui l'expliquent, et une capture se mettrait à
       parler plus fort que la page. */
    .pl > .wrap { max-width: min(1380px, 100%); }
    /* `container-type` sur la scène : les étiquettes se dimensionnent sur ELLE
       et non sur la fenêtre. Deux effets, tous deux voulus — elles grandissent
       avec la planche au lieu de rester bloquées à 15,5 px, et elles ne
       sautent plus au passage du seuil de 1100 px, la scène et la pile ayant
       la même largeur de part et d'autre. */
    .pl-scene { position: relative; width: 100%; aspect-ratio: 1288 / 700;
      container-type: inline-size; }
    /* Chaque planche a SON cadre : un téléphone est haut, une caisse est
       courte. Le rapport de la scène suit celui du dessin, sinon les cotes
       (en % du cadre) désigneraient un autre endroit que les flèches.
       Le rapport de l'ÉCRAN, lui, n'est plus écrit ici : il sort du
       générateur, avec les cotes (bloc FIG:pN-cotes). */
    #p3 .pl-scene { aspect-ratio: 1288 / 700; }
    #p4 .pl-scene { aspect-ratio: 1288 / 650; }
    #p5 .pl-scene { aspect-ratio: 1288 / 800; }
    /* p7 DÉCLARE UN CADRE DE 760 (`dessins.PLANCHES['p7']`) ET N'AVAIT PAS SA
       LIGNE ICI (mesuré le 18 sept. 2026) : la scène rendait 700, le SVG
       s'écrasait à 92 % et les quatre flèches de la fiche visaient à 19 à
       47 px de leur cible. Le rapport de la scène est celui du cadre. */
    #p7 .pl-scene { aspect-ratio: 1288 / 760; }
    .pl-fleches { position: absolute; inset: 0; width: 100%; height: 100%;
      overflow: visible; pointer-events: none; z-index: 2; }
    /* Deux encres pour un seul trait : celle de la page hors de l’écran,
       une encre fixe à l’intérieur — l’écran reconstitué reste blanc dans
       les deux thèmes, une flèche claire y disparaîtrait. */
    /* ══ LES FLÈCHES SE TRACENT, LES ÉTIQUETTES SUIVENT ═════════════════
       Demande de Dany (1ᵉʳ sept.) : « des animations d'apparition des
       explications, les unes après les autres, et qu'ensuite elles restent ».
       LE MASQUE FAIT LE TRACÉ, et c'est obligatoire : les traits sont des
       SURFACES remplies, pas des `stroke` — `stroke-dasharray` ne mord pas
       dessus. Le générateur émet donc, par flèche, la courbe NUE dans un
       `<mask>` ; on anime ce trait-là, et il révèle la surface en dessous.
       Le cercle du masque couvre la pointe, dont les barbes sortent de l'axe.
       RIEN N'EST CACHÉ SANS JAVASCRIPT : tout ceci ne vit que sous
       `.rev-actif`, posé sur <html> par le script d'en-tête et seulement si
       le mouvement est accepté. Sans script, en mouvement réduit, ou sur une
       planche jamais atteinte, le masque est plein et la flèche est là. */
    .pl-fleches .m-trait { fill: none; stroke: #fff; stroke-width: 16;
      stroke-linecap: round; stroke-linejoin: round; }
    .pl-fleches .m-tete { fill: #fff; }
    /* Le groupe d'une flèche ne porte plus QUE son masque : le rang (--i) est
       descendu sur le masque lui-même, seul nœud dont le trait animé hérite.
       La règle existe pour que la classe ne soit pas orpheline — un garde-fou
       du dépôt refuse une classe écrite sans une seule règle, et il a déjà
       attrapé un formulaire qui avait perdu tout son style. */
    .pl-fleches .pl-fl-g { transform-box: fill-box; }
    /* ══ CHAQUE FLÈCHE EST DÉCLENCHÉE PAR SON ÉTIQUETTE ══════════════════
       Elles l'étaient d'abord par la SCÈNE, et c'était trop tôt : mesuré au
       défilement à la molette, la planche recevait son signal alors qu'un
       cinquième seulement était à l'écran, et trois flèches sur quatre
       jouaient leur unique animation SOUS le pli — pour ne jamais la rejouer,
       l'observateur ne repassant pas. Le déclencheur est donc le `.vu` de
       l'étiquette elle-même : le script ajoute sa lettre à `data-fl` sur la
       scène, et la flèche part avec le texte qu'elle relie.
       LE RANG SERT ENCORE, et il vaut le même pas que les étiquettes (190 ms)
       — quand la planche entière entre d'un coup, les quatre paires se
       cascadent ensemble ; quand on descend lentement, chacune part chez
       elle. */
    .rev-actif .pl-fleches .m-trait { stroke-dasharray: 1; }
    .rev-actif .pl-scene .m-trait { stroke-dashoffset: 1; }
    .rev-actif .pl-scene .m-tete { opacity: 0; }
    .rev-actif .pl-scene[data-fl~="a"] .m-trait--a,
    .rev-actif .pl-scene[data-fl~="b"] .m-trait--b,
    .rev-actif .pl-scene[data-fl~="c"] .m-trait--c,
    .rev-actif .pl-scene[data-fl~="d"] .m-trait--d {
      stroke-dashoffset: 0;
      transition: stroke-dashoffset .62s cubic-bezier(.22,1,.36,1);
      transition-delay: calc(var(--i, 0) * 190ms); }
    /* La pointe se pose à .5s, l'instant MESURÉ où le trait touche sa cible :
       la courbe `cubic-bezier(.22,1,.36,1)` est chargée en tête et rend 99,9 %
       du parcours à 503 ms. La retarder ouvrirait une pause sur une flèche
       sans tête. */
    .rev-actif .pl-scene[data-fl~="a"] .m-tete--a,
    .rev-actif .pl-scene[data-fl~="b"] .m-tete--b,
    .rev-actif .pl-scene[data-fl~="c"] .m-tete--c,
    .rev-actif .pl-scene[data-fl~="d"] .m-tete--d {
      opacity: 1;
      transition: opacity .18s linear;
      transition-delay: calc(var(--i, 0) * 190ms + .5s); }
    @media (prefers-reduced-motion: reduce) {
      .rev-actif .pl-scene .m-trait { stroke-dashoffset: 0; }
      .rev-actif .pl-scene .m-tete { opacity: 1; }
      .rev-actif .pl-scene .m-trait, .rev-actif .pl-scene .m-tete { transition: none; }
    }
    /* ══ À L'IMPRESSION, RIEN N'ATTEND ══════════════════════════════════════
       Une page qu'on imprime n'est pas défilée : sans ce bloc, les vingt
       flèches et les vingt étiquettes des planches sortent BLANCHES — 265 mots
       et 20 liens perdus, l'écran nu flottant dans deux colonnes de vide.
       Le `[data-rev]` des autres blocs est traité dans la coquille partagée,
       qui sert les 134 pages ; ces deux lignes-ci ne concernent que l'accueil,
       seule page à porter des flèches. Spécificité alignée sur les règles
       d'attente (0,3,0) et posée APRÈS : à égalité, la dernière gagne. */
    @media print {
      .rev-actif .pl-scene .m-trait { stroke-dashoffset: 0; }
      .rev-actif .pl-scene .m-tete { opacity: 1; }
    }
    .pl-fleches .pl-hors .trait { fill: var(--ink); }
    .pl-fleches .pl-dans .trait { fill: #10151c; }
    .pl-scene .pl-ecran { position: absolute; margin: 0; z-index: 1;
      container-type: inline-size; }
    .pl-scene .pl-et { position: absolute; z-index: 3; margin: 0;
      font-size: clamp(13px, 1.46cqi, 18px); line-height: 1.4; text-wrap: pretty; }
    .pl-et a { color: var(--ink); text-decoration: none; font-weight: 700;
      border-bottom: 1px solid transparent; }
    .pl-et a:hover, .pl-et a:focus-visible { border-bottom-color: var(--accent); }
    .pl-et i { display: block; font-style: normal; font-weight: 400;
      color: var(--soft); margin-top: 2px; }
    .pl-socle { margin: clamp(20px, 2.4vw, 30px) 0 0; padding-top: 18px;
      border-top: 1px solid var(--line); max-width: 68ch;
      font-size: 1.02rem; line-height: 1.6; color: var(--soft); text-wrap: pretty; }
    .pl-socle b { color: var(--ink); font-weight: 700; }
    .pl-mention { margin: 14px 0 0; font-size: 13px; color: var(--soft); }

    /* ══ SOUS LE SEUIL, LA SCÈNE REDEVIENT UNE PILE ══════════════════════
       Et les flèches s'éteignent : une flèche ne peut pas désigner une cible
       qui n'est plus à sa place. C'est la même reddition que la page d'essai,
       et elle est honnête — l'étiquette redevient une ligne, précédée de la
       même flèche dessinée, en petit. */
    @media (max-width: 1099px) {
      .pl-scene { aspect-ratio: auto; display: flex; flex-direction: column;
        gap: clamp(16px, 2.6vw, 26px); }
      .pl-fleches { display: none; }
      .pl-scene .pl-ecran, .pl-scene .pl-et {
        position: static; left: auto; top: auto; width: auto; }
      /* EN PILE, L'ÉCRAN NE PREND PAS TOUTE LA LARGEUR. Sans borne, le
         téléphone du salon mesurait 919 × 1608 px à 999 px de large : un
         téléphone géant, ce qui est exactement le contraire de ce qu'il
         montre. Les écrans larges se bornent plus haut, mais se bornent. */
      /* `align-self` et non `margin-inline:auto` : dans une colonne flex, une
         marge automatique fait passer l'élément en largeur de CONTENU — et
         son contenu étant en `width:100%`, la boîte retombait à 2 px de large
         (mesuré aux trois largeurs). */
      /* 560 ET NON 620 : au-dessus du seuil la scène donne 554 px à l'écran ;
         à 620 on l'ÉLARGISSAIT en rétrécissant la fenêtre. Une marche à
         l'envers de 66 px, que personne ne comprend en la voyant. */
      .pl-scene .pl-ecran { order: 1; width: 100%; max-width: 560px; align-self: center; }
      #p3 .pl-ecran { max-width: 296px; }
      .pl-scene .pl-et { order: 2; position: relative; padding-left: 34px; }
      .pl-scene .pl-et .pl-fl { position: absolute; left: 0; top: .34em;
        width: 24px; height: 13px; }
      /* ══ AU DOIGT, LE TITRE SEUL FAIT 17 px ═════════════════════════════
         Et ces vingt titres sont les seuls chemins de l'accueil vers cinq
         pages de fond. Deux fausses solutions ont été mesurées avant celle-ci
         (2 sept. 2026) : un rembourrage vertical porte bien la BOÎTE à 44 px,
         mais la description, qui est un bloc peint après, recouvre les 11 px
         du bas — la surface réellement atteignable reste à 31,6 px, et la
         sonde devient verte pour rien ; passer le lien en `display:block`
         allonge la page de 5 % et transforme chaque titre en bande de 320 px
         qui capture le doigt sur 200 px de vide.
         L'extension est donc un calque, et il doit passer AU-DESSUS de la
         description : d'où le `z-index` sur le lien, qui crée le contexte
         d'empilement. L'écart minimal entre deux étiquettes est de 37,4 px —
         2 × 14 px d'extension ne les font pas se toucher. */
      .pl-scene .pl-et a { position: relative; z-index: 1; }
      .pl-scene .pl-et a::after { content: ''; position: absolute; inset: -14px 0; }
    }
    .pl-fl { width: 26px; height: 14px; fill: var(--accent); }
    @media (min-width: 1100px) { .pl-scene .pl-et .pl-fl { display: none; } }

/* FIG:p1-cotes */
    #p1 .cal{aspect-ratio:1.5}
    @media (min-width: 1100px) {
      #p1 .pl-ecran{left:23.292%;top:15.714%;width:54.348%}
      #p1 .pl-et--a{left:23.292%;top:1.143%;width:27.95%;min-height:13.143%}
      #p1 .pl-et--b{left:0.621%;top:45.429%;width:20.807%;min-height:16.0%}
      #p1 .pl-et--c{left:0.621%;top:66.857%;width:20.807%;min-height:16.0%}
      #p1 .pl-et--d{left:48.137%;top:85.143%;width:26.398%;min-height:12.857%}
    }
/* /FIG */
/* FIG:p2-cotes */
    #p2 .pos{aspect-ratio:1.20301}
    @media (min-width: 1100px) {
      #p2 .pl-ecran{left:28.261%;top:8.571%;width:43.478%}
      #p2 .pl-et--a{left:1.553%;top:12.0%;width:24.845%;min-height:16.0%}
      #p2 .pl-et--b{left:73.913%;top:42.857%;width:24.534%;min-height:16.0%}
      #p2 .pl-et--c{left:1.553%;top:47.143%;width:24.845%;min-height:12.286%}
      #p2 .pl-et--d{left:30.435%;top:79.429%;width:29.503%;min-height:12.286%}
    }
/* /FIG */
/* FIG:p3-cotes */
    #p3 .tel{aspect-ratio:0.47664}
    @media (min-width: 1100px) {
      #p3 .pl-ecran{left:40.295%;top:12.571%;width:19.41%}
      #p3 .pl-et--a{left:4.348%;top:13.714%;width:25.621%;min-height:16.0%}
      #p3 .pl-et--b{left:4.348%;top:41.143%;width:25.621%;min-height:16.0%}
      #p3 .pl-et--c{left:69.876%;top:53.143%;width:25.621%;min-height:12.286%}
      #p3 .pl-et--d{left:69.876%;top:73.143%;width:25.621%;min-height:12.286%}
    }
/* /FIG */
/* FIG:p4-cotes */
    #p4 .conf{aspect-ratio:1.26374}
    @media (min-width: 1100px) {
      #p4 .pl-ecran{left:26.708%;top:14.769%;width:46.584%}
      #p4 .pl-et--a{left:0.621%;top:1.231%;width:23.292%;min-height:14.154%}
      #p4 .pl-et--b{left:77.019%;top:46.154%;width:22.671%;min-height:15.385%}
      #p4 .pl-et--c{left:0.621%;top:61.538%;width:23.292%;min-height:15.385%}
      #p4 .pl-et--d{left:77.019%;top:9.231%;width:22.671%;min-height:17.231%}
    }
/* /FIG */
/* FIG:p6-cotes */
    #p6 .chat{aspect-ratio:0.73009}
    @media (min-width: 1100px) {
      #p6 .pl-ecran{left:37.189%;top:17.429%;width:25.621%}
      #p6 .pl-et--a{left:0.621%;top:14.286%;width:23.292%;min-height:16.0%}
      #p6 .pl-et--b{left:0.621%;top:54.286%;width:23.292%;min-height:16.0%}
      #p6 .pl-et--c{left:75.776%;top:65.714%;width:23.292%;min-height:16.0%}
      #p6 .pl-et--d{left:75.776%;top:85.714%;width:23.292%;min-height:12.286%}
    }
/* /FIG */
/* FIG:p5-cotes */
    #p5 .apl{aspect-ratio:1.08491}
    @media (min-width: 1100px) {
      #p5 .pl-ecran{left:25.932%;top:12.0%;width:48.137%}
      #p5 .pl-et--a{left:0.621%;top:1.0%;width:22.516%;min-height:14.75%}
      #p5 .pl-et--b{left:77.019%;top:22.0%;width:22.05%;min-height:14.0%}
      #p5 .pl-et--c{left:0.621%;top:62.5%;width:23.292%;min-height:14.0%}
      #p5 .pl-et--d{left:0.621%;top:22.0%;width:23.292%;min-height:13.0%}
    }
/* /FIG */
/* FIG:p7-cotes */
    #p7 .fic{aspect-ratio:0.78125}
    @media (min-width: 1100px) {
      #p7 .pl-ecran{left:30.59%;top:7.368%;width:38.82%}
      #p7 .pl-et--a{left:0.621%;top:12.632%;width:27.174%;min-height:14.737%}
      #p7 .pl-et--b{left:72.205%;top:26.316%;width:27.174%;min-height:14.737%}
      #p7 .pl-et--c{left:0.621%;top:48.947%;width:27.174%;min-height:14.737%}
      #p7 .pl-et--d{left:72.205%;top:62.632%;width:27.174%;min-height:14.737%}
    }
/* /FIG */

    /* ══ LES ÉCRANS RECONSTITUÉS ═════════════════════════════════════════
       Transplantés de frontend/proposition-accueil.html, à l'identique.
       Ils portent leur PROPRE jeu de jetons (--ws-*) et leur propre police :
       ce sont les couleurs du produit, pas celles de la vitrine. */
    .ws {
      --ws-blanc: #ffffff; --ws-filet: #e5dbcf; --ws-filet-fort: #d9cebf;
      --ws-encre: #1d1b19; --ws-doux: #6b645c; --ws-pale: #756a5f;
      --ws-vert: #5d7b60; --ws-vert-encre: #4b644e; --ws-maintenant: #d23b3b;
      --ws-accent: #b83c0c;      /* l'accent DU SALON, pas le nôtre */
      /* LE MÊME ACCENT, MAIS EN ENCRE — et ce n'est pas la même couleur.
         En aplat il ne bascule jamais (la marque du salon est écrite en ligne
         sur la racine, tenant-theme.js:73) et le blanc dessus tient dans les
         deux thèmes, 5,69:1. En ENCRE il se pose sur une surface qui, elle,
         bascule : #b83c0c donnait 5,69:1 sur la carte blanche et 2,56:1 sur
         l'espresso — mesuré, et sous le seuil. Le produit résout exactement ce
         cas et ne l'écrit pas en dur : `poserEncresAccent` (workspace.js:17926)
         CALCULE l'encre par `Contrast.ensureContrast`, qui garde la teinte et
         la saturation de la marque et ne remonte que la clarté. La planche n'a
         qu'un salon : la valeur ci-dessous est ce que ce moteur rend pour
         #b83c0c — en clair il rend l'accent AU PIXEL PRÈS, la nuit #f1612a. */
      --ws-accent-encre: #b83c0c;
      /* Cinq couleurs qui étaient écrites en dur dans les règles. Elles
         deviennent des jetons pour une seule raison : ce qui n'est pas un
         jeton ne bascule pas, et une moitié qui bascule sans l'autre donne
         1,06:1 — du texte invisible, pas un thème sombre. */
      --ws-fond-doux: #f3ede6;   /* --surface-soft (workspace.css:13) */
      --ws-ligne-heure: #f0e9df; /* le filet d'heure de la grille */
      --ws-vert-lavis: rgba(93, 170, 125, .14);
      --ws-ombre: rgba(31, 24, 18, .07);
      --ws-ombre-bloc: rgba(31, 24, 18, .14);
      /* LE TÉLÉPHONE EST UN OBJET, PAS UNE SURFACE À THÈME. Le produit ne
         contient aucun téléphone : ces deux valeurs ne sont donc copiées de
         nulle part, elles sont CHOISIES pour que le boîtier se détache de
         chaque fond — et mesurées comme telles. Un châssis qui suivrait
         `--ws-encre` s'inverserait en crème la nuit ; c'est exactement le
         piège que le commentaire de `.tel` signalait déjà. */
      --tel-cadre: #1d1b19;      /* le cadre noir autour de la dalle */
      --tel-metal: #cfc6ba;      /* le liseré de métal, sur la tranche */
      /* L'îlot n'est pas une surface : c'est un TROU dans la dalle, par où
         regardent les capteurs. Un trou n'a pas de thème — il est noir le
         jour comme la nuit, et c'est pour ça qu'il figure dans IMMUABLES. */
      --tel-ilot: #0a0a0a;
      /* LE SITE DU SALON, qui n'est pas le back-office. Clair = la variante
         `[data-theme="light"]` de style.css:2934 ; sombre = son `:root` nu. */
      --st-fond: #ffffff;        /* --ink-2  (index.html:1358) */
      --st-fond-2: #efe8da;      /* --ink-3  (index.html:1359) */
      --st-encre: #2a251c;       /* --paper  (style.css:2937) */
      --st-doux: #6d6355;        /* --muted  (index.html:1363) */
      --st-filet: #c9beb2;
      --ws-ui: 'Figtree', ui-sans-serif, system-ui, sans-serif;
      font-family: var(--ws-ui); color: var(--ws-encre); text-align: left;
    }

    /* ══ LA NUIT DES PLANCHES ═══════════════════════════════════════════
       Un écran de planche est le PORTRAIT d'un écran du produit : la nuit, il
       doit montrer ce que le produit montre vraiment la nuit, jamais un sombre
       inventé. Chaque valeur ci-dessous est relevée, avec sa ligne.

       ⛔ CE QUI NE BASCULE PAS, ET POURQUOI :
       · `--ws-maintenant` — la ligne rouge de l'heure courante. Vérifié : le
         produit n'a AUCUNE règle sombre dessus (workspace.css:2497 et 2635,
         #d23b3b dans les deux thèmes).
       · `--ws-accent` — la couleur de marque DU SALON. `tenant-theme.js:73`
         l'écrit en style EN LIGNE sur la racine, ce qui bat tout bloc
         `[data-theme]` : la couleur d'un salon ne change pas la nuit.
       · `--tel-cadre` / `--tel-metal` — un téléphone est un objet. Voir
         plus haut : seules valeurs choisies plutôt que relevées. */
    /* ⛔ LA NUIT, LES FENÊTRES PRENNENT LA FAMILLE DE LA VITRINE, PAS CELLE DU
       PRODUIT (5 sept. 2026, demande de Dany : « en thème sombre les fenêtres
       ne donnent pas bien en version espresso — adapte-les à la DA Co-Labs.ch »).
       Jusqu'ici ce bloc recopiait les jetons SOMBRES de workspace.css — un
       espresso brun (#2e281f, filets #473e2f) — au milieu d'une page dont la
       nuit est bleue (#0b1017). Le résultat, mesuré à l'écran : une carte
       brune posée sur du bleu nuit, deux objets qui ne se connaissent pas.
       En clair, les fenêtres gardent les jetons EXACTS du produit — c'est sa
       peau par défaut, et c'est elle qu'on vend. La nuit, la fidélité au pixel
       n'a plus de sens : personne ne compare une capture nocturne, et la page
       doit rester UN objet. Chaque teinte est prise dans la palette sombre de
       cette page (--surface #121926, --line #1f2a3d…), à un cran près pour
       que la carte se lève du fond. Seul l'ACCENT du salon (#b83c0c, terre
       de Sienne) reste ce qu'il est : ce n'est pas notre couleur mais la
       sienne, et sa variante d'encre est recalculée sur la nouvelle carte —
       Contrast.ensureContrast('#b83c0c', '#141c2a', 4.5) → #ed4d0f, 4,61:1.
       Mesuré : encre 15,8:1 · douce 10,9:1 · pâle 6,6:1 · vert 8,1:1. */
    :root[data-theme="dark"] .ws {
      --ws-blanc: #141c2a;       /* la carte : --surface + un cran */
      --ws-filet: #243044;       /* --line + un cran */
      --ws-filet-fort: #34435c;  /* --line-strong */
      --ws-encre: #f1f5fb;       /* --ink */
      --ws-doux: #c7cfdd;        /* --text */
      --ws-pale: #95a2ba;        /* --soft, relevé : 6,6:1 sur la carte */
      --ws-vert: #8fbe93;        /* le vert du produit passe tel quel : 8,1:1 */
      --ws-vert-encre: #8fd0a8;
      --ws-fond-doux: #111827;   /* un cran sous la carte */
      --ws-ligne-heure: #1b2536; /* le filet d'heure, à peine visible : c'est son rôle */
      --ws-vert-lavis: rgba(93, 170, 125, .16);
      --ws-ombre: rgba(0, 0, 0, .45);
      --ws-ombre-bloc: rgba(0, 0, 0, .5);
      /* Le boîtier du téléphone se lève du fond bleu nuit : 1,50:1 contre
         1,26:1 pour l'ancien brun — il était plus invisible qu'avant. */
      --tel-cadre: #26334a; --tel-metal: #6b7a94;
      --ws-accent-encre: #ed4d0f;
      /* La fenêtre du SITE de salon suit la même famille : ses encres crème
         (#f4efe6 sur #1c1916) étaient l'espresso le plus visible de la page. */
      --st-fond: #141c2a;
      --st-fond-2: #1b2536;
      --st-encre: #f1f5fb;
      --st-doux: #a5b0c4;        /* 7,8:1 */
      --st-filet: #34435c;
    }
    /* L'ENCRE DES FLÈCHES DANS L'ÉCRAN. Le calque clippé était figé à #10151c,
       ce qui n'était juste que tant que TOUS les écrans étaient blancs. Le jour
       où ils passent au sombre, cette pointe-là disparaît dessus — et c'est la
       seule des deux encres à ne pas suivre un jeton. */
    :root[data-theme="dark"] .pl-fleches .pl-dans .trait { fill: #f1f5fb; }

    /* ── L'AGENDA ─────────────────────────────────────────────────────── */
    .cal {
      --u-cal:max(0.18cqw, 1.12px);--gout:15.71%; --u: var(--u-cal);
      position: relative; width: 100%;
      background: var(--ws-blanc); border: 1px solid var(--ws-filet);
      border-radius: calc(20 * var(--u));
      box-shadow: 0 calc(16 * var(--u)) calc(40 * var(--u)) var(--ws-ombre);
      display: flex; flex-direction: column; overflow: hidden;
      font-size: calc(13 * var(--u));
    }
    .cal-barre {
      display: flex; align-items: center; justify-content: space-between;
      height: calc(38 * var(--u)); padding-inline: calc(10 * var(--u));
      border-bottom: 1px solid var(--ws-filet); color: var(--ws-doux);
      font-size: calc(11 * var(--u)); gap: calc(10 * var(--u));
    }
    .cal-jour { display: flex; align-items: center; gap: calc(7 * var(--u)); white-space: nowrap; }
    .cal-jour i { font-style: normal; font-weight: 600; color: var(--ws-doux); }
    .cal-jour em { display: inline-flex; align-items: center; gap: calc(4 * var(--u)); font-style: normal; font-weight: 700; color: var(--ws-encre); }
    .cal-jour b { font-weight: 600; font-size: calc(9 * var(--u)); color: var(--ws-doux); }
    .cal-ech { display: flex; align-items: center; gap: calc(2 * var(--u)); border: 1px solid var(--ws-filet); border-radius: calc(7 * var(--u)); padding: calc(2 * var(--u)); }
    .cal-ech em { font-style: normal; font-weight: 600; white-space: nowrap; padding: calc(2 * var(--u)) calc(7 * var(--u)); border-radius: calc(5 * var(--u)); }
    /* L'échelle courante en encre pleine — `.agenda-seg button.active`. */
    .cal-ech em.actif { background: var(--ws-encre); color: var(--ws-blanc); }
    .cal-tete, .cal-corps { display: grid; grid-template-columns: var(--gout) repeat(3, 1fr); }
    .cal-tete { height: calc(58 * var(--u)); border-bottom: 1px solid var(--ws-filet); }
    .cal-emp { display: flex; align-items: center; justify-content: center; gap: calc(7 * var(--u)); border-left: 1px solid var(--ws-filet); font-size: calc(13 * var(--u)); font-weight: 700; white-space: nowrap; min-width: 0; overflow: hidden; padding-inline: calc(4 * var(--u)); }
    /* ⚠️ L'ELLIPSE VIT SUR LE <em>, PAS SUR LE CONTENEUR : `text-overflow` ne
       mord pas sur le nœud de texte ANONYME d'un conteneur flex. Posée sur
       `.cal-emp`, elle ne faisait rien — « David Chappuis » était coupé net au
       bord de la carte, sans les trois points. */
    .cal-emp em { font-style: normal; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
    .cal-qui { display: flex; flex-direction: column; min-width: 0; line-height: 1.24; }
    .cal-qui b { font-weight: 600; font-size: calc(10 * var(--u)); color: var(--ws-pale); font-variant-numeric: tabular-nums; }
    .cal-emp i { width: calc(10 * var(--u)); height: calc(10 * var(--u)); border-radius: 50%; background: var(--c); flex: none; }
    .cal-corps { flex: 1; position: relative; }
    /* La gouttière monte d'un cran : sans ça, `.cal-now` — écrite après elle —
       peignait sa ligne rouge PAR-DESSUS l'étiquette de l'heure courante. */
    .cal-gout { position: relative; z-index: 2; border-right: 1px solid var(--ws-filet); }
    .cal-h { position: absolute; right: calc(7 * var(--u)); transform: translateY(-50%); font-size: calc(10 * var(--u)); font-weight: 600; color: var(--ws-pale); }
    .cal-h.haut { transform: translateY(0); }
    .cal-h.bas { transform: translateY(-100%); }
    .cal-col { position: relative; border-right: 1px solid var(--ws-filet); }
    .cal-col:last-child { border-right: none; }
    .cal-l { position: absolute; left: 0; right: 0; border-top: 1px solid var(--ws-ligne-heure); }
    .cal-rdv, .cal-libre { position: absolute; overflow: hidden; }
    /* LE BLOC PORTE LES DEUX OMBRES DU PRODUIT, et c'est la seconde qui le
       décolle de la colonne : sans elle le rendez-vous est un aplat posé à plat,
       et la grille perd le relief qu'elle a vraiment. */
    .cal-rdv {
      left: calc(4 * var(--u)); right: calc(4 * var(--u));
      border-radius: calc(9 * var(--u)); padding: calc(4 * var(--u)) calc(8 * var(--u));
      background: var(--c); color: #fff; border: 1px solid rgba(255, 255, 255, .45);
      box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .1), 0 calc(5 * var(--u)) calc(14 * var(--u)) var(--ws-ombre-bloc);
    }
    /* Le blanc pur sur les trois couleurs du semeur : mesuré 5,48 · 4,94 · 5,54
       — tous au-dessus de 4,5. La seconde ligne est à .96 et non à .9 : sur le
       vert de Laura Marchetti (#3f7d4e), .9 donne 4,35:1. */
    .cal-rdv b { display: block; font-weight: 800; font-size: calc(12 * var(--u)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    /* L'HEURE NE SE ROGNE PAS, LA PRESTATION SI — c'est la règle du produit,
       et la maquette ne la suivait pas. Là-bas, `.a-time` est en `flex:0 0 auto`
       et seul `.a-svc` porte l'ellipse (workspace.css:2343-2344) : on peut
       toujours lire QUAND, même quand le nom de la prestation ne tient pas.
       Ici la ligne entière était une seule chaîne rognée d'un bloc, si bien que
       « 09:30–10:00 Coupe homme » perdait ses derniers caractères — mesuré à
       16 px près à 1440, 31 px à 1024. */
    .cal-rdv span { display: flex; align-items: baseline; gap: calc(3 * var(--u));
      font-size: calc(10 * var(--u)); color: rgba(255, 255, 255, .96); white-space: nowrap; }
    .cal-rdv span i { font-style: normal; font-weight: 700; flex: 0 0 auto; }
    .cal-rdv span em { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
    .cal-libre {
      left: calc(3 * var(--u)); right: calc(3 * var(--u));
      border: calc(1.5 * var(--u)) dashed var(--ws-vert);
      background: var(--ws-vert-lavis); border-radius: calc(9 * var(--u));
      color: var(--ws-vert-encre); display: flex; flex-direction: column;
      align-items: center; justify-content: center; gap: calc(2 * var(--u));
    }
    .cal-libre b { font-size: calc(11 * var(--u)); font-weight: 800; }
    .cal-libre i { font-size: calc(10 * var(--u)); font-weight: 600; font-style: normal; }
    .cal-libre.mince i { display: none; }
    /* HORS SERVICE — ET CE N'EST PAS UNE INVENTION DE LA MAQUETTE. Le produit
       pose exactement cette bande sur ce qui précède l'arrivée d'une personne
       et ce qui suit son départ (`.agenda-offhours`, posée par
       `workspace.js:13884-13885`, hachurée à 45° dans `workspace.css:2171`).
       La planche l'omettait, et il manquait donc à la fois une VÉRITÉ du
       produit et la seule chose que l'étiquette (d) pouvait désigner : elle
       nomme la coiffeuse qui finit à midi, et sa flèche tombait — mesuré — dans
       la colonne du VOISIN, faute d'avoir quoi que ce soit à toucher.
       ⚠️ LES HACHURES SONT UN MOT DU VOCABULAIRE, PAS UN MOTIF. Dans ce dépôt
       elles disent « place prise » : c'est leur sens ici, et c'est pourquoi
       elles quittent le même jour le cadre photo du téléphone, où elles
       disaient le contraire de ce qu'on y montrait.
       ⚠️ ET LA BANDE NE PORTE AUCUN TEXTE, parce que le produit n'en met pas.
       Elle avait d'abord reçu un « JOURNÉE FINIE » — une invention : le mot
       n'existe que sur une colonne ENTIÈREMENT fermée (« Congé » ou
       « Non planifiée », workspace.js:13934). La bande d'après-service, elle,
       est muette (`:13938`). L'étiquette (d) dit ce qu'elle signifie ; la
       maquette n'a pas à l'écrire dans l'écran. */
    /* ⛔ LE TAUX DE REMPLISSAGE A ÉTÉ POSÉ PUIS RETIRÉ, ET LA RAISON MÉRITE
       D'ÊTRE ÉCRITE. Le produit l'affiche bien à côté des horaires
       (`.agenda-emp-fill`, `workspace.js:13808-13850`) : l'omettre est une
       infidélité, et elle est assumée. Sa formule compte les minutes de TOUTE
       la journée quand la grille n'en montre que cinq heures. Le chiffre exact
       de Sophie — 135/540, soit 25 % — fait lire « salon vide » à un patron qui
       compte son remplissage toutes les semaines ; et un chiffre flatteur
       supposerait des rendez-vous que le dessin ne montre pas. Cette page tient
       toute sa crédibilité de ce qu'on peut y vérifier chaque chose à l'œil :
       un nombre que le lecteur ne peut pas recompter n'y a pas sa place.
       ⚠️ Au passage, l'encre de ce badge dans le produit (#b78558 sur son
       propre lavis) mesure 2,72:1 — sous le seuil AA. Dette relevée en
       cherchant à le reproduire, pas recopiée. */
    .cal-hors {
      position: absolute; left: calc(3 * var(--u)); right: calc(3 * var(--u));
      border-radius: calc(9 * var(--u)); overflow: hidden;
      display: flex; align-items: flex-start; justify-content: center;
      padding-top: calc(9 * var(--u));
      background: repeating-linear-gradient(45deg,
        var(--ws-fond-doux) 0, var(--ws-fond-doux) calc(4 * var(--u)),
        var(--ws-ligne-heure) calc(4 * var(--u)), var(--ws-ligne-heure) calc(8 * var(--u)));
    }
    /* La ligne « maintenant » traverse les COLONNES, et s'arrête au bord de la
       gouttière — son étiquette y reste seule. Ce n'est pas un ajustement de
       goût : le produit l'écrit noir sur blanc, `.agenda-gutter .agenda-now {
       border-top-color: transparent }` (workspace.css:2515), commenté « dans la
       gouttière : pas de trait, seulement l'étiquette de l'heure ».
       La planche, elle, partait de `left: 0` : à 08:06 le trait rouge barrait
       « 08:00 » en plein milieu des chiffres — vu à la loupe, pas déduit. Le
       `z-index: 2` de la gouttière ne protégeait rien : les deux éléments sont
       positionnés dans le même contexte d'empilement. */
    .cal-now { position: absolute; left: var(--gout); right: 0; border-top: calc(2 * var(--u)) solid var(--ws-maintenant); }
    .cal-nowh { position: absolute; left: calc(2 * var(--u)); transform: translateY(-50%); background: var(--ws-maintenant); color: #fff; font-size: calc(10 * var(--u)); font-weight: 700; padding: calc(1 * var(--u)) calc(5 * var(--u)); border-radius: calc(5 * var(--u)); line-height: 1.3; }

    /* ── LE CHAT DU SITE ──────────────────────────────────────────────
       La bulle de Liah, redessinée depuis `setupIaWidget` (frontend/app.js:240).
       ⚠️ UN SEUL MOMENT, ET C'EST UNE CONTRAINTE : le widget efface ses boutons
       de réponse rapide dès qu'on en clique un (`clearSuggestions`,
       app.js:384). Montrer à la fois les boutons ET la carte de confirmation
       peindrait un état que l'écran ne prend jamais. La planche montre donc
       l'instant D'APRÈS la réservation. */
    .chat {
      --u-chat: max(0.30cqw, 1.05px); --u: var(--u-chat);
      width: 100%; height: 100%;
      background: var(--st-fond); border: 1px solid var(--ws-filet);
      border-radius: calc(16 * var(--u));
      box-shadow: 0 calc(16 * var(--u)) calc(40 * var(--u)) var(--ws-ombre);
      display: flex; flex-direction: column; overflow: hidden;
      font-size: calc(12 * var(--u));
    }
    .chat-tete { display: flex; align-items: center; gap: calc(9 * var(--u));
      padding: calc(11 * var(--u)) calc(13 * var(--u));
      border-bottom: 1px solid var(--ws-filet); }
    .chat-ava { flex: none; width: calc(30 * var(--u)); height: calc(30 * var(--u));
      border-radius: 50%; background: var(--ws-accent); color: #fff;
      display: flex; align-items: center; justify-content: center;
      font-weight: 800; font-size: calc(14 * var(--u)); }
    .chat-qui { min-width: 0; line-height: 1.3; }
    .chat-qui b { display: block; font-size: calc(13.5 * var(--u)); font-weight: 800; color: var(--st-encre); }
    .chat-qui span { display: block; font-size: calc(10.5 * var(--u)); color: var(--st-doux); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    /* La pastille verte du produit : « En ligne ». */
    .chat-pt { display: inline-block; width: calc(6 * var(--u)); height: calc(6 * var(--u));
      border-radius: 50%; background: var(--ws-vert); margin-right: calc(5 * var(--u)); }
    .chat-fil { flex: 1; min-height: 0; padding: calc(12 * var(--u)) calc(13 * var(--u));
      display: flex; flex-direction: column; gap: calc(8 * var(--u)); }
    .chat-m { margin: 0; max-width: 82%; align-self: flex-start;
      padding: calc(8 * var(--u)) calc(11 * var(--u)); border-radius: calc(13 * var(--u));
      background: var(--ws-fond-doux); color: var(--st-encre); line-height: 1.45; }
    .chat-m.moi { align-self: flex-end; background: var(--ws-accent); color: #fff; }
    /* LA CARTE DE CONFIRMATION N'APPARAÎT QUE SI L'OUTIL A RÉUSSI : le serveur
       ne la renvoie pas autrement (server.js:18903). C'est la seule preuve, à
       l'écran, qu'un rendez-vous a réellement été écrit. */
    .chat-ok { align-self: stretch; margin-top: calc(2 * var(--u));
      padding: calc(9 * var(--u)) calc(11 * var(--u)); border-radius: calc(11 * var(--u));
      border: 1px solid var(--ws-vert); background: var(--ws-vert-lavis); }
    .chat-ok b { display: block; font-size: calc(11.5 * var(--u)); font-weight: 800; color: var(--ws-vert-encre); }
    .chat-ok span { display: block; margin-top: calc(2 * var(--u)); font-size: calc(10.5 * var(--u)); color: var(--st-doux); }
    .chat-saisie { display: flex; align-items: center; gap: calc(8 * var(--u));
      margin: 0 calc(13 * var(--u)); padding: calc(8 * var(--u)) calc(11 * var(--u));
      border: 1px solid var(--ws-filet); border-radius: calc(11 * var(--u)); }
    .chat-saisie span { flex: 1; min-width: 0; color: var(--ws-pale); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .chat-saisie em { font-style: normal; font-weight: 700; color: var(--ws-accent-encre); white-space: nowrap; }
    .chat-btn { margin: calc(10 * var(--u)) calc(13 * var(--u)) calc(13 * var(--u));
      height: calc(34 * var(--u)); border-radius: calc(10 * var(--u));
      background: var(--ws-accent); color: #fff; font-weight: 700;
      display: flex; align-items: center; justify-content: center; }

    /* ── LE TÉLÉPHONE ET LE SITE DU SALON ─────────────────────────────
       LA POLICE N'EST PAS LA NÔTRE, ET C'EST LE PROPOS : Cormorant Garamond
       pour le nom de la maison, quand toute cette page parle en Georama
       Grotesque. */

    /* `#p1 .cal` et non `.cal` : le rapport de l'écran sort désormais du
       générateur, qui l'écrit préfixé par sa planche — une règle nue perdrait
       contre lui et l'agenda resterait en 1,5 sur un téléphone. */
    @media (max-width: 639px) {
      #p1 .cal { aspect-ratio: .92; --gout: 26%; }
      .cal-rdv span em { display: none; }
      .cal-ech { display: none; }
      .cal-qui b { display: none; }
      .cal-tete { height: calc(46 * var(--u)); }
    }
    @media (max-width: 399px) { .cal-h.haut { display: none; } }

    /* ── LA CAISSE ────────────────────────────────────────────────────
       L'écran d'encaissement du back-office. Chaque hauteur est POSÉE et non
       laissée au contenu : les flèches de la planche visent des fractions de
       la boîte, et une rangée qui grandirait les enverrait à côté. */
    .pos {
      --u: calc(100cqw / 480); width: 100%;
      background: var(--ws-blanc); border: 1px solid var(--ws-filet);
      border-radius: calc(22 * var(--u));
      box-shadow: 0 calc(16 * var(--u)) calc(40 * var(--u)) var(--ws-ombre);
      padding: calc(20 * var(--u)); overflow: hidden;
      display: flex; flex-direction: column; font-size: calc(14 * var(--u));
    }
    /* `color` EXPLICITE, et ce n'est pas une redondance : une règle globale
       `h3 { color: var(--ink) }` bat l'héritage de `.ws`. En thème sombre le
       titre s'écrivait donc en encre CLAIRE sur la carte blanche — 1,09:1,
       illisible (mesuré). Tout texte d'un écran reconstitué doit nommer sa
       couleur, jamais compter sur ce qui coule de la page. */
    .pos h3 { margin: 0; color: var(--ws-encre); height: calc(26 * var(--u)); font-size: calc(17 * var(--u)); font-weight: 700; line-height: calc(26 * var(--u)); }
    .pos-ctx { margin-top: calc(14 * var(--u)); height: calc(38 * var(--u)); display: flex; align-items: center; gap: calc(10 * var(--u)); padding-inline: calc(12 * var(--u)); border-radius: calc(10 * var(--u)); background: var(--ws-fond-doux); border: 1px solid var(--ws-filet); font-size: calc(13 * var(--u)); color: var(--ws-doux); font-weight: 600; }
    .pos-ctx { font-size: calc(14 * var(--u)); }
    .pos-ctx span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .pos-line { margin-top: calc(12 * var(--u)); height: calc(45 * var(--u)); display: flex; align-items: center; gap: calc(8 * var(--u)); border-bottom: 1px solid var(--ws-filet); }
    .pos-line i { width: calc(36 * var(--u)); height: calc(36 * var(--u)); border: 1px solid var(--ws-filet-fort); border-radius: calc(6 * var(--u)); display: flex; align-items: center; justify-content: center; font-style: normal; font-size: calc(15 * var(--u)); color: var(--ws-encre); flex: none; }
    .pos-line .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .pos-tot { margin-top: calc(14 * var(--u)); }
    .pos-tot .row { height: calc(26 * var(--u)); display: flex; justify-content: space-between; align-items: center; }
    .pos-tot .row.grand { height: calc(44 * var(--u)); font-size: calc(18 * var(--u)); font-weight: 700; border-top: 1px solid var(--ws-filet); }
    /* Le blanc sur l'accent DU GARAGE mesure 5,69:1 — au-dessus de AA. Ce
       n'est pas notre accent : le même code affiche de l'or chez un barbier. */
    /* LE PANNEAU DU TERMINAL, et il n'est pas décoratif : « Encaisser »
       enregistre la vente, il n'envoie RIEN au lecteur — c'est `#tm-envoyer`
       « Envoyer au terminal · <montant> » qui le fait (workspace.js:6866), et
       le produit désactive même « Encaisser » tant que le lecteur travaille
       (workspace.js:6737). La flèche « Le montant part au lecteur » visait le
       seul bouton de l'écran qui ne le fait pas. */
    .pos-term { margin-top: calc(10 * var(--u)); padding: calc(9 * var(--u)) calc(11 * var(--u)); border: 1px solid var(--ws-filet-fort); border-radius: calc(9 * var(--u)); display: flex; align-items: center; gap: calc(9 * var(--u)); flex-wrap: wrap; }
    .pos-term span { flex: 1; min-width: calc(150 * var(--u)); font-size: calc(12.5 * var(--u)); font-weight: 600; color: var(--ws-doux); }
    .pos-term b { font-size: calc(12.5 * var(--u)); font-weight: 700; color: var(--ws-accent-encre); white-space: nowrap; }
    .pos-btn { margin-top: calc(14 * var(--u)); height: calc(48 * var(--u)); border-radius: calc(12 * var(--u)); background: var(--ws-accent); color: #fff; display: flex; align-items: center; justify-content: center; font-size: calc(13.2 * var(--u)); font-weight: 700; text-transform: uppercase; letter-spacing: .12em; }

    /* ── LE SITE DU SALON, SUR UN TÉLÉPHONE ───────────────────────────
       LA POLICE N'EST PAS LA NÔTRE, ET C'EST LE PROPOS : un serif pour le nom
       de la maison, quand toute cette page parle en Georama.
       LE CHÂSSIS PUISE DANS LES JETONS DU PRODUIT (--ws-*), jamais dans ceux
       de la page : avec `--encre`/`--papier`, le téléphone s'inversait en
       thème sombre et le site du salon flottait dans le vide. */
    /* ══ UN TÉLÉPHONE, PAS UN RECTANGLE ARRONDI ═════════════════════════
       Demande de Dany : « refais complètement l'écran du téléphone, comme
       s'il était vu sur un iPhone 17 Pro Max, pour l'immersion ». Les quatre
       proportions viennent de la fiche technique, pas du crayon :
         · dalle 2868 × 1320 px          → rapport 2,173
         · boîtier 163,0 × 77,6 mm       → rapport 2,100
         · rayon d'angle 55 pt sur 430   → 12,8 % de la largeur
         · îlot 125 × 36,7 pt sur 440    → 28,4 % × 8,3 % de la largeur
       Et elles se referment : 250 unités de large, bord de 8 (3,2 %), donc
       une dalle de 234 × 508,5 — soit 2,173 au chiffre près. Aucune des
       quatre n'est arrondie « pour faire joli » : c'est ce bouclage-là qui
       fait qu'on reconnaît l'objet sans y penser.
       LE RAYON EST DIT DEUX FOIS parce qu'un pourcentage de `border-radius`
       se lit horizontalement sur la largeur et verticalement sur la hauteur :
       pour un coin ROND sur une boîte deux fois plus haute que large, le
       second nombre vaut le premier divisé par 2,098. */
    .tel {
      /* Le rapport n'est PAS écrit ici : il sort du générateur (`P3_ECRAN`),
         qui l'émet en `#p3 .tel` — plus spécifique, donc gagnant. Une seconde
         écriture ne servirait qu'à diverger. */
      --u: calc(100cqw / 250); width: 100%;
      background: var(--tel-cadre);
      /* ⚠️ LE LISERÉ N'EST PAS UNE BORDURE, ET C'EST MESURÉ. Une `border` de
         1,6 unité vaut 1,615 px à cette taille — le navigateur l'arrondit à
         1 px, et le bord tombait à 2,95 % au lieu de 3,20. Peint en anneau
         intérieur, il ne subit pas cet arrondi et le bord fait ses 8 unités
         pleines : la dalle retrouve exactement son 2,173. */
      box-shadow: inset 0 0 0 calc(1.6 * var(--u)) var(--tel-metal);
      border-radius: 13% / 6.196%;
      padding: calc(8 * var(--u));
      display: flex; overflow: hidden;
    }
    /* LA DALLE REDÉFINIT `--u` SUR ELLE-MÊME, et c'est ce qui permet de
       changer le boîtier sans retoucher une seule règle du site : les huit
       blocs `.site-*` sont écrits en unités de la LARGEUR DE DALLE, 113 comme
       avant. Ils gardent donc exactement leurs proportions ; seule la hauteur
       disponible augmente, le rapport passant de 1,91 à 2,173.
       Rayon intérieur = rayon extérieur moins le bord : 32,5 − 8 = 24,5
       unités de boîtier, soit 10,47 % d'une dalle de 234. */
    .tel-ecran { flex: 1; --u: calc(100cqw / 124); container-type: inline-size;
      position: relative; background: var(--st-fond); border-radius: 10.47% / 4.817%; overflow: hidden; display: flex; flex-direction: column; text-align: left; }
    /* L'ÎLOT : 28,4 % de la largeur de dalle, 8,3 % de haut, posé à 2,5 %
       du bord — les trois nombres de la fiche technique. Il est DANS la
       dalle et par-dessus la page, comme sur l'appareil. */
    .tel-ilot { position: absolute; left: 50%; transform: translateX(-50%);
      top: 2.5%; width: 28.4%; aspect-ratio: 125 / 36.7;
      background: var(--tel-ilot); border-radius: 999px; z-index: 2; }
    /* LA BARRE D'ÉTAT APPARTIENT AU TÉLÉPHONE, PAS AU PRODUIT — c'est
       l'appareil qui l'écrit, et c'est justement ce qui fait qu'on croit à
       l'objet. Elle occupe la marge haute que l'îlot impose de toute façon :
       sur l'appareil, aucune application ne peint sous lui. */
    .tel-etat { display: flex; align-items: center; justify-content: space-between;
      height: calc(15 * var(--u)); padding: 0 calc(9 * var(--u)) 0 calc(11 * var(--u));
      font-size: max(calc(6 * var(--u)), 11px); font-weight: 700; color: var(--st-encre);
      font-variant-numeric: tabular-nums; letter-spacing: .01em; }
    /* La jauge : un contour et son remplissage, rien de plus. Trois pictos
       inventés au crayon seraient trois occasions de se tromper. */
    .tel-pile { position: relative; width: calc(13 * var(--u)); height: calc(6.5 * var(--u));
      border: 1px solid var(--st-doux); border-radius: calc(2 * var(--u)); opacity: .85; }
    .tel-pile::before { content: ''; position: absolute; inset: calc(1.2 * var(--u));
      right: 38%; background: var(--st-encre); border-radius: calc(1 * var(--u)); }
    .tel-pile::after { content: ''; position: absolute; left: 100%; top: 28%; bottom: 28%;
      width: calc(1.4 * var(--u)); background: var(--st-doux); border-radius: 0 999px 999px 0; }
    .site-tete { padding: calc(7 * var(--u)) calc(8 * var(--u)) calc(4 * var(--u)); text-align: center; }
    .site-nom { display: block; font-family: 'Cormorant Garamond', Georgia, serif; font-size: calc(13 * var(--u)); font-weight: 600; line-height: 1.08; color: var(--st-encre); letter-spacing: .01em; }
    /* SA COULEUR, SANS UN MOT. Un filet d'accent sous le nom plutôt qu'une
       ligne de texte : il dit « ses couleurs » aussi bien, et il n'invente
       aucune baseline que ce salon n'a jamais écrite. */
    .site-sous { display: block; width: calc(28 * var(--u)); height: calc(2 * var(--u)); margin: calc(6 * var(--u)) auto 0; background: var(--ws-accent); }
    /* LE CADRE PHOTO HACHURÉ EST PARTI, ET CE N'ÉTAIT PAS UN DÉTAIL. Il
       occupait 65 px de haut sans rien dire, et il le disait mal : dans ce
       produit les hachures à 45° sont la bande « hors service » de l'agenda
       (`.agenda-offhours`, workspace.css:2171). Le seul motif du dépôt qui
       signifie « place prise » tenait donc lieu de photo d'accueil — la
       maquette annonçait le contraire de ce qu'elle voulait montrer. À sa
       place, le fil d'Ariane du vrai tunnel : même hauteur, et il informe. */
    .site-pas { display: flex; justify-content: center; gap: calc(7 * var(--u));
      margin: calc(2 * var(--u)) 0 0; padding: 0; list-style: none; }
    .site-pas li { width: calc(17 * var(--u)); height: calc(17 * var(--u));
      border-radius: 999px; border: 1px solid var(--st-filet);
      display: flex; align-items: center; justify-content: center;
      font-size: calc(8.5 * var(--u)); font-weight: 700; color: var(--st-doux);
      font-family: var(--ws-ui); }
    .site-pas li b { font-weight: 700; }
    /* Une étape franchie porte l'accent DU SALON — c'est sa marque qui avance,
       pas la nôtre. Une étape à venir reste un simple cercle vide. */
    .site-pas li.fait { background: var(--ws-accent); border-color: var(--ws-accent); color: #fff; }
    .site-pas li.ici { border-color: var(--ws-accent); border-width: calc(1.5 * var(--u)); color: var(--st-encre); }
    .site-q { display: block; font-family: 'Cormorant Garamond', Georgia, serif;
      font-size: calc(15 * var(--u)); font-weight: 600; color: var(--st-encre);
      line-height: 1.1; margin-bottom: calc(5 * var(--u)); }
    .site-choix { border: 1px solid var(--st-filet); border-radius: calc(8 * var(--u));
      padding: calc(5 * var(--u)) calc(8 * var(--u)); }
    .site-meta { display: block; margin-top: calc(1 * var(--u)); font-size: calc(6.5 * var(--u));
      font-weight: 600; color: var(--st-doux); line-height: 1.3;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .site-corps { flex: 1; min-height: 0; padding: calc(10 * var(--u)); font-family: var(--ws-ui); }
    .site-svc { display: block; margin-top: calc(3 * var(--u)); font-size: calc(8.4 * var(--u)); font-weight: 700; color: var(--st-encre); line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    /* LA LIGNE LONGUE D'UN AUTRE MÉTIER (19 sept. 2026). Les pages métier
       remplissent cet écran depuis le contenu de départ du produit, et « 1 h 30
       · 480 CHF · avec Anne-Sophie » ou « Diagnostic électronique » dépassaient
       la carte : l'ellipse coupait le prénom que l'étiquette (b) promet. Au-delà
       de 29 signes (la ligne de l'accueil) et de 21, `planches.metier()` pose
       `long` et le corps baisse d'un cran — mesuré : 177 × 6/6,5 = 163 < 166 à
       1440, 196 × 6/6,5 = 181 < 183 à 390 ; 176 × 7,8/8,4 = 163, 194 × 7,8/8,4
       = 180. L'accueil ne pose jamais la classe : rien n'y change. */
    .site-meta.long { font-size: calc(6 * var(--u)); }
    .site-svc.long { font-size: calc(7.8 * var(--u)); }
    /* SIX DÉPARTS, ET PAS UN TROU. La page d'essai en montrait cinq dont deux
       vides et un « déjà pris » : ni l'un ni l'autre n'existe dans le tunnel
       réel — `booking.js` rend une liste de `.slot-btn` sans état sélectionné
       et sans position vide. Les heures non plus n'étaient pas possibles :
       les départs sortent de `sched.start` par pas de `slotStep`
       (server.js:8866), donc 09:00, 09:30, 10:00… jamais 09:07. */
    /* LA BARRE DES JOURS EXISTE DANS LE PRODUIT (`.date-strip` /
       `.date-pill`, style.css:2207 et booking.js:2737) : quatorze pastilles
       défilantes, chacune portant l'abrégé du jour puis le quantième. La
       planche en montre quatre — une simplification, pas une invention.
       ⚠️ LE JOUR RETENU EST LE VENDREDI, ET C'EST DÉLIBÉRÉ. Mettre « jeu. 3 »
       aurait relié cette planche à celle de l'agenda… en la contredisant :
       ce jeudi-là, à 9 h 30, les trois collaborateurs sont pris (Noémie chez
       Olivier, Sandra chez Bastien, Chloé chez Damien), or le téléphone
       propose 09:30. Un jour non montré ailleurs ne peut rien contredire. */
    /* LES QUATRE PASTILLES DOIVENT TENIR, ET CE N'EST PAS UNE COQUETTERIE :
       la quatrième est celle qui porte « fermé ». Avec l'ancienne largeur, elle
       sortait du cadre et l'état que cette planche vient d'apprendre à dire
       n'était visible nulle part. Le vrai bandeau, lui, défile
       horizontalement (`.date-pill{flex:0 0 auto}`, style.css:2212) : ici il
       n'y a pas de doigt pour le faire glisser, donc tout doit être là. */
    .site-jours { display: flex; gap: calc(2 * var(--u)); margin-top: calc(7 * var(--u));
      overflow: hidden; }
    .site-j { flex: 0 0 auto; min-width: calc(30 * var(--u)); display: flex; flex-direction: column; align-items: center;
      gap: calc(1 * var(--u)); padding: calc(5 * var(--u)) calc(0.5 * var(--u));
      border: 1px solid var(--st-filet); border-radius: calc(7 * var(--u));
      font-size: calc(6.6 * var(--u)); line-height: 1.15; white-space: nowrap; }
    .site-j i { font-style: normal; font-weight: 600; color: var(--st-doux); text-transform: lowercase; }
    .site-j b { font-weight: 700; color: var(--st-encre); }
    /* L'ÉTAT PORTE UN MOT, JAMAIS LA COULEUR SEULE. C'est la règle du produit,
       écrite dans son propre balisage (`booking.html:120-123`) et posée pour le
       daltonisme : « un jour fermé est barré, un jour plein est grisé, et les
       deux sont réellement disabled ». La maquette n'avait qu'un demi-ton pour
       les deux cas — elle était moins lisible que l'écran qu'elle dessine, et
       la classe qui marquait le jour CHOISI s'appelait `.pris`, c'est-à-dire
       le contraire de ce qu'elle faisait. La troisième ligne est réservée dans
       les quatre pastilles, vide ou non : sans elle, deux d'entre elles
       seraient plus hautes que les autres. */
    /* LES MOTS SONT CEUX DU PRODUIT, ET LEUR CASSE AUSSI. Il écrit « Complet »
       et « Fermé » (clés `wiz.day.full` / `wiz.day.closed`) — pas en capitales.
       Les mettre en capitales était à la fois moins fidèle et plus large :
       « COMPLET » débordait de sa pastille et se lisait « OMPLET ». */
    .site-etat { display: block; min-height: calc(8 * var(--u)); font-size: calc(6.2 * var(--u));
      font-weight: 700; color: var(--st-doux); }
    .site-j.choisi { background: var(--st-encre); border-color: var(--st-encre); }
    .site-j.choisi i, .site-j.choisi b, .site-j.choisi .site-etat { color: var(--st-fond); }
    .site-j.plein { opacity: .55; }
    .site-j.ferme { opacity: .55; }
    .site-j.ferme b { text-decoration: line-through; }
    .site-heures { display: grid; grid-template-columns: repeat(3, 1fr); gap: calc(5 * var(--u)); margin-top: calc(7 * var(--u)); }
    .site-h { display: flex; align-items: center; justify-content: center; height: calc(20 * var(--u)); border-radius: calc(7 * var(--u)); border: 1px solid var(--st-filet); font-size: calc(9.5 * var(--u)); font-weight: 700; color: var(--st-encre); }
    /* UNE HEURE CHOISIE, ET L'ÉCRAN A ENFIN UN ÉTAT. Six pastilles identiques
       au-dessus d'un bouton « Réserver » ne disaient rien : on ne savait pas si
       la cliente avait choisi, ni ce que le bouton allait valider. C'était le
       premier motif d'incompréhension de cette planche. */
    .site-h.choisie { background: var(--ws-accent); border-color: var(--ws-accent); color: #fff; }
    .site-btn { margin-top: calc(7 * var(--u)); height: calc(22 * var(--u)); border-radius: calc(8 * var(--u)); background: var(--ws-accent); color: #fff; display: flex; align-items: center; justify-content: center; font-size: calc(9.5 * var(--u)); font-weight: 700; font-family: var(--ws-ui); }
    /* LES PÉRIODES DU PANNEAU « POUR QUAND ? » (pages métier de la famille
       « intervention sur site », 18 sept. 2026) : quatre souhaits à la place
       des six heures, sur deux colonnes parce que « Une date précise » ne
       tient pas dans une pastille d'heure. L'accueil ne s'en sert pas ; la
       règle vit ici parce que la feuille des planches est extraite d'ici. */
    .site-periodes { grid-template-columns: repeat(2, 1fr); }
    .site-periodes .site-h { font-size: calc(7.6 * var(--u)); padding: 0 calc(3 * var(--u)); text-align: center; line-height: 1.15; }

    /* ── LA PAGE « CONFIDENTIALITÉ » QUE LE SALON PUBLIE ───────────────
       Ce n'est pas notre page : c'est celle que le produit publie POUR chaque
       boutique (frontend/confidentialite.html), §5 « Transfert hors de
       Suisse ». D'où le nom de la maison en serif — la police du SITE, pas
       celle du back-office — et l'adresse en regard : c'est le sous-domaine
       qui isole le salon (`extractSubdomain`, backend/tenants.js:38). Un apex
       n'en désigne aucun : `hostname === base` retourne null. */
    .conf {
      --u: calc(100cqw / 460); width: 100%;
      background: var(--st-fond); border: 1px solid var(--ws-filet);
      border-radius: calc(18 * var(--u));
      box-shadow: 0 calc(16 * var(--u)) calc(40 * var(--u)) var(--ws-ombre);
      padding: calc(22 * var(--u)); overflow: hidden;
      display: flex; flex-direction: column;
    }
    /* La bordure est COMPRISE dans la hauteur (box-sizing global) : sans ça,
       chaque filet décalerait les lignes d'un pixel. */
    .conf-tete { height: calc(30 * var(--u)); display: flex; align-items: center; justify-content: space-between; gap: calc(10 * var(--u)); border-bottom: 1px solid var(--ws-filet); }
    .conf-nom { font-family: 'Cormorant Garamond', Georgia, serif; font-size: calc(16 * var(--u)); font-weight: 600; color: var(--st-encre); line-height: 1.1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .conf-dom { font-size: max(calc(10.5 * var(--u)), 11px); color: var(--ws-doux); white-space: nowrap; }
    .conf h3 { flex: none; margin-top: calc(12 * var(--u)); height: calc(22 * var(--u)); font-family: 'Cormorant Garamond', Georgia, serif; font-size: calc(15 * var(--u)); font-weight: 700; line-height: calc(20 * var(--u)); color: var(--st-encre); }
    .conf-p { flex: none; margin: calc(8 * var(--u)) 0 0; max-width: none; height: calc(30 * var(--u)); font-size: calc(11.9 * var(--u)); line-height: calc(15 * var(--u)); color: var(--ws-encre); overflow: hidden; }
    /* PAS DE FILET ENTRE LES LIGNES : six séparateurs empilés, c'est très
       exactement le « papier réglé » que cette DA refuse. L'air suffit. */
    .conf-l { flex: none; margin: calc(8 * var(--u)) 0 0; padding: 0; list-style: none; }
    .conf-l li { height: calc(24 * var(--u)); display: flex; align-items: baseline; gap: calc(7 * var(--u)); font-size: calc(12.4 * var(--u)); }
    .conf-l b { font-weight: 700; color: var(--ws-encre); flex: none; }
    .conf-l span { color: var(--ws-doux); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

    /* ── LA FICHE D'UNE CLIENTE ───────────────────────────────────────
       Le panneau du produit est un TIROIR : plus haut que large, et c'est
       pourquoi cette planche a le seul écran vertical du back-office.
       L'ordre des rangées est celui de `workspace.js:16206-16226` — on ne
       range pas les champs du métier en tête pour les mettre en valeur : ils
       arrivent APRÈS l'identité, exactement là où le produit les pose. */
    .fic {
      --u: calc(100cqw / 500); width: 100%;
      background: var(--ws-blanc); border: 1px solid var(--ws-filet);
      border-radius: calc(18 * var(--u));
      box-shadow: 0 calc(16 * var(--u)) calc(40 * var(--u)) var(--ws-ombre);
      padding: calc(22 * var(--u)); overflow: hidden;
      display: flex; flex-direction: column; color: var(--ws-encre);
      font-family: var(--ws-ui);
    }
    .fic-tete { flex: none; display: flex; align-items: baseline; justify-content: space-between;
      gap: calc(10 * var(--u)); padding-bottom: calc(12 * var(--u)); border-bottom: 1px solid var(--ws-filet); }
    .fic-tete b { font-size: calc(20 * var(--u)); font-weight: 800; color: var(--ws-encre); line-height: 1.1; }
    .fic-tete span { font-size: calc(11.5 * var(--u)); color: var(--ws-doux); white-space: nowrap; }
    .fic-meta { flex: none; margin-top: calc(10 * var(--u)); }
    .fic-r { height: calc(25 * var(--u)); display: flex; align-items: center; justify-content: space-between;
      gap: calc(12 * var(--u)); font-size: calc(12.5 * var(--u)); }
    .fic-r > span:first-child { color: var(--ws-doux); flex: none; }
    .fic-r > span:last-child { color: var(--ws-encre); font-weight: 600; min-width: 0;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    /* LES TROIS RANGÉES DU MÉTIER SE DÉTACHENT, et rien de plus : un liseré à
       gauche serait le « side-stripe » que cette DA refuse. Un fond très pâle
       suffit à dire « ces trois-là viennent de votre secteur ». */
    .fic-r.sect { background: var(--ws-fond-doux); margin-inline: calc(-8 * var(--u));
      padding-inline: calc(8 * var(--u)); }
    .fic-r.sect:first-of-type { border-top-left-radius: calc(6 * var(--u)); border-top-right-radius: calc(6 * var(--u)); }
    .fic-r.sect:last-of-type { border-bottom-left-radius: calc(6 * var(--u)); border-bottom-right-radius: calc(6 * var(--u)); }
    .fic-r.alerte > span:last-child { color: var(--ws-accent-encre); font-weight: 700; }
    .fic-h { flex: none; margin: calc(16 * var(--u)) 0 calc(8 * var(--u)); font-size: calc(11 * var(--u));
      font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--ws-doux); }
    .fic-rdv { flex: none; display: flex; flex-direction: column; gap: calc(8 * var(--u)); }
    .fic-a { border: 1px solid var(--ws-filet); border-radius: calc(10 * var(--u));
      padding: calc(9 * var(--u)) calc(11 * var(--u)); }
    .fa-t { display: flex; align-items: center; justify-content: space-between; gap: calc(10 * var(--u)); }
    .fa-t em { font-style: normal; font-weight: 700; font-size: calc(13 * var(--u)); color: var(--ws-encre); }
    .fa-t b { font-size: calc(10 * var(--u)); font-weight: 700; padding: 0 calc(7 * var(--u));
      border-radius: 999px; background: var(--ws-fond-doux); color: var(--ws-doux); white-space: nowrap; }
    .fa-t b.ok { background: var(--ws-vert-lavis); color: var(--ws-vert-encre); }
    .fa-s { display: block; margin-top: calc(3 * var(--u)); font-size: calc(11.5 * var(--u));
      color: var(--ws-doux); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .fa-q { display: block; margin-top: calc(2 * var(--u)); font-size: calc(10.5 * var(--u));
      color: var(--ws-pale); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

    /* ── LE DÉTAIL D'UN APPEL, DANS LE BACK-OFFICE ────────────────────
       CE N'EST PAS LE « RELEVÉ D'APPEL » DE L'ANCIENNE PAGE, qui était une
       invention de la vitrine : le produit rend un appel avec les MÊMES
       classes que le chat web — `chat-msg is-assistant` / `is-user`, le nom
       de qui parle au-dessus, et la liste des actions en dessous
       (workspace.js:7700-7752). On reconstitue donc CE panneau-là, et la
       mention « écran réel du produit, redessiné » redevient vraie.
       LE NOM DE L'APPELANT N'EXISTE PAS ICI. `who` vaut le nom complet OU le
       numéro OU « Numéro masqué » — jamais les deux — et `voice_calls` ne
       reçoit JAMAIS de `client_id` : l'INSERT du décroché ne le liste pas
       (server.js:18937) et aucune des trois UPDATE ne l'écrit. Un appel
       n'affiche donc jamais un patronyme. */
    .apl {
      --u: calc(100cqw / 460); width: 100%;
      background: var(--ws-blanc); border: 1px solid var(--ws-filet);
      border-radius: calc(18 * var(--u));
      box-shadow: 0 calc(16 * var(--u)) calc(40 * var(--u)) var(--ws-ombre);
      padding: calc(20 * var(--u)); overflow: hidden;
      display: flex; flex-direction: column; font-size: calc(12 * var(--u));
    }
    /* LE NOM ET SA LIGNE DE DÉTAIL SONT L'UN SOUS L'AUTRE, comme dans le
       produit : `<h2>` puis `<p class="ws-subtitle">` (workspace.js:7758-7762).
       Côte à côte, ils tenaient tant que le titre était un numéro ; depuis
       qu'il porte le NOM et que la ligne du dessous a récupéré le numéro, ils
       débordaient de 13 px à 390 px de fenêtre (mesuré). La forme fidèle était
       aussi la seule qui tienne. */
    .apl-tete { height: calc(42 * var(--u)); display: flex; flex-direction: column;
      justify-content: center; gap: calc(1 * var(--u)); border-bottom: 1px solid var(--ws-filet); }
    .apl-tete b { font-size: calc(16 * var(--u)); font-weight: 700; font-variant-numeric: tabular-nums; }
    .apl-tete span { font-size: calc(11.5 * var(--u)); color: var(--ws-doux); font-weight: 600;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .apl-fil { flex: 1; min-height: 0; margin-top: calc(12 * var(--u)); display: flex; flex-direction: column; gap: calc(7 * var(--u)); }
    .apl-m { display: flex; flex-direction: column; gap: calc(3 * var(--u)); max-width: 78%; }
    .apl-m.moi { align-self: flex-end; align-items: flex-end; }
    /* 10u ET NON 8,5u : à 8,5 ce libellé mesurait 9,4 px à toutes les
       largeurs de scène — le plus petit texte de la page, sur la planche
       qui demande le plus de lecture. */
    .apl-m i { font-style: normal; font-size: calc(10.5 * var(--u)); font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--ws-pale); }
    .apl-m p { margin: 0; max-width: none; padding: calc(7 * var(--u)) calc(10 * var(--u)); border-radius: calc(11 * var(--u)); background: var(--ws-fond-doux); color: var(--ws-encre); line-height: 1.35; }
    .apl-m.moi p { background: var(--ws-accent); color: #fff; }
    /* LE SILENCE, RENDU EN CREUX. C'est le seul comportement de cet écran qui
       ne peut pas exister en messagerie, et il est borné : une relance
       gratuite, raccrochage au second silence, plafond de quinze tours
       (backend/voice.js, MAX_SILENCES = 2, MAX_TURNS = 15). */
    .apl-m.rien p { background: transparent; border: 1px dashed var(--ws-filet-fort); color: var(--ws-doux); font-style: italic; }
    .apl-h { margin: calc(12 * var(--u)) 0 0; padding-top: calc(10 * var(--u)); border-top: 1px solid var(--ws-filet); font-size: calc(11.5 * var(--u)); font-weight: 700; color: var(--ws-doux); }
    .apl-act { margin: calc(7 * var(--u)) 0 0; padding: 0; list-style: none; }
    .apl-act li { display: flex; align-items: center; gap: calc(8 * var(--u)); font-size: calc(11.5 * var(--u)); font-weight: 600; color: var(--ws-vert-encre); }
    .apl-act li::before { content: ''; width: calc(6 * var(--u)); height: calc(6 * var(--u)); border-radius: 50%; background: var(--ws-vert); flex: none; }

    /* ══ SUR TÉLÉPHONE, LES ÉCRANS REPRENNENT DE LA VOIX ═════════════════
       Toute leur typographie est indexée sur `--u`, une unité de CONTENEUR :
       elle rétrécit linéairement avec la largeur. Mesuré à 390 px de fenêtre
       (écran rendu à 354 px) : la caisse descendait à 9,07 px et le relevé
       d'appel à 6,54 px — illisibles, et sous le plancher que le dépôt
       s'impose. L'agenda avait déjà son garde-fou (`max(0.18cqw, 1.05px)`,
       posé quand il a été transplanté) ; les quatre autres ne l'avaient pas.
       LE PLANCHER S'ACCOMPAGNE DE `aspect-ratio: auto`, et c'est obligatoire :
       les hauteurs de ces écrans sont POSÉES en `--u`, donc un `--u` planchéré
       demande plus de hauteur que le rapport engendré n'en accorde, et le
       contenu déborderait par le bas. La largeur, elle, ne bouge pas
       (`width: 100%`), donc rien ne déborde de côté. */
    @media (max-width: 719px) {
      #p2 .pos  { --u: max(calc(100cqw / 480), 1.18px); aspect-ratio: auto; }
      #p4 .conf { --u: max(calc(100cqw / 460), 1.18px); aspect-ratio: auto; }
      #p5 .apl  { --u: max(calc(100cqw / 460), 1.31px); aspect-ratio: auto; }
      #p3 .tel  { --u: max(calc(100cqw / 136), 1.55px); }
      /* La fiche cliente (7e planche, ajoutée le 3 sept.) n'avait PAS reçu ce
         plancher : à 390 px, 33 de ses 34 textes tombaient sous 10 px (7,1 px
         pour « Confirmé ») — mesuré le 18 sept. Même unité minimale que le
         comptoir, même règle : la largeur ne bouge pas, la hauteur suit. */
      #p7 .fic  { --u: max(calc(100cqw / 500), 1.18px); aspect-ratio: auto; }
    }

    /* LES 86 LIGNES DE `.top` SONT PARTIES (3 sept. 2026). Elles habillaient
       l'en-tête nu de l'accueil — logo seul — et lui seul les portait : posées
       après la sentinelle, elles n'entraient dans aucune coquille. L'accueil
       porte maintenant la MÊME barre que les 141 autres pages, dont le CSS est
       plus haut, dans la zone extraite. Rien ne les lisait plus.
       Ce qu'elles savaient faire et que `.nav` sait déjà : la pastille de
       langue qui glisse (elle existe en double depuis toujours, `.nav
       .colabs-lang::before`), le repli du « .ch » sous 480 px, l'effacement du
       mot « Co-Labs » sous 360 px. Rien n'a été perdu au passage. */